mirror of https://github.com/bsnes-emu/bsnes.git
Added icarus 20150821.
This commit is contained in:
parent
213879771e
commit
7081f46e45
|
@ -1 +1,2 @@
|
|||
ananke/libananke.so
|
||||
icarus/icarus
|
||||
|
|
|
@ -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
|
|
@ -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();
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
};
|
|
@ -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();
|
||||
}
|
|
@ -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();
|
||||
}
|
|
@ -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();
|
||||
}
|
|
@ -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();
|
||||
}
|
|
@ -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();
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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");
|
||||
}
|
|
@ -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");
|
||||
}
|
|
@ -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");
|
||||
}
|
|
@ -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");
|
||||
}
|
|
@ -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");
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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();
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
*.o
|
|
@ -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"});
|
||||
}
|
|
@ -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();
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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";
|
||||
}
|
|
@ -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;
|
Loading…
Reference in New Issue