From 5a8c814e250ed97d28f1a550b5999e0d72ef4f92 Mon Sep 17 00:00:00 2001 From: Tim Allen Date: Mon, 11 Jun 2018 14:50:18 +1000 Subject: [PATCH] Update to v106r40 release. byuu says: Changelog: - hiro: added BrowserDialog::openObject() [match file *or* folder by filters] - hiro: BrowserDialog accept button is now disabled when it would otherwise do nothing - eg openFile without a folder to enter or file to open selected - eg saveFile without a file name or with a file name that matches a folder name - bsnes: added support for gamepaks (game folders) - bsnes: store all save states inside per-game .bsz (ZIP) archives instead of .bst/ folders - this reduces the number of state files from 10+ to 1; without having folders sort before files - hiro: both gtk2 and gtk3 now use cairo to render Canvas; supports sx,sy [BearOso] - higan, bsnes: fast PPU/DSP are now run-time options instead of compile-time options - bsnes: disable fast PPU when loading Air Strike Patrol / Desert Fighter - bsnes: disable fast DSP when loading Koushien 2 - bsnes: added options to advanced panel to disable fast PPU and/or fast DSP --- higan/GNUmakefile | 2 - higan/emulator/emulator.hpp | 2 +- higan/emulator/thread.hpp | 2 +- higan/sfc/GNUmakefile | 2 +- higan/sfc/dsp/dsp.cpp | 16 +- higan/sfc/interface/interface.cpp | 19 ++ higan/sfc/interface/interface.hpp | 3 + higan/sfc/ppu-fast/ppu.cpp | 3 +- higan/sfc/ppu-fast/ppu.hpp | 6 + higan/sfc/ppu/io.cpp | 18 +- higan/sfc/ppu/ppu.cpp | 18 ++ higan/sfc/ppu/ppu.hpp | 21 +- higan/sfc/ppu/serialization.cpp | 4 + higan/sfc/sfc.hpp | 6 +- higan/sfc/system/serialization.cpp | 14 +- higan/sfc/system/system.cpp | 3 + higan/sfc/system/system.hpp | 8 + higan/target-bsnes/input/hotkeys.cpp | 4 +- .../presentation/presentation.cpp | 18 +- higan/target-bsnes/program/game-pak.cpp | 7 + higan/target-bsnes/program/game-rom.cpp | 148 +++++++++++++ higan/target-bsnes/program/game.cpp | 28 ++- higan/target-bsnes/program/interface.cpp | 197 ++++-------------- higan/target-bsnes/program/paths.cpp | 38 +++- higan/target-bsnes/program/program.cpp | 6 +- higan/target-bsnes/program/program.hpp | 16 +- higan/target-bsnes/program/state.cpp | 36 ---- higan/target-bsnes/program/states.cpp | 80 +++++++ higan/target-bsnes/program/utility.cpp | 12 ++ higan/target-bsnes/settings/advanced.cpp | 8 + higan/target-bsnes/settings/paths.cpp | 34 +-- higan/target-bsnes/settings/settings.cpp | 4 +- higan/target-bsnes/settings/settings.hpp | 13 +- higan/target-bsnes/tools/cheat-editor.cpp | 4 +- higan/target-bsnes/tools/state-manager.cpp | 39 +++- hiro/extension/browser-dialog.cpp | 116 +++++++---- hiro/extension/browser-dialog.hpp | 3 +- hiro/gtk/widget/canvas.cpp | 43 ++-- nall/nall.hpp | 1 + 39 files changed, 643 insertions(+), 359 deletions(-) create mode 100644 higan/target-bsnes/program/game-pak.cpp create mode 100644 higan/target-bsnes/program/game-rom.cpp delete mode 100644 higan/target-bsnes/program/state.cpp create mode 100644 higan/target-bsnes/program/states.cpp diff --git a/higan/GNUmakefile b/higan/GNUmakefile index 92d2b92c..801b62ff 100644 --- a/higan/GNUmakefile +++ b/higan/GNUmakefile @@ -4,11 +4,9 @@ include ../nall/GNUmakefile binary := application target := bsnes -profile := fast objects := libco emulator audio video resource flags += -I. -I.. -flags += $(if $(call streq,$(profile),accurate),-DPROFILE_ACCURATE,-DPROFILE_FAST) ifeq ($(platform),windows) ifeq ($(binary),application) diff --git a/higan/emulator/emulator.hpp b/higan/emulator/emulator.hpp index ddc271f3..e31b8a51 100644 --- a/higan/emulator/emulator.hpp +++ b/higan/emulator/emulator.hpp @@ -13,7 +13,7 @@ using namespace nall; namespace Emulator { static const string Name = "higan"; - static const string Version = "106.39"; + static const string Version = "106.40"; static const string Author = "byuu"; static const string License = "GPLv3"; static const string Website = "https://byuu.org/"; diff --git a/higan/emulator/thread.hpp b/higan/emulator/thread.hpp index 39d2bdef..e4bb6d66 100644 --- a/higan/emulator/thread.hpp +++ b/higan/emulator/thread.hpp @@ -45,7 +45,7 @@ struct Thread { s.integer(_clock); } -protected: +//protected: cothread_t _handle = nullptr; uintmax _frequency = 0; uintmax _scalar = 0; diff --git a/higan/sfc/GNUmakefile b/higan/sfc/GNUmakefile index e36a314a..e8a9725c 100644 --- a/higan/sfc/GNUmakefile +++ b/higan/sfc/GNUmakefile @@ -2,7 +2,7 @@ processors += wdc65816 spc700 arm7tdmi gsu hg51b upd96050 objects += sfc-interface sfc-system sfc-controller objects += sfc-cartridge sfc-memory -objects += sfc-cpu sfc-smp sfc-dsp $(if $(call streq,$(profile),accurate),sfc-ppu,sfc-ppu-fast) +objects += sfc-cpu sfc-smp sfc-dsp sfc-ppu sfc-ppu-fast objects += sfc-expansion sfc-satellaview sfc-21fx objects += sfc-icd sfc-mcc sfc-dip sfc-event objects += sfc-sa1 sfc-superfx diff --git a/higan/sfc/dsp/dsp.cpp b/higan/sfc/dsp/dsp.cpp index 96c67713..0efcdc0e 100644 --- a/higan/sfc/dsp/dsp.cpp +++ b/higan/sfc/dsp/dsp.cpp @@ -192,18 +192,18 @@ auto DSP::main() -> void { } auto DSP::tick() -> void { - #if defined(PROFILE_ACCURATE) - step(3 * 8); - synchronize(smp); - #endif + if(!system.fastDSP()) { + step(3 * 8); + synchronize(smp); + } } auto DSP::sample(int16 left, int16 right) -> void { stream->sample(left / 32768.0, right / 32768.0); - #if defined(PROFILE_FAST) - step(32 * 3 * 8); - synchronize(smp); - #endif + if(system.fastDSP()) { + step(32 * 3 * 8); + synchronize(smp); + } } /* register interface for S-SMP $00f2,$00f3 */ diff --git a/higan/sfc/interface/interface.cpp b/higan/sfc/interface/interface.cpp index 8bcbcb67..9d329b29 100644 --- a/higan/sfc/interface/interface.cpp +++ b/higan/sfc/interface/interface.cpp @@ -233,6 +233,9 @@ auto Interface::cheatSet(const string_vector& list) -> void { } auto Interface::cap(const string& name) -> bool { + if(name == "Fast PPU") return true; + if(name == "Fast DSP") return true; + if(name == "Mode") return true; if(name == "Blur Emulation") return true; if(name == "Color Emulation") return true; if(name == "Scanline Emulation") return true; @@ -240,6 +243,14 @@ auto Interface::cap(const string& name) -> bool { } auto Interface::get(const string& name) -> any { + if(name == "Mode") return string{ + system.fastPPU() && system.fastDSP() ? "[Fast PPU+DSP] " + : system.fastPPU() ? "[Fast PPU] " + : system.fastDSP() ? "[Fast DSP] " + : "" + }; + if(name == "Fast PPU") return settings.fastPPU; + if(name == "Fast DSP") return settings.fastDSP; if(name == "Blur Emulation") return settings.blurEmulation; if(name == "Color Emulation") return settings.colorEmulation; if(name == "Scanline Emulation") return settings.scanlineEmulation; @@ -247,6 +258,14 @@ auto Interface::get(const string& name) -> any { } auto Interface::set(const string& name, const any& value) -> bool { + if(name == "Fast PPU" && value.is()) { + settings.fastPPU = value.get(); + return true; + } + if(name == "Fast DSP" && value.is()) { + settings.fastDSP = value.get(); + return true; + } if(name == "Blur Emulation" && value.is()) { settings.blurEmulation = value.get(); return true; diff --git a/higan/sfc/interface/interface.hpp b/higan/sfc/interface/interface.hpp index 51c4f7b3..7c4eae57 100644 --- a/higan/sfc/interface/interface.hpp +++ b/higan/sfc/interface/interface.hpp @@ -67,6 +67,9 @@ struct Interface : Emulator::Interface { }; struct Settings { + bool fastPPU = false; + bool fastDSP = false; + bool blurEmulation = true; bool colorEmulation = true; bool scanlineEmulation = true; diff --git a/higan/sfc/ppu-fast/ppu.cpp b/higan/sfc/ppu-fast/ppu.cpp index 5b41920f..89c88d99 100644 --- a/higan/sfc/ppu-fast/ppu.cpp +++ b/higan/sfc/ppu-fast/ppu.cpp @@ -1,4 +1,6 @@ #include +#define PPU PPUfast +#define ppu ppufast namespace SuperFamicom { @@ -10,7 +12,6 @@ PPU ppu; #include "object.cpp" #include "window.cpp" #include "serialization.cpp" -#include PPU::PPU() { output = new uint32[512 * 512] + 16 * 512; //overscan offset diff --git a/higan/sfc/ppu-fast/ppu.hpp b/higan/sfc/ppu-fast/ppu.hpp index ca15c189..79cb85c3 100644 --- a/higan/sfc/ppu-fast/ppu.hpp +++ b/higan/sfc/ppu-fast/ppu.hpp @@ -5,6 +5,9 @@ //* vertical mosaic coordinates are not exact //* (hardware-mod) 128KB VRAM mode not supported +#define PPU PPUfast +#define ppu ppufast + struct PPU : Thread, PPUcounter { alwaysinline auto interlace() const -> bool { return latch.interlace; } alwaysinline auto overscan() const -> bool { return latch.overscan; } @@ -304,3 +307,6 @@ public: }; extern PPU ppu; + +#undef PPU +#undef ppu diff --git a/higan/sfc/ppu/io.cpp b/higan/sfc/ppu/io.cpp index eb1a9649..14d91f83 100644 --- a/higan/sfc/ppu/io.cpp +++ b/higan/sfc/ppu/io.cpp @@ -1,3 +1,14 @@ +auto PPU::latchCounters() -> void { + if(system.fastPPU()) { + return ppufast.latchCounters(); + } + + cpu.synchronize(ppu); + io.hcounter = hdot(); + io.vcounter = vcounter(); + latch.counters = 1; +} + auto PPU::addressVRAM() const -> uint16 { uint16 address = io.vramAddress; switch(io.vramMapping) { @@ -619,13 +630,6 @@ auto PPU::writeIO(uint24 addr, uint8 data) -> void { } } -auto PPU::latchCounters() -> void { - cpu.synchronize(ppu); - io.hcounter = hdot(); - io.vcounter = vcounter(); - latch.counters = 1; -} - auto PPU::updateVideoMode() -> void { switch(io.bgMode) { case 0: diff --git a/higan/sfc/ppu/ppu.cpp b/higan/sfc/ppu/ppu.cpp index 12e500f6..f4137866 100644 --- a/higan/sfc/ppu/ppu.cpp +++ b/higan/sfc/ppu/ppu.cpp @@ -25,6 +25,10 @@ bg4(Background::ID::BG4) { } PPU::~PPU() { + if(system.fastPPU()) { + _handle = nullptr; + } + output -= 16 * 512; delete[] output; } @@ -78,6 +82,10 @@ auto PPU::main() -> void { } auto PPU::load(Markup::Node node) -> bool { + if(system.fastPPU()) { + return ppufast.load(node); + } + ppu1.version = max(1, min(1, node["ppu1/version"].natural())); ppu2.version = max(1, min(3, node["ppu2/version"].natural())); ppu.vram.mask = node["ppu1/ram/size"].natural() / sizeof(uint16) - 1; @@ -86,6 +94,12 @@ auto PPU::load(Markup::Node node) -> bool { } auto PPU::power(bool reset) -> void { + if(system.fastPPU()) { + ppufast.power(reset); + _handle = ppufast._handle; + return; + } + create(Enter, system.cpuFrequency()); PPUcounter::reset(); memory::fill(output, 512 * 480); @@ -220,6 +234,10 @@ auto PPU::frame() -> void { } auto PPU::refresh() -> void { + if(system.fastPPU()) { + return ppufast.refresh(); + } + auto output = this->output; if(!overscan()) output -= 12 * 512; auto pitch = 512; diff --git a/higan/sfc/ppu/ppu.hpp b/higan/sfc/ppu/ppu.hpp index ff3cb855..99337a36 100644 --- a/higan/sfc/ppu/ppu.hpp +++ b/higan/sfc/ppu/ppu.hpp @@ -1,20 +1,29 @@ +#include + struct PPU : Thread, PPUcounter { - alwaysinline auto interlace() const -> bool { return display.interlace; } - alwaysinline auto overscan() const -> bool { return display.overscan; } - alwaysinline auto vdisp() const -> uint { return !io.overscan ? 225 : 240; } + //ppu.cpp + alwaysinline auto interlace() const -> bool { if(system.fastPPU()) return ppufast.interlace(); return display.interlace; } + alwaysinline auto overscan() const -> bool { if(system.fastPPU()) return ppufast.overscan(); return display.overscan; } + alwaysinline auto vdisp() const -> uint { if(system.fastPPU()) return ppufast.vdisp(); return !io.overscan ? 225 : 240; } PPU(); ~PPU(); - alwaysinline auto step(uint clocks) -> void; - static auto Enter() -> void; auto main() -> void; auto load(Markup::Node) -> bool; auto power(bool reset) -> void; + //io.cpp + auto latchCounters() -> void; + + //serialization.cpp auto serialize(serializer&) -> void; +private: + //ppu.cpp + alwaysinline auto step(uint clocks) -> void; + //io.cpp alwaysinline auto addressVRAM() const -> uint16; alwaysinline auto readVRAM() -> uint16; @@ -25,10 +34,8 @@ struct PPU : Thread, PPUcounter { alwaysinline auto writeCGRAM(uint8 addr, uint15 data) -> void; auto readIO(uint24 addr, uint8 data) -> uint8; auto writeIO(uint24 addr, uint8 data) -> void; - auto latchCounters() -> void; auto updateVideoMode() -> void; -private: struct VRAM { auto& operator[](uint addr) { return data[addr & mask]; } uint16 data[64 * 1024]; diff --git a/higan/sfc/ppu/serialization.cpp b/higan/sfc/ppu/serialization.cpp index e1441f4a..f1e45c5f 100644 --- a/higan/sfc/ppu/serialization.cpp +++ b/higan/sfc/ppu/serialization.cpp @@ -1,4 +1,8 @@ auto PPU::serialize(serializer& s) -> void { + if(system.fastPPU()) { + return ppufast.serialize(s); + } + Thread::serialize(s); PPUcounter::serialize(s); diff --git a/higan/sfc/sfc.hpp b/higan/sfc/sfc.hpp index 0d2737b7..c477a61d 100644 --- a/higan/sfc/sfc.hpp +++ b/higan/sfc/sfc.hpp @@ -46,21 +46,17 @@ namespace SuperFamicom { static inline auto PAL() -> bool; }; + #include #include #include #include #include #include -#if defined(PROFILE_ACCURATE) #include -#elif defined(PROFILE_FAST) - #include -#endif #include #include - #include #include #include #include diff --git a/higan/sfc/system/serialization.cpp b/higan/sfc/system/serialization.cpp index e6c53734..72253ac3 100644 --- a/higan/sfc/system/serialization.cpp +++ b/higan/sfc/system/serialization.cpp @@ -13,6 +13,9 @@ auto System::serialize() -> serializer { s.array(hash); s.array(description); + s.boolean(hacks.fastPPU); + s.boolean(hacks.fastDSP); + serializeAll(s); return s; } @@ -31,6 +34,12 @@ auto System::unserialize(serializer& s) -> bool { if(signature != 0x31545342) return false; if(string{version} != Emulator::SerializerVersion) return false; + s.boolean(hacks.fastPPU); + s.boolean(hacks.fastDSP); + + settings.fastPPU = hacks.fastPPU; + settings.fastDSP = hacks.fastDSP; + power(/* reset = */ false); serializeAll(s); return true; @@ -42,9 +51,9 @@ auto System::serialize(serializer& s) -> void { } auto System::serializeAll(serializer& s) -> void { + system.serialize(s); random.serialize(s); cartridge.serialize(s); - system.serialize(s); cpu.serialize(s); smp.serialize(s); ppu.serialize(s); @@ -91,6 +100,9 @@ auto System::serializeInit() -> void { s.array(hash); s.array(description); + s.boolean(hacks.fastPPU); + s.boolean(hacks.fastDSP); + serializeAll(s); serializeSize = s.size(); } diff --git a/higan/sfc/system/system.cpp b/higan/sfc/system/system.cpp index 8eb586d2..e74aea7e 100644 --- a/higan/sfc/system/system.cpp +++ b/higan/sfc/system/system.cpp @@ -88,6 +88,9 @@ auto System::unload() -> void { } auto System::power(bool reset) -> void { + hacks.fastPPU = settings.fastPPU; + hacks.fastDSP = settings.fastDSP; + Emulator::video.reset(); Emulator::video.setInterface(interface); Emulator::video.setPalette(); diff --git a/higan/sfc/system/system.hpp b/higan/sfc/system/system.hpp index e07bf3b4..eff1a558 100644 --- a/higan/sfc/system/system.hpp +++ b/higan/sfc/system/system.hpp @@ -6,6 +6,9 @@ struct System { inline auto cpuFrequency() const -> double { return information.cpuFrequency; } inline auto apuFrequency() const -> double { return information.apuFrequency; } + inline auto fastPPU() const -> bool { return hacks.fastPPU; } + inline auto fastDSP() const -> bool { return hacks.fastDSP; } + auto run() -> void; auto runToSave() -> void; @@ -29,6 +32,11 @@ private: double apuFrequency = 32040.0 * 768.0; } information; + struct Hacks { + bool fastPPU = false; + bool fastDSP = false; + } hacks; + uint serializeSize = 0; auto serialize(serializer&) -> void; diff --git a/higan/target-bsnes/input/hotkeys.cpp b/higan/target-bsnes/input/hotkeys.cpp index 50015508..311bb112 100644 --- a/higan/target-bsnes/input/hotkeys.cpp +++ b/higan/target-bsnes/input/hotkeys.cpp @@ -10,11 +10,11 @@ auto InputManager::bindHotkeys() -> void { })); hotkeys.append(InputHotkey("Save State").onPress([&] { - program->saveState({"Quick/Slot ", stateSlot}); + program->saveState({"quick/slot ", stateSlot}); })); hotkeys.append(InputHotkey("Load State").onPress([&] { - program->loadState({"Quick/Slot ", stateSlot}); + program->loadState({"quick/slot ", stateSlot}); })); hotkeys.append(InputHotkey("Increment State Slot").onPress([&] { diff --git a/higan/target-bsnes/presentation/presentation.cpp b/higan/target-bsnes/presentation/presentation.cpp index 2b37b562..5fce950b 100644 --- a/higan/target-bsnes/presentation/presentation.cpp +++ b/higan/target-bsnes/presentation/presentation.cpp @@ -13,7 +13,10 @@ Presentation::Presentation() { loadRecentGame.setText("Load Recent Game"); updateRecentGames(); resetSystem.setText("Reset System").setEnabled(false).onActivate([&] { - if(emulator->loaded()) emulator->reset(); + if(emulator->loaded()) { + program->applyHacks(); + emulator->reset(); + } }); unloadGame.setText("Unload Game").setEnabled(false).onActivate([&] { program->unload(); @@ -110,18 +113,18 @@ Presentation::Presentation() { saveState.setText("Save State"); for(uint index : range(QuickStates)) { saveState.append(MenuItem().setText({"Slot ", 1 + index}).onActivate([=] { - program->saveState({"Quick/Slot ", 1 + index}); + program->saveState({"quick/slot ", 1 + index}); })); } loadState.setText("Load State"); for(uint index : range(QuickStates)) { loadState.append(MenuItem().setText({"Slot ", 1 + index}).onActivate([=] { - program->loadState({"Quick/Slot ", 1 + index}); + program->loadState({"quick/slot ", 1 + index}); })); } loadState.append(MenuSeparator()); - loadState.append(MenuItem().setText("Recovery Slot").onActivate([&] { - program->loadState("Quick/Recovery Slot"); + loadState.append(MenuItem().setText("Recovery").onActivate([&] { + program->loadState("quick/recovery"); })); pauseEmulation.setText("Pause Emulation").onToggle([&] { if(pauseEmulation.checked()) audio->clear(); @@ -134,14 +137,15 @@ Presentation::Presentation() { aboutWindow->setCentered(*this).setVisible().setFocused(); }); - viewport.setDroppable().onDrop([&](auto locations) { + viewport.setDroppable().onDrop([&](string_vector locations) { program->gameQueue = locations; program->load(); - presentation->setFocused(); + setFocused(); }); statusBar.setFont(Font().setBold()); statusBar.setVisible(settings["UserInterface/ShowStatusBar"].boolean()); + program->updateMessage(); onClose([&] { program->quit(); diff --git a/higan/target-bsnes/program/game-pak.cpp b/higan/target-bsnes/program/game-pak.cpp new file mode 100644 index 00000000..04e866f8 --- /dev/null +++ b/higan/target-bsnes/program/game-pak.cpp @@ -0,0 +1,7 @@ +auto Program::openPakSFC(string name, vfs::file::mode mode) -> vfs::shared::file { + return vfs::fs::file::open({superNintendo.location, name}, mode); +} + +auto Program::openPakGB(string name, vfs::file::mode mode) -> vfs::shared::file { + return vfs::fs::file::open({gameBoy.location, name}, mode); +} diff --git a/higan/target-bsnes/program/game-rom.cpp b/higan/target-bsnes/program/game-rom.cpp new file mode 100644 index 00000000..304bd89f --- /dev/null +++ b/higan/target-bsnes/program/game-rom.cpp @@ -0,0 +1,148 @@ +auto Program::openRomSFC(string name, vfs::file::mode mode) -> vfs::shared::file { + if(name == "program.rom" && mode == vfs::file::mode::read) { + return vfs::memory::file::open(superNintendo.program.data(), superNintendo.program.size()); + } + + if(name == "data.rom" && mode == vfs::file::mode::read) { + return vfs::memory::file::open(superNintendo.data.data(), superNintendo.data.size()); + } + + if(name == "expansion.rom" && mode == vfs::file::mode::read) { + return vfs::memory::file::open(superNintendo.expansion.data(), superNintendo.expansion.size()); + } + + if(name == "arm6.program.rom" && mode == vfs::file::mode::read) { + if(superNintendo.firmware.size() == 0x28000) { + return vfs::memory::file::open(&superNintendo.firmware.data()[0x00000], 0x20000); + } + if(auto memory = superNintendo.document["game/board/memory(type=ROM,content=Program,architecture=ARM6)"]) { + string location = locate({"firmware/", memory["identifier"].text().downcase(), ".program.rom"}); + return vfs::fs::file::open(location, mode); + } + } + + if(name == "arm6.data.rom" && mode == vfs::file::mode::read) { + if(superNintendo.firmware.size() == 0x28000) { + return vfs::memory::file::open(&superNintendo.firmware.data()[0x20000], 0x08000); + } + if(auto memory = superNintendo.document["game/board/memory(type=ROM,content=Data,architecture=ARM6)"]) { + string location = locate({"firmware/", memory["identifier"].text().downcase(), ".data.rom"}); + return vfs::fs::file::open(location, mode); + } + } + + if(name == "hg51bs169.data.rom" && mode == vfs::file::mode::read) { + if(superNintendo.firmware.size() == 0xc00) { + return vfs::memory::file::open(superNintendo.firmware.data(), superNintendo.firmware.size()); + } + if(auto memory = superNintendo.document["game/board/memory(type=ROM,content=Data,architecture=HG51BS169)"]) { + string location = locate({"firmware/", memory["identifier"].text().downcase(), ".data.rom"}); + return vfs::fs::file::open(location, mode); + } + } + + if(name == "lr35902.boot.rom" && mode == vfs::file::mode::read) { + if(superNintendo.firmware.size() == 0x100) { + return vfs::memory::file::open(superNintendo.firmware.data(), superNintendo.firmware.size()); + } + if(auto memory = superNintendo.document["game/board/memory(type=ROM,content=Boot,architecture=LR35902)"]) { + string location = locate({"firmware/", memory["identifier"].text().downcase(), ".boot.rom"}); + return vfs::fs::file::open(location, mode); + } + } + + if(name == "upd7725.program.rom" && mode == vfs::file::mode::read) { + if(superNintendo.firmware.size() == 0x2000) { + return vfs::memory::file::open(&superNintendo.firmware.data()[0x0000], 0x1800); + } + if(auto memory = superNintendo.document["game/board/memory(type=ROM,content=Program,architecture=uPD7725)"]) { + string location = locate({"firmware/", memory["identifier"].text().downcase(), ".program.rom"}); + return vfs::fs::file::open(location, mode); + } + } + + if(name == "upd7725.data.rom" && mode == vfs::file::mode::read) { + if(superNintendo.firmware.size() == 0x2000) { + return vfs::memory::file::open(&superNintendo.firmware.data()[0x1800], 0x0800); + } + if(auto memory = superNintendo.document["game/board/memory(type=ROM,content=Data,architecture=uPD7725)"]) { + string location = locate({"firmware/", memory["identifier"].text().downcase(), ".data.rom"}); + return vfs::fs::file::open(location, mode); + } + } + + if(name == "upd96050.program.rom" && mode == vfs::file::mode::read) { + if(superNintendo.firmware.size() == 0xd000) { + return vfs::memory::file::open(&superNintendo.firmware.data()[0x0000], 0xc000); + } + if(auto memory = superNintendo.document["game/board/memory(type=ROM,content=Program,architecture=uPD96050)"]) { + string location = locate({"firmware/", memory["identifier"].text().downcase(), ".program.rom"}); + return vfs::fs::file::open(location, mode); + } + } + + if(name == "upd96050.data.rom" && mode == vfs::file::mode::read) { + if(superNintendo.firmware.size() == 0xd000) { + return vfs::memory::file::open(&superNintendo.firmware.data()[0xc000], 0x1000); + } + if(auto memory = superNintendo.document["game/board/memory(type=ROM,content=Data,architecture=uPD96050)"]) { + string location = locate({"firmware/", memory["identifier"].text().downcase(), ".data.rom"}); + return vfs::fs::file::open(location, mode); + } + } + + if(name == "save.ram") { + return vfs::fs::file::open(path("Saves", superNintendo.location, ".srm"), mode); + } + + if(name == "download.ram") { + return vfs::fs::file::open(path("Saves", superNintendo.location, ".psr"), mode); + } + + if(name == "time.rtc") { + return vfs::fs::file::open(path("Saves", superNintendo.location, ".rtc"), mode); + } + + if(name == "arm6.data.ram") { + return vfs::fs::file::open(path("Saves", superNintendo.location, ".srm"), mode); + } + + if(name == "hg51bs169.data.ram") { + return vfs::fs::file::open(path("Saves", superNintendo.location, ".srm"), mode); + } + + if(name == "upd7725.data.ram") { + return vfs::fs::file::open(path("Saves", superNintendo.location, ".srm"), mode); + } + + if(name == "upd96050.data.ram") { + return vfs::fs::file::open(path("Saves", superNintendo.location, ".srm"), mode); + } + + if(name == "msu1/data.rom") { + return vfs::fs::file::open({Location::notsuffix(superNintendo.location), ".msu"}, mode); + } + + if(name.match("msu1/track-*.pcm")) { + name.trimLeft("msu1/track-", 1L); + return vfs::fs::file::open({Location::notsuffix(superNintendo.location), name}, mode); + } + + return {}; +} + +auto Program::openRomGB(string name, vfs::file::mode mode) -> vfs::shared::file { + if(name == "program.rom" && mode == vfs::file::mode::read) { + return vfs::memory::file::open(gameBoy.program.data(), gameBoy.program.size()); + } + + if(name == "save.ram") { + return vfs::fs::file::open(path("Saves", gameBoy.location, ".sav"), mode); + } + + if(name == "time.rtc") { + return vfs::fs::file::open(path("Saves", gameBoy.location, ".sav"), mode); + } + + return {}; +} diff --git a/higan/target-bsnes/program/game.cpp b/higan/target-bsnes/program/game.cpp index cd0d3314..f0c24d87 100644 --- a/higan/target-bsnes/program/game.cpp +++ b/higan/target-bsnes/program/game.cpp @@ -8,6 +8,7 @@ auto Program::load() -> void { if(emulator->load(media.id)) { gameQueue = {}; connectDevices(); + applyHacks(); emulator->power(); presentation->setTitle(emulator->title()); presentation->resetSystem.setEnabled(true); @@ -44,11 +45,24 @@ auto Program::loadFile(string location) -> vector { } auto Program::loadSuperNintendo(string location) -> void { - auto rom = loadFile(location); - if(!rom) return; + vector rom; + + //game pak + if(location.endsWith("/")) { + 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 { + //game ROM + rom = loadFile(location); + } //Heuristics::SuperFamicom() call will remove copier header from rom if present auto heuristics = Heuristics::SuperFamicom(rom, location); + superNintendo.label = heuristics.label(); superNintendo.manifest = heuristics.manifest(); superNintendo.document = BML::unserialize(superNintendo.manifest); superNintendo.location = location; @@ -77,8 +91,14 @@ auto Program::loadSuperNintendo(string location) -> void { } auto Program::loadGameBoy(string location) -> void { - auto rom = loadFile(location); - if(!rom) return; + vector rom; + + //game pak + if(location.endsWith("/")) { + rom.append(file::read({location, "program.rom"})); + } else { + rom = loadFile(location); + } auto heuristics = Heuristics::GameBoy(rom, location); gameBoy.manifest = heuristics.manifest(); diff --git a/higan/target-bsnes/program/interface.cpp b/higan/target-bsnes/program/interface.cpp index 11472cf1..4fecaa6d 100644 --- a/higan/target-bsnes/program/interface.cpp +++ b/higan/target-bsnes/program/interface.cpp @@ -6,174 +6,47 @@ #include auto Program::open(uint id, string name, vfs::file::mode mode, bool required) -> vfs::shared::file { - //System + vfs::shared::file result; - if(id == 0 && name == "manifest.bml" && mode == vfs::file::mode::read) { - return vfs::memory::file::open(Resource::System::Manifest.data(), Resource::System::Manifest.size()); - } - - if(id == 0 && name == "boards.bml" && mode == vfs::file::mode::read) { - return vfs::memory::file::open(Resource::System::Boards.data(), Resource::System::Boards.size()); - } - - if(id == 0 && name == "ipl.rom" && mode == vfs::file::mode::read) { - return vfs::memory::file::open(Resource::System::IPLROM.data(), Resource::System::IPLROM.size()); - } - - //Super Famicom - - if(id == 1 && name == "manifest.bml" && mode == vfs::file::mode::read) { - return vfs::memory::file::open(superNintendo.manifest.data(), superNintendo.manifest.size()); - } - - if(id == 1 && name == "program.rom" && mode == vfs::file::mode::read) { - return vfs::memory::file::open(superNintendo.program.data(), superNintendo.program.size()); - } - - if(id == 1 && name == "data.rom" && mode == vfs::file::mode::read) { - return vfs::memory::file::open(superNintendo.data.data(), superNintendo.data.size()); - } - - if(id == 1 && name == "expansion.rom" && mode == vfs::file::mode::read) { - return vfs::memory::file::open(superNintendo.expansion.data(), superNintendo.expansion.size()); - } - - if(id == 1 && name == "arm6.program.rom" && mode == vfs::file::mode::read) { - if(superNintendo.firmware.size() == 0x28000) { - return vfs::memory::file::open(&superNintendo.firmware.data()[0x00000], 0x20000); + if(id == 0) { //System + if(name == "manifest.bml" && mode == vfs::file::mode::read) { + result = vfs::memory::file::open(Resource::System::Manifest.data(), Resource::System::Manifest.size()); } - if(auto memory = superNintendo.document["game/board/memory(type=ROM,content=Program,architecture=ARM6)"]) { - string location = locate({"firmware/", memory["identifier"].text().downcase(), ".program.rom"}); - return vfs::fs::file::open(location, mode); + + if(name == "boards.bml" && mode == vfs::file::mode::read) { + result = vfs::memory::file::open(Resource::System::Boards.data(), Resource::System::Boards.size()); + } + + if(name == "ipl.rom" && mode == vfs::file::mode::read) { + result = vfs::memory::file::open(Resource::System::IPLROM.data(), Resource::System::IPLROM.size()); } } - if(id == 1 && name == "arm6.data.rom" && mode == vfs::file::mode::read) { - if(superNintendo.firmware.size() == 0x28000) { - return vfs::memory::file::open(&superNintendo.firmware.data()[0x20000], 0x08000); - } - if(auto memory = superNintendo.document["game/board/memory(type=ROM,content=Data,architecture=ARM6)"]) { - string location = locate({"firmware/", memory["identifier"].text().downcase(), ".data.rom"}); - return vfs::fs::file::open(location, mode); + if(id == 1) { //Super Famicom + if(name == "manifest.bml" && mode == vfs::file::mode::read) { + result = vfs::memory::file::open(superNintendo.manifest.data(), superNintendo.manifest.size()); + } else if(superNintendo.location.endsWith("/")) { + result = openPakSFC(name, mode); + } else { + result = openRomSFC(name, mode); } } - if(id == 1 && name == "hg51bs169.data.rom" && mode == vfs::file::mode::read) { - if(superNintendo.firmware.size() == 0xc00) { - return vfs::memory::file::open(superNintendo.firmware.data(), superNintendo.firmware.size()); - } - if(auto memory = superNintendo.document["game/board/memory(type=ROM,content=Data,architecture=HG51BS169)"]) { - string location = locate({"firmware/", memory["identifier"].text().downcase(), ".data.rom"}); - return vfs::fs::file::open(location, mode); + if(id == 2) { //Game Boy + if(name == "manifest.bml" && mode == vfs::file::mode::read) { + result = vfs::memory::file::open(gameBoy.manifest.data(), gameBoy.manifest.size()); + } else if(gameBoy.location.endsWith("/")) { + result = openPakGB(name, mode); + } else { + result = openRomGB(name, mode); } } - if(id == 1 && name == "lr35902.boot.rom" && mode == vfs::file::mode::read) { - if(superNintendo.firmware.size() == 0x100) { - return vfs::memory::file::open(superNintendo.firmware.data(), superNintendo.firmware.size()); - } - if(auto memory = superNintendo.document["game/board/memory(type=ROM,content=Boot,architecture=LR35902)"]) { - string location = locate({"firmware/", memory["identifier"].text().downcase(), ".boot.rom"}); - return vfs::fs::file::open(location, mode); - } + if(!result && required) { + MessageDialog({"Error: missing required data: ", name}).setParent(*presentation).error(); } - if(id == 1 && name == "upd7725.program.rom" && mode == vfs::file::mode::read) { - if(superNintendo.firmware.size() == 0x2000) { - return vfs::memory::file::open(&superNintendo.firmware.data()[0x0000], 0x1800); - } - if(auto memory = superNintendo.document["game/board/memory(type=ROM,content=Program,architecture=uPD7725)"]) { - string location = locate({"firmware/", memory["identifier"].text().downcase(), ".program.rom"}); - return vfs::fs::file::open(location, mode); - } - } - - if(id == 1 && name == "upd7725.data.rom" && mode == vfs::file::mode::read) { - if(superNintendo.firmware.size() == 0x2000) { - return vfs::memory::file::open(&superNintendo.firmware.data()[0x1800], 0x0800); - } - if(auto memory = superNintendo.document["game/board/memory(type=ROM,content=Data,architecture=uPD7725)"]) { - string location = locate({"firmware/", memory["identifier"].text().downcase(), ".data.rom"}); - return vfs::fs::file::open(location, mode); - } - } - - if(id == 1 && name == "upd96050.program.rom" && mode == vfs::file::mode::read) { - if(superNintendo.firmware.size() == 0xd000) { - return vfs::memory::file::open(&superNintendo.firmware.data()[0x0000], 0xc000); - } - if(auto memory = superNintendo.document["game/board/memory(type=ROM,content=Program,architecture=uPD96050)"]) { - string location = locate({"firmware/", memory["identifier"].text().downcase(), ".program.rom"}); - return vfs::fs::file::open(location, mode); - } - } - - if(id == 1 && name == "upd96050.data.rom" && mode == vfs::file::mode::read) { - if(superNintendo.firmware.size() == 0xd000) { - return vfs::memory::file::open(&superNintendo.firmware.data()[0xc000], 0x1000); - } - if(auto memory = superNintendo.document["game/board/memory(type=ROM,content=Data,architecture=uPD96050)"]) { - string location = locate({"firmware/", memory["identifier"].text().downcase(), ".data.rom"}); - return vfs::fs::file::open(location, mode); - } - } - - if(id == 1 && name == "save.ram") { - return vfs::fs::file::open(path("Saves", superNintendo.location, ".srm"), mode); - } - - if(id == 1 && name == "download.ram") { - return vfs::fs::file::open(path("Saves", superNintendo.location, ".psr"), mode); - } - - if(id == 1 && name == "time.rtc") { - return vfs::fs::file::open(path("Saves", superNintendo.location, ".rtc"), mode); - } - - if(id == 1 && name == "arm6.data.ram") { - return vfs::fs::file::open(path("Saves", superNintendo.location, ".srm"), mode); - } - - if(id == 1 && name == "hg51bs169.data.ram") { - return vfs::fs::file::open(path("Saves", superNintendo.location, ".srm"), mode); - } - - if(id == 1 && name == "upd7725.data.ram") { - return vfs::fs::file::open(path("Saves", superNintendo.location, ".srm"), mode); - } - - if(id == 1 && name == "upd96050.data.ram") { - return vfs::fs::file::open(path("Saves", superNintendo.location, ".srm"), mode); - } - - if(id == 1 && name == "msu1/data.rom") { - return vfs::fs::file::open({Location::notsuffix(superNintendo.location), ".msu"}, mode); - } - - if(id == 1 && name.match("msu1/track-*.pcm")) { - name.trimLeft("msu1/track-", 1L); - return vfs::fs::file::open({Location::notsuffix(superNintendo.location), name}, mode); - } - - //Game Boy - - if(id == 2 && name == "manifest.bml" && mode == vfs::file::mode::read) { - return vfs::memory::file::open(gameBoy.manifest.data(), gameBoy.manifest.size()); - } - - if(id == 2 && name == "program.rom" && mode == vfs::file::mode::read) { - return vfs::memory::file::open(gameBoy.program.data(), gameBoy.program.size()); - } - - if(id == 2 && name == "save.ram") { - return vfs::fs::file::open(path("Saves", gameBoy.location, ".sav"), mode); - } - - if(id == 2 && name == "time.rtc") { - return vfs::fs::file::open(path("Saves", gameBoy.location, ".sav"), mode); - } - - return {}; + return result; } auto Program::load(uint id, string name, string type, string_vector options) -> Emulator::Platform::Load { @@ -185,10 +58,10 @@ auto Program::load(uint id, string name, string type, string_vector options) -> dialog.setTitle("Load Super Nintendo"); dialog.setPath(path("Games", settings["Path/Recent/SuperNintendo"].text())); dialog.setFilters({string{"Super Nintendo Games|*.sfc:*.smc:*.zip"}}); - superNintendo.location = dialog.openFile(); + superNintendo.location = dialog.openObject(); } - if(file::exists(superNintendo.location)) { - settings["Path/Recent/SuperNintendo"].setValue(Location::path(superNintendo.location)); + if(inode::exists(superNintendo.location)) { + settings["Path/Recent/SuperNintendo"].setValue(Location::dir(superNintendo.location)); loadSuperNintendo(superNintendo.location); return {id, ""}; } @@ -202,10 +75,10 @@ auto Program::load(uint id, string name, string type, string_vector options) -> dialog.setTitle("Load Game Boy"); dialog.setPath(path("Games", settings["Path/Recent/GameBoy"].text())); dialog.setFilters({string{"Game Boy Games|*.gb:*.gbc:*.zip"}}); - gameBoy.location = dialog.openFile(); + gameBoy.location = dialog.openObject(); } - if(file::exists(gameBoy.location)) { - settings["Path/Recent/GameBoy"].setValue(Location::path(gameBoy.location)); + if(inode::exists(gameBoy.location)) { + settings["Path/Recent/GameBoy"].setValue(Location::dir(gameBoy.location)); loadGameBoy(gameBoy.location); return {id, ""}; } @@ -242,7 +115,7 @@ auto Program::videoRefresh(const uint32* data, uint pitch, uint width, uint heig current = chrono::timestamp(); if(current != previous) { previous = current; - statusText = {"FPS: ", frameCounter}; + statusText = {emulator->get("Mode").get(), "FPS: ", frameCounter}; frameCounter = 0; } } diff --git a/higan/target-bsnes/program/paths.cpp b/higan/target-bsnes/program/paths.cpp index ea30465f..e5386eda 100644 --- a/higan/target-bsnes/program/paths.cpp +++ b/higan/target-bsnes/program/paths.cpp @@ -22,17 +22,43 @@ auto Program::path(string type, string location, string extension) -> string { } } - if(type == "States") { - if(auto path = settings["Path/States"].text()) { - pathname = path; - } - } - if(type == "Cheats") { if(auto path = settings["Path/Cheats"].text()) { pathname = path; } } + if(type == "States") { + if(auto path = settings["Path/States"].text()) { + pathname = path; + } + } + return {pathname, prefix, suffix}; } + +auto Program::gamePath() -> string { + if(!emulator->loaded()) return ""; + if(gameBoy.location) return gameBoy.location; + return superNintendo.location; +} + +auto Program::cheatPath() -> string { + if(!emulator->loaded()) return ""; + auto location = gamePath(); + if(location.endsWith("/")) { + return {location, "cheats.bml"}; + } else { + return path("Cheats", location, ".cht"); + } +} + +auto Program::statePath() -> string { + if(!emulator->loaded()) return ""; + auto location = gamePath(); + if(location.endsWith("/")) { + return {location, "bsnes/states/"}; + } else { + return path("States", location, ".bsz"); + } +} diff --git a/higan/target-bsnes/program/program.cpp b/higan/target-bsnes/program/program.cpp index db4d1cf6..6ceb9c57 100644 --- a/higan/target-bsnes/program/program.cpp +++ b/higan/target-bsnes/program/program.cpp @@ -1,8 +1,10 @@ #include "../bsnes.hpp" #include "interface.cpp" #include "game.cpp" +#include "game-pak.cpp" +#include "game-rom.cpp" #include "paths.cpp" -#include "state.cpp" +#include "states.cpp" #include "utility.cpp" unique_pointer program; @@ -66,7 +68,7 @@ Program::Program(string_vector arguments) { for(auto& argument : arguments) { if(argument == "--fullscreen") { presentation->toggleFullscreenMode(); - } else if(file::exists(argument)) { + } else if(inode::exists(argument)) { gameQueue.append(argument); } } diff --git a/higan/target-bsnes/program/program.hpp b/higan/target-bsnes/program/program.hpp index 09209219..2a0c54d2 100644 --- a/higan/target-bsnes/program/program.hpp +++ b/higan/target-bsnes/program/program.hpp @@ -20,11 +20,21 @@ struct Program : Emulator::Platform { auto save() -> void; auto unload() -> void; + //game-pak.cpp + auto openPakSFC(string name, vfs::file::mode mode) -> vfs::shared::file; + auto openPakGB(string name, vfs::file::mode mode) -> vfs::shared::file; + + //game-rom.cpp + auto openRomSFC(string name, vfs::file::mode mode) -> vfs::shared::file; + auto openRomGB(string name, vfs::file::mode mode) -> vfs::shared::file; + //paths.cpp auto path(string type, string location, string extension = "") -> string; - - //state.cpp + auto gamePath() -> string; + auto cheatPath() -> string; auto statePath() -> string; + + //states.cpp auto loadState(string filename) -> bool; auto saveState(string filename) -> bool; auto saveRecoveryState() -> bool; @@ -35,12 +45,14 @@ struct Program : Emulator::Platform { auto initializeInputDriver() -> void; auto updateVideoShader() -> void; auto connectDevices() -> void; + auto applyHacks() -> void; auto showMessage(string text) -> void; auto updateMessage() -> void; auto focused() -> bool; public: struct SuperNintendo { + string label; string location; string manifest; Markup::Node document; diff --git a/higan/target-bsnes/program/state.cpp b/higan/target-bsnes/program/state.cpp deleted file mode 100644 index fe9008d5..00000000 --- a/higan/target-bsnes/program/state.cpp +++ /dev/null @@ -1,36 +0,0 @@ -auto Program::statePath() -> string { - if(!emulator->loaded()) return ""; - return path("States", superNintendo.location, ".bst/"); -} - -auto Program::loadState(string filename) -> bool { - if(!emulator->loaded()) return false; - string location = {statePath(), filename, ".bst"}; - string prefix = Location::prefix(location); - if(!file::exists(location)) return showMessage({"[", prefix, "] not found"}), false; - if(filename != "Quick/Recovery Slot") saveRecoveryState(); - auto memory = file::read(location); - serializer s{memory.data(), memory.size()}; - if(!emulator->unserialize(s)) return showMessage({"[", prefix, "] is in incompatible format"}), false; - return showMessage({"Loaded [", prefix, "]"}), true; -} - -auto Program::saveState(string filename) -> bool { - if(!emulator->loaded()) return false; - directory::create({statePath(), "Quick/"}); - string location = {statePath(), filename, ".bst"}; - string prefix = Location::prefix(location); - serializer s = emulator->serialize(); - if(!s.size()) return showMessage({"Failed to save [", prefix, "]"}), false; - if(!file::write(location, s.data(), s.size())) return showMessage({"Unable to write [", prefix, "] to disk"}), false; - return showMessage({"Saved [", prefix, "]"}), true; -} - -auto Program::saveRecoveryState() -> bool { - if(!emulator->loaded()) return false; - directory::create({statePath(), "Quick/"}); - serializer s = emulator->serialize(); - if(!s.size()) return false; - if(!file::write({statePath(), "Quick/Recovery Slot.bst"}, s.data(), s.size())) return false; - return true; -} diff --git a/higan/target-bsnes/program/states.cpp b/higan/target-bsnes/program/states.cpp new file mode 100644 index 00000000..616b2f6e --- /dev/null +++ b/higan/target-bsnes/program/states.cpp @@ -0,0 +1,80 @@ +auto Program::loadState(string filename) -> bool { + if(!emulator->loaded()) return false; + + string prefix = Location::file(filename); + vector memory; + + if(gamePath().endsWith("/")) { + string location = {statePath(), filename, ".bst"}; + if(!file::exists(location)) return showMessage({"[", prefix, "] not found"}), false; + if(filename != "quick/recovery") saveRecoveryState(); + memory = file::read(location); + } else { + string location = {filename, ".bst"}; + Decode::ZIP input; + if(input.open(statePath())) { + for(auto& file : input.file) { + if(file.name != location) continue; + memory = input.extract(file); + break; + } + } + } + + if(memory) { + serializer s{memory.data(), memory.size()}; + if(!emulator->unserialize(s)) return showMessage({"[", prefix, "] is in incompatible format"}), false; + return showMessage({"Loaded [", prefix, "]"}), true; + } else { + return showMessage({"[", prefix, "] not found"}), false; + } +} + +auto Program::saveState(string filename) -> bool { + if(!emulator->loaded()) return false; + + string prefix = Location::file(filename); + serializer s = emulator->serialize(); + if(!s.size()) return showMessage({"Failed to save [", prefix, "]"}), false; + + if(gamePath().endsWith("/")) { + string location = {statePath(), filename, ".bst"}; + directory::create(Location::path(location)); + if(!file::write(location, s.data(), s.size())) return showMessage({"Unable to write [", prefix, "] to disk"}), false; + } else { + string location = {filename, ".bst"}; + + struct State { + string name; + vector memory; + }; + vector states; + + Decode::ZIP input; + if(input.open(statePath())) { + for(auto& file : input.file) { + if(!file.name.endsWith(".bst")) continue; + if(file.name == location) continue; + states.append({file.name, input.extract(file)}); + } + } + + Encode::ZIP output{statePath()}; + for(auto& state : states) { + output.append(state.name, state.memory.data(), state.memory.size()); + } + output.append(location, s.data(), s.size()); + } + + return showMessage({"Saved [", prefix, "]"}), true; +} + +auto Program::saveRecoveryState() -> bool { + if(!emulator->loaded()) return false; + string location = {statePath(), "quick/recovery.bst"}; + directory::create(Location::path(location)); + serializer s = emulator->serialize(); + if(!s.size()) return false; + if(!file::write(location, s.data(), s.size())) return false; + return true; +} diff --git a/higan/target-bsnes/program/utility.cpp b/higan/target-bsnes/program/utility.cpp index cbb20c08..40df2358 100644 --- a/higan/target-bsnes/program/utility.cpp +++ b/higan/target-bsnes/program/utility.cpp @@ -65,6 +65,18 @@ auto Program::connectDevices() -> void { } } +auto Program::applyHacks() -> void { + bool fastPPU = settingsWindow->advanced.fastPPUOption.checked(); + bool fastDSP = settingsWindow->advanced.fastDSPOption.checked(); + + auto label = superNintendo.label; + if(label == "AIR STRIKE PATROL" || label == "DESERT FIGHTER") fastPPU = false; + if(label == "KOUSHIEN_2") fastDSP = false; + + emulator->set("Fast PPU", fastPPU); + emulator->set("Fast DSP", fastDSP); +} + auto Program::showMessage(string text) -> void { statusTime = chrono::timestamp(); statusMessage = text; diff --git a/higan/target-bsnes/settings/advanced.cpp b/higan/target-bsnes/settings/advanced.cpp index 8968377e..90536e1a 100644 --- a/higan/target-bsnes/settings/advanced.cpp +++ b/higan/target-bsnes/settings/advanced.cpp @@ -109,4 +109,12 @@ AdvancedSettings::AdvancedSettings(TabFrame* parent) : TabFrameItem(parent) { settings.save(); } }); + + emulatorLabel.setText("Emulator").setFont(Font().setBold()); + fastPPUOption.setText("Fast PPU").setChecked(settings["Emulator/FastPPU"].boolean()).onToggle([&] { + settings["Emulator/FastPPU"].setValue(fastPPUOption.checked()); + }); + fastDSPOption.setText("Fast DSP").setChecked(settings["Emulator/FastDSP"].boolean()).onToggle([&] { + settings["Emulator/FastDSP"].setValue(fastDSPOption.checked()); + }); } diff --git a/higan/target-bsnes/settings/paths.cpp b/higan/target-bsnes/settings/paths.cpp index c513b9d0..1699bbac 100644 --- a/higan/target-bsnes/settings/paths.cpp +++ b/higan/target-bsnes/settings/paths.cpp @@ -39,18 +39,6 @@ PathSettings::PathSettings(TabFrame* parent) : TabFrameItem(parent) { settings["Path/Saves"].setValue(""); refreshPaths(); }); - statesLabel.setText("States:"); - statesPath.setEditable(false); - statesAssign.setText("Assign ...").onActivate([&] { - if(auto location = BrowserDialog().setParent(*settingsWindow).selectFolder()) { - settings["Path/States"].setValue(location); - refreshPaths(); - } - }); - statesReset.setText("Reset").onActivate([&] { - settings["Path/States"].setValue(""); - refreshPaths(); - }); cheatsLabel.setText("Cheats:"); cheatsPath.setEditable(false); cheatsAssign.setText("Assign ...").onActivate([&] { @@ -63,6 +51,18 @@ PathSettings::PathSettings(TabFrame* parent) : TabFrameItem(parent) { settings["Path/Cheats"].setValue(""); refreshPaths(); }); + statesLabel.setText("States:"); + statesPath.setEditable(false); + statesAssign.setText("Assign ...").onActivate([&] { + if(auto location = BrowserDialog().setParent(*settingsWindow).selectFolder()) { + settings["Path/States"].setValue(location); + refreshPaths(); + } + }); + statesReset.setText("Reset").onActivate([&] { + settings["Path/States"].setValue(""); + refreshPaths(); + }); refreshPaths(); } @@ -83,14 +83,14 @@ auto PathSettings::refreshPaths() -> void { } else { savesPath.setText("").setForegroundColor({128, 128, 128}); } - if(auto location = settings["Path/States"].text()) { - statesPath.setText(location).setForegroundColor(); - } else { - statesPath.setText("").setForegroundColor({128, 128, 128}); - } if(auto location = settings["Path/Cheats"].text()) { cheatsPath.setText(location).setForegroundColor(); } else { cheatsPath.setText("").setForegroundColor({128, 128, 128}); } + if(auto location = settings["Path/States"].text()) { + statesPath.setText(location).setForegroundColor(); + } else { + statesPath.setText("").setForegroundColor({128, 128, 128}); + } } diff --git a/higan/target-bsnes/settings/settings.cpp b/higan/target-bsnes/settings/settings.cpp index 0a3294f2..2e77544a 100644 --- a/higan/target-bsnes/settings/settings.cpp +++ b/higan/target-bsnes/settings/settings.cpp @@ -43,8 +43,8 @@ Settings::Settings() { set("Path/Games", ""); set("Path/Patches", ""); set("Path/Saves", ""); - set("Path/States", ""); set("Path/Cheats", ""); + set("Path/States", ""); set("Path/Recent/SuperNintendo", Path::user()); set("Path/Recent/GameBoy", Path::user()); set("Path/Recent/BSMemory", Path::user()); @@ -52,6 +52,8 @@ Settings::Settings() { set("UserInterface/ShowStatusBar", true); + set("Emulator/FastPPU", true); + set("Emulator/FastDSP", true); set("Emulator/AutoSaveMemory/Enable", true); set("Emulator/AutoSaveMemory/Interval", 30); diff --git a/higan/target-bsnes/settings/settings.hpp b/higan/target-bsnes/settings/settings.hpp index cea008c4..74454607 100644 --- a/higan/target-bsnes/settings/settings.hpp +++ b/higan/target-bsnes/settings/settings.hpp @@ -90,16 +90,16 @@ public: LineEdit savesPath{&savesLayout, Size{~0, 0}}; Button savesAssign{&savesLayout, Size{80, 0}}; Button savesReset{&savesLayout, Size{80, 0}}; - HorizontalLayout statesLayout{&layout, Size{~0, 0}}; - Label statesLabel{&statesLayout, Size{55, 0}}; - LineEdit statesPath{&statesLayout, Size{~0, 0}}; - Button statesAssign{&statesLayout, Size{80, 0}}; - Button statesReset{&statesLayout, Size{80, 0}}; HorizontalLayout cheatsLayout{&layout, Size{~0, 0}}; Label cheatsLabel{&cheatsLayout, Size{55, 0}}; LineEdit cheatsPath{&cheatsLayout, Size{~0, 0}}; Button cheatsAssign{&cheatsLayout, Size{80, 0}}; Button cheatsReset{&cheatsLayout, Size{80, 0}}; + HorizontalLayout statesLayout{&layout, Size{~0, 0}}; + Label statesLabel{&statesLayout, Size{55, 0}}; + LineEdit statesPath{&statesLayout, Size{~0, 0}}; + Button statesAssign{&statesLayout, Size{80, 0}}; + Button statesReset{&statesLayout, Size{80, 0}}; }; struct AdvancedSettings : TabFrameItem { @@ -115,6 +115,9 @@ public: ComboButton audioDriverOption{&driverLayout, Size{~0, 0}}; Label inputDriverLabel{&driverLayout, Size{0, 0}}; ComboButton inputDriverOption{&driverLayout, Size{~0, 0}}; + Label emulatorLabel{&layout, Size{~0, 0}, 2}; + CheckLabel fastPPUOption{&layout, Size{~0, 0}}; + CheckLabel fastDSPOption{&layout, Size{~0, 0}}; }; struct SettingsWindow : Window { diff --git a/higan/target-bsnes/tools/cheat-editor.cpp b/higan/target-bsnes/tools/cheat-editor.cpp index 960c31fe..f3abf1fc 100644 --- a/higan/target-bsnes/tools/cheat-editor.cpp +++ b/higan/target-bsnes/tools/cheat-editor.cpp @@ -191,7 +191,7 @@ auto CheatEditor::removeCheats() -> void { auto CheatEditor::loadCheats() -> void { cheats.reset(); - auto location = program->path("Cheats", program->superNintendo.location, ".cht"); + auto location = program->cheatPath(); auto document = BML::unserialize(string::read(location)); for(auto cheat : document.find("cheat")) { cheats.append({cheat["name"].text(), cheat["code"].text(), (bool)cheat["enable"]}); @@ -211,7 +211,7 @@ auto CheatEditor::saveCheats() -> void { document.append(" enable\n"); document.append("\n"); } - auto location = program->path("Cheats", program->superNintendo.location, ".cht"); + auto location = program->cheatPath(); if(document) { file::write(location, document); } else { diff --git a/higan/target-bsnes/tools/state-manager.cpp b/higan/target-bsnes/tools/state-manager.cpp index 6b99c6b6..91b11cf2 100644 --- a/higan/target-bsnes/tools/state-manager.cpp +++ b/higan/target-bsnes/tools/state-manager.cpp @@ -48,7 +48,7 @@ auto StateWindow::doChange() -> void { || c == '|') valid = false; } if(auto input = nameValue.property("input")) { - if(name != input && file::exists({program->statePath(), name, ".bst"})) valid = false; + if(name != input && file::exists({program->statePath(), "managed/", name, ".bst"})) valid = false; } nameValue.setBackgroundColor(valid ? Color{} : Color{255, 224, 224}); acceptButton.setEnabled(valid); @@ -81,12 +81,12 @@ StateManager::StateManager(TabFrame* parent) : TabFrameItem(parent) { }); loadButton.setText("Load").onActivate([&] { if(auto item = stateList.selected()) { - program->loadState(item.cell(0).text()); + program->loadState({"managed/", item.cell(0).text()}); } }); saveButton.setText("Save").onActivate([&] { if(auto item = stateList.selected()) { - program->saveState(item.cell(0).text()); + program->saveState({"managed/", item.cell(0).text()}); } }); addButton.setText("Add").onActivate([&] { @@ -107,16 +107,33 @@ auto StateManager::loadStates() -> void { stateList.append(TableViewHeader().setVisible(false) .append(TableViewColumn().setExpandable()) ); - for(auto filename : directory::ifiles(program->statePath(), "*.bst")) { - stateList.append(TableViewItem() - .append(TableViewCell().setText(filename.trimRight(".bst", 1L))) - ); + if(program->gamePath().endsWith("/")) { + for(auto filename : directory::ifiles({program->statePath(), "managed/"}, "*.bst")) { + stateList.append(TableViewItem() + .append(TableViewCell().setText(filename.trimRight(".bst", 1L))) + ); + } + } else { + Decode::ZIP input; + if(input.open(program->statePath())) { + string_vector states; + for(auto& file : input.file) { + if(!file.name.match("managed/*.bst")) continue; + states.append(Location::prefix(file.name)); + } + states.isort(); + for(auto& state : states) { + stateList.append(TableViewItem() + .append(TableViewCell().setText(state)) + ); + } + } } stateList.resizeColumns().doChange(); } auto StateManager::createState(string name) -> void { - program->saveState(name); + program->saveState({"managed/", name}); loadStates(); for(auto item : stateList.items()) { if(item.cell(0).text() == name) item.setSelected(); @@ -126,8 +143,8 @@ auto StateManager::createState(string name) -> void { auto StateManager::modifyState(string name) -> void { if(auto item = stateList.selected()) { - string from = {program->statePath(), item.cell(0).text(), ".bst"}; - string to = {program->statePath(), name, ".bst"}; + string from = {program->statePath(), "managed/", item.cell(0).text(), ".bst"}; + string to = {program->statePath(), "managed/", name, ".bst"}; if(from != to) { file::rename(from, to); loadStates(); @@ -144,7 +161,7 @@ auto StateManager::removeStates() -> void { if(MessageDialog("Are you sure you want to permanently remove the selected state(s)?") .setParent(*toolsWindow).question() == "Yes") { for(auto item : batched) { - string location = {program->statePath(), item.cell(0).text(), ".bst"}; + string location = {program->statePath(), "managed/", item.cell(0).text(), ".bst"}; file::remove(location); } loadStates(); diff --git a/hiro/extension/browser-dialog.cpp b/hiro/extension/browser-dialog.cpp index dbe6e625..ed0f1f6c 100644 --- a/hiro/extension/browser-dialog.cpp +++ b/hiro/extension/browser-dialog.cpp @@ -36,28 +36,37 @@ private: auto BrowserDialogWindow::accept() -> void { auto batched = view.batched(); - if(state.action == "openFile" && batched) { - string name = batched.left()->cell(0)->text(); + if(state.action == "openFile" && batched.size() == 1) { + string name = batched[0].text(); if(isFolder(name)) return setPath({state.path, name}); response.selected.append(string{state.path, name}); } - if(state.action == "openFiles") { + if(state.action == "openFiles" && batched) { for(auto item : batched) { - string name = item->cell(0)->text(); - response.selected.append(string{state.path, name, isFolder(name) ? "/" : ""}); + string name = item.text(); + if(isFolder(name)) { + response.selected.reset(); + return; + } + response.selected.append(string{state.path, name}); } } - if(state.action == "openFolder" && batched) { - string name = batched.left()->cell(0)->text(); + if(state.action == "openFolder" && batched.size() == 1) { + string name = batched[0].text(); if(!isMatch(name)) return setPath({state.path, name}); response.selected.append(string{state.path, name, "/"}); } + if(state.action == "openObject" && batched.size() == 1) { + string name = batched[0].text(); + if(!isMatch(name) && isFolder(name)) return setPath({state.path, name}); + response.selected.append(string{state.path, name, isFolder(name) ? "/" : ""}); + } + if(state.action == "saveFile") { string name = fileName.text(); - if(!name && batched) name = batched.left()->cell(0)->text(); if(!name || isFolder(name)) return; if(file::exists({state.path, name})) { if(MessageDialog("File already exists. Overwrite it?").question() != "Yes") return; @@ -66,11 +75,11 @@ auto BrowserDialogWindow::accept() -> void { } if(state.action == "selectFolder") { - if(batched) { - string name = batched.left()->cell(0)->text(); - if(isFolder(name)) response.selected.append(string{state.path, name, "/"}); - } else { + if(!batched) { response.selected.append(state.path); + } else if(batched.size() == 1) { + string name = batched[0].text(); + if(isFolder(name)) response.selected.append(string{state.path, name, "/"}); } } @@ -79,16 +88,16 @@ auto BrowserDialogWindow::accept() -> void { //table view item double-clicked, or enter pressed on selected table view item auto BrowserDialogWindow::activate() -> void { - auto selectedItem = view.selected(); + auto batched = view.batched(); - if(state.action == "saveFile" && selectedItem) { - string name = selectedItem->cell(0)->text(); + if(state.action == "saveFile" && batched.size() == 1) { + string name = batched[0].text(); if(isFolder(name)) return setPath({state.path, name}); fileName.setText(name); } - if(state.action == "selectFolder" && selectedItem) { - string name = selectedItem->cell(0)->text(); + if(state.action == "selectFolder" && batched.size() == 1) { + string name = batched[0].text(); if(isFolder(name)) return setPath({state.path, name}); } @@ -97,13 +106,31 @@ auto BrowserDialogWindow::activate() -> void { //table view item changed auto BrowserDialogWindow::change() -> void { - fileName.setText(""); + auto batched = view.batched(); + if(state.action == "openFile") { + acceptButton.setEnabled(batched.size() == 1); + } + if(state.action == "openFiles") { + bool enabled = true; + for(auto item : batched) enabled &= !isFolder(item.text()); + if(batched.size() == 1 && isFolder(batched[0].text())) enabled = true; + acceptButton.setEnabled(enabled); + } + if(state.action == "openFolder") { + acceptButton.setEnabled(batched.size() == 1); + } + if(state.action == "openObject") { + acceptButton.setEnabled(batched.size() == 1); + } if(state.action == "saveFile") { - if(auto selectedItem = view.selected()) { - string name = selectedItem->cell(0)->text(); - if(!isFolder(name)) fileName.setText(name); + if(batched.size() == 1) { + string name = batched[0].text(); + if(!isFolder(name)) fileName.setText(name).doChange(); } } + if(state.action == "selectFolder") { + acceptButton.setEnabled(!batched || (batched.size() == 1 && isFolder(batched[0].text()))); + } } auto BrowserDialogWindow::isFolder(const string& name) -> bool { @@ -138,11 +165,15 @@ auto BrowserDialogWindow::run() -> BrowserDialog::Response { optionList.append(ComboButtonItem().setText(option)); } optionList.doChange(); //updates response.option to point to the default (first) option - fileName.setVisible(state.action == "saveFile").onActivate([&] { accept(); }); + fileName.setVisible(state.action == "saveFile").onActivate([&] { accept(); }).onChange([&] { + auto name = fileName.text(); + acceptButton.setEnabled(name && !isFolder(name)); + fileName.setBackgroundColor(acceptButton.enabled() ? Color{} : Color{255, 224, 224}); + }); acceptButton.onActivate([&] { accept(); }); - if(state.action == "openFile" || state.action == "openFiles" || state.action == "openFolder") acceptButton.setText("Open"); - if(state.action == "saveFile") acceptButton.setText("Save"); - if(state.action == "selectFolder") acceptButton.setText("Select"); + if(state.action.beginsWith("open")) acceptButton.setText("Open"); + if(state.action.beginsWith("save")) acceptButton.setText("Save"); + if(state.action.beginsWith("select")) acceptButton.setText("Select"); cancelButton.setText("Cancel").onActivate([&] { window.setModal(false); }); if(!state.filters) state.filters.append("All|*"); @@ -170,26 +201,32 @@ auto BrowserDialogWindow::setPath(string path) -> void { path.transform("\\", "/"); if((path || Path::root() == "/") && !path.endsWith("/")) path.append("/"); pathName.setText(state.path = path); - view.reset(); - auto contents = directory::icontents(path); - bool folderMode = state.action == "openFolder"; for(auto content : contents) { - if(!content.endsWith("/")) continue; - content.trimRight("/"); - if(folderMode && isMatch(content)) continue; - + bool isFolder = content.endsWith("/"); + if(isFolder) { + content.trimRight("/", 1L); + if(state.action == "openFolder" || state.action == "openObject") { + if(isMatch(content)) continue; + } + } else { + continue; + } view.append(ListViewItem().setText(content).setIcon(Icon::Emblem::Folder)); } for(auto content : contents) { - if(content.endsWith("/") != folderMode) continue; //file mode shows files; folder mode shows folders - content.trimRight("/"); + bool isFolder = content.endsWith("/"); + if(isFolder) { + content.trimRight("/", 1L); + if(state.action != "openFolder" && state.action != "openObject") continue; + } else { + if(state.action == "openFolder") continue; + } if(!isMatch(content)) continue; - - view.append(ListViewItem().setText(content).setIcon(folderMode ? Icon::Action::Open : Icon::Emblem::File)); + view.append(ListViewItem().setText(content).setIcon(isFolder ? Icon::Action::Open : Icon::Emblem::File)); } Application::processEvents(); @@ -223,6 +260,13 @@ auto BrowserDialog::openFolder() -> string { return {}; } +auto BrowserDialog::openObject() -> string { + state.action = "openObject"; + if(!state.title) state.title = "Open Object"; + if(auto result = _run()) return result.left(); + return {}; +} + auto BrowserDialog::option() -> string { return response.option; } diff --git a/hiro/extension/browser-dialog.hpp b/hiro/extension/browser-dialog.hpp index c078f1b9..ff150e5c 100644 --- a/hiro/extension/browser-dialog.hpp +++ b/hiro/extension/browser-dialog.hpp @@ -7,8 +7,9 @@ struct BrowserDialog { BrowserDialog(); auto openFile() -> string; //one existing file - auto openFiles() -> string_vector; //any existing files or folders + auto openFiles() -> string_vector; //any existing files auto openFolder() -> string; //one existing folder + auto openObject() -> string; //one existing file or folder auto option() -> string; auto saveFile() -> string; //one file auto selected() -> string_vector; diff --git a/hiro/gtk/widget/canvas.cpp b/hiro/gtk/widget/canvas.cpp index 3e42e804..91b643a5 100644 --- a/hiro/gtk/widget/canvas.cpp +++ b/hiro/gtk/widget/canvas.cpp @@ -10,11 +10,13 @@ GtkSelectionData* data, unsigned type, unsigned timestamp, pCanvas* p) -> void { p->self().doDrop(paths); } +//GTK3 static auto Canvas_draw(GtkWidget* widget, cairo_t* context, pCanvas* p) -> signed { p->_onDraw(context); return true; } +//GTK2 static auto Canvas_expose(GtkWidget* widget, GdkEventExpose* event, pCanvas* p) -> signed { p->_onExpose(event); return true; @@ -116,7 +118,8 @@ auto pCanvas::update() -> void { } auto pCanvas::_onDraw(cairo_t* context) -> void { - #if HIRO_GTK==3 + if(!surface) return; + int sx = 0, sy = 0, dx = 0, dy = 0; int width = surfaceWidth, height = surfaceHeight; auto geometry = pSizable::state().geometry; @@ -137,41 +140,19 @@ auto pCanvas::_onDraw(cairo_t* context) -> void { dy = 0; } - //TODO: support non-zero sx,sy - gdk_cairo_set_source_pixbuf(context, surface, dx, dy); + cairo_set_source_rgba(context, 0.0, 0.0, 0.0, 0.0); + cairo_paint(context); + gdk_cairo_set_source_pixbuf(context, surface, dx - sx, dy - sy); + cairo_rectangle(context, dx, dy, width, height); cairo_paint(context); - #endif } auto pCanvas::_onExpose(GdkEventExpose* expose) -> void { - #if HIRO_GTK==2 - if(surface == nullptr) return; + if(!surface) return; - int sx = 0, sy = 0, dx = 0, dy = 0; - int width = surfaceWidth; - int height = surfaceHeight; - auto geometry = pSizable::state().geometry; - - if(width <= geometry.width()) { - sx = 0; - dx = (geometry.width() - width) / 2; - } else { - sx = (width - geometry.width()) / 2; - dx = 0; - width = geometry.width(); - } - - if(height <= geometry.height()) { - sy = 0; - dy = (geometry.height() - height) / 2; - } else { - sy = (height - geometry.height()) / 2; - dy = 0; - height = geometry.height(); - } - - gdk_draw_pixbuf(gtk_widget_get_window(gtkWidget), nullptr, surface, sx, sy, dx, dy, width, height, GDK_RGB_DITHER_NONE, 0, 0); - #endif + cairo_t* context = gdk_cairo_create(gtk_widget_get_window(gtkWidget)); + _onDraw(context); + cairo_destroy(context); } auto pCanvas::_rasterize() -> void { diff --git a/nall/nall.hpp b/nall/nall.hpp index b25c52f4..fd7fac00 100644 --- a/nall/nall.hpp +++ b/nall/nall.hpp @@ -72,6 +72,7 @@ #include #include #include +#include #include #include #include