auto Program::load() -> void { unload(); if(auto configuration = string::read(locate("configuration.bml"))) { emulator->configure(configuration); settingsWindow->advanced.updateConfiguration(); } if(!emulator->load()) return; gameQueue = {}; screenshot = {}; frameAdvance = false; if(!verified() && settingsWindow->advanced.warnOnUnverifiedGames.checked()) { auto response = MessageDialog( "Warning: this game image is unverified.\n" "Running it *may* be a security risk.\n\n" "Do you wish to run the game anyway?" ).setParent(*presentation).question({"Always", "Yes", "No"}); if(response == "No") { emulator->unload(); return showMessage("Game loading cancelled"); } if(response == "Always") { settingsWindow->advanced.warnOnUnverifiedGames.setChecked(false).doToggle(); } } hackCompatibility(); emulator->power(); if(settingsWindow->advanced.autoLoadStateOnLoad.checked()) { program->loadState("quick/undo"); } showMessage({ verified() ? "Verified game loaded" : "Game loaded", appliedPatch() ? " and patch applied" : "" }); presentation->setTitle(emulator->titles().merge(" + ")); presentation->resetSystem.setEnabled(true); presentation->unloadGame.setEnabled(true); presentation->toolsMenu.setVisible(true); presentation->speedNormal.setChecked(); presentation->pauseEmulation.setChecked(false); presentation->updateStatusIcon(); presentation->resizeViewport(); toolsWindow->cheatEditor.loadCheats(); toolsWindow->stateManager.loadStates(); toolsWindow->manifestViewer.loadManifest(); string locations = superFamicom.location; if(auto& location = gameBoy.location) locations.append("|", location); if(auto& location = bsMemory.location) locations.append("|", location); if(auto& location = sufamiTurboA.location) locations.append("|", location); if(auto& location = sufamiTurboB.location) locations.append("|", location); presentation->addRecentGame(locations); updateVideoPalette(); updateAudioEffects(); updateAudioFrequency(); } auto Program::loadFile(string location) -> vector { if(Location::suffix(location) == ".zip") { Decode::ZIP archive; if(archive.open(location)) { for(auto& file : archive.file) { auto type = Location::suffix(file.name); if(type == ".sfc" || type == ".smc" || type == ".gb" || type == ".gbc" || type == ".bs" || type == ".st") { return archive.extract(file); } } } return {}; } else { return file::read(location); } } auto Program::loadSuperFamicom(string location) -> bool { string manifest; vector rom; if(location.endsWith("/")) { manifest = file::read({location, "manifest.bml"}); rom.append(file::read({location, "program.rom"})); rom.append(file::read({location, "data.rom"})); rom.append(file::read({location, "expansion.rom"})); for(auto& filename : directory::files(location, "*.boot.rom" )) rom.append(file::read({location, filename})); for(auto& filename : directory::files(location, "*.program.rom")) rom.append(file::read({location, filename})); for(auto& filename : directory::files(location, "*.data.rom" )) rom.append(file::read({location, filename})); } else { manifest = file::read({Location::notsuffix(location), ".bml"}); rom = loadFile(location); } if(rom.size() < 0x8000) return false; //assume ROM and IPS agree on whether a copier header is present superFamicom.patched = applyPatchIPS(rom, location); if((rom.size() & 0x7fff) == 512) { //remove copier header memory::move(&rom[0], &rom[512], rom.size() - 512); rom.resize(rom.size() - 512); } //assume BPS is made against a ROM without a copier header if(!superFamicom.patched) superFamicom.patched = applyPatchBPS(rom, location); auto heuristics = Heuristics::SuperFamicom(rom, location); auto sha256 = Hash::SHA256(rom).digest(); if(auto document = BML::unserialize(string::read(locate("database/Super Famicom.bml")))) { if(auto game = document[{"game(sha256=", sha256, ")"}]) { manifest = BML::serialize(game); superFamicom.verified = true; } } superFamicom.label = heuristics.label(); superFamicom.manifest = manifest ? manifest : heuristics.manifest(); hackPatchMemory(rom); hackOverclockSuperFX(); superFamicom.document = BML::unserialize(superFamicom.manifest); superFamicom.location = location; uint offset = 0; if(auto size = heuristics.programRomSize()) { superFamicom.program.resize(size); memory::copy(&superFamicom.program[0], &rom[offset], size); offset += size; } if(auto size = heuristics.dataRomSize()) { superFamicom.data.resize(size); memory::copy(&superFamicom.data[0], &rom[offset], size); offset += size; } if(auto size = heuristics.expansionRomSize()) { superFamicom.expansion.resize(size); memory::copy(&superFamicom.expansion[0], &rom[offset], size); offset += size; } if(auto size = heuristics.firmwareRomSize()) { superFamicom.firmware.resize(size); memory::copy(&superFamicom.firmware[0], &rom[offset], size); offset += size; } return true; } auto Program::loadGameBoy(string location) -> bool { string manifest; vector rom; if(location.endsWith("/")) { manifest = file::read({location, "manifest.bml"}); rom.append(file::read({location, "program.rom"})); } else { manifest = file::read({Location::notsuffix(location), ".bml"}); rom = loadFile(location); } if(rom.size() < 0x4000) return false; gameBoy.patched = applyPatchIPS(rom, location) || applyPatchBPS(rom, location); auto heuristics = Heuristics::GameBoy(rom, location); auto sha256 = Hash::SHA256(rom).digest(); if(auto document = BML::unserialize(string::read(locate("database/Game Boy.bml")))) { if(auto game = document[{"game(sha256=", sha256, ")"}]) { manifest = BML::serialize(game); gameBoy.verified = true; } } if(auto document = BML::unserialize(string::read(locate("database/Game Boy Color.bml")))) { if(auto game = document[{"game(sha256=", sha256, ")"}]) { manifest = BML::serialize(game); gameBoy.verified = true; } } gameBoy.manifest = manifest ? manifest : heuristics.manifest(); gameBoy.document = BML::unserialize(gameBoy.manifest); gameBoy.location = location; gameBoy.program = rom; return true; } auto Program::loadBSMemory(string location) -> bool { string manifest; vector rom; if(location.endsWith("/")) { manifest = file::read({location, "manifest.bml"}); rom.append(file::read({location, "program.rom"})); rom.append(file::read({location, "program.flash"})); } else { manifest = file::read({Location::notsuffix(location), ".bml"}); rom = loadFile(location); } if(rom.size() < 0x8000) return false; bsMemory.patched = applyPatchIPS(rom, location) || applyPatchBPS(rom, location); auto heuristics = Heuristics::BSMemory(rom, location); auto sha256 = Hash::SHA256(rom).digest(); if(auto document = BML::unserialize(string::read(locate("database/BS Memory.bml")))) { if(auto game = document[{"game(sha256=", sha256, ")"}]) { manifest = BML::serialize(game); bsMemory.verified = true; } } bsMemory.manifest = manifest ? manifest : heuristics.manifest(); bsMemory.document = BML::unserialize(bsMemory.manifest); bsMemory.location = location; bsMemory.program = rom; return true; } auto Program::loadSufamiTurboA(string location) -> bool { string manifest; vector rom; if(location.endsWith("/")) { manifest = file::read({location, "manifest.bml"}); rom.append(file::read({location, "program.rom"})); } else { manifest = file::read({Location::notsuffix(location), ".bml"}); rom = loadFile(location); } if(rom.size() < 0x20000) return false; sufamiTurboA.patched = applyPatchIPS(rom, location) || applyPatchBPS(rom, location); auto heuristics = Heuristics::SufamiTurbo(rom, location); auto sha256 = Hash::SHA256(rom).digest(); if(auto document = BML::unserialize(string::read(locate("database/Sufami Turbo.bml")))) { if(auto game = document[{"game(sha256=", sha256, ")"}]) { manifest = BML::serialize(game); sufamiTurboA.verified = true; } } sufamiTurboA.manifest = manifest ? manifest : heuristics.manifest(); sufamiTurboA.document = BML::unserialize(sufamiTurboA.manifest); sufamiTurboA.location = location; sufamiTurboA.program = rom; return true; } auto Program::loadSufamiTurboB(string location) -> bool { string manifest; vector rom; if(location.endsWith("/")) { manifest = file::read({location, "manifest.bml"}); rom.append(file::read({location, "program.rom"})); } else { manifest = file::read({Location::notsuffix(location), ".bml"}); rom = loadFile(location); } if(rom.size() < 0x20000) return false; sufamiTurboB.patched = applyPatchIPS(rom, location) || applyPatchBPS(rom, location); auto heuristics = Heuristics::SufamiTurbo(rom, location); auto sha256 = Hash::SHA256(rom).digest(); if(auto document = BML::unserialize(string::read(locate("database/Sufami Turbo.bml")))) { if(auto game = document[{"game(sha256=", sha256, ")"}]) { manifest = BML::serialize(game); sufamiTurboB.verified = true; } } sufamiTurboB.manifest = manifest ? manifest : heuristics.manifest(); sufamiTurboB.document = BML::unserialize(sufamiTurboB.manifest); sufamiTurboB.location = location; sufamiTurboB.program = rom; return true; } auto Program::save() -> void { if(!emulator->loaded()) return; emulator->save(); } auto Program::reset() -> void { if(!emulator->loaded()) return; hackCompatibility(); emulator->reset(); showMessage("Game reset"); } auto Program::unload() -> void { if(!emulator->loaded()) return; toolsWindow->cheatEditor.saveCheats(); toolsWindow->setVisible(false); if(settingsWindow->advanced.autoSaveStateOnUnload.checked()) { saveUndoState(); } if(auto configuration = emulator->configuration()) { file::write(locate("configuration.bml"), configuration); } emulator->unload(); showMessage("Game unloaded"); superFamicom = {}; gameBoy = {}; bsMemory = {}; sufamiTurboA = {}; sufamiTurboB = {}; presentation->setTitle({"bsnes v", Emulator::Version}); presentation->resetSystem.setEnabled(false); presentation->unloadGame.setEnabled(false); presentation->toolsMenu.setVisible(false); presentation->updateStatusIcon(); presentation->clearViewport(); } //a game is considered verified if the game plus its slot(s) are found in the games database auto Program::verified() const -> bool { if(!emulator->loaded()) return false; if(superFamicom && !superFamicom.verified) return false; if(gameBoy && !gameBoy.verified) return false; if(bsMemory && !bsMemory.verified) return false; if(sufamiTurboA && !sufamiTurboA.verified) return false; if(sufamiTurboB && !sufamiTurboB.verified) return false; return true; }