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(); }