bsnes/higan/md/cartridge/cartridge.cpp

298 lines
8.2 KiB
C++

#include <md/md.hpp>
namespace MegaDrive {
Cartridge cartridge;
#include "serialization.cpp"
auto Cartridge::hashes() const -> vector<string> {
vector<string> hashes;
hashes.append(information.hash);
if(slot) for(auto& hash : slot->hashes()) hashes.append(hash);
return hashes;
}
auto Cartridge::manifests() const -> vector<string> {
vector<string> manifests;
manifests.append(information.manifest);
if(slot) for(auto& manifest : slot->manifests()) manifests.append(manifest);
return manifests;
}
auto Cartridge::titles() const -> vector<string> {
vector<string> titles;
titles.append(information.title);
if(slot) for(auto& title : slot->titles()) titles.append(title);
return titles;
}
auto Cartridge::load() -> bool {
unload();
if(auto loaded = platform->load(ID::MegaDrive, "Mega Drive", "md", {"Auto", "NTSC-J", "NTSC-U", "PAL"})) {
information.pathID = loaded.pathID;
information.region = loaded.option;
} else return false;
if(auto fp = platform->open(pathID(), "manifest.bml", File::Read, File::Required)) {
information.manifest = fp->reads();
} else return false;
information.document = BML::unserialize(information.manifest);
information.hash = information.document["game/sha256"].text();
information.title = information.document["game/label"].text();
if(!loadROM(rom, information.document["game/board/memory(type=ROM,content=Program)"])) {
return unload(), false;
}
if(!loadROM(patch, information.document["game/board/memory(type=ROM,content=Patch)"])) {
patch.reset();
}
if(!loadRAM(ram, information.document["game/board/memory(type=RAM,content=Save)"])) {
ram.reset();
}
if(information.region == "Auto") {
if(auto region = information.document["game/region"].text()) {
information.region = region.upcase();
} else {
information.region = "NTSC-J";
}
}
read = {&Cartridge::readLinear, this};
write = {&Cartridge::writeLinear, this};
if(rom.size > 0x200000) {
read = {&Cartridge::readBanked, this};
write = {&Cartridge::writeBanked, this};
}
if(information.document["game/board/slot(type=MegaDrive)"]) {
slot = new Cartridge{depth + 1};
if(!slot->load()) slot.reset();
if(patch) {
read = {&Cartridge::readLockOn, this};
write = {&Cartridge::writeLockOn, this};
} else {
read = {&Cartridge::readGameGenie, this};
write = {&Cartridge::writeGameGenie, this};
}
}
//easter egg: power draw increases with each successively stacked cartridge
//simulate increasing address/data line errors as stacking increases
if(depth >= 3) {
auto reader = read;
auto writer = write;
auto scramble = [=](uint32 value) -> uint32 {
uint chance = max(1, (1 << 19) >> depth) - 1;
if((random() & chance) == 0) value ^= 1 << (random() & 31);
return value;
};
read = [=](uint22 address) -> uint16 {
return scramble(reader(scramble(address)));
};
write = [=](uint22 address, uint16 data) -> void {
writer(scramble(address), scramble(data));
};
}
return true;
}
auto Cartridge::save() -> void {
saveRAM(ram, information.document["game/board/memory(type=RAM,content=Save)"]);
if(slot) slot->save();
}
auto Cartridge::unload() -> void {
rom.reset();
patch.reset();
ram.reset();
read.reset();
write.reset();
if(slot) slot->unload();
slot.reset();
}
auto Cartridge::power() -> void {
ramEnable = 1;
ramWritable = 1;
for(uint n : range(8)) romBank[n] = n;
gameGenie = {};
}
//
auto Cartridge::loadROM(Memory& rom, Markup::Node memory) -> bool {
if(!memory) return false;
auto name = string{memory["content"].text(), ".", memory["type"].text()}.downcase();
rom.size = memory["size"].natural() >> 1;
rom.mask = bit::round(rom.size) - 1;
rom.data = new uint16[rom.mask + 1]();
if(auto fp = platform->open(pathID(), name, File::Read, File::Required)) {
for(uint n : range(rom.size)) rom.data[n] = fp->readm(2);
} else return false;
return true;
}
auto Cartridge::loadRAM(Memory& ram, Markup::Node memory) -> bool {
if(!memory) return false;
auto name = string{memory["content"].text(), ".", memory["type"].text()}.downcase();
if(auto mode = memory["mode"].text()) {
if(mode == "lo" ) ram.bits = 0x00ff;
if(mode == "hi" ) ram.bits = 0xff00;
if(mode == "word") ram.bits = 0xffff;
}
ram.size = memory["size"].natural() >> (ram.bits == 0xffff);
ram.mask = bit::round(ram.size) - 1;
ram.data = new uint16[ram.mask + 1]();
if(!(bool)memory["volatile"]) {
if(auto fp = platform->open(pathID(), name, File::Read)) {
for(uint n : range(ram.size)) {
if(ram.bits != 0xffff) ram.data[n] = fp->readm(1) * 0x0101;
if(ram.bits == 0xffff) ram.data[n] = fp->readm(2);
}
}
}
return true;
}
auto Cartridge::saveRAM(Memory& ram, Markup::Node memory) -> bool {
if(!memory) return false;
if((bool)memory["volatile"]) return true;
auto name = string{memory["content"].text(), ".", memory["type"].text()}.downcase();
if(auto fp = platform->open(pathID(), name, File::Write)) {
for(uint n : range(ram.size)) {
if(ram.bits != 0xffff) fp->writem(ram.data[n], 1);
if(ram.bits == 0xffff) fp->writem(ram.data[n], 2);
}
} else return false;
return true;
}
//
auto Cartridge::readIO(uint24 address) -> uint16 {
if(slot) slot->readIO(address);
return 0x0000;
}
auto Cartridge::writeIO(uint24 address, uint16 data) -> void {
if(address == 0xa130f1) ramEnable = data.bit(0), ramWritable = data.bit(1);
if(address == 0xa130f3) romBank[1] = data;
if(address == 0xa130f5) romBank[2] = data;
if(address == 0xa130f7) romBank[3] = data;
if(address == 0xa130f9) romBank[4] = data;
if(address == 0xa130fb) romBank[5] = data;
if(address == 0xa130fd) romBank[6] = data;
if(address == 0xa130ff) romBank[7] = data;
if(slot) slot->writeIO(address, data);
}
//
auto Cartridge::readLinear(uint22 address) -> uint16 {
if(ramEnable && ram && address >= 0x200000) return ram.read(address);
return rom.read(address);
}
auto Cartridge::writeLinear(uint22 address, uint16 data) -> void {
//emulating ramWritable will break commercial software:
//it does not appear that many (any?) games actually connect $a130f1.d1 to /WE;
//hence RAM ends up always being writable, and many games fail to set d1=1
if(ramEnable && ram && address >= 0x200000) return ram.write(address, data);
}
//
auto Cartridge::readBanked(uint22 address) -> uint16 {
address = romBank[address.bits(19,21)] << 19 | address.bits(0,18);
return rom.read(address);
}
auto Cartridge::writeBanked(uint22 address, uint16 data) -> void {
}
//
auto Cartridge::readLockOn(uint22 address) -> uint16 {
if(address < 0x200000) return rom.read(address);
if(ramEnable && address >= 0x300000) return patch.read(address);
if(slot) return slot->read(address);
return 0x0000;
}
auto Cartridge::writeLockOn(uint22 address, uint16 data) -> void {
if(slot) return slot->write(address, data);
}
//
auto Cartridge::readGameGenie(uint22 address) -> uint16 {
if(gameGenie.enable) {
for(auto& code : gameGenie.codes) {
if(code.enable && code.address == address) return code.data;
}
if(slot) return slot->read(address);
}
return rom.read(address);
}
auto Cartridge::writeGameGenie(uint22 address, uint16 data) -> void {
if(gameGenie.enable) {
if(slot) return slot->write(address, data);
}
if(address == 0x02 && data == 0x0001) {
gameGenie.enable = true;
}
if(address >= 0x04 && address <= 0x20 && !address.bit(0)) {
address = address - 0x04 >> 1;
auto& code = gameGenie.codes[address / 3];
if(address % 3 == 0) code.address.bits(16,23) = data.byte(0);
if(address % 3 == 1) code.address.bits( 0,15) = data;
if(address % 3 == 2) code.data = data, code.enable = true;
}
}
//
Cartridge::Memory::operator bool() const {
return size;
}
auto Cartridge::Memory::reset() -> void {
delete[] data;
data = nullptr;
size = 0;
mask = 0;
bits = 0;
}
auto Cartridge::Memory::read(uint24 address) -> uint16 {
if(!size) return 0x0000;
return data[address >> 1 & mask];
}
auto Cartridge::Memory::write(uint24 address, uint16 word) -> void {
if(!size) return;
if(bits == 0x00ff) word.byte(1) = word.byte(0);
if(bits == 0xff00) word.byte(0) = word.byte(1);
data[address >> 1 & mask] = word;
}
}