From fb84bbb5d1c6f3d07674b19c4a8e88e17de4c522 Mon Sep 17 00:00:00 2001 From: gmestanley Date: Sun, 11 Jan 2026 14:59:37 -0300 Subject: [PATCH] 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 --- README.md | 3 + patterns/gbx.hexpat | 61 ++++++++++++++++++++ patterns/gen.hexpat | 137 ++++++++++++++++++++++++++++++++++++++++++++ patterns/n64.hexpat | 4 ++ patterns/nes.hexpat | 104 ++++++++++++++++++++++++++------- 5 files changed, 288 insertions(+), 21 deletions(-) create mode 100644 patterns/gbx.hexpat create mode 100644 patterns/gen.hexpat create mode 100644 patterns/n64.hexpat diff --git a/README.md b/README.md index 40d9e02..a53c40b 100644 --- a/README.md +++ b/README.md @@ -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 | diff --git a/patterns/gbx.hexpat b/patterns/gbx.hexpat new file mode 100644 index 0000000..428f490 --- /dev/null +++ b/patterns/gbx.hexpat @@ -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; diff --git a/patterns/gen.hexpat b/patterns/gen.hexpat new file mode 100644 index 0000000..8dca21f --- /dev/null +++ b/patterns/gen.hexpat @@ -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; \ No newline at end of file diff --git a/patterns/n64.hexpat b/patterns/n64.hexpat new file mode 100644 index 0000000..3eb8cc7 --- /dev/null +++ b/patterns/n64.hexpat @@ -0,0 +1,4 @@ +#pragma author gmestanley +#pragma description Nintendo 64 ROM header + +char name[20] @ 0x20; diff --git a/patterns/nes.hexpat b/patterns/nes.hexpat index 6159a9a..9574f0f 100644 --- a/patterns/nes.hexpat +++ b/patterns/nes.hexpat @@ -124,7 +124,7 @@ enum ExtendedConsoleType : ConsoleType { VT03, VT09, VT32, - VT3xx, + VT36x, UM6578, FamicomNetworkSystem }; @@ -143,9 +143,33 @@ bitfield MiscellaneousROMsHeaderByte { numberOfMiscellaneousROMs : 2; 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\""; }; +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];