mirror of
https://github.com/bsnes-emu/bsnes.git
synced 2025-09-19 17:21:27 +02:00
Compare commits
13 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
6ac67c260b | ||
|
032e924495 | ||
|
b389d17c9a | ||
|
d59ae34e12 | ||
|
85f2e9a6d4 | ||
|
019fc1a2c6 | ||
|
84e98833ca | ||
|
d4751c5244 | ||
|
ab345ff20c | ||
|
c495c132a7 | ||
|
ef746bbda4 | ||
|
94b2538af5 | ||
|
7f404e6edb |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -1,3 +1,5 @@
|
||||
purify/*.o
|
||||
purify/purify
|
||||
purify/analyze-gba
|
||||
ananke/ananke.o
|
||||
ananke/libananke.so
|
||||
|
51
ananke/Makefile
Normal file
51
ananke/Makefile
Normal file
@@ -0,0 +1,51 @@
|
||||
include nall/Makefile
|
||||
include phoenix/Makefile
|
||||
|
||||
path := /usr/local/lib
|
||||
flags := -I. -O3 -fomit-frame-pointer
|
||||
ifeq ($(arch),win32)
|
||||
flags := -m32 $(flags)
|
||||
endif
|
||||
|
||||
all:
|
||||
$(cpp) $(flags) -fPIC -o ananke.o -c ananke.cpp
|
||||
ifeq ($(platform),x)
|
||||
$(cpp) $(flags) -shared -Wl,-soname,libananke.so.1 -o libananke.so ananke.o
|
||||
else ifeq ($(platform),win)
|
||||
$(cpp) $(flags) -fPIC -o phoenix.o -c phoenix/phoenix.cpp $(phoenixflags)
|
||||
$(cpp) $(flags) -shared -o phoenix.dll phoenix.o $(phoenixlink)
|
||||
$(cpp) $(flags) -shared -o ananke.dll ananke.o -L. -lphoenix
|
||||
endif
|
||||
|
||||
resource: force
|
||||
sourcery resource/resource.bml resource/resource.cpp resource/resource.hpp
|
||||
|
||||
clean:
|
||||
-@$(call delete,*.o)
|
||||
-@$(call delete,*.so)
|
||||
|
||||
install: uninstall
|
||||
ifeq ($(platform),x)
|
||||
if [ ! -d ~/.config/ananke ]; then mkdir ~/.config/ananke; fi
|
||||
sudo cp libananke.so $(path)/libananke.so.1
|
||||
sudo ln -s $(path)/libananke.so.1 $(path)/libananke.so
|
||||
endif
|
||||
|
||||
uninstall:
|
||||
ifeq ($(platform),x)
|
||||
if [ -f $(path)/libananke.so ]; then sudo rm $(path)/libananke.so; fi
|
||||
if [ -f $(path)/libananke.so.1 ]; then sudo rm $(path)/libananke.so.1; fi
|
||||
endif
|
||||
|
||||
sync:
|
||||
ifeq ($(shell id -un),byuu)
|
||||
if [ -d ./nall ]; then rm -r ./nall; fi
|
||||
if [ -d ./phoenix ]; then rm -r ./phoenix; fi
|
||||
cp -r ../nall ./nall
|
||||
cp -r ../phoenix ./phoenix
|
||||
rm -r nall/test
|
||||
rm -r phoenix/nall
|
||||
rm -r phoenix/test
|
||||
endif
|
||||
|
||||
force:
|
143
ananke/ananke.cpp
Normal file
143
ananke/ananke.cpp
Normal file
@@ -0,0 +1,143 @@
|
||||
#include <nall/nall.hpp>
|
||||
#include <nall/beat/patch.hpp>
|
||||
#include "heuristics/famicom.hpp"
|
||||
#include "heuristics/super-famicom.hpp"
|
||||
#include "heuristics/game-boy.hpp"
|
||||
#include "heuristics/game-boy-advance.hpp"
|
||||
using namespace nall;
|
||||
|
||||
#include <phoenix/phoenix.hpp>
|
||||
using namespace phoenix;
|
||||
|
||||
namespace Database {
|
||||
#include "database/super-famicom.hpp"
|
||||
#include "database/sufami-turbo.hpp"
|
||||
#include "database/bsx-satellaview.hpp"
|
||||
};
|
||||
|
||||
struct Ananke {
|
||||
#include "configuration.cpp"
|
||||
|
||||
struct Information {
|
||||
string path; //path to selected file
|
||||
string name; //name of selected file (inside of archive if .zip)
|
||||
string archive; //pathname of archive
|
||||
string manifest; //manifest from successfully applied patch
|
||||
} information;
|
||||
|
||||
//archive.cpp
|
||||
vector<uint8_t> extractROM();
|
||||
vector<uint8_t> extractFile(const string &filename);
|
||||
|
||||
//patch.cpp
|
||||
void applyBeatPatch(vector<uint8_t> &buffer);
|
||||
|
||||
//famicom.cpp
|
||||
void copyFamicomSaves(const string &pathname);
|
||||
string createFamicomHeuristic(vector<uint8_t> &buffer);
|
||||
string openFamicom(vector<uint8_t> &buffer);
|
||||
|
||||
//super-famicom.cpp
|
||||
void copySuperFamicomSaves(const string &pathname);
|
||||
string createSuperFamicomDatabase(vector<uint8_t> &buffer, Markup::Node &document, const string &manifest);
|
||||
string createSuperFamicomHeuristic(vector<uint8_t> &buffer);
|
||||
void createSuperFamicomHeuristicFirmware(vector<uint8_t> &buffer, const string &pathname, bool firmware_appended);
|
||||
string openSuperFamicom(vector<uint8_t> &buffer);
|
||||
|
||||
//sufami-turbo.cpp
|
||||
void copySufamiTurboSaves(const string &pathname);
|
||||
string createSufamiTurboDatabase(vector<uint8_t> &buffer, Markup::Node &document, const string &manifest);
|
||||
string createSufamiTurboHeuristic(vector<uint8_t> &buffer);
|
||||
string openSufamiTurbo(vector<uint8_t> &buffer);
|
||||
|
||||
//bsx-satellaview.cpp
|
||||
string createBsxSatellaviewDatabase(vector<uint8_t> &buffer, Markup::Node &document, const string &manifest);
|
||||
string createBsxSatellaviewHeuristic(vector<uint8_t> &buffer);
|
||||
string openBsxSatellaview(vector<uint8_t> &buffer);
|
||||
|
||||
//game-boy.cpp
|
||||
void copyGameBoySaves(const string &pathname);
|
||||
string createGameBoyHeuristic(vector<uint8_t> &buffer);
|
||||
string openGameBoy(vector<uint8_t> &buffer);
|
||||
|
||||
//game-boy-advance.cpp
|
||||
void copyGameBoyAdvanceSaves(const string &pathname);
|
||||
string createGameBoyAdvanceHeuristic(vector<uint8_t> &buffer);
|
||||
string openGameBoyAdvance(vector<uint8_t> &buffer);
|
||||
|
||||
static bool supported(const string &filename);
|
||||
string open(string filename = "");
|
||||
};
|
||||
|
||||
#include "resource/resource.cpp"
|
||||
#include "file-dialog.cpp"
|
||||
#include "archive.cpp"
|
||||
#include "patch.cpp"
|
||||
#include "famicom.cpp"
|
||||
#include "super-famicom.cpp"
|
||||
#include "sufami-turbo.cpp"
|
||||
#include "bsx-satellaview.cpp"
|
||||
#include "game-boy.cpp"
|
||||
#include "game-boy-advance.cpp"
|
||||
|
||||
FileDialog *fileDialog = nullptr;
|
||||
|
||||
bool Ananke::supported(const string &filename) {
|
||||
string extension = nall::extension(filename);
|
||||
|
||||
if(extension == "fc" ) return true;
|
||||
if(extension == "nes") return true;
|
||||
if(extension == "sfc") return true;
|
||||
if(extension == "smc") return true;
|
||||
if(extension == "st" ) return true;
|
||||
if(extension == "bs" ) return true;
|
||||
if(extension == "gb" ) return true;
|
||||
if(extension == "gbc") return true;
|
||||
if(extension == "gba") return true;
|
||||
if(extension == "zip") return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
string Ananke::open(string filename) {
|
||||
if(filename.empty()) {
|
||||
if(!fileDialog) fileDialog = new FileDialog;
|
||||
fileDialog->setPath(config.path);
|
||||
filename = fileDialog->open();
|
||||
}
|
||||
|
||||
if(filename.empty()) return "";
|
||||
|
||||
information.path = dir(filename);
|
||||
information.name = notdir(filename);
|
||||
config.path = information.path; //remember last used directory
|
||||
|
||||
vector<uint8_t> buffer;
|
||||
if(filename.endswith(".zip")) {
|
||||
information.archive = filename;
|
||||
buffer = extractROM();
|
||||
} else {
|
||||
buffer = file::read(filename);
|
||||
}
|
||||
if(buffer.size() == 0) return ""; //failed to read file
|
||||
|
||||
applyBeatPatch(buffer);
|
||||
|
||||
if(information.name.endswith(".fc") || information.name.endswith(".nes")) return openFamicom(buffer);
|
||||
if(information.name.endswith(".sfc") || information.name.endswith(".smc")) return openSuperFamicom(buffer);
|
||||
if(information.name.endswith(".st")) return openSufamiTurbo(buffer);
|
||||
if(information.name.endswith(".bs")) return openBsxSatellaview(buffer);
|
||||
if(information.name.endswith(".gb") || information.name.endswith(".gbc")) return openGameBoy(buffer);
|
||||
if(information.name.endswith(".gba")) return openGameBoyAdvance(buffer);
|
||||
return "";
|
||||
}
|
||||
|
||||
extern "C" string ananke_browse(const string &filename) {
|
||||
Ananke ananke;
|
||||
return ananke.open();
|
||||
}
|
||||
|
||||
extern "C" string ananke_open(const string &filename) {
|
||||
Ananke ananke;
|
||||
return ananke.open(filename);
|
||||
}
|
29
ananke/archive.cpp
Normal file
29
ananke/archive.cpp
Normal file
@@ -0,0 +1,29 @@
|
||||
vector<uint8_t> Ananke::extractROM() {
|
||||
unzip archive;
|
||||
if(archive.open(information.archive)) {
|
||||
for(auto &file : archive.file) {
|
||||
if(
|
||||
file.name.endswith(".fc") || file.name.endswith(".nes")
|
||||
|| file.name.endswith(".sfc") || file.name.endswith(".smc")
|
||||
|| file.name.endswith(".gb") || file.name.endswith(".gbc")
|
||||
|| file.name.endswith(".gba")
|
||||
) {
|
||||
information.name = notdir(file.name);
|
||||
return archive.extract(file);
|
||||
}
|
||||
}
|
||||
}
|
||||
return vector<uint8_t>();
|
||||
}
|
||||
|
||||
vector<uint8_t> Ananke::extractFile(const string &filename) {
|
||||
unzip archive;
|
||||
if(archive.open(information.archive)) {
|
||||
for(auto &file : archive.file) {
|
||||
if(notdir(file.name) == filename) {
|
||||
return archive.extract(file);
|
||||
}
|
||||
}
|
||||
}
|
||||
return vector<uint8_t>();
|
||||
}
|
60
ananke/bsx-satellaview.cpp
Normal file
60
ananke/bsx-satellaview.cpp
Normal file
@@ -0,0 +1,60 @@
|
||||
string Ananke::createBsxSatellaviewDatabase(vector<uint8_t> &buffer, Markup::Node &document, const string &manifest) {
|
||||
string pathname = {
|
||||
userpath(), "Emulation/BS-X Satellaview/",
|
||||
document["release/information/name"].text(),
|
||||
" (", document["release/information/region"].text(), ")",
|
||||
" (", document["release/information/revision"].text(), ")",
|
||||
".bs/"
|
||||
};
|
||||
directory::create(pathname);
|
||||
|
||||
//strip "release" root node from database entry (since a single game manifest isn't part of a database)
|
||||
string markup = manifest;
|
||||
markup.replace("\n ", "\n");
|
||||
markup.replace("information", "\ninformation");
|
||||
markup.ltrim<1>("release\n");
|
||||
|
||||
file::write({pathname, "manifest.bml"}, markup);
|
||||
file::write({pathname, "program.rom"}, buffer);
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
string Ananke::createBsxSatellaviewHeuristic(vector<uint8_t> &buffer) {
|
||||
string pathname = {
|
||||
userpath(), "Emulation/BS-X Satellaview/",
|
||||
nall::basename(information.name),
|
||||
" (!).bs/"
|
||||
};
|
||||
directory::create(pathname);
|
||||
|
||||
file::write({pathname, "manifest.bml"}, {
|
||||
"cartridge\n"
|
||||
" rom name=program.rom size=0x", hex(buffer.size()), " type=FlashROM\n",
|
||||
"\n",
|
||||
"information\n",
|
||||
" title: ", nall::basename(information.name), "\n"
|
||||
});
|
||||
file::write({pathname, "program.rom"}, buffer);
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
string Ananke::openBsxSatellaview(vector<uint8_t> &buffer) {
|
||||
string sha256 = nall::sha256(buffer.data(), buffer.size());
|
||||
|
||||
string databaseText = string::read({configpath(), "ananke/database/BS-X Satellaview.bml"}).strip();
|
||||
if(databaseText.empty()) databaseText = string{Database::BsxSatellaview}.strip();
|
||||
lstring databaseItem = databaseText.split("\n\n");
|
||||
|
||||
for(auto &item : databaseItem) {
|
||||
item.append("\n");
|
||||
auto document = Markup::Document(item);
|
||||
|
||||
if(document["release/information/sha256"].text() == sha256) {
|
||||
return createBsxSatellaviewDatabase(buffer, document, item);
|
||||
}
|
||||
}
|
||||
|
||||
return createBsxSatellaviewHeuristic(buffer);
|
||||
}
|
13
ananke/configuration.cpp
Normal file
13
ananke/configuration.cpp
Normal file
@@ -0,0 +1,13 @@
|
||||
struct Configuration : configuration {
|
||||
string path;
|
||||
|
||||
Configuration() {
|
||||
append(path = userpath(), "Path");
|
||||
directory::create({configpath(), "ananke/"});
|
||||
load({configpath(), "ananke/settings.cfg"});
|
||||
}
|
||||
|
||||
~Configuration() {
|
||||
save({configpath(), "ananke/settings.cfg"});
|
||||
}
|
||||
} config;
|
19
ananke/database/bsx-satellaview.hpp
Normal file
19
ananke/database/bsx-satellaview.hpp
Normal file
@@ -0,0 +1,19 @@
|
||||
string BsxSatellaview = R"(
|
||||
|
||||
database revision=2013-01-14
|
||||
|
||||
release
|
||||
cartridge
|
||||
rom name=program.rom size=0x80000 type=MaskROM
|
||||
information
|
||||
title: 鮫亀 キャラカセット
|
||||
name: Same Game - Character Cassette
|
||||
region: JP
|
||||
revision: 1.0
|
||||
board: BSMC-CR-01
|
||||
serial: BSMC-ZS5J-JPN
|
||||
sha256: 80c34b50817d58820bc8c88d2d9fa462550b4a76372e19c6467cbfbc8cf5d9ef
|
||||
configuration
|
||||
rom name=program.rom size=0x80000 type=MaskROM
|
||||
|
||||
)";
|
162
ananke/database/sufami-turbo.hpp
Normal file
162
ananke/database/sufami-turbo.hpp
Normal file
@@ -0,0 +1,162 @@
|
||||
string SufamiTurbo = R"(
|
||||
|
||||
database revision=2013-01-14
|
||||
|
||||
release
|
||||
cartridge linkable
|
||||
rom name=program.rom size=0x80000
|
||||
ram name=save.ram size=0x800
|
||||
information
|
||||
title: SDウルトラバトル ウルトラマン伝説
|
||||
name: SD Ultra Battle - Ultraman Densetsu
|
||||
region: JP
|
||||
revision: 1.0
|
||||
serial: SFT-0101-JPN
|
||||
sha256: 2bb55214fb668ca603d7b944b14f105dfb10b987a8902d420fe4ae1cb69c1d4a
|
||||
configuration
|
||||
rom name=program.rom size=0x80000
|
||||
ram name=save.ram size=0x800
|
||||
linkable
|
||||
|
||||
release
|
||||
cartridge linkable
|
||||
rom name=program.rom size=0x80000
|
||||
ram name=save.ram size=0x800
|
||||
information
|
||||
title: SDウルトラバトル セブン伝説
|
||||
name: SD Ultra Battle - Seven Densetsu
|
||||
region: JP
|
||||
revision: 1.0
|
||||
serial: SFT-0102-JPN
|
||||
sha256: 2fec5f2bc7dee010af10569a3d2bc18715a79a126940800c3eade5abbd625e3f
|
||||
configuration
|
||||
rom name=program.rom size=0x80000
|
||||
ram name=save.ram size=0x800
|
||||
linkable
|
||||
|
||||
release
|
||||
cartridge linkable
|
||||
rom name=program.rom size=0x80000
|
||||
ram name=save.ram size=0x800
|
||||
information
|
||||
title: ポイポイ忍者ワールド
|
||||
name: Poi Poi Ninja World
|
||||
region: JP
|
||||
revision: 1.0
|
||||
serial: SFT-0103-JPN
|
||||
sha256: 602b20b788640f5743487108a10f3f77bca5ce2d24208b25b1ca498a96eb0d69
|
||||
configuration
|
||||
rom name=program.rom size=0x80000
|
||||
ram name=save.ram size=0x800
|
||||
linkable
|
||||
|
||||
release
|
||||
cartridge linkable
|
||||
rom name=program.rom size=0x80000
|
||||
ram name=save.ram size=0x2000
|
||||
information
|
||||
title: SDガンダムジェネレーション 一年戦争記
|
||||
name: SD Gundam Generation - Ichinen Sensouki
|
||||
region: JP
|
||||
revision: 1.0
|
||||
serial: SFT-0104-JPN
|
||||
sha256: 3e82215bed08274874b30d461fc4a965c6bca932229da5d46d56e36f484d65eb
|
||||
configuration
|
||||
rom name=program.rom size=0x80000
|
||||
ram name=save.ram size=0x2000
|
||||
linkable
|
||||
|
||||
release
|
||||
cartridge linkable
|
||||
rom name=program.rom size=0x80000
|
||||
ram name=save.ram size=0x2000
|
||||
information
|
||||
title: SDガンダムジェネレーション グリプス戦記
|
||||
name: SD Gundam Generation - Grips Senki
|
||||
region: JP
|
||||
revision: 1.0
|
||||
serial: SFT-0105-JPN
|
||||
sha256: 8547a08ed11fe408eac282a90ac46654bd2e5f49bda3aec8e5edf166a0a4b9af
|
||||
configuration
|
||||
rom name=program.rom size=0x80000
|
||||
ram name=save.ram size=0x2000
|
||||
linkable
|
||||
|
||||
release
|
||||
cartridge
|
||||
rom name=program.rom size=0x80000
|
||||
information
|
||||
title: ゲゲゲの鬼太郎 妖怪ドンジャラ
|
||||
name: Gegege no Kitarou - Youkai Donjara
|
||||
region: JP
|
||||
revision: 1.0
|
||||
serial: SFT-0106-JPN
|
||||
sha256: d93b3a570e7cf343f680ab0768a50b77e3577f9c555007e2de3decd6bc4765c8
|
||||
configuration
|
||||
rom name=program.rom size=0x80000
|
||||
|
||||
release
|
||||
cartridge linkable
|
||||
rom name=program.rom size=0x80000
|
||||
ram name=save.ram size=0x2000
|
||||
information
|
||||
title: SDガンダムジェネレーション アクシズ戦記
|
||||
name: SD Gundam Generation - Axis Senki
|
||||
region: JP
|
||||
revision: 1.0
|
||||
serial: SFT-0107-JPN
|
||||
sha256: 2a9d7c9a61318861028a73ca03e32a48cff162d76cba36fbaab8690b212efe9b
|
||||
configuration
|
||||
rom name=program.rom size=0x80000
|
||||
ram name=save.ram size=0x2000
|
||||
linkable
|
||||
|
||||
release
|
||||
cartridge linkable
|
||||
rom name=program.rom size=0x80000
|
||||
ram name=save.ram size=0x2000
|
||||
information
|
||||
title: SDガンダムジェネレーション バビロニア建国戦記
|
||||
name: SD Gundam Generation - Babylonia Kenkoku Senki
|
||||
region: JP
|
||||
revision: 1.0
|
||||
serial: SFT-0108-JPN
|
||||
sha256: 60ac017c18f534e8cf24ca7f38e22ce92db95ea6c30b2d59d76f13c4f1c8a6e4
|
||||
configuration
|
||||
rom name=program.rom size=0x80000
|
||||
ram name=save.ram size=0x2000
|
||||
linkable
|
||||
|
||||
release
|
||||
cartridge linkable
|
||||
rom name=program.rom size=0x80000
|
||||
ram name=save.ram size=0x2000
|
||||
information
|
||||
title: SDガンダムジェネレーション ザンスカール戦記
|
||||
name: SD Gundam Generation - Zanscar Senki
|
||||
region: JP
|
||||
revision: 1.0
|
||||
serial: SFT-0110-JPN
|
||||
sha256: 5951a58a91d8e397d0a237ccc2b1248e17c7312cb9cc11cbc350200a97b4e021
|
||||
configuration
|
||||
rom name=program.rom size=0x80000
|
||||
ram name=save.ram size=0x2000
|
||||
linkable
|
||||
|
||||
release
|
||||
cartridge linkable
|
||||
rom name=program.rom size=0x80000
|
||||
ram name=save.ram size=0x800
|
||||
information
|
||||
title: SDガンダムジェネレーション コロニー格闘記
|
||||
name: SD Gundam Generation - Colony Kakutouki
|
||||
region: JP
|
||||
revision: 1.0
|
||||
serial: SFT-0111-JPN
|
||||
sha256: e639b5d5d722432b6809ccc6801dc584e1a3016379f34b335ed2dfa73b1ebf69
|
||||
configuration
|
||||
rom name=program.rom size=0x80000
|
||||
ram name=save.ram size=0x800
|
||||
linkable
|
||||
|
||||
)";
|
13382
ananke/database/super-famicom.hpp
Normal file
13382
ananke/database/super-famicom.hpp
Normal file
File diff suppressed because it is too large
Load Diff
32
ananke/famicom.cpp
Normal file
32
ananke/famicom.cpp
Normal file
@@ -0,0 +1,32 @@
|
||||
void Ananke::copyFamicomSaves(const string &pathname) {
|
||||
if(!file::exists({pathname, "save.ram"})) {
|
||||
if(file::exists({information.path, nall::basename(information.name), ".sav"})) {
|
||||
file::copy({information.path, nall::basename(information.name), ".srm"}, {pathname, "save.ram"});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
string Ananke::createFamicomHeuristic(vector<uint8_t> &buffer) {
|
||||
string pathname = {
|
||||
userpath(), "Emulation/Famicom/",
|
||||
nall::basename(information.name),
|
||||
" (!).fc/"
|
||||
};
|
||||
directory::create(pathname);
|
||||
|
||||
FamicomCartridge info(buffer.data(), buffer.size());
|
||||
string markup = info.markup();
|
||||
markup.append("\ninformation\n title: ", nall::basename(information.name), "\n");
|
||||
if(!information.manifest.empty()) markup = information.manifest; //override with embedded beat manifest, if one exists
|
||||
|
||||
file::write({pathname, "manifest.bml"}, markup);
|
||||
file::write({pathname, "program.rom"}, buffer.data() + 16, info.prgrom);
|
||||
if(info.chrrom > 0) file::write({pathname, "character.rom"}, buffer.data() + 16 + info.prgrom, info.chrrom);
|
||||
|
||||
copyFamicomSaves(pathname);
|
||||
return pathname;
|
||||
}
|
||||
|
||||
string Ananke::openFamicom(vector<uint8_t> &buffer) {
|
||||
return createFamicomHeuristic(buffer);
|
||||
}
|
125
ananke/file-dialog.cpp
Normal file
125
ananke/file-dialog.cpp
Normal file
@@ -0,0 +1,125 @@
|
||||
struct FileDialog : Window {
|
||||
VerticalLayout layout;
|
||||
HorizontalLayout pathLayout;
|
||||
LineEdit pathEdit;
|
||||
Button homeButton;
|
||||
Button upButton;
|
||||
ListView fileList;
|
||||
HorizontalLayout controlLayout;
|
||||
Label filterLabel;
|
||||
Button openButton;
|
||||
|
||||
string open() {
|
||||
setModal();
|
||||
setVisible();
|
||||
fileList.setFocused();
|
||||
filename = "";
|
||||
bool backspace = false;
|
||||
|
||||
dialogActive = true;
|
||||
while(dialogActive) {
|
||||
OS::processEvents();
|
||||
if(Keyboard::pressed(Keyboard::Scancode::Escape)) onClose();
|
||||
if(Keyboard::pressed(Keyboard::Scancode::Backspace)) {
|
||||
if(backspace == false) {
|
||||
backspace = true;
|
||||
if(fileList.focused()) upButton.onActivate();
|
||||
}
|
||||
} else {
|
||||
backspace = false;
|
||||
}
|
||||
usleep(20 * 1000);
|
||||
}
|
||||
|
||||
return filename;
|
||||
}
|
||||
|
||||
void setPath(const string &path) {
|
||||
pathname = string{path}.transform("\\", "/");
|
||||
if(pathname.empty()) pathname = userpath();
|
||||
if(pathname.endswith("/") == false) pathname.append("/");
|
||||
pathEdit.setText(pathname);
|
||||
|
||||
fileList.reset();
|
||||
filenameList.reset();
|
||||
|
||||
lstring folders = directory::ifolders(pathname);
|
||||
for(auto &folder : folders) {
|
||||
fileList.append(string{folder}.rtrim<1>("/"));
|
||||
fileList.setImage(filenameList.size(), 0, {resource::folder, sizeof resource::folder});
|
||||
filenameList.append({pathname, folder});
|
||||
}
|
||||
|
||||
lstring files = directory::ifiles(pathname);
|
||||
for(auto &file : files) {
|
||||
if(Ananke::supported(file) == false) continue; //ignore unsupported extensions
|
||||
fileList.append(file);
|
||||
if(extension(file) == "zip") {
|
||||
fileList.setImage(filenameList.size(), 0, {resource::archive, sizeof resource::archive});
|
||||
} else {
|
||||
fileList.setImage(filenameList.size(), 0, {resource::file, sizeof resource::file});
|
||||
}
|
||||
filenameList.append({pathname, file});
|
||||
}
|
||||
|
||||
fileList.setSelection(0);
|
||||
fileList.setSelected();
|
||||
fileList.setFocused();
|
||||
}
|
||||
|
||||
FileDialog() {
|
||||
setFrameGeometry({64, 64, 480, 600});
|
||||
setTitle("Load Image");
|
||||
|
||||
layout.setMargin(5);
|
||||
homeButton.setImage({resource::home, sizeof resource::home});
|
||||
upButton.setImage({resource::up, sizeof resource::up});
|
||||
filterLabel.setText("Filter: *.fc, *.sfc, *.st, *.bs, *.gb, *.gbc, *.gba, *.nes, *.smc, *.zip");
|
||||
openButton.setText("Open");
|
||||
|
||||
append(layout);
|
||||
layout.append(pathLayout, {~0, 0}, 5);
|
||||
pathLayout.append(pathEdit, {~0, 0}, 5);
|
||||
pathLayout.append(homeButton, {28, 28}, 5);
|
||||
pathLayout.append(upButton, {28, 28});
|
||||
layout.append(fileList, {~0, ~0}, 5);
|
||||
layout.append(controlLayout, {~0, 0});
|
||||
controlLayout.append(filterLabel, {~0, 0}, 5);
|
||||
controlLayout.append(openButton, {80, 0});
|
||||
|
||||
pathEdit.onActivate = [&] {
|
||||
string path = pathEdit.text();
|
||||
setPath(path);
|
||||
};
|
||||
|
||||
homeButton.onActivate = [&] {
|
||||
setPath(userpath());
|
||||
};
|
||||
|
||||
upButton.onActivate = [&] {
|
||||
setPath(parentdir(pathname));
|
||||
};
|
||||
|
||||
fileList.onActivate = openButton.onActivate = [&] {
|
||||
if(fileList.selected() == false) return;
|
||||
string name = filenameList(fileList.selection());
|
||||
if(name.empty()) return;
|
||||
|
||||
if(name.endswith("/")) return setPath(name);
|
||||
filename = name;
|
||||
onClose();
|
||||
};
|
||||
|
||||
onClose = [&] {
|
||||
dialogActive = false;
|
||||
setModal(false);
|
||||
setVisible(false);
|
||||
};
|
||||
}
|
||||
|
||||
private:
|
||||
bool dialogActive;
|
||||
string pathname;
|
||||
string filename;
|
||||
lstring filenameList;
|
||||
};
|
37
ananke/game-boy-advance.cpp
Normal file
37
ananke/game-boy-advance.cpp
Normal file
@@ -0,0 +1,37 @@
|
||||
void Ananke::copyGameBoyAdvanceSaves(const string &pathname) {
|
||||
if(!file::exists({pathname, "save.ram"})) {
|
||||
if(file::exists({information.path, nall::basename(information.name), ".sav"})) {
|
||||
file::copy({information.path, nall::basename(information.name), ".sav"}, {pathname, "save.ram"});
|
||||
}
|
||||
}
|
||||
|
||||
if(!file::exists({pathname, "rtc.ram"})) {
|
||||
if(file::exists({information.path, nall::basename(information.name), ".rtc"})) {
|
||||
file::copy({information.path, nall::basename(information.name), ".rtc"}, {pathname, "rtc.ram"});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
string Ananke::createGameBoyAdvanceHeuristic(vector<uint8_t> &buffer) {
|
||||
string pathname = {
|
||||
userpath(), "Emulation/Game Boy Advance/",
|
||||
nall::basename(information.name),
|
||||
" (!).gba/"
|
||||
};
|
||||
directory::create(pathname);
|
||||
|
||||
GameBoyAdvanceCartridge info(buffer.data(), buffer.size());
|
||||
string markup = info.markup;
|
||||
markup.append("\ninformation\n title: ", nall::basename(information.name), "\n");
|
||||
if(!information.manifest.empty()) markup = information.manifest; //override with embedded beat manifest, if one exists
|
||||
|
||||
file::write({pathname, "manifest.bml"}, markup);
|
||||
file::write({pathname, "program.rom"}, buffer);
|
||||
|
||||
copyGameBoyAdvanceSaves(pathname);
|
||||
return pathname;
|
||||
}
|
||||
|
||||
string Ananke::openGameBoyAdvance(vector<uint8_t> &buffer) {
|
||||
return createGameBoyAdvanceHeuristic(buffer);
|
||||
}
|
39
ananke/game-boy.cpp
Normal file
39
ananke/game-boy.cpp
Normal file
@@ -0,0 +1,39 @@
|
||||
void Ananke::copyGameBoySaves(const string &pathname) {
|
||||
if(!file::exists({pathname, "save.ram"})) {
|
||||
if(file::exists({information.path, nall::basename(information.name), ".sav"})) {
|
||||
file::copy({information.path, nall::basename(information.name), ".sav"}, {pathname, "save.ram"});
|
||||
}
|
||||
}
|
||||
|
||||
if(!file::exists({pathname, "rtc.ram"})) {
|
||||
if(file::exists({information.path, nall::basename(information.name), ".rtc"})) {
|
||||
file::copy({information.path, nall::basename(information.name), ".rtc"}, {pathname, "rtc.ram"});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
string Ananke::createGameBoyHeuristic(vector<uint8_t> &buffer) {
|
||||
GameBoyCartridge info(buffer.data(), buffer.size());
|
||||
|
||||
string pathname = {
|
||||
userpath(),
|
||||
"Emulation/Game Boy", (info.info.cgb ? " Color" : ""), "/",
|
||||
nall::basename(information.name),
|
||||
" (!).", (info.info.cgb ? "gbc" : "gb"), "/"
|
||||
};
|
||||
directory::create(pathname);
|
||||
|
||||
string markup = info.markup;
|
||||
markup.append("\ninformation\n title: ", nall::basename(information.name), "\n");
|
||||
if(!information.manifest.empty()) markup = information.manifest; //override with embedded beat manifest, if one exists
|
||||
|
||||
file::write({pathname, "manifest.bml"}, markup);
|
||||
file::write({pathname, "program.rom"}, buffer);
|
||||
|
||||
copyGameBoySaves(pathname);
|
||||
return pathname;
|
||||
}
|
||||
|
||||
string Ananke::openGameBoy(vector<uint8_t> &buffer) {
|
||||
return createGameBoyHeuristic(buffer);
|
||||
}
|
173
ananke/heuristics/famicom.hpp
Normal file
173
ananke/heuristics/famicom.hpp
Normal file
@@ -0,0 +1,173 @@
|
||||
#ifndef NALL_EMULATION_FAMICOM_HPP
|
||||
#define NALL_EMULATION_FAMICOM_HPP
|
||||
|
||||
#include <nall/sha256.hpp>
|
||||
#include <nall/string.hpp>
|
||||
|
||||
namespace nall {
|
||||
|
||||
struct FamicomCartridge {
|
||||
string markup;
|
||||
inline FamicomCartridge(const uint8_t *data, unsigned size);
|
||||
|
||||
//private:
|
||||
unsigned mapper;
|
||||
unsigned mirror;
|
||||
unsigned prgrom;
|
||||
unsigned prgram;
|
||||
unsigned chrrom;
|
||||
unsigned chrram;
|
||||
};
|
||||
|
||||
FamicomCartridge::FamicomCartridge(const uint8_t *data, unsigned size) {
|
||||
markup = "";
|
||||
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");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif
|
@@ -46,21 +46,16 @@ GameBoyAdvanceCartridge::GameBoyAdvanceCartridge(const uint8_t *data, unsigned s
|
||||
identifiers = list.concatenate(",");
|
||||
|
||||
markup = "";
|
||||
|
||||
markup.append("<?xml version='1.0' encoding='UTF-8'?>\n");
|
||||
markup.append("<cartridge sha256='", sha256(data, size), "'>\n");
|
||||
markup.append(" <rom name='program.rom' size='0x", hex(size), "'/>\n");
|
||||
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");
|
||||
|
||||
markup.append("</cartridge>\n");
|
||||
markup.transform("'", "\"");
|
||||
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");
|
||||
}
|
||||
|
||||
}
|
@@ -108,13 +108,11 @@ GameBoyCartridge::GameBoyCartridge(uint8_t *romdata, unsigned romsize) {
|
||||
|
||||
if(info.mapper == "MBC2") info.ramsize = 512; //512 x 4-bit
|
||||
|
||||
markup = "<?xml version='1.0' encoding='UTF-8'?>\n";
|
||||
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");
|
||||
markup.append("</cartridge>\n");
|
||||
markup.transform("'", "\"");
|
||||
markup = "";
|
||||
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");
|
||||
}
|
||||
|
||||
}
|
@@ -14,11 +14,8 @@ struct SatellaviewCartridge {
|
||||
SatellaviewCartridge::SatellaviewCartridge(const uint8_t *data, unsigned size) {
|
||||
markup = "";
|
||||
|
||||
markup.append("<?xml version='1.0' encoding='UTF-8'?>\n");
|
||||
markup.append("<cartridge sha256='", sha256(data, size) ,"'>\n");
|
||||
markup.append(" <rom name='program.rom' size='0x", hex(size), "'/>\n");
|
||||
markup.append("</cartridge>\n");
|
||||
markup.transform("'", "\"");
|
||||
markup.append("cartridge\n");
|
||||
markup.append(" rom name=program.rom size=0x", hex(size), " type=FlashROM\n");
|
||||
}
|
||||
|
||||
}
|
@@ -20,12 +20,10 @@ SufamiTurboCartridge::SufamiTurboCartridge(const uint8_t *data, unsigned size) {
|
||||
unsigned ramsize = data[0x37] * 0x800; //2KB
|
||||
bool linkable = data[0x35] != 0x00; //TODO: unconfirmed
|
||||
|
||||
markup.append("<?xml version='1.0' encoding='UTF-8'?>\n");
|
||||
markup.append("<cartridge linkable='", linkable, "' sha256='", sha256(data, size) ,"'>\n");
|
||||
markup.append(" <rom name='program.rom' size='0x", hex(romsize), "'/>\n");
|
||||
markup.append(" <ram name='save.ram' size='0x", hex(ramsize), "'/>\n");
|
||||
markup.append("</cartridge>\n");
|
||||
markup.transform("'", "\"");
|
||||
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");
|
||||
}
|
||||
|
||||
}
|
814
ananke/heuristics/super-famicom.hpp
Normal file
814
ananke/heuristics/super-famicom.hpp
Normal file
@@ -0,0 +1,814 @@
|
||||
#ifndef NALL_EMULATION_SUPER_FAMICOM_HPP
|
||||
#define NALL_EMULATION_SUPER_FAMICOM_HPP
|
||||
|
||||
#include <nall/sha256.hpp>
|
||||
#include <nall/string.hpp>
|
||||
|
||||
namespace nall {
|
||||
|
||||
struct SuperFamicomCartridge {
|
||||
string markup;
|
||||
inline SuperFamicomCartridge(const uint8_t *data, unsigned size);
|
||||
|
||||
//private:
|
||||
inline void read_header(const uint8_t *data, unsigned size);
|
||||
inline unsigned find_header(const uint8_t *data, unsigned size);
|
||||
inline unsigned score_header(const uint8_t *data, unsigned size, unsigned addr);
|
||||
|
||||
enum HeaderField {
|
||||
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 {
|
||||
ModeNormal,
|
||||
ModeBsxSlotted,
|
||||
ModeBsx,
|
||||
ModeSufamiTurbo,
|
||||
ModeSuperGameBoy,
|
||||
};
|
||||
|
||||
enum Type {
|
||||
TypeNormal,
|
||||
TypeBsxSlotted,
|
||||
TypeBsxBios,
|
||||
TypeBsx,
|
||||
TypeSufamiTurboBios,
|
||||
TypeSufamiTurbo,
|
||||
TypeSuperGameBoy1Bios,
|
||||
TypeSuperGameBoy2Bios,
|
||||
TypeGameBoy,
|
||||
TypeUnknown,
|
||||
};
|
||||
|
||||
enum Region {
|
||||
NTSC,
|
||||
PAL,
|
||||
};
|
||||
|
||||
enum MemoryMapper {
|
||||
LoROM,
|
||||
HiROM,
|
||||
ExLoROM,
|
||||
ExHiROM,
|
||||
SuperFXROM,
|
||||
SA1ROM,
|
||||
SPC7110ROM,
|
||||
BSCLoROM,
|
||||
BSCHiROM,
|
||||
BSXROM,
|
||||
STROM,
|
||||
};
|
||||
|
||||
enum DSP1MemoryMapper {
|
||||
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_appended; //true if firmware is appended to end of ROM data
|
||||
|
||||
Mode mode;
|
||||
Type type;
|
||||
Region region;
|
||||
MemoryMapper mapper;
|
||||
DSP1MemoryMapper dsp1_mapper;
|
||||
|
||||
bool has_bsx_slot;
|
||||
bool has_superfx;
|
||||
bool has_sa1;
|
||||
bool has_srtc;
|
||||
bool has_sdd1;
|
||||
bool has_spc7110;
|
||||
bool has_spc7110rtc;
|
||||
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_appended = false;
|
||||
|
||||
//skip copier header
|
||||
if((size & 0x7fff) == 512) data += 512, size -= 512;
|
||||
|
||||
markup = "";
|
||||
if(size < 0x8000) return;
|
||||
|
||||
read_header(data, size);
|
||||
|
||||
markup = "";
|
||||
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");
|
||||
|
||||
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\n"
|
||||
" icd2 revision=1\n"
|
||||
" rom name=sgb.boot.rom size=0x100\n"
|
||||
" map id=io address=00-3f,80-bf:6000-7fff\n"
|
||||
);
|
||||
if((rom_size & 0x7fff) == 0x100) {
|
||||
firmware_appended = true;
|
||||
rom_size -= 0x100;
|
||||
}
|
||||
}
|
||||
|
||||
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"
|
||||
);
|
||||
if((rom_size & 0x7fff) == 0xc00) {
|
||||
firmware_appended = true;
|
||||
rom_size -= 0xc00;
|
||||
}
|
||||
}
|
||||
|
||||
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 offset=0x400000\n"
|
||||
" map id=rom address=40-7f:0000-ffff offset=0x400000\n"
|
||||
" map id=rom address=80-bf:8000-ffff offset=0x000000 mask=0xc00000\n"
|
||||
" map id=rom address=c0-ff:0000-ffff offset=0x000000 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 offset=0x000000 mask=0x8000\n"
|
||||
" map id=rom address=20-3f:8000-ffff offset=0x100000 mask=0x8000\n"
|
||||
" map id=rom address=80-9f:8000-ffff offset=0x200000 mask=0x8000\n"
|
||||
" map id=rom address=a0-bf:8000-ffff offset=0x100000 mask=0x8000\n"
|
||||
" map id=ram address=70-7f,f0-ff:0000-7fff\n"
|
||||
" bsxslot\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"
|
||||
" bsxslot\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(
|
||||
" bsx\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=0x40000\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_spc7110rtc) {
|
||||
markup.append(
|
||||
" epsonrtc\n"
|
||||
" ram name=rtc.ram size=0x10\n"
|
||||
" map id=io address=00-3f,80-bf:4840-4842\n"
|
||||
);
|
||||
}
|
||||
|
||||
if(has_srtc) {
|
||||
markup.append(
|
||||
" sharprtc\n"
|
||||
" ram name=rtc.ram size=0x10\n"
|
||||
" map id=io address=00-3f,80-bf:2800-2801\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((size & 0x7fff) == 0x2000) {
|
||||
firmware_appended = true;
|
||||
rom_size -= 0x2000;
|
||||
}
|
||||
}
|
||||
|
||||
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((size & 0x7fff) == 0x2000) {
|
||||
firmware_appended = true;
|
||||
rom_size -= 0x2000;
|
||||
}
|
||||
}
|
||||
|
||||
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((size & 0x7fff) == 0x2000) {
|
||||
firmware_appended = true;
|
||||
rom_size -= 0x2000;
|
||||
}
|
||||
}
|
||||
|
||||
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 address=30-3f,b0-bf:8000-ffff select=0x4000\n"
|
||||
);
|
||||
if((size & 0x7fff) == 0x2000) {
|
||||
firmware_appended = true;
|
||||
rom_size -= 0x2000;
|
||||
}
|
||||
}
|
||||
|
||||
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((size & 0xffff) == 0xd000) {
|
||||
firmware_appended = true;
|
||||
rom_size -= 0xd000;
|
||||
}
|
||||
}
|
||||
|
||||
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((size & 0xffff) == 0xd000) {
|
||||
firmware_appended = true;
|
||||
rom_size -= 0xd000;
|
||||
}
|
||||
}
|
||||
|
||||
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"
|
||||
);
|
||||
if((size & 0x3ffff) == 0x28000) {
|
||||
firmware_appended = true;
|
||||
rom_size -= 0x28000;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SuperFamicomCartridge::read_header(const uint8_t *data, unsigned size) {
|
||||
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_srtc = false;
|
||||
has_sdd1 = false;
|
||||
has_spc7110 = false;
|
||||
has_spc7110rtc = 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 = find_header(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_srtc = 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_spc7110rtc = (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;
|
||||
}
|
||||
}
|
||||
|
||||
unsigned SuperFamicomCartridge::find_header(const uint8_t *data, unsigned size) {
|
||||
unsigned score_lo = score_header(data, size, 0x007fc0);
|
||||
unsigned score_hi = score_header(data, size, 0x00ffc0);
|
||||
unsigned score_ex = score_header(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;
|
||||
}
|
||||
}
|
||||
|
||||
unsigned SuperFamicomCartridge::score_header(const uint8_t *data, unsigned size, unsigned addr) {
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif
|
118
ananke/nall/base64.hpp
Normal file
118
ananke/nall/base64.hpp
Normal file
@@ -0,0 +1,118 @@
|
||||
#ifndef NALL_BASE64_HPP
|
||||
#define NALL_BASE64_HPP
|
||||
|
||||
#include <nall/stdint.hpp>
|
||||
#include <nall/string.hpp>
|
||||
|
||||
namespace nall {
|
||||
struct base64 {
|
||||
static bool encode(char *&output, const uint8_t* input, unsigned inlength) {
|
||||
output = new char[inlength * 8 / 6 + 8]();
|
||||
|
||||
unsigned i = 0, o = 0;
|
||||
while(i < inlength) {
|
||||
switch(i % 3) {
|
||||
|
||||
case 0: {
|
||||
output[o++] = enc(input[i] >> 2);
|
||||
output[o] = enc((input[i] & 3) << 4);
|
||||
break;
|
||||
}
|
||||
|
||||
case 1: {
|
||||
uint8_t prev = dec(output[o]);
|
||||
output[o++] = enc(prev + (input[i] >> 4));
|
||||
output[o] = enc((input[i] & 15) << 2);
|
||||
break;
|
||||
}
|
||||
|
||||
case 2: {
|
||||
uint8_t prev = dec(output[o]);
|
||||
output[o++] = enc(prev + (input[i] >> 6));
|
||||
output[o++] = enc(input[i] & 63);
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
i++;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static string encode(const string &data) {
|
||||
char *buffer = nullptr;
|
||||
encode(buffer, (const uint8_t*)(const char*)data, data.length());
|
||||
string result = buffer;
|
||||
delete[] buffer;
|
||||
return result;
|
||||
}
|
||||
|
||||
static bool decode(uint8_t *&output, unsigned &outlength, const char *input) {
|
||||
unsigned inlength = strlen(input), infix = 0;
|
||||
output = new uint8_t[inlength + 1]();
|
||||
|
||||
unsigned i = 0, o = 0;
|
||||
while(i < inlength) {
|
||||
uint8_t x = dec(input[i]);
|
||||
|
||||
switch(i++ & 3) {
|
||||
|
||||
case 0: {
|
||||
output[o] = x << 2;
|
||||
break;
|
||||
}
|
||||
|
||||
case 1: {
|
||||
output[o++] |= x >> 4;
|
||||
output[o] = (x & 15) << 4;
|
||||
break;
|
||||
}
|
||||
|
||||
case 2: {
|
||||
output[o++] |= x >> 2;
|
||||
output[o] = (x & 3) << 6;
|
||||
break;
|
||||
}
|
||||
|
||||
case 3: {
|
||||
output[o++] |= x;
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
outlength = o;
|
||||
return true;
|
||||
}
|
||||
|
||||
static string decode(const string &data) {
|
||||
uint8_t *buffer = nullptr;
|
||||
unsigned size = 0;
|
||||
decode(buffer, size, (const char*)data);
|
||||
string result = (const char*)buffer;
|
||||
delete[] buffer;
|
||||
return result;
|
||||
}
|
||||
|
||||
private:
|
||||
static char enc(uint8_t n) {
|
||||
//base64 for URL encodings (URL = -_, MIME = +/)
|
||||
static char lookup_table[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
|
||||
return lookup_table[n & 63];
|
||||
}
|
||||
|
||||
static uint8_t dec(char n) {
|
||||
if(n >= 'A' && n <= 'Z') return n - 'A';
|
||||
if(n >= 'a' && n <= 'z') return n - 'a' + 26;
|
||||
if(n >= '0' && n <= '9') return n - '0' + 52;
|
||||
if(n == '-') return 62;
|
||||
if(n == '_') return 63;
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
84
ananke/nall/beat/archive.hpp
Normal file
84
ananke/nall/beat/archive.hpp
Normal file
@@ -0,0 +1,84 @@
|
||||
#ifndef NALL_BEAT_ARCHIVE_HPP
|
||||
#define NALL_BEAT_ARCHIVE_HPP
|
||||
|
||||
#include <nall/beat/base.hpp>
|
||||
|
||||
namespace nall {
|
||||
|
||||
struct beatArchive : beatBase {
|
||||
bool create(const string &beatname, string pathname, const string &metadata = "") {
|
||||
if(fp.open(beatname, file::mode::write) == false) return false;
|
||||
if(pathname.endswith("/") == false) pathname.append("/");
|
||||
|
||||
checksum = ~0;
|
||||
writeString("BPA1");
|
||||
writeNumber(metadata.length());
|
||||
writeString(metadata);
|
||||
|
||||
lstring list;
|
||||
ls(list, pathname, pathname);
|
||||
for(auto &name : list) {
|
||||
if(name.endswith("/")) {
|
||||
name.rtrim<1>("/");
|
||||
writeNumber(0 | ((name.length() - 1) << 1));
|
||||
writeString(name);
|
||||
} else {
|
||||
file stream;
|
||||
if(stream.open({pathname, name}, file::mode::read) == false) return false;
|
||||
writeNumber(1 | ((name.length() - 1) << 1));
|
||||
writeString(name);
|
||||
unsigned size = stream.size();
|
||||
writeNumber(size);
|
||||
uint32_t checksum = ~0;
|
||||
while(size--) {
|
||||
uint8_t data = stream.read();
|
||||
write(data);
|
||||
checksum = crc32_adjust(checksum, data);
|
||||
}
|
||||
writeChecksum(~checksum);
|
||||
}
|
||||
}
|
||||
|
||||
writeChecksum(~checksum);
|
||||
fp.close();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool unpack(const string &beatname, string pathname) {
|
||||
if(fp.open(beatname, file::mode::read) == false) return false;
|
||||
if(pathname.endswith("/") == false) pathname.append("/");
|
||||
|
||||
checksum = ~0;
|
||||
if(readString(4) != "BPA1") return false;
|
||||
unsigned length = readNumber();
|
||||
while(length--) read();
|
||||
|
||||
directory::create(pathname);
|
||||
while(fp.offset() < fp.size() - 4) {
|
||||
unsigned data = readNumber();
|
||||
string name = readString((data >> 1) + 1);
|
||||
if(name.position("\\") || name.position("../")) return false; //block path exploits
|
||||
|
||||
if((data & 1) == 0) {
|
||||
directory::create({pathname, name});
|
||||
} else {
|
||||
file stream;
|
||||
if(stream.open({pathname, name}, file::mode::write) == false) return false;
|
||||
unsigned size = readNumber();
|
||||
uint32_t checksum = ~0;
|
||||
while(size--) {
|
||||
uint8_t data = read();
|
||||
stream.write(data);
|
||||
checksum = crc32_adjust(checksum, data);
|
||||
}
|
||||
if(readChecksum(~checksum) == false) return false;
|
||||
}
|
||||
}
|
||||
|
||||
return readChecksum(~checksum);
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
92
ananke/nall/beat/base.hpp
Normal file
92
ananke/nall/beat/base.hpp
Normal file
@@ -0,0 +1,92 @@
|
||||
#ifndef NALL_BEAT_BASE_HPP
|
||||
#define NALL_BEAT_BASE_HPP
|
||||
|
||||
namespace nall {
|
||||
|
||||
struct beatBase {
|
||||
protected:
|
||||
file fp;
|
||||
uint32_t checksum;
|
||||
|
||||
void ls(lstring &list, const string &path, const string &basepath) {
|
||||
lstring paths = directory::folders(path);
|
||||
for(auto &pathname : paths) {
|
||||
list.append(string{path, pathname}.ltrim<1>(basepath));
|
||||
ls(list, {path, pathname}, basepath);
|
||||
}
|
||||
|
||||
lstring files = directory::files(path);
|
||||
for(auto &filename : files) {
|
||||
list.append(string{path, filename}.ltrim<1>(basepath));
|
||||
}
|
||||
}
|
||||
|
||||
void write(uint8_t data) {
|
||||
fp.write(data);
|
||||
checksum = crc32_adjust(checksum, data);
|
||||
}
|
||||
|
||||
void writeNumber(uint64_t data) {
|
||||
while(true) {
|
||||
uint64_t x = data & 0x7f;
|
||||
data >>= 7;
|
||||
if(data == 0) return write(0x80 | x);
|
||||
write(x);
|
||||
data--;
|
||||
}
|
||||
}
|
||||
|
||||
void writeString(const string &text) {
|
||||
unsigned length = text.length();
|
||||
for(unsigned n = 0; n < length; n++) write(text[n]);
|
||||
}
|
||||
|
||||
void writeChecksum(uint32_t checksum) {
|
||||
write(checksum >> 0);
|
||||
write(checksum >> 8);
|
||||
write(checksum >> 16);
|
||||
write(checksum >> 24);
|
||||
}
|
||||
|
||||
uint8_t read() {
|
||||
uint8_t data = fp.read();
|
||||
checksum = crc32_adjust(checksum, data);
|
||||
return data;
|
||||
}
|
||||
|
||||
uint64_t readNumber() {
|
||||
uint64_t data = 0, shift = 1;
|
||||
while(true) {
|
||||
uint8_t x = read();
|
||||
data += (x & 0x7f) * shift;
|
||||
if(x & 0x80) break;
|
||||
shift <<= 7;
|
||||
data += shift;
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
string readString(unsigned length) {
|
||||
string text;
|
||||
text.reserve(length + 1);
|
||||
for(unsigned n = 0; n < length; n++) {
|
||||
text[n] = fp.read();
|
||||
checksum = crc32_adjust(checksum, text[n]);
|
||||
}
|
||||
text[length] = 0;
|
||||
return text;
|
||||
}
|
||||
|
||||
bool readChecksum(uint32_t source) {
|
||||
uint32_t checksum = 0;
|
||||
checksum |= read() << 0;
|
||||
checksum |= read() << 8;
|
||||
checksum |= read() << 16;
|
||||
checksum |= read() << 24;
|
||||
return checksum == source;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
@@ -1,5 +1,5 @@
|
||||
#ifndef NALL_BPS_DELTA_HPP
|
||||
#define NALL_BPS_DELTA_HPP
|
||||
#ifndef NALL_BEAT_DELTA_HPP
|
||||
#define NALL_BEAT_DELTA_HPP
|
||||
|
||||
#include <nall/crc32.hpp>
|
||||
#include <nall/file.hpp>
|
@@ -1,5 +1,5 @@
|
||||
#ifndef NALL_BPS_LINEAR_HPP
|
||||
#define NALL_BPS_LINEAR_HPP
|
||||
#ifndef NALL_BEAT_LINEAR_HPP
|
||||
#define NALL_BEAT_LINEAR_HPP
|
||||
|
||||
#include <nall/crc32.hpp>
|
||||
#include <nall/file.hpp>
|
@@ -1,5 +1,5 @@
|
||||
#ifndef NALL_BPS_METADATA_HPP
|
||||
#define NALL_BPS_METADATA_HPP
|
||||
#ifndef NALL_BEAT_METADATA_HPP
|
||||
#define NALL_BEAT_METADATA_HPP
|
||||
|
||||
#include <nall/crc32.hpp>
|
||||
#include <nall/file.hpp>
|
242
ananke/nall/beat/multi.hpp
Normal file
242
ananke/nall/beat/multi.hpp
Normal file
@@ -0,0 +1,242 @@
|
||||
#ifndef NALL_BEAT_MULTI_HPP
|
||||
#define NALL_BEAT_MULTI_HPP
|
||||
|
||||
#include <nall/beat/patch.hpp>
|
||||
#include <nall/beat/linear.hpp>
|
||||
#include <nall/beat/delta.hpp>
|
||||
|
||||
namespace nall {
|
||||
|
||||
struct bpsmulti {
|
||||
enum : unsigned {
|
||||
CreatePath = 0,
|
||||
CreateFile = 1,
|
||||
ModifyFile = 2,
|
||||
MirrorFile = 3,
|
||||
};
|
||||
|
||||
enum : unsigned {
|
||||
OriginSource = 0,
|
||||
OriginTarget = 1,
|
||||
};
|
||||
|
||||
bool create(const string &patchName, const string &sourcePath, const string &targetPath, bool delta = false, const string &metadata = "") {
|
||||
if(fp.open()) fp.close();
|
||||
fp.open(patchName, file::mode::write);
|
||||
checksum = ~0;
|
||||
|
||||
writeString("BPM1"); //signature
|
||||
writeNumber(metadata.length());
|
||||
writeString(metadata);
|
||||
|
||||
lstring sourceList, targetList;
|
||||
ls(sourceList, sourcePath, sourcePath);
|
||||
ls(targetList, targetPath, targetPath);
|
||||
|
||||
for(auto &targetName : targetList) {
|
||||
if(targetName.endswith("/")) {
|
||||
targetName.rtrim<1>("/");
|
||||
writeNumber(CreatePath | ((targetName.length() - 1) << 2));
|
||||
writeString(targetName);
|
||||
} else if(auto position = sourceList.find(targetName)) { //if sourceName == targetName
|
||||
file sp, dp;
|
||||
sp.open({sourcePath, targetName}, file::mode::read);
|
||||
dp.open({targetPath, targetName}, file::mode::read);
|
||||
|
||||
bool identical = sp.size() == dp.size();
|
||||
uint32_t cksum = ~0;
|
||||
|
||||
for(unsigned n = 0; n < sp.size(); n++) {
|
||||
uint8_t byte = sp.read();
|
||||
if(identical && byte != dp.read()) identical = false;
|
||||
cksum = crc32_adjust(cksum, byte);
|
||||
}
|
||||
|
||||
if(identical) {
|
||||
writeNumber(MirrorFile | ((targetName.length() - 1) << 2));
|
||||
writeString(targetName);
|
||||
writeNumber(OriginSource);
|
||||
writeChecksum(~cksum);
|
||||
} else {
|
||||
writeNumber(ModifyFile | ((targetName.length() - 1) << 2));
|
||||
writeString(targetName);
|
||||
writeNumber(OriginSource);
|
||||
|
||||
if(delta == false) {
|
||||
bpslinear patch;
|
||||
patch.source({sourcePath, targetName});
|
||||
patch.target({targetPath, targetName});
|
||||
patch.create({temppath(), "temp.bps"});
|
||||
} else {
|
||||
bpsdelta patch;
|
||||
patch.source({sourcePath, targetName});
|
||||
patch.target({targetPath, targetName});
|
||||
patch.create({temppath(), "temp.bps"});
|
||||
}
|
||||
|
||||
auto buffer = file::read({temppath(), "temp.bps"});
|
||||
writeNumber(buffer.size());
|
||||
for(auto &byte : buffer) write(byte);
|
||||
}
|
||||
} else {
|
||||
writeNumber(CreateFile | ((targetName.length() - 1) << 2));
|
||||
writeString(targetName);
|
||||
auto buffer = file::read({targetPath, targetName});
|
||||
writeNumber(buffer.size());
|
||||
for(auto &byte : buffer) write(byte);
|
||||
writeChecksum(crc32_calculate(buffer.data(), buffer.size()));
|
||||
}
|
||||
}
|
||||
|
||||
//checksum
|
||||
writeChecksum(~checksum);
|
||||
fp.close();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool apply(const string &patchName, const string &sourcePath, const string &targetPath) {
|
||||
directory::remove(targetPath); //start with a clean directory
|
||||
directory::create(targetPath);
|
||||
|
||||
if(fp.open()) fp.close();
|
||||
fp.open(patchName, file::mode::read);
|
||||
checksum = ~0;
|
||||
|
||||
if(readString(4) != "BPM1") return false;
|
||||
auto metadataLength = readNumber();
|
||||
while(metadataLength--) read();
|
||||
|
||||
while(fp.offset() < fp.size() - 4) {
|
||||
auto encoding = readNumber();
|
||||
unsigned action = encoding & 3;
|
||||
unsigned targetLength = (encoding >> 2) + 1;
|
||||
string targetName = readString(targetLength);
|
||||
|
||||
if(action == CreatePath) {
|
||||
directory::create({targetPath, targetName, "/"});
|
||||
} else if(action == CreateFile) {
|
||||
file fp;
|
||||
fp.open({targetPath, targetName}, file::mode::write);
|
||||
auto fileSize = readNumber();
|
||||
while(fileSize--) fp.write(read());
|
||||
uint32_t cksum = readChecksum();
|
||||
} else if(action == ModifyFile) {
|
||||
auto encoding = readNumber();
|
||||
string originPath = encoding & 1 ? targetPath : sourcePath;
|
||||
string sourceName = (encoding >> 1) == 0 ? targetName : readString(encoding >> 1);
|
||||
auto patchSize = readNumber();
|
||||
vector<uint8_t> buffer;
|
||||
buffer.resize(patchSize);
|
||||
for(unsigned n = 0; n < patchSize; n++) buffer[n] = read();
|
||||
bpspatch patch;
|
||||
patch.modify(buffer.data(), buffer.size());
|
||||
patch.source({originPath, sourceName});
|
||||
patch.target({targetPath, targetName});
|
||||
if(patch.apply() != bpspatch::result::success) return false;
|
||||
} else if(action == MirrorFile) {
|
||||
auto encoding = readNumber();
|
||||
string originPath = encoding & 1 ? targetPath : sourcePath;
|
||||
string sourceName = (encoding >> 1) == 0 ? targetName : readString(encoding >> 1);
|
||||
file::copy({originPath, sourceName}, {targetPath, targetName});
|
||||
uint32_t cksum = readChecksum();
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t cksum = ~checksum;
|
||||
if(read() != (uint8_t)(cksum >> 0)) return false;
|
||||
if(read() != (uint8_t)(cksum >> 8)) return false;
|
||||
if(read() != (uint8_t)(cksum >> 16)) return false;
|
||||
if(read() != (uint8_t)(cksum >> 24)) return false;
|
||||
|
||||
fp.close();
|
||||
return true;
|
||||
}
|
||||
|
||||
protected:
|
||||
file fp;
|
||||
uint32_t checksum;
|
||||
|
||||
//create() functions
|
||||
void ls(lstring &list, const string &path, const string &basepath) {
|
||||
lstring paths = directory::folders(path);
|
||||
for(auto &pathname : paths) {
|
||||
list.append(string{path, pathname}.ltrim<1>(basepath));
|
||||
ls(list, {path, pathname}, basepath);
|
||||
}
|
||||
|
||||
lstring files = directory::files(path);
|
||||
for(auto &filename : files) {
|
||||
list.append(string{path, filename}.ltrim<1>(basepath));
|
||||
}
|
||||
}
|
||||
|
||||
void write(uint8_t data) {
|
||||
fp.write(data);
|
||||
checksum = crc32_adjust(checksum, data);
|
||||
}
|
||||
|
||||
void writeNumber(uint64_t data) {
|
||||
while(true) {
|
||||
uint64_t x = data & 0x7f;
|
||||
data >>= 7;
|
||||
if(data == 0) {
|
||||
write(0x80 | x);
|
||||
break;
|
||||
}
|
||||
write(x);
|
||||
data--;
|
||||
}
|
||||
}
|
||||
|
||||
void writeString(const string &text) {
|
||||
unsigned length = text.length();
|
||||
for(unsigned n = 0; n < length; n++) write(text[n]);
|
||||
}
|
||||
|
||||
void writeChecksum(uint32_t cksum) {
|
||||
write(cksum >> 0);
|
||||
write(cksum >> 8);
|
||||
write(cksum >> 16);
|
||||
write(cksum >> 24);
|
||||
}
|
||||
|
||||
//apply() functions
|
||||
uint8_t read() {
|
||||
uint8_t data = fp.read();
|
||||
checksum = crc32_adjust(checksum, data);
|
||||
return data;
|
||||
}
|
||||
|
||||
uint64_t readNumber() {
|
||||
uint64_t data = 0, shift = 1;
|
||||
while(true) {
|
||||
uint8_t x = read();
|
||||
data += (x & 0x7f) * shift;
|
||||
if(x & 0x80) break;
|
||||
shift <<= 7;
|
||||
data += shift;
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
string readString(unsigned length) {
|
||||
string text;
|
||||
text.reserve(length + 1);
|
||||
for(unsigned n = 0; n < length; n++) text[n] = read();
|
||||
text[length] = 0;
|
||||
return text;
|
||||
}
|
||||
|
||||
uint32_t readChecksum() {
|
||||
uint32_t checksum = 0;
|
||||
checksum |= read() << 0;
|
||||
checksum |= read() << 8;
|
||||
checksum |= read() << 16;
|
||||
checksum |= read() << 24;
|
||||
return checksum;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
@@ -1,5 +1,5 @@
|
||||
#ifndef NALL_BPS_PATCH_HPP
|
||||
#define NALL_BPS_PATCH_HPP
|
||||
#ifndef NALL_BEAT_PATCH_HPP
|
||||
#define NALL_BEAT_PATCH_HPP
|
||||
|
||||
#include <nall/crc32.hpp>
|
||||
#include <nall/file.hpp>
|
25
ananke/nall/crc16.hpp
Normal file
25
ananke/nall/crc16.hpp
Normal file
@@ -0,0 +1,25 @@
|
||||
#ifndef NALL_CRC16_HPP
|
||||
#define NALL_CRC16_HPP
|
||||
|
||||
#include <nall/stdint.hpp>
|
||||
|
||||
namespace nall {
|
||||
inline uint16_t crc16_adjust(uint16_t crc16, uint8_t data) {
|
||||
for(unsigned n = 0; n < 8; n++) {
|
||||
if((crc16 & 1) ^ (data & 1)) crc16 = (crc16 >> 1) ^ 0x8408;
|
||||
else crc16 >>= 1;
|
||||
data >>= 1;
|
||||
}
|
||||
return crc16;
|
||||
}
|
||||
|
||||
inline uint16_t crc16_calculate(const uint8_t *data, unsigned length) {
|
||||
uint16_t crc16 = ~0;
|
||||
for(unsigned n = 0; n < length; n++) {
|
||||
crc16 = crc16_adjust(crc16, data[n]);
|
||||
}
|
||||
return ~crc16;
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
@@ -1,6 +1,7 @@
|
||||
#ifndef NALL_DIRECTORY_HPP
|
||||
#define NALL_DIRECTORY_HPP
|
||||
|
||||
#include <nall/file.hpp>
|
||||
#include <nall/intrinsics.hpp>
|
||||
#include <nall/sort.hpp>
|
||||
#include <nall/string.hpp>
|
||||
@@ -18,28 +19,75 @@ namespace nall {
|
||||
|
||||
struct directory {
|
||||
static bool create(const string &pathname, unsigned permissions = 0755); //recursive
|
||||
static bool remove(const string &pathname);
|
||||
static bool remove(const string &pathname); //recursive
|
||||
static bool exists(const string &pathname);
|
||||
static lstring folders(const string &pathname, const string &pattern = "*");
|
||||
static lstring files(const string &pathname, const string &pattern = "*");
|
||||
static lstring contents(const string &pathname, const string &pattern = "*");
|
||||
|
||||
static lstring folders(const string &pathname, const string &pattern = "*") {
|
||||
lstring folders = directory::ufolders(pathname, pattern);
|
||||
folders.sort();
|
||||
return folders;
|
||||
}
|
||||
|
||||
static lstring files(const string &pathname, const string &pattern = "*") {
|
||||
lstring files = directory::ufiles(pathname, pattern);
|
||||
files.sort();
|
||||
return files;
|
||||
}
|
||||
|
||||
static lstring contents(const string &pathname, const string &pattern = "*") {
|
||||
lstring folders = directory::ufolders(pathname); //pattern search of contents should only filter files
|
||||
lstring files = directory::ufiles(pathname, pattern);
|
||||
folders.sort();
|
||||
files.sort();
|
||||
for(auto &file : files) folders.append(file);
|
||||
return folders;
|
||||
}
|
||||
|
||||
static lstring ifolders(const string &pathname, const string &pattern = "*") {
|
||||
lstring folders = ufolders(pathname, pattern);
|
||||
folders.isort();
|
||||
return folders;
|
||||
}
|
||||
|
||||
static lstring ifiles(const string &pathname, const string &pattern = "*") {
|
||||
lstring files = ufiles(pathname, pattern);
|
||||
files.isort();
|
||||
return files;
|
||||
}
|
||||
|
||||
static lstring icontents(const string &pathname, const string &pattern = "*") {
|
||||
lstring folders = directory::ufolders(pathname); //pattern search of contents should only filter files
|
||||
lstring files = directory::ufiles(pathname, pattern);
|
||||
folders.isort();
|
||||
files.isort();
|
||||
for(auto &file : files) folders.append(file);
|
||||
return folders;
|
||||
}
|
||||
|
||||
private:
|
||||
//internal functions; these return unsorted lists
|
||||
static lstring ufolders(const string &pathname, const string &pattern = "*");
|
||||
static lstring ufiles(const string &pathname, const string &pattern = "*");
|
||||
};
|
||||
|
||||
#if defined(PLATFORM_WINDOWS)
|
||||
inline bool directory::create(const string &pathname, unsigned permissions) {
|
||||
string fullpath = pathname, path;
|
||||
fullpath.transform("/", "\\");
|
||||
fullpath.rtrim<1>("\\");
|
||||
lstring pathpart = fullpath.split("\\");
|
||||
bool result = false;
|
||||
for(auto &part : pathpart) {
|
||||
path.append(part, "\\");
|
||||
result = _wmkdir(utf16_t(path)) == 0;
|
||||
string path;
|
||||
lstring list = string{pathname}.transform("\\", "/").rtrim<1>("/").split("/");
|
||||
bool result = true;
|
||||
for(auto &part : list) {
|
||||
path.append(part, "/");
|
||||
result &= (_wmkdir(utf16_t(path)) == 0);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
inline bool directory::remove(const string &pathname) {
|
||||
lstring list = directory::contents(pathname);
|
||||
for(auto &name : list) {
|
||||
if(name.endswith("/")) directory::remove({pathname, name});
|
||||
else file::remove({pathname, name});
|
||||
}
|
||||
return _wrmdir(utf16_t(pathname)) == 0;
|
||||
}
|
||||
|
||||
@@ -51,7 +99,7 @@ struct directory {
|
||||
return (result & FILE_ATTRIBUTE_DIRECTORY);
|
||||
}
|
||||
|
||||
inline lstring directory::folders(const string &pathname, const string &pattern) {
|
||||
inline lstring directory::ufolders(const string &pathname, const string &pattern) {
|
||||
lstring list;
|
||||
string path = pathname;
|
||||
path.transform("/", "\\");
|
||||
@@ -77,12 +125,11 @@ struct directory {
|
||||
}
|
||||
FindClose(handle);
|
||||
}
|
||||
if(list.size() > 0) list.sort();
|
||||
for(auto &name : list) name.append("/"); //must append after sorting
|
||||
return list;
|
||||
}
|
||||
|
||||
inline lstring directory::files(const string &pathname, const string &pattern) {
|
||||
inline lstring directory::ufiles(const string &pathname, const string &pattern) {
|
||||
lstring list;
|
||||
string path = pathname;
|
||||
path.transform("/", "\\");
|
||||
@@ -104,29 +151,26 @@ struct directory {
|
||||
}
|
||||
FindClose(handle);
|
||||
}
|
||||
if(list.size() > 0) list.sort();
|
||||
return list;
|
||||
}
|
||||
|
||||
inline lstring directory::contents(const string &pathname, const string &pattern) {
|
||||
lstring folders = directory::folders(pathname); //pattern search of contents() should only filter files
|
||||
lstring files = directory::files(pathname, pattern);
|
||||
for(auto &file : files) folders.append(file);
|
||||
return folders;
|
||||
}
|
||||
#else
|
||||
inline bool directory::create(const string &pathname, unsigned permissions) {
|
||||
string fullpath = pathname, path = "/";
|
||||
fullpath.trim<1>("/");
|
||||
lstring pathpart = fullpath.split("/");
|
||||
for(auto &part : pathpart) {
|
||||
if(!directory::exists(path)) mkdir(path, permissions);
|
||||
string path;
|
||||
lstring list = string{pathname}.rtrim<1>("/").split("/");
|
||||
bool result = true;
|
||||
for(auto &part : list) {
|
||||
path.append(part, "/");
|
||||
result &= (mkdir(path, permissions) == 0);
|
||||
}
|
||||
return mkdir(path, permissions) == 0;
|
||||
return result;
|
||||
}
|
||||
|
||||
inline bool directory::remove(const string &pathname) {
|
||||
lstring list = directory::contents(pathname);
|
||||
for(auto &name : list) {
|
||||
if(name.endswith("/")) directory::remove({pathname, name});
|
||||
else file::remove({pathname, name});
|
||||
}
|
||||
return rmdir(pathname) == 0;
|
||||
}
|
||||
|
||||
@@ -137,7 +181,7 @@ struct directory {
|
||||
return true;
|
||||
}
|
||||
|
||||
inline lstring directory::folders(const string &pathname, const string &pattern) {
|
||||
inline lstring directory::ufolders(const string &pathname, const string &pattern) {
|
||||
lstring list;
|
||||
DIR *dp;
|
||||
struct dirent *ep;
|
||||
@@ -152,12 +196,11 @@ struct directory {
|
||||
}
|
||||
closedir(dp);
|
||||
}
|
||||
if(list.size() > 0) list.sort();
|
||||
for(auto &name : list) name.append("/"); //must append after sorting
|
||||
return list;
|
||||
}
|
||||
|
||||
inline lstring directory::files(const string &pathname, const string &pattern) {
|
||||
inline lstring directory::ufiles(const string &pathname, const string &pattern) {
|
||||
lstring list;
|
||||
DIR *dp;
|
||||
struct dirent *ep;
|
||||
@@ -172,16 +215,8 @@ struct directory {
|
||||
}
|
||||
closedir(dp);
|
||||
}
|
||||
if(list.size() > 0) list.sort();
|
||||
return list;
|
||||
}
|
||||
|
||||
inline lstring directory::contents(const string &pathname, const string &pattern) {
|
||||
lstring folders = directory::folders(pathname); //pattern search of contents() should only filter files
|
||||
lstring files = directory::files(pathname, pattern);
|
||||
for(auto &file : files) folders.append(file);
|
||||
return folders;
|
||||
}
|
||||
#endif
|
||||
|
||||
}
|
@@ -74,6 +74,22 @@ namespace nall {
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool write(const string &filename, const string &text) {
|
||||
file fp;
|
||||
if(fp.open(filename, mode::write) == false) return false;
|
||||
fp.print(text);
|
||||
fp.close();
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool write(const string &filename, const vector<uint8_t> &buffer) {
|
||||
file fp;
|
||||
if(fp.open(filename, mode::write) == false) return false;
|
||||
fp.write(buffer.data(), buffer.size());
|
||||
fp.close();
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool write(const string &filename, const uint8_t *data, unsigned size) {
|
||||
file fp;
|
||||
if(fp.open(filename, mode::write) == false) return false;
|
||||
@@ -82,6 +98,11 @@ namespace nall {
|
||||
return true;
|
||||
}
|
||||
|
||||
static string sha256(const string &filename) {
|
||||
auto buffer = read(filename);
|
||||
return nall::sha256(buffer.data(), buffer.size());
|
||||
}
|
||||
|
||||
uint8_t read() {
|
||||
if(!fp) return 0xff; //file not open
|
||||
if(file_mode == mode::write) return 0xff; //reads not permitted
|
@@ -24,6 +24,14 @@ struct image {
|
||||
uint64_t mask;
|
||||
unsigned depth;
|
||||
unsigned shift;
|
||||
|
||||
inline bool operator==(const Channel &source) {
|
||||
return mask == source.mask && depth == source.depth && shift == source.shift;
|
||||
}
|
||||
|
||||
inline bool operator!=(const Channel &source) {
|
||||
return !operator==(source);
|
||||
}
|
||||
} alpha, red, green, blue;
|
||||
|
||||
typedef double (*interpolation)(double, double, double, double, double);
|
||||
@@ -31,6 +39,9 @@ struct image {
|
||||
static inline unsigned bitShift(uint64_t color);
|
||||
static inline uint64_t normalize(uint64_t color, unsigned sourceDepth, unsigned targetDepth);
|
||||
|
||||
inline bool operator==(const image &source);
|
||||
inline bool operator!=(const image &source);
|
||||
|
||||
inline image& operator=(const image &source);
|
||||
inline image& operator=(image &&source);
|
||||
inline image(const image &source);
|
||||
@@ -89,6 +100,26 @@ uint64_t image::normalize(uint64_t color, unsigned sourceDepth, unsigned targetD
|
||||
|
||||
//public
|
||||
|
||||
bool image::operator==(const image &source) {
|
||||
if(width != source.width) return false;
|
||||
if(height != source.height) return false;
|
||||
if(pitch != source.pitch) return false;
|
||||
|
||||
if(endian != source.endian) return false;
|
||||
if(stride != source.stride) return false;
|
||||
|
||||
if(alpha != source.alpha) return false;
|
||||
if(red != source.red) return false;
|
||||
if(green != source.green) return false;
|
||||
if(blue != source.blue) return false;
|
||||
|
||||
return memcmp(data, source.data, width * height * stride) == 0;
|
||||
}
|
||||
|
||||
bool image::operator!=(const image &source) {
|
||||
return !operator==(source);
|
||||
}
|
||||
|
||||
image& image::operator=(const image &source) {
|
||||
free();
|
||||
|
||||
@@ -110,6 +141,8 @@ image& image::operator=(const image &source) {
|
||||
}
|
||||
|
||||
image& image::operator=(image &&source) {
|
||||
free();
|
||||
|
||||
width = source.width;
|
||||
height = source.height;
|
||||
pitch = source.pitch;
|
@@ -13,6 +13,7 @@
|
||||
#include <nall/bit.hpp>
|
||||
#include <nall/bmp.hpp>
|
||||
#include <nall/config.hpp>
|
||||
#include <nall/crc16.hpp>
|
||||
#include <nall/crc32.hpp>
|
||||
#include <nall/directory.hpp>
|
||||
#include <nall/dl.hpp>
|
||||
@@ -39,6 +40,7 @@
|
||||
#include <nall/stream.hpp>
|
||||
#include <nall/string.hpp>
|
||||
#include <nall/traits.hpp>
|
||||
#include <nall/unzip.hpp>
|
||||
#include <nall/utility.hpp>
|
||||
#include <nall/varint.hpp>
|
||||
#include <nall/vector.hpp>
|
@@ -29,7 +29,7 @@ namespace nall {
|
||||
for(signed i = 1, j; i < size; i++) {
|
||||
T copy = std::move(list[i]);
|
||||
for(j = i - 1; j >= 0; j--) {
|
||||
if(lessthan(list[j], copy)) break;
|
||||
if(!lessthan(copy, list[j])) break;
|
||||
list[j + 1] = std::move(list[j]);
|
||||
}
|
||||
list[j + 1] = std::move(copy);
|
||||
@@ -55,7 +55,7 @@ namespace nall {
|
||||
T *buffer = new T[size];
|
||||
unsigned offset = 0, left = 0, right = middle;
|
||||
while(left < middle && right < size) {
|
||||
if(lessthan(list[left], list[right])) {
|
||||
if(!lessthan(list[right], list[left])) {
|
||||
buffer[offset++] = std::move(list[left++]);
|
||||
} else {
|
||||
buffer[offset++] = std::move(list[right++]);
|
@@ -1,7 +1,7 @@
|
||||
#ifndef NALL_STREAM_ZIP_HPP
|
||||
#define NALL_STREAM_ZIP_HPP
|
||||
|
||||
#include <nall/zip.hpp>
|
||||
#include <nall/unzip.hpp>
|
||||
|
||||
namespace nall {
|
||||
|
||||
@@ -14,7 +14,7 @@ struct zipstream : memorystream {
|
||||
uint8_t *data = new uint8_t[size];
|
||||
stream.read(data, size);
|
||||
|
||||
zip archive;
|
||||
unzip archive;
|
||||
if(archive.open(data, size) == false) return;
|
||||
delete[] data;
|
||||
|
@@ -22,13 +22,13 @@
|
||||
|
||||
#define NALL_STRING_INTERNAL_HPP
|
||||
#include <nall/string/base.hpp>
|
||||
#include <nall/string/bml.hpp>
|
||||
#include <nall/string/bsv.hpp>
|
||||
#include <nall/string/cast.hpp>
|
||||
#include <nall/string/compare.hpp>
|
||||
#include <nall/string/convert.hpp>
|
||||
#include <nall/string/core.hpp>
|
||||
#include <nall/string/cstring.hpp>
|
||||
#include <nall/string/datetime.hpp>
|
||||
#include <nall/string/filename.hpp>
|
||||
#include <nall/string/math-fixed-point.hpp>
|
||||
#include <nall/string/math-floating-point.hpp>
|
||||
@@ -44,7 +44,10 @@
|
||||
#include <nall/string/variadic.hpp>
|
||||
#include <nall/string/wildcard.hpp>
|
||||
#include <nall/string/wrapper.hpp>
|
||||
#include <nall/string/xml.hpp>
|
||||
#include <nall/string/markup/node.hpp>
|
||||
#include <nall/string/markup/bml.hpp>
|
||||
#include <nall/string/markup/xml.hpp>
|
||||
#include <nall/string/markup/document.hpp>
|
||||
#undef NALL_STRING_INTERNAL_HPP
|
||||
|
||||
#endif
|
@@ -23,6 +23,9 @@ namespace nall {
|
||||
|
||||
struct string {
|
||||
inline static string read(const string &filename);
|
||||
inline static string date();
|
||||
inline static string time();
|
||||
inline static string datetime();
|
||||
|
||||
inline void reserve(unsigned);
|
||||
inline bool empty() const;
|
||||
@@ -65,6 +68,7 @@ namespace nall {
|
||||
template<unsigned limit = 0> inline string& ltrim(const char *key = " ");
|
||||
template<unsigned limit = 0> inline string& rtrim(const char *key = " ");
|
||||
template<unsigned limit = 0> inline string& trim(const char *key = " ", const char *rkey = 0);
|
||||
inline string& strip();
|
||||
|
||||
inline optional<unsigned> position(const char *key) const;
|
||||
inline optional<unsigned> iposition(const char *key) const;
|
||||
@@ -115,6 +119,7 @@ namespace nall {
|
||||
inline optional<unsigned> find(const char*) const;
|
||||
inline string concatenate(const char*) const;
|
||||
inline void append() {}
|
||||
inline void isort();
|
||||
template<typename... Args> inline void append(const string&, Args&&...);
|
||||
|
||||
template<unsigned Limit = 0> inline lstring& split(const char*, const char*);
|
||||
@@ -163,6 +168,7 @@ namespace nall {
|
||||
inline string realpath(const string &name);
|
||||
inline string userpath();
|
||||
inline string configpath();
|
||||
inline string temppath();
|
||||
|
||||
//strm.hpp
|
||||
inline unsigned strmcpy(char *target, const char *source, unsigned length);
|
||||
@@ -182,6 +188,7 @@ namespace nall {
|
||||
template<unsigned limit = 0> inline char* ltrim(char *str, const char *key = " ");
|
||||
template<unsigned limit = 0> inline char* rtrim(char *str, const char *key = " ");
|
||||
template<unsigned limit = 0> inline char* trim(char *str, const char *key = " ", const char *rkey = 0);
|
||||
inline char* strip(char *s);
|
||||
|
||||
//utility.hpp
|
||||
template<bool Insensitive> alwaysinline bool chrequal(char x, char y);
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user