patterns: Improvements to NES & IPS, add SNES, NSF, NSFe (#455)

* Add credit to ne.hexpat

* Add many changes to nes.hexpat

* Fixing dependance on variables declared in if statement

* Added mappers and inline to NES 2.0 header, removed needless parenthesises

* Add files via upload

* Add files via upload

* Create nsf.hexpat

* Used full name of the SNES on description

* Add SNES, NSF & NSFe, new description for NES

* Removing erroneous condition in ips.hexpat's truncatedSize

* Removing unnecessary std.string import in ips.hexpat

* Added both locations for sections in PE, clearer variable names, reorganized DOS stub

* Delete patterns/nsfe.hexpat

* Delete patterns/nsfmetadata.hexpat

* Added chunks from NSFe to NSF

* Added NSFe

* Fix size of truncatedSize in ips.hexpat

---------

Co-authored-by: Nik <werwolv98@gmail.com>
This commit is contained in:
gmestanley
2025-12-05 17:15:50 -03:00
committed by GitHub
parent a525160243
commit 0d8bd76c2c
7 changed files with 1357 additions and 942 deletions

View File

@@ -123,7 +123,9 @@ Everything will immediately show up in ImHex's Content Store and gets bundled wi
| NBT | | [`patterns/nbt.hexpat`](patterns/nbt.hexpat) | Minecraft NBT format |
| 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) | .nes file format |
| nes | | [`patterns/nes.hexpat`](patterns/nes.hexpat) | Nintendo Entertainment System ROM |
| NSF | | [`patterns/nsf.hexpat`](patterns/nsf.hexpat) | NES Sound Format |
| NSFe | | [`patterns/nsfe.hexpat`](patterns/nsfe.hexpat) | NES Sound Format extended |
| NotepadCache | | [`patterns/notepad-cache.hexpat`](patterns/notepad-cache.hexpat) | Windows Notepad Cache |
| NotepadStateFile | | [`patterns/notepad-state.hexpat`](patterns/notepad-state.hexpat) | Windows Notepad .bin State files |
| NotepadWindowState | | [`patterns/notepadwindowstate.hexpat`](patterns/notepadwindowstate.hexpat) | Windows 11 Notepad - Window State .bin file |
@@ -169,6 +171,7 @@ Everything will immediately show up in ImHex's Content Store and gets bundled wi
| SHR | | [`patterns/SHR.hexpat`](patterns/SHR.hexpat) | Apple IIgs Super Hi-Res (SHR) + PaintWorks Animation (ANI) |
| shx | | [`patterns/shx.hexpat`](patterns/shx.hexpat) | ESRI index file |
| smk | | [`patterns/smk.hexpat`](patterns/smk.hexpat) | Smacker video file |
| SNES | | [`patterns/snes.hexpat`](patterns/snes.hexpat) | Super Nintendo Entertainment System ROM header |
| sup | | [`patterns/sup.hexpat`](patterns/sup.hexpat) | PGS Subtitle |
| SPIRV | | [`patterns/spirv.hexpat`](patterns/spirv.hexpat) | SPIR-V header and instructions |
| STDF | | [`patterns/stdfv4.hexpat`](patterns/stdfv4.hexpat) | Standard test data format for IC testers |

View File

