ImHex-Patterns
ImHex-Patterns copied to clipboard
SNES ROM files
Specs: https://snes.nesdev.org/wiki/Memory_map https://snes.nesdev.org/wiki/ROM_file_formats
#pragma description SNES ROM
#pragma MIME application/x-snes-rom
import std.mem;
import std.sys;
enum MapMode : u8 {
SlowLoROM = 0x20,
SlowHiROM = 0x21,
SlowExLoROM = 0x22,
SlowExHiROM = 0x25,
FastLoROM = 0x30,
FastHiROM = 0x31,
FastExLoROM = 0x32,
FastExHiROM = 0x35,
};
enum CartridgeType : u8 {
ROM = 0x00,
ROM_RAM = 0x01,
ROM_RAM_BAT = 0x02,
ROM_CoP = 0x33, // Coprocessor
ROM_CoP_RAM = 0x34,
ROM_CoP_RAM_BAT = 0x35,
};
enum Country : u8 {
Japan = 0x00,
USA = 0x01,
Europe = 0x02,
Scandinavia = 0x03,
France = 0x06,
Netherlands = 0x07,
Spain = 0x08,
Germany = 0x09,
Italy = 0x0A,
China = 0x0B,
Korea = 0x0D,
Canada = 0x0F,
Brazil = 0x10,
Australia = 0x11,
};
struct InterruptVectors {
u16 reserved[4];
u16 cop [[name("COP (Co-Processor)")]];
u16 brk [[name("BRK (Break)")]];
u16 abort [[name("ABORT")]];
u16 nmi [[name("NMI (Non-Maskable Interrupt)")]];
u16 reset [[name("RESET")]];
u16 irq [[name("IRQ (Interrupt Request)")]];
};
struct SNESHeader {
char title[21];
MapMode map_mode;
CartridgeType type;
u8 rom_size [[comment("1 << (n + 10) bytes")]];
u8 ram_size [[comment("1 << (n + 10) bytes")]];
Country country;
u8 licensee;
u8 version;
u16 checksum_complement;
u16 checksum;
InterruptVectors native_vectors [[name("Native Vectors")]];
InterruptVectors emu_vectors [[name("Emulation Vectors")]];
};
fn get_header_offset() {
u64 size = std::mem::size();
u64 header_offset = 0;
// Check for copier header (512 bytes)
// Files are usually multiples of 1024 (banks). Extra 512 bytes indicates header.
if ((size % 1024) == 512) {
header_offset = 512;
}
// Possible locations based on memory mapping
u64 lorom_addr = 0x7FC0 + header_offset;
u64 hirom_addr = 0xFFC0 + header_offset;
u64 exhirom_addr = 0x40FFC0 + header_offset;
// Detection Strategy: Checksum + Complement == 0xFFFF
// Check LoROM
if (lorom_addr + 0x30 < size) {
u16 sum = std::mem::read_unsigned(lorom_addr + 0x1C, 2) + std::mem::read_unsigned(lorom_addr + 0x1E, 2);
if (sum == 0xFFFF) return lorom_addr;
}
// Check HiROM
if (hirom_addr + 0x30 < size) {
u16 sum = std::mem::read_unsigned(hirom_addr + 0x1C, 2) + std::mem::read_unsigned(hirom_addr + 0x1E, 2);
if (sum == 0xFFFF) return hirom_addr;
}
// Check ExHiROM
if (exhirom_addr + 0x30 < size) {
u16 sum = std::mem::read_unsigned(exhirom_addr + 0x1C, 2) + std::mem::read_unsigned(exhirom_addr + 0x1E, 2);
if (sum == 0xFFFF) return exhirom_addr;
}
// Fallback if checksums are invalid (e.g. modified/homebrew)
// Heuristic: Map Mode byte is usually 0x20-0x3F
u8 lorom_mode = std::mem::read_unsigned(lorom_addr + 0x15, 1);
if (lorom_mode >= 0x20 && lorom_mode <= 0x35) return lorom_addr;
return lorom_addr;
};
u64 offset = get_header_offset();
bool has_copier_header = (std::mem::size() % 1024) == 512;
u64 rom_data_start = has_copier_header ? 512 : 0;
// Fix 1: Use conditional size instead of if block for safer parsing
u8 copier_header[has_copier_header ? 512 : 0] @ 0x00 [[name("Copier Header (SMC/SWC)")]];
SNESHeader header @ offset [[name("Internal ROM Header")]];
struct Bank32k {
u8 data[0x8000];
};
struct Bank64k {
u8 data[0x10000];
};
fn is_lorom(MapMode mode) {
return mode == MapMode::SlowLoROM || mode == MapMode::FastLoROM ||
mode == MapMode::SlowExLoROM || mode == MapMode::FastExLoROM;
};
// Fix 2: Wrap conditional logic in a struct to ensure attributes work correctly
struct ROMData {
if (is_lorom(header.map_mode)) {
Bank32k banks[while($ < std::mem::size())] [[name("LoROM Banks (32KB)")]];
} else {
Bank64k banks[while($ < std::mem::size())] [[name("HiROM Banks (64KB)")]];
}
};
ROMData rom_data @ rom_data_start [[name("Cartridge ROM Data")]];
This?
how 2 use this