diff --git a/Makefile b/Makefile index 817f860f..c5241339 100644 --- a/Makefile +++ b/Makefile @@ -6,8 +6,8 @@ gb := gb gba := gba profile := accuracy -# target := higan -target := loki +target := higan +# target := loki ifeq ($(target),loki) options += debugger diff --git a/emulator/emulator.hpp b/emulator/emulator.hpp index 7ce051a2..b6c5dfb2 100644 --- a/emulator/emulator.hpp +++ b/emulator/emulator.hpp @@ -3,7 +3,7 @@ namespace Emulator { static const char Name[] = "higan"; - static const char Version[] = "094.06"; + static const char Version[] = "094.07"; static const char Author[] = "byuu"; static const char License[] = "GPLv3"; static const char Website[] = "http://byuu.org/"; diff --git a/target-loki/Makefile b/target-loki/Makefile index ce4fa934..554d4855 100644 --- a/target-loki/Makefile +++ b/target-loki/Makefile @@ -6,7 +6,7 @@ include processor/Makefile include sfc/Makefile include gb/Makefile -ui_objects := ui-loki ui-settings +ui_objects := ui-loki ui-settings ui-input ui_objects += ui-interface ui-debugger ui_objects += ui-presentation ui-terminal ui_objects += ruby phoenix @@ -33,6 +33,7 @@ objects := $(patsubst %,obj/%.o,$(objects)) obj/ui-loki.o: $(ui)/loki.cpp $(call rwildcard,$(ui)/) obj/ui-settings.o: $(ui)/settings/settings.cpp $(call rwildcard,$(ui)/) +obj/ui-input.o: $(ui)/input/input.cpp $(call rwildcard,$(ui)/) obj/ui-interface.o: $(ui)/interface/interface.cpp $(call rwildcard,$(ui)/) obj/ui-debugger.o: $(ui)/debugger/debugger.cpp $(call rwildcard,$(ui)/) obj/ui-presentation.o: $(ui)/presentation/presentation.cpp $(call rwildcard,$(ui)/) diff --git a/target-loki/debugger/debugger.cpp b/target-loki/debugger/debugger.cpp index f1947680..200681fa 100644 --- a/target-loki/debugger/debugger.cpp +++ b/target-loki/debugger/debugger.cpp @@ -77,6 +77,7 @@ void Debugger::leave() { } bool Debugger::breakpointTest(Source source, Breakpoint::Mode mode, unsigned addr, uint8 data) { + if(savingState) return false; for(unsigned n = 0; n < breakpoints.size(); n++) { auto& bp = breakpoints[n]; if(bp.source != source) continue; @@ -123,6 +124,8 @@ void Debugger::cpuExec(uint24 addr) { } } + if(savingState) return; + if(breakpointTest(Source::CPU, Breakpoint::Mode::Execute, addr)) { echo(cpuDisassemble(), "\n"); return leave(); @@ -366,6 +369,8 @@ void Debugger::smpExec(uint16 addr) { } } + if(savingState) return; + if(breakpointTest(Source::SMP, Breakpoint::Mode::Execute, addr)) { echo(smpDisassemble(), "\n"); return leave(); @@ -423,6 +428,23 @@ string Debugger::sourceName(Source source) { return "none"; } +void Debugger::stateLoad(string filename) { + auto memory = file::read(filename); + if(memory.size() == 0) return echo("Error: state file ", notdir(filename), " not found\n"); + serializer s(memory.data(), memory.size()); + if(emulator->unserialize(s) == false) return echo("Error: failed to unserialize state from ", notdir(filename), "\n"); + echo("State loaded from ", notdir(filename), "\n"); +} + +void Debugger::stateSave(string filename) { + savingState = true; + serializer s = emulator->serialize(); + if(file::write(filename, s.data(), s.size())) { + echo("State saved to ", notdir(filename), "\n"); + } + savingState = false; +} + void Debugger::tracerDisable(Source source) { if(source != Source::CPU && source != Source::SMP) return; file& tracerFile = (source == Source::CPU ? cpuTracerFile : smpTracerFile); diff --git a/target-loki/debugger/debugger.hpp b/target-loki/debugger/debugger.hpp index 647b2993..3ed978cb 100644 --- a/target-loki/debugger/debugger.hpp +++ b/target-loki/debugger/debugger.hpp @@ -60,12 +60,15 @@ struct Debugger { void smpRead(uint16 addr, uint8 data); void smpWrite(uint16 addr, uint8 data); string sourceName(Source source); + void stateLoad(string filename); + void stateSave(string filename); void tracerDisable(Source source); void tracerEnable(Source source, string filename); void tracerMaskDisable(Source source); void tracerMaskEnable(Source source); - bool running = false; + bool running = false; //emulation runs asynchronously (cooperatively) to terminal commands + bool savingState = false; //suppresses all break events to allow state to be captured synchronously uint8* apuUsage = nullptr; vector breakpoints; diff --git a/target-loki/input/input.cpp b/target-loki/input/input.cpp new file mode 100644 index 00000000..a45105a4 --- /dev/null +++ b/target-loki/input/input.cpp @@ -0,0 +1,82 @@ +#include "../loki.hpp" +InputManager* inputManager = nullptr; + +void AbstractInput::bind() { + for(auto device : inputManager->devices) { + if(device->isKeyboard() == false) continue; + if(auto group = device->find("Button")) { + if(auto input = device->group[group()].find(mapping)) { + this->device = device; + this->group = group(); + this->input = input(); + break; + } + } + } +} + +int16_t AbstractInput::poll() { + if(device == nullptr) return 0; + return device->group[group].input[input].value; +} + +InputManager::InputManager() { + inputManager = this; +} + +void InputManager::load() { + unsigned guid = 0; + Configuration::Node emulatorNode; + + for(auto& port : emulator->port) { + Configuration::Node portNode; + + for(auto& device : port.device) { + Configuration::Node deviceNode; + + for(auto& number : device.order) { + auto& input = device.input[number]; + input.guid = guid++; + + auto abstract = new AbstractInput; + abstract->name = string{input.name}.replace(" ", ""); + abstract->mapping = "None"; + inputMap.append(abstract); + + deviceNode.append(abstract->mapping, abstract->name); + } + + portNode.append(deviceNode, string{device.name}.replace(" ", "")); + } + + emulatorNode.append(portNode, string{port.name}.replace(" ", "")); + } + + append(emulatorNode, "SuperFamicom"); + + Configuration::Document::load(program->path("input.bml")); + Configuration::Document::save(program->path("input.bml")); +} + +void InputManager::unload() { + Configuration::Document::save(program->path("input.bml")); +} + +void InputManager::bind() { + for(auto input : inputMap) input->bind(); +} + +void InputManager::poll() { + auto devices = input.poll(); + bool changed = devices.size() != this->devices.size(); + if(changed == false) { + for(unsigned n = 0; n < devices.size(); n++) { + changed = devices[n] != this->devices[n]; + if(changed) break; + } + } + if(changed == true) { + this->devices = devices; + bind(); + } +} diff --git a/target-loki/input/input.hpp b/target-loki/input/input.hpp new file mode 100644 index 00000000..d8b84a87 --- /dev/null +++ b/target-loki/input/input.hpp @@ -0,0 +1,25 @@ +struct AbstractInput { + void bind(); + int16_t poll(); + + string name; + string mapping; + + HID::Device* device = nullptr; + unsigned group = 0; + unsigned input = 0; +}; + +struct InputManager : Configuration::Document { + InputManager(); + void load(); + void unload(); + + void bind(); + void poll(); + + vector devices; + vector inputMap; +}; + +extern InputManager* inputManager; diff --git a/target-loki/interface/interface.cpp b/target-loki/interface/interface.cpp index 094ad718..388b30db 100644 --- a/target-loki/interface/interface.cpp +++ b/target-loki/interface/interface.cpp @@ -39,25 +39,6 @@ void Interface::unload() { emulator->unload(); } -void Interface::inputEvent(HID::Device& device, unsigned group, unsigned input, int16_t oldValue, int16_t newValue) { - if(device.isKeyboard() == false) return; - - switch(input) { - case 84: gamepad.up = newValue; break; - case 85: gamepad.down = newValue; break; - case 86: gamepad.left = newValue; break; - case 87: gamepad.right = newValue; break; - case 60: gamepad.b = newValue; break; - case 58: gamepad.a = newValue; break; - case 35: gamepad.y = newValue; break; - case 53: gamepad.x = newValue; break; - case 38: gamepad.l = newValue; break; - case 37: gamepad.r = newValue; break; - case 65: gamepad.select = newValue; break; - case 89: gamepad.start = newValue; break; - } -} - //bindings void Interface::loadRequest(unsigned id, string name, string type) { @@ -100,8 +81,6 @@ void Interface::videoRefresh(const uint32_t* palette, const uint32_t* data, unsi video.unlock(); video.refresh(); } - - input.poll(); } void Interface::audioSample(int16_t lsample, int16_t rsample) { @@ -115,25 +94,8 @@ void Interface::audioSample(int16_t lsample, int16_t rsample) { } int16_t Interface::inputPoll(unsigned port, unsigned device, unsigned input) { - if(presentation->focused() == false) return 0; - if(port != 0 || device != 0) return 0; - - switch(input) { - case 0: return gamepad.b; - case 1: return gamepad.y; - case 2: return gamepad.select; - case 3: return gamepad.start; - case 4: return gamepad.up; - case 5: return gamepad.down; - case 6: return gamepad.left; - case 7: return gamepad.right; - case 8: return gamepad.a; - case 9: return gamepad.x; - case 10: return gamepad.l; - case 11: return gamepad.r; - } - - return 0; + unsigned guid = emulator->port[port].device[device].input[input].guid; + return inputManager->inputMap[guid]->poll(); } void Interface::inputRumble(unsigned port, unsigned device, unsigned input, bool enable) { diff --git a/target-loki/interface/interface.hpp b/target-loki/interface/interface.hpp index 162f02f3..1438075e 100644 --- a/target-loki/interface/interface.hpp +++ b/target-loki/interface/interface.hpp @@ -2,7 +2,6 @@ struct Interface : Emulator::Interface::Bind { Interface(); bool load(string pathname); void unload(); - void inputEvent(HID::Device& device, unsigned group, unsigned input, int16_t oldValue, int16_t newValue); //bindings void loadRequest(unsigned id, string name, string type); @@ -20,20 +19,6 @@ struct Interface : Emulator::Interface::Bind { string pathname; //path to game folder lstring pathnames; - struct Gamepad { - bool up = false; - bool down = false; - bool left = false; - bool right = false; - bool b = false; - bool a = false; - bool y = false; - bool x = false; - bool l = false; - bool r = false; - bool select = false; - bool start = false; - } gamepad; }; extern Interface* interface; diff --git a/target-loki/loki.cpp b/target-loki/loki.cpp index 570a3f64..26232e1c 100644 --- a/target-loki/loki.cpp +++ b/target-loki/loki.cpp @@ -15,6 +15,7 @@ string Program::path(string name) { } void Program::main() { + inputManager->poll(); debugger->main(); } @@ -27,6 +28,7 @@ Program::Program(string pathname) { directory::create(userpath); new Settings; + new InputManager; new Interface; new Debugger; new Presentation; @@ -51,7 +53,7 @@ Program::Program(string pathname) { input.driver(settings->input.driver); input.set(Input::Handle, presentation->viewport.handle()); if(input.init() == false) { input.driver("None"); input.init(); } - input.onChange = {&Interface::inputEvent, interface}; + input.onChange = {&Terminal::inputEvent, terminal}; dspaudio.setPrecision(16); dspaudio.setBalance(0.0); @@ -61,15 +63,19 @@ Program::Program(string pathname) { presentation->showSplash(); + inputManager->load(); interface->load(pathname); debugger->load(); + terminal->load(); Application::main = {&Program::main, this}; Application::run(); + terminal->unload(); debugger->unload(); interface->unload(); - settings->save(); + inputManager->unload(); + settings->unload(); } int main(int argc, char** argv) { diff --git a/target-loki/loki.hpp b/target-loki/loki.hpp index 57347072..293b21ea 100644 --- a/target-loki/loki.hpp +++ b/target-loki/loki.hpp @@ -22,6 +22,7 @@ using namespace ruby; using namespace phoenix; #include "settings/settings.hpp" +#include "input/input.hpp" #include "interface/interface.hpp" #include "debugger/debugger.hpp" #include "presentation/presentation.hpp" diff --git a/target-loki/presentation/presentation.cpp b/target-loki/presentation/presentation.cpp index 147e83af..00a85862 100644 --- a/target-loki/presentation/presentation.cpp +++ b/target-loki/presentation/presentation.cpp @@ -3,9 +3,12 @@ Presentation* presentation = nullptr; Presentation::Presentation() { presentation = this; + if(settings->geometry.presentation.empty()) { + settings->geometry.presentation = "64,64,512,480"; + } setTitle({"loki v", Emulator::Version}); - setWindowGeometry({0, 0, 512, 480}); + setGeometry(settings->geometry.presentation); setResizable(false); layout.append(viewport, {0, 0, 512, 480}); diff --git a/target-loki/presentation/presentation.hpp b/target-loki/presentation/presentation.hpp index 6ddb7145..f5094b11 100644 --- a/target-loki/presentation/presentation.hpp +++ b/target-loki/presentation/presentation.hpp @@ -1,10 +1,11 @@ struct Presentation : Window { - FixedLayout layout; - Viewport viewport; - nall::image splash; - Presentation(); void showSplash(); + + FixedLayout layout; + Viewport viewport; + + nall::image splash; }; extern Presentation* presentation; diff --git a/target-loki/settings/settings.cpp b/target-loki/settings/settings.cpp index 1ccb2061..d4789a5c 100644 --- a/target-loki/settings/settings.cpp +++ b/target-loki/settings/settings.cpp @@ -16,15 +16,23 @@ Settings::Settings() { input.append(input.driver = ruby::input.optimalDriver(), "Driver"); append(input, "Input"); + geometry.append(geometry.presentation = "", "Presentation"); + geometry.append(geometry.terminal = "", "Terminal"); + append(geometry, "Geometry"); + load(); } void Settings::load() { Configuration::Document::load(program->path("settings.bml")); - save(); //create configuration file if it does not exist + Configuration::Document::save(program->path("settings.bml")); } -void Settings::save() { +void Settings::unload() { + //remember window geometry for next run + geometry.presentation = presentation->geometry().text(); + geometry.terminal = terminal->geometry().text(); + Configuration::Document::save(program->path("settings.bml")); } diff --git a/target-loki/settings/settings.hpp b/target-loki/settings/settings.hpp index fccf39a8..316e833a 100644 --- a/target-loki/settings/settings.hpp +++ b/target-loki/settings/settings.hpp @@ -14,9 +14,15 @@ struct Settings : Configuration::Document { string driver; } input; + struct Geometry : Configuration::Node { + string presentation; + string terminal; + } geometry; + Settings(); void load(); - void save(); + void unload(); + void command(string s, lstring args); }; diff --git a/target-loki/terminal/terminal.cpp b/target-loki/terminal/terminal.cpp index c77f4d00..bae88715 100644 --- a/target-loki/terminal/terminal.cpp +++ b/target-loki/terminal/terminal.cpp @@ -3,9 +3,13 @@ Terminal* terminal = nullptr; Terminal::Terminal() { terminal = this; + if(settings->geometry.terminal.empty()) { + unsigned y = 64 + presentation->geometry().height + presentation->frameMargin().height; + settings->geometry.terminal = {"64,", y, ",800,480"}; + } setTitle({"loki v", Emulator::Version}); - setWindowGeometry({0, 480 + frameMargin().height, 800, 480}); + setGeometry(settings->geometry.terminal); console.setFont(Font::monospace(8)); console.setPrompt("$ "); @@ -17,7 +21,52 @@ Terminal::Terminal() { console.onActivate = {&Terminal::command, this}; } +void Terminal::load() { + if(file::exists(program->path("aliases.cfg"))) { + string filedata = file::read(program->path("aliases.cfg")); + lstring lines = filedata.split("\n"); + for(auto& line : lines) { + lstring part = line.split<1>(" => "); + if(part.size() != 2) continue; + aliases.append({part[0], part[1]}); + } + } + + if(file::exists(program->path("hotkeys.cfg"))) { + string filedata = file::read(program->path("hotkeys.cfg")); + lstring lines = filedata.split("\n"); + for(auto& line : lines) { + lstring part = line.split<1>(" => "); + if(part.size() != 2) continue; + hotkeys.append({part[0], part[1]}); + } + } +} + +void Terminal::unload() { + file fp; + if(fp.open(program->path("aliases.cfg"), file::mode::write)) { + for(auto& alias : aliases) fp.print(alias.name, " => ", alias.mapping, "\n"); + fp.close(); + } + if(fp.open(program->path("hotkeys.cfg"), file::mode::write)) { + for(auto& hotkey : hotkeys) fp.print(hotkey.name, " => ", hotkey.mapping, "\n"); + fp.close(); + } +} + void Terminal::command(string t) { + for(auto& alias : aliases) { + lstring tokens; + if(tokenize(tokens, t, alias.name) == false) continue; + string output = alias.mapping; + for(unsigned n = 0; n < tokens.size(); n++) { + output.replace(string{"{", 1 + n, "}"}, tokens[n]); + } + t = output; + break; + } + auto source = Debugger::Source::CPU; if(t.beginsWith("cpu/" )) { source = Debugger::Source::CPU; t.ltrim<1>("cpu/" ); } if(t.beginsWith("smp/" )) { source = Debugger::Source::SMP; t.ltrim<1>("smp/" ); } @@ -32,14 +81,41 @@ void Terminal::command(string t) { if(source == Debugger::Source::CPU) t.replace("$", hex(SFC::cpu.regs.pc)); if(source == Debugger::Source::SMP) t.replace("$", hex(SFC::smp.regs.pc)); - lstring args = t.strip().qsplit(" ").strip(); - string s = args.takeFirst(); + lstring part = t.strip().split<1>(" "), args; + string s = part(0); + string p = part(1); + if(p) args = p.qsplit(" ").strip(); unsigned argc = args.size(); if(s.empty()) return; if(s.beginsWith("settings.")) return settings->command(s, args); + if(s == "aliases") { + echoAliases(); + return; + } + + if(s == "aliases.append") { + lstring part = p.qsplit<1>("=>").strip(); + if(part.size() == 2) aliases.append({part[0], part[1]}); + echoAliases(); + return; + } + + if(s == "aliases.remove" && argc == 1) { + unsigned n = decimal(args[0]); + if(n < aliases.size()) aliases.remove(n); + echoAliases(); + return; + } + + if(s == "aliases.reset") { + aliases.reset(); + echo("All aliases removed\n"); + return; + } + if(s == "break") { debugger->stop(); return; @@ -74,6 +150,7 @@ void Terminal::command(string t) { if(s == "breakpoints.reset") { debugger->breakpoints.reset(); + echo("All breakpoints removed\n"); return; } @@ -112,6 +189,37 @@ void Terminal::command(string t) { return; } + if(s == "hotkeys") { + echoHotkeys(); + return; + } + + if(s == "hotkeys.append") { + lstring part = p.qsplit<1>("=>").strip(); + if(part.size() == 2) hotkeys.append({part[0], part[1]}); + echoHotkeys(); + return; + } + + if(s == "hotkeys.remove" && argc == 1) { + unsigned n = decimal(args[0]); + if(n < hotkeys.size()) hotkeys.remove(n); + echoHotkeys(); + return; + } + + if(s == "hotkeys.reset") { + hotkeys.reset(); + echo("All hotkeys removed\n"); + return; + } + + if(s == "power") { + emulator->power(); + echo("System has been power cycled\n"); + return; + } + if(s == "quit") { Application::quit(); return; @@ -124,6 +232,12 @@ void Terminal::command(string t) { return; } + if(s == "reset") { + emulator->reset(); + echo("System has been reset\n"); + return; + } + if(s == "run" && argc == 0) { debugger->run(); return; @@ -143,6 +257,18 @@ void Terminal::command(string t) { return; } + if(s == "state.load" && argc == 1) { + string pathname = {interface->pathname, "loki/state-", args[0], ".bst"}; + debugger->stateLoad(pathname); + return; + } + + if(s == "state.save" && argc == 1) { + string pathname = {interface->pathname, "loki/state-", args[0], ".bst"}; + debugger->stateSave(pathname); + return; + } + if(s == "step" && argc == 0) { debugger->run(); if(source == Debugger::Source::CPU) debugger->cpuStepFor = 1u; @@ -204,6 +330,36 @@ void Terminal::command(string t) { echo("Error: unrecognized command: ", s, "\n"); } +void Terminal::echoAliases() { + if(aliases.size() == 0) return echo("No aliases defined\n"); + echo("# alias\n"); + echo("--- -----\n"); + for(unsigned n = 0; n < aliases.size(); n++) { + echo(format<-3>(n), " ", aliases[n].name, " => ", aliases[n].mapping, "\n"); + } +} + +void Terminal::echoHotkeys() { + if(hotkeys.size() == 0) return echo("No hotkeys defined\n"); + echo("# hotkey\n"); + echo("--- ------\n"); + for(unsigned n = 0; n < hotkeys.size(); n++) { + echo(format<-3>(n), " ", hotkeys[n].name, " => ", hotkeys[n].mapping, "\n"); + } +} + +void Terminal::inputEvent(HID::Device& device, unsigned group, unsigned input, int16_t oldValue, int16_t newValue) { + if(focused() == false) return; + if(device.isKeyboard() == false) return; //only capture keyboard events + if(oldValue != 0 || newValue != 1) return; //only capture key down events + string name = device.group[group].input[input].name; + + for(auto& hotkey : hotkeys) { + if(name != hotkey.name) continue; + command(hotkey.mapping); + } +} + void Terminal::reset() { console.reset(); } diff --git a/target-loki/terminal/terminal.hpp b/target-loki/terminal/terminal.hpp index 2ffd0120..5c0581fb 100644 --- a/target-loki/terminal/terminal.hpp +++ b/target-loki/terminal/terminal.hpp @@ -1,11 +1,30 @@ struct Terminal : Window { + struct Alias { + string name; + string mapping; + }; + + struct Hotkey { + string name; + string mapping; + }; + + Terminal(); + void load(); + void unload(); + + void command(string s); + void echoAliases(); + void echoHotkeys(); + void inputEvent(HID::Device& device, unsigned group, unsigned input, int16_t oldValue, int16_t newValue); + void reset(); + void print(const string& text); + VerticalLayout layout; Console console; - Terminal(); - void command(string s); - void reset(); - void print(const string& text); + vector aliases; + vector hotkeys; }; extern Terminal* terminal;