diff --git a/bsnes/nall/file.hpp b/bsnes/nall/file.hpp index 40453448..8447f3ab 100755 --- a/bsnes/nall/file.hpp +++ b/bsnes/nall/file.hpp @@ -23,6 +23,7 @@ namespace nall { enum class time : unsigned { create, modify, access }; static bool read(const string &filename, uint8_t *&data, unsigned &size) { + data = 0; file fp; if(fp.open(filename, mode::read) == false) return false; size = fp.size(); diff --git a/bsnes/nall/string/base.hpp b/bsnes/nall/string/base.hpp index ef331515..86c42c20 100755 --- a/bsnes/nall/string/base.hpp +++ b/bsnes/nall/string/base.hpp @@ -103,6 +103,9 @@ namespace nall { template inline lstring& qsplit(const char*, const char*); template inline lstring& iqsplit(const char*, const char*); + inline bool operator==(const lstring&) const; + inline bool operator!=(const lstring&) const; + lstring(); lstring(std::initializer_list); diff --git a/bsnes/nall/string/core.hpp b/bsnes/nall/string/core.hpp index 9903a6f7..e2af4eea 100755 --- a/bsnes/nall/string/core.hpp +++ b/bsnes/nall/string/core.hpp @@ -135,6 +135,19 @@ optional lstring::find(const char *key) const { return { false, 0 }; } +bool lstring::operator==(const lstring &source) const { + if(this == &source) return true; + if(size() != source.size()) return false; + for(unsigned n = 0; n < size(); n++) { + if(operator[](n) != source[n]) return false; + } + return true; +} + +bool lstring::operator!=(const lstring &source) const { + return !operator==(source); +} + inline lstring::lstring() { } diff --git a/bsnes/nes/nes.hpp b/bsnes/nes/nes.hpp index 8269c317..40280caa 100755 --- a/bsnes/nes/nes.hpp +++ b/bsnes/nes/nes.hpp @@ -4,7 +4,7 @@ namespace NES { namespace Info { static const char Name[] = "bnes"; - static const char Version[] = "000.12"; + static const char Version[] = "000.13"; } } diff --git a/bsnes/phoenix/core/core.cpp b/bsnes/phoenix/core/core.cpp index 726e0153..204996c3 100755 --- a/bsnes/phoenix/core/core.cpp +++ b/bsnes/phoenix/core/core.cpp @@ -290,6 +290,10 @@ void Window::setWidgetFont(const string &font) { return p.setWidgetFont(font); } +string Window::statusText() { + return state.statusText; +} + void Window::synchronizeLayout() { setGeometry(geometry()); } diff --git a/bsnes/phoenix/core/core.hpp b/bsnes/phoenix/core/core.hpp index 5c824d1c..14087c87 100755 --- a/bsnes/phoenix/core/core.hpp +++ b/bsnes/phoenix/core/core.hpp @@ -153,6 +153,7 @@ struct Window : private nall::base_from_member, Object { void setTitle(const nall::string &text); void setVisible(bool visible = true); void setWidgetFont(const nall::string &font); + nall::string statusText(); void synchronizeLayout(); Window(); diff --git a/bsnes/snes/interface/interface.cpp b/bsnes/snes/interface/interface.cpp index 25404c8d..5f6fba95 100755 --- a/bsnes/snes/interface/interface.cpp +++ b/bsnes/snes/interface/interface.cpp @@ -27,9 +27,38 @@ bool Interface::cartridgeLoaded() { return cartridge.loaded(); } -void Interface::loadCartridge(const string &xml, const uint8_t *data, unsigned size) { - cartridge.rom.copy(data, size); - cartridge.load(Cartridge::Mode::Normal, { xml }); +void Interface::loadCartridge(const CartridgeData &base) { + cartridge.rom.copy(base.data, base.size); + cartridge.load(Cartridge::Mode::Normal, { base.xml }); + system.power(); +} + +void Interface::loadSatellaviewSlottedCartridge(const CartridgeData &base, const CartridgeData &slot) { + cartridge.rom.copy(base.data, base.size); + if(slot.data) bsxflash.memory.copy(slot.data, slot.size); + cartridge.load(Cartridge::Mode::BsxSlotted, { base.xml, slot.xml }); + system.power(); +} + +void Interface::loadSatellaviewCartridge(const CartridgeData &base, const CartridgeData &slot) { + cartridge.rom.copy(base.data, base.size); + if(slot.data) bsxflash.memory.copy(slot.data, slot.size); + cartridge.load(Cartridge::Mode::Bsx, { base.xml, slot.xml }); + system.power(); +} + +void Interface::loadSufamiTurboCartridge(const CartridgeData &base, const CartridgeData &slotA, const CartridgeData &slotB) { + cartridge.rom.copy(base.data, base.size); + if(slotA.data) sufamiturbo.slotA.rom.copy(slotA.data, slotA.size); + if(slotB.data) sufamiturbo.slotB.rom.copy(slotB.data, slotB.size); + cartridge.load(Cartridge::Mode::SufamiTurbo, { base.xml, slotA.xml, slotB.xml }); + system.power(); +} + +void Interface::loadSuperGameBoyCartridge(const CartridgeData &base, const CartridgeData &slot) { + cartridge.rom.copy(base.data, base.size); + GameBoy::cartridge.load(slot.xml, slot.data, slot.size); + cartridge.load(Cartridge::Mode::SuperGameBoy, { base.xml, "" }); system.power(); } @@ -59,6 +88,10 @@ bool Interface::unserialize(serializer &s) { } void Interface::setCheats(const lstring &list) { + if(cartridge.mode() == Cartridge::Mode::SuperGameBoy) { + return icd2.setCheats(list); + } + cheat.reset(); foreach(code, list) { lstring codelist; diff --git a/bsnes/snes/interface/interface.hpp b/bsnes/snes/interface/interface.hpp index e94f75de..c91c98a6 100755 --- a/bsnes/snes/interface/interface.hpp +++ b/bsnes/snes/interface/interface.hpp @@ -8,8 +8,18 @@ public: virtual void connect(bool port, Input::Device device); + struct CartridgeData { + string xml; + const uint8_t *data; + unsigned size; + }; + virtual bool cartridgeLoaded(); - virtual void loadCartridge(const string &xml, const uint8_t *data, unsigned size); + virtual void loadCartridge(const CartridgeData &base); + virtual void loadSatellaviewSlottedCartridge(const CartridgeData &base, const CartridgeData &slot); + virtual void loadSatellaviewCartridge(const CartridgeData &base, const CartridgeData &slot); + virtual void loadSufamiTurboCartridge(const CartridgeData &base, const CartridgeData &slotA, const CartridgeData &slotB); + virtual void loadSuperGameBoyCartridge(const CartridgeData &base, const CartridgeData &slot); virtual void unloadCartridge(); virtual void power(); diff --git a/bsnes/snes/snes.hpp b/bsnes/snes/snes.hpp index abbfe616..31871ed9 100755 --- a/bsnes/snes/snes.hpp +++ b/bsnes/snes/snes.hpp @@ -4,7 +4,7 @@ namespace SNES { namespace Info { static const char Name[] = "bsnes"; - static const char Version[] = "082.16"; + static const char Version[] = "082.17"; static const unsigned SerializerVersion = 22; } } diff --git a/bsnes/ui/base.hpp b/bsnes/ui/base.hpp index d2b8326c..8a86ff50 100755 --- a/bsnes/ui/base.hpp +++ b/bsnes/ui/base.hpp @@ -30,9 +30,10 @@ using namespace ruby; struct Application { bool quit; + bool pause; bool autopause; - string realpath; + string basepath; string userpath; string title; diff --git a/bsnes/ui/config/config.cpp b/bsnes/ui/config/config.cpp index 837a94e8..b1b09412 100755 --- a/bsnes/ui/config/config.cpp +++ b/bsnes/ui/config/config.cpp @@ -14,7 +14,9 @@ Config::Config() { attach(input.driver = "", "Input::Driver"); attach(input.focusPolicy = 1, "Input::FocusPolicy"); - attach(path.last = application->realpath, "Path::Recent"); + attach(path.bios.satellaview = "", "Path::BIOS::Satellaview"); + attach(path.bios.sufamiTurbo = "", "Path::BIOS::SufamiTurbo"); + attach(path.bios.superGameBoy = "", "Path::BIOS::SuperGameBoy"); attach(nes.controllerPort1Device = 1, "NES::Controller::Port1"); attach(nes.controllerPort2Device = 0, "NES::Controller::Port2"); diff --git a/bsnes/ui/config/config.hpp b/bsnes/ui/config/config.hpp index 23f1d0eb..3bacf588 100755 --- a/bsnes/ui/config/config.hpp +++ b/bsnes/ui/config/config.hpp @@ -18,7 +18,11 @@ struct Config : public configuration { } input; struct Path { - string last; + struct BIOS { + string satellaview; + string sufamiTurbo; + string superGameBoy; + } bios; } path; struct NES { diff --git a/bsnes/ui/general/file-browser.cpp b/bsnes/ui/general/file-browser.cpp index 01d738a7..69434ddf 100755 --- a/bsnes/ui/general/file-browser.cpp +++ b/bsnes/ui/general/file-browser.cpp @@ -27,13 +27,13 @@ FileBrowser::FileBrowser() { }; pathBrowse.onTick = [&] { - string path = OS::folderSelect(*this, activePath); + string path = OS::folderSelect(*this, mode->path); if(path != "") setPath(path); }; pathUp.onTick = [&] { - if(activePath == "/") return; - string path = activePath; + if(mode->path == "/") return; + string path = mode->path; path.rtrim<1>("/"); path = dir(path); setPath(path); @@ -41,18 +41,36 @@ FileBrowser::FileBrowser() { fileList.onActivate = openButton.onTick = { &FileBrowser::fileListActivate, this }; - setPath(config->path.last); + filterModes[Mode::Default ] = { "Default", "", { "*" } }; + filterModes[Mode::NES ] = { "NES", "", { "*.nes" } }; + filterModes[Mode::SNES ] = { "SNES", "", { "*.sfc" } }; + filterModes[Mode::GameBoy ] = { "GameBoy", "", { "*.gb", "*.gbc" } }; + filterModes[Mode::Satellaview] = { "Satellaview", "", { "*.bs" } }; + filterModes[Mode::SufamiTurbo] = { "SufamiTurbo", "", { "*.st" } }; + mode = &filterModes[Mode::Default]; + + foreach(mode, filterModes) config.attach(mode.path, mode.name); + config.load(string{ application->userpath, "paths.cfg" }); + config.save(string{ application->userpath, "paths.cfg" }); } -void FileBrowser::open(const string &title, const lstring &filterList, function callback) { - this->callback = callback; - this->filterList = filterList; +FileBrowser::~FileBrowser() { + config.save(string{ application->userpath, "paths.cfg" }); +} + +void FileBrowser::open(const string &title, unsigned requestedMode, function requestedCallback) { + callback = requestedCallback; + if(mode == &filterModes[requestedMode]) { + setVisible(); + return; + } + mode = &filterModes[requestedMode]; setTitle(title); - setPath(activePath); + setPath(mode->path); string filterText = "Files of type: "; - foreach(filter, filterList) filterText.append(filter, ", "); + foreach(filter, mode->filter) filterText.append(filter, ", "); filterText.trim<1>(", "); filterLabel.setText(filterText); @@ -60,9 +78,9 @@ void FileBrowser::open(const string &title, const lstring &filterList, function< } void FileBrowser::setPath(const string &path) { - config->path.last = path; - activePath = path; - pathEdit.setText(activePath); + mode->path = path; + if(mode->path == "") mode->path = application->basepath; + pathEdit.setText(mode->path); fileList.reset(); fileNameList.reset(); @@ -71,8 +89,11 @@ void FileBrowser::setPath(const string &path) { foreach(fileName, contentsList) { if(fileName.endswith("/")) { fileNameList.append(fileName); - } else foreach(filter, filterList) { - if(fileName.wildcard(filter)) fileNameList.append(fileName); + } else foreach(filter, mode->filter) { + if(fileName.wildcard(filter)) { + fileNameList.append(fileName); + break; + } } } @@ -84,8 +105,39 @@ void FileBrowser::setPath(const string &path) { void FileBrowser::fileListActivate() { unsigned selection = fileList.selection(); string fileName = fileNameList[selection]; - if(fileName.endswith("/")) return setPath({ activePath, fileName }); + if(fileName.endswith("/")) { + if(loadFolder({ mode->path, fileName })) return; + return setPath({ mode->path, fileName }); + } + loadFile({ mode->path, fileName }); +} - if(callback) callback({ activePath, fileName }); +bool FileBrowser::loadFolder(const string &requestedPath) { + bool accept = false; + string path = requestedPath; + path.rtrim<1>("/"); + foreach(filter, mode->filter) { + if(path.wildcard(filter)) accept = true; + } + if(accept == false) return false; + + lstring contentsList = directory::contents(requestedPath); + lstring fileNameList; + foreach(fileName, contentsList) { + foreach(filter, mode->filter) { + if(fileName.wildcard(filter)) { + fileNameList.append(fileName); + break; + } + } + } + + if(fileNameList.size() != 1) return false; + loadFile({ requestedPath, fileNameList[0] }); + return true; +} + +void FileBrowser::loadFile(const string &filename) { + if(callback) callback(filename); setVisible(false); } diff --git a/bsnes/ui/general/file-browser.hpp b/bsnes/ui/general/file-browser.hpp index 16ef37e3..c261ce7e 100755 --- a/bsnes/ui/general/file-browser.hpp +++ b/bsnes/ui/general/file-browser.hpp @@ -9,18 +9,28 @@ struct FileBrowser : Window { Label filterLabel; Button openButton; - void open(const string &title, const lstring &filterList, function callback); + struct Mode { enum : unsigned { Default, NES, SNES, GameBoy, Satellaview, SufamiTurbo }; }; + void open(const string &title, unsigned mode, function callback); FileBrowser(); + ~FileBrowser(); private: - string activePath; - lstring filterList; + configuration config; + struct FilterMode { + string name; + string path; + lstring filter; + } *mode; + linear_vector filterModes; + lstring fileNameList; function callback; void setPath(const string &path); void fileListActivate(); + bool loadFolder(const string &path); + void loadFile(const string &filename); }; extern FileBrowser *fileBrowser; diff --git a/bsnes/ui/general/general.cpp b/bsnes/ui/general/general.cpp index 1c06a519..50e7f351 100755 --- a/bsnes/ui/general/general.cpp +++ b/bsnes/ui/general/general.cpp @@ -1,3 +1,4 @@ #include "../base.hpp" #include "main-window.cpp" #include "file-browser.cpp" +#include "slot-loader.cpp" diff --git a/bsnes/ui/general/general.hpp b/bsnes/ui/general/general.hpp index 737238df..a0f43444 100755 --- a/bsnes/ui/general/general.hpp +++ b/bsnes/ui/general/general.hpp @@ -1,2 +1,3 @@ #include "main-window.hpp" #include "file-browser.hpp" +#include "slot-loader.hpp" diff --git a/bsnes/ui/general/main-window.cpp b/bsnes/ui/general/main-window.cpp index d93f910e..54010bcb 100755 --- a/bsnes/ui/general/main-window.cpp +++ b/bsnes/ui/general/main-window.cpp @@ -10,6 +10,10 @@ MainWindow::MainWindow() { cartridgeLoadSNES.setText("Load SNES Cartridge ..."); cartridgeLoadNES.setText("Load NES Cartridge ..."); cartridgeLoadGameBoy.setText("Load Game Boy Cartridge ..."); + cartridgeLoadSatellaviewSlotted.setText("Load Satellaview-Slotted Cartridge ..."); + cartridgeLoadSatellaview.setText("Load Satellaview Cartridge ..."); + cartridgeLoadSufamiTurbo.setText("Load Sufami Turbo Cartridge ..."); + cartridgeLoadSuperGameBoy.setText("Load Super Game Boy Cartridge ..."); nesMenu.setText("NES"); nesPower.setText("Power Cycle"); @@ -86,13 +90,15 @@ MainWindow::MainWindow() { toolsStateManager.setText("State Manager ..."); toolsTest.setText("Test"); - helpMenu.setText("Help"); - helpAbout.setText("About ..."); - append(cartridgeMenu); cartridgeMenu.append(cartridgeLoadNES); cartridgeMenu.append(cartridgeLoadSNES); cartridgeMenu.append(cartridgeLoadGameBoy); + cartridgeMenu.append(cartridgeSeparator); + cartridgeMenu.append(cartridgeLoadSatellaviewSlotted); + cartridgeMenu.append(cartridgeLoadSatellaview); + cartridgeMenu.append(cartridgeLoadSufamiTurbo); + cartridgeMenu.append(cartridgeLoadSuperGameBoy); append(nesMenu); nesMenu.append(nesPower); @@ -167,9 +173,6 @@ MainWindow::MainWindow() { toolsMenu.append(toolsSeparator3); toolsMenu.append(toolsTest); - append(helpMenu); - helpMenu.append(helpAbout); - setMenuVisible(); setStatusText("No cartridge loaded"); @@ -182,23 +185,28 @@ MainWindow::MainWindow() { onSize = [&] { utility->resizeMainWindow(); }; cartridgeLoadNES.onTick = [&] { - fileBrowser->open("Load NES Cartridge", { "*.nes" }, [](string filename) { - interface->loadCartridge(filename); + fileBrowser->open("Load Cartridge - NES", FileBrowser::Mode::NES, [](string filename) { + interface->nes.loadCartridge(filename); }); }; cartridgeLoadSNES.onTick = [&] { - fileBrowser->open("Load SNES Cartridge", { "*.sfc" }, [](string filename) { - interface->loadCartridge(filename); + fileBrowser->open("Load Cartridge - SNES", FileBrowser::Mode::SNES, [](string filename) { + interface->snes.loadCartridge(filename); }); }; cartridgeLoadGameBoy.onTick = [&] { - fileBrowser->open("Load Game Boy Cartridge", { "*.gb", "*.gbc" }, [](string filename) { - interface->loadCartridge(filename); + fileBrowser->open("Load Cartridge - Game Boy", FileBrowser::Mode::GameBoy, [](string filename) { + interface->gameBoy.loadCartridge(filename); }); }; + cartridgeLoadSatellaviewSlotted.onTick = [&] { slotLoader->loadSatellaviewSlotted(); }; + cartridgeLoadSatellaview.onTick = [&] { slotLoader->loadSatellaview(); }; + cartridgeLoadSufamiTurbo.onTick = [&] { slotLoader->loadSufamiTurbo(); }; + cartridgeLoadSuperGameBoy.onTick = [&] { slotLoader->loadSuperGameBoy(); }; + nesPower.onTick = { &Interface::power, interface }; nesReset.onTick = { &Interface::reset, interface }; @@ -276,19 +284,11 @@ MainWindow::MainWindow() { NES::cpu.trace = toolsTest.checked(); }; - helpAbout.onTick = [&] { - MessageWindow::information(*this, { - application->title, "\n\n", - "Author: byuu\n", - "Website: http://byuu.org/" - }); - }; - synchronize(); } void MainWindow::synchronize() { - if(interface->loaded()) { + if(interface->cartridgeLoaded()) { toolsStateSave.setEnabled(true); toolsStateLoad.setEnabled(true); } else { diff --git a/bsnes/ui/general/main-window.hpp b/bsnes/ui/general/main-window.hpp index b7a2bedd..5ed765d3 100755 --- a/bsnes/ui/general/main-window.hpp +++ b/bsnes/ui/general/main-window.hpp @@ -6,6 +6,11 @@ struct MainWindow : Window { Item cartridgeLoadSNES; Item cartridgeLoadNES; Item cartridgeLoadGameBoy; + Separator cartridgeSeparator; + Item cartridgeLoadSatellaviewSlotted; + Item cartridgeLoadSatellaview; + Item cartridgeLoadSufamiTurbo; + Item cartridgeLoadSuperGameBoy; Menu nesMenu; Item nesPower; @@ -67,9 +72,6 @@ struct MainWindow : Window { Separator toolsSeparator3; CheckItem toolsTest; - Menu helpMenu; - Item helpAbout; - void synchronize(); MainWindow(); diff --git a/bsnes/ui/general/slot-loader.cpp b/bsnes/ui/general/slot-loader.cpp new file mode 100755 index 00000000..a2a329b6 --- /dev/null +++ b/bsnes/ui/general/slot-loader.cpp @@ -0,0 +1,185 @@ +SlotLoader *slotLoader = 0; + +SlotLoaderPath::SlotLoaderPath() { + browse.setText("Browse ..."); + append(label, 40, 0, 5); + append(path, ~0, 0, 5); + append(browse, 80, 0); +} + +SlotLoader::SlotLoader() { + layout.setMargin(5); + base.label.setText("Base:"); + slot[0].label.setText("Slot:"); + slot[1].label.setText("Slot:"); + loadButton.setText("Load"); + + append(layout); + layout.append(base, ~0, 0, 5); + layout.append(slot[0], ~0, 0, 5); + layout.append(slot[1], ~0, 0, 5); + layout.append(controlLayout, ~0, 0); + controlLayout.append(spacer, ~0, 0); + controlLayout.append(loadButton, 80, 0); + + setGeometry({ 128, 128, 500, layout.minimumGeometry().height }); + windowManager->append(this, "SlotLoader"); +} + +void SlotLoader::synchronize() { + loadButton.setEnabled(base.path.text() != ""); +} + +void SlotLoader::loadSatellaviewSlotted() { + setTitle("Load Cartridge - Satellaview-Slotted"); + + base.path.setText(""); + + slot[0].path.setText(""); + slot[0].path.setEnabled(true); + slot[0].browse.setEnabled(true); + + slot[1].path.setText(""); + slot[1].path.setEnabled(false); + slot[1].browse.setEnabled(false); + + base.browse.onTick = [&] { + fileBrowser->open("Load Cartridge - SNES", FileBrowser::Mode::SNES, [&](const string &filename) { + base.path.setText(filename); + synchronize(); + }); + }; + + slot[0].browse.onTick = [&] { + fileBrowser->open("Load Cartridge - Satellaview", FileBrowser::Mode::Satellaview, [&](const string &filename) { + slot[0].path.setText(filename); + synchronize(); + }); + }; + + loadButton.onTick = [&] { + this->setVisible(false); + interface->snes.loadSatellaviewSlottedCartridge(base.path.text(), slot[0].path.text()); + }; + + synchronize(); + setVisible(); +} + +void SlotLoader::loadSatellaview() { + setTitle("Load Cartridge - Satellaview"); + + base.path.setText(config->path.bios.satellaview); + + slot[0].path.setText(""); + slot[0].path.setEnabled(true); + slot[0].browse.setEnabled(true); + + slot[1].path.setText(""); + slot[1].path.setEnabled(false); + slot[1].browse.setEnabled(false); + + base.browse.onTick = [&] { + fileBrowser->open("Load BIOS - Satellaview", FileBrowser::Mode::SNES, [&](const string &filename) { + config->path.bios.satellaview = filename; + base.path.setText(filename); + synchronize(); + }); + }; + + slot[0].browse.onTick = [&] { + fileBrowser->open("Load Cartridge - Satellaview", FileBrowser::Mode::Satellaview, [&](const string &filename) { + slot[0].path.setText(filename); + synchronize(); + }); + }; + + loadButton.onTick = [&] { + this->setVisible(false); + interface->snes.loadSatellaviewCartridge(base.path.text(), slot[0].path.text()); + }; + + synchronize(); + setVisible(); +} + +void SlotLoader::loadSufamiTurbo() { + setTitle("Load Cartridge - Sufami Turbo"); + + base.path.setText(config->path.bios.sufamiTurbo); + + slot[0].path.setText(""); + slot[0].path.setEnabled(true); + slot[0].browse.setEnabled(true); + + slot[1].path.setText(""); + slot[1].path.setEnabled(true); + slot[1].browse.setEnabled(true); + + base.browse.onTick = [&] { + fileBrowser->open("Load BIOS - Sufami Turbo", FileBrowser::Mode::SNES, [&](const string &filename) { + config->path.bios.sufamiTurbo = filename; + base.path.setText(filename); + synchronize(); + }); + }; + + slot[0].browse.onTick = [&] { + fileBrowser->open("Load Cartridge - Sufami Turbo", FileBrowser::Mode::SufamiTurbo, [&](const string &filename) { + slot[0].path.setText(filename); + synchronize(); + }); + }; + + slot[1].browse.onTick = [&] { + fileBrowser->open("Load Cartridge - Sufami Turbo", FileBrowser::Mode::SufamiTurbo, [&](const string &filename) { + slot[1].path.setText(filename); + synchronize(); + }); + }; + + loadButton.onTick = [&] { + this->setVisible(false); + interface->snes.loadSufamiTurboCartridge(base.path.text(), slot[0].path.text(), slot[1].path.text()); + }; + + synchronize(); + setVisible(); +} + +void SlotLoader::loadSuperGameBoy() { + setTitle("Load Cartridge - Super Game Boy"); + + base.path.setText(config->path.bios.superGameBoy); + + slot[0].path.setText(""); + slot[0].path.setEnabled(true); + slot[0].browse.setEnabled(true); + + slot[1].path.setText(""); + slot[1].path.setEnabled(false); + slot[1].browse.setEnabled(false); + + base.browse.onTick = [&] { + fileBrowser->open("Load BIOS - Super Game Boy", FileBrowser::Mode::SNES, [&](const string &filename) { + config->path.bios.superGameBoy = filename; + base.path.setText(filename); + synchronize(); + }); + }; + + slot[0].browse.onTick = [&] { + fileBrowser->open("Load Cartridge - Game Boy", FileBrowser::Mode::GameBoy, [&](const string &filename) { + slot[0].path.setText(filename); + synchronize(); + }); + }; + + loadButton.onTick = [&] { + this->setVisible(false); + interface->snes.loadSuperGameBoyCartridge(base.path.text(), slot[0].path.text()); + }; + + synchronize(); + setVisible(); +} diff --git a/bsnes/ui/general/slot-loader.hpp b/bsnes/ui/general/slot-loader.hpp new file mode 100755 index 00000000..d86cb0e5 --- /dev/null +++ b/bsnes/ui/general/slot-loader.hpp @@ -0,0 +1,29 @@ +struct SlotLoaderPath : HorizontalLayout { + Label label; + LineEdit path; + Button browse; + + string name; + lstring filter; + + SlotLoaderPath(); +}; + +struct SlotLoader : Window { + VerticalLayout layout; + SlotLoaderPath base; + SlotLoaderPath slot[2]; + HorizontalLayout controlLayout; + Widget spacer; + Button loadButton; + + void synchronize(); + void loadSatellaviewSlotted(); + void loadSatellaview(); + void loadSufamiTurbo(); + void loadSuperGameBoy(); + + SlotLoader(); +}; + +extern SlotLoader *slotLoader; diff --git a/bsnes/ui/input/user-interface.cpp b/bsnes/ui/input/user-interface.cpp index bd865a05..02cc6796 100755 --- a/bsnes/ui/input/user-interface.cpp +++ b/bsnes/ui/input/user-interface.cpp @@ -3,6 +3,10 @@ void HotkeyGeneral::inputEvent(int16_t scancode, int16_t value) { utility->toggleFullScreen(); } + if(scancode == pause.scancode && value) { + application->pause = !application->pause; + } + if(scancode == turboMode.scancode) { static bool Vsync, Async; if(value) { @@ -20,13 +24,16 @@ void HotkeyGeneral::inputEvent(int16_t scancode, int16_t value) { HotkeyGeneral::HotkeyGeneral() { name = "General"; - toggleFullScreen.name = "Toggle Fullscreen"; - turboMode.name = "Turbo Mode"; + toggleFullScreen.name = "Toggle fullscreen"; + pause.name = "Pause emulation"; + turboMode.name = "Turbo mode"; toggleFullScreen.mapping = "KB0::F11"; + pause.mapping = "KB0::P"; turboMode.mapping = "KB0::Tilde"; append(toggleFullScreen); + append(pause); append(turboMode); } diff --git a/bsnes/ui/input/user-interface.hpp b/bsnes/ui/input/user-interface.hpp index 24e60b3c..328b26b9 100755 --- a/bsnes/ui/input/user-interface.hpp +++ b/bsnes/ui/input/user-interface.hpp @@ -1,5 +1,6 @@ struct HotkeyGeneral : TertiaryInput { DigitalInput toggleFullScreen; + DigitalInput pause; DigitalInput turboMode; void inputEvent(int16_t scancode, int16_t value); diff --git a/bsnes/ui/interface/gameboy.cpp b/bsnes/ui/interface/gameboy.cpp index 1442448b..c65377ce 100755 --- a/bsnes/ui/interface/gameboy.cpp +++ b/bsnes/ui/interface/gameboy.cpp @@ -3,11 +3,14 @@ bool InterfaceGameBoy::loadCartridge(const string &filename) { unsigned size; if(file::read(filename, data, size) == false) return false; + interface->unloadCartridge(); interface->baseName = nall::basename(filename); + GameBoyCartridge info(data, size); GameBoy::Interface::loadCartridge(info.xml, data, size); - delete[] data; + + interface->loadCartridge(::Interface::Mode::GameBoy); return true; } diff --git a/bsnes/ui/interface/interface.cpp b/bsnes/ui/interface/interface.cpp index de107953..b36bfee4 100755 --- a/bsnes/ui/interface/interface.cpp +++ b/bsnes/ui/interface/interface.cpp @@ -25,7 +25,7 @@ void Interface::setController(unsigned port, unsigned device) { } } -bool Interface::loaded() { +bool Interface::cartridgeLoaded() { switch(mode()) { case Mode::NES: return nes.cartridgeLoaded(); case Mode::SNES: return snes.cartridgeLoaded(); @@ -34,32 +34,28 @@ bool Interface::loaded() { return false; } +void Interface::loadCartridge(Mode mode) { + utility->setMode(this->mode = mode); + bindControllers(); + cheatEditor->load({ baseName, ".cht" }); + stateManager->load({ baseName, ".bsa" }, 0u); + utility->showMessage({ "Loaded ", notdir(baseName) }); +} + bool Interface::loadCartridge(const string &filename) { bool result = false; - setCheatCodes(); - unloadCartridge(); - if(filename.endswith(".nes")) result = loadCartridgeNES(filename); - if(filename.endswith(".sfc")) result = loadCartridgeSNES(filename); - if(filename.endswith(".gb" )) result = loadCartridgeGameBoy(filename); - if(filename.endswith(".gbc")) result = loadCartridgeGameBoy(filename); - if(result == true) { - bindControllers(); - cheatEditor->load({ baseName, ".cht" }); - stateManager->load({ baseName, ".bsa" }, 0u); - } + if(filename.endswith(".nes")) result = nes.loadCartridge(filename); + if(filename.endswith(".sfc")) result = snes.loadCartridge(filename); + if(filename.endswith(".gb" )) result = gameBoy.loadCartridge(filename); + if(filename.endswith(".gbc")) result = gameBoy.loadCartridge(filename); return result; } -bool Interface::loadCartridgeNES(const string &filename) { - if(nes.loadCartridge(filename) == false) return false; - utility->setMode(mode = Mode::NES); - return true; -} - void Interface::unloadCartridge() { - if(loaded() == false) return; + if(cartridgeLoaded() == false) return; cheatEditor->save({ baseName, ".cht" }); stateManager->save({ baseName, ".bsa" }, 0u); + setCheatCodes(); switch(mode()) { case Mode::NES: nes.unloadCartridge(); break; @@ -70,33 +66,6 @@ void Interface::unloadCartridge() { utility->setMode(mode = Mode::None); } -void Interface::unloadCartridgeNES() { - nes.unloadCartridge(); - utility->setMode(mode = Mode::None); -} - -bool Interface::loadCartridgeSNES(const string &filename) { - if(snes.loadCartridge(filename) == false) return false; - utility->setMode(mode = Mode::SNES); - return true; -} - -void Interface::unloadCartridgeSNES() { - snes.unloadCartridge(); - utility->setMode(mode = Mode::None); -} - -bool Interface::loadCartridgeGameBoy(const string &filename) { - if(gameBoy.loadCartridge(filename) == false) return false; - utility->setMode(mode = Mode::GameBoy); - return true; -} - -void Interface::unloadCartridgeGameBoy() { - gameBoy.unloadCartridge(); - utility->setMode(mode = Mode::None); -} - void Interface::power() { switch(mode()) { case Mode::NES: return nes.power(); @@ -138,19 +107,23 @@ bool Interface::unserialize(serializer &s) { } bool Interface::saveState(const string &filename) { + bool result = false; switch(mode()) { - case Mode::SNES: return snes.saveState(filename); - case Mode::GameBoy: return gameBoy.saveState(filename); + case Mode::SNES: result = snes.saveState(filename); break; + case Mode::GameBoy: result = gameBoy.saveState(filename); break; } - return false; + utility->showMessage(result == true ? "Saved state" : "Failed to save state"); + return result; } bool Interface::loadState(const string &filename) { + bool result = false; switch(mode()) { - case Mode::SNES: return snes.loadState(filename); - case Mode::GameBoy: return gameBoy.loadState(filename); + case Mode::SNES: result = snes.loadState(filename); break; + case Mode::GameBoy: result = gameBoy.loadState(filename); break; } - return false; + utility->showMessage(result == true ? "Loaded state" : "Failed to load state"); + return result; } void Interface::setCheatCodes(const lstring &list) { @@ -178,7 +151,7 @@ void Interface::videoRefresh() { time(¤t); if(current != previous) { previous = current; - mainWindow->setStatusText({ "FPS: ", frameCounter }); + utility->setStatusText({ "FPS: ", frameCounter }); frameCounter = 0; } } diff --git a/bsnes/ui/interface/interface.hpp b/bsnes/ui/interface/interface.hpp index 5cb51fc7..95c1b258 100755 --- a/bsnes/ui/interface/interface.hpp +++ b/bsnes/ui/interface/interface.hpp @@ -9,17 +9,10 @@ struct Interface : property { void bindControllers(); void setController(unsigned port, unsigned device); - bool loaded(); - - bool loadCartridge(const string &filename); - bool loadCartridgeNES(const string &filename); - bool loadCartridgeSNES(const string &filename); - bool loadCartridgeGameBoy(const string &filename); - + bool cartridgeLoaded(); + void loadCartridge(Mode mode); + bool loadCartridge(const string &filename); //auto-detect system-type based on file extension void unloadCartridge(); - void unloadCartridgeNES(); - void unloadCartridgeSNES(); - void unloadCartridgeGameBoy(); void power(); void reset(); @@ -38,7 +31,6 @@ struct Interface : property { string baseName; // = "/path/to/cartridge" (no extension) -private: InterfaceNES nes; InterfaceSNES snes; InterfaceGameBoy gameBoy; diff --git a/bsnes/ui/interface/nes.cpp b/bsnes/ui/interface/nes.cpp index 8d7d357b..6487fd4e 100755 --- a/bsnes/ui/interface/nes.cpp +++ b/bsnes/ui/interface/nes.cpp @@ -17,10 +17,13 @@ bool InterfaceNES::loadCartridge(const string &filename) { filemap fp; if(fp.open(filename, filemap::mode::read) == false) return false; + interface->unloadCartridge(); interface->baseName = nall::basename(filename); - NES::Interface::loadCartridge("", fp.data(), fp.size()); + NES::Interface::loadCartridge("", fp.data(), fp.size()); fp.close(); + + interface->loadCartridge(::Interface::Mode::NES); return true; } diff --git a/bsnes/ui/interface/snes.cpp b/bsnes/ui/interface/snes.cpp index 2d2161bd..e554b71d 100755 --- a/bsnes/ui/interface/snes.cpp +++ b/bsnes/ui/interface/snes.cpp @@ -26,11 +26,95 @@ bool InterfaceSNES::loadCartridge(const string &filename) { unsigned size; if(file::read(filename, data, size) == false) return false; + interface->unloadCartridge(); interface->baseName = nall::basename(filename); - string xml = SNESCartridge(data, size).xmlMemoryMap; - SNES::Interface::loadCartridge(xml, data, size); + string xml = SNESCartridge(data, size).xmlMemoryMap; + SNES::Interface::loadCartridge({ xml, data, size }); delete[] data; + + interface->loadCartridge(::Interface::Mode::SNES); + return true; +} + +bool InterfaceSNES::loadSatellaviewSlottedCartridge(const string &basename, const string &slotname) { + uint8_t *data[2]; + unsigned size[2]; + if(file::read(basename, data[0], size[0]) == false) return false; + file::read(slotname, data[1], size[1]); + + interface->unloadCartridge(); + interface->baseName = nall::basename(basename); + if(data[1]) interface->baseName.append("+", nall::basename(notdir(slotname))); + + string xml = SNESCartridge(data[0], size[0]).xmlMemoryMap; + SNES::Interface::loadSatellaviewSlottedCartridge({ xml, data[0], size[0] }, { "", data[1], size[1] }); + delete[] data[0]; + if(data[1]) delete[] data[1]; + + interface->loadCartridge(::Interface::Mode::SNES); + return true; +} + +bool InterfaceSNES::loadSatellaviewCartridge(const string &basename, const string &slotname) { + uint8_t *data[2]; + unsigned size[2]; + if(file::read(basename, data[0], size[0]) == false) return false; + file::read(slotname, data[1], size[1]); + + interface->unloadCartridge(); + interface->baseName = nall::basename(basename); + if(data[1]) interface->baseName.append("+", nall::basename(notdir(slotname))); + + string xml = SNESCartridge(data[0], size[0]).xmlMemoryMap; + SNES::Interface::loadSatellaviewCartridge({ xml, data[0], size[0] }, { "", data[1], size[1] }); + delete[] data[0]; + if(data[1]) delete[] data[1]; + + interface->loadCartridge(::Interface::Mode::SNES); + return true; +} + +bool InterfaceSNES::loadSufamiTurboCartridge(const string &basename, const string &slotAname, const string &slotBname) { + uint8_t *data[3]; + unsigned size[3]; + if(file::read(basename, data[0], size[0]) == false) return false; + file::read(slotAname, data[1], size[1]); + file::read(slotBname, data[2], size[2]); + + interface->unloadCartridge(); + interface->baseName = nall::basename(basename); + if(data[1] && data[2]) interface->baseName = { nall::basename(slotAname), "+", nall::basename(notdir(slotBname)) }; + else if(data[1]) interface->baseName = nall::basename(slotAname); + else if(data[2]) interface->baseName = nall::basename(slotBname); + + string xml = SNESCartridge(data[0], size[0]).xmlMemoryMap; + SNES::Interface::loadSufamiTurboCartridge({ xml, data[0], size[0] }, { "", data[1], size[1] }, { "", data[2], size[2] }); + delete[] data[0]; + if(data[1]) delete[] data[1]; + if(data[2]) delete[] data[2]; + + interface->loadCartridge(::Interface::Mode::SNES); + return true; +} + +bool InterfaceSNES::loadSuperGameBoyCartridge(const string &basename, const string &slotname) { + uint8_t *data[2]; + unsigned size[2]; + if(file::read(basename, data[0], size[0]) == false) return false; + file::read(slotname, data[1], size[1]); + + interface->unloadCartridge(); + interface->baseName = nall::basename(basename); + if(data[1]) interface->baseName = nall::basename(slotname); + + string xml = SNESCartridge(data[0], size[0]).xmlMemoryMap; + string gbXml = GameBoyCartridge(data[1], size[1]).xml; + SNES::Interface::loadSuperGameBoyCartridge({ xml, data[0], size[0] }, { gbXml, data[1], size[1] }); + delete[] data[0]; + if(data[1]) delete[] data[1]; + + interface->loadCartridge(::Interface::Mode::SNES); return true; } diff --git a/bsnes/ui/interface/snes.hpp b/bsnes/ui/interface/snes.hpp index cb55f23d..429e2af3 100755 --- a/bsnes/ui/interface/snes.hpp +++ b/bsnes/ui/interface/snes.hpp @@ -2,6 +2,10 @@ struct InterfaceSNES : SNES::Interface { void setController(bool port, unsigned device); bool loadCartridge(const string &filename); + bool loadSatellaviewSlottedCartridge(const string &basename, const string &slotname); + bool loadSatellaviewCartridge(const string &basename, const string &slotname); + bool loadSufamiTurboCartridge(const string &basename, const string &slotAname, const string &slotBname); + bool loadSuperGameBoyCartridge(const string &basename, const string &slotname); void unloadCartridge(); bool saveState(const string &filename); diff --git a/bsnes/ui/main.cpp b/bsnes/ui/main.cpp index 5c46d0c8..09bcd265 100755 --- a/bsnes/ui/main.cpp +++ b/bsnes/ui/main.cpp @@ -7,8 +7,9 @@ void Application::run() { inputManager->scan(); autopause = (mainWindow->focused() == false && config->input.focusPolicy == 2); + utility->updateStatus(); - if(interface->loaded() == false || autopause) { + if(interface->cartridgeLoaded() == false || pause || autopause) { audio.clear(); usleep(20 * 1000); return; @@ -17,12 +18,15 @@ void Application::run() { interface->run(); } -Application::Application(int argc, char **argv) : quit(false) { +Application::Application(int argc, char **argv) { application = this; + quit = false; + pause = false; + autopause = false; { char path[PATH_MAX]; auto unused = ::realpath(argv[0], path); - realpath = path; + basepath = path; unused = ::userpath(path); userpath = path; #if defined(PLATFORM_WIN) @@ -52,6 +56,7 @@ Application::Application(int argc, char **argv) : quit(false) { windowManager = new WindowManager; mainWindow = new MainWindow; fileBrowser = new FileBrowser; + slotLoader = new SlotLoader; settingsWindow = new SettingsWindow; cheatEditor = new CheatEditor; stateManager = new StateManager; @@ -99,6 +104,7 @@ Application::~Application() { delete stateManager; delete cheatEditor; delete settingsWindow; + delete slotLoader; delete fileBrowser; delete mainWindow; delete windowManager; diff --git a/bsnes/ui/tools/cheat-editor.cpp b/bsnes/ui/tools/cheat-editor.cpp index 51b58fe8..0d71301d 100755 --- a/bsnes/ui/tools/cheat-editor.cpp +++ b/bsnes/ui/tools/cheat-editor.cpp @@ -43,7 +43,7 @@ CheatEditor::CheatEditor() { } void CheatEditor::synchronize() { - layout.setEnabled(interface->loaded()); + layout.setEnabled(interface->cartridgeLoaded()); if(cheatList.selected()) { unsigned n = cheatList.selection(); diff --git a/bsnes/ui/tools/state-manager.cpp b/bsnes/ui/tools/state-manager.cpp index b1a1fb4b..704090df 100755 --- a/bsnes/ui/tools/state-manager.cpp +++ b/bsnes/ui/tools/state-manager.cpp @@ -38,7 +38,7 @@ StateManager::StateManager() { } void StateManager::synchronize() { - layout.setEnabled(interface->loaded()); + layout.setEnabled(interface->cartridgeLoaded()); descEdit.setText(""); descEdit.setEnabled(false); diff --git a/bsnes/ui/utility/utility.cpp b/bsnes/ui/utility/utility.cpp index 219e8b8a..130828fd 100755 --- a/bsnes/ui/utility/utility.cpp +++ b/bsnes/ui/utility/utility.cpp @@ -71,14 +71,10 @@ void Utility::resizeMainWindow(bool shrink) { } } -void Utility::shrinkMainWindow() { -} - void Utility::toggleFullScreen() { - static bool fullScreen = false; static Geometry geometry; - if(fullScreen == false) { + if(mainWindow->fullScreen() == false) { geometry = mainWindow->geometry(); mainWindow->setMenuVisible(false); mainWindow->setStatusVisible(false); @@ -92,7 +88,6 @@ void Utility::toggleFullScreen() { mainWindow->setGeometry(geometry); } - fullScreen ^= 1; resizeMainWindow(); } @@ -101,3 +96,33 @@ void Utility::bindVideoShader() { data.readfile(config->video.shader); video.set(Video::Shader, (const char*)data); } + +void Utility::updateStatus() { + time_t currentTime = time(0); + string text; + if((currentTime - statusTime) <= 2) { + text = statusMessage; + } else if(interface->cartridgeLoaded() == false) { + text = "No cartridge loaded"; + } else if(application->pause || application->autopause) { + text = "Paused"; + } else { + text = statusText; + } + if(text != mainWindow->statusText()) { + mainWindow->setStatusText(text); + } +} + +void Utility::setStatusText(const string &text) { + statusText = text; +} + +void Utility::showMessage(const string &message) { + statusTime = time(0); + statusMessage = message; +} + +Utility::Utility() { + statusTime = 0; +} diff --git a/bsnes/ui/utility/utility.hpp b/bsnes/ui/utility/utility.hpp index 9f1f0989..27c2c005 100755 --- a/bsnes/ui/utility/utility.hpp +++ b/bsnes/ui/utility/utility.hpp @@ -1,9 +1,19 @@ struct Utility { void setMode(Interface::Mode mode); void resizeMainWindow(bool shrink = false); - void shrinkMainWindow(); void toggleFullScreen(); void bindVideoShader(); + + void updateStatus(); + void setStatusText(const string &text); + void showMessage(const string &text); + + Utility(); + +private: + time_t statusTime; + string statusText; + string statusMessage; }; extern Utility *utility;