#include "../idaldr.h" #include "addr.cpp" //---------------------------------------------------------------------------- static void map_io_seg(ea_t start, ea_t end, const char *const name) { segment_t s; s.start_ea = start; s.end_ea = end; s.type = SEG_IMEM; s.sel = allocate_selector(start >> 4); if ( !add_segm_ex(&s, name, NULL, ADDSEG_NOSREG|ADDSEG_SPARSE) ) loader_failure("Failed adding %s segment\n", name); } //---------------------------------------------------------------------------- static void map_hwregs() { map_io_seg(0x2100, 0x2140, "ppu"); map_io_seg(0x2140, 0x2144, "apu"); map_io_seg(0x2180, 0x2184, "wramrw"); map_io_seg(0x4016, 0x4018, "joypad"); map_io_seg(0x4200, 0x4220, "misc"); map_io_seg(0x4300, 0x4380, "dma"); } //---------------------------------------------------------------------------- static void map_wram() { segment_t s; s.start_ea = 0x7e0000; s.end_ea = 0x800000; s.type = SEG_IMEM; s.sel = allocate_selector(s.start_ea >> 4); char seg_name[0x10]; qsnprintf(seg_name, sizeof(seg_name), "wram"); if ( !add_segm_ex(&s, seg_name, NULL, ADDSEG_NOSREG|ADDSEG_SPARSE) ) loader_failure("Failed adding %s segment\n", seg_name); } //---------------------------------------------------------------------------- static void map_lorom_sram_offset(uint32 ram_size, uint8 start_bank) { // Usually, the lower half of bank (0x8000 bytes) is SRAM, and the upper half is ROM mirror. // However, some cartridges maps the whole of bank (0x10000 bytes) to SRAM. // In that case, the upper half is probably mirrored as same as the lower half. // create ram banks const uint32 bank_size = 0x8000; uint32 ram_chunks = (ram_size + bank_size - 1) / bank_size; for ( uint32 mapped = 0, bank = start_bank; mapped < ram_chunks; bank++, mapped++ ) { if ( bank == 0x7e ) bank = 0xfe; segment_t s; s.start_ea = uint32(bank << 16); s.end_ea = s.start_ea + bank_size; s.type = SEG_IMEM; s.sel = allocate_selector(s.start_ea >> 4); char seg_name[0x10]; qsnprintf(seg_name, sizeof(seg_name), ".%02X", bank); if ( !add_segm_ex(&s, seg_name, "BANK_RAM", ADDSEG_NOSREG|ADDSEG_SPARSE) ) loader_failure("Failed adding %s segment\n", seg_name); } } //---------------------------------------------------------------------------- static void map_hirom_sram_offset(uint32 ram_size, uint8 start_bank) { // create ram banks const uint32 bank_size = 0x2000; uint32 ram_chunks = (ram_size + bank_size - 1) / bank_size; for ( uint32 mapped = 0, bank = start_bank; mapped < ram_chunks; bank++, mapped++ ) { segment_t s; s.start_ea = uint32((bank << 16) + 0x6000); s.end_ea = s.start_ea + bank_size; s.type = SEG_IMEM; s.sel = allocate_selector(s.start_ea >> 4); char seg_name[0x10]; qsnprintf(seg_name, sizeof(seg_name), ".%02X", bank); if ( !add_segm_ex(&s, seg_name, "BANK_RAM", ADDSEG_NOSREG|ADDSEG_SPARSE) ) loader_failure("Failed adding %s segment\n", seg_name); } } //---------------------------------------------------------------------------- static void map_lorom_sram(uint32 ram_size) { // create ram banks 70-7d (and fe-ff) map_lorom_sram_offset(ram_size, 0x70); } //---------------------------------------------------------------------------- static void map_hirom_sram(uint32 ram_size) { // create ram banks 20-3f map_hirom_sram_offset(ram_size, 0x20); } //---------------------------------------------------------------------------- static void map_superfx_sram(uint32 ram_size) { // create ram banks 70-71 const uint32 bank_size = 0x10000; uint32 ram_chunks = (ram_size + bank_size - 1) / bank_size; for ( uint32 mapped = 0, bank = 0x70; mapped < ram_chunks; bank++, mapped++ ) { segment_t s; s.start_ea = uint32(bank << 16); s.end_ea = s.start_ea + bank_size; s.type = SEG_IMEM; s.sel = allocate_selector(s.start_ea >> 4); char seg_name[0x10]; qsnprintf(seg_name, sizeof(seg_name), ".%02X", bank); if ( !add_segm_ex(&s, seg_name, "BANK_RAM", ADDSEG_NOSREG|ADDSEG_SPARSE) ) loader_failure("Failed adding %s segment\n", seg_name); } } //---------------------------------------------------------------------------- static void map_superfx_workram() { segment_t s; s.start_ea = 0x6000; s.end_ea = 0x8000; s.type = SEG_IMEM; s.sel = allocate_selector(s.start_ea >> 4); char seg_name[0x10]; qsnprintf(seg_name, sizeof(seg_name), "sfxram"); if ( !add_segm_ex(&s, seg_name, NULL, ADDSEG_NOSREG|ADDSEG_SPARSE) ) loader_failure("Failed adding %s segment\n", seg_name); } //---------------------------------------------------------------------------- static void map_superfx_hwregs() { map_io_seg(0x3000, 0x3500, "superfx"); } //---------------------------------------------------------------------------- static void map_sa1_bwram(uint32 ram_size) { // create ram banks 40-41 const uint32 bank_size = 0x10000; uint32 ram_chunks = (ram_size + bank_size - 1) / bank_size; for ( uint32 mapped = 0, bank = 0x40; mapped < ram_chunks; bank++, mapped++ ) { segment_t s; s.start_ea = uint32(bank << 16); s.end_ea = s.start_ea + bank_size; s.type = SEG_IMEM; s.sel = allocate_selector(s.start_ea >> 4); char seg_name[0x10]; qsnprintf(seg_name, sizeof(seg_name), ".%02X", bank); if ( !add_segm_ex(&s, seg_name, "BANK_RAM", ADDSEG_NOSREG|ADDSEG_SPARSE) ) loader_failure("Failed adding %s segment\n", seg_name); } } //---------------------------------------------------------------------------- static void map_sa1_iram() { segment_t s; s.start_ea = 0x3000; s.end_ea = 0x3800; s.type = SEG_IMEM; s.sel = allocate_selector(s.start_ea >> 4); char seg_name[0x10]; qsnprintf(seg_name, sizeof(seg_name), "iram"); if ( !add_segm_ex(&s, seg_name, NULL, ADDSEG_NOSREG|ADDSEG_SPARSE) ) loader_failure("Failed adding %s segment\n", seg_name); } //---------------------------------------------------------------------------- static void map_sa1_hwregs() { map_io_seg(0x2200, 0x2400, "sa1"); } //---------------------------------------------------------------------------- static void map_cx4_hwregs() { map_io_seg(0x6000, 0x8000, "cx4"); } //---------------------------------------------------------------------------- static void map_spc7110_hwregs() { map_io_seg(0x4800, 0x4840, "spc7110"); map_io_seg(0x500000, 0x600000, "decomprom"); } //---------------------------------------------------------------------------- static void map_sdd1_hwregs() { map_io_seg(0x4800, 0x4808, "sdd1"); } //---------------------------------------------------------------------------- static sel_t map_lorom_offset(linput_t *li, uint32 rom_start_in_file, uint32 rom_size, uint8 start_bank, uint32 offset) { // 32KB chunks count uint32 chunks = (rom_size + 0x8000 - 1) / 0x8000; // map rom to banks sel_t start_sel = 0; for ( uint32 mapped = 0, bank = start_bank; mapped < chunks; bank++, mapped++ ) { if ( bank == 0x7e || bank == 0x7f ) continue; uint32 map_size = qmin(0x8000, rom_size - (0x8000 * mapped)); ea_t start = uint32((bank << 16) + 0x8000); ea_t end = start + 0x8000; uint32 off_in_file = rom_start_in_file + offset + (mapped << 15); if ( !file2base(li, off_in_file, start, start + map_size, FILEREG_PATCHABLE) ) loader_failure("Failed mapping 0x%x -> [0x%a, 0x%a)\n", off_in_file, start, end); char seg_name[0x10]; sel_t selector = allocate_selector((start - 0x8000) >> 4); qsnprintf(seg_name, sizeof(seg_name), ".%02X", bank); if ( !add_segm(selector, start, end, seg_name, "BANK_ROM") ) loader_failure("Failed adding .BANK segment\n"); if ( bank == start_bank ) start_sel = selector; } return start_sel; } //---------------------------------------------------------------------------- static sel_t map_hirom_offset(linput_t *li, uint32 rom_start_in_file, uint32 rom_size, uint8 start_bank, uint32 offset) { sel_t start_sel = 0; // map rom to banks uint32 chunks = (rom_size + 0x10000 - 1) / 0x10000; for ( uint32 mapped = 0, bank = start_bank; mapped < chunks; bank++, mapped++ ) { if ( bank == 0x7e || bank == 0x7f ) continue; uint32 map_size = qmin(0x10000, rom_size - (0x10000 * mapped)); ea_t start = uint32(bank << 16); ea_t end = start + 0x10000; uint32 off_in_file = rom_start_in_file + offset + (mapped << 16); if ( !file2base(li, off_in_file, start, start + map_size, FILEREG_PATCHABLE) ) loader_failure("Failed mapping 0x%x -> [0x%a, 0x%a)\n", off_in_file, start, end); char seg_name[0x10]; sel_t selector = allocate_selector((start) >> 4); qsnprintf(seg_name, sizeof(seg_name), ".%02X", bank); if ( !add_segm(selector, start, end, seg_name, "BANK_ROM") ) loader_failure("Failed adding .BANK segment\n"); if ( bank == start_bank ) start_sel = selector; } return start_sel; } //---------------------------------------------------------------------------- static sel_t map_lorom(linput_t *li, uint32 rom_start_in_file, uint32 rom_size) { // map rom to banks 80-ff return map_lorom_offset(li, rom_start_in_file, rom_size, 0x80, 0); } //---------------------------------------------------------------------------- static sel_t map_hirom(linput_t *li, uint32 rom_start_in_file, uint32 rom_size) { // map rom to banks c0-ff return map_hirom_offset(li, rom_start_in_file, rom_size, 0xc0, 0); } //---------------------------------------------------------------------------- static sel_t map_exhirom(linput_t *li, uint32 rom_start_in_file, uint32 rom_size) { if ( rom_size <= 0x400000 ) return BADSEL; // map rom to banks 40-7f sel_t start_sel = map_hirom_offset(li, rom_start_in_file, rom_size - 0x400000, 0x40, 0x400000); // map rom to banks c0-ff map_hirom_offset(li, rom_start_in_file, qmin(rom_size, 0x400000), 0xc0, 0); return start_sel; } //---------------------------------------------------------------------------- static sel_t map_superfx(linput_t *li, uint32 rom_start_in_file, uint32 rom_size) { // map rom to banks 00-3f (LoROM layout) sel_t start_sel = map_lorom_offset(li, rom_start_in_file, qmin(rom_size, 0x200000), 0x00, 0); // map rom to banks c0-df (HiROM layout) map_hirom_offset(li, rom_start_in_file, qmin(rom_size, 0x200000), 0xc0, 0); return start_sel; } //---------------------------------------------------------------------------- static sel_t map_sa1(linput_t *li, uint32 rom_start_in_file, uint32 rom_size) { // map rom to banks 00-3f (LoROM layout) sel_t start_sel = map_lorom_offset(li, rom_start_in_file, qmin(rom_size, 0x200000), 0x00, 0); // map rom to banks c0-ff (HiROM layout) map_hirom_offset(li, rom_start_in_file, rom_size, 0xc0, 0); return start_sel; } //---------------------------------------------------------------------------- static sel_t map_sdd1rom(linput_t *li, uint32 rom_start_in_file, uint32 rom_size) { // map rom to banks 80-bf (LoROM layout) sel_t start_sel = map_lorom_offset(li, rom_start_in_file, qmin(rom_size, 0x200000), 0x80, 0); // map rom to banks c0-ff (HiROM layout) map_hirom_offset(li, rom_start_in_file, qmin(rom_size, 0x400000), 0xc0, 0); return start_sel; } //---------------------------------------------------------------------------- static sel_t map_lorom_cartridge(linput_t *li, uint32 rom_start_in_file, uint32 rom_size, uint32 ram_size) { sel_t start_sel = map_lorom(li, rom_start_in_file, qmin(rom_size, 0x400000)); map_lorom_sram(ram_size); return start_sel; } //---------------------------------------------------------------------------- static sel_t map_hirom_cartridge(linput_t *li, uint32 rom_start_in_file, uint32 rom_size, uint32 ram_size) { sel_t start_sel = map_hirom(li, rom_start_in_file, qmin(rom_size, 0x400000)); map_hirom_sram(ram_size); return start_sel; } //---------------------------------------------------------------------------- static sel_t map_exlorom_cartridge(linput_t *li, uint32 rom_start_in_file, uint32 rom_size, uint32 ram_size) { // S-DD1 cartridge should be handled by map_sdd1_cartridge sel_t start_sel = map_lorom_offset(li, rom_start_in_file, qmin(rom_size, 0x200000), 0x80, 0); map_hirom_offset(li, rom_start_in_file, qmin(rom_size, 0x400000), 0x40, 0); map_lorom_sram(ram_size); return start_sel; } //---------------------------------------------------------------------------- static sel_t map_exhirom_cartridge(linput_t *li, uint32 rom_start_in_file, uint32 rom_size, uint32 ram_size) { sel_t start_sel = map_exhirom(li, rom_start_in_file, rom_size); map_hirom_sram(ram_size); return start_sel; } //---------------------------------------------------------------------------- static sel_t map_superfx_cartridge(linput_t *li, uint32 rom_start_in_file, uint32 rom_size, uint32 ram_size) { sel_t start_sel = map_superfx(li, rom_start_in_file, rom_size); map_superfx_sram(ram_size); map_superfx_workram(); map_superfx_hwregs(); return start_sel; } //---------------------------------------------------------------------------- static sel_t map_sa1_cartridge(linput_t *li, uint32 rom_start_in_file, uint32 rom_size, uint32 ram_size) { sel_t start_sel = map_sa1(li, rom_start_in_file, rom_size); map_sa1_bwram(ram_size); map_sa1_iram(); map_sa1_hwregs(); return start_sel; } //---------------------------------------------------------------------------- static sel_t map_cx4_cartridge(linput_t *li, uint32 rom_start_in_file, uint32 rom_size, uint32 ram_size) { sel_t start_sel = map_lorom(li, rom_start_in_file, qmin(rom_size, 0x400000)); map_lorom_sram(ram_size); map_cx4_hwregs(); return start_sel; } //---------------------------------------------------------------------------- static sel_t map_spc7110_cartridge(linput_t *li, uint32 rom_start_in_file, uint32 rom_size, uint32 ram_size) { sel_t start_sel = map_hirom_offset(li, rom_start_in_file, qmin(rom_size, 0x100000), 0xc0, 0); // create ram banks 00-3f map_hirom_sram_offset(ram_size, 0x00); map_spc7110_hwregs(); return start_sel; } //---------------------------------------------------------------------------- static sel_t map_sdd1_cartridge(linput_t *li, uint32 rom_start_in_file, uint32 rom_size, uint32 ram_size) { sel_t start_sel = map_sdd1rom(li, rom_start_in_file, rom_size); map_lorom_sram(ram_size); map_sdd1_hwregs(); return start_sel; } //---------------------------------------------------------------------------- static void map_sharprtc() { map_io_seg(0x2800, 0x2802, "sharprtc"); } //---------------------------------------------------------------------------- static void map_epsonrtc() { map_io_seg(0x4840, 0x4843, "epsonrtc"); } //---------------------------------------------------------------------------- static void map_obc1() { // TODO: Add OBC-1 registers } //---------------------------------------------------------------------------- static void map_dsp1(SuperFamicomCartridge::DSP1MemoryMapper /*dsp1_mapper*/) { // TODO: Add DSP-1 registers } //---------------------------------------------------------------------------- static void map_dsp2() { // TODO: Add DSP-2 registers } //---------------------------------------------------------------------------- static void map_dsp3() { // TODO: Add DSP-3 registers } //---------------------------------------------------------------------------- static void map_dsp4() { // TODO: Add DSP-4 registers } //---------------------------------------------------------------------------- static void map_st010() { // TODO: Add ST-010 registers } //---------------------------------------------------------------------------- static void map_st011() { // TODO: Add ST-011 registers } //---------------------------------------------------------------------------- static void map_st018() { // TODO: Add ST-018 registers } //---------------------------------------------------------------------------- static int idaapi accept_file( qstring *fileformatname, qstring *processor, linput_t *li, const char *) { SuperFamicomCartridge cartridge(li); unsigned score = SuperFamicomCartridge::score_header(li, cartridge.header_offset); // It is enough to have the first byte of the supposed 'reset vector' match // one of 6 values, to have a treshold of 8. arm_eep0.bin has such a byte. // Thus a treshold of 9 (or more) seems in order. Here are some scores: // - m65816_ffVI.snes: 20 // - m65816_pacman.snes: 14 // - m65816_z2ybd.snes: 14 const int ACCEPTABLE_SCORE_TRESHOLD = 9; if ( score >= ACCEPTABLE_SCORE_TRESHOLD && cartridge.type != SuperFamicomCartridge::TypeUnknown ) { *fileformatname = "SNES ROM"; *processor = "m65816"; return 1; } return 0; } //---------------------------------------------------------------------------- static void add_interrupt_vector(snes_addr_t &sa, uint16 addr, const char *name, bool make_code) { // Set 'addr' as dword ea_t real_ea = sa.xlat(addr); create_word(real_ea, 2); ea_t orig_vector_addr = get_word(real_ea); ea_t vector_addr = sa.xlat(orig_vector_addr); if ( orig_vector_addr != 0 && orig_vector_addr != 0xffff ) { // Set 'vector_addr' name to be 'name' if ( !has_user_name(get_flags(vector_addr)) ) set_name(vector_addr, name, SN_NOCHECK); // Push the vector_addr into the autoanalysis queue. // Do not make use of auto_make_proc(), because some // interrupt handler functions are ``overlaid''. Thus, // we'd break a procedure w/ inserting another // procedure right into the previous procedure's code. if ( make_code ) auto_make_code(vector_addr); // Set 'real_ea' as offset refinfo_t ri; ri.init(REF_OFF16, vector_addr - orig_vector_addr); op_offset_ex(real_ea, OPND_MASK, &ri); set_cmt(real_ea, name, false); } } //---------------------------------------------------------------------------- void idaapi load_file(linput_t *li, ushort /*neflags*/, const char * /*ffn*/) { // One should always set the processor type // as early as possible: IDA will draw some // informations from it; e.g., the size of segments. // // Should this instruction be placed after the calls to // map_mode_2x(), IDA would create 32-bits segments, // because, until the processor type is specified, IDA // assumes x86. set_processor_type("m65816", SETPROC_LOADER); SuperFamicomCartridge cartridge(li); // Determine whether ROM has a header int32 start = cartridge.has_copier_header ? 512 : 0; // Store information for the cpu module netnode node; node.create("$ m65816"); node.hashset("device", "snes"); cartridge.write_hash(node); snes_addr_t sa; sa.addr_init(cartridge); sel_t start_cs; if ( cartridge.has_cx4 ) { start_cs = map_cx4_cartridge(li, start, cartridge.rom_size, cartridge.ram_size); } else if ( cartridge.has_spc7110 ) { start_cs = map_spc7110_cartridge(li, start, cartridge.rom_size, cartridge.ram_size); } else if ( cartridge.has_sdd1 ) { start_cs = map_sdd1_cartridge(li, start, cartridge.rom_size, cartridge.ram_size); } else { switch ( cartridge.mapper ) { case SuperFamicomCartridge::LoROM: start_cs = map_lorom_cartridge(li, start, cartridge.rom_size, cartridge.ram_size); break; case SuperFamicomCartridge::HiROM: start_cs = map_hirom_cartridge(li, start, cartridge.rom_size, cartridge.ram_size); break; case SuperFamicomCartridge::ExLoROM: start_cs = map_exlorom_cartridge(li, start, cartridge.rom_size, cartridge.ram_size); break; case SuperFamicomCartridge::ExHiROM: start_cs = map_exhirom_cartridge(li, start, cartridge.rom_size, cartridge.ram_size); break; case SuperFamicomCartridge::SuperFXROM: start_cs = map_superfx_cartridge(li, start, cartridge.rom_size, cartridge.ram_size); break; case SuperFamicomCartridge::SA1ROM: start_cs = map_sa1_cartridge(li, start, cartridge.rom_size, cartridge.ram_size); break; default: loader_failure("Unsupported mapper: %s", cartridge.mapper_string()); } } inf_set_start_cs(start_cs); // Hardware registers map_hwregs(); // WRAM map_wram(); if ( cartridge.has_sharprtc ) map_sharprtc(); if ( cartridge.has_epsonrtc ) map_epsonrtc(); if ( cartridge.has_obc1 ) map_obc1(); if ( cartridge.has_dsp1 ) map_dsp1(cartridge.dsp1_mapper); if ( cartridge.has_dsp2 ) map_dsp2(); if ( cartridge.has_dsp3 ) map_dsp3(); if ( cartridge.has_dsp4 ) map_dsp4(); if ( cartridge.has_st010 ) map_st010(); if ( cartridge.has_st011 ) map_st011(); if ( cartridge.has_st018 ) map_st018(); ea_t reset_vector_loc = sa.xlat(0xfffc); uint16 start_pc = get_word(reset_vector_loc); ea_t start_address = sa.xlat(start_pc); inf_set_start_ip(start_address & 0xffff); // ------- Most important vectors // http://en.wikibooks.org/wiki/Super_NES_Programming/SNES_memory_map add_interrupt_vector(sa, 0xfffc, "Emulation-mode RESET", true); add_interrupt_vector(sa, 0xffea, "Native-mode NMI", true); add_interrupt_vector(sa, 0xffee, "Native-mode IRQ", true); add_interrupt_vector(sa, 0xfffe, "Emulation-mode IRQ", true); // ------- Native-mode vectors add_interrupt_vector(sa, 0xffe4, "Native-mode COP", false); add_interrupt_vector(sa, 0xffe6, "Native-mode BRK", false); add_interrupt_vector(sa, 0xffe8, "Native-mode ABORT", false); add_interrupt_vector(sa, 0xffec, "Native-mode RESET", false); // ------- Emulation-mode vectors add_interrupt_vector(sa, 0xfff4, "Emulation-mode COP", false); add_interrupt_vector(sa, 0xfff8, "Emulation-mode ABORT", false); add_interrupt_vector(sa, 0xfffa, "Emulation-mode NMI", false); // ------- Undefined vectors create_word(sa.xlat(0xffe0), 2); create_word(sa.xlat(0xffe2), 2); create_word(sa.xlat(0xfff0), 2); create_word(sa.xlat(0xfff2), 2); create_word(sa.xlat(0xfff6), 2); // Header info ea_t header = sa.xlat(0xffc0); set_name(header, "snes_header"); create_strlit(header, 21, STRTYPE_C); set_cmt(header, "Game Title", false); create_byte(header + 0x15, 1); set_cmt(header + 0x15, "ROM Makeup / ROM Speed and Map Mode", false); create_byte(header + 0x16, 1); set_cmt(header + 0x16, "Chipset", false); create_byte(header + 0x17, 1); set_cmt(header + 0x17, "ROM Size", false); create_byte(header + 0x18, 1); set_cmt(header + 0x18, "RAM Size", false); create_byte(header + 0x19, 1); set_cmt(header + 0x19, "Country", false); create_byte(header + 0x1a, 1); set_cmt(header + 0x1a, "Developer ID", false); create_byte(header + 0x1b, 1); set_cmt(header + 0x1b, "ROM Version", false); create_word(header + 0x1c, 2); set_cmt(header + 0x1c, "Checksum Complement", false); create_word(header + 0x1e, 2); set_cmt(header + 0x1e, "Checksum", false); } //---------------------------------------------------------------------------- loader_t LDSC = { IDP_INTERFACE_VERSION, LDRF_RELOAD, accept_file, load_file, NULL, NULL, NULL, };