mirror of https://github.com/bsnes-emu/bsnes.git
Update to v094r07 release.
byuu says: Changelog for loki: - added command aliases (match with * [sorry, regex lib isn't available everywhere yet], replace with {1}+) - added command hotkeys - added window geometry saving - added save state support - added power/reset commands - added an input manager, so you can remap keys (limiting it to the keyboard for now though) The combination of aliases and hotkeys really makes things shine. Save states will temporarily disable your breakpoints (run/step are technically temporary breakpoints) so as to ensure the state is captured at a good time. In practice, this should pose about as much of a problem as higan desyncing and breaking when capturing states ... should be exceedingly rare to ever even notice this behavior at all, with 99.9% of state captures happening in half an instruction boundary. But still, keep it in mind, as you might see the CPU step one instruction ahead. Tracing and usage map functionality is still enabled during state synchronization. So at this point, I have 100% of the essential stuff in. All that's left now is to add polish / wishlist features like bass and mosaic integration.
This commit is contained in:
parent
3016e595f0
commit
ecc651c88b
4
Makefile
4
Makefile
|
@ -6,8 +6,8 @@ gb := gb
|
||||||
gba := gba
|
gba := gba
|
||||||
|
|
||||||
profile := accuracy
|
profile := accuracy
|
||||||
# target := higan
|
target := higan
|
||||||
target := loki
|
# target := loki
|
||||||
|
|
||||||
ifeq ($(target),loki)
|
ifeq ($(target),loki)
|
||||||
options += debugger
|
options += debugger
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
|
|
||||||
namespace Emulator {
|
namespace Emulator {
|
||||||
static const char Name[] = "higan";
|
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 Author[] = "byuu";
|
||||||
static const char License[] = "GPLv3";
|
static const char License[] = "GPLv3";
|
||||||
static const char Website[] = "http://byuu.org/";
|
static const char Website[] = "http://byuu.org/";
|
||||||
|
|
|
@ -6,7 +6,7 @@ include processor/Makefile
|
||||||
include sfc/Makefile
|
include sfc/Makefile
|
||||||
include gb/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-interface ui-debugger
|
||||||
ui_objects += ui-presentation ui-terminal
|
ui_objects += ui-presentation ui-terminal
|
||||||
ui_objects += ruby phoenix
|
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-loki.o: $(ui)/loki.cpp $(call rwildcard,$(ui)/)
|
||||||
obj/ui-settings.o: $(ui)/settings/settings.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-interface.o: $(ui)/interface/interface.cpp $(call rwildcard,$(ui)/)
|
||||||
obj/ui-debugger.o: $(ui)/debugger/debugger.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)/)
|
obj/ui-presentation.o: $(ui)/presentation/presentation.cpp $(call rwildcard,$(ui)/)
|
||||||
|
|
|
@ -77,6 +77,7 @@ void Debugger::leave() {
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Debugger::breakpointTest(Source source, Breakpoint::Mode mode, unsigned addr, uint8 data) {
|
bool Debugger::breakpointTest(Source source, Breakpoint::Mode mode, unsigned addr, uint8 data) {
|
||||||
|
if(savingState) return false;
|
||||||
for(unsigned n = 0; n < breakpoints.size(); n++) {
|
for(unsigned n = 0; n < breakpoints.size(); n++) {
|
||||||
auto& bp = breakpoints[n];
|
auto& bp = breakpoints[n];
|
||||||
if(bp.source != source) continue;
|
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)) {
|
if(breakpointTest(Source::CPU, Breakpoint::Mode::Execute, addr)) {
|
||||||
echo(cpuDisassemble(), "\n");
|
echo(cpuDisassemble(), "\n");
|
||||||
return leave();
|
return leave();
|
||||||
|
@ -366,6 +369,8 @@ void Debugger::smpExec(uint16 addr) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(savingState) return;
|
||||||
|
|
||||||
if(breakpointTest(Source::SMP, Breakpoint::Mode::Execute, addr)) {
|
if(breakpointTest(Source::SMP, Breakpoint::Mode::Execute, addr)) {
|
||||||
echo(smpDisassemble(), "\n");
|
echo(smpDisassemble(), "\n");
|
||||||
return leave();
|
return leave();
|
||||||
|
@ -423,6 +428,23 @@ string Debugger::sourceName(Source source) {
|
||||||
return "none";
|
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) {
|
void Debugger::tracerDisable(Source source) {
|
||||||
if(source != Source::CPU && source != Source::SMP) return;
|
if(source != Source::CPU && source != Source::SMP) return;
|
||||||
file& tracerFile = (source == Source::CPU ? cpuTracerFile : smpTracerFile);
|
file& tracerFile = (source == Source::CPU ? cpuTracerFile : smpTracerFile);
|
||||||
|
|
|
@ -60,12 +60,15 @@ struct Debugger {
|
||||||
void smpRead(uint16 addr, uint8 data);
|
void smpRead(uint16 addr, uint8 data);
|
||||||
void smpWrite(uint16 addr, uint8 data);
|
void smpWrite(uint16 addr, uint8 data);
|
||||||
string sourceName(Source source);
|
string sourceName(Source source);
|
||||||
|
void stateLoad(string filename);
|
||||||
|
void stateSave(string filename);
|
||||||
void tracerDisable(Source source);
|
void tracerDisable(Source source);
|
||||||
void tracerEnable(Source source, string filename);
|
void tracerEnable(Source source, string filename);
|
||||||
void tracerMaskDisable(Source source);
|
void tracerMaskDisable(Source source);
|
||||||
void tracerMaskEnable(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;
|
uint8* apuUsage = nullptr;
|
||||||
vector<Breakpoint> breakpoints;
|
vector<Breakpoint> breakpoints;
|
||||||
|
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<HID::Device*> devices;
|
||||||
|
vector<AbstractInput*> inputMap;
|
||||||
|
};
|
||||||
|
|
||||||
|
extern InputManager* inputManager;
|
|
@ -39,25 +39,6 @@ void Interface::unload() {
|
||||||
emulator->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
|
//bindings
|
||||||
|
|
||||||
void Interface::loadRequest(unsigned id, string name, string type) {
|
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.unlock();
|
||||||
video.refresh();
|
video.refresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
input.poll();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Interface::audioSample(int16_t lsample, int16_t rsample) {
|
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) {
|
int16_t Interface::inputPoll(unsigned port, unsigned device, unsigned input) {
|
||||||
if(presentation->focused() == false) return 0;
|
unsigned guid = emulator->port[port].device[device].input[input].guid;
|
||||||
if(port != 0 || device != 0) return 0;
|
return inputManager->inputMap[guid]->poll();
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Interface::inputRumble(unsigned port, unsigned device, unsigned input, bool enable) {
|
void Interface::inputRumble(unsigned port, unsigned device, unsigned input, bool enable) {
|
||||||
|
|
|
@ -2,7 +2,6 @@ struct Interface : Emulator::Interface::Bind {
|
||||||
Interface();
|
Interface();
|
||||||
bool load(string pathname);
|
bool load(string pathname);
|
||||||
void unload();
|
void unload();
|
||||||
void inputEvent(HID::Device& device, unsigned group, unsigned input, int16_t oldValue, int16_t newValue);
|
|
||||||
|
|
||||||
//bindings
|
//bindings
|
||||||
void loadRequest(unsigned id, string name, string type);
|
void loadRequest(unsigned id, string name, string type);
|
||||||
|
@ -20,20 +19,6 @@ struct Interface : Emulator::Interface::Bind {
|
||||||
|
|
||||||
string pathname; //path to game folder
|
string pathname; //path to game folder
|
||||||
lstring pathnames;
|
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;
|
extern Interface* interface;
|
||||||
|
|
|
@ -15,6 +15,7 @@ string Program::path(string name) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void Program::main() {
|
void Program::main() {
|
||||||
|
inputManager->poll();
|
||||||
debugger->main();
|
debugger->main();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -27,6 +28,7 @@ Program::Program(string pathname) {
|
||||||
directory::create(userpath);
|
directory::create(userpath);
|
||||||
|
|
||||||
new Settings;
|
new Settings;
|
||||||
|
new InputManager;
|
||||||
new Interface;
|
new Interface;
|
||||||
new Debugger;
|
new Debugger;
|
||||||
new Presentation;
|
new Presentation;
|
||||||
|
@ -51,7 +53,7 @@ Program::Program(string pathname) {
|
||||||
input.driver(settings->input.driver);
|
input.driver(settings->input.driver);
|
||||||
input.set(Input::Handle, presentation->viewport.handle());
|
input.set(Input::Handle, presentation->viewport.handle());
|
||||||
if(input.init() == false) { input.driver("None"); input.init(); }
|
if(input.init() == false) { input.driver("None"); input.init(); }
|
||||||
input.onChange = {&Interface::inputEvent, interface};
|
input.onChange = {&Terminal::inputEvent, terminal};
|
||||||
|
|
||||||
dspaudio.setPrecision(16);
|
dspaudio.setPrecision(16);
|
||||||
dspaudio.setBalance(0.0);
|
dspaudio.setBalance(0.0);
|
||||||
|
@ -61,15 +63,19 @@ Program::Program(string pathname) {
|
||||||
|
|
||||||
presentation->showSplash();
|
presentation->showSplash();
|
||||||
|
|
||||||
|
inputManager->load();
|
||||||
interface->load(pathname);
|
interface->load(pathname);
|
||||||
debugger->load();
|
debugger->load();
|
||||||
|
terminal->load();
|
||||||
|
|
||||||
Application::main = {&Program::main, this};
|
Application::main = {&Program::main, this};
|
||||||
Application::run();
|
Application::run();
|
||||||
|
|
||||||
|
terminal->unload();
|
||||||
debugger->unload();
|
debugger->unload();
|
||||||
interface->unload();
|
interface->unload();
|
||||||
settings->save();
|
inputManager->unload();
|
||||||
|
settings->unload();
|
||||||
}
|
}
|
||||||
|
|
||||||
int main(int argc, char** argv) {
|
int main(int argc, char** argv) {
|
||||||
|
|
|
@ -22,6 +22,7 @@ using namespace ruby;
|
||||||
using namespace phoenix;
|
using namespace phoenix;
|
||||||
|
|
||||||
#include "settings/settings.hpp"
|
#include "settings/settings.hpp"
|
||||||
|
#include "input/input.hpp"
|
||||||
#include "interface/interface.hpp"
|
#include "interface/interface.hpp"
|
||||||
#include "debugger/debugger.hpp"
|
#include "debugger/debugger.hpp"
|
||||||
#include "presentation/presentation.hpp"
|
#include "presentation/presentation.hpp"
|
||||||
|
|
|
@ -3,9 +3,12 @@ Presentation* presentation = nullptr;
|
||||||
|
|
||||||
Presentation::Presentation() {
|
Presentation::Presentation() {
|
||||||
presentation = this;
|
presentation = this;
|
||||||
|
if(settings->geometry.presentation.empty()) {
|
||||||
|
settings->geometry.presentation = "64,64,512,480";
|
||||||
|
}
|
||||||
|
|
||||||
setTitle({"loki v", Emulator::Version});
|
setTitle({"loki v", Emulator::Version});
|
||||||
setWindowGeometry({0, 0, 512, 480});
|
setGeometry(settings->geometry.presentation);
|
||||||
setResizable(false);
|
setResizable(false);
|
||||||
|
|
||||||
layout.append(viewport, {0, 0, 512, 480});
|
layout.append(viewport, {0, 0, 512, 480});
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
struct Presentation : Window {
|
struct Presentation : Window {
|
||||||
FixedLayout layout;
|
|
||||||
Viewport viewport;
|
|
||||||
nall::image splash;
|
|
||||||
|
|
||||||
Presentation();
|
Presentation();
|
||||||
void showSplash();
|
void showSplash();
|
||||||
|
|
||||||
|
FixedLayout layout;
|
||||||
|
Viewport viewport;
|
||||||
|
|
||||||
|
nall::image splash;
|
||||||
};
|
};
|
||||||
|
|
||||||
extern Presentation* presentation;
|
extern Presentation* presentation;
|
||||||
|
|
|
@ -16,15 +16,23 @@ Settings::Settings() {
|
||||||
input.append(input.driver = ruby::input.optimalDriver(), "Driver");
|
input.append(input.driver = ruby::input.optimalDriver(), "Driver");
|
||||||
append(input, "Input");
|
append(input, "Input");
|
||||||
|
|
||||||
|
geometry.append(geometry.presentation = "", "Presentation");
|
||||||
|
geometry.append(geometry.terminal = "", "Terminal");
|
||||||
|
append(geometry, "Geometry");
|
||||||
|
|
||||||
load();
|
load();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Settings::load() {
|
void Settings::load() {
|
||||||
Configuration::Document::load(program->path("settings.bml"));
|
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"));
|
Configuration::Document::save(program->path("settings.bml"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -14,9 +14,15 @@ struct Settings : Configuration::Document {
|
||||||
string driver;
|
string driver;
|
||||||
} input;
|
} input;
|
||||||
|
|
||||||
|
struct Geometry : Configuration::Node {
|
||||||
|
string presentation;
|
||||||
|
string terminal;
|
||||||
|
} geometry;
|
||||||
|
|
||||||
Settings();
|
Settings();
|
||||||
void load();
|
void load();
|
||||||
void save();
|
void unload();
|
||||||
|
|
||||||
void command(string s, lstring args);
|
void command(string s, lstring args);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -3,9 +3,13 @@ Terminal* terminal = nullptr;
|
||||||
|
|
||||||
Terminal::Terminal() {
|
Terminal::Terminal() {
|
||||||
terminal = this;
|
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});
|
setTitle({"loki v", Emulator::Version});
|
||||||
setWindowGeometry({0, 480 + frameMargin().height, 800, 480});
|
setGeometry(settings->geometry.terminal);
|
||||||
|
|
||||||
console.setFont(Font::monospace(8));
|
console.setFont(Font::monospace(8));
|
||||||
console.setPrompt("$ ");
|
console.setPrompt("$ ");
|
||||||
|
@ -17,7 +21,52 @@ Terminal::Terminal() {
|
||||||
console.onActivate = {&Terminal::command, this};
|
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) {
|
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;
|
auto source = Debugger::Source::CPU;
|
||||||
if(t.beginsWith("cpu/" )) { source = Debugger::Source::CPU; t.ltrim<1>("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/" ); }
|
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::CPU) t.replace("$", hex(SFC::cpu.regs.pc));
|
||||||
if(source == Debugger::Source::SMP) t.replace("$", hex(SFC::smp.regs.pc));
|
if(source == Debugger::Source::SMP) t.replace("$", hex(SFC::smp.regs.pc));
|
||||||
|
|
||||||
lstring args = t.strip().qsplit(" ").strip();
|
lstring part = t.strip().split<1>(" "), args;
|
||||||
string s = args.takeFirst();
|
string s = part(0);
|
||||||
|
string p = part(1);
|
||||||
|
if(p) args = p.qsplit(" ").strip();
|
||||||
unsigned argc = args.size();
|
unsigned argc = args.size();
|
||||||
|
|
||||||
if(s.empty()) return;
|
if(s.empty()) return;
|
||||||
|
|
||||||
if(s.beginsWith("settings.")) return settings->command(s, args);
|
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") {
|
if(s == "break") {
|
||||||
debugger->stop();
|
debugger->stop();
|
||||||
return;
|
return;
|
||||||
|
@ -74,6 +150,7 @@ void Terminal::command(string t) {
|
||||||
|
|
||||||
if(s == "breakpoints.reset") {
|
if(s == "breakpoints.reset") {
|
||||||
debugger->breakpoints.reset();
|
debugger->breakpoints.reset();
|
||||||
|
echo("All breakpoints removed\n");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -112,6 +189,37 @@ void Terminal::command(string t) {
|
||||||
return;
|
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") {
|
if(s == "quit") {
|
||||||
Application::quit();
|
Application::quit();
|
||||||
return;
|
return;
|
||||||
|
@ -124,6 +232,12 @@ void Terminal::command(string t) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(s == "reset") {
|
||||||
|
emulator->reset();
|
||||||
|
echo("System has been reset\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if(s == "run" && argc == 0) {
|
if(s == "run" && argc == 0) {
|
||||||
debugger->run();
|
debugger->run();
|
||||||
return;
|
return;
|
||||||
|
@ -143,6 +257,18 @@ void Terminal::command(string t) {
|
||||||
return;
|
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) {
|
if(s == "step" && argc == 0) {
|
||||||
debugger->run();
|
debugger->run();
|
||||||
if(source == Debugger::Source::CPU) debugger->cpuStepFor = 1u;
|
if(source == Debugger::Source::CPU) debugger->cpuStepFor = 1u;
|
||||||
|
@ -204,6 +330,36 @@ void Terminal::command(string t) {
|
||||||
echo("Error: unrecognized command: ", s, "\n");
|
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() {
|
void Terminal::reset() {
|
||||||
console.reset();
|
console.reset();
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,30 @@
|
||||||
struct Terminal : Window {
|
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;
|
VerticalLayout layout;
|
||||||
Console console;
|
Console console;
|
||||||
|
|
||||||
Terminal();
|
vector<Alias> aliases;
|
||||||
void command(string s);
|
vector<Hotkey> hotkeys;
|
||||||
void reset();
|
|
||||||
void print(const string& text);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
extern Terminal* terminal;
|
extern Terminal* terminal;
|
||||||
|
|
Loading…
Reference in New Issue