bsnes/icarus/heuristics/super-famicom.cpp
Tim Allen 07995c05a5 Update to v100 release.
byuu says:

higan has finally reached v100!

I feel it's important to stress right away that this is not "version
1.00", nor is it a major milestone release. Rather than arbitrary version
numbers, all of my software simply bumps version numbers by one for each
official release. As such, higan v100 is simply higan's 100th release.

That said, the primary focus of this release has been code
clean-ups. These are always somewhat dangerous in that regressions are
possible. We've tested through sixteen WIP revisions, one of which was
open to the public, to try and minimize any regressions. But all the same,
please report any regressions if you discover any.

Changelog (since v099):
FC: render during pixels 1-256 instead of 0-255 [hex_usr]
FC: rewrote controller emulation code
SFC: 8% speedup over the previous release thanks to PPU optimizations
SFC: fixed nasty DB address wrapping regression from v099
SFC: USART developer controller removed; superseded by 21fx
SFC: Super Multitap option removed from controller port 1; ports
    renamed 2-5
SFC: hidden option to experiment with 128KB VRAM (strictly for novelty)
higan: audio volume no longer divided by number of audio streams
higan: updated controller polling code to fix possible future mapping
    issues
higan: replaced nall/stream with nall/vfs for file-loading subsystem
tomoko: can now load multi-slotted games via command-line
tomoko: synchronize video removed from UI; still available in the
    settings file
tomoko, icarus: can navigate to root drive selection on Windows
all: major code cleanups and refactoring (~1MB diff against v099)

Note 1: the audio volume change means that SGB and MSU1 games won't
lose half the volume on the SNES sounds anymore. However, if one goes
overboard and drives the sound all the way to max volume with the MSU1,
clamping may occur. The obvious solution is not to drive volume that high
(it will vastly overpower the SNES audio, which usually never exceeds
25% volume.) Another option is to lower the volume in the audio settings
panel to 50%. In general, neither is likely to ever be necessary.

Note 2: the synchronize video option was hidden from the UI because it
is no longer useful. With the advent of compositors, the loss of the
complicated timing settings panel, support for the WonderSwan and its
75hz display, the need to emulate variable refresh rate behaviors in the
Game Boy, the unfortunate latency spike and audio distortion caused by
long Vsync pauses, and the arrival of adaptive sync technology ... it
no longer makes sense to present this option. However, as stated, you
can edit settings.bml to enable this option anyway if you insist and
understand the aforementioned risks.

Changelog (since v099r16 open beta):

- fixed MSU1 audio sign extension
- fixed compilation with SGB support disabled
- icarus can now navigate to root directory
- fixed compilation issues with OS X port
- (hopefully) fixed label height issue with hiro that affected icarus
  import dialog
- (mostly) fixed BS Memory, Sufami Turbo slot loading

Errata:

- forgot to remove the " - Slot A", " - Slot B" suffixes for Sufami
  Turbo slot loading
  - this means you have to navigate up one folder and then into Sufami
    Turbo/ to load games for this system
- moving WonderSwan orientation controls to the device slot is causing
  some nastiness
  - can now select orientation from the main menu, but it doesn't rotate
    the display
2016-07-08 22:04:59 +10:00

835 lines
26 KiB
C++

