Update to v106r05 release.

byuu says:

Changelog:

  - Super Famicom: added remaining generic board types
  - icarus: improved Super Famicom heuristics
  - icarus: reworked BS Memory heuristics
  - icarus: reworked Sufami Turbo heuristics

Notes: this is really complicated, and is going to take a long time to
work 100% smoothly again.

Starting off, I am trying to get rid of the weird edge case zero-byte
SRAM mapping for the Cx4. It has the RAM region present, but returns
logic low (0x00) instead of open bus, when SRAM isn't present. I started
by making it `map=ram` instead of `ram/map`, which is gross, and then it ended
up detecing the map tag ending in RAM and pulling the Cx4 data RAM into that
slot. Ugh. The preservation board mapping is still as it was before and will
need to be updated once I get the syntax down.

The BS Memory and Sufami Turbo moving to the new `game/memory`
ending means I can't use the SuperFamicom::Cartridge::loadMemory
function that looks at the old-style rom/ram tags. Because I didn't
write more code, the result is those sub-carts won't load now.

The old heuristics were short-circuiting on SA1 before bothering with
BS-X slots, so that's why SD Gundam G-Next wasn't asking for a data
pack. The problem is, I don't know where the BS-X pack maps to on this
cartridge. It's at c0-ef on the other BS-X slotted cartridges, but
that's mapped to the SA1 on regular SA1 cartridges, so ... for now, it's
not actually mapped in.

I'm still struggling with naming conventions on all these boards. I'll
make a public post about that, though.
This commit is contained in:
Tim Allen 2018-02-11 08:45:44 +11:00
parent c38a771f22
commit 5e330da4e8
10 changed files with 253 additions and 969 deletions

View File

