mirror of
https://github.com/WerWolv/ImHex-Patterns.git
synced 2026-03-27 23:37:04 -05:00
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:
@@ -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 |
|
||||
|
||||
@@ -4,26 +4,25 @@
|
||||
|
||||
#pragma endian big
|
||||
|
||||
import std.mem;
|
||||
import std.string;
|
||||
|
||||
struct Hunk {
|
||||
u24 offset;
|
||||
u16 length;
|
||||
if (!length) {
|
||||
u16 runCount;
|
||||
u8 payload;
|
||||
}
|
||||
else {
|
||||
u8 payload[length];
|
||||
}
|
||||
u24 offset;
|
||||
u16 length;
|
||||
if (!length) {
|
||||
u16 runCount;
|
||||
u8 payload;
|
||||
}
|
||||
else {
|
||||
u8 payload[length];
|
||||
}
|
||||
};
|
||||
|
||||
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];
|
||||
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[std::mem::read_string(std::mem::size()-3, 3) != "EOF"];
|
||||
};
|
||||
|
||||
IPS ips @ 0x00;
|
||||
|
||||
@@ -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
109
patterns/nsf.hexpat
Normal 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
48
patterns/nsfe.hexpat
Normal 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;
|
||||
1853
patterns/pe.hexpat
1853
patterns/pe.hexpat
File diff suppressed because it is too large
Load Diff
110
patterns/snes.hexpat
Normal file
110
patterns/snes.hexpat
Normal 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;
|
||||
Reference in New Issue
Block a user