Added icarus 20150821.

This commit is contained in:
Tim Allen 2015-08-21 21:29:53 +10:00
parent 213879771e
commit 7081f46e45
24 changed files with 1967 additions and 0 deletions

1
.gitignore vendored
View File

@ -1 +1,2 @@
ananke/libananke.so ananke/libananke.so
icarus/icarus

29
icarus/GNUmakefile Normal file
View File

@ -0,0 +1,29 @@
include ../nall/GNUmakefile
include ../hiro/GNUmakefile
flags += -O3 -I..
link +=
objects := obj/hiro.o obj/icarus.o
objects += $(if $(call streq,$(platform),windows),obj/resource.o)
all: $(objects)
$(compiler) -o icarus $(objects) $(link) $(hirolink)
obj/hiro.o: ../hiro/hiro.cpp
$(compiler) $(hiroflags) -o obj/hiro.o -c ../hiro/hiro.cpp
obj/icarus.o: icarus.cpp $(call rwildcard,core/) $(call rwildcard,heuristics/) $(call rwildcard,ui/)
$(compiler) $(cppflags) $(flags) -o obj/icarus.o -c icarus.cpp
obj/resource.o:
windres ../hiro/windows/hiro.rc obj/resource.o
clean:
if [ -f ./icarus ]; then rm ./icarus; fi
$(call delete,obj/*.o)
install:
if [ -f ./icarus ]; then cp ./icarus $(prefix)/bin/icarus; fi
uninstall:
if [ -f $(prefix)/bin/icarus ]; then rm $(prefix)/bin/icarus; fi

View File

@ -0,0 +1,40 @@
auto Icarus::bsxSatellaviewManifest(const string& location) -> string {
vector<uint8_t> buffer;
concatenate(buffer, {location, "program.rom"});
return bsxSatellaviewManifest(buffer, location);
}
auto Icarus::bsxSatellaviewManifest(vector<uint8_t>& buffer, const string& location) -> string {
BsxSatellaviewCartridge cartridge{buffer.data(), buffer.size()};
if(auto markup = cartridge.markup) {
markup.append("\n");
markup.append("information\n");
markup.append(" sha256: ", Hash::SHA256(buffer.data(), buffer.size()).digest(), "\n");
markup.append(" title: ", location.prefixname(), "\n");
markup.append(" note: ", "heuristically generated by icarus\n");
return markup;
}
return "";
}
auto Icarus::bsxSatellaviewImport(vector<uint8_t>& buffer, const string& location) -> bool {
auto name = location.prefixname();
auto source = location.pathname();
string target{settings.libraryPath, "BS-X Satellaview/", name, ".bs/"};
//if(directory::exists(target)) return failure("game already exists");
BsxSatellaviewCartridge cartridge{buffer.data(), buffer.size()};
auto markup = cartridge.markup;
if(!markup) return failure("does not appear to be a valid image");
markup.append("\n");
markup.append("information\n");
markup.append(" title: ", name, "\n");
markup.append(" note: heuristically generated by icarus\n");
if(!directory::create(target)) return failure("library path unwritable");
if(settings.createManifests) file::write({target, "manifest.bml"}, markup);
file::write({target, "program.rom"}, buffer);
return success();
}

70
icarus/core/core.cpp Normal file
View File

@ -0,0 +1,70 @@
auto Icarus::error() const -> string {
return errorMessage;
}
auto Icarus::success() -> bool {
errorMessage = "";
return true;
}
auto Icarus::failure(const string& message) -> bool {
errorMessage = message;
return false;
}
auto Icarus::manifest(string location) -> string {
location.transform("\\", "/").rtrim("/").append("/");
if(!directory::exists(location)) return "";
auto type = location.suffixname().downcase();
if(type == ".fc") return famicomManifest(location);
if(type == ".sfc") return superFamicomManifest(location);
if(type == ".gb") return gameBoyManifest(location);
if(type == ".gbc") return gameBoyColorManifest(location);
if(type == ".gba") return gameBoyAdvanceManifest(location);
if(type == ".bs") return bsxSatellaviewManifest(location);
if(type == ".st") return sufamiTurboManifest(location);
return "";
}
auto Icarus::import(string location) -> bool {
location.transform("\\", "/").rtrim("/");
if(!file::exists(location)) return failure("file does not exist");
if(!file::readable(location)) return failure("file is unreadable");
auto name = location.prefixname();
auto type = location.suffixname().downcase();
if(!name || !type) return failure("invalid file name");
auto buffer = file::read(location);
if(!buffer) return failure("file is empty");
if(type == ".zip") {
Decode::ZIP zip;
if(!zip.open(location)) return failure("ZIP archive is invalid");
if(!zip.file) return failure("ZIP archive is empty");
name = zip.file[0].name.prefixname();
type = zip.file[0].name.suffixname().downcase();
buffer = zip.extract(zip.file[0]);
}
if(type == ".fc" || type == ".nes") return famicomImport(buffer, location);
if(type == ".sfc" || type == ".smc") return superFamicomImport(buffer, location);
if(type == ".gb") return gameBoyImport(buffer, location);
if(type == ".gbc") return gameBoyColorImport(buffer, location);
if(type == ".gba") return gameBoyAdvanceImport(buffer, location);
if(type == ".bs") return bsxSatellaviewImport(buffer, location);
if(type == ".st") return sufamiTurboImport(buffer, location);
return failure("unrecognized file extension");
}
auto Icarus::concatenate(vector<uint8_t>& output, const string& location) -> void {
if(auto input = file::read(location)) {
auto size = output.size();
output.resize(size + input.size());
memory::copy(output.data() + size, input.data(), input.size());
}
}

50
icarus/core/core.hpp Normal file
View File

@ -0,0 +1,50 @@
struct Icarus {
//core.cpp
auto error() const -> string;
auto success() -> bool;
auto failure(const string& message) -> bool;
auto manifest(string location) -> string;
auto import(string location) -> bool;
auto concatenate(vector<uint8_t>& output, const string& location) -> void;
//famicom.cpp
auto famicomManifest(const string& location) -> string;
auto famicomManifest(vector<uint8_t>& buffer, const string& location) -> string;
auto famicomImport(vector<uint8_t>& buffer, const string& location) -> bool;
//super-famicom.cpp
auto superFamicomManifest(const string& location) -> string;
auto superFamicomManifest(vector<uint8_t>& buffer, const string& location) -> string;
auto superFamicomImport(vector<uint8_t>& buffer, const string& location) -> bool;
auto superFamicomImportScanManifest(vector<Markup::Node>& roms, Markup::Node node) -> void;
//game-boy.cpp
auto gameBoyManifest(const string& location) -> string;
auto gameBoyManifest(vector<uint8_t>& buffer, const string& location) -> string;
auto gameBoyImport(vector<uint8_t>& buffer, const string& location) -> bool;
//game-boy-color.cpp
auto gameBoyColorManifest(const string& location) -> string;
auto gameBoyColorManifest(vector<uint8_t>& buffer, const string& location) -> string;
auto gameBoyColorImport(vector<uint8_t>& buffer, const string& location) -> bool;
//game-boy-advance.cpp
auto gameBoyAdvanceManifest(const string& location) -> string;
auto gameBoyAdvanceManifest(vector<uint8_t>& buffer, const string& location) -> string;
auto gameBoyAdvanceImport(vector<uint8_t>& buffer, const string& location) -> bool;
//bsx-satellaview.cpp
auto bsxSatellaviewManifest(const string& location) -> string;
auto bsxSatellaviewManifest(vector<uint8_t>& buffer, const string& location) -> string;
auto bsxSatellaviewImport(vector<uint8_t>& buffer, const string& location) -> bool;
//sufami-turbo.cpp
auto sufamiTurboManifest(const string& location) -> string;
auto sufamiTurboManifest(vector<uint8_t>& buffer, const string& location) -> string;
auto sufamiTurboImport(vector<uint8_t>& buffer, const string& location) -> bool;
private:
string errorMessage;
};

45
icarus/core/famicom.cpp Normal file
View File

@ -0,0 +1,45 @@
auto Icarus::famicomManifest(const string& location) -> string {
vector<uint8_t> buffer;
concatenate(buffer, {location, "ines.rom"});
concatenate(buffer, {location, "program.rom"});
concatenate(buffer, {location, "character.rom"});
return famicomManifest(buffer, location);
}
auto Icarus::famicomManifest(vector<uint8_t>& buffer, const string& location) -> string {
FamicomCartridge cartridge{buffer.data(), buffer.size()};
if(auto markup = cartridge.markup) {
markup.append("\n");
markup.append("information\n");
markup.append(" sha256: ", Hash::SHA256(buffer.data(), buffer.size()).digest(), "\n");
markup.append(" title: ", location.prefixname(), "\n");
markup.append(" note: ", "heuristically generated by icarus\n");
return markup;
}
return "";
}
auto Icarus::famicomImport(vector<uint8_t>& buffer, const string& location) -> bool {
auto name = location.prefixname();
auto source = location.pathname();
string target{settings.libraryPath, "Famicom/", name, ".fc/"};
//if(directory::exists(target)) return failure("game already exists");
FamicomCartridge cartridge{buffer.data(), buffer.size()};
auto markup = cartridge.markup;
if(!markup) return failure("does not appear to be a valid image");
markup.append("\n");
markup.append("information\n");
markup.append(" title: ", name, "\n");
markup.append(" note: heuristically generated by icarus\n");
if(!directory::create(target)) return failure("library path unwritable");
if(settings.createManifests) file::write({target, "manifest.bml"}, markup);
file::write({target, "ines.rom"}, buffer.data(), 16);
file::write({target, "program.rom"}, buffer.data() + 16, cartridge.prgrom);
if(!cartridge.chrrom) return success();
file::write({target, "character.rom"}, buffer.data() + 16 + cartridge.prgrom, cartridge.chrrom);
return success();
}

View File

@ -0,0 +1,40 @@
auto Icarus::gameBoyAdvanceManifest(const string& location) -> string {
vector<uint8_t> buffer;
concatenate(buffer, {location, "program.rom"});
return gameBoyAdvanceManifest(buffer, location);
}
auto Icarus::gameBoyAdvanceManifest(vector<uint8_t>& buffer, const string& location) -> string {
GameBoyAdvanceCartridge cartridge{buffer.data(), buffer.size()};
if(auto markup = cartridge.markup) {
markup.append("\n");
markup.append("information\n");
markup.append(" sha256: ", Hash::SHA256(buffer.data(), buffer.size()).digest(), "\n");
markup.append(" title: ", location.prefixname(), "\n");
markup.append(" note: ", "heuristically generated by icarus\n");
return markup;
}
return "";
}
auto Icarus::gameBoyAdvanceImport(vector<uint8_t>& buffer, const string& location) -> bool {
auto name = location.prefixname();
auto source = location.pathname();
string target{settings.libraryPath, "Game Boy Advance/", name, ".gba/"};
//if(directory::exists(target)) return failure("game already exists");
GameBoyAdvanceCartridge cartridge{buffer.data(), buffer.size()};
auto markup = cartridge.markup;
if(!markup) return failure("does not appear to be a valid image");
markup.append("\n");
markup.append("information\n");
markup.append(" title: ", name, "\n");
markup.append(" note: heuristically generated by icarus\n");
if(!directory::create(target)) return failure("library path unwritable");
if(settings.createManifests) file::write({target, "manifest.bml"}, markup);
file::write({target, "program.rom"}, buffer);
return success();
}

View File

@ -0,0 +1,40 @@
auto Icarus::gameBoyColorManifest(const string& location) -> string {
vector<uint8_t> buffer;
concatenate(buffer, {location, "program.rom"});
return gameBoyColorManifest(buffer, location);
}
auto Icarus::gameBoyColorManifest(vector<uint8_t>& buffer, const string& location) -> string {
GameBoyCartridge cartridge{buffer.data(), buffer.size()};
if(auto markup = cartridge.markup) {
markup.append("\n");
markup.append("information\n");
markup.append(" sha256: ", Hash::SHA256(buffer.data(), buffer.size()).digest(), "\n");
markup.append(" title: ", location.prefixname(), "\n");
markup.append(" note: ", "heuristically generated by icarus\n");
return markup;
}
return "";
}
auto Icarus::gameBoyColorImport(vector<uint8_t>& buffer, const string& location) -> bool {
auto name = location.prefixname();
auto source = location.pathname();
string target{settings.libraryPath, "Game Boy Color/", name, ".gbc/"};
//if(directory::exists(target)) return failure("game already exists");
GameBoyCartridge cartridge{buffer.data(), buffer.size()};
auto markup = cartridge.markup;
if(!markup) return failure("does not appear to be a valid image");
markup.append("\n");
markup.append("information\n");
markup.append(" title: ", name, "\n");
markup.append(" note: heuristically generated by icarus\n");
if(!directory::create(target)) return failure("library path unwritable");
if(settings.createManifests) file::write({target, "manifest.bml"}, markup);
file::write({target, "program.rom"}, buffer);
return success();
}

40
icarus/core/game-boy.cpp Normal file
View File

@ -0,0 +1,40 @@
auto Icarus::gameBoyManifest(const string& location) -> string {
vector<uint8_t> buffer;
concatenate(buffer, {location, "program.rom"});
return gameBoyManifest(buffer, location);
}
auto Icarus::gameBoyManifest(vector<uint8_t>& buffer, const string& location) -> string {
GameBoyCartridge cartridge{buffer.data(), buffer.size()};
if(auto markup = cartridge.markup) {
markup.append("\n");
markup.append("information\n");
markup.append(" sha256: ", Hash::SHA256(buffer.data(), buffer.size()).digest(), "\n");
markup.append(" title: ", location.prefixname(), "\n");
markup.append(" note: ", "heuristically generated by icarus\n");
return markup;
}
return "";
}
auto Icarus::gameBoyImport(vector<uint8_t>& buffer, const string& location) -> bool {
auto name = location.prefixname();
auto source = location.pathname();
string target{settings.libraryPath, "Game Boy/", name, ".gb/"};
//if(directory::exists(target)) return failure("game already exists");
GameBoyCartridge cartridge{buffer.data(), buffer.size()};
auto markup = cartridge.markup;
if(!markup) return failure("does not appear to be a valid image");
markup.append("\n");
markup.append("information\n");
markup.append(" title: ", name, "\n");
markup.append(" note: heuristically generated by icarus\n");
if(!directory::create(target)) return failure("library path unwritable");
if(settings.createManifests) file::write({target, "manifest.bml"}, markup);
file::write({target, "program.rom"}, buffer);
return success();
}

View File

@ -0,0 +1,40 @@
auto Icarus::sufamiTurboManifest(const string& location) -> string {
vector<uint8_t> buffer;
concatenate(buffer, {location, "program.rom"});
return sufamiTurboManifest(buffer, location);
}
auto Icarus::sufamiTurboManifest(vector<uint8_t>& buffer, const string& location) -> string {
SufamiTurboCartridge cartridge{buffer.data(), buffer.size()};
if(auto markup = cartridge.markup) {
markup.append("\n");
markup.append("information\n");
markup.append(" sha256: ", Hash::SHA256(buffer.data(), buffer.size()).digest(), "\n");
markup.append(" title: ", location.prefixname(), "\n");
markup.append(" note: ", "heuristically generated by icarus\n");
return markup;
}
return "";
}
auto Icarus::sufamiTurboImport(vector<uint8_t>& buffer, const string& location) -> bool {
auto name = location.prefixname();
auto source = location.pathname();
string target{settings.libraryPath, "Sufami Turbo/", name, ".st/"};
//if(directory::exists(target)) return failure("game already exists");
SufamiTurboCartridge cartridge{buffer.data(), buffer.size()};
auto markup = cartridge.markup;
if(!markup) return failure("does not appear to be a valid image");
markup.append("\n");
markup.append("information\n");
markup.append(" title: ", name, "\n");
markup.append(" note: heuristically generated by icarus\n");
if(!directory::create(target)) return failure("library path unwritable");
if(settings.createManifests) file::write({target, "manifest.bml"}, markup);
file::write({target, "program.rom"}, buffer);
return success();
}

View File

@ -0,0 +1,72 @@
auto Icarus::superFamicomManifest(const string& location) -> string {
vector<uint8_t> buffer;
auto files = directory::files(location, "*.rom");
concatenate(buffer, {location, "program.rom"});
concatenate(buffer, {location, "data.rom" });
for(auto& file : files.match("*.boot.rom" )) concatenate(buffer, {location, file});
for(auto& file : files.match("*.program.rom")) concatenate(buffer, {location, file});
for(auto& file : files.match("*.data.rom" )) concatenate(buffer, {location, file});
return superFamicomManifest(buffer, location);
}
auto Icarus::superFamicomManifest(vector<uint8_t>& buffer, const string& location) -> string {
SuperFamicomCartridge cartridge{buffer.data(), buffer.size()};
if(auto markup = cartridge.markup) {
markup.append("\n");
markup.append("information\n");
markup.append(" sha256: ", Hash::SHA256(buffer.data(), buffer.size()).digest(), "\n");
markup.append(" title: ", location.prefixname(), "\n");
markup.append(" note: ", "heuristically generated by icarus\n");
return markup;
}
return "";
}
auto Icarus::superFamicomImport(vector<uint8_t>& buffer, const string& location) -> bool {
auto name = location.prefixname();
auto source = location.pathname();
string target{settings.libraryPath, "Super Famicom/", name, ".sfc/"};
//if(directory::exists(target)) return failure("game already exists");
SuperFamicomCartridge cartridge{buffer.data(), buffer.size()};
auto markup = cartridge.markup;
if(!markup) return failure("does not appear to be a valid image");
vector<Markup::Node> roms;
auto document = BML::unserialize(markup);
superFamicomImportScanManifest(roms, document);
for(auto rom : roms) {
auto name = rom["name"].text();
auto size = rom["size"].decimal();
if(name == "program.rom" || name == "data.rom" || cartridge.firmware_appended) continue;
if(file::size({source, name}) != size) return failure({"firmware (", name, ") missing or invalid"});
}
markup.append("\n");
markup.append("information\n");
markup.append(" title: ", name, "\n");
markup.append(" note: ", "heuristically generated by icarus\n");
if(!directory::create(target)) return failure("library path unwritable");
if(settings.createManifests) file::write({target, "manifest.bml"}, markup);
unsigned offset = (buffer.size() & 0x7fff) == 512 ? 512 : 0; //skip header if present
for(auto rom : roms) {
auto name = rom["name"].text();
auto size = rom["size"].decimal();
if(name == "program.rom" || name == "data.rom" || cartridge.firmware_appended) {
file::write({target, name}, buffer.data() + offset, size);
offset += size;
} else {
auto firmware = file::read({source, name});
file::write({target, name}, firmware);
}
}
return success();
}
auto Icarus::superFamicomImportScanManifest(vector<Markup::Node>& roms, Markup::Node node) -> void {
if(node.name() == "rom") roms.append(node);
for(auto leaf : node) superFamicomImportScanManifest(roms, leaf);
}

View File

@ -0,0 +1,10 @@
struct BsxSatellaviewCartridge {
BsxSatellaviewCartridge(const uint8_t* data, unsigned size);
string markup;
};
BsxSatellaviewCartridge::BsxSatellaviewCartridge(const uint8_t* data, unsigned size) {
markup.append("cartridge\n");
markup.append(" rom name=program.rom size=0x", hex(size), " type=FlashROM\n");
}

View File

@ -0,0 +1,161 @@
struct FamicomCartridge {
FamicomCartridge(const uint8_t* data, unsigned size);
string markup;
//private:
unsigned mapper;
unsigned mirror;
unsigned prgrom;
unsigned prgram;
unsigned chrrom;
unsigned chrram;
};
FamicomCartridge::FamicomCartridge(const uint8_t* data, unsigned size) {
if(size < 16) return;
if(data[0] != 'N') return;
if(data[1] != 'E') return;
if(data[2] != 'S') return;
if(data[3] != 26) return;
mapper = ((data[7] >> 4) << 4) | (data[6] >> 4);
mirror = ((data[6] & 0x08) >> 2) | (data[6] & 0x01);
prgrom = data[4] * 0x4000;
chrrom = data[5] * 0x2000;
prgram = 0u;
chrram = chrrom == 0u ? 8192u : 0u;
markup.append("cartridge\n");
switch(mapper) {
default:
markup.append(" board type=NES-NROM-256\n");
markup.append(" mirror mode=", mirror == 0 ? "horizontal" : "vertical", "\n");
break;
case 1:
markup.append(" board type=NES-SXROM\n");
markup.append(" chip type=MMC1B2\n");
prgram = 8192;
break;
case 2:
markup.append(" board type=NES-UOROM\n");
markup.append(" mirror mode=", mirror == 0 ? "horizontal" : "vertical", "\n");
break;
case 3:
markup.append(" board type=NES-CNROM\n");
markup.append(" mirror mode=", mirror == 0 ? "horizontal" : "vertical", "\n");
break;
case 4:
//MMC3
markup.append(" board type=NES-TLROM\n");
markup.append(" chip type=MMC3B\n");
prgram = 8192;
//MMC6
//markup.append(" board type=NES-HKROM\n");
//markup.append(" chip type=MMC6n");
//prgram = 1024;
break;
case 5:
markup.append(" board type=NES-ELROM\n");
markup.append(" chip type=MMC5\n");
prgram = 65536;
break;
case 7:
markup.append(" board type=NES-AOROM\n");
break;
case 9:
markup.append(" board type=NES-PNROM\n");
markup.append(" chip type=MMC2\n");
prgram = 8192;
break;
case 10:
markup.append(" board type=NES-FKROM\n");
markup.append(" chip type=MMC4\n");
prgram = 8192;
break;
case 16:
markup.append(" board type=BANDAI-FCG\n");
markup.append(" chip type=LZ93D50\n");
break;
case 21:
case 23:
case 25:
//VRC4
markup.append(" board type=KONAMI-VRC-4\n");
markup.append(" chip type=VRC4\n");
markup.append(" pinout a0=1 a1=0\n");
prgram = 8192;
break;
case 22:
//VRC2
markup.append(" board type=KONAMI-VRC-2\n");
markup.append(" chip type=VRC2\n");
markup.append(" pinout a0=0 a1=1\n");
break;
case 24:
markup.append(" board type=KONAMI-VRC-6\n");
markup.append(" chip type=VRC6\n");
break;
case 26:
markup.append(" board type=KONAMI-VRC-6\n");
markup.append(" chip type=VRC6\n");
prgram = 8192;
break;
case 34:
markup.append(" board type=NES-BNROM\n");
markup.append(" mirror mode=", mirror == 0 ? "horizontal" : "vertical", "\n");
break;
case 66:
markup.append(" board type=NES-GNROM\n");
markup.append(" mirror mode=", mirror == 0 ? "horizontal" : "vertical", "\n");
break;
case 69:
markup.append(" board type=SUNSOFT-5B\n");
markup.append(" chip type=5B\n");
prgram = 8192;
break;
case 73:
markup.append(" board type=KONAMI-VRC-3\n");
markup.append(" chip type=VRC3\n");
markup.append(" mirror mode=", mirror == 0 ? "horizontal" : "vertical", "\n");
prgram = 8192;
break;
case 75:
markup.append(" board type=KONAMI-VRC-1\n");
markup.append(" chip type=VRC1\n");
break;
case 85:
markup.append(" board type=KONAMI-VRC-7\n");
markup.append(" chip type=VRC7\n");
prgram = 8192;
break;
}
markup.append(" prg\n");
if(prgrom) markup.append(" rom name=program.rom size=0x", hex(prgrom), "\n");
if(prgram) markup.append(" ram name=save.ram size=0x", hex(prgram), "\n");
markup.append(" chr\n");
if(chrrom) markup.append(" rom name=character.rom size=0x", hex(chrrom), "\n");
if(chrram) markup.append(" ram size=0x", hex(chrram), "\n");
}

View File

@ -0,0 +1,50 @@
struct GameBoyAdvanceCartridge {
GameBoyAdvanceCartridge(const uint8_t *data, unsigned size);
string markup;
string identifiers;
};
GameBoyAdvanceCartridge::GameBoyAdvanceCartridge(const uint8_t *data, unsigned size) {
struct Identifier {
string name;
unsigned size;
};
vector<Identifier> idlist;
idlist.append({"SRAM_V", 6});
idlist.append({"SRAM_F_V", 8});
idlist.append({"EEPROM_V", 8});
idlist.append({"FLASH_V", 7});
idlist.append({"FLASH512_V", 10});
idlist.append({"FLASH1M_V", 9});
lstring list;
for(auto& id : idlist) {
for(signed n = 0; n < size - 16; n++) {
if(!memcmp(data + n, (const char*)id.name, id.size)) {
const char* p = (const char*)data + n + id.size;
if(p[0] >= '0' && p[0] <= '9'
&& p[1] >= '0' && p[1] <= '9'
&& p[2] >= '0' && p[2] <= '9'
) {
char text[16];
memcpy(text, data + n, id.size + 3);
text[id.size + 3] = 0;
list.appendOnce(text);
}
}
}
}
identifiers = list.merge(",");
markup.append("cartridge\n");
markup.append(" rom name=program.rom size=0x", hex(size), "\n");
if(0);
else if(identifiers.beginsWith("SRAM_V" )) markup.append(" ram name=save.ram type=SRAM size=0x8000\n");
else if(identifiers.beginsWith("SRAM_F_V" )) markup.append(" ram name=save.ram type=FRAM size=0x8000\n");
else if(identifiers.beginsWith("EEPROM_V" )) markup.append(" ram name=save.ram type=EEPROM size=0x0\n");
else if(identifiers.beginsWith("FLASH_V" )) markup.append(" ram name=save.ram type=FlashROM size=0x10000\n");
else if(identifiers.beginsWith("FLASH512_V")) markup.append(" ram name=save.ram type=FlashROM size=0x10000\n");
else if(identifiers.beginsWith("FLASH1M_V" )) markup.append(" ram name=save.ram type=FlashROM size=0x20000\n");
//if(identifiers.empty() == false) markup.append(" #detected: ", identifiers, "\n");
}

View File

@ -0,0 +1,107 @@
struct GameBoyCartridge {
GameBoyCartridge(uint8_t* data, unsigned size);
string markup;
//private:
struct Information {
string mapper;
bool ram;
bool battery;
bool rtc;
bool rumble;
unsigned romsize;
unsigned ramsize;
bool cgb;
bool cgbonly;
} info;
};
GameBoyCartridge::GameBoyCartridge(uint8_t *romdata, unsigned romsize) {
if(romsize < 0x4000) return;
info.mapper = "unknown";
info.ram = false;
info.battery = false;
info.rtc = false;
info.rumble = false;
info.romsize = 0;
info.ramsize = 0;
unsigned base = romsize - 0x8000;
if(romdata[base + 0x0104] == 0xce && romdata[base + 0x0105] == 0xed
&& romdata[base + 0x0106] == 0x66 && romdata[base + 0x0107] == 0x66
&& romdata[base + 0x0108] == 0xcc && romdata[base + 0x0109] == 0x0d
&& romdata[base + 0x0147] >= 0x0b && romdata[base + 0x0147] <= 0x0d
) {
//MMM01 stores header at bottom of image
//flip this around for consistency with all other mappers
uint8_t header[0x8000];
memcpy(header, romdata + base, 0x8000);
memmove(romdata + 0x8000, romdata, romsize - 0x8000);
memcpy(romdata, header, 0x8000);
}
info.cgb = (romdata[0x0143] & 0x80) == 0x80;
info.cgbonly = (romdata[0x0143] & 0xc0) == 0xc0;
switch(romdata[0x0147]) {
case 0x00: info.mapper = "none"; break;
case 0x01: info.mapper = "MBC1"; break;
case 0x02: info.mapper = "MBC1"; info.ram = true; break;
case 0x03: info.mapper = "MBC1"; info.ram = true; info.battery = true; break;
case 0x05: info.mapper = "MBC2"; info.ram = true; break;
case 0x06: info.mapper = "MBC2"; info.ram = true; info.battery = true; break;
case 0x08: info.mapper = "none"; info.ram = true; break;
case 0x09: info.mapper = "MBC0"; info.ram = true; info.battery = true; break;
case 0x0b: info.mapper = "MMM01"; break;
case 0x0c: info.mapper = "MMM01"; info.ram = true; break;
case 0x0d: info.mapper = "MMM01"; info.ram = true; info.battery = true; break;
case 0x0f: info.mapper = "MBC3"; info.rtc = true; info.battery = true; break;
case 0x10: info.mapper = "MBC3"; info.rtc = true; info.ram = true; info.battery = true; break;
case 0x11: info.mapper = "MBC3"; break;
case 0x12: info.mapper = "MBC3"; info.ram = true; break;
case 0x13: info.mapper = "MBC3"; info.ram = true; info.battery = true; break;
case 0x19: info.mapper = "MBC5"; break;
case 0x1a: info.mapper = "MBC5"; info.ram = true; break;
case 0x1b: info.mapper = "MBC5"; info.ram = true; info.battery = true; break;
case 0x1c: info.mapper = "MBC5"; info.rumble = true; break;
case 0x1d: info.mapper = "MBC5"; info.rumble = true; info.ram = true; break;
case 0x1e: info.mapper = "MBC5"; info.rumble = true; info.ram = true; info.battery = true; break;
case 0xfc: break; //Pocket Camera
case 0xfd: break; //Bandai TAMA5
case 0xfe: info.mapper = "HuC3"; break;
case 0xff: info.mapper = "HuC1"; info.ram = true; info.battery = true; break;
}
switch(romdata[0x0148]) { default:
case 0x00: info.romsize = 2 * 16 * 1024; break;
case 0x01: info.romsize = 4 * 16 * 1024; break;
case 0x02: info.romsize = 8 * 16 * 1024; break;
case 0x03: info.romsize = 16 * 16 * 1024; break;
case 0x04: info.romsize = 32 * 16 * 1024; break;
case 0x05: info.romsize = 64 * 16 * 1024; break;
case 0x06: info.romsize = 128 * 16 * 1024; break;
case 0x07: info.romsize = 256 * 16 * 1024; break;
case 0x52: info.romsize = 72 * 16 * 1024; break;
case 0x53: info.romsize = 80 * 16 * 1024; break;
case 0x54: info.romsize = 96 * 16 * 1024; break;
}
switch(romdata[0x0149]) { default:
case 0x00: info.ramsize = 0 * 1024; break;
case 0x01: info.ramsize = 2 * 1024; break;
case 0x02: info.ramsize = 8 * 1024; break;
case 0x03: info.ramsize = 32 * 1024; break;
}
if(info.mapper == "MBC2") info.ramsize = 512; //512 x 4-bit
markup.append("cartridge\n");
markup.append(" board type=", info.mapper, "\n");
markup.append(" rom name=program.rom size=0x", hex(romsize), "\n");
if(info.ramsize > 0) markup.append(" ram name=save.ram size=0x", hex(info.ramsize), "\n");
}

View File

@ -0,0 +1,18 @@
struct SufamiTurboCartridge {
SufamiTurboCartridge(const uint8_t* data, unsigned size);
string markup;
};
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("cartridge", 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");
}

View File

@ -0,0 +1,843 @@
struct SuperFamicomCartridge {
SuperFamicomCartridge(const uint8_t *data, unsigned size);
string markup;
//private:
auto readHeader(const uint8_t *data, unsigned size) -> void;
auto findHeader(const uint8_t *data, unsigned size) -> unsigned;
auto scoreHeader(const uint8_t *data, unsigned size, unsigned addr) -> unsigned;
enum HeaderField : unsigned {
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 Mode : unsigned {
ModeNormal,
ModeBsxSlotted,
ModeBsx,
ModeSufamiTurbo,
ModeSuperGameBoy,
};
enum Type : unsigned {
TypeNormal,
TypeBsxSlotted,
TypeBsxBios,
TypeBsx,
TypeSufamiTurboBios,
TypeSufamiTurbo,
TypeSuperGameBoy1Bios,
TypeSuperGameBoy2Bios,
TypeGameBoy,
TypeUnknown,
};
enum Region : unsigned {
NTSC,
PAL,
};
enum MemoryMapper : unsigned {
LoROM,
HiROM,
ExLoROM,
ExHiROM,
SuperFXROM,
SA1ROM,
SPC7110ROM,
BSCLoROM,
BSCHiROM,
BSXROM,
STROM,
};
enum DSP1MemoryMapper : unsigned {
DSP1Unmapped,
DSP1LoROM1MB,
DSP1LoROM2MB,
DSP1HiROM,
};
bool loaded; //is a base cartridge inserted?
unsigned crc32; //crc32 of all cartridges (base+slot(s))
unsigned rom_size;
unsigned ram_size;
bool firmware_required; //true if firmware is required for emulation
bool firmware_appended; //true if firmware is present at end of data
Mode mode;
Type type;
Region region;
MemoryMapper mapper;
DSP1MemoryMapper dsp1_mapper;
bool has_bsx_slot;
bool has_superfx;
bool has_sa1;
bool has_sharprtc;
bool has_epsonrtc;
bool has_sdd1;
bool has_spc7110;
bool has_cx4;
bool has_dsp1;
bool has_dsp2;
bool has_dsp3;
bool has_dsp4;
bool has_obc1;
bool has_st010;
bool has_st011;
bool has_st018;
};
SuperFamicomCartridge::SuperFamicomCartridge(const uint8_t *data, unsigned size) {
firmware_required = false;
firmware_appended = false;
//skip copier header
if((size & 0x7fff) == 512) data += 512, size -= 512;
if(size < 0x8000) return;
readHeader(data, size);
if(type == TypeGameBoy) return;
if(type == TypeBsx) return;
if(type == TypeSufamiTurbo) return;
const char* range = (rom_size > 0x200000) || (ram_size > 32 * 1024) ? "0000-7fff" : "0000-ffff";
markup.append("cartridge 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 == TypeSuperGameBoy1Bios || type == TypeSuperGameBoy2Bios) {
firmware_required = true;
if((rom_size & 0x7fff) == 0x100) {
firmware_appended = true;
rom_size -= 0x100;
}
}
//end firmware detection
if(type == TypeSuperGameBoy1Bios || type == TypeSuperGameBoy2Bios) {
markup.append(
" rom name=program.rom size=0x", hex(rom_size), "\n"
" map id=rom address=00-7f,80-ff:8000-ffff mask=0x8000\n"
" icd2 revision=1\n"
" rom name=sgb.boot.rom size=0x100\n"
" map id=io address=00-3f,80-bf:6000-7fff\n"
);
}
else if(has_cx4) {
markup.append(
" hitachidsp model=HG51B169 frequency=20000000\n"
" rom id=program name=program.rom size=0x", hex(rom_size), "\n"
" rom id=data name=cx4.data.rom size=0xc00\n"
" ram id=data size=0xc00\n"
" map id=io address=00-3f,80-bf:6000-7fff\n"
" map id=rom address=00-7f,80-ff:8000-ffff mask=0x8000\n"
" map id=ram address=70-77:0000-7fff\n"
);
}
else if(has_spc7110) {
markup.append(
" spc7110\n"
" rom id=program name=program.rom size=0x100000\n"
" rom id=data name=data.rom size=0x", hex(rom_size - 0x100000), "\n"
" ram name=save.ram size=0x", hex(ram_size), "\n"
" map id=io address=00-3f,80-bf:4800-483f\n"
" map id=io address=50:0000-ffff\n"
" map id=rom address=00-3f,80-bf:8000-ffff\n"
" map id=rom address=c0-ff:0000-ffff\n"
" map id=ram address=00-3f,80-bf:6000-7fff mask=0xe000\n"
);
}
else if(has_sdd1) {
markup.append(
" sdd1\n"
" rom name=program.rom size=0x", hex(rom_size), "\n"
);
if(ram_size > 0) markup.append(
" ram name=save.ram size=0x", hex(ram_size), "\n"
);
markup.append(
" map id=io address=00-3f,80-bf:4800-4807\n"
" map id=rom address=00-3f,80-bf:8000-ffff mask=0x8000\n"
" map id=rom address=c0-ff:0000-ffff\n"
);
if(ram_size > 0) markup.append(
" map id=ram address=20-3f,a0-bf:6000-7fff mask=0xe000\n"
" map id=ram address=70-7f:0000-7fff\n"
);
}
else if(mapper == LoROM) {
markup.append(
" rom name=program.rom size=0x", hex(rom_size), "\n"
);
if(ram_size > 0) markup.append(
" ram name=save.ram size=0x", hex(ram_size), "\n"
);
markup.append(
" map id=rom address=00-7f,80-ff:8000-ffff mask=0x8000\n"
);
if(ram_size > 0) markup.append(
" map id=ram address=70-7f,f0-ff:", range, "\n"
);
}
else if(mapper == HiROM) {
markup.append(
" rom name=program.rom size=0x", hex(rom_size), "\n"
);
if(ram_size > 0) markup.append(
" ram name=save.ram size=0x", hex(ram_size), "\n"
);
markup.append(
" map id=rom address=00-3f,80-bf:8000-ffff\n"
" map id=rom address=40-7f,c0-ff:0000-ffff\n"
);
if(ram_size > 0) markup.append(
" map id=ram address=10-3f,90-bf:6000-7fff mask=0xe000\n"
);
}
else if(mapper == ExLoROM) {
markup.append(
" rom name=program.rom size=0x", hex(rom_size), "\n"
);
if(ram_size > 0) markup.append(
" ram name=save.ram size=0x", hex(ram_size), "\n"
);
markup.append(
" map id=rom address=00-3f,80-bf:8000-ffff mask=0x8000\n"
" map id=rom address=40-7f:0000-ffff\n"
);
if(ram_size > 0) markup.append(
" map id=ram address=20-3f,a0-bf:6000-7fff\n"
" map id=ram address=70-7f:0000-7fff\n"
);
}
else if(mapper == ExHiROM) {
markup.append(
" rom name=program.rom size=0x", hex(rom_size), "\n"
);
if(ram_size > 0) markup.append(
" ram name=save.ram size=0x", hex(ram_size), "\n"
);
markup.append(
" map id=rom address=00-3f:8000-ffff base=0x400000\n"
" map id=rom address=40-7f:0000-ffff base=0x400000\n"
" map id=rom address=80-bf:8000-ffff mask=0xc00000\n"
" map id=rom address=c0-ff:0000-ffff mask=0xc00000\n"
);
if(ram_size > 0) markup.append(
" map id=ram address=20-3f,a0-bf:6000-7fff mask=0xe000\n"
" map id=ram address=70-7f:", range, "\n"
);
}
else if(mapper == SuperFXROM) {
markup.append(
" superfx revision=3\n"
" rom name=program.rom size=0x", hex(rom_size), "\n"
);
if(ram_size > 0) markup.append(
" ram name=save.ram size=0x", hex(ram_size), "\n"
);
markup.append(
" map id=io address=00-3f,80-bf:3000-32ff\n"
" map id=rom address=00-3f,80-bf:8000-ffff mask=0x8000\n"
" map id=rom address=40-5f,c0-df:0000-ffff\n"
);
if(ram_size > 0) markup.append(
" map id=ram address=00-3f,80-bf:6000-7fff size=0x2000\n"
" map id=ram address=70-71,f0-f1:0000-ffff\n"
);
}
else if(mapper == SA1ROM) {
markup.append(
" sa1\n"
" rom name=program.rom size=0x", hex(rom_size), "\n"
);
if(ram_size > 0) markup.append(
" ram id=bitmap name=save.ram size=0x", hex(ram_size), "\n"
);
markup.append(
" ram id=internal size=0x800\n"
" map id=io address=00-3f,80-bf:2200-23ff\n"
" map id=rom address=00-3f,80-bf:8000-ffff\n"
" map id=rom address=c0-ff:0000-ffff\n"
);
if(ram_size > 0) markup.append(
" map id=bwram address=00-3f,80-bf:6000-7fff\n"
" map id=bwram address=40-4f:0000-ffff\n"
);
markup.append(
" map id=iram address=00-3f,80-bf:3000-37ff\n"
);
}
else if(mapper == BSCLoROM) {
markup.append(
" rom name=program.rom size=0x", hex(rom_size), "\n"
" ram name=save.ram size=0x", hex(ram_size), "\n"
" map id=rom address=00-1f:8000-ffff base=0x000000 mask=0x8000\n"
" map id=rom address=20-3f:8000-ffff base=0x100000 mask=0x8000\n"
" map id=rom address=80-9f:8000-ffff base=0x200000 mask=0x8000\n"
" map id=rom address=a0-bf:8000-ffff base=0x100000 mask=0x8000\n"
" map id=ram address=70-7f,f0-ff:0000-7fff\n"
" satellaview\n"
" map id=rom address=c0-ef:0000-ffff\n"
);
}
else if(mapper == BSCHiROM) {
markup.append(
" rom name=program.rom size=0x", hex(rom_size), "\n"
" ram name=save.ram size=0x", hex(ram_size), "\n"
" map id=rom address=00-1f,80-9f:8000-ffff\n"
" map id=rom address=40-5f,c0-df:0000-ffff\n"
" map id=ram address=20-3f,a0-bf:6000-7fff\n"
" satellaview\n"
" map id=rom address=20-3f,a0-bf:8000-ffff\n"
" map id=rom address=60-7f,e0-ff:0000-ffff\n"
);
}
else if(mapper == BSXROM) {
markup.append(
" mcc\n"
" rom name=program.rom size=0x", hex(rom_size), "\n"
" ram id=save name=save.ram size=0x", hex(ram_size), "\n"
" ram id=download name=bsx.ram size=0x80000\n"
" map id=io address=00-3f,80-bf:5000-5fff\n"
" map id=rom address=00-3f,80-bf:8000-ffff\n"
" map id=rom address=40-7f,c0-ff:0000-ffff\n"
" map id=ram address=20-3f:6000-7fff\n"
);
}
else if(mapper == STROM) {
markup.append(
" rom name=program.rom size=0x", hex(rom_size), "\n"
" map id=rom address='00-1f,80-9f:8000-ffff mask=0x8000\n"
" sufamiturbo\n"
" slot id=A\n"
" map id=rom address=20-3f,a0-bf:8000-ffff mask=0x8000\n"
" map id=ram address=60-63,e0-e3:8000-ffff\n"
" slot id=B\n"
" map id=rom address=40-5f,c0-df:8000-ffff mask=0x8000\n"
" map id=ram address=70-73,f0-f3:8000-ffff\n"
);
}
if(has_sharprtc) {
markup.append(
" sharprtc\n"
" ram name=rtc.ram size=0x10\n"
" map id=io address=00-3f,80-bf:2800-2801\n"
);
}
if(has_epsonrtc) {
markup.append(
" epsonrtc\n"
" ram name=rtc.ram size=0x10\n"
" map id=io address=00-3f,80-bf:4840-4842\n"
);
}
if(has_obc1) {
markup.append(
" obc1\n"
" ram name=save.ram size=0x2000\n"
" map id=io address=00-3f,80-bf:6000-7fff\n"
);
}
if(has_dsp1) {
markup.append(
" necdsp model=uPD7725 frequency=8000000\n"
" rom id=program name=dsp1b.program.rom size=0x1800\n"
" rom id=data name=dsp1b.data.rom size=0x800\n"
" ram id=data size=0x200\n"
);
if(dsp1_mapper == DSP1LoROM1MB) markup.append(
" map id=io address=20-3f,a0-bf:8000-ffff select=0x4000\n"
);
if(dsp1_mapper == DSP1LoROM2MB) markup.append(
" map id=io address=60-6f,e0-ef:0000-7fff select=0x4000\n"
);
if(dsp1_mapper == DSP1HiROM) markup.append(
" map id=io address=00-1f,80-9f:6000-7fff select=0x1000\n"
);
}
if(has_dsp2) {
markup.append(
" necdsp model=uPD7725 frequency=8000000\n"
" rom id=program name=dsp2.program.rom size=0x1800\n"
" rom id=data name=dsp2.data.rom size=0x800\n"
" ram id=data size=0x200\n"
" map id=io address=20-3f,a0-bf:8000-ffff select=0x4000\n"
);
}
if(has_dsp3) {
markup.append(
" necdsp model=uPD7725 frequency=8000000\n"
" rom id=program name=dsp3.program.rom size=0x1800\n"
" rom id=data name=dsp3.data.rom size=0x800\n"
" ram id=data size=0x200\n"
" map id=io address=20-3f,a0-bf:8000-ffff select=0x4000\n"
);
}
if(has_dsp4) {
markup.append(
" necdsp model=uPD7725 frequency=8000000\n"
" rom id=program name=dsp4.program.rom size=0x1800\n"
" rom id=data name=dsp4.data.rom size=0x800\n"
" ram id=data size=0x200\n"
" map id=io address=30-3f,b0-bf:8000-ffff select=0x4000\n"
);
}
if(has_st010) {
markup.append(
" necdsp model=uPD96050 frequency=11000000\n"
" rom id=program name=st010.program.rom size=0xc000\n"
" rom id=data name=st010.data.rom size=0x1000\n"
" ram id=data name=save.ram size=0x1000\n"
" map id=io address=60-67,e0-e7:0000-3fff select=0x0001\n"
" map id=ram address=68-6f,e8-ef:0000-7fff\n"
);
}
if(has_st011) {
markup.append(
" necdsp model=uPD96050 frequency=15000000\n"
" rom id=program name=st011.program.rom size=0xc000\n"
" rom id=data name=st011.data.rom size=0x1000\n"
" ram id=data name=save.ram size=0x1000\n"
" map id=io address=60-67,e0-e7:0000-3fff select=0x0001\n"
" map id=ram address=68-6f,e8-ef:0000-7fff\n"
);
}
if(has_st018) {
markup.append(
" armdsp frequency=21477272\n"
" rom id=program name=st018.program.rom size=0x20000\n"
" rom id=data name=st018.data.rom size=0x8000\n"
" ram name=save.ram size=0x4000\n"
" map id=io address=00-3f,80-bf:3800-38ff\n"
);
}
}
auto SuperFamicomCartridge::readHeader(const uint8_t *data, unsigned size) -> void {
type = TypeUnknown;
mapper = LoROM;
dsp1_mapper = DSP1Unmapped;
region = NTSC;
rom_size = size;
ram_size = 0;
has_bsx_slot = false;
has_superfx = false;
has_sa1 = false;
has_sharprtc = false;
has_epsonrtc = false;
has_sdd1 = false;
has_spc7110 = false;
has_cx4 = false;
has_dsp1 = false;
has_dsp2 = false;
has_dsp3 = false;
has_dsp4 = false;
has_obc1 = false;
has_st010 = false;
has_st011 = false;
has_st018 = false;
//=====================
//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 = TypeGameBoy;
return;
}
}
if(size < 32768) {
type = TypeUnknown;
return;
}
const unsigned 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;
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) ? NTSC : 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 = TypeBsx;
mapper = BSXROM;
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 = TypeSufamiTurboBios;
} else {
type = TypeSufamiTurbo;
}
mapper = STROM;
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 = TypeSuperGameBoy2Bios;
return;
}
if(!memcmp(data + index, "Super GAMEBOY", 13)) {
type = TypeSuperGameBoy1Bios;
return;
}
//=====================
//detect standard carts
//=====================
//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 = TypeBsxBios;
mapper = BSXROM;
region = NTSC; //BS-X only released in Japan
return; //RAM size handled internally by load_cart_bsx() -> BSXCart class
} else {
type = TypeBsxSlotted;
mapper = (index == 0x7fc0 ? BSCLoROM : BSCHiROM);
region = NTSC; //BS-X slotted cartridges only released in Japan
}
} else {
//standard cart
type = TypeNormal;
if(index == 0x7fc0 && size >= 0x401000) {
mapper = ExLoROM;
} else if(index == 0x7fc0 && mapperid == 0x32) {
mapper = ExLoROM;
} else if(index == 0x7fc0) {
mapper = LoROM;
} else if(index == 0xffc0) {
mapper = HiROM;
} else { //index == 0x40ffc0
mapper = ExHiROM;
}
}
if(mapperid == 0x20 && (rom_type == 0x13 || rom_type == 0x14 || rom_type == 0x15 || rom_type == 0x1a)) {
has_superfx = true;
mapper = SuperFXROM;
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;
mapper = SA1ROM;
}
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);
mapper = SPC7110ROM;
}
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_mapper = DSP1LoROM1MB;
} else if((mapperid & 0x2f) == 0x20) {
dsp1_mapper = DSP1LoROM2MB;
} else if((mapperid & 0x2f) == 0x21) {
dsp1_mapper = DSP1HiROM;
}
}
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, unsigned size) -> unsigned {
unsigned score_lo = scoreHeader(data, size, 0x007fc0);
unsigned score_hi = scoreHeader(data, size, 0x00ffc0);
unsigned 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, unsigned size, unsigned addr) -> unsigned {
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;
}

45
icarus/icarus.cpp Normal file
View File

@ -0,0 +1,45 @@
#include <nall/nall.hpp>
using namespace nall;
#include <hiro/hiro.hpp>
using namespace hiro;
#include "settings.cpp"
Settings settings;
#include "heuristics/famicom.hpp"
#include "heuristics/super-famicom.hpp"
#include "heuristics/game-boy.hpp"
#include "heuristics/game-boy-advance.hpp"
#include "heuristics/bsx-satellaview.hpp"
#include "heuristics/sufami-turbo.hpp"
#include "core/core.hpp"
#include "core/core.cpp"
#include "core/famicom.cpp"
#include "core/super-famicom.cpp"
#include "core/game-boy.cpp"
#include "core/game-boy-color.cpp"
#include "core/game-boy-advance.cpp"
#include "core/bsx-satellaview.cpp"
#include "core/sufami-turbo.cpp"
Icarus icarus;
#include "ui/ui.hpp"
#include "ui/scan-dialog.cpp"
#include "ui/import-dialog.cpp"
#include "ui/error-dialog.cpp"
#include <nall/main.hpp>
auto nall::main(lstring args) -> void {
if(args.size() == 3 && args[1] == "-m") {
if(!directory::exists(args[2])) return print("error: directory not found\n");
return print(icarus.manifest(args[2]));
}
new ScanDialog;
new ImportDialog;
new ErrorDialog;
scanDialog->show();
Application::run();
}

1
icarus/obj/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
*.o

27
icarus/settings.cpp Normal file
View File

@ -0,0 +1,27 @@
struct Settings : Configuration::Document {
Settings();
~Settings();
Configuration::Node root;
string activePath;
string libraryPath;
bool createManifests = false;
};
Settings::Settings() {
root.append(activePath, "ActivePath");
root.append(libraryPath, "LibraryPath");
root.append(createManifests, "CreateManifests");
append(root, "Settings");
directory::create({configpath(), "icarus/"});
load({configpath(), "icarus/settings.bml"});
save({configpath(), "icarus/settings.bml"});
if(!activePath) activePath = userpath();
if(!libraryPath) libraryPath = {userpath(), "Emulation/"};
}
Settings::~Settings() {
save({configpath(), "icarus/settings.bml"});
}

View File

@ -0,0 +1,16 @@
ErrorDialog::ErrorDialog() {
errorDialog = this;
onClose([&] { setVisible(false); scanDialog->show(); });
layout.setMargin(5);
errorLog.setEditable(false);
closeButton.setText("Close").onActivate([&] { doClose(); });
setSize({800, 360});
setCentered();
}
auto ErrorDialog::show(const string& text) -> void {
errorLog.setText(text);
setVisible();
}

View File

@ -0,0 +1,52 @@
ImportDialog::ImportDialog() {
importDialog = this;
onClose([&] {
stopButton.setEnabled(false).setText("Stopping ...");
abort = true;
});
layout.setMargin(5);
stopButton.setText("Stop").onActivate([&] { doClose(); });
setTitle("icarus - Importing ...");
setSize({480, layout.minimumSize().height()});
setCentered();
}
auto ImportDialog::run(lstring locations) -> void {
abort = false;
errors.reset();
unsigned position = 0;
setVisible(true);
for(auto& location : locations) {
auto name = location.basename();
if(abort) {
errors.append(string{"[", name, "] aborted"});
continue;
}
statusLabel.setText(name);
double progress = 100.0 * (double)position++ / (double)locations.size() + 0.5;
progressBar.setPosition((unsigned)progress);
Application::processEvents();
if(!icarus.import(location)) {
errors.append(string{"[", name, "] ", icarus.error()});
}
}
setVisible(false);
if(errors) {
string message{"Import completed, but with ", errors.size(), " error", errors.size() ? "s" : "", ". View log?"};
if(MessageDialog().setTitle("icarus").setText(message).question() == "Yes") {
errorDialog->show(errors.merge("\n"));
} else {
scanDialog->show();
}
} else {
MessageDialog().setTitle("icarus").setText("Import completed successfully.").information();
scanDialog->show();
}
}

116
icarus/ui/scan-dialog.cpp Normal file
View File

@ -0,0 +1,116 @@
ScanDialog::ScanDialog() {
scanDialog = this;
onClose(&Application::quit);
layout.setMargin(5);
pathEdit.onActivate([&] { refresh(); });
refreshButton.setIcon(Icon::Action::Refresh).setBordered(false).onActivate([&] {
pathEdit.setText(settings.activePath);
refresh();
});
homeButton.setIcon(Icon::Go::Home).setBordered(false).onActivate([&] {
pathEdit.setText(userpath());
refresh();
});
upButton.setIcon(Icon::Go::Up).setBordered(false).onActivate([&] {
pathEdit.setText(settings.activePath.dirname());
refresh();
});
scanList.onActivate([&] { activate(); });
selectAllButton.setText("Select All").onActivate([&] {
for(auto& item : scanList.items()) item.cell(0).setChecked(true);
});
unselectAllButton.setText("Unselect All").onActivate([&] {
for(auto& item : scanList.items()) item.cell(0).setChecked(false);
});
createManifestsLabel.setChecked(settings.createManifests).setText("Create Manifests").onToggle([&] {
settings.createManifests = createManifestsLabel.checked();
});
importButton.setText("Import ...").onActivate([&] { import(); });
setTitle("icarus");
setSize({800, 480});
setCentered();
}
auto ScanDialog::show() -> void {
setVisible();
pathEdit.setText(settings.activePath);
refresh();
}
auto ScanDialog::refresh() -> void {
scanList.reset();
scanList.append(ListViewHeader().setVisible(false).append(ListViewColumn().setExpandable()));
auto pathname = pathEdit.text().transform("\\", "/").rtrim("/").append("/");
if(!directory::exists(pathname)) return;
pathEdit.setText(settings.activePath = pathname);
auto contents = directory::icontents(pathname);
for(auto& name : contents) {
if(!name.endsWith("/")) continue;
if(gamePakType(name.suffixname())) continue;
scanList.append(ListViewItem().append(ListViewCell().setIcon(Icon::Emblem::Folder).setText(name.rtrim("/"))));
}
for(auto& name : contents) {
if(name.endsWith("/")) continue;
if(!gameRomType(name.suffixname().downcase())) continue;
scanList.append(ListViewItem().append(ListViewCell().setCheckable().setIcon(Icon::Emblem::File).setText(name)));
}
Application::processEvents();
scanList.resizeColumns();
scanList.setFocused();
}
auto ScanDialog::activate() -> void {
if(auto item = scanList.selected()) {
string location{settings.activePath, item.cell(0).text()};
if(directory::exists(location) && !gamePakType(location.suffixname())) {
pathEdit.setText(location);
refresh();
}
}
}
auto ScanDialog::import() -> void {
lstring filenames;
for(auto& item : scanList.items()) {
if(item.cell(0).checked()) {
filenames.append(string{settings.activePath, item.cell(0).text()});
}
}
if(!filenames) {
MessageDialog().setParent(*this).setText("Nothing selected to import.").error();
return;
}
setVisible(false);
importDialog->run(filenames);
}
auto ScanDialog::gamePakType(const string& type) -> bool {
return type == ".sys"
|| type == ".fc"
|| type == ".sfc"
|| type == ".gb"
|| type == ".gbc"
|| type == ".gba"
|| type == ".bs"
|| type == ".st";
}
auto ScanDialog::gameRomType(const string& type) -> bool {
return type == ".zip"
|| type == ".fc" || type == ".nes"
|| type == ".sfc" || type == ".smc"
|| type == ".gb"
|| type == ".gbc"
|| type == ".gba"
|| type == ".bs"
|| type == ".st";
}

54
icarus/ui/ui.hpp Normal file
View File

@ -0,0 +1,54 @@
struct ScanDialog : Window {
ScanDialog();
auto show() -> void;
auto refresh() -> void;
auto activate() -> void;
auto import() -> void;
auto gamePakType(const string& type) -> bool;
auto gameRomType(const string& type) -> bool;
VerticalLayout layout{this};
HorizontalLayout pathLayout{&layout, Size{~0, 0}};
LineEdit pathEdit{&pathLayout, Size{~0, 0}, 0};
Button refreshButton{&pathLayout, Size{0, 0}, 0};
Button homeButton{&pathLayout, Size{0, 0}, 0};
Button upButton{&pathLayout, Size{0, 0}, 0};
ListView scanList{&layout, Size{~0, ~0}};
HorizontalLayout controlLayout{&layout, Size{~0, 0}};
Button selectAllButton{&controlLayout, Size{100, 0}};
Button unselectAllButton{&controlLayout, Size{100, 0}};
CheckLabel createManifestsLabel{&controlLayout, Size{~0, 0}};
Button importButton{&controlLayout, Size{80, 0}};
};
struct ImportDialog : Window {
ImportDialog();
auto run(lstring locations) -> void;
bool abort;
lstring errors;
VerticalLayout layout{this};
Label statusLabel{&layout, Size{~0, 0}};
ProgressBar progressBar{&layout, Size{~0, 0}};
HorizontalLayout controlLayout{&layout, Size{~0, 0}};
Widget spacer{&controlLayout, Size{~0, 0}};
Button stopButton{&controlLayout, Size{80, 0}};
};
struct ErrorDialog : Window {
ErrorDialog();
auto show(const string& text) -> void;
VerticalLayout layout{this};
TextEdit errorLog{&layout, Size{~0, ~0}};
HorizontalLayout controlLayout{&layout, Size{~0, 0}};
Widget spacer{&controlLayout, Size{~0, 0}};
Button closeButton{&controlLayout, Size{80, 0}};
};
ScanDialog* scanDialog = nullptr;
ImportDialog* importDialog = nullptr;
ErrorDialog* errorDialog = nullptr;