From 9ad8b7eaac56da5fb30af942a1c6c484346b7b6b Mon Sep 17 00:00:00 2001 From: Tim Allen Date: Tue, 1 May 2012 09:43:23 +1000 Subject: [PATCH] Update to v088r10 release. byuu says: ethos is going to be absolutely amazing. You guys are in for a treat :D I'm impressing the hell out of myself with how well-structured this code is, it's allowing me to do amazing new things. Just a small sampling of what's in store (and already implemented): The file browser will display folders as "[ folder name ]", and cartridge folders as "Game Name" (no extension, no /) [icons would be nicer, but well ... phoenix.] Folders are sorted above cartridge folders. Cartridge folders for other systems do not show up in the list. Not only are unique paths stored for each image type, your position in the list is saved across runs. Some voodoo was added to GTK+ so that all targets even scroll directly to that item when you open the list. Load->System->Enter restarts your last game. That sounds really simple and obvious, but it makes an -incredible- difference. Didn't realize it until I tried an implementation of it, wow. The input mapping list now lets you bind as many hotkeys as you want to any given input. So SFC::Port1::Joypad::B = Keyboard::Z or Joypad::Button1 ... no need to remap everything to switch between keyboard and joypad. Either one activates the key. There is a separate Hotkeys tab now. This should hopefully end the confusion about how to remap hotkeys that users experience. Hotkeys are different, too. Instead of OR logic, they use AND logic. So Fullscreen = Keyboard::Alt and Keyboard::Enter. Both must be pressed to enter the key. This lets you easily implement "super" modifier keys. The actual codebase has new features the old UI never had, and has about ~50% of the old functionality (so far, of course), yet is only ~25% as much code. The entire GUI no longer needs to pull in all the headers for each emulated system. It just needs a small interface header file. Then bind the entire system with exactly **two** lines of code. Everything is dynamically generated for you after that. --- bsnes/Makefile | 2 +- bsnes/emulator/emulator.hpp | 7 +- bsnes/emulator/interface.hpp | 2 + bsnes/fc/interface/interface.cpp | 97 ++++++++++ bsnes/fc/interface/interface.hpp | 19 ++ bsnes/gb/gb.hpp | 4 +- bsnes/gb/interface/interface.cpp | 117 ++++++++++++ bsnes/gb/interface/interface.hpp | 21 +++ bsnes/gba/interface/interface.cpp | 59 +++--- bsnes/nall/file.hpp | 10 +- bsnes/phoenix/gtk/widget/list-view.cpp | 7 + bsnes/sfc/cartridge/markup.cpp | 8 +- bsnes/sfc/chip/link/link.cpp | 2 +- bsnes/sfc/chip/msu1/msu1.cpp | 4 +- bsnes/sfc/chip/msu1/serialization.cpp | 4 +- bsnes/sfc/controller/gamepad/gamepad.cpp | 3 +- bsnes/sfc/controller/usart/usart.cpp | 2 +- bsnes/sfc/interface/interface.cpp | 130 ++++++++++++- bsnes/sfc/interface/interface.hpp | 24 ++- bsnes/target-ethos/bootstrap.cpp | 24 +-- bsnes/target-ethos/ethos.cpp | 15 +- bsnes/target-ethos/ethos.hpp | 2 + bsnes/target-ethos/general/browser.cpp | 115 +++++++++--- bsnes/target-ethos/general/browser.hpp | 18 +- bsnes/target-ethos/general/presentation.cpp | 44 +++-- bsnes/target-ethos/general/presentation.hpp | 8 +- bsnes/target-ethos/input/hotkeys.cpp | 56 ++++++ bsnes/target-ethos/input/input.cpp | 198 +++++++++++++++++--- bsnes/target-ethos/input/input.hpp | 45 ++++- bsnes/target-ethos/interface/interface.cpp | 9 +- bsnes/target-ethos/settings/hotkey.cpp | 66 +++++++ bsnes/target-ethos/settings/hotkey.hpp | 19 ++ bsnes/target-ethos/settings/input.cpp | 98 +++++++++- bsnes/target-ethos/settings/input.hpp | 7 + bsnes/target-ethos/settings/settings.cpp | 5 + bsnes/target-ethos/settings/settings.hpp | 1 + bsnes/target-ethos/utility/utility.cpp | 11 ++ 37 files changed, 1101 insertions(+), 162 deletions(-) create mode 100755 bsnes/target-ethos/input/hotkeys.cpp create mode 100755 bsnes/target-ethos/settings/hotkey.cpp create mode 100755 bsnes/target-ethos/settings/hotkey.hpp diff --git a/bsnes/Makefile b/bsnes/Makefile index e4fd0e0e..f1b70c6c 100755 --- a/bsnes/Makefile +++ b/bsnes/Makefile @@ -6,7 +6,7 @@ gb := gb gba := gba profile := accuracy -target := ui +target := ethos # options += console diff --git a/bsnes/emulator/emulator.hpp b/bsnes/emulator/emulator.hpp index 193d5ebb..ee11d20e 100755 --- a/bsnes/emulator/emulator.hpp +++ b/bsnes/emulator/emulator.hpp @@ -1,7 +1,12 @@ #ifndef EMULATOR_HPP #define EMULATOR_HPP -static const char Version[] = "088.09"; +namespace Emulator { + static const char Name[] = "bsnes"; + static const char Version[] = "088.10"; + static const char Author[] = "byuu"; + static const char License[] = "GPLv3"; +} #include #include diff --git a/bsnes/emulator/interface.hpp b/bsnes/emulator/interface.hpp index f864227e..2ecbee52 100755 --- a/bsnes/emulator/interface.hpp +++ b/bsnes/emulator/interface.hpp @@ -14,6 +14,7 @@ struct Interface { } information; struct Firmware { + string displayname; string name; unsigned id; }; @@ -47,6 +48,7 @@ struct Interface { unsigned id; struct Input { string name; + unsigned type; //0 = digital, 1 = analog unsigned id; unsigned guid; }; diff --git a/bsnes/fc/interface/interface.cpp b/bsnes/fc/interface/interface.cpp index 3777b9b1..6fb084be 100755 --- a/bsnes/fc/interface/interface.cpp +++ b/bsnes/fc/interface/interface.cpp @@ -4,4 +4,101 @@ namespace Famicom { Interface *interface = nullptr; +bool Interface::loaded() { + return cartridge.loaded(); +} + +void Interface::load(unsigned id, const stream &memory, const string &markup) { + if(id == 0) { + cartridge.load(markup, memory); + system.power(); + input.connect(0, Input::Device::Joypad); + input.connect(1, Input::Device::Joypad); + } +} + +void Interface::unload() { + cartridge.unload(); +} + +void Interface::power() { + system.power(); +} + +void Interface::reset() { + system.reset(); +} + +void Interface::run() { + system.run(); +} + +void Interface::updatePalette() { + video.generate_palette(); +} + +Interface::Interface() { + interface = this; + + information.name = "Famicom"; + information.width = 256; + information.height = 240; + information.frequency = 1789772; + information.ports = 2; + information.resettable = true; + + { + Media media; + media.displayname = "Famicom"; + media.name = "program.rom"; + media.filter = "*.fc"; + media.id = 0; + this->media.append(media); + } + + { + Port port; + port.name = "Port 1"; + port.id = 0; + { + Port::Device device; + device.name = "Controller"; + device.id = 0; + device.input.append({"A", 0, 0}); + device.input.append({"B", 0, 1}); + device.input.append({"Select", 0, 2}); + device.input.append({"Start", 0, 3}); + device.input.append({"Up", 0, 4}); + device.input.append({"Down", 0, 5}); + device.input.append({"Left", 0, 6}); + device.input.append({"Right", 0, 7}); + device.displayinput = {4, 5, 6, 7, 1, 0, 2, 3}; + port.device.append(device); + } + this->port.append(port); + } + + { + Port port; + port.name = "Port 2"; + port.id = 1; + { + Port::Device device; + device.name = "Controller"; + device.id = 0; + device.input.append({"A", 0, 0}); + device.input.append({"B", 0, 1}); + device.input.append({"Select", 0, 2}); + device.input.append({"Start", 0, 3}); + device.input.append({"Up", 0, 4}); + device.input.append({"Down", 0, 5}); + device.input.append({"Left", 0, 6}); + device.input.append({"Right", 0, 7}); + device.displayinput = {4, 5, 6, 7, 1, 0, 2, 3}; + port.device.append(device); + } + this->port.append(port); + } +} + } diff --git a/bsnes/fc/interface/interface.hpp b/bsnes/fc/interface/interface.hpp index 72ed99df..3cb03c56 100755 --- a/bsnes/fc/interface/interface.hpp +++ b/bsnes/fc/interface/interface.hpp @@ -1,4 +1,23 @@ +#ifndef FC_HPP +namespace Famicom { +#endif + struct Interface : Emulator::Interface { + bool loaded(); + void load(unsigned id, const stream &memory, const string &markup = ""); + void unload(); + + void power(); + void reset(); + void run(); + + void updatePalette(); + + Interface(); }; extern Interface *interface; + +#ifndef FC_HPP +} +#endif diff --git a/bsnes/gb/gb.hpp b/bsnes/gb/gb.hpp index d8c4beb6..0a1a3903 100755 --- a/bsnes/gb/gb.hpp +++ b/bsnes/gb/gb.hpp @@ -1,5 +1,5 @@ -#ifndef GAMEBOY_HPP -#define GAMEBOY_HPP +#ifndef GB_HPP +#define GB_HPP #include #include diff --git a/bsnes/gb/interface/interface.cpp b/bsnes/gb/interface/interface.cpp index bcdbff3b..eca9fe5e 100755 --- a/bsnes/gb/interface/interface.cpp +++ b/bsnes/gb/interface/interface.cpp @@ -4,4 +4,121 @@ namespace GameBoy { Interface *interface = nullptr; +bool Interface::loaded() { + return cartridge.loaded(); +} + +void Interface::load(unsigned id, const stream &stream, const string &markup) { + if(id == 0) stream.read(system.bootROM.dmg, min( 256u, stream.size())); + if(id == 1) stream.read(system.bootROM.sgb, min( 256u, stream.size())); + if(id == 2) stream.read(system.bootROM.cgb, min(2048u, stream.size())); + if(id == 3) { + cartridge.load(System::Revision::GameBoy, markup, stream); + system.power(); + } + if(id == 4) { + cartridge.load(System::Revision::SuperGameBoy, markup, stream); + system.power(); + } + if(id == 5) { + cartridge.load(System::Revision::GameBoyColor, markup, stream); + system.power(); + } +} + +void Interface::unload() { + cartridge.unload(); +} + +void Interface::power() { + system.power(); +} + +void Interface::reset() { + system.power(); +} + +void Interface::run() { + system.run(); +} + +void Interface::updatePalette() { + video.generate_palette(); +} + +Interface::Interface() { + interface = this; + + information.name = "Game Boy"; + information.width = 160; + information.height = 144; + information.frequency = 4194304; + information.ports = 1; + information.resettable = false; + + { + Firmware firmware; + firmware.displayname = "Game Boy"; + firmware.name = "Game Boy.sys/boot.rom"; + firmware.id = 0; + this->firmware.append(firmware); + } + + { + Firmware firmware; + firmware.displayname = "Super Game Boy"; + firmware.name = "Super Game Boy.sfc/boot.rom"; + firmware.id = 1; + this->firmware.append(firmware); + } + + { + Firmware firmware; + firmware.displayname = "Game Boy Color"; + firmware.name = "Game Boy Color.sys/boot.rom"; + firmware.id = 2; + this->firmware.append(firmware); + } + + { + Media media; + media.displayname = "Game Boy"; + media.name = "program.rom"; + media.filter = "*.gb"; + media.id = 3; + this->media.append(media); + } + + { + Media media; + media.displayname = "Game Boy Color"; + media.name = "program.rom"; + media.filter = "*.gbc"; + media.id = 5; + this->media.append(media); + } + + { + Port port; + port.name = "Device"; + port.id = 0; + { + Port::Device device; + device.name = "Controller"; + device.id = 0; + device.input.append({"Up", 0, 0}); + device.input.append({"Down", 0, 1}); + device.input.append({"Left", 0, 2}); + device.input.append({"Right", 0, 3}); + device.input.append({"B", 0, 4}); + device.input.append({"A", 0, 5}); + device.input.append({"Select", 0, 6}); + device.input.append({"Start", 0, 7}); + device.displayinput = {0, 1, 2, 3, 4, 5, 6, 7}; + port.device.append(device); + } + this->port.append(port); + } +} + } diff --git a/bsnes/gb/interface/interface.hpp b/bsnes/gb/interface/interface.hpp index 42aa5aad..31225ef9 100755 --- a/bsnes/gb/interface/interface.hpp +++ b/bsnes/gb/interface/interface.hpp @@ -1,6 +1,27 @@ +#ifndef GB_HPP +namespace GameBoy { +#endif + struct Interface : Emulator::Interface { + //Super Game Boy bindings virtual void lcdScanline() {} virtual void joypWrite(bool p15, bool p14) {} + + bool loaded(); + void load(unsigned id, const stream &memory, const string &markup = ""); + void unload(); + + void power(); + void reset(); + void run(); + + void updatePalette(); + + Interface(); }; extern Interface *interface; + +#ifndef GB_HPP +} +#endif diff --git a/bsnes/gba/interface/interface.cpp b/bsnes/gba/interface/interface.cpp index cbd0df62..3be8a816 100755 --- a/bsnes/gba/interface/interface.cpp +++ b/bsnes/gba/interface/interface.cpp @@ -59,43 +59,44 @@ Interface::Interface() { information.resettable = false; { - Firmware firmware; - firmware.name = "BIOS"; - firmware.id = 1; - this->firmware.append(firmware); + Firmware firmware; + firmware.displayname = "Game Boy Advance"; + firmware.name = "Game Boy Advance.sys/bios.rom"; + firmware.id = 1; + this->firmware.append(firmware); } { - Media media; - media.displayname = "Game Boy Advance"; - media.name = "program.rom"; - media.filter = "*.gba"; - media.id = 0; - this->media.append(media); + Media media; + media.displayname = "Game Boy Advance"; + media.name = "program.rom"; + media.filter = "*.gba"; + media.id = 0; + this->media.append(media); } { - Port port; - port.name = "Device"; - port.id = 0; + Port port; + port.name = "Device"; + port.id = 0; { - Port::Device device; - device.name = "Controller"; - device.id = 0; - device.input.append({"A", 0}); - device.input.append({"B", 1}); - device.input.append({"Select", 2}); - device.input.append({"Start", 3}); - device.input.append({"Right", 4}); - device.input.append({"Left", 5}); - device.input.append({"Up", 6}); - device.input.append({"Down", 7}); - device.input.append({"R", 8}); - device.input.append({"L", 9}); - device.displayinput = { 6, 7, 5, 4, 1, 0, 9, 8, 2, 3 }; - port.device.append(device); + Port::Device device; + device.name = "Controller"; + device.id = 0; + device.input.append({"A", 0, 0}); + device.input.append({"B", 0, 1}); + device.input.append({"Select", 0, 2}); + device.input.append({"Start", 0, 3}); + device.input.append({"Right", 0, 4}); + device.input.append({"Left", 0, 5}); + device.input.append({"Up", 0, 6}); + device.input.append({"Down", 0, 7}); + device.input.append({"R", 0, 8}); + device.input.append({"L", 0, 9}); + device.displayinput = {6, 7, 5, 4, 1, 0, 9, 8, 2, 3}; + port.device.append(device); } - this->port.append(port); + this->port.append(port); } } diff --git a/bsnes/nall/file.hpp b/bsnes/nall/file.hpp index 83e2c992..830c5a1a 100755 --- a/bsnes/nall/file.hpp +++ b/bsnes/nall/file.hpp @@ -129,13 +129,13 @@ namespace nall { file_offset = req_offset; } - int offset() const { - if(!fp) return -1; //file not open + unsigned offset() const { + if(!fp) return 0; //file not open return file_offset; } - int size() const { - if(!fp) return -1; //file not open + unsigned size() const { + if(!fp) return 0; //file not open return file_size; } @@ -227,7 +227,7 @@ namespace nall { file() { memset(buffer, 0, sizeof buffer); - buffer_offset = -1; + buffer_offset = -1; //invalidate buffer buffer_dirty = false; fp = 0; file_offset = 0; diff --git a/bsnes/phoenix/gtk/widget/list-view.cpp b/bsnes/phoenix/gtk/widget/list-view.cpp index 48dc73c5..9ace5a55 100755 --- a/bsnes/phoenix/gtk/widget/list-view.cpp +++ b/bsnes/phoenix/gtk/widget/list-view.cpp @@ -104,6 +104,13 @@ void pListView::setSelection(unsigned row) { GtkTreeIter iter; if(gtk_tree_model_get_iter_from_string(model, &iter, string(row)) == false) return; gtk_tree_selection_select_iter(selection, &iter); + + //scroll window to selected item + char *path = gtk_tree_model_get_string_from_iter(model, &iter); + GtkTreePath *treePath = gtk_tree_path_new_from_string(path); + gtk_tree_view_scroll_to_cell(GTK_TREE_VIEW(subWidget), treePath, nullptr, true, 0.5, 0.0); + gtk_tree_path_free(treePath); + g_free(path); } void pListView::constructor() { diff --git a/bsnes/sfc/cartridge/markup.cpp b/bsnes/sfc/cartridge/markup.cpp index 6fc3b43a..1908cc23 100755 --- a/bsnes/sfc/cartridge/markup.cpp +++ b/bsnes/sfc/cartridge/markup.cpp @@ -223,7 +223,7 @@ void Cartridge::parse_markup_necdsp(XML::Node &root) { string firmware = root["firmware"].data; string sha256 = root["sha256"].data; - string path = interface->path(Slot::Base, firmware); + string path = interface->path((unsigned)Slot::Base, firmware); unsigned promsize = (necdsp.revision == NECDSP::Revision::uPD7725 ? 2048 : 16384); unsigned dromsize = (necdsp.revision == NECDSP::Revision::uPD7725 ? 1024 : 2048); unsigned filesize = promsize * 3 + dromsize * 2; @@ -288,7 +288,7 @@ void Cartridge::parse_markup_hitachidsp(XML::Node &root) { string firmware = root["firmware"].data; string sha256 = root["sha256"].data; - string path = interface->path(Slot::Base, firmware); + string path = interface->path((unsigned)Slot::Base, firmware); file fp; if(fp.open(path, file::mode::read) == false) { interface->message({ "Warning: Hitachi DSP firmware ", firmware, " is missing." }); @@ -338,7 +338,7 @@ void Cartridge::parse_markup_armdsp(XML::Node &root) { string firmware = root["firmware"].data; string sha256 = root["sha256"].data; - string path = interface->path(Slot::Base, firmware); + string path = interface->path((unsigned)Slot::Base, firmware); file fp; if(fp.open(path, file::mode::read) == false) { interface->message({ "Warning: ARM DSP firmware ", firmware, " is missing." }); @@ -520,7 +520,7 @@ void Cartridge::parse_markup_obc1(XML::Node &root) { void Cartridge::parse_markup_msu1(XML::Node &root) { if(root.exists() == false) { - has_msu1 = file::exists(interface->path(Cartridge::Slot::Base, "msu1.rom")); + has_msu1 = file::exists(interface->path((unsigned)Cartridge::Slot::Base, "msu1.rom")); if(has_msu1) { Mapping m({ &MSU1::mmio_read, &msu1 }, { &MSU1::mmio_write, &msu1 }); m.banklo = 0x00, m.bankhi = 0x3f, m.addrlo = 0x2000, m.addrhi = 0x2007; diff --git a/bsnes/sfc/chip/link/link.cpp b/bsnes/sfc/chip/link/link.cpp index f71853cb..a14d947c 100755 --- a/bsnes/sfc/chip/link/link.cpp +++ b/bsnes/sfc/chip/link/link.cpp @@ -22,7 +22,7 @@ void Link::init() { void Link::load() { if(opened()) close(); - string basename = interface->path(Cartridge::Slot::Base, ""); + string basename = interface->path((unsigned)Cartridge::Slot::Base, ""); string name = program != "" ? program : notdir(basename); string path = dir(basename); if(open(name, path)) { diff --git a/bsnes/sfc/chip/msu1/msu1.cpp b/bsnes/sfc/chip/msu1/msu1.cpp index 7a5c9e0a..76b61544 100755 --- a/bsnes/sfc/chip/msu1/msu1.cpp +++ b/bsnes/sfc/chip/msu1/msu1.cpp @@ -52,7 +52,7 @@ void MSU1::init() { void MSU1::load() { if(datafile.open()) datafile.close(); - datafile.open(interface->path(Cartridge::Slot::Base, "msu1.rom"), file::mode::read); + datafile.open(interface->path((unsigned)Cartridge::Slot::Base, "msu1.rom"), file::mode::read); } void MSU1::unload() { @@ -112,7 +112,7 @@ void MSU1::mmio_write(unsigned addr, uint8 data) { case 4: mmio.audio_track = (mmio.audio_track & 0xff00) | (data << 0); case 5: mmio.audio_track = (mmio.audio_track & 0x00ff) | (data << 8); if(audiofile.open()) audiofile.close(); - if(audiofile.open(interface->path(Cartridge::Slot::Base, { "track-", (unsigned)mmio.audio_track, ".pcm" }), file::mode::read)) { + if(audiofile.open(interface->path((unsigned)Cartridge::Slot::Base, { "track-", (unsigned)mmio.audio_track, ".pcm" }), file::mode::read)) { uint32 header = audiofile.readm(4); if(header != 0x4d535531) { //verify 'MSU1' header audiofile.close(); diff --git a/bsnes/sfc/chip/msu1/serialization.cpp b/bsnes/sfc/chip/msu1/serialization.cpp index fc141868..35a970b3 100755 --- a/bsnes/sfc/chip/msu1/serialization.cpp +++ b/bsnes/sfc/chip/msu1/serialization.cpp @@ -16,12 +16,12 @@ void MSU1::serialize(serializer &s) { s.integer(mmio.audio_play); if(datafile.open()) datafile.close(); - if(datafile.open(interface->path(Cartridge::Slot::Base, "msu1.rom"), file::mode::read)) { + if(datafile.open(interface->path((unsigned)Cartridge::Slot::Base, "msu1.rom"), file::mode::read)) { datafile.seek(mmio.data_offset); } if(audiofile.open()) audiofile.close(); - if(audiofile.open(interface->path(Cartridge::Slot::Base, { "track-", (unsigned)mmio.audio_track, ".pcm" }), file::mode::read)) { + if(audiofile.open(interface->path((unsigned)Cartridge::Slot::Base, { "track-", (unsigned)mmio.audio_track, ".pcm" }), file::mode::read)) { audiofile.seek(mmio.audio_offset); } } diff --git a/bsnes/sfc/controller/gamepad/gamepad.cpp b/bsnes/sfc/controller/gamepad/gamepad.cpp index d4276962..23d838f9 100755 --- a/bsnes/sfc/controller/gamepad/gamepad.cpp +++ b/bsnes/sfc/controller/gamepad/gamepad.cpp @@ -2,7 +2,8 @@ uint2 Gamepad::data() { if(counter >= 16) return 1; - uint2 result = interface->inputPoll(port, (unsigned)Input::Device::Joypad, counter); + uint2 result = 0; + if(counter < 12) result = interface->inputPoll(port, (unsigned)Input::Device::Joypad, counter); if(latched == 0) counter++; return result; } diff --git a/bsnes/sfc/controller/usart/usart.cpp b/bsnes/sfc/controller/usart/usart.cpp index 3f1f12ca..236a2ca3 100755 --- a/bsnes/sfc/controller/usart/usart.cpp +++ b/bsnes/sfc/controller/usart/usart.cpp @@ -121,7 +121,7 @@ USART::USART(bool port) : Controller(port) { txlength = 0; txdata = 0; - string filename = interface->path(Cartridge::Slot::Base, "usart.so"); + string filename = interface->path((unsigned)Cartridge::Slot::Base, "usart.so"); if(open_absolute(filename)) { init = sym("usart_init"); main = sym("usart_main"); diff --git a/bsnes/sfc/interface/interface.cpp b/bsnes/sfc/interface/interface.cpp index d752eefc..59517c21 100755 --- a/bsnes/sfc/interface/interface.cpp +++ b/bsnes/sfc/interface/interface.cpp @@ -4,8 +4,134 @@ namespace SuperFamicom { Interface *interface = nullptr; -void Interface::message(const string &text) { - print(text, "\n"); +bool Interface::loaded() { + return cartridge.loaded(); +} + +void Interface::load(unsigned id, const stream &stream, const string &markup) { + if(id == 0) { + stream.read(smp.iplrom, min(64u, stream.size())); + } + + if(id == 1) { + cartridge.rom.copy(stream); + cartridge.load(Cartridge::Mode::Normal, markup); + system.power(); + input.connect(0, Input::Device::Joypad); + input.connect(1, Input::Device::Joypad); + } +} + +void Interface::unload() { + cartridge.unload(); +} + +void Interface::power() { + system.power(); +} + +void Interface::reset() { + system.reset(); +} + +void Interface::run() { + system.run(); +} + +void Interface::updatePalette() { + video.generate_palette(); +} + +Interface::Interface() { + interface = this; + + information.name = "Super Famicom"; + information.width = 256; + information.height = 240; + information.frequency = 32040; + information.ports = 2; + information.resettable = true; + + { + Firmware firmware; + firmware.displayname = "Super Famicom"; + firmware.name = "Super Famicom.sys/spc700.rom"; + firmware.id = 0; + this->firmware.append(firmware); + } + + { + Media media; + media.displayname = "Super Famicom"; + media.name = "program.rom"; + media.filter = "*.sfc"; + media.id = 1; + this->media.append(media); + } + + { + Port port; + port.name = "Port 1"; + port.id = 0; + { + Port::Device device; + device.name = "None"; + device.id = 0; + port.device.append(device); + } + { + Port::Device device; + device.name = "Controller"; + device.id = 1; + device.input.append({"B", 0, 0}); + device.input.append({"Y", 0, 1}); + device.input.append({"Select", 0, 2}); + device.input.append({"Start", 0, 3}); + device.input.append({"Up", 0, 4}); + device.input.append({"Down", 0, 5}); + device.input.append({"Left", 0, 6}); + device.input.append({"Right", 0, 7}); + device.input.append({"A", 0, 8}); + device.input.append({"X", 0, 9}); + device.input.append({"L", 0, 10}); + device.input.append({"R", 0, 11}); + device.displayinput = {4, 5, 6, 7, 0, 8, 1, 9, 10, 11, 2, 3}; + port.device.append(device); + } + this->port.append(port); + } + + { + Port port; + port.name = "Port 2"; + port.id = 1; + { + Port::Device device; + device.name = "None"; + device.id = 0; + port.device.append(device); + } + { + Port::Device device; + device.name = "Controller"; + device.id = 1; + device.input.append({"B", 0, 0}); + device.input.append({"Y", 0, 1}); + device.input.append({"Select", 0, 2}); + device.input.append({"Start", 0, 3}); + device.input.append({"Up", 0, 4}); + device.input.append({"Down", 0, 5}); + device.input.append({"Left", 0, 6}); + device.input.append({"Right", 0, 7}); + device.input.append({"A", 0, 8}); + device.input.append({"X", 0, 9}); + device.input.append({"L", 0, 10}); + device.input.append({"R", 0, 11}); + device.displayinput = {4, 5, 6, 7, 0, 8, 1, 9, 10, 11, 2, 3}; + port.device.append(device); + } + this->port.append(port); + } } } diff --git a/bsnes/sfc/interface/interface.hpp b/bsnes/sfc/interface/interface.hpp index 84721707..7be5c62e 100755 --- a/bsnes/sfc/interface/interface.hpp +++ b/bsnes/sfc/interface/interface.hpp @@ -1,6 +1,26 @@ +#ifndef SFC_HPP +namespace SuperFamicom { +#endif + struct Interface : Emulator::Interface { - virtual string path(Cartridge::Slot slot, const string &hint) = 0; - virtual void message(const string &text); + virtual string path(unsigned slot, const string &hint) { return ""; } + virtual void message(const string &text) {} + + bool loaded(); + void load(unsigned id, const stream &stream, const string &markup = ""); + void unload(); + + void power(); + void reset(); + void run(); + + void updatePalette(); + + Interface(); }; extern Interface *interface; + +#ifndef SFC_HPP +} +#endif diff --git a/bsnes/target-ethos/bootstrap.cpp b/bsnes/target-ethos/bootstrap.cpp index 9d078d7c..ac40604b 100755 --- a/bsnes/target-ethos/bootstrap.cpp +++ b/bsnes/target-ethos/bootstrap.cpp @@ -1,8 +1,14 @@ +#include +#include +#include #include void Application::bootstrap() { interface = new Interface; + emulator.append(new Famicom::Interface); + emulator.append(new SuperFamicom::Interface); + emulator.append(new GameBoy::Interface); emulator.append(new GameBoyAdvance::Interface); for(auto &system : emulator) { @@ -12,23 +18,9 @@ void Application::bootstrap() { system->callback.inputPoll = {&Interface::inputPoll, interface}; system->updatePalette(); - string basepath = path({system->information.name, ".sys/"}); - - string manifest; - manifest.readfile({basepath, "manifest.xml"}); - XML::Document document(manifest); - for(auto &firmware : system->firmware) { - string path = firmware.name; - path.lower(); - for(auto &root : document) { - for(auto &node : root) { - if(node.name == path) { - filestream fs({basepath, node["firmware"].data}); - system->load(firmware.id, fs); - } - } - } + filestream fs{application->path(firmware.name)}; + system->load(firmware.id, fs); } } } diff --git a/bsnes/target-ethos/ethos.cpp b/bsnes/target-ethos/ethos.cpp index dbd016f9..501ef2f8 100755 --- a/bsnes/target-ethos/ethos.cpp +++ b/bsnes/target-ethos/ethos.cpp @@ -2,6 +2,7 @@ #include "bootstrap.cpp" Application *application = nullptr; +DSP dspaudio; Emulator::Interface& system() { struct application_interface_null{}; @@ -67,6 +68,7 @@ Application::Application(int argc, char **argv) { videoSettings = new VideoSettings; audioSettings = new AudioSettings; inputSettings = new InputSettings; + hotkeySettings = new HotkeySettings; settings = new Settings; presentation->setVisible(); @@ -79,19 +81,28 @@ Application::Application(int argc, char **argv) { audio.driver("ALSA"); audio.set(Audio::Handle, presentation->viewport.handle()); - audio.set(Audio::Synchronize, false); + audio.set(Audio::Synchronize, true); audio.set(Audio::Latency, 80u); - audio.set(Audio::Frequency, 32768u); + audio.set(Audio::Frequency, 48000u); audio.init(); input.driver("SDL"); input.set(Input::Handle, presentation->viewport.handle()); input.init(); + dspaudio.setPrecision(16); + dspaudio.setVolume(2.0); + dspaudio.setBalance(0.0); + dspaudio.setResampler(DSP::ResampleEngine::Linear); + dspaudio.setResamplerFrequency(48000u); + while(quit == false) { OS::processEvents(); run(); } + + browser->saveConfiguration(); + inputManager->saveConfiguration(); } Application::~Application() { diff --git a/bsnes/target-ethos/ethos.hpp b/bsnes/target-ethos/ethos.hpp index f66fd8ed..a01cff0d 100755 --- a/bsnes/target-ethos/ethos.hpp +++ b/bsnes/target-ethos/ethos.hpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include #include @@ -48,3 +49,4 @@ struct Application { }; extern Application *application; +extern DSP dspaudio; diff --git a/bsnes/target-ethos/general/browser.cpp b/bsnes/target-ethos/general/browser.cpp index 4fa872f4..550b8244 100755 --- a/bsnes/target-ethos/general/browser.cpp +++ b/bsnes/target-ethos/general/browser.cpp @@ -1,6 +1,7 @@ Browser *browser = nullptr; Browser::Browser() { + bootstrap(); setGeometry({128, 128, 640, 400}); layout.setMargin(5); @@ -44,40 +45,112 @@ Browser::Browser() { synchronize(); } +void Browser::synchronize() { + openButton.setEnabled(fileList.selected()); + if(fileList.selected()) { + for(auto &folder : folderList) { + if(folder.filter == media.filter) { + folder.selection = fileList.selection(); + } + } + } +} + +void Browser::saveConfiguration() { + config.save(application->path("paths.cfg")); +} + +void Browser::bootstrap() { + for(auto &emulator : application->emulator) { + for(auto &media : emulator->media) { + bool found = false; + for(auto &folder : folderList) { + if(folder.filter == media.filter) { + found = true; + break; + } + } + if(found == true) continue; + + Folder folder; + folder.filter = media.filter; + folder.path = application->basepath; + folder.selection = 0; + folderList.append(folder); + } + } + + for(auto &folder : folderList) { + config.append(folder.path, folder.filter); + config.append(folder.selection, string{folder.filter, "::selection"}); + } + + config.load(application->path("paths.cfg")); + config.save(application->path("paths.cfg")); +} + void Browser::open(Emulator::Interface::Media &media, function callback) { this->media = media; this->callback = callback; setTitle({"Load ", media.displayname}); - setPath("/media/sdb1/root/cartridges/Game Boy Advance/"); + + string path; + unsigned selection = 0; + for(auto &folder : folderList) { + if(folder.filter == media.filter) { + path = folder.path; + selection = folder.selection; + break; + } + } + if(path.empty()) path = application->basepath; + setPath(path, selection); filterLabel.setText({"Files of type: ", media.filter}); setVisible(); + fileList.setFocused(); } -void Browser::synchronize() { - openButton.setEnabled(fileList.selected()); -} +void Browser::setPath(const string &path, unsigned selection) { + for(auto &folder : folderList) { + if(folder.filter == media.filter) folder.path = path; + } -void Browser::setPath(const string &path) { this->path = path; pathEdit.setText(path); fileList.reset(); filenameList.reset(); - lstring contents = directory::contents(path); + lstring contents = directory::folders(path); + for(auto &filename : contents) { - if(filename.endswith("/")) { - filenameList.append(filename); - } else if(filename.wildcard(media.filter)) { + string filter = {media.filter, "/"}; + if(!filename.wildcard(R"(*.??/)") && !filename.wildcard(R"(*.???/)")) { + string name = filename; + name.rtrim<1>("/"); + name = {"[ ", name, " ]"}; filenameList.append(filename); + fileList.append(name); } } - for(auto &filename : filenameList) fileList.append(filename); - fileList.setSelection(0); + for(auto &filename : contents) { + string filter = {media.filter, "/"}; + if(filename.wildcard(R"(*.??/)") || filename.wildcard(R"(*.???/)")) { + if(filename.wildcard(filter)) { + string name = filename; + filter.ltrim<1>("*"); + name.rtrim<1>(filter); + filenameList.append(filename); + fileList.append(name); + } + } + } + + fileList.setSelection(selection); fileList.setFocused(); synchronize(); } @@ -85,22 +158,8 @@ void Browser::setPath(const string &path) { void Browser::fileListActivate() { unsigned selection = fileList.selection(); string filename = filenameList[selection]; - if(filename.endswith("/")) { - if(loadFolder({path, filename})) return; - return setPath({path, filename}); - } - loadFile({path, filename}); -} - -bool Browser::loadFolder(const string &path) { - string requested = path; - requested.rtrim<1>("/"); - if(requested.wildcard(media.filter) == false) return false; - loadFile(path); - return true; -} - -void Browser::loadFile(const string &filename) { + string filter = {media.filter, "/"}; + if(filename.wildcard(filter) == false) return setPath({path, filename}); setVisible(false); - if(callback) callback(filename); + if(callback) callback({path, filename}); } diff --git a/bsnes/target-ethos/general/browser.hpp b/bsnes/target-ethos/general/browser.hpp index 325de616..ff64a18c 100755 --- a/bsnes/target-ethos/general/browser.hpp +++ b/bsnes/target-ethos/general/browser.hpp @@ -10,19 +10,27 @@ struct Browser : Window { Button openButton; void open(Emulator::Interface::Media &media, function callback); + void saveConfiguration(); + void synchronize(); + void bootstrap(); Browser(); -public: +private: + configuration config; + struct Folder { + string filter; + string path; + unsigned selection; + }; + vector folderList; + Emulator::Interface::Media media; function callback; string path; lstring filenameList; - void synchronize(); - void setPath(const string &path); + void setPath(const string &path, unsigned selection = 0); void fileListActivate(); - bool loadFolder(const string &path); - void loadFile(const string &filename); }; extern Browser *browser; diff --git a/bsnes/target-ethos/general/presentation.cpp b/bsnes/target-ethos/general/presentation.cpp index 7a5d6fa2..09944e42 100755 --- a/bsnes/target-ethos/general/presentation.cpp +++ b/bsnes/target-ethos/general/presentation.cpp @@ -1,15 +1,24 @@ Presentation *presentation = nullptr; void Presentation::synchronize() { + for(auto &system : emulatorList) system->menu.setVisible(false); for(auto &system : emulatorList) { - system->menu.setVisible(system->interface == application->active); + if(system->interface == application->active) { + activeSystem = system; + system->menu.setVisible(true); + return; + } } } -Presentation::Presentation() { +void Presentation::setSystemName(const string &name) { + if(activeSystem) activeSystem->menu.setText(name); +} + +Presentation::Presentation() : activeSystem(nullptr) { bootstrap(); - setTitle("ethos"); + setTitle({Emulator::Name, " v", Emulator::Version}); setGeometry({1024, 600, 720, 480}); setBackgroundColor({0, 0, 0}); setMenuFont(application->normalFont); @@ -22,13 +31,9 @@ Presentation::Presentation() { configurationSettings.setText("Configuration ..."); toolsMenu.setText("Tools"); - for(auto &system : emulatorList) { - loadMenu.append(system->load); - } append(loadMenu); - for(auto &system : emulatorList) { - append(system->menu); - } + for(auto &item : loadList) loadMenu.append(*item); + for(auto &system : emulatorList) append(system->menu); append(settingsMenu); settingsMenu.append(configurationSettings); append(toolsMenu); @@ -48,17 +53,18 @@ void Presentation::bootstrap() { System *system = new System; system->interface = emulator; - system->name = emulator->information.name; - system->filter = "*.gba"; + for(auto &media : emulator->media) { + Item *item = new Item; + item->setText({media.displayname, " ..."}); + item->onActivate = [=, &media] { + browser->open(media, [=, &media](string filename) { + utility->loadMedia(system->interface, media, filename); + }); + }; + loadList.append(item); + } - system->load.setText(system->name); - system->load.onActivate = [=] { - browser->open(system->interface->media[0], [=](string filename) { - utility->loadMedia(system->interface, system->interface->media[0], filename); - }); - }; - - system->menu.setText(system->name); + system->menu.setText(emulator->information.name); system->power.setText("Power"); system->reset.setText("Reset"); system->unload.setText("Unload"); diff --git a/bsnes/target-ethos/general/presentation.hpp b/bsnes/target-ethos/general/presentation.hpp index a8bf66f3..9aefbd46 100755 --- a/bsnes/target-ethos/general/presentation.hpp +++ b/bsnes/target-ethos/general/presentation.hpp @@ -5,9 +5,6 @@ struct Presentation : Window { struct System { Emulator::Interface *interface; - string name; - string filter; - Item load; Menu menu; Item power; Item reset; @@ -18,13 +15,18 @@ struct Presentation : Window { vector emulatorList; Menu loadMenu; + vector loadList; Menu settingsMenu; Item configurationSettings; Menu toolsMenu; void synchronize(); + void setSystemName(const string &name); void bootstrap(); Presentation(); + +private: + System *activeSystem; }; extern Presentation *presentation; diff --git a/bsnes/target-ethos/input/hotkeys.cpp b/bsnes/target-ethos/input/hotkeys.cpp new file mode 100755 index 00000000..d28fb772 --- /dev/null +++ b/bsnes/target-ethos/input/hotkeys.cpp @@ -0,0 +1,56 @@ +void InputManager::appendHotkeys() { + { + auto hotkey = new HotkeyInput; + hotkey->name = "Fast Forward"; + hotkey->mapping = "KB0::Tilde"; + hotkey->logic = 1; + hotkeyMap.append(hotkey); + + hotkey->press = [] { + video.set(Video::Synchronize, false); + audio.set(Audio::Synchronize, false); + }; + + hotkey->release = [] { + video.set(Video::Synchronize, false); + audio.set(Audio::Synchronize, true); + }; + } + + { + auto hotkey = new HotkeyInput; + hotkey->name = "Power Cycle"; + hotkey->mapping = "None"; + hotkey->logic = 1; + hotkeyMap.append(hotkey); + + hotkey->press = [] { + utility->power(); + }; + } + + { + auto hotkey = new HotkeyInput; + hotkey->name = "Soft Reset"; + hotkey->mapping = "None"; + hotkey->logic = 1; + hotkeyMap.append(hotkey); + + hotkey->press = [] { + utility->reset(); + }; + } + + for(auto &hotkey : hotkeyMap) { + config.append(hotkey->mapping, hotkey->name); + } +} + +void InputManager::pollHotkeys() { + for(auto &hotkey : hotkeyMap) { + bool state = hotkey->poll(); + if(hotkey->state == 0 && state == 1) if(hotkey->press) hotkey->press(); + if(hotkey->state == 1 && state == 0) if(hotkey->release) hotkey->release(); + hotkey->state = state; + } +} diff --git a/bsnes/target-ethos/input/input.cpp b/bsnes/target-ethos/input/input.cpp index 3edb0908..a659b7a3 100755 --- a/bsnes/target-ethos/input/input.cpp +++ b/bsnes/target-ethos/input/input.cpp @@ -1,35 +1,176 @@ #include "../ethos.hpp" +#include "hotkeys.cpp" InputManager *inputManager = nullptr; void AbstractInput::bind() { - if(mapping.empty()) type = Type::Button, mapping = "None"; + inputList.reset(); + lstring list = mapping.split(","); - if(mapping.endswith(".Up")) type = Type::HatUp; - else if(mapping.endswith(".Down")) type = Type::HatDown; - else if(mapping.endswith(".Left")) type = Type::HatLeft; - else if(mapping.endswith(".Right")) type = Type::HatRight; - else if(mapping.endswith(".Lo")) type = Type::AxisLo; - else if(mapping.endswith(".Hi")) type = Type::AxisHi; - else if(mapping.beginswith("JP") && mapping.position("Axis")) type = Type::Axis; - else if(mapping.beginswith("MS") && mapping.endswith("axis")) type = Type::MouseAxis; - else if(mapping.beginswith("MS")) type = Type::MouseButton; - else type = Type::Button; + for(auto &mapping : list) { + Input::Type type; + if(mapping.endswith(".Up")) type = Input::Type::HatUp; + else if(mapping.endswith(".Down")) type = Input::Type::HatDown; + else if(mapping.endswith(".Left")) type = Input::Type::HatLeft; + else if(mapping.endswith(".Right")) type = Input::Type::HatRight; + else if(mapping.endswith(".Lo")) type = Input::Type::AxisLo; + else if(mapping.endswith(".Hi")) type = Input::Type::AxisHi; + else if(mapping.beginswith("JP") && mapping.position("Axis")) type = Input::Type::Axis; + else if(mapping.beginswith("MS") && mapping.endswith("axis")) type = Input::Type::MouseAxis; + else if(mapping.beginswith("MS")) type = Input::Type::MouseButton; + else type = Input::Type::Button; - string decode = mapping; - if(auto position = decode.position(".")) decode[position()] = 0; - scancode = Scancode::decode(decode); + string decode = mapping; + if(auto position = decode.position(".")) decode[position()] = 0; + unsigned scancode = Scancode::decode(decode); + + inputList.append({type, scancode}); + } } +bool AbstractInput::append(const string &encode) { + if(mapping.position(encode)) return false; //mapping already bound + if(mapping.empty() || mapping == "None") mapping = encode; //remove "None" + else mapping.append(",", encode); //add to existing mapping list + bind(); + return true; +} + +AbstractInput::AbstractInput() : state(false) { +} + +// + +bool DigitalInput::bind(unsigned scancode, int16_t value) { + using nall::Keyboard; + using nall::Mouse; + + if(scancode == Scancode::None || scancode == keyboard(0)[Keyboard::Escape]) { + inputList.reset(); + mapping = "None"; + return true; + } + + string encode = Scancode::encode(scancode); + + if(Keyboard::isAnyKey(scancode) || Keyboard::isAnyModifier(scancode) || Joypad::isAnyButton(scancode)) { + if(value == 0) return false; + return append(encode); + } + + if(Mouse::isAnyButton(scancode)) { + if(value == 0) return false; + return append(encode); + } + + if(Joypad::isAnyHat(scancode)) { + if(value & Joypad::HatUp ) { encode.append(".Up" ); return append(encode); } + if(value & Joypad::HatDown ) { encode.append(".Down" ); return append(encode); } + if(value & Joypad::HatLeft ) { encode.append(".Left" ); return append(encode); } + if(value & Joypad::HatRight) { encode.append(".Right"); return append(encode); } + } + + if(Joypad::isAnyAxis(scancode)) { + if(value < -12288) { encode.append(".Lo"); return append(encode); } + if(value > +24576) { encode.append(".Hi"); return append(encode); } + } + + return false; +} + +int16_t DigitalInput::poll() { + bool result = logic; + + for(auto &item : inputList) { + int16_t value = inputManager->poll(item.scancode); + bool output = logic; + switch(item.type) { + case Input::Type::Button: output = value; break; + case Input::Type::MouseButton: output = value & input.acquired(); break; + case Input::Type::HatUp: output = value & Joypad::HatUp; break; + case Input::Type::HatDown: output = value & Joypad::HatDown; break; + case Input::Type::HatLeft: output = value & Joypad::HatLeft; break; + case Input::Type::HatRight: output = value & Joypad::HatRight; break; + case Input::Type::AxisLo: output = value < -16384; break; + case Input::Type::AxisHi: output = value > +16384; break; + } + if(logic == 0) result |= output; + if(logic == 1) result &= output; + } + + return result; +} + +// + +bool AnalogInput::bind(unsigned scancode, int16_t value) { + using nall::Keyboard; + using nall::Mouse; + + if(scancode == Scancode::None || scancode == keyboard(0)[Keyboard::Escape]) { + inputList.reset(); + mapping = "None"; + return true; + } + + string encode = Scancode::encode(scancode); + + if(Mouse::isAnyAxis(scancode)) return append(encode); + if(Joypad::isAnyAxis(scancode)) return append(encode); + + return false; + +append: + if(mapping.position(encode)) return false; //mapping already bound + if(mapping.empty() || mapping == "None") mapping = encode; //remove "None" + else mapping.append(",", encode); //add to existing mapping list + AbstractInput::bind(); + return true; +} + +int16_t AnalogInput::poll() { + int16_t result = 0; + + for(auto &item : inputList) { + int16_t value = inputManager->poll(item.scancode); + switch(item.type) { + case Input::Type::MouseAxis: value = input.acquired() ? value : 0; break; + case Input::Type::Axis: value = value; break; + } + result += value; + } + + return result; +} + +// + void InputManager::bind() { - for(auto &input : inputMap) input.data.bind(); + for(auto &input : inputMap) input->bind(); + for(auto &input : hotkeyMap) input->bind(); } void InputManager::poll() { - input.poll(table); + activeScancode = !activeScancode; + input.poll(scancode[activeScancode]); + + for(unsigned n = 0; n < Scancode::Limit; n++) { + if(scancode[0][n] != scancode[1][n]) { + if(settings->focused()) { + inputSettings->inputEvent(n, scancode[activeScancode][n]); + hotkeySettings->inputEvent(n, scancode[activeScancode][n]); + } + } + } + + if(presentation->focused()) pollHotkeys(); } -int16_t InputManager::poll(unsigned guid) { - return table[inputMap[guid].scancode]; +int16_t InputManager::poll(unsigned scancode) { + return this->scancode[activeScancode][scancode]; +} + +void InputManager::saveConfiguration() { + config.save(application->path("input.cfg")); } InputManager::InputManager() { @@ -44,24 +185,29 @@ void InputManager::bootstrap() { for(auto &number : device.displayinput) { auto &input = device.input[number]; - AbstractInput abstract; - abstract.type = AbstractInput::Type::Button; - abstract.name = {emulator->information.name, "::", port.name, "::", device.name, "::", input.name}; - abstract.mapping = "None"; - abstract.scancode = 0; - abstract.name.replace(" ", ""); + AbstractInput *abstract = nullptr; + if(input.type == 0) abstract = new DigitalInput; + if(input.type == 1) abstract = new AnalogInput; + if(input.type >= 2) continue; + + abstract->name = {emulator->information.name, "::", port.name, "::", device.name, "::", input.name}; + abstract->name.replace(" ", ""); + abstract->mapping = "None"; + abstract->logic = 0; //OR input.guid = guid++; - inputMap(input.guid) = abstract; + inputMap.append(abstract); } } } } for(auto &input : inputMap) { - config.append(input.data.mapping, input.data.name); + config.append(input->mapping, input->name); } + appendHotkeys(); + config.load(application->path("input.cfg")); config.save(application->path("input.cfg")); diff --git a/bsnes/target-ethos/input/input.hpp b/bsnes/target-ethos/input/input.hpp index f68a1746..142a1df3 100755 --- a/bsnes/target-ethos/input/input.hpp +++ b/bsnes/target-ethos/input/input.hpp @@ -1,23 +1,56 @@ struct AbstractInput { - enum class Type : unsigned { Button, MouseButton, MouseAxis, HatUp, HatDown, HatLeft, HatRight, Axis, AxisLo, AxisHi } type; string name; string mapping; - unsigned scancode; + bool logic; //0 = OR, 1 = AND + bool state; + + struct Input { + enum class Type : unsigned { Button, MouseButton, MouseAxis, HatUp, HatDown, HatLeft, HatRight, Axis, AxisLo, AxisHi } type; + unsigned scancode; + }; + vector inputList; void bind(); + bool append(const string &mapping); + virtual bool bind(unsigned scancode, int16_t value) = 0; + virtual int16_t poll() = 0; + AbstractInput(); +}; + +struct DigitalInput : AbstractInput { + using AbstractInput::bind; + bool bind(unsigned scancode, int16_t value); + int16_t poll(); +}; + +struct AnalogInput : AbstractInput { + using AbstractInput::bind; + bool bind(unsigned scancode, int16_t value); + int16_t poll(); +}; + +struct HotkeyInput : DigitalInput { + function press; + function release; }; struct InputManager { - int16_t table[Scancode::Limit]; - - map inputMap; + vector inputMap; + vector hotkeyMap; + int16_t scancode[2][Scancode::Limit]; + bool activeScancode; void bind(); void poll(); - int16_t poll(unsigned guid); + int16_t poll(unsigned scancode); + void saveConfiguration(); void bootstrap(); InputManager(); + //hotkeys.cpp + void appendHotkeys(); + void pollHotkeys(); + private: configuration config; }; diff --git a/bsnes/target-ethos/interface/interface.cpp b/bsnes/target-ethos/interface/interface.cpp index 9ff86d0e..da0c8bfc 100755 --- a/bsnes/target-ethos/interface/interface.cpp +++ b/bsnes/target-ethos/interface/interface.cpp @@ -34,10 +34,15 @@ void Interface::videoRefresh(const uint32_t *data, unsigned pitch, unsigned widt } void Interface::audioSample(int16_t lsample, int16_t rsample) { - audio.sample(lsample, rsample); + signed samples[] = {lsample, rsample}; + dspaudio.sample(samples); + while(dspaudio.pending()) { + dspaudio.read(samples); + audio.sample(samples[0], samples[1]); + } } int16_t Interface::inputPoll(unsigned port, unsigned device, unsigned input) { unsigned guid = system().port[port].device[device].input[input].guid; - return inputManager->poll(guid); + return inputManager->inputMap[guid]->poll(); } diff --git a/bsnes/target-ethos/settings/hotkey.cpp b/bsnes/target-ethos/settings/hotkey.cpp new file mode 100755 index 00000000..1b5e8043 --- /dev/null +++ b/bsnes/target-ethos/settings/hotkey.cpp @@ -0,0 +1,66 @@ +HotkeySettings *hotkeySettings = nullptr; + +HotkeySettings::HotkeySettings() : activeInput(nullptr) { + title.setFont(application->titleFont); + title.setText("Hotkey Bindings"); + + inputList.setHeaderText("Name", "Mapping"); + inputList.setHeaderVisible(); + clearButton.setText("Clear"); + + append(title, {~0, 0}, 5); + append(inputList, {~0, ~0}, 5); + append(controlLayout, {~0, 0}); + controlLayout.append(spacer, {~0, 0}); + controlLayout.append(clearButton, {80, 0}); + + inputList.onChange = {&HotkeySettings::synchronize, this}; + inputList.onActivate = {&HotkeySettings::assignInput, this}; + clearButton.onActivate = {&HotkeySettings::clearInput, this}; + + refresh(); +} + +void HotkeySettings::synchronize() { + clearButton.setEnabled(inputList.selected()); +} + +void HotkeySettings::refresh() { + inputList.reset(); + for(auto &hotkey : inputManager->hotkeyMap) { + string mapping = hotkey->mapping; + mapping.replace(",", " and "); + inputList.append(hotkey->name, mapping); + } + synchronize(); +} + +void HotkeySettings::clearInput() { + activeInput = inputManager->hotkeyMap[inputList.selection()]; + inputEvent(Scancode::None, 1); +} + +void HotkeySettings::assignInput() { + activeInput = inputManager->hotkeyMap[inputList.selection()]; + + settings->setStatusText({"Set assignment for [", activeInput->name, "] ..."}); + settings->panelList.setEnabled(false); + inputList.setEnabled(false); + clearButton.setEnabled(false); +} + +void HotkeySettings::inputEvent(unsigned scancode, int16_t value) { + using nall::Mouse; + + if(activeInput == nullptr) return; + if(Mouse::isAnyButton(scancode) || Mouse::isAnyAxis(scancode)) return; + if(Joypad::isAnyAxis(scancode)) return; + if(activeInput->bind(scancode, value) == false) return; + + activeInput = nullptr; + settings->setStatusText(""); + settings->panelList.setEnabled(true); + inputList.setEnabled(true); + clearButton.setEnabled(true); + refresh(); +} diff --git a/bsnes/target-ethos/settings/hotkey.hpp b/bsnes/target-ethos/settings/hotkey.hpp new file mode 100755 index 00000000..9e194ce8 --- /dev/null +++ b/bsnes/target-ethos/settings/hotkey.hpp @@ -0,0 +1,19 @@ +struct HotkeySettings : SettingsLayout { + Label title; + ListView inputList; + HorizontalLayout controlLayout; + Widget spacer; + Button clearButton; + + void synchronize(); + void refresh(); + void clearInput(); + void assignInput(); + void inputEvent(unsigned scancode, int16_t value); + HotkeySettings(); + +private: + HotkeyInput *activeInput; +}; + +extern HotkeySettings *hotkeySettings; diff --git a/bsnes/target-ethos/settings/input.cpp b/bsnes/target-ethos/settings/input.cpp index 4e533a07..761cb024 100755 --- a/bsnes/target-ethos/settings/input.cpp +++ b/bsnes/target-ethos/settings/input.cpp @@ -1,6 +1,6 @@ InputSettings *inputSettings = nullptr; -InputSettings::InputSettings() { +InputSettings::InputSettings() : activeInput(nullptr) { title.setFont(application->titleFont); title.setText("Input Settings"); inputList.setHeaderText("Name", "Mapping"); @@ -28,11 +28,43 @@ InputSettings::InputSettings() { portList.onChange = {&InputSettings::portChanged, this}; deviceList.onChange = {&InputSettings::deviceChanged, this}; inputList.onChange = {&InputSettings::synchronize, this}; + inputList.onActivate = {&InputSettings::assignInput, this}; + assign[0].onActivate = [&] { assignMouseInput(0); }; + assign[1].onActivate = [&] { assignMouseInput(1); }; + assign[2].onActivate = [&] { assignMouseInput(2); }; + clearButton.onActivate = {&InputSettings::clearInput, this}; systemChanged(); } void InputSettings::synchronize() { + if(inputList.selected() == false) { + assign[0].setVisible(false); + assign[1].setVisible(false); + assign[2].setVisible(false); + } else { + unsigned number = activeDevice().displayinput[inputList.selection()]; + auto &input = activeDevice().input[number]; + activeInput = inputManager->inputMap[input.guid]; + + if(dynamic_cast(activeInput)) { + assign[0].setText("Mouse Left"); + assign[1].setText("Mouse Middle"); + assign[2].setText("Mouse Right"); + assign[0].setVisible(true); + assign[1].setVisible(true); + assign[2].setVisible(true); + } + + if(dynamic_cast(activeInput)) { + assign[0].setText("Mouse X-axis"); + assign[1].setText("Mouse Y-axis"); + assign[0].setVisible(true); + assign[1].setVisible(true); + assign[2].setVisible(false); + } + } + clearButton.setEnabled(inputList.selected()); } @@ -68,7 +100,69 @@ void InputSettings::deviceChanged() { inputList.reset(); for(unsigned number : activeDevice().displayinput) { auto &input = activeDevice().input[number]; - inputList.append(input.name, inputManager->inputMap(input.guid).mapping); + auto abstract = inputManager->inputMap(input.guid); + string mapping = abstract->mapping; + mapping.replace(",", " or "); + inputList.append(input.name, mapping); } synchronize(); } + +void InputSettings::clearInput() { + unsigned number = activeDevice().displayinput[inputList.selection()]; + auto &input = activeDevice().input[number]; + activeInput = inputManager->inputMap[input.guid]; + inputEvent(Scancode::None, 1); +} + +void InputSettings::assignInput() { + unsigned number = activeDevice().displayinput[inputList.selection()]; + auto &input = activeDevice().input[number]; + activeInput = inputManager->inputMap[input.guid]; + + settings->setStatusText({"Set assignment for [", activeDevice().name, "::", input.name, "] ..."}); + settings->panelList.setEnabled(false); + systemList.setEnabled(false); + portList.setEnabled(false); + deviceList.setEnabled(false); + inputList.setEnabled(false); + assign[0].setEnabled(false); + assign[1].setEnabled(false); + assign[2].setEnabled(false); + clearButton.setEnabled(false); +} + +void InputSettings::assignMouseInput(unsigned n) { + unsigned number = activeDevice().displayinput[inputList.selection()]; + auto &input = activeDevice().input[number]; + activeInput = inputManager->inputMap[input.guid]; + + if(dynamic_cast(activeInput)) { + return inputEvent(mouse(0).button(n), 1, true); + } + + if(dynamic_cast(activeInput)) { + return inputEvent(mouse(0).axis(n), 1, true); + } +} + +void InputSettings::inputEvent(unsigned scancode, int16_t value, bool allowMouseInput) { + using nall::Mouse; + if(activeInput == nullptr) return; + if(allowMouseInput == false && (Mouse::isAnyButton(scancode) || Mouse::isAnyAxis(scancode))) return; + if(activeInput->bind(scancode, value) == false) return; + + activeInput = nullptr; + deviceChanged(); + settings->setStatusText(""); + settings->panelList.setEnabled(true); + systemList.setEnabled(true); + portList.setEnabled(true); + deviceList.setEnabled(true); + inputList.setEnabled(true); + assign[0].setEnabled(true); + assign[1].setEnabled(true); + assign[2].setEnabled(true); + clearButton.setEnabled(true); + synchronize(); +} diff --git a/bsnes/target-ethos/settings/input.hpp b/bsnes/target-ethos/settings/input.hpp index c5754afd..a67a65ea 100755 --- a/bsnes/target-ethos/settings/input.hpp +++ b/bsnes/target-ethos/settings/input.hpp @@ -19,7 +19,14 @@ struct InputSettings : SettingsLayout { void systemChanged(); void portChanged(); void deviceChanged(); + void clearInput(); + void assignInput(); + void assignMouseInput(unsigned n); + void inputEvent(unsigned scancode, int16_t value, bool allowMouseInput = false); InputSettings(); + +private: + AbstractInput *activeInput; }; extern InputSettings *inputSettings; diff --git a/bsnes/target-ethos/settings/settings.cpp b/bsnes/target-ethos/settings/settings.cpp index 22ca2b93..bd5e166b 100755 --- a/bsnes/target-ethos/settings/settings.cpp +++ b/bsnes/target-ethos/settings/settings.cpp @@ -2,6 +2,7 @@ #include "video.cpp" #include "audio.cpp" #include "input.cpp" +#include "hotkey.cpp" Settings *settings = nullptr; void SettingsLayout::append(Sizable &sizable, const Size &size, unsigned spacing) { @@ -25,12 +26,14 @@ Settings::Settings() { panelList.append("Video"); panelList.append("Audio"); panelList.append("Input"); + panelList.append("Hotkeys"); append(layout); layout.append(panelList, {120, ~0}, 5); append(*videoSettings); append(*audioSettings); append(*inputSettings); + append(*hotkeySettings); panelList.onChange = {&Settings::panelChanged, this}; @@ -42,11 +45,13 @@ void Settings::panelChanged() { videoSettings->setVisible(false); audioSettings->setVisible(false); inputSettings->setVisible(false); + hotkeySettings->setVisible(false); if(panelList.selected() == false) return; switch(panelList.selection()) { case 0: return videoSettings->setVisible(); case 1: return audioSettings->setVisible(); case 2: return inputSettings->setVisible(); + case 3: return hotkeySettings->setVisible(); } } diff --git a/bsnes/target-ethos/settings/settings.hpp b/bsnes/target-ethos/settings/settings.hpp index 7fcc016a..e4b1ad9b 100755 --- a/bsnes/target-ethos/settings/settings.hpp +++ b/bsnes/target-ethos/settings/settings.hpp @@ -9,6 +9,7 @@ struct SettingsLayout : HorizontalLayout { #include "video.hpp" #include "audio.hpp" #include "input.hpp" +#include "hotkey.hpp" struct Settings : Window { HorizontalLayout layout; diff --git a/bsnes/target-ethos/utility/utility.cpp b/bsnes/target-ethos/utility/utility.cpp index 71f27725..1775eebe 100755 --- a/bsnes/target-ethos/utility/utility.cpp +++ b/bsnes/target-ethos/utility/utility.cpp @@ -21,6 +21,14 @@ void Utility::loadMedia(Emulator::Interface *emulator, Emulator::Interface::Medi filestream fs({pathname, memory.name}); system().load(memory.id, fs); } + + system().updatePalette(); + dspaudio.setFrequency(emulator->information.frequency); + + string displayname = pathname; + displayname.rtrim<1>("/"); + presentation->setTitle(notdir(nall::basename(displayname))); + presentation->setSystemName(media.displayname); } void Utility::saveMedia() { @@ -30,10 +38,12 @@ void Utility::saveMedia() { } void Utility::power() { + if(application->active == nullptr) return; system().power(); } void Utility::reset() { + if(application->active == nullptr) return; system().reset(); } @@ -43,6 +53,7 @@ void Utility::unload() { system().unload(); setInterface(nullptr); } + presentation->setTitle({Emulator::Name, " v", Emulator::Version}); video.clear(); }