mirror of https://github.com/bsnes-emu/bsnes.git
Update to ananke v01 release.
byuu says: This updated anake fixes all of the reported game issues thus far.
This commit is contained in:
parent
6ac67c260b
commit
8d88337e28
|
@ -1,5 +1,2 @@
|
||||||
purify/*.o
|
|
||||||
purify/purify
|
purify/purify
|
||||||
purify/analyze-gba
|
|
||||||
ananke/ananke.o
|
|
||||||
ananke/libananke.so
|
ananke/libananke.so
|
||||||
|
|
|
@ -3,25 +3,22 @@ include phoenix/Makefile
|
||||||
|
|
||||||
path := /usr/local/lib
|
path := /usr/local/lib
|
||||||
flags := -I. -O3 -fomit-frame-pointer
|
flags := -I. -O3 -fomit-frame-pointer
|
||||||
ifeq ($(arch),win32)
|
|
||||||
flags := -m32 $(flags)
|
|
||||||
endif
|
|
||||||
|
|
||||||
all:
|
all:
|
||||||
$(cpp) $(flags) -fPIC -o ananke.o -c ananke.cpp
|
$(cpp) $(flags) -fPIC -o obj/ananke.o -c ananke.cpp
|
||||||
ifeq ($(platform),x)
|
ifeq ($(platform),x)
|
||||||
$(cpp) $(flags) -shared -Wl,-soname,libananke.so.1 -o libananke.so ananke.o
|
$(cpp) $(flags) -shared -Wl,-soname,libananke.so.1 -o libananke.so obj/ananke.o
|
||||||
else ifeq ($(platform),win)
|
else ifeq ($(platform),win)
|
||||||
$(cpp) $(flags) -fPIC -o phoenix.o -c phoenix/phoenix.cpp $(phoenixflags)
|
$(cpp) $(flags) -fPIC -o obj/phoenix.o -c phoenix/phoenix.cpp $(phoenixflags)
|
||||||
$(cpp) $(flags) -shared -o phoenix.dll phoenix.o $(phoenixlink)
|
$(cpp) $(flags) -shared -o phoenix.dll obj/phoenix.o $(phoenixlink)
|
||||||
$(cpp) $(flags) -shared -o ananke.dll ananke.o -L. -lphoenix
|
$(cpp) $(flags) -shared -o ananke.dll obj/ananke.o -L. -lphoenix
|
||||||
endif
|
endif
|
||||||
|
|
||||||
resource: force
|
resource: force
|
||||||
sourcery resource/resource.bml resource/resource.cpp resource/resource.hpp
|
sourcery resource/resource.bml resource/resource.cpp resource/resource.hpp
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
-@$(call delete,*.o)
|
-@$(call delete,obj/*.o)
|
||||||
-@$(call delete,*.so)
|
-@$(call delete,*.so)
|
||||||
|
|
||||||
install: uninstall
|
install: uninstall
|
||||||
|
|
|
@ -36,6 +36,7 @@ struct Ananke {
|
||||||
void copyFamicomSaves(const string &pathname);
|
void copyFamicomSaves(const string &pathname);
|
||||||
string createFamicomHeuristic(vector<uint8_t> &buffer);
|
string createFamicomHeuristic(vector<uint8_t> &buffer);
|
||||||
string openFamicom(vector<uint8_t> &buffer);
|
string openFamicom(vector<uint8_t> &buffer);
|
||||||
|
string syncFamicom(const string &pathname);
|
||||||
|
|
||||||
//super-famicom.cpp
|
//super-famicom.cpp
|
||||||
void copySuperFamicomSaves(const string &pathname);
|
void copySuperFamicomSaves(const string &pathname);
|
||||||
|
@ -43,30 +44,36 @@ struct Ananke {
|
||||||
string createSuperFamicomHeuristic(vector<uint8_t> &buffer);
|
string createSuperFamicomHeuristic(vector<uint8_t> &buffer);
|
||||||
void createSuperFamicomHeuristicFirmware(vector<uint8_t> &buffer, const string &pathname, bool firmware_appended);
|
void createSuperFamicomHeuristicFirmware(vector<uint8_t> &buffer, const string &pathname, bool firmware_appended);
|
||||||
string openSuperFamicom(vector<uint8_t> &buffer);
|
string openSuperFamicom(vector<uint8_t> &buffer);
|
||||||
|
string syncSuperFamicom(const string &pathname);
|
||||||
|
|
||||||
//sufami-turbo.cpp
|
//sufami-turbo.cpp
|
||||||
void copySufamiTurboSaves(const string &pathname);
|
void copySufamiTurboSaves(const string &pathname);
|
||||||
string createSufamiTurboDatabase(vector<uint8_t> &buffer, Markup::Node &document, const string &manifest);
|
string createSufamiTurboDatabase(vector<uint8_t> &buffer, Markup::Node &document, const string &manifest);
|
||||||
string createSufamiTurboHeuristic(vector<uint8_t> &buffer);
|
string createSufamiTurboHeuristic(vector<uint8_t> &buffer);
|
||||||
string openSufamiTurbo(vector<uint8_t> &buffer);
|
string openSufamiTurbo(vector<uint8_t> &buffer);
|
||||||
|
string syncSufamiTurbo(const string &pathname);
|
||||||
|
|
||||||
//bsx-satellaview.cpp
|
//bsx-satellaview.cpp
|
||||||
string createBsxSatellaviewDatabase(vector<uint8_t> &buffer, Markup::Node &document, const string &manifest);
|
string createBsxSatellaviewDatabase(vector<uint8_t> &buffer, Markup::Node &document, const string &manifest);
|
||||||
string createBsxSatellaviewHeuristic(vector<uint8_t> &buffer);
|
string createBsxSatellaviewHeuristic(vector<uint8_t> &buffer);
|
||||||
string openBsxSatellaview(vector<uint8_t> &buffer);
|
string openBsxSatellaview(vector<uint8_t> &buffer);
|
||||||
|
string syncBsxSatellaview(const string &pathname);
|
||||||
|
|
||||||
//game-boy.cpp
|
//game-boy.cpp
|
||||||
void copyGameBoySaves(const string &pathname);
|
void copyGameBoySaves(const string &pathname);
|
||||||
string createGameBoyHeuristic(vector<uint8_t> &buffer);
|
string createGameBoyHeuristic(vector<uint8_t> &buffer);
|
||||||
string openGameBoy(vector<uint8_t> &buffer);
|
string openGameBoy(vector<uint8_t> &buffer);
|
||||||
|
string syncGameBoy(const string &pathname);
|
||||||
|
|
||||||
//game-boy-advance.cpp
|
//game-boy-advance.cpp
|
||||||
void copyGameBoyAdvanceSaves(const string &pathname);
|
void copyGameBoyAdvanceSaves(const string &pathname);
|
||||||
string createGameBoyAdvanceHeuristic(vector<uint8_t> &buffer);
|
string createGameBoyAdvanceHeuristic(vector<uint8_t> &buffer);
|
||||||
string openGameBoyAdvance(vector<uint8_t> &buffer);
|
string openGameBoyAdvance(vector<uint8_t> &buffer);
|
||||||
|
string syncGameBoyAdvance(const string &pathname);
|
||||||
|
|
||||||
static bool supported(const string &filename);
|
static bool supported(const string &filename);
|
||||||
string open(string filename = "");
|
string open(string filename = "");
|
||||||
|
string sync(string pathname);
|
||||||
};
|
};
|
||||||
|
|
||||||
#include "resource/resource.cpp"
|
#include "resource/resource.cpp"
|
||||||
|
@ -132,6 +139,17 @@ string Ananke::open(string filename) {
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
string Ananke::sync(string pathname) {
|
||||||
|
if(pathname.endswith(".fc/")) return syncFamicom(pathname);
|
||||||
|
if(pathname.endswith(".sfc/")) return syncSuperFamicom(pathname);
|
||||||
|
if(pathname.endswith(".st/")) return syncSufamiTurbo(pathname);
|
||||||
|
if(pathname.endswith(".bs/")) return syncBsxSatellaview(pathname);
|
||||||
|
if(pathname.endswith(".gb/")) return syncGameBoy(pathname);
|
||||||
|
if(pathname.endswith(".gbc/")) return syncGameBoy(pathname);
|
||||||
|
if(pathname.endswith(".gba/")) return syncGameBoyAdvance(pathname);
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
extern "C" string ananke_browse(const string &filename) {
|
extern "C" string ananke_browse(const string &filename) {
|
||||||
Ananke ananke;
|
Ananke ananke;
|
||||||
return ananke.open();
|
return ananke.open();
|
||||||
|
@ -141,3 +159,8 @@ extern "C" string ananke_open(const string &filename) {
|
||||||
Ananke ananke;
|
Ananke ananke;
|
||||||
return ananke.open(filename);
|
return ananke.open(filename);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extern "C" string ananke_sync(const string &pathname) {
|
||||||
|
Ananke ananke;
|
||||||
|
return ananke.sync(pathname);
|
||||||
|
}
|
||||||
|
|
|
@ -17,7 +17,7 @@ string Ananke::createBsxSatellaviewDatabase(vector<uint8_t> &buffer, Markup::Nod
|
||||||
file::write({pathname, "manifest.bml"}, markup);
|
file::write({pathname, "manifest.bml"}, markup);
|
||||||
file::write({pathname, "program.rom"}, buffer);
|
file::write({pathname, "program.rom"}, buffer);
|
||||||
|
|
||||||
return "";
|
return pathname;
|
||||||
}
|
}
|
||||||
|
|
||||||
string Ananke::createBsxSatellaviewHeuristic(vector<uint8_t> &buffer) {
|
string Ananke::createBsxSatellaviewHeuristic(vector<uint8_t> &buffer) {
|
||||||
|
@ -37,7 +37,7 @@ string Ananke::createBsxSatellaviewHeuristic(vector<uint8_t> &buffer) {
|
||||||
});
|
});
|
||||||
file::write({pathname, "program.rom"}, buffer);
|
file::write({pathname, "program.rom"}, buffer);
|
||||||
|
|
||||||
return "";
|
return pathname;
|
||||||
}
|
}
|
||||||
|
|
||||||
string Ananke::openBsxSatellaview(vector<uint8_t> &buffer) {
|
string Ananke::openBsxSatellaview(vector<uint8_t> &buffer) {
|
||||||
|
@ -58,3 +58,11 @@ string Ananke::openBsxSatellaview(vector<uint8_t> &buffer) {
|
||||||
|
|
||||||
return createBsxSatellaviewHeuristic(buffer);
|
return createBsxSatellaviewHeuristic(buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
string Ananke::syncBsxSatellaview(const string &pathname) {
|
||||||
|
auto buffer = file::read({pathname, "program.rom"});
|
||||||
|
if(buffer.size() == 0) return "";
|
||||||
|
|
||||||
|
directory::remove(pathname);
|
||||||
|
return openBsxSatellaview(buffer);
|
||||||
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
string BsxSatellaview = R"(
|
string BsxSatellaview = R"(
|
||||||
|
|
||||||
database revision=2013-01-14
|
database revision=2013-01-16
|
||||||
|
|
||||||
release
|
release
|
||||||
cartridge
|
cartridge
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
string SufamiTurbo = R"(
|
string SufamiTurbo = R"(
|
||||||
|
|
||||||
database revision=2013-01-14
|
database revision=2013-01-16
|
||||||
|
|
||||||
release
|
release
|
||||||
cartridge linkable
|
cartridge linkable
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
string SuperFamicom = R"(
|
string SuperFamicom = R"(
|
||||||
|
|
||||||
database revision=2013-01-14
|
database revision=2013-01-16
|
||||||
|
|
||||||
release
|
release
|
||||||
cartridge region=NTSC
|
cartridge region=NTSC
|
||||||
|
@ -208,10 +208,10 @@ release
|
||||||
board type=LJ3M revision=01
|
board type=LJ3M revision=01
|
||||||
rom name=program.rom size=0x600000
|
rom name=program.rom size=0x600000
|
||||||
ram name=save.ram size=0x2000
|
ram name=save.ram size=0x2000
|
||||||
map id=rom address=00-3f:8000-ffff offset=0x400000
|
map id=rom address=00-3f:8000-ffff base=0x400000
|
||||||
map id=rom address=40-7d:0000-ffff offset=0x400000
|
map id=rom address=40-7d:0000-ffff base=0x400000
|
||||||
map id=rom address=80-bf:8000-ffff offset=0x000000 mask=0xc00000
|
map id=rom address=80-bf:8000-ffff mask=0xc00000
|
||||||
map id=rom address=c0-ff:0000-ffff offset=0x000000 mask=0xc00000
|
map id=rom address=c0-ff:0000-ffff mask=0xc00000
|
||||||
map id=ram address=80-bf:6000-7fff mask=0xe000
|
map id=ram address=80-bf:6000-7fff mask=0xe000
|
||||||
information
|
information
|
||||||
title: テイルズ オブ ファンタジア
|
title: テイルズ オブ ファンタジア
|
||||||
|
|
|
@ -30,3 +30,10 @@ string Ananke::createFamicomHeuristic(vector<uint8_t> &buffer) {
|
||||||
string Ananke::openFamicom(vector<uint8_t> &buffer) {
|
string Ananke::openFamicom(vector<uint8_t> &buffer) {
|
||||||
return createFamicomHeuristic(buffer);
|
return createFamicomHeuristic(buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//this currently cannot work:
|
||||||
|
//game folders discard iNES header required for heuristic detection
|
||||||
|
//a games database of all commercial Famicom software will be required
|
||||||
|
string Ananke::syncFamicom(const string &pathname) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
|
@ -35,3 +35,22 @@ string Ananke::createGameBoyAdvanceHeuristic(vector<uint8_t> &buffer) {
|
||||||
string Ananke::openGameBoyAdvance(vector<uint8_t> &buffer) {
|
string Ananke::openGameBoyAdvance(vector<uint8_t> &buffer) {
|
||||||
return createGameBoyAdvanceHeuristic(buffer);
|
return createGameBoyAdvanceHeuristic(buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
string Ananke::syncGameBoyAdvance(const string &pathname) {
|
||||||
|
auto buffer = file::read({pathname, "program.rom"});
|
||||||
|
if(buffer.size() == 0) return "";
|
||||||
|
|
||||||
|
auto save = file::read({pathname, "save.ram"});
|
||||||
|
if(save.size() == 0) save = file::read({pathname, "save.rwm"});
|
||||||
|
|
||||||
|
auto rtc = file::read({pathname, "rtc.ram"});
|
||||||
|
if(rtc.size() == 0) rtc = file::read({pathname, "rtc.rwm"});
|
||||||
|
|
||||||
|
directory::remove(pathname);
|
||||||
|
string outputPath = openGameBoyAdvance(buffer);
|
||||||
|
|
||||||
|
if(save.size()) file::write({outputPath, "save.ram"}, save);
|
||||||
|
if(rtc.size()) file::write({outputPath, "rtc.ram"}, rtc);
|
||||||
|
|
||||||
|
return outputPath;
|
||||||
|
}
|
||||||
|
|
|
@ -37,3 +37,22 @@ string Ananke::createGameBoyHeuristic(vector<uint8_t> &buffer) {
|
||||||
string Ananke::openGameBoy(vector<uint8_t> &buffer) {
|
string Ananke::openGameBoy(vector<uint8_t> &buffer) {
|
||||||
return createGameBoyHeuristic(buffer);
|
return createGameBoyHeuristic(buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
string Ananke::syncGameBoy(const string &pathname) {
|
||||||
|
auto buffer = file::read({pathname, "program.rom"});
|
||||||
|
if(buffer.size() == 0) return "";
|
||||||
|
|
||||||
|
auto save = file::read({pathname, "save.ram"});
|
||||||
|
if(save.size() == 0) save = file::read({pathname, "save.rwm"});
|
||||||
|
|
||||||
|
auto rtc = file::read({pathname, "rtc.ram"});
|
||||||
|
if(rtc.size() == 0) rtc = file::read({pathname, "rtc.rwm"});
|
||||||
|
|
||||||
|
directory::remove(pathname);
|
||||||
|
string outputPath = openGameBoy(buffer);
|
||||||
|
|
||||||
|
if(save.size()) file::write({outputPath, "save.ram"}, save);
|
||||||
|
if(rtc.size()) file::write({outputPath, "rtc.ram"}, rtc);
|
||||||
|
|
||||||
|
return outputPath;
|
||||||
|
}
|
||||||
|
|
|
@ -91,10 +91,10 @@ struct SuperFamicomCartridge {
|
||||||
bool has_bsx_slot;
|
bool has_bsx_slot;
|
||||||
bool has_superfx;
|
bool has_superfx;
|
||||||
bool has_sa1;
|
bool has_sa1;
|
||||||
bool has_srtc;
|
bool has_sharprtc;
|
||||||
|
bool has_epsonrtc;
|
||||||
bool has_sdd1;
|
bool has_sdd1;
|
||||||
bool has_spc7110;
|
bool has_spc7110;
|
||||||
bool has_spc7110rtc;
|
|
||||||
bool has_cx4;
|
bool has_cx4;
|
||||||
bool has_dsp1;
|
bool has_dsp1;
|
||||||
bool has_dsp2;
|
bool has_dsp2;
|
||||||
|
@ -128,7 +128,7 @@ SuperFamicomCartridge::SuperFamicomCartridge(const uint8_t *data, unsigned size)
|
||||||
if(type == TypeSuperGameBoy1Bios || type == TypeSuperGameBoy2Bios) {
|
if(type == TypeSuperGameBoy1Bios || type == TypeSuperGameBoy2Bios) {
|
||||||
markup.append(
|
markup.append(
|
||||||
" rom name=program.rom size=0x", hex(rom_size), "\n"
|
" rom name=program.rom size=0x", hex(rom_size), "\n"
|
||||||
" map id=rom address=00-7f,80-ff:8000-ffff\n"
|
" map id=rom address=00-7f,80-ff:8000-ffff mask=0x8000\n"
|
||||||
" icd2 revision=1\n"
|
" icd2 revision=1\n"
|
||||||
" rom name=sgb.boot.rom size=0x100\n"
|
" rom name=sgb.boot.rom size=0x100\n"
|
||||||
" map id=io address=00-3f,80-bf:6000-7fff\n"
|
" map id=io address=00-3f,80-bf:6000-7fff\n"
|
||||||
|
@ -244,10 +244,10 @@ SuperFamicomCartridge::SuperFamicomCartridge(const uint8_t *data, unsigned size)
|
||||||
" ram name=save.ram size=0x", hex(ram_size), "\n"
|
" ram name=save.ram size=0x", hex(ram_size), "\n"
|
||||||
);
|
);
|
||||||
markup.append(
|
markup.append(
|
||||||
" map id=rom address=00-3f:8000-ffff offset=0x400000\n"
|
" map id=rom address=00-3f:8000-ffff base=0x400000\n"
|
||||||
" map id=rom address=40-7f:0000-ffff offset=0x400000\n"
|
" map id=rom address=40-7f:0000-ffff base=0x400000\n"
|
||||||
" map id=rom address=80-bf:8000-ffff offset=0x000000 mask=0xc00000\n"
|
" map id=rom address=80-bf:8000-ffff mask=0xc00000\n"
|
||||||
" map id=rom address=c0-ff:0000-ffff offset=0x000000 mask=0xc00000\n"
|
" map id=rom address=c0-ff:0000-ffff mask=0xc00000\n"
|
||||||
);
|
);
|
||||||
if(ram_size > 0) markup.append(
|
if(ram_size > 0) markup.append(
|
||||||
" map id=ram address=20-3f,a0-bf:6000-7fff mask=0xe000\n"
|
" map id=ram address=20-3f,a0-bf:6000-7fff mask=0xe000\n"
|
||||||
|
@ -301,10 +301,10 @@ SuperFamicomCartridge::SuperFamicomCartridge(const uint8_t *data, unsigned size)
|
||||||
markup.append(
|
markup.append(
|
||||||
" rom name=program.rom size=0x", hex(rom_size), "\n"
|
" rom name=program.rom size=0x", hex(rom_size), "\n"
|
||||||
" ram name=save.ram size=0x", hex(ram_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=00-1f:8000-ffff base=0x000000 mask=0x8000\n"
|
||||||
" map id=rom address=20-3f:8000-ffff offset=0x100000 mask=0x8000\n"
|
" map id=rom address=20-3f:8000-ffff base=0x100000 mask=0x8000\n"
|
||||||
" map id=rom address=80-9f:8000-ffff offset=0x200000 mask=0x8000\n"
|
" map id=rom address=80-9f:8000-ffff base=0x200000 mask=0x8000\n"
|
||||||
" map id=rom address=a0-bf:8000-ffff offset=0x100000 mask=0x8000\n"
|
" map id=rom address=a0-bf:8000-ffff base=0x100000 mask=0x8000\n"
|
||||||
" map id=ram address=70-7f,f0-ff:0000-7fff\n"
|
" map id=ram address=70-7f,f0-ff:0000-7fff\n"
|
||||||
" bsxslot\n"
|
" bsxslot\n"
|
||||||
" map id=rom address=c0-ef:0000-ffff\n"
|
" map id=rom address=c0-ef:0000-ffff\n"
|
||||||
|
@ -351,15 +351,7 @@ SuperFamicomCartridge::SuperFamicomCartridge(const uint8_t *data, unsigned size)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(has_spc7110rtc) {
|
if(has_sharprtc) {
|
||||||
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(
|
markup.append(
|
||||||
" sharprtc\n"
|
" sharprtc\n"
|
||||||
" ram name=rtc.ram size=0x10\n"
|
" ram name=rtc.ram size=0x10\n"
|
||||||
|
@ -367,6 +359,14 @@ SuperFamicomCartridge::SuperFamicomCartridge(const uint8_t *data, unsigned size)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(has_epsonrtc) {
|
||||||
|
markup.append(
|
||||||
|
" epsonrtc\n"
|
||||||
|
" ram name=rtc.ram size=0x10\n"
|
||||||
|
" map id=io address=00-3f,80-bf:4840-4842\n"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if(has_obc1) {
|
if(has_obc1) {
|
||||||
markup.append(
|
markup.append(
|
||||||
" obc1\n"
|
" obc1\n"
|
||||||
|
@ -431,7 +431,7 @@ SuperFamicomCartridge::SuperFamicomCartridge(const uint8_t *data, unsigned size)
|
||||||
" rom id=program name=dsp4.program.rom size=0x1800\n"
|
" rom id=program name=dsp4.program.rom size=0x1800\n"
|
||||||
" rom id=data name=dsp4.data.rom size=0x800\n"
|
" rom id=data name=dsp4.data.rom size=0x800\n"
|
||||||
" ram id=data size=0x200\n"
|
" ram id=data size=0x200\n"
|
||||||
" map address=30-3f,b0-bf:8000-ffff select=0x4000\n"
|
" map id=io address=30-3f,b0-bf:8000-ffff select=0x4000\n"
|
||||||
);
|
);
|
||||||
if((size & 0x7fff) == 0x2000) {
|
if((size & 0x7fff) == 0x2000) {
|
||||||
firmware_appended = true;
|
firmware_appended = true;
|
||||||
|
@ -495,10 +495,10 @@ void SuperFamicomCartridge::read_header(const uint8_t *data, unsigned size) {
|
||||||
has_bsx_slot = false;
|
has_bsx_slot = false;
|
||||||
has_superfx = false;
|
has_superfx = false;
|
||||||
has_sa1 = false;
|
has_sa1 = false;
|
||||||
has_srtc = false;
|
has_sharprtc = false;
|
||||||
|
has_epsonrtc = false;
|
||||||
has_sdd1 = false;
|
has_sdd1 = false;
|
||||||
has_spc7110 = false;
|
has_spc7110 = false;
|
||||||
has_spc7110rtc = false;
|
|
||||||
has_cx4 = false;
|
has_cx4 = false;
|
||||||
has_dsp1 = false;
|
has_dsp1 = false;
|
||||||
has_dsp2 = false;
|
has_dsp2 = false;
|
||||||
|
@ -645,7 +645,7 @@ void SuperFamicomCartridge::read_header(const uint8_t *data, unsigned size) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if(mapperid == 0x35 && rom_type == 0x55) {
|
if(mapperid == 0x35 && rom_type == 0x55) {
|
||||||
has_srtc = true;
|
has_sharprtc = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(mapperid == 0x32 && (rom_type == 0x43 || rom_type == 0x45)) {
|
if(mapperid == 0x32 && (rom_type == 0x43 || rom_type == 0x45)) {
|
||||||
|
@ -654,7 +654,7 @@ void SuperFamicomCartridge::read_header(const uint8_t *data, unsigned size) {
|
||||||
|
|
||||||
if(mapperid == 0x3a && (rom_type == 0xf5 || rom_type == 0xf9)) {
|
if(mapperid == 0x3a && (rom_type == 0xf5 || rom_type == 0xf9)) {
|
||||||
has_spc7110 = true;
|
has_spc7110 = true;
|
||||||
has_spc7110rtc = (rom_type == 0xf9);
|
has_epsonrtc = (rom_type == 0xf9);
|
||||||
mapper = SPC7110ROM;
|
mapper = SPC7110ROM;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -45,7 +45,12 @@ ifeq ($(compiler),)
|
||||||
endif
|
endif
|
||||||
|
|
||||||
c := $(compiler) -std=gnu99
|
c := $(compiler) -std=gnu99
|
||||||
cpp := $(subst cc,++,$(compiler)) -std=gnu++0x
|
cpp := $(subst cc,++,$(compiler)) -std=gnu++11
|
||||||
|
|
||||||
|
ifeq ($(arch),x86)
|
||||||
|
c := $(c) -m32
|
||||||
|
cpp := $(cpp) -m32
|
||||||
|
endif
|
||||||
|
|
||||||
ifeq ($(prefix),)
|
ifeq ($(prefix),)
|
||||||
prefix := /usr/local
|
prefix := /usr/local
|
||||||
|
|
|
@ -5,12 +5,6 @@
|
||||||
//example: property<owner>::readonly<type> implies that only owner has full
|
//example: property<owner>::readonly<type> implies that only owner has full
|
||||||
//access to type; and all other code has readonly access.
|
//access to type; and all other code has readonly access.
|
||||||
//
|
//
|
||||||
//this code relies on extended friend semantics from C++0x to work, as it
|
|
||||||
//declares a friend class via a template paramter. it also exploits a bug in
|
|
||||||
//G++ 4.x to work even in C++98 mode.
|
|
||||||
//
|
|
||||||
//if compiling elsewhere, simply remove the friend class and private semantics
|
|
||||||
|
|
||||||
//property can be used either of two ways:
|
//property can be used either of two ways:
|
||||||
//struct foo {
|
//struct foo {
|
||||||
// property<foo>::readonly<bool> x;
|
// property<foo>::readonly<bool> x;
|
||||||
|
@ -50,8 +44,6 @@
|
||||||
|
|
||||||
namespace nall {
|
namespace nall {
|
||||||
template<typename C> struct property {
|
template<typename C> struct property {
|
||||||
template<typename T> struct traits { typedef T type; };
|
|
||||||
|
|
||||||
template<typename T> struct readonly {
|
template<typename T> struct readonly {
|
||||||
const T* operator->() const { return &value; }
|
const T* operator->() const { return &value; }
|
||||||
const T& operator()() const { return value; }
|
const T& operator()() const { return value; }
|
||||||
|
@ -61,7 +53,7 @@ namespace nall {
|
||||||
operator T&() { return value; }
|
operator T&() { return value; }
|
||||||
const T& operator=(const T& value_) { return value = value_; }
|
const T& operator=(const T& value_) { return value = value_; }
|
||||||
T value;
|
T value;
|
||||||
friend class traits<C>::type;
|
friend C;
|
||||||
};
|
};
|
||||||
|
|
||||||
template<typename T> struct writeonly {
|
template<typename T> struct writeonly {
|
||||||
|
@ -73,7 +65,7 @@ namespace nall {
|
||||||
T* operator->() { return &value; }
|
T* operator->() { return &value; }
|
||||||
operator T&() { return value; }
|
operator T&() { return value; }
|
||||||
T value;
|
T value;
|
||||||
friend class traits<C>::type;
|
friend C;
|
||||||
};
|
};
|
||||||
|
|
||||||
template<typename T> struct readwrite {
|
template<typename T> struct readwrite {
|
||||||
|
|
|
@ -30,6 +30,7 @@
|
||||||
#include <nall/string/cstring.hpp>
|
#include <nall/string/cstring.hpp>
|
||||||
#include <nall/string/datetime.hpp>
|
#include <nall/string/datetime.hpp>
|
||||||
#include <nall/string/filename.hpp>
|
#include <nall/string/filename.hpp>
|
||||||
|
#include <nall/string/format.hpp>
|
||||||
#include <nall/string/math-fixed-point.hpp>
|
#include <nall/string/math-fixed-point.hpp>
|
||||||
#include <nall/string/math-floating-point.hpp>
|
#include <nall/string/math-floating-point.hpp>
|
||||||
#include <nall/string/platform.hpp>
|
#include <nall/string/platform.hpp>
|
||||||
|
|
|
@ -28,6 +28,8 @@ namespace nall {
|
||||||
inline static string datetime();
|
inline static string datetime();
|
||||||
|
|
||||||
inline void reserve(unsigned);
|
inline void reserve(unsigned);
|
||||||
|
inline void resize(unsigned);
|
||||||
|
inline void clear(char);
|
||||||
inline bool empty() const;
|
inline bool empty() const;
|
||||||
|
|
||||||
template<typename... Args> inline string& assign(Args&&... args);
|
template<typename... Args> inline string& assign(Args&&... args);
|
||||||
|
@ -64,6 +66,7 @@ namespace nall {
|
||||||
inline string& qlower();
|
inline string& qlower();
|
||||||
inline string& qupper();
|
inline string& qupper();
|
||||||
inline string& transform(const char *before, const char *after);
|
inline string& transform(const char *before, const char *after);
|
||||||
|
inline string& reverse();
|
||||||
|
|
||||||
template<unsigned limit = 0> inline string& ltrim(const char *key = " ");
|
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& rtrim(const char *key = " ");
|
||||||
|
@ -159,6 +162,12 @@ namespace nall {
|
||||||
inline char* qstrupper(char *str);
|
inline char* qstrupper(char *str);
|
||||||
inline char* strtr(char *dest, const char *before, const char *after);
|
inline char* strtr(char *dest, const char *before, const char *after);
|
||||||
|
|
||||||
|
//format.hpp
|
||||||
|
template<signed precision = 0, char padchar = ' '> inline string format(const string &value);
|
||||||
|
template<signed precision = 0, char padchar = '0'> inline string hex(uintmax_t value);
|
||||||
|
template<signed precision = 0, char padchar = '0'> inline string octal(uintmax_t value);
|
||||||
|
template<signed precision = 0, char padchar = '0'> inline string binary(uintmax_t value);
|
||||||
|
|
||||||
//math.hpp
|
//math.hpp
|
||||||
inline bool strint(const char *str, int &result);
|
inline bool strint(const char *str, int &result);
|
||||||
inline bool strmath(const char *str, int &result);
|
inline bool strmath(const char *str, int &result);
|
||||||
|
@ -200,12 +209,11 @@ namespace nall {
|
||||||
inline char* integer(char *result, intmax_t value);
|
inline char* integer(char *result, intmax_t value);
|
||||||
inline char* decimal(char *result, uintmax_t value);
|
inline char* decimal(char *result, uintmax_t value);
|
||||||
|
|
||||||
|
//these functions are deprecated, use format() instead:
|
||||||
template<unsigned length = 0, char padding = ' '> inline string integer(intmax_t value);
|
template<unsigned length = 0, char padding = ' '> inline string integer(intmax_t value);
|
||||||
template<unsigned length = 0, char padding = ' '> inline string linteger(intmax_t value);
|
template<unsigned length = 0, char padding = ' '> inline string linteger(intmax_t value);
|
||||||
template<unsigned length = 0, char padding = ' '> inline string decimal(uintmax_t value);
|
template<unsigned length = 0, char padding = ' '> inline string decimal(uintmax_t value);
|
||||||
template<unsigned length = 0, char padding = ' '> inline string ldecimal(uintmax_t value);
|
template<unsigned length = 0, char padding = ' '> inline string ldecimal(uintmax_t value);
|
||||||
template<unsigned length = 0, char padding = '0'> inline string hex(uintmax_t value);
|
|
||||||
template<unsigned length = 0, char padding = '0'> inline string binary(uintmax_t value);
|
|
||||||
inline unsigned fp(char *str, long double value);
|
inline unsigned fp(char *str, long double value);
|
||||||
inline string fp(long double value);
|
inline string fp(long double value);
|
||||||
|
|
||||||
|
|
|
@ -12,11 +12,18 @@ static void istring(string &output, const T &value, Args&&... args) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void string::reserve(unsigned size_) {
|
void string::reserve(unsigned size_) {
|
||||||
if(size_ > size) {
|
if(size_ > size) resize(size_);
|
||||||
|
}
|
||||||
|
|
||||||
|
void string::resize(unsigned size_) {
|
||||||
size = size_;
|
size = size_;
|
||||||
data = (char*)realloc(data, size + 1);
|
data = (char*)realloc(data, size + 1);
|
||||||
data[size] = 0;
|
data[size] = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void string::clear(char c) {
|
||||||
|
for(unsigned n = 0; n < size; n++) data[n] = c;
|
||||||
|
data[size] = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool string::empty() const {
|
bool string::empty() const {
|
||||||
|
|
|
@ -0,0 +1,73 @@
|
||||||
|
#ifdef NALL_STRING_INTERNAL_HPP
|
||||||
|
|
||||||
|
namespace nall {
|
||||||
|
|
||||||
|
template<signed precision, char padchar> string format(const string &value) {
|
||||||
|
if(precision == 0) return value;
|
||||||
|
|
||||||
|
bool padright = precision >= 0;
|
||||||
|
unsigned padding = abs(precision);
|
||||||
|
|
||||||
|
unsigned length = value.length();
|
||||||
|
if(padding <= length) {
|
||||||
|
if(padright) return substr(value, length - padding);
|
||||||
|
else return substr(value, 0, padding);
|
||||||
|
}
|
||||||
|
|
||||||
|
string buffer;
|
||||||
|
buffer.resize(padding);
|
||||||
|
buffer.clear(padchar);
|
||||||
|
|
||||||
|
memcpy(buffer() + (padright ? padding - length : 0), value, length);
|
||||||
|
return buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<signed precision, char padchar> string hex(uintmax_t value) {
|
||||||
|
string buffer;
|
||||||
|
buffer.resize(sizeof(uintmax_t) * 2);
|
||||||
|
|
||||||
|
unsigned size = 0;
|
||||||
|
do {
|
||||||
|
unsigned n = value & 15;
|
||||||
|
buffer[size++] = n < 10 ? '0' + n : 'a' + n - 10;
|
||||||
|
value >>= 4;
|
||||||
|
} while(value);
|
||||||
|
buffer[size] = 0;
|
||||||
|
buffer.reverse();
|
||||||
|
|
||||||
|
return format<precision, padchar>(buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<signed precision, char padchar> string octal(uintmax_t value) {
|
||||||
|
string buffer;
|
||||||
|
buffer.resize(sizeof(uintmax_t) * 3);
|
||||||
|
|
||||||
|
unsigned size = 0;
|
||||||
|
do {
|
||||||
|
buffer[size++] = '0' + (value & 7);
|
||||||
|
value >>= 3;
|
||||||
|
} while(value);
|
||||||
|
buffer[size] = 0;
|
||||||
|
buffer.reverse();
|
||||||
|
|
||||||
|
return format<precision, padchar>(buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<signed precision, char padchar> string binary(uintmax_t value) {
|
||||||
|
string buffer;
|
||||||
|
buffer.resize(sizeof(uintmax_t) * 8);
|
||||||
|
|
||||||
|
unsigned size = 0;
|
||||||
|
do {
|
||||||
|
buffer[size++] = '0' + (value & 1);
|
||||||
|
value >>= 1;
|
||||||
|
} while(value);
|
||||||
|
buffer[size] = 0;
|
||||||
|
buffer.reverse();
|
||||||
|
|
||||||
|
return format<precision, padchar>(buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
|
@ -201,50 +201,6 @@ template<unsigned length_, char padding> string ldecimal(uintmax_t value) {
|
||||||
return (const char*)result;
|
return (const char*)result;
|
||||||
}
|
}
|
||||||
|
|
||||||
template<unsigned length_, char padding> string hex(uintmax_t value) {
|
|
||||||
char buffer[64];
|
|
||||||
unsigned size = 0;
|
|
||||||
|
|
||||||
do {
|
|
||||||
unsigned n = value & 15;
|
|
||||||
buffer[size++] = n < 10 ? '0' + n : 'a' + n - 10;
|
|
||||||
value >>= 4;
|
|
||||||
} while(value);
|
|
||||||
|
|
||||||
unsigned length = (length_ == 0 ? size : length_);
|
|
||||||
char result[length + 1];
|
|
||||||
memset(result, padding, length);
|
|
||||||
result[length] = 0;
|
|
||||||
|
|
||||||
for(signed x = length - 1, y = 0; x >= 0 && y < size; x--, y++) {
|
|
||||||
result[x] = buffer[y];
|
|
||||||
}
|
|
||||||
|
|
||||||
return (const char*)result;
|
|
||||||
}
|
|
||||||
|
|
||||||
template<unsigned length_, char padding> string binary(uintmax_t value) {
|
|
||||||
char buffer[256];
|
|
||||||
unsigned size = 0;
|
|
||||||
|
|
||||||
do {
|
|
||||||
unsigned n = value & 1;
|
|
||||||
buffer[size++] = '0' + n;
|
|
||||||
value >>= 1;
|
|
||||||
} while(value);
|
|
||||||
|
|
||||||
unsigned length = (length_ == 0 ? size : length_);
|
|
||||||
char result[length + 1];
|
|
||||||
memset(result, padding, length);
|
|
||||||
result[length] = 0;
|
|
||||||
|
|
||||||
for(signed x = length - 1, y = 0; x >= 0 && y < size; x--, y++) {
|
|
||||||
result[x] = buffer[y];
|
|
||||||
}
|
|
||||||
|
|
||||||
return (const char*)result;
|
|
||||||
}
|
|
||||||
|
|
||||||
//using sprintf is certainly not the most ideal method to convert
|
//using sprintf is certainly not the most ideal method to convert
|
||||||
//a double to a string ... but attempting to parse a double by
|
//a double to a string ... but attempting to parse a double by
|
||||||
//hand, digit-by-digit, results in subtle rounding errors.
|
//hand, digit-by-digit, results in subtle rounding errors.
|
||||||
|
|
|
@ -27,6 +27,11 @@ string& string::upper() { nall::strupper(data); return *this; }
|
||||||
string& string::qlower() { nall::qstrlower(data); return *this; }
|
string& string::qlower() { nall::qstrlower(data); return *this; }
|
||||||
string& string::qupper() { nall::qstrupper(data); return *this; }
|
string& string::qupper() { nall::qstrupper(data); return *this; }
|
||||||
string& string::transform(const char *before, const char *after) { nall::strtr(data, before, after); return *this; }
|
string& string::transform(const char *before, const char *after) { nall::strtr(data, before, after); return *this; }
|
||||||
|
string& string::reverse() {
|
||||||
|
unsigned length = strlen(data), pivot = length >> 1;
|
||||||
|
for(signed x = 0, y = length - 1; x < pivot && y >= 0; x++, y--) std::swap(data[x], data[y]);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
template<unsigned limit> string& string::ltrim(const char *key) { nall::ltrim<limit>(data, key); return *this; }
|
template<unsigned limit> string& string::ltrim(const char *key) { nall::ltrim<limit>(data, key); return *this; }
|
||||||
template<unsigned limit> string& string::rtrim(const char *key) { nall::rtrim<limit>(data, key); return *this; }
|
template<unsigned limit> string& string::rtrim(const char *key) { nall::rtrim<limit>(data, key); return *this; }
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
*.o
|
|
@ -15,11 +15,6 @@ ifeq ($(platform),x)
|
||||||
else ifeq ($(platform),win)
|
else ifeq ($(platform),win)
|
||||||
phoenixflags := -DPHOENIX_WINDOWS
|
phoenixflags := -DPHOENIX_WINDOWS
|
||||||
phoenixlink := -lkernel32 -luser32 -lgdi32 -ladvapi32 -lole32 -lcomctl32 -lcomdlg32 -lshlwapi
|
phoenixlink := -lkernel32 -luser32 -lgdi32 -ladvapi32 -lole32 -lcomctl32 -lcomdlg32 -lshlwapi
|
||||||
|
|
||||||
ifeq ($(arch),win32)
|
|
||||||
phoenixflags := -m32 $(phoenixflags)
|
|
||||||
phoenixlink := -m32 $(phoenixlink)
|
|
||||||
endif
|
|
||||||
else
|
else
|
||||||
phoenixflags := -DPHOENIX_REFERENCE
|
phoenixflags := -DPHOENIX_REFERENCE
|
||||||
phoenixlink :=
|
phoenixlink :=
|
||||||
|
|
|
@ -26,7 +26,7 @@ string Ananke::createSufamiTurboDatabase(vector<uint8_t> &buffer, Markup::Node &
|
||||||
file::write({pathname, "program.rom"}, buffer);
|
file::write({pathname, "program.rom"}, buffer);
|
||||||
copySufamiTurboSaves(pathname);
|
copySufamiTurboSaves(pathname);
|
||||||
|
|
||||||
return "";
|
return pathname;
|
||||||
}
|
}
|
||||||
|
|
||||||
string Ananke::createSufamiTurboHeuristic(vector<uint8_t> &buffer) {
|
string Ananke::createSufamiTurboHeuristic(vector<uint8_t> &buffer) {
|
||||||
|
@ -48,7 +48,7 @@ string Ananke::createSufamiTurboHeuristic(vector<uint8_t> &buffer) {
|
||||||
file::write({pathname, "program.rom"}, buffer);
|
file::write({pathname, "program.rom"}, buffer);
|
||||||
copySufamiTurboSaves(pathname);
|
copySufamiTurboSaves(pathname);
|
||||||
|
|
||||||
return "";
|
return pathname;
|
||||||
}
|
}
|
||||||
|
|
||||||
string Ananke::openSufamiTurbo(vector<uint8_t> &buffer) {
|
string Ananke::openSufamiTurbo(vector<uint8_t> &buffer) {
|
||||||
|
@ -69,3 +69,18 @@ string Ananke::openSufamiTurbo(vector<uint8_t> &buffer) {
|
||||||
|
|
||||||
return createSufamiTurboHeuristic(buffer);
|
return createSufamiTurboHeuristic(buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
string Ananke::syncSufamiTurbo(const string &pathname) {
|
||||||
|
auto buffer = file::read({pathname, "program.rom"});
|
||||||
|
if(buffer.size() == 0) return "";
|
||||||
|
|
||||||
|
auto save = file::read({pathname, "save.ram"});
|
||||||
|
if(save.size() == 0) save = file::read({pathname, "save.rwm"});
|
||||||
|
|
||||||
|
directory::remove(pathname);
|
||||||
|
string outputPath = openSufamiTurbo(buffer);
|
||||||
|
|
||||||
|
if(save.size()) file::write({outputPath, "save.ram"}, save);
|
||||||
|
|
||||||
|
return outputPath;
|
||||||
|
}
|
||||||
|
|
|
@ -137,3 +137,74 @@ string Ananke::openSuperFamicom(vector<uint8_t> &buffer) {
|
||||||
|
|
||||||
return createSuperFamicomHeuristic(buffer);
|
return createSuperFamicomHeuristic(buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
string Ananke::syncSuperFamicom(const string &pathname) {
|
||||||
|
vector<uint8_t> buffer;
|
||||||
|
|
||||||
|
auto append = [&](string filename) {
|
||||||
|
filename = {pathname, filename};
|
||||||
|
auto data = file::read(filename);
|
||||||
|
if(data.size() == 0) return; //file does not exist
|
||||||
|
|
||||||
|
unsigned position = buffer.size();
|
||||||
|
buffer.resize(buffer.size() + data.size());
|
||||||
|
memcpy(buffer.data() + position, data.data(), data.size());
|
||||||
|
};
|
||||||
|
|
||||||
|
append("program.rom");
|
||||||
|
append("data.rom");
|
||||||
|
|
||||||
|
append("dsp1.rom");
|
||||||
|
append("dsp1.program.rom");
|
||||||
|
append("dsp1.data.rom");
|
||||||
|
|
||||||
|
append("dsp1b.rom");
|
||||||
|
append("dsp1b.program.rom");
|
||||||
|
append("dsp1b.data.rom");
|
||||||
|
|
||||||
|
append("dsp2.rom");
|
||||||
|
append("dsp2.program.rom");
|
||||||
|
append("dsp2.data.rom");
|
||||||
|
|
||||||
|
append("dsp3.rom");
|
||||||
|
append("dsp3.program.rom");
|
||||||
|
append("dsp3.data.rom");
|
||||||
|
|
||||||
|
append("dsp4.rom");
|
||||||
|
append("dsp4.program.rom");
|
||||||
|
append("dsp4.data.rom");
|
||||||
|
|
||||||
|
append("st010.rom");
|
||||||
|
append("st010.program.rom");
|
||||||
|
append("st010.data.rom");
|
||||||
|
|
||||||
|
append("st011.rom");
|
||||||
|
append("st011.program.rom");
|
||||||
|
append("st011.data.rom");
|
||||||
|
|
||||||
|
append("st018.rom");
|
||||||
|
append("st018.program.rom");
|
||||||
|
append("st018.data.rom");
|
||||||
|
|
||||||
|
append("cx4.rom");
|
||||||
|
append("cx4.data.rom");
|
||||||
|
|
||||||
|
append("sgb.rom");
|
||||||
|
append("sgb.boot.rom");
|
||||||
|
|
||||||
|
if(buffer.size() == 0) return "";
|
||||||
|
|
||||||
|
auto save = file::read({pathname, "save.ram"});
|
||||||
|
if(save.size() == 0) save = file::read({pathname, "save.rwm"});
|
||||||
|
|
||||||
|
auto rtc = file::read({pathname, "rtc.ram"});
|
||||||
|
if(rtc.size() == 0) rtc= file::read({pathname, "rtc.rwm"});
|
||||||
|
|
||||||
|
directory::remove(pathname);
|
||||||
|
string outputPath = openSuperFamicom(buffer);
|
||||||
|
|
||||||
|
if(save.size()) file::write({outputPath, "save.ram"}, save);
|
||||||
|
if(rtc.size()) file::write({outputPath, "rtc.ram"}, save);
|
||||||
|
|
||||||
|
return outputPath;
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue