bsnes/qt/cartridge/cartridge.cpp

401 lines
13 KiB
C++
Executable File

#include "../ui-base.hpp"
Cartridge cartridge;
//================
//public functions
//================
bool Cartridge::information(const char *filename, Cartridge::Information &info) {
if(extension(filename) != "sfc") return false; //do not parse compressed images
file fp;
if(fp.open(filename, file::mode_read) == false) return false;
unsigned offset = 0;
if((fp.size() & 0x7fff) == 512) offset = 512;
uint16_t complement, checksum;
fp.seek(0x7fdc + offset);
complement = fp.readl(2);
checksum = fp.readl(2);
unsigned header = offset + (complement + checksum == 65535 ? 0x7fb0 : 0xffb0);
fp.seek(header + 0x10);
char name[22];
fp.read((uint8_t*)name, 21);
name[21] = 0;
info.name = decodeShiftJIS(name);
fp.seek(header + 0x29);
uint8_t region = fp.read();
info.region = (region <= 1 || region >= 13) ? "NTSC" : "PAL";
info.romSize = fp.size() & ~0x7fff;
fp.seek(header + 0x28);
info.ramSize = fp.readl(1);
if(info.ramSize) info.ramSize = 1024 << (info.ramSize & 7);
fp.close();
return true;
}
bool Cartridge::saveStatesSupported() {
if(SNES::cartridge.mode() == SNES::Cartridge::Mode::Bsx) return false;
if(SNES::cartridge.has_dsp3()) return false;
if(SNES::cartridge.has_dsp4()) return false;
if(SNES::cartridge.has_st0011()) return false;
if(SNES::cartridge.has_st0018()) return false;
if(SNES::cartridge.has_serial()) return false;
return true;
}
bool Cartridge::loadNormal(const char *base) {
unload();
if(loadCartridge(baseName = base, cartridge.baseXml, SNES::memory::cartrom) == false) return false;
SNES::cartridge.basename = nall::basename(baseName);
SNES::cartridge.load(SNES::Cartridge::Mode::Normal,
lstring() << cartridge.baseXml);
loadMemory(baseName, ".srm", SNES::memory::cartram);
loadMemory(baseName, ".rtc", SNES::memory::cartrtc);
fileName = baseName;
name = notdir(nall::basename(baseName));
utility.modifySystemState(Utility::LoadCartridge);
return true;
}
bool Cartridge::loadBsxSlotted(const char *base, const char *slot) {
unload();
if(loadCartridge(baseName = base, cartridge.baseXml, SNES::memory::cartrom) == false) return false;
loadCartridge(slotAName = slot, cartridge.slotAXml, SNES::memory::bsxflash);
SNES::cartridge.basename = nall::basename(baseName);
SNES::cartridge.load(SNES::Cartridge::Mode::BsxSlotted,
lstring() << cartridge.baseXml << cartridge.slotAXml);
loadMemory(baseName, ".srm", SNES::memory::cartram);
loadMemory(baseName, ".rtc", SNES::memory::cartrtc);
fileName = baseName;
name = notdir(nall::basename(baseName));
if(*slot) name << " + " << notdir(nall::basename(slotAName));
utility.modifySystemState(Utility::LoadCartridge);
return true;
}
bool Cartridge::loadBsx(const char *base, const char *slot) {
unload();
if(loadCartridge(baseName = base, cartridge.baseXml, SNES::memory::cartrom) == false) return false;
loadCartridge(slotAName = slot, cartridge.slotAXml, SNES::memory::bsxflash);
SNES::cartridge.basename = nall::basename(baseName);
SNES::cartridge.load(SNES::Cartridge::Mode::Bsx,
lstring() << cartridge.baseXml << cartridge.slotAXml);
loadMemory(baseName, ".srm", SNES::memory::bsxram );
loadMemory(baseName, ".psr", SNES::memory::bsxpram);
fileName = slotAName;
name = *slot
? notdir(nall::basename(slotAName))
: notdir(nall::basename(baseName));
utility.modifySystemState(Utility::LoadCartridge);
return true;
}
bool Cartridge::loadSufamiTurbo(const char *base, const char *slotA, const char *slotB) {
unload();
if(loadCartridge(baseName = base, cartridge.baseXml, SNES::memory::cartrom) == false) return false;
loadCartridge(slotAName = slotA, cartridge.slotAXml, SNES::memory::stArom);
loadCartridge(slotBName = slotB, cartridge.slotBXml, SNES::memory::stBrom);
SNES::cartridge.basename = nall::basename(baseName);
SNES::cartridge.load(SNES::Cartridge::Mode::SufamiTurbo,
lstring() << cartridge.baseXml << cartridge.slotAXml << cartridge.slotBXml);
loadMemory(slotAName, ".srm", SNES::memory::stAram);
loadMemory(slotBName, ".srm", SNES::memory::stBram);
fileName = slotAName;
if(!*slotA && !*slotB) name = notdir(nall::basename(baseName));
else if(!*slotB) name = notdir(nall::basename(slotAName));
else if(!*slotA) name = notdir(nall::basename(slotBName));
else name = notdir(nall::basename(slotAName)) << " + " << notdir(nall::basename(slotBName));
utility.modifySystemState(Utility::LoadCartridge);
return true;
}
bool Cartridge::loadSuperGameBoy(const char *base, const char *slot) {
unload();
if(loadCartridge(baseName = base, cartridge.baseXml, SNES::memory::cartrom) == false) return false;
loadCartridge(slotAName = slot, cartridge.slotAXml, SNES::memory::gbrom);
SNES::cartridge.basename = nall::basename(baseName);
SNES::cartridge.load(SNES::Cartridge::Mode::SuperGameBoy,
lstring() << cartridge.baseXml << cartridge.slotAXml);
loadMemory(slotAName, ".sav", SNES::memory::gbram);
loadMemory(slotBName, ".rtc", SNES::memory::gbrtc);
fileName = slotAName;
name = *slot
? notdir(nall::basename(slotAName))
: notdir(nall::basename(baseName));
utility.modifySystemState(Utility::LoadCartridge);
return true;
}
void Cartridge::saveMemory() {
if(SNES::cartridge.loaded() == false) return;
switch(SNES::cartridge.mode()) {
case SNES::Cartridge::Mode::Normal:
case SNES::Cartridge::Mode::BsxSlotted: {
saveMemory(baseName, ".srm", SNES::memory::cartram);
saveMemory(baseName, ".rtc", SNES::memory::cartrtc);
} break;
case SNES::Cartridge::Mode::Bsx: {
saveMemory(baseName, ".srm", SNES::memory::bsxram );
saveMemory(baseName, ".psr", SNES::memory::bsxpram);
} break;
case SNES::Cartridge::Mode::SufamiTurbo: {
saveMemory(slotAName, ".srm", SNES::memory::stAram);
saveMemory(slotBName, ".srm", SNES::memory::stBram);
} break;
case SNES::Cartridge::Mode::SuperGameBoy: {
saveMemory(slotAName, ".sav", SNES::memory::gbram);
saveMemory(slotAName, ".rtc", SNES::memory::gbrtc);
} break;
}
}
void Cartridge::unload() {
if(SNES::cartridge.loaded() == false) return;
utility.modifySystemState(Utility::UnloadCartridge);
}
void Cartridge::loadCheats() {
string name;
name << filepath(nall::basename(baseName), config().path.cheat);
name << ".cht";
cheatEditorWindow->load(name);
}
void Cartridge::saveCheats() {
string name;
name << filepath(nall::basename(baseName), config().path.cheat);
name << ".cht";
cheatEditorWindow->save(name);
}
//=================
//private functions
//=================
bool Cartridge::loadCartridge(string &filename, string &xml, SNES::MappedRAM &memory) {
if(file::exists(filename) == false) return false;
uint8_t *data;
unsigned size;
audio.clear();
if(reader.load(filename, data, size) == false) return false;
patchApplied = false;
string name(filepath(nall::basename(filename), config().path.patch), ".ups");
file fp;
if(config().file.applyPatches && fp.open(name, file::mode_read)) {
unsigned patchsize = fp.size();
uint8_t *patchdata = new uint8_t[patchsize];
fp.read(patchdata, patchsize);
fp.close();
uint8_t *outdata = 0;
unsigned outsize = 0;
ups patcher;
ups::result result = patcher.apply(patchdata, patchsize, data, size, outdata, outsize);
delete[] patchdata;
bool apply = false;
if(result == ups::ok) apply = true;
if(config().file.bypassPatchCrc32) {
if(result == ups::input_crc32_invalid ) apply = true;
if(result == ups::output_crc32_invalid) apply = true;
}
if(apply == true) {
delete[] data;
data = outdata;
size = outsize;
patchApplied = true;
} else {
delete[] outdata;
}
}
name = string(nall::basename(filename), ".xml");
if(file::exists(name)) {
//prefer manually created XML cartridge mapping
xml.readfile(name);
} else {
//generate XML mapping from data via heuristics
xml = snes_information(data, size).xml_memory_map;
}
memory.copy(data, size);
delete[] data;
return true;
}
bool Cartridge::loadMemory(const char *filename, const char *extension, SNES::MappedRAM &memory) {
if(memory.size() == 0 || memory.size() == -1U) return false;
string name;
name << filepath(nall::basename(filename), config().path.save);
name << extension;
file fp;
if(fp.open(name, file::mode_read) == false) return false;
unsigned size = fp.size();
uint8_t *data = new uint8_t[size];
fp.read(data, size);
fp.close();
memory.copy(data, size);
delete[] data;
return true;
}
bool Cartridge::saveMemory(const char *filename, const char *extension, SNES::MappedRAM &memory) {
if(memory.size() == 0 || memory.size() == -1U) return false;
string name;
name << filepath(nall::basename(filename), config().path.save);
name << extension;
file fp;
if(fp.open(name, file::mode_write) == false) return false;
fp.write(memory.data(), memory.size());
fp.close();
return true;
}
string Cartridge::decodeShiftJIS(const char *text) {
unsigned length = strlen(text), offset = 0;
string output;
for(unsigned i = 0; i < length;) {
unsigned code = 0;
uint8_t n = text[i++];
if(n == 0x00) {
//string terminator
break;
} else if(n >= 0x20 && n <= 0x7f) {
//ASCII
code = n;
} else if(n >= 0xa0 && n <= 0xdf) {
//ShiftJIS half-width katakana
unsigned dakuten = 0, handakuten = 0;
switch(n) {
case 0xa1: code = 0xe38082; break; //(period)
case 0xa2: code = 0xe3808c; break; //(open quote)
case 0xa3: code = 0xe3808d; break; //(close quote)
case 0xa4: code = 0xe38081; break; //(comma)
case 0xa5: code = 0xe383bb; break; //(separator)
case 0xa6: code = 0xe383b2; break; //wo
case 0xa7: code = 0xe382a1; break; //la
case 0xa8: code = 0xe382a3; break; //li
case 0xa9: code = 0xe382a5; break; //lu
case 0xaa: code = 0xe382a7; break; //le
case 0xab: code = 0xe382a9; break; //lo
case 0xac: code = 0xe383a3; break; //lya
case 0xad: code = 0xe383a5; break; //lyu
case 0xae: code = 0xe383a7; break; //lyo
case 0xaf: code = 0xe38383; break; //ltsu
case 0xb0: code = 0xe383bc; break; //-
case 0xb1: code = 0xe382a2; break; //a
case 0xb2: code = 0xe382a4; break; //i
case 0xb3: code = 0xe382a6; break; //u
case 0xb4: code = 0xe382a8; break; //e
case 0xb5: code = 0xe382aa; break; //o
case 0xb6: code = 0xe382ab; dakuten = 0xe382ac; break; //ka, ga
case 0xb7: code = 0xe382ad; dakuten = 0xe382ae; break; //ki, gi
case 0xb8: code = 0xe382af; dakuten = 0xe382b0; break; //ku, gu
case 0xb9: code = 0xe382b1; dakuten = 0xe382b2; break; //ke, ge
case 0xba: code = 0xe382b3; dakuten = 0xe382b4; break; //ko, go
case 0xbb: code = 0xe382b5; dakuten = 0xe382b6; break; //sa, za
case 0xbc: code = 0xe382b7; dakuten = 0xe382b8; break; //shi, zi
case 0xbd: code = 0xe382b9; dakuten = 0xe382ba; break; //su, zu
case 0xbe: code = 0xe382bb; dakuten = 0xe382bc; break; //se, ze
case 0xbf: code = 0xe382bd; dakuten = 0xe382be; break; //so, zo
case 0xc0: code = 0xe382bf; dakuten = 0xe38380; break; //ta, da
case 0xc1: code = 0xe38381; dakuten = 0xe38382; break; //chi, di
case 0xc2: code = 0xe38384; dakuten = 0xe38385; break; //tsu, du
case 0xc3: code = 0xe38386; dakuten = 0xe38387; break; //te, de
case 0xc4: code = 0xe38388; dakuten = 0xe38389; break; //to, do
case 0xc5: code = 0xe3838a; break; //na
case 0xc6: code = 0xe3838b; break; //ni
case 0xc7: code = 0xe3838c; break; //nu
case 0xc8: code = 0xe3838d; break; //ne
case 0xc9: code = 0xe3838e; break; //no
case 0xca: code = 0xe3838f; dakuten = 0xe38390; handakuten = 0xe38391; break; //ha, ba, pa
case 0xcb: code = 0xe38392; dakuten = 0xe38393; handakuten = 0xe38394; break; //hi, bi, pi
case 0xcc: code = 0xe38395; dakuten = 0xe38396; handakuten = 0xe38397; break; //fu, bu, pu
case 0xcd: code = 0xe38398; dakuten = 0xe38399; handakuten = 0xe3839a; break; //he, be, pe
case 0xce: code = 0xe3839b; dakuten = 0xe3839c; handakuten = 0xe3839d; break; //ho, bo, po
case 0xcf: code = 0xe3839e; break; //ma
case 0xd0: code = 0xe3839f; break; //mi
case 0xd1: code = 0xe383a0; break; //mu
case 0xd2: code = 0xe383a1; break; //me
case 0xd3: code = 0xe383a2; break; //mo
case 0xd4: code = 0xe383a4; break; //ya
case 0xd5: code = 0xe383a6; break; //yu
case 0xd6: code = 0xe383a8; break; //yo
case 0xd7: code = 0xe383a9; break; //ra
case 0xd8: code = 0xe383aa; break; //ri
case 0xd9: code = 0xe383ab; break; //ru
case 0xda: code = 0xe383ac; break; //re
case 0xdb: code = 0xe383ad; break; //ro
case 0xdc: code = 0xe383af; break; //wa
case 0xdd: code = 0xe383b3; break; //n
}
if(dakuten && ((uint8_t)text[i] == 0xde)) {
code = dakuten;
i++;
} else if(handakuten && ((uint8_t)text[i] == 0xdf)) {
code = handakuten;
i++;
}
}
if(code) {
if((uint8_t)(code >> 16)) output[offset++] = (char)(code >> 16);
if((uint8_t)(code >> 8)) output[offset++] = (char)(code >> 8);
if((uint8_t)(code >> 0)) output[offset++] = (char)(code >> 0);
}
}
output[offset] = 0;
return output;
}