struct SuperFamicomCartridge {
SuperFamicomCartridge(const uint8_t* data, uint size, bool has_msu1 = false);
string markup;
//private:
auto readHeader(const uint8_t* data, uint size) -> void;
auto findHeader(const uint8_t* data, uint size) -> uint;
auto scoreHeader(const uint8_t* data, uint size, uint addr) -> uint;
enum HeaderField : uint {
CartName = 0x00,
Mapper = 0x15,
RomType = 0x16,
RomSize = 0x17,
RamSize = 0x18,
CartRegion = 0x19,
Company = 0x1a,
Version = 0x1b,
Complement = 0x1c, //inverse checksum
Checksum = 0x1e,
ResetVector = 0x3c,
};
enum class Type : uint {
SatellaviewBIOS,
SufamiTurboBIOS,
SuperGameBoy1BIOS,
SuperGameBoy2BIOS,
LoROM,
HiROM,
ExLoROM,
ExHiROM,
SuperFX,
SA1,
LoROMSatellaview,
HiROMSatellaview,
CampusChallenge92,
Powerfest94,
//invalid types
Unknown,
GameBoy,
Satellaview,
SufamiTurbo,
};
enum class Region : uint {
NTSC,
PAL,
};
enum class DSP1Type : uint {
None,
LoROM1MB,
LoROM2MB,
HiROM,
};
bool loaded = false; //is a base cartridge inserted?
uint crc32 = 0; //crc32 of all cartridges (base+slot(s))
uint rom_size = 0;
uint ram_size = 0;
bool firmware_required = false; //true if firmware is required for emulation
bool firmware_appended = false; //true if firmware is present at end of data
Type type = Type::Unknown;
Region region = Region::NTSC;
DSP1Type dsp1_type = DSP1Type::None;
bool has_bsx_slot = false;
bool has_superfx = false;
bool has_sa1 = false;
bool has_sharprtc = false;
bool has_epsonrtc = false;
bool has_sdd1 = false;
bool has_spc7110 = false;
bool has_cx4 = false;
bool has_dsp1 = false;
bool has_dsp2 = false;
bool has_dsp3 = false;
bool has_dsp4 = false;
bool has_obc1 = false;
bool has_st010 = false;
bool has_st011 = false;
bool has_st018 = false;
};
SuperFamicomCartridge::SuperFamicomCartridge(const uint8_t* data, uint size, bool has_msu1) {
//skip copier header
if((size & 0x7fff) == 512) data += 512, size -= 512;
if(size < 0x8000) return;
readHeader(data, size);
if(type == Type::Unknown) return;
if(type == Type::GameBoy) return;
if(type == Type::Satellaview) return;
if(type == Type::SufamiTurbo) return;
const char* range = (rom_size > 0x200000) || (ram_size > 32 * 1024) ? "0000-7fff" : "0000-ffff";
markup.append("board region=", region == Region::NTSC ? "ntsc" : "pal", "\n");
//detect appended firmware
if(has_dsp1) {
firmware_required = true;
if((size & 0x7fff) == 0x2000) {
firmware_appended = true;
rom_size -= 0x2000;
}
}
if(has_dsp2) {
firmware_required = true;
if((size & 0x7fff) == 0x2000) {
firmware_appended = true;
rom_size -= 0x2000;
}
}
if(has_dsp3) {
firmware_required = true;
if((size & 0x7fff) == 0x2000) {
firmware_appended = true;
rom_size -= 0x2000;
}
}
if(has_dsp4) {
firmware_required = true;
if((size & 0x7fff) == 0x2000) {
firmware_appended = true;
rom_size -= 0x2000;
}
}
if(has_st010) {
firmware_required = true;
if((size & 0xffff) == 0xd000) {
firmware_appended = true;
rom_size -= 0xd000;
}
}
if(has_st011) {
firmware_required = true;
if((size & 0xffff) == 0xd000) {
firmware_appended = true;
rom_size -= 0xd000;
}
}
if(has_st018) {
firmware_required = true;
if((size & 0x3ffff) == 0x28000) {
firmware_appended = true;
rom_size -= 0x28000;
}
}
if(has_cx4) {
firmware_required = true;
if((rom_size & 0x7fff) == 0xc00) {
firmware_appended = true;
rom_size -= 0xc00;
}
}
if(type == Type::SuperGameBoy1BIOS || type == Type::SuperGameBoy2BIOS) {
firmware_required = true;
if((rom_size & 0x7fff) == 0x100) {
firmware_appended = true;
rom_size -= 0x100;
}
}
//end firmware detection
if(type == Type::SatellaviewBIOS) {
markup.append(
" ram name=save.ram size=0x", hex(ram_size), "\n"
" map address=10-1f:5000-5fff mask=0xf000\n"
" mcc\n"
" map address=00-0f:5000\n"
" map=mcu address=00-3f,80-bf:8000-ffff mask=0x408000\n"
" map=mcu address=40-7d,c0-ff:0000-ffff\n"
" rom name=program.rom size=0x", hex(rom_size), "\n"
" ram name=download.ram size=0x80000\n"
" map address=00-3f,80-bf:6000-7fff mask=0xe000\n"
" bsmemory\n"
);
}
else if(type == Type::SufamiTurboBIOS) {
markup.append(
" rom name=program.rom size=0x", hex(rom_size), "\n"
" map address=00-1f,80-9f:8000-ffff mask=0x8000\n"
" sufamiturbo\n"
" rom\n"
" map address=20-3f,a0-bf:8000-ffff mask=0x8000\n"
" ram\n"
" map address=60-6f,e0-ef:0000-ffff\n"
" sufamiturbo\n"
" rom\n"
" map address=40-5f,c0-df:0000-7fff mask=0x8000\n"
" map address=40-5f,c0-df:8000-ffff mask=0x8000\n"
" ram\n"
" map address=70-7d,f0-ff:0000-ffff\n"
);
}
else if(type == Type::SuperGameBoy1BIOS || type == Type::SuperGameBoy2BIOS) {
markup.append(
" rom name=program.rom size=0x", hex(rom_size), "\n"
" map address=00-7d,80-ff:8000-ffff mask=0x8000\n"
" map address=40-7d,c0-ff:0000-7fff mask=0x8000\n"
" icd2 revision=1\n"
" map address=00-3f,80-bf:6000-67ff,7000-7fff\n"
" rom name=sgb.boot.rom size=0x100\n"
);
}
else if(has_cx4) {
markup.append(
" hitachidsp model=HG51B169 frequency=20000000\n"
" map address=00-3f,80-bf:6c00-6fff,7c00-7fff\n"
" rom name=program.rom size=0x", hex(rom_size), "\n"
" map address=00-3f,80-bf:8000-ffff mask=0x8000\n"
" ram name=save.ram size=0\n"
" map address=70-77:0000-7fff mask=0x8000\n"
" drom name=cx4.data.rom size=0xc00\n"
" dram name=cx4.data.ram size=0xc00 volatile\n"
" map address=00-3f,80-bf:6000-6bff,7000-7bff mask=0xf000\n"
);
}
else if(has_spc7110) {
markup.append(
" spc7110\n"
" map address=00-3f,80-bf:4800-483f\n"
" map address=50,58:0000-ffff\n"
" map=mcu address=00-3f,80-bf:8000-ffff mask=0x800000\n"
" map=mcu address=c0-ff:0000-ffff mask=0xc00000\n"
" prom name=program.rom size=0x100000\n"
" drom name=data.rom size=0x", hex(rom_size - 0x100000), "\n"
" ram name=save.ram size=0x", hex(ram_size), "\n"
" map address=00-3f,80-bf:6000-7fff mask=0xe000\n"
);
}
else if(has_sdd1) {
markup.append(
" sdd1\n"
" map address=00-3f,80-bf:4800-480f\n"
" rom name=program.rom size=0x", hex(rom_size), "\n"
" map address=00-3f,80-bf:8000-ffff\n"
" map address=c0-ff:0000-ffff\n"
);
if(ram_size > 0) markup.append(
" ram name=save.ram size=0x", hex(ram_size), "\n"
" map address=00-3f,80-bf:6000-7fff mask=0xe000\n"
" map address=70-73:0000-ffff mask=0x8000\n"
);
}
else if(type == Type::LoROM) {
markup.append(
" rom name=program.rom size=0x", hex(rom_size), "\n"
" map address=00-7d,80-ff:8000-ffff mask=0x8000\n"
" map address=40-6f,c0-ef:0000-7fff mask=0x8000\n"
);
if(ram_size > 0) markup.append(
" ram name=save.ram size=0x", hex(ram_size), "\n"
" map address=70-7d,f0-ff:", range, "\n"
);
}
else if(type == Type::HiROM) {
markup.append(
" rom name=program.rom size=0x", hex(rom_size), "\n"
" map address=00-3f,80-bf:8000-ffff\n"
" map address=40-7f,c0-ff:0000-ffff\n"
);
if(ram_size > 0) markup.append(
" ram name=save.ram size=0x", hex(ram_size), "\n"
" map address=10-3f,90-bf:6000-7fff mask=0xe000\n"
);
}
else if(type == Type::ExLoROM) {
markup.append(
" rom name=program.rom size=0x", hex(rom_size), "\n"
" map address=00-3f,80-bf:8000-ffff mask=0x8000\n"
" map address=40-7d:0000-ffff\n"
);
if(ram_size > 0) markup.append(
" ram name=save.ram size=0x", hex(ram_size), "\n"
" map address=20-3f,a0-bf:6000-7fff mask=0xe000\n"
" map address=70-7d:0000-7fff mask=0x8000\n"
);
}
else if(type == Type::ExHiROM) {
markup.append(
" rom name=program.rom size=0x", hex(rom_size), "\n"
" map address=00-3f:8000-ffff base=0x400000\n"
" map address=40-7d:0000-ffff base=0x400000\n"
" map address=80-bf:8000-ffff mask=0xc00000\n"
" map address=c0-ff:0000-ffff mask=0xc00000\n"
);
if(ram_size > 0) markup.append(
" ram name=save.ram size=0x", hex(ram_size), "\n"
" map address=20-3f,a0-bf:6000-7fff mask=0xe000\n"
" map address=70-7d:", range, "\n"
);
}
else if(type == Type::SuperFX) {
markup.append(
" superfx\n"
" map address=00-3f,80-bf:3000-34ff\n"
" rom name=program.rom size=0x", hex(rom_size), "\n"
" map address=00-3f,80-bf:8000-ffff mask=0x8000\n"
" map address=40-5f,c0-df:0000-ffff\n"
);
if(ram_size > 0) markup.append(
" ram name=save.ram size=0x", hex(ram_size), "\n"
" map address=00-3f,80-bf:6000-7fff size=0x2000\n"
" map address=70-71,f0-f1:0000-ffff\n"
);
}
else if(type == Type::SA1) {
markup.append(
" sa1\n"
" map address=00-3f,80-bf:2200-23ff\n"
" rom name=program.rom size=0x", hex(rom_size), "\n"
" map address=00-3f,80-bf:8000-ffff mask=0x408000\n"
" map address=c0-ff:0000-ffff\n"
);
if(ram_size > 0) markup.append(
" bwram name=save.ram size=0x", hex(ram_size), "\n"
" map address=00-3f,80-bf:6000-7fff size=0x2000\n"
" map address=40-4f:0000-ffff\n"
);
markup.append(
" iram id=internal size=0x800 volatile\n"
" map address=00-3f,80-bf:3000-37ff size=0x800\n"
);
}
else if(type == Type::LoROMSatellaview) {
markup.append(
" rom name=program.rom size=0x", hex(rom_size), "\n"
" map address=00-1f:8000-ffff base=0x000000 mask=0x8000\n"
" map address=20-3f:8000-ffff base=0x100000 mask=0x8000\n"
" map address=80-9f:8000-ffff base=0x200000 mask=0x8000\n"
" map address=a0-bf:8000-ffff base=0x100000 mask=0x8000\n"
" ram name=save.ram size=0x", hex(ram_size), "\n"
" map address=70-7d,f0-ff:0000-7fff mask=0x8000\n"
" bsmemory\n"
" map address=c0-ef:0000-ffff\n"
);
}
else if(type == Type::HiROMSatellaview) {
markup.append(
" rom name=program.rom size=0x", hex(rom_size), "\n"
" map address=00-1f,80-9f:8000-ffff\n"
" map address=40-5f,c0-df:0000-ffff\n"
" ram name=save.ram size=0x", hex(ram_size), "\n"
" map address=20-3f,a0-bf:6000-7fff\n"
" bsmemory\n"
" map address=20-3f,a0-bf:8000-ffff\n"
" map address=60-7f,e0-ff:0000-ffff\n"
);
}
else if(type == Type::CampusChallenge92) {
markup.append(
" event=CC92 timer=360\n"
" map address=c0,e0:0000\n"
" map=mcu address=00-1f,80-9f:8000-ffff\n"
" rom name=program.rom size=0x40000\n"
" rom name=slot-1.rom size=0x80000\n"
" rom name=slot-2.rom size=0x80000\n"
" rom name=slot-3.rom size=0x80000\n"
" ram name=save.ram size=0x2000 volatile\n"
" map address=70-7d,f0-ff:0000-7fff mask=0x8000\n"
" necdsp model=uPD7725 frequency=8000000\n"
" map address=20-3f,a0-bf:8000-ffff mask=0x7fff\n"
" prom name=dsp1.program.rom size=0x1800\n"
" drom name=dsp1.data.rom size=0x800\n"
" dram name=dsp1.data.ram size=0x200 volatile\n"
);
return;
}
else if(type == Type::Powerfest94) {
markup.append(
" event=PF94 timer=360\n"
" map address=10,20:6000\n"
" map=mcu address=00-3f,80-bf:8000-ffff\n"
" map=mcu address=c0-ff:0000-ffff\n"
" rom name=program.rom size=0x40000\n"
" rom name=slot-1.rom size=0x80000\n"
" rom name=slot-2.rom size=0x80000\n"
" rom name=slot-3.rom size=0x100000\n"
" ram name=save.ram size=0x2000 volatile\n"
" map address=30-3f,b0-bf:6000-7fff mask=0xe000\n"
" necdsp model=uPD7725 frequency=8000000\n"
" map address=00-0f,80-8f:6000-7fff mask=0xfff\n"
" prom name=dsp1.program.rom size=0x1800\n"
" drom name=dsp1.data.rom size=0x800\n"
" dram name=dsp1.data.ram size=0x200 volatile\n"
);
return;
}
if(has_sharprtc) {
markup.append(
" sharprtc\n"
" map address=00-3f,80-bf:2800-2801\n"
" ram name=rtc.ram size=0x10\n"
);
}
if(has_epsonrtc) {
markup.append(
" epsonrtc\n"
" map address=00-3f,80-bf:4840-4842\n"
" ram name=rtc.ram size=0x10\n"
);
}
if(has_obc1) {
markup.append(
" obc1\n"
" map address=00-3f,80-bf:6000-7fff mask=0xe000\n"
" ram name=save.ram size=0x2000\n"
);
}
if(has_dsp1) {
markup.append(
" necdsp model=uPD7725 frequency=8000000\n"
);
if(dsp1_type == DSP1Type::LoROM1MB) markup.append(
" map address=20-3f,a0-bf:8000-ffff mask=0x3fff\n"
);
if(dsp1_type == DSP1Type::LoROM2MB) markup.append(
" map address=60-6f,e0-ef:0000-7fff mask=0x3fff\n"
);
if(dsp1_type == DSP1Type::HiROM) markup.append(
" map address=00-1f,80-9f:6000-7fff mask=0xfff\n"
);
markup.append(
" prom name=dsp1b.program.rom size=0x1800\n"
" drom name=dsp1b.data.rom size=0x800\n"
" dram name=dsp1b.data.ram size=0x200 volatile\n"
);
}
if(has_dsp2) {
markup.append(
" necdsp model=uPD7725 frequency=8000000\n"
" map address=20-3f,a0-bf:8000-ffff mask=0x3fff\n"
" prom name=dsp2.program.rom size=0x1800\n"
" drom name=dsp2.data.rom size=0x800\n"
" dram name=dsp2.data.ram size=0x200 volatile\n"
);
}
if(has_dsp3) {
markup.append(
" necdsp model=uPD7725 frequency=8000000\n"
" map address=20-3f,a0-bf:8000-ffff mask=0x3fff\n"
" prom name=dsp3.program.rom size=0x1800\n"
" drom name=dsp3.data.rom size=0x800\n"
" dram name=dsp3.data.ram size=0x200 volatile\n"
);
}
if(has_dsp4) {
markup.append(
" necdsp model=uPD7725 frequency=8000000\n"
" map address=30-3f,b0-bf:8000-ffff mask=0x3fff\n"
" prom name=dsp4.program.rom size=0x1800\n"
" drom name=dsp4.data.rom size=0x800\n"
" dram name=dsp4.data.ram size=0x200 volatile\n"
);
}
if(has_st010) {
markup.append(
" necdsp model=uPD96050 frequency=11000000\n"
" map address=60-67,e0-e7:0000-3fff\n"
" prom name=st010.program.rom size=0xc000\n"
" drom name=st010.data.rom size=0x1000\n"
" dram name=save.ram size=0x1000\n"
" map address=68-6f,e8-ef:0000-7fff mask=0x8000\n"
);
}
if(has_st011) {
markup.append(
" necdsp model=uPD96050 frequency=15000000\n"
" map address=60-67,e0-e7:0000-3fff\n"
" prom name=st011.program.rom size=0xc000\n"
" drom name=st011.data.rom size=0x1000\n"
" dram name=save.ram size=0x1000\n"
" map address=68-6f,e8-ef:0000-7fff mask=0x8000\n"
);
}
if(has_st018) {
markup.append(
" armdsp frequency=21477272\n"
" map address=00-3f,80-bf:3800-38ff\n"
" prom name=st018.program.rom size=0x20000\n"
" drom name=st018.data.rom size=0x8000\n"
" ram name=save.ram size=0x4000\n"
);
}
if(has_msu1) {
markup.append(
" msu1\n"
" map address=00-3f,80-bf:2000-2007\n"
" rom name=msu1.rom\n"
);
}
}
auto SuperFamicomCartridge::readHeader(const uint8_t* data, uint size) -> void {
//detect Game Boy carts
if(size >= 0x0140) {
if(data[0x0104] == 0xce && data[0x0105] == 0xed && data[0x0106] == 0x66 && data[0x0107] == 0x66
&& data[0x0108] == 0xcc && data[0x0109] == 0x0d && data[0x010a] == 0x00 && data[0x010b] == 0x0b) {
type = Type::GameBoy;
return;
}
}
const uint index = findHeader(data, size);
const uint8_t mapperid = data[index + Mapper];
const uint8_t rom_type = data[index + RomType];
const uint8_t rom_size = data[index + RomSize];
const uint8_t company = data[index + Company];
const uint8_t regionid = data[index + CartRegion] & 0x7f;
const uint16_t complement = data[index + Complement] | data[index + Complement + 1] << 8;
const uint16_t checksum = data[index + Checksum] | data[index + Checksum + 1] << 8;
this->rom_size = size;
ram_size = 1024 << (data[index + RamSize] & 7);
if(ram_size == 1024) ram_size = 0; //no RAM present
if(rom_size == 0 && ram_size) ram_size = 0; //fix for Bazooka Blitzkrieg's malformed header (swapped ROM and RAM sizes)
//0, 1, 13 = NTSC; 2 - 12 = PAL
region = (regionid <= 1 || regionid >= 13) ? Region::NTSC : Region::PAL;
//detect BS-X flash carts
if(data[index + 0x13] == 0x00 || data[index + 0x13] == 0xff) {
if(data[index + 0x14] == 0x00) {
const uint8_t n15 = data[index + 0x15];
if(n15 == 0x00 || n15 == 0x80 || n15 == 0x84 || n15 == 0x9c || n15 == 0xbc || n15 == 0xfc) {
if(data[index + 0x1a] == 0x33 || data[index + 0x1a] == 0xff) {
type = Type::Satellaview;
region = Region::NTSC; //BS-X only released in Japan
return;
}
}
}
}
//detect Sufami Turbo carts
if(!memcmp(data, "BANDAI SFC-ADX", 14)) {
if(!memcmp(data + 16, "SFC-ADX BACKUP", 14)) {
type = Type::SufamiTurboBIOS;
} else {
type = Type::SufamiTurbo;
}
region = Region::NTSC; //Sufami Turbo only released in Japan
return; //RAM size handled outside this routine
}
//detect Super Game Boy BIOS
if(!memcmp(data + index, "Super GAMEBOY2", 14)) {
type = Type::SuperGameBoy2BIOS;
return;
}
if(!memcmp(data + index, "Super GAMEBOY", 13)) {
type = Type::SuperGameBoy1BIOS;
return;
}
//detect competition carts
if(!memcmp(data + index, "\x00\x08\x22\x02\x1c\x00\x10\x00\x08\x65\x80\x84\x20\x00\x22\x25\x00\x83\x0c\x80\x10", 21)
&& complement == 0x0100 && checksum == 0x2d02 && (size == 0x1c0000 || size == 0x1c2000)) {
type = Type::CampusChallenge92; //dark title screen version
return;
}
if(!memcmp(data + index, "\xc9\x80\x80\x44\x15\x00\x62\x09\x29\xa0\x52\x70\x50\x12\x05\x35\x31\x63\xc0\x22\x01", 21)
&& complement == 0x2011 && checksum == 0xf8c0 && (size == 0x240000 || size == 0x242000)) {
type = Type::Powerfest94; //10,000 points version
return;
}
if(!memcmp(data + index, "PREHISTORIK MAN ", 21)
&& complement == 0xffff && checksum == 0x0000 && (size == 0x240000 || size == 0x242000)) {
type = Type::Powerfest94; //1,000,000 points version
return;
}
//detect presence of BS-X flash cartridge connector (reads extended header information)
if(data[index - 14] == 'Z') {
if(data[index - 11] == 'J') {
uint8_t n13 = data[index - 13];
if((n13 >= 'A' && n13 <= 'Z') || (n13 >= '0' && n13 <= '9')) {
if(company == 0x33 || (data[index - 10] == 0x00 && data[index - 4] == 0x00)) {
has_bsx_slot = true;
}
}
}
}
if(has_bsx_slot) {
if(!memcmp(data + index, "Satellaview BS-X ", 21)) {
//BS-X base cart
type = Type::SatellaviewBIOS;
region = Region::NTSC; //BS-X only released in Japan
return; //RAM size handled internally by load_cart_bsx() -> BSXCart class
} else {
type = (index == 0x7fc0 ? Type::LoROMSatellaview : Type::HiROMSatellaview);
region = Region::NTSC; //BS-X slotted cartridges only released in Japan
}
} else {
//standard cart
if(index == 0x7fc0 && size >= 0x401000) {
type = Type::ExLoROM;
} else if(index == 0x7fc0 && mapperid == 0x32) {
type = Type::ExLoROM;
} else if(index == 0x7fc0) {
type = Type::LoROM;
} else if(index == 0xffc0) {
type = Type::HiROM;
} else if(index == 0x40ffc0) {
type = Type::ExHiROM;
} else {
type = Type::Unknown;
return;
}
}
if(mapperid == 0x20 && (rom_type == 0x13 || rom_type == 0x14 || rom_type == 0x15 || rom_type == 0x1a)) {
has_superfx = true;
type = Type::SuperFX;
ram_size = 1024 << (data[index - 3] & 7);
if(ram_size == 1024) ram_size = 0;
}
if(mapperid == 0x23 && (rom_type == 0x32 || rom_type == 0x34 || rom_type == 0x35)) {
has_sa1 = true;
type = Type::SA1;
}
if(mapperid == 0x35 && rom_type == 0x55) {
has_sharprtc = true;
}
if(mapperid == 0x32 && (rom_type == 0x43 || rom_type == 0x45)) {
has_sdd1 = true;
}
if(mapperid == 0x3a && (rom_type == 0xf5 || rom_type == 0xf9)) {
has_spc7110 = true;
has_epsonrtc = (rom_type == 0xf9);
}
if(mapperid == 0x20 && rom_type == 0xf3) {
has_cx4 = true;
}
if((mapperid == 0x20 || mapperid == 0x21) && rom_type == 0x03) {
has_dsp1 = true;
}
if(mapperid == 0x30 && rom_type == 0x05 && company != 0xb2) {
has_dsp1 = true;
}
if(mapperid == 0x31 && (rom_type == 0x03 || rom_type == 0x05)) {
has_dsp1 = true;
}
if(has_dsp1 == true) {
if((mapperid & 0x2f) == 0x20 && size <= 0x100000) {
dsp1_type = DSP1Type::LoROM1MB;
} else if((mapperid & 0x2f) == 0x20) {
dsp1_type = DSP1Type::LoROM2MB;
} else if((mapperid & 0x2f) == 0x21) {
dsp1_type = DSP1Type::HiROM;
}
}
if(mapperid == 0x20 && rom_type == 0x05) {
has_dsp2 = true;
}
if(mapperid == 0x30 && rom_type == 0x05 && company == 0xb2) {
has_dsp3 = true;
}
if(mapperid == 0x30 && rom_type == 0x03) {
has_dsp4 = true;
}
if(mapperid == 0x30 && rom_type == 0x25) {
has_obc1 = true;
}
if(mapperid == 0x30 && rom_type == 0xf6 && rom_size >= 10) {
has_st010 = true;
}
if(mapperid == 0x30 && rom_type == 0xf6 && rom_size < 10) {
has_st011 = true;
}
if(mapperid == 0x30 && rom_type == 0xf5) {
has_st018 = true;
}
}
auto SuperFamicomCartridge::findHeader(const uint8_t* data, uint size) -> uint {
uint score_lo = scoreHeader(data, size, 0x007fc0);
uint score_hi = scoreHeader(data, size, 0x00ffc0);
uint score_ex = scoreHeader(data, size, 0x40ffc0);
if(score_ex) score_ex += 4; //favor ExHiROM on images > 32mbits
if(score_lo >= score_hi && score_lo >= score_ex) {
return 0x007fc0;
} else if(score_hi >= score_ex) {
return 0x00ffc0;
} else {
return 0x40ffc0;
}
}
auto SuperFamicomCartridge::scoreHeader(const uint8_t* data, uint size, uint addr) -> uint {
if(size < addr + 64) return 0; //image too small to contain header at this location?
int score = 0;
uint16_t resetvector = data[addr + ResetVector] | (data[addr + ResetVector + 1] << 8);
uint16_t checksum = data[addr + Checksum ] | (data[addr + Checksum + 1] << 8);
uint16_t complement = data[addr + Complement ] | (data[addr + Complement + 1] << 8);
uint8_t resetop = data[(addr & ~0x7fff) | (resetvector & 0x7fff)]; //first opcode executed upon reset
uint8_t mapper = data[addr + Mapper] & ~0x10; //mask off irrelevent FastROM-capable bit
//$00:[000-7fff] contains uninitialized RAM and MMIO.
//reset vector must point to ROM at $00:[8000-ffff] to be considered valid.
if(resetvector < 0x8000) return 0;
//some images duplicate the header in multiple locations, and others have completely
//invalid header information that cannot be relied upon.
//below code will analyze the first opcode executed at the specified reset vector to
//determine the probability that this is the correct header.
//most likely opcodes
if(resetop == 0x78 //sei
|| resetop == 0x18 //clc (clc; xce)
|| resetop == 0x38 //sec (sec; xce)
|| resetop == 0x9c //stz $nnnn (stz $4200)
|| resetop == 0x4c //jmp $nnnn
|| resetop == 0x5c //jml $nnnnnn
) score += 8;
//plausible opcodes
if(resetop == 0xc2 //rep #$nn
|| resetop == 0xe2 //sep #$nn
|| resetop == 0xad //lda $nnnn
|| resetop == 0xae //ldx $nnnn
|| resetop == 0xac //ldy $nnnn
|| resetop == 0xaf //lda $nnnnnn
|| resetop == 0xa9 //lda #$nn
|| resetop == 0xa2 //ldx #$nn
|| resetop == 0xa0 //ldy #$nn
|| resetop == 0x20 //jsr $nnnn
|| resetop == 0x22 //jsl $nnnnnn
) score += 4;
//implausible opcodes
if(resetop == 0x40 //rti
|| resetop == 0x60 //rts
|| resetop == 0x6b //rtl
|| resetop == 0xcd //cmp $nnnn
|| resetop == 0xec //cpx $nnnn
|| resetop == 0xcc //cpy $nnnn
) score -= 4;
//least likely opcodes
if(resetop == 0x00 //brk #$nn
|| resetop == 0x02 //cop #$nn
|| resetop == 0xdb //stp
|| resetop == 0x42 //wdm
|| resetop == 0xff //sbc $nnnnnn,x
) score -= 8;
//at times, both the header and reset vector's first opcode will match ...
//fallback and rely on info validity in these cases to determine more likely header.
//a valid checksum is the biggest indicator of a valid header.
if((checksum + complement) == 0xffff && (checksum != 0) && (complement != 0)) score += 4;
if(addr == 0x007fc0 && mapper == 0x20) score += 2; //0x20 is usually LoROM
if(addr == 0x00ffc0 && mapper == 0x21) score += 2; //0x21 is usually HiROM
if(addr == 0x007fc0 && mapper == 0x22) score += 2; //0x22 is usually ExLoROM
if(addr == 0x40ffc0 && mapper == 0x25) score += 2; //0x25 is usually ExHiROM
if(data[addr + Company] == 0x33) score += 2; //0x33 indicates extended header
if(data[addr + RomType] < 0x08) score++;
if(data[addr + RomSize] < 0x10) score++;
if(data[addr + RamSize] < 0x08) score++;
if(data[addr + CartRegion] < 14) score++;
if(score < 0) score = 0;
return score;
}