patterns: More accurate variable declarations on nes.hexpat, add n64, gen, and gbx.hexpat (#477)

* Add n64, gen, and gbx.hexpat

* Add n64, gbx, and gen.hexpat to README.md

* Remove leftover string import from n64.hexpat

* More accurate variable declarations on nes.hexpat

* Add source to gbx.hexpat

* Add accidentally missing curly brace in nes.hexpat
This commit is contained in:
gmestanley
2026-01-11 14:59:37 -03:00
committed by GitHub
parent 097ab49cae
commit fb84bbb5d1
5 changed files with 288 additions and 21 deletions

View File

@@ -97,6 +97,8 @@ Everything will immediately show up in ImHex's Content Store and gets bundled wi
| Flipper Zero Settings | | [`patterns/flipper_settings.hexpat`](patterns/flipper_settings.hexpat) | Flipper Zero Settings Files |
| GB | `application/x-gameboy-rom` | [`patterns/gb.hexpat`](patterns/gb.hexpat) | Game Boy ROM |
| GBA | `application/x-gameboy-advance-rom` | [`patterns/gba.hexpat`](patterns/gba.hexpat) | Game Boy Advance ROM header |
| GBX | | [`patterns/gbx.hexpat`](patterns/gbx.hexpat) | GameBoy ROM file GBX footer |
| Gen | | [`patterns/gen.hexpat`](patterns/gen.hexpat) | Sega Genesis/MegaDrive ROM |
| GGUF | | [`patterns/gguf.hexpat`](patterns/gguf.hexpat) | GGML Inference Models |
| GIF | `image/gif` | [`patterns/gif.hexpat`](patterns/gif.hexpat) | GIF image files |
| GLTF | `model/gltf-binary` | [`patterns/gltf.hexpat`](patterns/gltf.hexpat) | GL Transmission Format binary 3D model file |
@@ -136,6 +138,7 @@ Everything will immediately show up in ImHex's Content Store and gets bundled wi
| MSSCMP | | [`patterns/msscmp.hexpat`](patterns/msscmp.hexpat) | Miles Sound System Compressed Archive |
| NACP | | [`patterns/nacp.hexpat`](patterns/nacp.hexpat) | Nintendo Switch NACP files |
| NBT | | [`patterns/nbt.hexpat`](patterns/nbt.hexpat) | Minecraft NBT format |
| N64 | | [`patterns/n64.hexpat`](patterns/n64.hexpat) | Nintendo 64 ROM header |
| NDS | `application/x-nintendo-ds-rom` | [`patterns/nds.hexpat`](patterns/nds.hexpat) | DS Cartridge Header |
| NE | `application/x-ms-ne-executable` | [`patterns/ne.hexpat`](patterns/ne.hexpat) | NE header and Standard NE fields |
| nes | | [`patterns/nes.hexpat`](patterns/nes.hexpat) | Nintendo Entertainment System ROM |

61
patterns/gbx.hexpat Normal file
View File

@@ -0,0 +1,61 @@
#pragma author gmestanley
#pragma description GameBoy ROM file GBX footer
#pragma source hhug.me/gbx/1.0
import gb;
bitfield SachenMMC2SolderPad {
padding : 5;
opt1 : 1;
openBus512KiBOuterBanks : 1;
openBus1MiBOuterBanks : 1;
};
bitfield VastFameRunningValueType {
TaiwanReleases : 1;
MainlandChinaReleases : 1;
};
enum VastFamePCBType : u8 {
DSHGGB81,
BCR1616T3P
};
fn specialMapper(str value) { return value == "SAM2" || value == "VF01" || value == "GB81"; };
struct MapperVariables {
match(parent.mapper) {
("SAM2"): SachenMMC2SolderPad solderPadConfig;
("VF01"): VastFameRunningValueType runningValue;
("GB81"): VastFamePCBType pcb; }
padding[3*specialMapper(parent.mapper)];
u32 mapperVariables[8-specialMapper(parent.mapper)];
};
struct CartridgeInformation {
char mapper[4];
bool battery;
bool rumble;
bool timer;
padding[1];
u32 romSize;
u32 ramSize;
if (specialMapper(mapper))
MapperVariables mapperVariables;
else
MapperVariables mapperVariables [[inline]];
};
struct GBXMetadata {
u32 footerSize;
u32 majorVersion;
u32 minorVersion;
char signature[4];
};
struct GBXFooter {
CartridgeInformation cartInfo;
GBXMetadata metadata;
};
be GBXFooter gbxFooter @ std::mem::size() - 0x40;

137
patterns/gen.hexpat Normal file
View File

@@ -0,0 +1,137 @@
#pragma author gmestanley
#pragma description Sega Genesis/MegaDrive header
#pragma source plutiedev.com/rom-header wiki.neogeodev.org/index.php?title=68k_vector_table
#pragma endian big
import std.string;
struct M68000Vectors {
u32 stackPointerReset;
u32 programCounterReset [[comment("Entry Point")]];
u32 busError;
u32 addressError;
u32 illegalInstruction;
u32 divisionByZero;
u32 chkInstruction;
u32 trapVInstruction;
u32 privilegeViolation;
u32 trace;
u32 lineAInstruction;
u32 lineFInstruction;
padding[12];
u32 uninitializedInterruptVector;
padding[32];
u32 spuriousInterrupt;
u32 interruptAutovectors[7];
u32 traps[16];
};
M68000Vectors vectors @ 0x00;
struct Info {
char softwareType[2];
char space;
char serialNumber[8];
char dash;
char revision[2];
};
enum DeviceType : char {
NotFilled = ' ',
Button3Controller = 'J',
Button6Controller = '6',
MasterSystemController = '0',
AnalogJoystick = 'A',
Multitap = '4',
Lightgun = 'G',
Activator = 'L',
Mouse = 'M',
Trackball = 'B',
Mouse = 'T',
Trackball = 'V',
Keyboard = 'K',
RS232 = 'R',
Printer = 'P',
CDROM = 'C',
FloppyDrive = 'F',
Download = 'D'
};
bitfield RAMType {
addresses : 1;
padding : 3;
bits : 2;
saves : 1;
sig : 1;
};
enum MemoryType : char {
RAM = ' ',
EEPROM = '@'
};
struct ExtraMemory {
char signature[2];
RAMType ramType;
MemoryType memoryType;
u32 startAddress;
u32 endAddress;
};
fn renderMicrophoneType(str value) {
match(value) {
("00"): value = "NoMicJapanOnly";
("10"): value = "MicJapanOnly";
("20"): value = "NoMicOverseasOnly";
("30"): value = "MicOverseasOnly";
("40"): value = "NoMic";
("50"): value = "Mic";
("60"): value = "NoMicJapan";
("70"): value = "NoMicOverseas";
}
};
struct ModemSupport {
char signature[2];
char publisher[4];
char gameNumber[2];
char comma;
char version;
char microphone[2] [[format("renderMicrophoneType")]];
};
enum RegionType : char {
None = ' ',
Japan = 'J',
Americas = 'U',
Europe = 'E'
};
fn formatTerminatedString(str string) {
u8 index;
while (index < std::string::length(string)) {
if (std::mem::read_string($+index, 2) == " ")
break;
index += 1;
}
return "\"" + std::string::substr(string, 0, index) + "\"";
};
struct Header {
char systemType[16] [[format("formatTerminatedString")]];
char copyright[16] [[format("formatTerminatedString")]];
char domesticTitle[48] [[format("formatTerminatedString")]];
char overseasTitle[48] [[format("formatTerminatedString")]];
Info info;
u16 checksum;
DeviceType deviceType[16];
u32 romAddressRange[2];
u32 ramAddressRange[2];
if ($[$] == 'R') ExtraMemory extraMemory; else padding[12];
if ($[$] == 'M') ModemSupport modemSupport; else padding[12];
padding[40];
RegionType regions[3];
};
Header header @ 0x100;

4
patterns/n64.hexpat Normal file
View File

@@ -0,0 +1,4 @@
#pragma author gmestanley
#pragma description Nintendo 64 ROM header
char name[20] @ 0x20;

View File

@@ -124,7 +124,7 @@ enum ExtendedConsoleType : ConsoleType {
VT03,
VT09,
VT32,
VT3xx,
VT36x,
UM6578,
FamicomNetworkSystem
};
@@ -144,8 +144,32 @@ bitfield MiscellaneousROMsHeaderByte {
padding : 6;
};
enum DefaultExpansionDeviceType : u8 {
Unspecified,
StandardControllers,
FourScoreorSatellite,
FamicomFourPlayersAdapter,
VsSystem1P4016h,
VsSystem1P4017h,
MAMEPinballJapan,
VsZapper,
Zapper,
TwoZappers,
BandaiHyperShotLightgun,
PowerPadSideA,
PowerPadSideB,
FamilyTrainerSideA,
FamilyTrainerSideB,
Multicart = 0x2A
};
fn formatDefaultExpansionDevice(u8 value) {
DefaultExpansionDeviceType enumValue = value;
return enumValue;
};
bitfield DefaultExpansionDevice {
defaultExpansionDevice : 6;
defaultExpansionDevice : 6 [[format("formatDefaultExpansionDevice")]];
};
struct NES2Attributes {
@@ -171,16 +195,17 @@ fn renderEOF(str string) {
return "\"NES<EOF>\"";
};
u8 FILLED_NES2_FLAGS = 0b1100;
struct Header {
char identifier[4] [[format("renderEOF")]];
u8 prgROMSizeBy16KiBs;
u8 chrROMSizeBy8KiBs;
Flags flags;
if ($[0x07] & 12 != 4) {
if ($[0x07] & FILLED_NES2_FLAGS != 0b100) {
iNESFlags7 inesFlags7;
if (inesFlags7.nes2Format)
NES2Attributes nes2Attributes;
else if ($[0x07] & 12 == 0 && !std::mem::read_unsigned(0x0C, 4)) {
else if ($[0x07] & FILLED_NES2_FLAGS == 0 && !std::mem::read_unsigned(0x0C, 4)) {
u8 prgRAMSizeBy8KiBs;
iNESFlags9 inesFlags9;
iNESFlags10 inesFlags10;
@@ -191,10 +216,9 @@ struct Header {
Header header @ 0x00;
u8 FILLED_HIGHER_NYBBLE = 0b1111;
u8 FILLED_NES2_FLAGS = 0b1100;
u8 SET_NES2_FLAGS = 0b1000;
u16 mapperValue = (0x0100 * ($[0x08] & FILLED_HIGHER_NYBBLE)) * ($[7] & FILLED_NES2_FLAGS == SET_NES2_FLAGS)
+ (0x10 * ($[0x07] >> 4)) * ($[7] & FILLED_NES2_FLAGS != 0b100)
+ (0x10 * ($[0x07] >> 4)) * ($[0x07] & FILLED_NES2_FLAGS != 0b100)
+ header.flags.lowerMapperNybble;
fn identifyMapper(u16 mapperValue, u8 submapperValue) {
str mapper;
@@ -202,7 +226,7 @@ fn identifyMapper(u16 mapperValue, u8 submapperValue) {
str designation;
match (mapperValue) {
(0): mapper = "No mapper";
(1): mapper = "MMC1B";
(1): mapper = "Nintendo MMC1B";
(2): mapper = "UxROM";
(3): mapper = "CNROM-32";
(4): mapper = "MMC3";
@@ -229,7 +253,7 @@ fn identifyMapper(u16 mapperValue, u8 submapperValue) {
(25): mapper = "Konami VRC4d or VRC2c + VRC4b";
(26): mapper = "Konami VRC6b";
(27): mapper = "World Hero";
(28): mapper = "Action 53, homebrew";
(28): mapper = "InfiniteNESLives' Action 53";
(29): mapper = "RET-CUFROM";
(30): mapper = "UNROM 512";
(31): mapper = "NSF";
@@ -246,8 +270,8 @@ fn identifyMapper(u16 mapperValue, u8 submapperValue) {
(42): mapper = "FDS -> NES Hacks";
(43): mapper = "TONY-I/YS-612";
(44): mapper = "Super Big 7-in-1";
(45): mapper = "GA23C";
(46): mapper = "Lite Star Rumble Station";
(45): mapper = "GA23C"; //TC3294 according to NintendulatorNRS
(46): mapper = "Lite Star's Rumble Station";
(47): mapper = "Nintendo Super Spike V'Ball + NWC";
(48): mapper = "Taito TC0690";
(49): mapper = "Super HIK 4-in-1";
@@ -294,18 +318,40 @@ fn identifyMapper(u16 mapperValue, u8 submapperValue) {
(90): mapper = "J.Y. Company ASIC [ROM nametables & extended mirroring]";
(91): mapper = "J.Y. Company clone boards";
(92): mapper = "Jaleco JF-17 [16 KiB PRG ROM]";
(124): mapper = "Super Game Mega Type III";
(126): mapper = "TEC9719";
(132): mapper = "TXC 05-00002-010";
(173): mapper = "C&E 05-00002-010";
(187): mapper = "Kǎ Shèng A98402";
(256): mapper = "V.R. Technology OneBus";
(269): mapper = "Nice Code Games Xplosion 121-in-1";
(355): mapper = "Jùjīng 3D-BLOCK";
(419): mapper = "Taikee TK-8007 MCU";
(422): mapper = "ING-022";
(423): mapper = "Lexibook Compact Cyber Arcade";
(424): mapper = "Lexibook Retro TV Game Console";
(425): mapper = "Cube Tech Handheld";
(426): mapper = "V.R. Technology OneBus [Serial ROM in GPIO]";
(534): mapper = "ING003C";
(594): mapper = "Rinco FSG2";
(595): mapper = "NES-4MROM-512";
}
match (mapperValue) {
(0): designation = "NROM";
(1): designation = "SxROM";
(4): designation = "TxROM";
(4): {
if (header.prgROMSizeBy16KiBs >= 8 && header.chrROMSizeBy8KiBs >= 16) {
if (($[0x08] != 1 && ($[0x07] & FILLED_NES2_FLAGS == 0 && !std::mem::read_unsigned(0x0C, 4))) ||
($[0x0A]&FILLED_HIGHER_NYBBLE!=7 && ($[0x07] & FILLED_NES2_FLAGS == SET_NES2_FLAGS)))
designation = "TLROM [128~512 KiBs PRG ROM]";
else {
if (header.flags.batterybackedPRGRAM) designation = "TKROM [128~512 KiBs PRG ROM, 8 KiB PRG RAM]";
else designation = "TSROM [128~512 KiBs PRG ROM, 8 KiB PRG RAM, no battery]";
}
}
else if (header.prgROMSizeBy16KiBs == 4) designation = "TBROM [64 KiBs PRG ROM]";
else designation = "TxROM";
}
}
if (mapperValue == 3) {
match (submapperValue) {
@@ -355,6 +401,22 @@ fn identifyMapper(u16 mapperValue, u8 submapperValue) {
(1): submapper = "82-in-1";
}
}
else if (mapperValue == 256 || mapperValue == 419) {
match (submapperValue) {
(0): submapper = "Normal";
(1): submapper = "Waixing VT03";
(2): submapper = "Nice Code VT02";
(3): submapper = "Hummer Technology";
(4): submapper = "Nice Code VT03";
(5): submapper = "Waixing VT02";
(11): submapper = "Vibes";
(12): submapper = "Cheertone";
(13): submapper = "Cube Tech";
(14): submapper = "Unknown developer (Publisher: Karaoto)";
(15): submapper = "JungleTac Fuzhou";
}
submapper += " wiring";
}
std::print("Mapper: " + mapper + " (" + std::string::to_string(mapperValue) + ")");
if (submapper) std::print("Submapper: " + submapper + " (" + std::string::to_string(submapperValue) + ")");
if (designation) std::print("Designation: " + designation);
@@ -422,7 +484,7 @@ enum EncodingType : u8 {
fn titleLength(u8 value) { return value+1; };
struct OfficialHeader {
struct Footer {
char title[16] [[hex::spec_name("Title Registration Area")]];
u16 programChecksum;
u16 characterChecksum;
@@ -431,7 +493,7 @@ struct OfficialHeader {
EncodingType encodingType [[hex::spec_name("Registration Characters Type Distinction")]];
u8 titleLength [[hex::spec_name("Registration Characters Count"), transform("titleLength")]];
u8 licenseeID [[hex::spec_name("Maker Code")]];
u8 complementaryChecksum [[hex::spec_name("Checksum for characterChecksum~makerID")]];
u8 complementaryChecksum [[hex::spec_name("Checksum for Character Checksum~Maker Code")]];
};
u16 PRGROM_MINIMUM_SIZE = 16384;
@@ -439,7 +501,7 @@ u8 LOWER_TWO_DIGITS = 0b11;
u32 calculatedPRGROMSize = (std::mem::size()-16-(4096*($[0x0E]&LOWER_TWO_DIGITS))) * ($[0x09]&FILLED_HIGHER_NYBBLE==0x0F)
+ (PRGROM_MINIMUM_SIZE * ((0x0100 * ($[0x09] & FILLED_HIGHER_NYBBLE)) * ($[0x07] & FILLED_NES2_FLAGS == SET_NES2_FLAGS) + header.prgROMSizeBy16KiBs)) * ($[0x09]&FILLED_HIGHER_NYBBLE!=0x0F);
fn hasOfficialHeader() {
fn hasFooter() {
u8 sum;
for (u8 i = 0, i < 8, i += 1) {
sum += $[(calculatedPRGROMSize - 14) + i];
@@ -447,13 +509,13 @@ fn hasOfficialHeader() {
return !sum;
};
u8 OFFICIAL_HEADER_SIZE = 26;
u8 FOOTER_SIZE = 26;
u8 VECTORS_SIZE = 6;
struct PRGROM {
u8 data[calculatedPRGROMSize - OFFICIAL_HEADER_SIZE * hasOfficialHeader() - VECTORS_SIZE];
if (hasOfficialHeader())
OfficialHeader officialHeader;
u8 data[calculatedPRGROMSize - FOOTER_SIZE * hasFooter() - VECTORS_SIZE];
if (hasFooter())
Footer footer;
u16 nmi;
u16 resetVector [[comment("Entry Point")]];
u16 externalIRQ;
@@ -470,8 +532,8 @@ struct MiscellaneousROMs {
u8 decryptionDataPROM[16];
u8 decryptionCounterOutPROM[16];
}
else if ($[0x0D] == ExtendedConsoleType::VT3xx) {
u8 embeddedROM[4096];
else if ($[0x0D] == ExtendedConsoleType::VT369) {
u8 embeddedROM[std::mem::size()-calculatedCHRROMSize-calculatedPRGROMSize-sizeof(trainer)-0x10];
}
else if (mapperValue == 355) {
u8 antiCloningROM[1024];