ImHex-Patterns icon indicating copy to clipboard operation
ImHex-Patterns copied to clipboard

SNES ROM files

Open H-A-M-G-E-R opened this issue 10 months ago • 2 comments

Specs: https://snes.nesdev.org/wiki/Memory_map https://snes.nesdev.org/wiki/ROM_file_formats

H-A-M-G-E-R avatar Mar 14 '25 20:03 H-A-M-G-E-R

#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?

iamdroppy avatar Dec 01 '25 17:12 iamdroppy

how 2 use this

H-A-M-G-E-R avatar Dec 02 '25 16:12 H-A-M-G-E-R