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
|
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