@@ -4,7 +4,6 @@
#pragma endian big
import std.mem;
import std.string;
struct Hunk {
@@ -23,7 +22,7 @@ struct IPS {
char signature[5];
Hunk hunks[while($ < std::mem::size() - (3 + 3 * (std::mem::read_string(std::mem::size()-3, 3) != "EOF")))];
char eof[3];
u24 truncatedSize[3+(std::mem::read_string(std::mem::size()-3, 3) != "EOF")>3];
u24 truncatedSize[std::mem::read_string(std::mem::size()-3, 3) != "EOF"];
};
IPS ips @ 0x00;

View File

@@ -145,7 +145,7 @@ struct NES2Attributes {
}
MiscellaneousROMs miscellaneousROMs;
DefaultExpansionDevice defaultExpansionDevice;
};
} [[inline]];
fn renderEOF(str string) {
return "\"NES<EOF>\"";
@@ -243,7 +243,7 @@ struct OfficialHeader {
u8 complementaryChecksum [[hex::spec_name("Checksum for characterChecksum~makerID")]];
};
u24 calculatedPRGROMSize = 16384 * ((0x0100 * $[9] & 0x0F) * ($[7] & 12 == 8) + header.prgROMSizeBy16KiBs);
u24 calculatedPRGROMSize = 16384 * ((0x0100 * ($[9] & 0x0F)) * ($[7] & 12 == 8) + header.prgROMSizeBy16KiBs);
fn hasOfficialHeader() {
u8 sum;
@@ -263,4 +263,145 @@ struct PRGROM {
};
PRGROM prgROM @ 0x10 + sizeof(trainer);
u8 chrROM[8192 * ((0x0100 * $[9] >> 4) * ($[7] & 12 == 8) + header.chrROMSizeBy8KiBs)] @ addressof(prgROM) + 16384 * header.prgROMSizeBy16KiBs;
u8 chrROM[8192 * ((0x0100 * ($[9] >> 4)) * ($[7] & 12 == 8) + header.chrROMSizeBy8KiBs)] @ addressof(prgROM) + 16384 * header.prgROMSizeBy16KiBs;
fn identifyMapper(u16 mapperValue, u8 submapperValue) {
str mapper;
str submapper;
str designation;
match (mapperValue) {
(0): mapper = "NROM";
(1): mapper = "MMC1B";
(2): mapper = "UxROM";
(3): mapper = "CNROM-32";
(4): mapper = "MMC3";
(5): mapper = "MMC5";
(6): mapper = "Front Fareast Magic Card 1/2M RAM Cartridge";
(7): mapper = "AxROM";
(8): mapper = "Front Fareast Magic Card 1/2M RAM Cartridge (Initial latch-based banking mode 4)";
(9): mapper = "MMC2";
(10): mapper = "MMC4";
(11): mapper = "Color Dreams";
(12): mapper = "(See submapper)";
(13): mapper = "CPROM";
(14): mapper = "SL-1632";
(15): mapper = "K-102xx";
(16): mapper = "Bandai FCG boards";
(17): mapper = "Front Fareast Super Magic Card RAM cartridge";
(18): mapper = "SS88006 (Jaleco)";
(19): mapper = "Namco 129/163";
(20): mapper = "Famicom Disk System";
(21): mapper = "VRC4a/VRC4c";
(22): mapper = "VRC2a";
(23): mapper = "VRC4e or VRC2b + VRC4f";
(24): mapper = "VRC6a";
(25): mapper = "VRC4d or VRC2c + VRC4b";
(26): mapper = "VRC6b";
(27): mapper = "World Hero";
(28): mapper = "Action 53";
(29): mapper = "RET-CUFROM";
(30): mapper = "UNROM 512";
(31): mapper = "NSF";
(32): mapper = "G-101";
(33): mapper = "TC0190";
(34): mapper = "(See submapper)";
(35): mapper = "J.Y. Company (8KiB WRAM)";
(36): mapper = "01-22000-400";
(37): mapper = "SMB+Tetris+NWC";
(38): mapper = "Crime Busters";
(39): mapper = "Study & Game 32-in-1";
(40): mapper = "NTDEC 27xx";
(41): mapper = "Caltron 6-in-1";
(42): mapper = "FDS -> NES Hacks";
(43): mapper = "TONY-I/YS-612";
(44): mapper = "Super Big 7-in-1";
(45): mapper = "GA23C";
(46): mapper = "Rumble Station";
(47): mapper = "Super Spike V'Ball + NWC";
(48): mapper = "TC0690";
(49): mapper = "Super HIK 4-in-1";
(50): mapper = "761214";
(51): mapper = "11-in-1 Ball Games";
(52): mapper = "Realtec 8213";
(53): mapper = "Supervision 16-in-1";
(54): mapper = "Novel Diamond 9999999-in-1";
(55): mapper = "QFJ";
(56): mapper = "KS202";
(57): mapper = "GK";
(58): mapper = "WQ";
(59): mapper = "T3H53";
(60): mapper = "Reset-based NROM-128 4-in-1";
(61): mapper = "(See submapper)";
(62): mapper = "Super 700-in-1";
(63): mapper = "(See submapper)";
(64): mapper = "RAMBO-1";
(65): mapper = "H3001";
(66): mapper = "GxROM";
(67): mapper = "Sunsoft-3";
(68): mapper = "Sunsoft-4";
(69): mapper = "Sunsoft FME-7/Sunsoft 5A/Sunsoft 5B";
(70): mapper = "Family Trainer Mat";
(71): mapper = "Camerica";
(72): mapper = "JF-17";
(73): mapper = "VRC3";
(74): mapper = "860908C";
(75): mapper = "VRC1";
(76): mapper = "NAMCOT-3446";
(77): mapper = "Napoleon Senki";
(78): mapper = "74HC161/32";
(79): mapper = "NINA-003/NINA-006";
(80): mapper = "X1-005";
(81): mapper = "N715021";
(82): mapper = "X1-017";
}
if (mapperValue == 3) {
match (submapperValue) {
(0): submapper = "Bus conflict";
(1): submapper = "No bus conflicts";
(2): submapper = "AND-type bus conflicts";
}
}
else if (mapperValue == 4) designation = "TxROM";
else if (mapperValue == 12) {
match (submapperValue) {
(0): submapper = "SL-5020B (Gouder)";
(1): submapper = "Front Fareast Magic Card 4M RAM Cartridge";
}
}
else if (mapperValue == 16) {
match (submapperValue) {
(0): submapper = "FCG-1/2 or LZ93D50";
(4): submapper = "FCG-1/2";
(5): submapper = "LZ93D50";
}
}
else if (mapperValue == 34) {
match (submapperValue) {
(0): submapper = "NINA-001/NINA-002";
(1): submapper = "BNROM";
}
}
else if (mapperValue == 40) {
match (submapperValue) {
(0): submapper = "NTDEC 2722";
(1): submapper = "NTDEC 2752";
}
}
else if (mapperValue == 61) {
match (submapperValue) {
(0): submapper = "NTDEC 0324";
(1): submapper = "NTDEC BS-N032";
(_): submapper = "GS-2017";
}
}
else if (mapperValue == 63) {
match (submapperValue) {
(0): submapper = "TH2291-3";
(1): submapper = "82AB";
}
}
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);
};
identifyMapper(0x0100 * ($[8] & 0x0F) + 0x10 * ($[7] & 0x0F) + header.flags.lowerMapperNybble, $[8] >> 4);

109
patterns/nsf.hexpat Normal file
View File

@@ -0,0 +1,109 @@
#pragma author gmestanley
#pragma description NES Sound Format file
import std.string;
struct ChunkMetadata {
u32 length;
char ID[4];
} [[inline]];
struct TimeChunkData {
u32 trackLengths[while($<addressof(parent.metadata)+6+parent.metadata.length)];
} [[inline]];
fn formatMetadataName(str string) {
return "\"" + std::string::substr(string, 0, sizeof(string)-1) + "\"";
};
struct Name {
char trackName[] [[format("formatMetadataName")]];
};
struct TlblChunkData {
Name trackNames[while($<addressof(parent.metadata)+6+parent.metadata.length)];
} [[inline]];
struct AuthChunkData {
char gameTitle[] [[format("formatMetadataName")]];
char songwriting[] [[format("formatMetadataName")]];
char copyright[] [[format("formatMetadataName")]];
char ripper[] [[format("formatMetadataName")]];
} [[inline]];
struct TextChunkData {
char text[parent.metadata.length];
} [[inline]];
struct Chunk {
ChunkMetadata metadata;
if (metadata.ID == "time") TimeChunkData timeChunkData;
else if (metadata.ID == "text") TextChunkData textChunkData;
else if (metadata.ID == "tlbl") TlblChunkData tlblChunkData;
else if (metadata.ID == "auth") AuthChunkData authChunkData;
else u8 data[metadata.length];
};
bitfield NSF2Flags {
padding : 4;
irq : 1;
nonReturningInitNotUsed : 1;
playSubroutineNotUsed : 1;
playbackNSFeChunk : 1;
};
bitfield Region {
isPAL : 1;
palNTSCDual : 1;
};
enum ExtraSoundChipSupport : u8 {
None,
VRC6,
VRC7,
FDS = 4,
MMC5 = 8,
Namco163 = 16,
Sunsoft5B = 32,
VTxx = 64
};
fn formatName(str string) {
for (u8 nameIndex = 0, nameIndex < 32, nameIndex += 1) {
if (!$[$+nameIndex] || nameIndex == 31)
return "\"" + std::string::substr(string, 0, nameIndex) + "\"";
}
};
fn renderEOF(str value) {
return "\"NESM<EOF>\"";
};
struct Header {
char signature[5] [[format("renderEOF")]];
u8 version;
u8 songAmount;
u8 startingSong;
u16 dataLoadAddress;
u16 dataInitAddress;
u16 dataPlayAddress;
char gameName[32] [[format("formatName")]];
char songwriting[32] [[format("formatName")]];
char copyrightHolder[32] [[format("formatName")]];
u16 ntscPlaySpeed;
u8 bankswitchInitValues[8];
u16 palPlaySpeed;
Region region;
ExtraSoundChipSupport extraSoundChipSupport;
NSF2Flags nsf2flags;
u24 dataLength;
};
Header header @ 0x00;
struct Chunks {
if (header.dataLength)
NSFEChunk chunks[while($<std::mem::size())];
};
Chunks metadata @ 0x80+header.dataLength;

48
patterns/nsfe.hexpat Normal file
View File

@@ -0,0 +1,48 @@
#pragma author gmestanley
#pragma description NSFe file format
import std.string;
struct ChunkMetadata {
u32 length;
char ID[4];
} [[inline]];
struct TimeChunkData {
u32 trackLengths[while($<addressof(parent.metadata)+6+parent.metadata.length)];
} [[inline]];
fn formatMetadataName(str string) {
return "\"" + std::string::substr(string, 0, sizeof(string)-1) + "\"";
};
struct Name {
char trackName[] [[format("formatMetadataName")]];
};
struct TlblChunkData {
Name trackNames[while($<addressof(parent.metadata)+6+parent.metadata.length)];
} [[inline]];
struct AuthChunkData {
char gameTitle[] [[format("formatMetadataName")]];
char songwriting[] [[format("formatMetadataName")]];
char copyright[] [[format("formatMetadataName")]];
char ripper[] [[format("formatMetadataName")]];
} [[inline]];
struct TextChunkData {
char text[parent.metadata.length];
} [[inline]];
struct Chunk {
ChunkMetadata metadata;
if (metadata.ID == "time") TimeChunkData timeChunkData;
else if (metadata.ID == "text") TextChunkData textChunkData;
else if (metadata.ID == "tlbl") TlblChunkData tlblChunkData;
else if (metadata.ID == "auth") AuthChunkData authChunkData;
else u8 data[metadata.length];
};
char signature[4] @ 0x00;
Chunk chunks[while($<std::mem::size())] @ 0x04;

View File

@@ -5,11 +5,33 @@
#pragma MIME application/x-msdownload
#pragma MIME application/vnd.microsoft.portable-executable
import hex.dec;
import std.core;
import std.string;
import type.guid;
import type.time;
fn formatNullTerminatedString(str string) {
return "\"" + std::string::substr(string, 0, std::string::length(string)-1) + "\"";
};
s16 dosMessageOffset;
struct DOSProgramData {
u8 code[while($ < addressof(this) + dosMessageOffset)];
// $ is the character that ends text on DOS
char message[while(std::mem::read_string($-1, 1) != "$")] @ addressof(this) + dosMessageOffset [[format("formatNullTerminatedString")]];
} [[hex::spec_name("e_program")]];
fn finddosmessage() {
for (u8 cursor = $, cursor < $+16, cursor += 1) {
if ($[cursor] == 0xBA) { // Message offset instruction
dosMessageOffset = std::mem::read_unsigned(cursor+1, 2);
break;
}
}
};
struct DOSHeader {
char signature[2] [[hex::spec_name("e_magic")]];
u16 extraPageSize [[hex::spec_name("e_cblp")]];
@@ -32,33 +54,15 @@ struct DOSHeader {
u32 coffHeaderPointer [[hex::spec_name("e_lfanew")]];
};
u16 dosMessageOffset;
fn isstubdata(char c) {
return c == 0x0D || c == 0x0A || c == '$';
};
struct DOSStub {
u8 code[while($ < addressof(this) + dosMessageOffset)];
char message[while(!isstubdata(std::mem::read_unsigned($, 1)))];
char data[while(std::mem::read_string($-1, 1) != "$")];
} [[hex::spec_name("e_program")]];
fn finddosmessage() {
for (u8 i = $, i < $+16, i += 1) {
if (std::mem::read_unsigned(i, 1) == 0xBA) { // Message offset instruction
dosMessageOffset = std::mem::read_unsigned(i+1, 2);
break;
}
}
};
struct PEHeader {
DOSHeader dosHeader;
finddosmessage();
if (!dosMessageOffset)
u8 dosStub[while(std::mem::read_unsigned($, 4))] [[hex::spec_name("e_program")]];
else DOSStub dosStub;
/* Some newer executables have garbage data instead. Four 00
characters is when 00 (null) stops being part of it instead
of being the threshold, so it can be used as a limit */
u8 dosProgramData[while(std::mem::read_unsigned($, 5))] [[hex::spec_name("e_program")]];
else DOSProgramData dosProgramData;
};
PEHeader peHeader @ 0x00;
@@ -254,7 +258,7 @@ enum AlignmentType : u8 {
};
fn formatAlignmentBits(u8 value) {
if (value > 0) {
if (value) {
AlignmentType enumValue = value;
return enumValue;
}
@@ -325,10 +329,6 @@ fn wordsize() {
return std::mem::read_unsigned(addressof(coffHeader.optionalHeader.magic), 2) / 0x41;
};
fn formatNullTerminatedString(str string) {
return "\"" + std::string::substr(string, 0, std::string::length(string)-1) + "\"";
};
// Exception Table
bitfield FunctionBitfield {
prologLength : 8;
@@ -408,8 +408,15 @@ bitfield OrdinalFlagByte {
flag : 1 [[name("ordinalFlag")]];
};
fn formatMangledString(str string) {
return hex::dec::demangle(formatNullTerminatedString(string));
};
struct ImportsName {
u16 hint;
if (std::mem::read_string($, 1) == "?")
char name[] [[format("formatMangledString")]];
else
char name[] [[format("formatNullTerminatedString")]];
if ($ % 2 == 1) { u8 pad; }
};
@@ -679,14 +686,14 @@ struct VersionEntryHeader {
u16 valueLength;
u16 type;
char16 key[] [[format("formatNullTerminatedString16")]];
padding[while(std::mem::read_unsigned($, 1) == 0 && $ < addressof(key)+sizeof(key)+5)];
padding[while(!$[$] && $ < addressof(key)+sizeof(key)+5)];
};
struct StringInfo {
VersionEntryHeader stringInfoHeader;
if (stringInfoHeader.valueLength > 0) {
char16 string[] [[format("formatNullTerminatedString16")]];
padding[while(std::mem::read_unsigned($, 1) == 0)];
padding[while(!$[$])];
}
};
@@ -702,7 +709,7 @@ struct VersionEntry {
}
else {
u8 value[header.valueLength];
padding[while(std::mem::read_unsigned($, 1) == 0)];
padding[while(!$[$])];
}
};
@@ -723,15 +730,15 @@ struct Version {
// * Resources Using TrueChar
fn displayTrueChar(u8 value) {
char chr = char(value);
str notation = "0x";
if (value < 0x10)
notation += "0";
if (value == 0)
return "'␀' (" + std::format(notation + "{:X}", value) + ")";
else
return "'" + char(value) + "' (" + std::format(notation + "{:X}", value) + ")";
if (value < 0x10) notation += "0";
if (!value) chr = "";
return "'" + chr + "' (" + std::format(notation + "{:X}", value) + ")";
};
using TrueChar = u8 [[format("displayTrueChar")]];
// Resource Table
enum ResourceID : u32 {
Cursor = 0x01,
@@ -749,8 +756,6 @@ enum ResourceID : u32 {
};
ResourceID resourceIDType;
using TrueChar = u8 [[format("displayTrueChar")]];
struct DataEntry {
u32 dataRVA;
u32 size;
@@ -798,7 +803,7 @@ bitfield OffsetField {
};
struct DataField {
if (std::mem::read_unsigned($+3, 1) >= 0x80) {
if ($[$+3] >= 0x80) {
OffsetField offsetToData;
ResourceDirectory directory @ coffHeader.optionalHeader.directories[2].rva - relativeVirtualDifference() + offsetToData.offset;
}
@@ -972,8 +977,7 @@ enum DebugType : u32 {
OmapToSRC,
OmapFromSRC,
Borland,
Reserved10,
CLSID,
CLSID = 11,
REPRO = 16,
ExtendedDLLCharacteristics = 20
};
@@ -1056,7 +1060,7 @@ fn noDataDirectories() {
return true;
};
fn clearBoolArray() {
fn clearDirectoryChecks() {
for (u32 i = 0, i < coffHeader.optionalHeader.numberOfRVAsAndSizes, i += 1)
dataDirectoryInSection[i] = false;
};
@@ -1098,7 +1102,7 @@ struct Section {
DelayedImportsTable delayedImportTable @ coffHeader.optionalHeader.directories[13].rva - relativeVirtualDifference();
}
}
clearBoolArray();
clearDirectoryChecks();
LineNumber lineNumbers[sectionsTable[currentSectionIndex].numberOfLineNumbers] @ sectionsTable[currentSectionIndex].ptrLineNumbers;
@@ -1110,7 +1114,8 @@ struct Section {
currentSectionIndex += 1; // Make the current section index the next section's index
} [[name(sectionsTable[currentSectionIndex-1].name)]];
Section sections[coffHeader.numberOfSections] @ sectionsTable[0].ptrRawData;
Section sections[coffHeader.numberOfSections] @ coffHeader.optionalHeader.sizeOfHeaders * !sectionsTable[0].sizeOfRawData
+ sectionsTable[0].ptrRawData * sectionsTable[0].sizeOfRawData>0;
// Symbol & String Tables
enum SectionNumberType : s16 {

110
patterns/snes.hexpat Normal file
View File

@@ -0,0 +1,110 @@
#pragma author gmestanley
#pragma description Super Nintendo Entertainment System ROM header
#pragma sources snes.nesdev.org/wiki/ROM_header CPU_vectors en.wikibooks.org/wiki/Super_NES_Programming/SNES_memory_map
import std.string;
u24 headerPosition = 0x7FC0;
fn calculateHeaderPosition() {
if (std::mem::size() > 0x20000 && std::mem::size() < 0x600000) headerPosition += 0x8000;
else if (std::mem::size() >= 0x600000) headerPosition += 0x400000;
};
calculateHeaderPosition();
enum ChipsetSubtype : u8 {
SPC7110,
ST01x,
ST018,
CX4
};
struct ExpandedHeader {
char makerID[2];
char gameID[4];
padding[6];
u8 expansionFlashSize [[comment("1 << N")]];
u8 expansionRAMSize [[comment("1 << N")]];
u8 specialVersion;
ChipsetSubtype chipsetSubtype;
};
struct ConditionalStruct {
if ($[headerPosition+0x1A] == 0x33) ExpandedHeader expandedHeader @ headerPosition - 0x10;
else if (!$[headerPosition+0x14]) ChipsetSubtype chipsetSubtype @ headerPosition - 1;
} [[inline]];
ConditionalStruct conditionalStruct @ $;
enum MappingMode : u8 {
LoROM,
HiROM,
ExHiROM = 5
};
fn formatMappingMode(u8 value) {
MappingMode enumValue = value;
return enumValue;
};
bitfield ROMType {
mappingMode : 4 [[format("formatMappingMode")]];
speed : 1;
unknown : 1;
};
enum CoprocessorType : u8 {
DSP,
GSU,
OBC1,
SA1,
SDD1,
SRTC,
Other = 0x0E,
Custom
};
fn formatExtraHardwareType(u8 value) {
str valueMeaning = " (ROM";
if (!value) valueMeaning += " only";
else if (value) {
if (value > 3) valueMeaning += " + coprocessor";
if (value != 3 || value != 6) valueMeaning += " + RAM";
if (value == 2 || value > 5) valueMeaning += " + battery";
}
return std::string::to_string(value) + valueMeaning + ")";
};
fn formatCoprocessorType(u8 value) {
CoprocessorType enumValue = value;
return enumValue;
};
bitfield ExtraHardware {
extraHardwareType : 4 [[format("formatExtraHardwareType")]];
coprocessorType : 4 [[format("formatCoprocessorType")]];
};
enum Country : u8 {
NTSC = 1,
PAL
};
struct Header {
char title[21];
ROMType romType;
ExtraHardware extraHardware;
u8 romSize [[comment("1 << N, rounded up")]];
u8 ramSize [[comment("1 << N")]];
Country country;
u8 developerID;
u8 romVersion;
u16 checksumComplement;
u16 checksum;
padding[4];
u16 vectors[6];
padding[4];
u16 emulationModeVectors[6];
};
Header header @ headerPosition;