@ -12,7 +12,7 @@ using namespace nall;
namespace Emulator {
static const string Name = "higan";
static const string Version = "106.04";
static const string Version = "106.05";
static const string Author = "byuu";
static const string License = "GPLv3";
static const string Website = "https://byuu.org/";

View File

@ -50,7 +50,7 @@ auto Cartridge::loadBoard(Markup::Node node) -> Markup::Node {
line.append(" size=", rom.left()["size"].text());
rom.removeLeft();
}
if(line.endsWith("ram")) {
if(line.endsWith("ram") && ram) {
line.append(" name=", ram.left()["name"].text());
line.append(" size=", ram.left()["size"].text());
if(ram.left()["type"].text() == "RAM") line.append(" volatile");
@ -115,14 +115,14 @@ auto Cartridge::loadGameBoy(Markup::Node node) -> void {
}
auto Cartridge::loadBSMemory(Markup::Node node) -> void {
information.title.bsMemory = node["information/title"].text();
information.title.bsMemory = node["game/label"].text();
bsmemory.readonly = (node["board/rom/type"].text() == "mrom");
loadMemory(bsmemory.memory, node["board/rom"], File::Required, bsmemory.pathID);
}
auto Cartridge::loadSufamiTurboA(Markup::Node node) -> void {
information.title.sufamiTurboA = node["information/title"].text();
information.title.sufamiTurboA = node["game/label"].text();
loadMemory(sufamiturboA.rom, node["board/rom"], File::Required, sufamiturboA.pathID);
loadMemory(sufamiturboA.ram, node["board/ram"], File::Optional, sufamiturboA.pathID);
@ -136,7 +136,7 @@ auto Cartridge::loadSufamiTurboA(Markup::Node node) -> void {
}
auto Cartridge::loadSufamiTurboB(Markup::Node node) -> void {
information.title.sufamiTurboB = node["information/title"].text();
information.title.sufamiTurboB = node["game/label"].text();
loadMemory(sufamiturboB.rom, node["board/rom"], File::Required, sufamiturboB.pathID);
loadMemory(sufamiturboB.ram, node["board/ram"], File::Optional, sufamiturboB.pathID);

View File

@ -1,5 +1,5 @@
database
revision: 2018-02-08
revision: 2018-02-10
//Boards (Production)
@ -414,19 +414,75 @@ board: YJ0N-01
//Boards (Generic)
database
revision: 2018-02-08
revision: 2018-02-10
board: CX4
board: BS-HIROM-NVRAM
rom
map address=00-1f,80-9f:8000-ffff
map address=40-5f,c0-df:0000-ffff
ram
map address=20-3f,a0-bf:6000-7fff mask=0xe000
bsmemory
map address=20-3f,a0-bf:8000-ffff
map address=60-7d,e0-ff:0000-ffff
board: BS-LOROM-NVRAM
rom
map address=00-1f:8000-ffff base=0x000000 mask=0x8000
map address=20-3f:8000-ffff base=0x100000 mask=0x8000
map address=80-9f:8000-ffff base=0x200000 mask=0x8000
map address=a0-bf:8000-ffff base=0x100000 mask=0x8000
ram
map address=70-7d,f0-ff:0000-7fff mask=0x8000
bsmemory
map address=c0-ef:0000-ffff
board: BS-NVRAM
ram
map address=10-1f:5000-5fff mask=0xf000
mcc
map address=00-0f:5000
map=mcu address=00-3f,80-bf:8000-ffff mask=0x408000
map=mcu address=40-7d,c0-ff:0000-ffff
rom
ram
map address=00-3f,80-bf:6000-7fff mask=0xe000
bsmemory
board: BS-SA1-NVRAM
sa1
map address=00-3f,80-bf:2200-23ff
rom
map address=00-3f,80-bf:8000-ffff mask=0x408000
map address=c0-ff:0000-ffff
bwram
map address=00-3f,80-bf:6000-7fff size=0x2000
map address=40-4f:0000-ffff
iram
map address=00-3f,80-bf:3000-37ff size=0x800
bsmemory
map
board: CX4-LOROM
hitachidsp model=HG51B169 frequency=20000000
map address=00-3f,80-bf:6c00-6fff,7c00-7fff
map=ram address=70-77:0000-7fff mask=0x8000
rom
map address=00-3f,80-bf:8000-ffff mask=0x8000
ram
map address=70-77:0000-7fff mask=0x8000
drom
dram
map address=00-3f,80-bf:6000-6bff,7000-7bff mask=0xf000
board: DSP-HIROM
rom
map address=00-3f,80-bf:8000-ffff
map address=40-7d,c0-ff:0000-ffff
necdsp model=uPD7725 frequency=8000000
map address=00-1f,80-9f:6000-7fff mask=0xfff
prom
drom
dram
board: DSP-HIROM-NVRAM
rom
map address=00-3f,80-bf:8000-ffff
@ -469,6 +525,12 @@ board: EXHIROM-NVRAM
map address=20-3f,a0-bf:6000-7fff mask=0xe000
map address=70-7d:0000-7fff
board: EXLOROM-NVRAM
rom
map address=00-7d,80-ff:8000-ffff mask=0x8000
ram
map address=70-7d,f0-ff:0000-7fff mask=0x8000
board: HIROM
rom
map address=00-3f,80-bf:8000-ffff
@ -483,13 +545,13 @@ board: HIROM-NVRAM
board: LOROM
rom
map address=00-7d,f0-ff:8000-ffff mask=0x8000
map address=00-7d,80-ff:8000-ffff mask=0x8000
board: LOROM-NVRAM
rom
map address=00-7d,f0-ff:8000-ffff mask=0x8000
map address=00-6f,80-ef:8000-ffff mask=0x8000
ram
map address=70-7d,f0-ff:0000-7fff mask=0x8000
map address=70-7d,f0-ff:0000-ffff mask=0x8000
board: OBC1-LOROM-NVRAM
rom
@ -541,19 +603,7 @@ board: SDD1-NVRAM
map address=00-3f,80-bf:6000-7fff mask=0xe000
map address=70-73:0000-ffff mask=0x8000
board: SETA-LOROM-NVRAM
rom
map address=00-7d,80-ff:8000-ffff mask=0x8000
map address=40-6f,c0-ef:0000-7fff mask=0x8000
ram
map address=70-7d,f0-ff:0000-ffff
armdsp frequency=21477272
map address=00-3f,80-bf:3800-38ff
prom
drom
ram
board: SGB
board: SGB-LOROM
rom
map address=00-7d,80-ff:8000-ffff mask=0x8000
map address=40-7d,c0-ff:0000-7fff mask=0x8000
@ -561,6 +611,17 @@ board: SGB
map address=00-3f,80-bf:6000-67ff,7000-7fff
rom
board: SPC7110-NVRAM
spc7110
map address=00-3f,80-bf:4800-483f
map address=50,58:0000-ffff
map=mcu address=00-3f,80-bf:8000-ffff mask=0x800000
map=mcu address=c0-ff:0000-ffff mask=0xc00000
prom
drom
ram
map address=00-3f,80-bf:6000-7fff mask=0xe000
board: SPC7110-RTC-NVRAM
spc7110
map address=00-3f,80-bf:4800-483f
@ -575,7 +636,32 @@ board: SPC7110-RTC-NVRAM
map address=00-3f,80-bf:4800-4842
ram
board: ST-LOROM-BATTERY
board: ST-LOROM
rom
map address=00-1f,80-9f:8000-ffff mask=0x8000
sufamiturbo
rom
map address=20-3f,a0-bf:8000-ffff mask=0x8000
ram
map address=60-6f,e0-ef:0000-ffff
sufamiturbo
rom
map address=40-5f,c0-df:0000-7fff mask=0x8000
map address=40-5f,c0-df:8000-ffff mask=0x8000
ram
map address=70-7d,f0-ff:0000-ffff
board: ST010-LOROM-BATTERY
rom
map address=00-7d,80-ff:8000-ffff mask=0x8000
necdsp model=uPD96050 frequency=11000000
map address=60-67,e0-e7:0000-3fff
prom
drom
dram
map address=68-6f,e8-ef:0000-7fff mask=0x8000
board: ST011-LOROM-BATTERY
rom
map address=00-7d,80-ff:8000-ffff mask=0x8000
necdsp model=uPD96050 frequency=15000000
@ -585,6 +671,18 @@ board: ST-LOROM-BATTERY
dram
map address=68-6f,e8-ef:0000-7fff mask=0x8000
board: ST018-LOROM-NVRAM
rom
map address=00-7d,80-ff:8000-ffff mask=0x8000
map address=40-6f,c0-ef:0000-7fff mask=0x8000
ram
map address=70-7d,f0-ff:0000-ffff
armdsp frequency=21477272
map address=00-3f,80-bf:3800-38ff
prom
drom
ram
board: SUPERFX-(RAM,NVRAM)
superfx
map address=00-3f,80-bf:3000-34ff

View File

@ -5,30 +5,20 @@ auto Icarus::bsMemoryManifest(string location) -> string {
}
auto Icarus::bsMemoryManifest(vector<uint8_t>& buffer, string location) -> string {
string markup;
string digest = Hash::SHA256(buffer.data(), buffer.size()).digest();
string digest = Hash::SHA256(buffer).digest();
if(settings["icarus/UseDatabase"].boolean() && !markup) {
for(auto node : database.bsMemory) {
if(node["sha256"].text() == digest) {
markup.append(node.text(), "\n sha256: ", digest, "\n");
break;
}
if(settings["icarus/UseDatabase"].boolean()) {
for(auto game : database.bsMemory.find("game")) {
if(game["sha256"].text() == digest) return BML::serialize(game);
}
}
if(settings["icarus/UseHeuristics"].boolean() && !markup) {
BSMemoryCartridge cartridge{buffer.data(), buffer.size()};
if(markup = cartridge.markup) {
markup.append("\n");
markup.append("information\n");
markup.append(" title: ", Location::prefix(location), "\n");
markup.append(" sha256: ", digest, "\n");
markup.append(" note: ", "heuristically generated by icarus\n");
}
if(settings["icarus/UseHeuristics"].boolean()) {
Heuristics::BSMemory game{buffer.data(), buffer.size()};
if(string manifest = game.manifest()) return manifest;
}
return markup;
return "";
}
auto Icarus::bsMemoryImport(vector<uint8_t>& buffer, string location) -> string {

View File

@ -5,30 +5,20 @@ auto Icarus::sufamiTurboManifest(string location) -> string {
}
auto Icarus::sufamiTurboManifest(vector<uint8_t>& buffer, string location) -> string {
string markup;
string digest = Hash::SHA256(buffer.data(), buffer.size()).digest();
string digest = Hash::SHA256(buffer).digest();
if(settings["icarus/UseDatabase"].boolean() && !markup) {
for(auto node : database.sufamiTurbo) {
if(node["sha256"].text() == digest) {
markup.append(node.text(), "\n sha256: ", digest, "\n");
break;
}
if(settings["icarus/UseDatabase"].boolean()) {
for(auto game : database.sufamiTurbo.find("game")) {
if(game["sha256"].text() == digest) return BML::serialize(game);
}
}
if(settings["icarus/UseHeuristics"].boolean() && !markup) {
SufamiTurboCartridge cartridge{buffer.data(), buffer.size()};
if(markup = cartridge.markup) {
markup.append("\n");
markup.append("information\n");
markup.append(" title: ", Location::prefix(location), "\n");
markup.append(" sha256: ", digest, "\n");
markup.append(" note: ", "heuristically generated by icarus\n");
}
if(settings["icarus/UseHeuristics"].boolean()) {
Heuristics::SufamiTurbo game{buffer.data(), buffer.size()};
if(string manifest = game.manifest()) return manifest;
}
return markup;
return "";
}
auto Icarus::sufamiTurboImport(vector<uint8_t>& buffer, string location) -> string {

View File

@ -15,24 +15,14 @@ auto Icarus::superFamicomManifest(vector<uint8_t>& buffer, string location) -> s
if(settings["icarus/UseDatabase"].boolean()) {
for(auto game : database.superFamicom.find("game")) {
if(game["sha256"].text() == digest) {
return BML::serialize(game);
}
if(game["sha256"].text() == digest) return BML::serialize(game);
}
}
if(settings["icarus/UseHeuristics"].boolean()) {
bool hasMSU1 = exists({location, "msu1.rom"});
Heuristics::SuperFamicom game{buffer.data(), buffer.size(), hasMSU1};
if(string manifest = game.manifest()) {
// markup.append("\n");
// markup.append("game\n");
// markup.append(" sha256: ", digest, "\n");
// markup.append(" region: ", cartridge.region == SuperFamicomCartridge::Region::NTSC ? "NTSC" : "PAL", "\n");
// markup.append(" label: ", Location::prefix(location), "\n");
// markup.append(" note: ", "heuristically generated by icarus\n");
return manifest;
}
if(string manifest = game.manifest()) return manifest;
}
return "";

View File

@ -1,10 +1,34 @@
struct BSMemoryCartridge {
BSMemoryCartridge(const uint8_t* data, uint size);
namespace Heuristics {
string markup;
struct BSMemory {
BSMemory(const uint8_t* data, uint size);
explicit operator bool() const;
auto manifest() const -> string;
private:
const uint8_t* data = nullptr;
uint size = 0;
};
BSMemoryCartridge::BSMemoryCartridge(const uint8_t* data, uint size) {
markup.append("board\n");
markup.append(" rom type=flash name=program.rom size=0x", hex(size), "\n");
BSMemory::BSMemory(const uint8_t* data, uint size) : data(data), size(size) {
}
BSMemory::operator bool() const {
return size == 0x100000;
}
auto BSMemory::manifest() const -> string {
if(!operator bool()) return "";
string output;
output.append("game\n");
output.append(" sha256: ", Hash::SHA256(data, size).digest(), "\n");
output.append(" memory\n");
output.append(" type: NAND\n");
output.append(" size: 0x", hex(size), "\n");
output.append(" name: program.rom\n");
return output;
}
}

View File

@ -1,18 +1,48 @@
struct SufamiTurboCartridge {
SufamiTurboCartridge(const uint8_t* data, unsigned size);
namespace Heuristics {
string markup;
struct SufamiTurbo {
SufamiTurbo(const uint8_t* data, uint size);
explicit operator bool() const;
auto manifest() const -> string;
private:
const uint8_t* data = nullptr;
uint size = 0;
};
SufamiTurboCartridge::SufamiTurboCartridge(const uint8_t* data, unsigned size) {
if(size < 0x20000) return; //too small to be a valid game?
if(memcmp(data, "BANDAI SFC-ADX", 14)) return; //missing required header?
unsigned romsize = data[0x36] * 0x20000; //128KB
unsigned ramsize = data[0x37] * 0x800; //2KB
bool linkable = data[0x35] != 0x00; //TODO: unconfirmed
markup.append("board", linkable ? " linkable" : "", "\n");
markup.append(" rom name=program.rom size=0x", hex(romsize), "\n");
if(ramsize)
markup.append(" ram name=save.ram size=0x", hex(ramsize), "\n");
SufamiTurbo::SufamiTurbo(const uint8_t* data, uint size) : data(data), size(size) {
}
SufamiTurbo::operator bool() const {
return size >= 0x20000;
}
auto SufamiTurbo::manifest() const -> string {
if(!operator bool()) return "";
uint romSize = data[0x36] * 0x20000; //128KB
uint ramSize = data[0x37] * 0x800; //2KB
bool linkable = data[0x35]; //TODO: unconfirmed
string output;
output.append("game\n");
if(linkable) {
output.append(" linkable\n");
}
if(romSize) {
output.append(" memory\n");
output.append(" type: ROM\n");
output.append(" size: 0x", hex(romSize), "\n");
output.append(" name: program.rom\n");
}
if(ramSize) {
output.append(" memory\n");
output.append(" type: NVRAM\n");
output.append(" size: 0x", hex(ramSize), "\n");
output.append(" name: save.ram\n");
}
return output;
}
}

View File

@ -12,6 +12,7 @@ struct SuperFamicom {
auto board() const -> string;
auto label() const -> string;
auto serial() const -> string;
auto romSize() const -> uint;
auto ramSize() const -> uint;
auto expansionRamSize() const -> uint;
@ -62,20 +63,20 @@ auto SuperFamicom::manifest() const -> string {
output.append(memory("ROM", size - 0x2000, "program.rom"));
output.append(memory("ROM", 0x1800, "dsp.program.rom"));
output.append(memory("ROM", 0x800, "dsp.data.rom"));
} else if(board.left() == "SETA") {
output.append(memory("ROM", size - 0x28000, "program.rom"));
output.append(memory("ROM", 0x20000, "seta.program.rom"));
output.append(memory("ROM", 0x8000, "seta.data.rom"));
} else if(board.left() == "SGB") {
output.append(memory("ROM", size - 0x100, "program.rom"));
output.append(memory("ROM", 0x100, "sgb.boot.rom"));
} else if(board.left() == "SPC7110") {
output.append(memory("ROM", 0x100000, "program.rom"));
output.append(memory("ROM", size - 0x100000, "data.rom"));
} else if(board.left() == "ST") {
} else if(board.left() == "ST010" || board.left() == "ST011") {
output.append(memory("ROM", size - 0xd000, "program.rom"));
output.append(memory("ROM", 0xc000, "st.program.rom"));
output.append(memory("ROM", 0x1000, "st.data.rom"));
} else if(board.left() == "ST018") {
output.append(memory("ROM", size - 0x28000, "program.rom"));
output.append(memory("ROM", 0x20000, "seta.program.rom"));
output.append(memory("ROM", 0x8000, "seta.data.rom"));
} else {
output.append(memory("ROM", size, "program.rom"));
}
@ -90,10 +91,9 @@ auto SuperFamicom::manifest() const -> string {
output.append(memory(type, size, "expansion.ram"));
}
if(board.left() == "BSX") {
if(board.left() == "BS" && board(1) == "NVRAM") {
output.append(memory("NVRAM", 0x80000, "download.ram"));
} else if(board.left() == "CX4") {
output.append(memory("RAM", 0, "save.ram"));
output.append(memory("RAM", 0xc00, "cx4.data.ram"));
} else if(board.left() == "DSP") {
output.append(memory("RAM", 0x200, "dsp.data.ram"));
@ -101,12 +101,12 @@ auto SuperFamicom::manifest() const -> string {
output.append(memory("NVRAM", 0x10, "rtc.ram"));
} else if(board.left() == "SA1") {
output.append(memory("RAM", 0x800, "internal.ram"));
} else if(board.left() == "SETA") {
output.append(memory("NVRAM", 0x4000, "seta.save.ram"));
} else if(board.left() == "SPC7110" && board(1) == "RTC") {
output.append(memory("NVRAM", 0x10, "rtc.ram"));
} else if(board.left() == "ST") {
} else if(board.left() == "ST010" || board.left() == "ST011") {
output.append(memory("NVRAM", 0x1000, "st.save.ram"));
} else if(board.left() == "ST018") {
output.append(memory("NVRAM", 0x4000, "seta.save.ram"));
}
if(hasMSU1) {
@ -250,8 +250,12 @@ auto SuperFamicom::board() const -> string {
if(headerAddress == 0x40ffb0) mode = "EXHIROM-";
}
if(serial() == "A9PJ") {
board.append("ST-");
if(serial() == "A9PJ") {
//Sufami Turbo (JPN)
board.append("ST-", mode);
} else if(serial() == "ZSBJ") {
//BS-X: Sore wa Namae o Nusumareta Machi no Monogatari (JPN)
board.append("BS-");
} else if(serial().match("Z\?\?J")) {
board.append("BS-", mode);
} else if(cartridgeTypeLo >= 0x3) {
@ -262,15 +266,16 @@ auto SuperFamicom::board() const -> string {
if(cartridgeTypeHi == 0x4) board.append("SDD1-");
if(cartridgeTypeHi == 0x5) board.append("RTC-", mode);
if(cartridgeTypeHi == 0xe && cartridgeTypeLo == 0x3) board.append("SGB-");
if(cartridgeTypeHi == 0xe && cartridgeTypeLo == 0x5) board.append("BSX-");
if(cartridgeTypeHi == 0xf && cartridgeSubType == 0x00 && cartridgeTypeLo == 0x5) board.append("SPC7110-");
if(cartridgeTypeHi == 0xf && cartridgeSubType == 0x00 && cartridgeTypeLo == 0x9) board.append("SPC7110-RTC-");
if(cartridgeTypeHi == 0xf && cartridgeSubType == 0x01) board.append("ST-");
if(cartridgeTypeHi == 0xf && cartridgeSubType == 0x02) board.append("SETA-");
if(cartridgeTypeHi == 0xf && cartridgeSubType == 0x10) board.append("CX4-");
} else {
board.append(mode);
if(cartridgeTypeHi == 0xf && cartridgeSubType == 0x01) board.append(romSize() == 0x100000 ? "ST010-" : "ST011-");
if(cartridgeTypeHi == 0xf && cartridgeSubType == 0x02) board.append("ST018-", mode);
if(cartridgeTypeHi == 0xf && cartridgeSubType == 0x10) board.append("CX4-", mode);
}
if(!board) board.append(mode);
//grow ROM region and restrict RAM region for large LOROM boards ("EXLOROM")
if(board.beginsWith("LOROM-") && romSize() > 0x380000 && ramSize()) board.prepend("EX");
if(cartridgeTypeLo == 0x1 || cartridgeTypeLo == 0x4) board.append("RAM-");
if(cartridgeTypeLo == 0x2 || cartridgeTypeLo == 0x5) board.append("NVRAM-");
@ -304,6 +309,19 @@ auto SuperFamicom::serial() const -> string {
return "";
}
auto SuperFamicom::romSize() const -> uint {
//subtract appended firmware size, if firmware is present
if((size & 0x7fff) == 0x100) return size - 0x100;
if((size & 0x7fff) == 0xc00) return size - 0xc00;
if((size & 0x7fff) == 0x2000) return size - 0x2000;
if((size & 0xffff) == 0xd000) return size - 0xd000;
if((size & 0x3ffff) == 0x28000) return size - 0x28000;
return size;
//auto romSize = data[headerAddress + 0x27] & 15;
//return 1024 << romSize;
}
auto SuperFamicom::ramSize() const -> uint {
auto ramSize = data[headerAddress + 0x28] & 7;
if(ramSize) return 1024 << ramSize;

View File

@ -1,856 +0,0 @@
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;
uint firmware_size = 0;
string firmware_missing;
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;
//ignore images too small to be valid
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 firmware
if(has_dsp1) {
if((size & 0x7fff) == 0x2000) {
firmware_size = 0x2000;
} else {
firmware_missing = "DSP1";
}
}
if(has_dsp2) {
if((size & 0x7fff) == 0x2000) {
firmware_size = 0x2000;
} else {
firmware_missing = "DSP2";
}
}
if(has_dsp3) {
if((size & 0x7fff) == 0x2000) {
firmware_size = 0x2000;
} else {
firmware_missing = "DSP3";
}
}
if(has_dsp4) {
if((size & 0x7fff) == 0x2000) {
firmware_size = 0x2000;
} else {
firmware_missing = "DSP4";
}
}
if(has_st010) {
if((size & 0xffff) == 0xd000) {
firmware_size = 0xd000;
} else {
firmware_missing = "ST010";
}
}
if(has_st011) {
if((size & 0xffff) == 0xd000) {
firmware_size = 0xd000;
} else {
firmware_missing = "ST011";
}
}
if(has_st018) {
if((size & 0x3ffff) == 0x28000) {
firmware_size = 0x28000;
} else {
firmware_missing = "ST018";
}
}
if(has_cx4) {
if((rom_size & 0x7fff) == 0xc00) {
firmware_size = 0xc00;
} else {
firmware_missing = "CX4";
}
}
if(type == Type::SuperGameBoy1BIOS) {
if((rom_size & 0x7fff) == 0x100) {
firmware_size = 0x100;
} else {
firmware_missing = "SGB1";
}
}
if(type == Type::SuperGameBoy2BIOS) {
if((rom_size & 0x7fff) == 0x100) {
firmware_size = 0x100;
} else {
firmware_missing = "SGB2";
}
}
rom_size -= firmware_size;
//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) {
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=sgb1.boot.rom size=0x100\n"
);
}
else if(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=2\n"
" map address=00-3f,80-bf:6000-67ff,7000-7fff\n"
" rom name=sgb2.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;
}