mirror of https://github.com/bsnes-emu/bsnes.git
Update to v088r09 release.
byuu says: Lots of work on ethos, nothing more. Settings window is in, InputManager pulls all the inputs from all cores and binds them to ruby inputs, main window adds menu and dynamically maps in all systems and cartridge slots and options and such, file browser's back in, RAM is loaded and saved, etc. It's barely usable, but you have to set up your inputs from the config file by hand for now.
This commit is contained in:
parent
4fd20f0ae0
commit
76553756a2
|
@ -1,7 +1,7 @@
|
|||
#ifndef EMULATOR_HPP
|
||||
#define EMULATOR_HPP
|
||||
|
||||
static const char Version[] = "088.08";
|
||||
static const char Version[] = "088.09";
|
||||
|
||||
#include <nall/platform.hpp>
|
||||
#include <nall/algorithm.hpp>
|
||||
|
|
|
@ -10,26 +10,52 @@ struct Interface {
|
|||
unsigned height;
|
||||
unsigned frequency;
|
||||
unsigned ports;
|
||||
bool resettable;
|
||||
} information;
|
||||
|
||||
struct Media {
|
||||
struct Firmware {
|
||||
string name;
|
||||
unsigned id;
|
||||
};
|
||||
vector<Firmware> firmware;
|
||||
|
||||
struct MediaObject {
|
||||
string displayname;
|
||||
string name;
|
||||
string filter;
|
||||
unsigned id;
|
||||
};
|
||||
|
||||
struct Media : MediaObject {
|
||||
vector<MediaObject> slot;
|
||||
};
|
||||
vector<Media> media;
|
||||
|
||||
struct Controller {
|
||||
struct Memory {
|
||||
string name;
|
||||
string port;
|
||||
string device;
|
||||
struct Input {
|
||||
unsigned id;
|
||||
uint8_t *data;
|
||||
unsigned size;
|
||||
};
|
||||
vector<Memory> memory;
|
||||
|
||||
struct Port {
|
||||
string name;
|
||||
unsigned id;
|
||||
struct Device {
|
||||
string name;
|
||||
unsigned id;
|
||||
struct Input {
|
||||
string name;
|
||||
unsigned id;
|
||||
unsigned guid;
|
||||
};
|
||||
vector<Input> input;
|
||||
vector<unsigned> displayinput;
|
||||
};
|
||||
vector<Input> inputs;
|
||||
vector<Device> device;
|
||||
};
|
||||
vector<Controller> controllers;
|
||||
vector<Port> port;
|
||||
|
||||
struct Callback {
|
||||
function<uint32_t (unsigned, uint16_t, uint16_t, uint16_t)> videoColor;
|
||||
|
@ -52,8 +78,8 @@ struct Interface {
|
|||
if(callback.audioSample) return callback.audioSample(lsample, rsample);
|
||||
}
|
||||
|
||||
virtual int16_t inputPoll(unsigned port, unsigned device, unsigned id) {
|
||||
if(callback.inputPoll) return callback.inputPoll(port, device, id);
|
||||
virtual int16_t inputPoll(unsigned port, unsigned device, unsigned input) {
|
||||
if(callback.inputPoll) return callback.inputPoll(port, device, input);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
|
@ -29,6 +29,8 @@ bool Cartridge::load(const string &markup, const stream &memory) {
|
|||
ram.size = numeral(info["size"].data);
|
||||
ram.mask = ram.size - 1;
|
||||
for(unsigned n = 0; n < ram.size; n++) ram.data[n] = 0xff;
|
||||
|
||||
interface->memory.append({"save.ram", 2, ram.data, ram.size});
|
||||
}
|
||||
|
||||
if(info["type"].data == "EEPROM") {
|
||||
|
@ -39,6 +41,8 @@ bool Cartridge::load(const string &markup, const stream &memory) {
|
|||
eeprom.mask = size > 16 * 1024 * 1024 ? 0x0fffff00 : 0x0f000000;
|
||||
eeprom.test = size > 16 * 1024 * 1024 ? 0x0dffff00 : 0x0d000000;
|
||||
for(unsigned n = 0; n < eeprom.size; n++) eeprom.data[n] = 0xff;
|
||||
|
||||
interface->memory.append({"save.ram", 3, eeprom.data, eeprom.size});
|
||||
}
|
||||
|
||||
if(info["type"].data == "FlashROM") {
|
||||
|
@ -46,6 +50,8 @@ bool Cartridge::load(const string &markup, const stream &memory) {
|
|||
flashrom.id = numeral(info["id"].data);
|
||||
flashrom.size = numeral(info["size"].data);
|
||||
for(unsigned n = 0; n < flashrom.size; n++) flashrom.data[n] = 0xff;
|
||||
|
||||
interface->memory.append({"save.ram", 4, flashrom.data, flashrom.size});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -8,9 +8,24 @@ bool Interface::loaded() {
|
|||
return cartridge.loaded();
|
||||
}
|
||||
|
||||
void Interface::load(unsigned id, const stream &memory, const string &markup) {
|
||||
if(id == 0) cartridge.load(markup, memory);
|
||||
if(id == 1) memory.read(bios.data, min(bios.size, memory.size()));
|
||||
void Interface::load(unsigned id, const stream &stream, const string &markup) {
|
||||
if(id == 0) {
|
||||
memory.reset();
|
||||
cartridge.load(markup, stream);
|
||||
system.power();
|
||||
}
|
||||
if(id == 1) {
|
||||
stream.read(bios.data, min(bios.size, stream.size()));
|
||||
}
|
||||
if(id == 2) {
|
||||
stream.read(cartridge.ram.data, min(cartridge.ram.size, stream.size()));
|
||||
}
|
||||
if(id == 3) {
|
||||
stream.read(cartridge.eeprom.data, min(cartridge.eeprom.size, stream.size()));
|
||||
}
|
||||
if(id == 4) {
|
||||
stream.read(cartridge.flashrom.data, min(cartridge.flashrom.size, stream.size()));
|
||||
}
|
||||
}
|
||||
|
||||
void Interface::unload() {
|
||||
|
@ -36,36 +51,51 @@ void Interface::updatePalette() {
|
|||
Interface::Interface() {
|
||||
interface = this;
|
||||
|
||||
information.name = "Game Boy Advance";
|
||||
information.width = 240;
|
||||
information.height = 160;
|
||||
information.frequency = 32768;
|
||||
information.ports = 1;
|
||||
information.name = "Game Boy Advance";
|
||||
information.width = 240;
|
||||
information.height = 160;
|
||||
information.frequency = 32768;
|
||||
information.ports = 1;
|
||||
information.resettable = false;
|
||||
|
||||
{
|
||||
Media medium;
|
||||
medium.name = "Game Boy Advance";
|
||||
medium.filter = "*.sfc";
|
||||
medium.id = 0;
|
||||
media.append(medium);
|
||||
Firmware firmware;
|
||||
firmware.name = "BIOS";
|
||||
firmware.id = 1;
|
||||
this->firmware.append(firmware);
|
||||
}
|
||||
|
||||
{
|
||||
Controller controller;
|
||||
controller.name = "Controller";
|
||||
controller.port = 0;
|
||||
controller.device = 0;
|
||||
controller.inputs.append({"Up", 6});
|
||||
controller.inputs.append({"Down", 7});
|
||||
controller.inputs.append({"Left", 5});
|
||||
controller.inputs.append({"Right", 4});
|
||||
controller.inputs.append({"B", 1});
|
||||
controller.inputs.append({"A", 0});
|
||||
controller.inputs.append({"L", 9});
|
||||
controller.inputs.append({"R", 8});
|
||||
controller.inputs.append({"Select", 2});
|
||||
controller.inputs.append({"Start", 3});
|
||||
controllers.append(controller);
|
||||
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::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);
|
||||
}
|
||||
this->port.append(port);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -68,6 +68,7 @@ struct map {
|
|||
}
|
||||
|
||||
inline RHS& operator()(const LHS &name) {
|
||||
if(auto position = find(name)) return list[position()].data;
|
||||
return insert(name, RHS());
|
||||
}
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@ include gb/Makefile
|
|||
include gba/Makefile
|
||||
name := ethos
|
||||
|
||||
ui_objects := ui-ethos ui-interface ui-general
|
||||
ui_objects := ui-ethos ui-interface ui-utility ui-input ui-general ui-settings
|
||||
ui_objects += phoenix ruby
|
||||
ui_objects += $(if $(call streq,$(platform),win),resource)
|
||||
|
||||
|
@ -40,7 +40,10 @@ objects := $(patsubst %,obj/%.o,$(objects))
|
|||
|
||||
obj/ui-ethos.o: $(ui)/ethos.cpp $(call rwildcard,$(ui)/)
|
||||
obj/ui-interface.o: $(ui)/interface/interface.cpp $(call rwildcard,$(ui)/)
|
||||
obj/ui-utility.o: $(ui)/utility/utility.cpp $(call rwildcard,$(ui)/)
|
||||
obj/ui-input.o: $(ui)/input/input.cpp $(call rwildcard,$(ui)/)
|
||||
obj/ui-general.o: $(ui)/general/general.cpp $(call rwildcard,$(ui)/)
|
||||
obj/ui-settings.o: $(ui)/settings/settings.cpp $(call rwildcard,$(ui)/)
|
||||
|
||||
obj/ruby.o: ruby/ruby.cpp $(call rwildcard,ruby/*)
|
||||
$(call compile,$(rubyflags))
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
#include <gba/interface/interface.hpp>
|
||||
|
||||
void Application::bootstrap() {
|
||||
interface = new Interface;
|
||||
|
||||
emulator.append(new GameBoyAdvance::Interface);
|
||||
|
||||
for(auto &system : emulator) {
|
||||
system->callback.videoColor = {&Interface::videoColor, interface};
|
||||
system->callback.videoRefresh = {&Interface::videoRefresh, interface};
|
||||
system->callback.audioSample = {&Interface::audioSample, interface};
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,14 +1,30 @@
|
|||
#include "ethos.hpp"
|
||||
#include "bootstrap.cpp"
|
||||
|
||||
Application *application = nullptr;
|
||||
|
||||
Emulator::Interface& system() {
|
||||
struct application_interface_null{};
|
||||
if(application->active == nullptr) throw application_interface_null();
|
||||
return *application->active;
|
||||
}
|
||||
|
||||
string Application::path(const string &filename) {
|
||||
string path = {basepath, filename};
|
||||
if(file::exists(path)) return path;
|
||||
if(directory::exists(path)) return path;
|
||||
return {userpath, filename};
|
||||
}
|
||||
|
||||
void Application::run() {
|
||||
inputManager->poll();
|
||||
|
||||
if(active == nullptr || system().loaded() == false) {
|
||||
usleep(20 * 1000);
|
||||
return;
|
||||
}
|
||||
|
||||
system().run();
|
||||
}
|
||||
|
||||
Application::Application(int argc, char **argv) {
|
||||
|
@ -23,21 +39,14 @@ Application::Application(int argc, char **argv) {
|
|||
unused = ::userpath(path);
|
||||
userpath = path;
|
||||
if(Intrinsics::platform() == Intrinsics::Platform::Windows) {
|
||||
userpath.append("bsnes/");
|
||||
userpath.append("ethos/");
|
||||
} else {
|
||||
userpath.append(".config/bsnes/");
|
||||
userpath.append(".config/ethos/");
|
||||
}
|
||||
mkdir(userpath, 0755);
|
||||
|
||||
interface = new Interface;
|
||||
|
||||
auto gba = new GameBoyAdvance::Interface;
|
||||
gba->callback.videoColor = {&Interface::videoColor, interface};
|
||||
gba->callback.videoRefresh = {&Interface::videoRefresh, interface};
|
||||
gba->callback.audioSample = {&Interface::audioSample, interface};
|
||||
gba->callback.inputPoll = {&Interface::inputPoll, interface};
|
||||
gba->updatePalette();
|
||||
emulators.append(gba);
|
||||
bootstrap();
|
||||
active = nullptr;
|
||||
|
||||
if(Intrinsics::platform() == Intrinsics::Platform::Windows) {
|
||||
normalFont = "Tahoma, 8";
|
||||
|
@ -51,39 +60,37 @@ Application::Application(int argc, char **argv) {
|
|||
monospaceFont = "Liberation Mono, 8";
|
||||
}
|
||||
|
||||
filestream bios{"/home/byuu/.config/bsnes/Game Boy Advance.sys/bios.rom"};
|
||||
gba->load(1, bios);
|
||||
utility = new Utility;
|
||||
inputManager = new InputManager;
|
||||
browser = new Browser;
|
||||
presentation = new Presentation;
|
||||
videoSettings = new VideoSettings;
|
||||
audioSettings = new AudioSettings;
|
||||
inputSettings = new InputSettings;
|
||||
settings = new Settings;
|
||||
|
||||
string manifest;
|
||||
manifest.readfile("/media/sdb1/root/cartridges/Game Boy Advance/Super Mario Advance (US).gba/manifest.xml");
|
||||
filestream fs{"/media/sdb1/root/cartridges/Game Boy Advance/Super Mario Advance (US).gba/program.rom"};
|
||||
gba->load(0, fs, manifest);
|
||||
gba->power();
|
||||
|
||||
videoWindow = new VideoWindow;
|
||||
|
||||
videoWindow->setVisible();
|
||||
presentation->setVisible();
|
||||
|
||||
video.driver("OpenGL");
|
||||
video.set(Video::Handle, videoWindow->viewport.handle());
|
||||
video.set(Video::Handle, presentation->viewport.handle());
|
||||
video.set(Video::Synchronize, false);
|
||||
video.set(Video::Depth, 24u);
|
||||
video.init();
|
||||
|
||||
audio.driver("ALSA");
|
||||
audio.set(Audio::Handle, videoWindow->viewport.handle());
|
||||
audio.set(Audio::Synchronize, true);
|
||||
audio.set(Audio::Handle, presentation->viewport.handle());
|
||||
audio.set(Audio::Synchronize, false);
|
||||
audio.set(Audio::Latency, 80u);
|
||||
audio.set(Audio::Frequency, 32768u);
|
||||
audio.init();
|
||||
|
||||
input.driver("SDL");
|
||||
input.set(Input::Handle, videoWindow->viewport.handle());
|
||||
input.set(Input::Handle, presentation->viewport.handle());
|
||||
input.init();
|
||||
|
||||
while(quit == false) {
|
||||
OS::processEvents();
|
||||
gba->run();
|
||||
run();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
#include <emulator/emulator.hpp>
|
||||
#include <gba/interface/interface.hpp>
|
||||
|
||||
#include <nall/platform.hpp>
|
||||
#include <nall/config.hpp>
|
||||
#include <nall/directory.hpp>
|
||||
#include <nall/map.hpp>
|
||||
#include <nall/stream/file.hpp>
|
||||
#include <nall/stream/memory.hpp>
|
||||
#include <nall/stream/vector.hpp>
|
||||
|
@ -15,10 +16,17 @@ using namespace phoenix;
|
|||
using namespace ruby;
|
||||
|
||||
#include "interface/interface.hpp"
|
||||
#include "utility/utility.hpp"
|
||||
#include "input/input.hpp"
|
||||
#include "general/general.hpp"
|
||||
#include "settings/settings.hpp"
|
||||
|
||||
Emulator::Interface& system();
|
||||
|
||||
struct Application {
|
||||
vector<Emulator::Interface*> emulators;
|
||||
vector<Emulator::Interface*> emulator;
|
||||
Emulator::Interface *active;
|
||||
|
||||
bool quit;
|
||||
bool pause;
|
||||
bool autopause;
|
||||
|
@ -34,6 +42,7 @@ struct Application {
|
|||
|
||||
string path(const string &filename);
|
||||
void run();
|
||||
void bootstrap();
|
||||
Application(int argc, char **argv);
|
||||
~Application();
|
||||
};
|
||||
|
|
|
@ -0,0 +1,106 @@
|
|||
Browser *browser = nullptr;
|
||||
|
||||
Browser::Browser() {
|
||||
setGeometry({128, 128, 640, 400});
|
||||
|
||||
layout.setMargin(5);
|
||||
pathBrowse.setText("Browse ...");
|
||||
pathUp.setText("..");
|
||||
openButton.setText("Open");
|
||||
|
||||
append(layout);
|
||||
layout.append(pathLayout, {~0, 0}, 5);
|
||||
pathLayout.append(pathEdit, {~0, 0}, 5);
|
||||
pathLayout.append(pathBrowse, {0, 0}, 5);
|
||||
pathLayout.append(pathUp, {0, 0});
|
||||
layout.append(fileList, {~0, ~0}, 5);
|
||||
layout.append(controlLayout, {~0, 0});
|
||||
controlLayout.append(filterLabel, {~0, 0}, 5);
|
||||
controlLayout.append(openButton, {80, 0});
|
||||
|
||||
pathEdit.onActivate = [&] {
|
||||
string path = pathEdit.text();
|
||||
path.transform("\\", "/");
|
||||
if(path.endswith("/") == false) path.append("/");
|
||||
setPath(path);
|
||||
};
|
||||
|
||||
pathBrowse.onActivate = [&] {
|
||||
string path = DialogWindow::folderSelect(*this, this->path);
|
||||
if(!path.empty()) setPath(path);
|
||||
};
|
||||
|
||||
pathUp.onActivate = [&] {
|
||||
if(this->path == "/") return;
|
||||
string path = this->path;
|
||||
path.rtrim<1>("/");
|
||||
path = dir(path);
|
||||
setPath(path);
|
||||
};
|
||||
|
||||
fileList.onChange = {&Browser::synchronize, this};
|
||||
fileList.onActivate = openButton.onActivate = {&Browser::fileListActivate, this};
|
||||
|
||||
synchronize();
|
||||
}
|
||||
|
||||
void Browser::open(Emulator::Interface::Media &media, function<void (string)> callback) {
|
||||
this->media = media;
|
||||
this->callback = callback;
|
||||
|
||||
setTitle({"Load ", media.displayname});
|
||||
setPath("/media/sdb1/root/cartridges/Game Boy Advance/");
|
||||
|
||||
filterLabel.setText({"Files of type: ", media.filter});
|
||||
|
||||
setVisible();
|
||||
}
|
||||
|
||||
void Browser::synchronize() {
|
||||
openButton.setEnabled(fileList.selected());
|
||||
}
|
||||
|
||||
void Browser::setPath(const string &path) {
|
||||
this->path = path;
|
||||
pathEdit.setText(path);
|
||||
|
||||
fileList.reset();
|
||||
filenameList.reset();
|
||||
|
||||
lstring contents = directory::contents(path);
|
||||
for(auto &filename : contents) {
|
||||
if(filename.endswith("/")) {
|
||||
filenameList.append(filename);
|
||||
} else if(filename.wildcard(media.filter)) {
|
||||
filenameList.append(filename);
|
||||
}
|
||||
}
|
||||
|
||||
for(auto &filename : filenameList) fileList.append(filename);
|
||||
fileList.setSelection(0);
|
||||
fileList.setFocused();
|
||||
synchronize();
|
||||
}
|
||||
|
||||
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) {
|
||||
setVisible(false);
|
||||
if(callback) callback(filename);
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
struct Browser : Window {
|
||||
VerticalLayout layout;
|
||||
HorizontalLayout pathLayout;
|
||||
LineEdit pathEdit;
|
||||
Button pathBrowse;
|
||||
Button pathUp;
|
||||
ListView fileList;
|
||||
HorizontalLayout controlLayout;
|
||||
Label filterLabel;
|
||||
Button openButton;
|
||||
|
||||
void open(Emulator::Interface::Media &media, function<void (string)> callback);
|
||||
Browser();
|
||||
|
||||
public:
|
||||
Emulator::Interface::Media media;
|
||||
function<void (string)> callback;
|
||||
string path;
|
||||
lstring filenameList;
|
||||
|
||||
void synchronize();
|
||||
void setPath(const string &path);
|
||||
void fileListActivate();
|
||||
bool loadFolder(const string &path);
|
||||
void loadFile(const string &filename);
|
||||
};
|
||||
|
||||
extern Browser *browser;
|
|
@ -1,2 +1,3 @@
|
|||
#include "../ethos.hpp"
|
||||
#include "video-window.cpp"
|
||||
#include "browser.cpp"
|
||||
#include "presentation.cpp"
|
||||
|
|
|
@ -1 +1,2 @@
|
|||
#include "video-window.hpp"
|
||||
#include "browser.hpp"
|
||||
#include "presentation.hpp"
|
||||
|
|
|
@ -0,0 +1,78 @@
|
|||
Presentation *presentation = nullptr;
|
||||
|
||||
void Presentation::synchronize() {
|
||||
for(auto &system : emulatorList) {
|
||||
system->menu.setVisible(system->interface == application->active);
|
||||
}
|
||||
}
|
||||
|
||||
Presentation::Presentation() {
|
||||
bootstrap();
|
||||
|
||||
setTitle("ethos");
|
||||
setGeometry({1024, 600, 720, 480});
|
||||
setBackgroundColor({0, 0, 0});
|
||||
setMenuFont(application->normalFont);
|
||||
setMenuVisible();
|
||||
setStatusFont(application->boldFont);
|
||||
setStatusVisible();
|
||||
|
||||
loadMenu.setText("Load");
|
||||
settingsMenu.setText("Settings");
|
||||
configurationSettings.setText("Configuration ...");
|
||||
toolsMenu.setText("Tools");
|
||||
|
||||
for(auto &system : emulatorList) {
|
||||
loadMenu.append(system->load);
|
||||
}
|
||||
append(loadMenu);
|
||||
for(auto &system : emulatorList) {
|
||||
append(system->menu);
|
||||
}
|
||||
append(settingsMenu);
|
||||
settingsMenu.append(configurationSettings);
|
||||
append(toolsMenu);
|
||||
|
||||
append(layout);
|
||||
layout.append(viewport, {0, 0, 720, 480});
|
||||
|
||||
onClose = [&] { application->quit = true; };
|
||||
|
||||
configurationSettings.onActivate = [&] { settings->setVisible(); };
|
||||
|
||||
synchronize();
|
||||
}
|
||||
|
||||
void Presentation::bootstrap() {
|
||||
for(auto &emulator : application->emulator) {
|
||||
System *system = new System;
|
||||
system->interface = emulator;
|
||||
|
||||
system->name = emulator->information.name;
|
||||
system->filter = "*.gba";
|
||||
|
||||
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->power.setText("Power");
|
||||
system->reset.setText("Reset");
|
||||
system->unload.setText("Unload");
|
||||
|
||||
system->menu.append(system->power);
|
||||
if(emulator->information.resettable)
|
||||
system->menu.append(system->reset);
|
||||
system->menu.append(system->separator);
|
||||
system->menu.append(system->unload);
|
||||
|
||||
system->power.onActivate = {&Utility::power, utility};
|
||||
system->reset.onActivate = {&Utility::reset, utility};
|
||||
system->unload.onActivate = {&Utility::unload, utility};
|
||||
|
||||
emulatorList.append(system);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
struct Presentation : Window {
|
||||
FixedLayout layout;
|
||||
Viewport viewport;
|
||||
|
||||
struct System {
|
||||
Emulator::Interface *interface;
|
||||
|
||||
string name;
|
||||
string filter;
|
||||
Item load;
|
||||
Menu menu;
|
||||
Item power;
|
||||
Item reset;
|
||||
Separator separator;
|
||||
Item unload;
|
||||
function<void (string)> callback;
|
||||
};
|
||||
vector<System*> emulatorList;
|
||||
|
||||
Menu loadMenu;
|
||||
Menu settingsMenu;
|
||||
Item configurationSettings;
|
||||
Menu toolsMenu;
|
||||
|
||||
void synchronize();
|
||||
void bootstrap();
|
||||
Presentation();
|
||||
};
|
||||
|
||||
extern Presentation *presentation;
|
|
@ -1,12 +0,0 @@
|
|||
VideoWindow *videoWindow = nullptr;
|
||||
|
||||
VideoWindow::VideoWindow() {
|
||||
setTitle("ethos");
|
||||
setGeometry({1024, 600, 720, 480});
|
||||
setBackgroundColor({0, 0, 0});
|
||||
|
||||
append(layout);
|
||||
layout.append(viewport, {0, 0, 720, 480});
|
||||
|
||||
onClose = [&] { application->quit = true; };
|
||||
}
|
|
@ -1,8 +0,0 @@
|
|||
struct VideoWindow : Window {
|
||||
FixedLayout layout;
|
||||
Viewport viewport;
|
||||
|
||||
VideoWindow();
|
||||
};
|
||||
|
||||
extern VideoWindow *videoWindow;
|
|
@ -0,0 +1,69 @@
|
|||
#include "../ethos.hpp"
|
||||
InputManager *inputManager = nullptr;
|
||||
|
||||
void AbstractInput::bind() {
|
||||
if(mapping.empty()) type = Type::Button, mapping = "None";
|
||||
|
||||
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;
|
||||
|
||||
string decode = mapping;
|
||||
if(auto position = decode.position(".")) decode[position()] = 0;
|
||||
scancode = Scancode::decode(decode);
|
||||
}
|
||||
|
||||
void InputManager::bind() {
|
||||
for(auto &input : inputMap) input.data.bind();
|
||||
}
|
||||
|
||||
void InputManager::poll() {
|
||||
input.poll(table);
|
||||
}
|
||||
|
||||
int16_t InputManager::poll(unsigned guid) {
|
||||
return table[inputMap[guid].scancode];
|
||||
}
|
||||
|
||||
InputManager::InputManager() {
|
||||
bootstrap();
|
||||
}
|
||||
|
||||
void InputManager::bootstrap() {
|
||||
unsigned guid = 0;
|
||||
for(auto &emulator : application->emulator) {
|
||||
for(auto &port : emulator->port) {
|
||||
for(auto &device : port.device) {
|
||||
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(" ", "");
|
||||
|
||||
input.guid = guid++;
|
||||
inputMap(input.guid) = abstract;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for(auto &input : inputMap) {
|
||||
config.append(input.data.mapping, input.data.name);
|
||||
}
|
||||
|
||||
config.load(application->path("input.cfg"));
|
||||
config.save(application->path("input.cfg"));
|
||||
|
||||
bind();
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
struct AbstractInput {
|
||||
enum class Type : unsigned { Button, MouseButton, MouseAxis, HatUp, HatDown, HatLeft, HatRight, Axis, AxisLo, AxisHi } type;
|
||||
string name;
|
||||
string mapping;
|
||||
unsigned scancode;
|
||||
|
||||
void bind();
|
||||
};
|
||||
|
||||
struct InputManager {
|
||||
int16_t table[Scancode::Limit];
|
||||
|
||||
map<unsigned, AbstractInput> inputMap;
|
||||
|
||||
void bind();
|
||||
void poll();
|
||||
int16_t poll(unsigned guid);
|
||||
void bootstrap();
|
||||
InputManager();
|
||||
|
||||
private:
|
||||
configuration config;
|
||||
};
|
||||
|
||||
extern InputManager *inputManager;
|
|
@ -20,29 +20,24 @@ void Interface::videoRefresh(const uint32_t *data, unsigned pitch, unsigned widt
|
|||
video.unlock();
|
||||
video.refresh();
|
||||
}
|
||||
|
||||
static unsigned frameCounter = 0;
|
||||
static time_t previous, current;
|
||||
frameCounter++;
|
||||
|
||||
time(¤t);
|
||||
if(current != previous) {
|
||||
previous = current;
|
||||
utility->setStatusText({"FPS: ", frameCounter});
|
||||
frameCounter = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void Interface::audioSample(int16_t lsample, int16_t rsample) {
|
||||
audio.sample(lsample, rsample);
|
||||
}
|
||||
|
||||
int16_t Interface::inputPoll(unsigned port, unsigned device, unsigned id) {
|
||||
using nall::Keyboard;
|
||||
static int16_t table[Scancode::Limit];
|
||||
if(id == 0) input.poll(table);
|
||||
|
||||
switch(id) {
|
||||
case 0: return table[keyboard(0)[Keyboard::X]]; //A
|
||||
case 1: return table[keyboard(0)[Keyboard::Z]]; //B
|
||||
case 2: return table[keyboard(0)[Keyboard::Apostrophe]]; //Select
|
||||
case 3: return table[keyboard(0)[Keyboard::Return]]; //Start
|
||||
case 4: return table[keyboard(0)[Keyboard::Right]]; //Right
|
||||
case 5: return table[keyboard(0)[Keyboard::Left]]; //Left
|
||||
case 6: return table[keyboard(0)[Keyboard::Up]]; //Up
|
||||
case 7: return table[keyboard(0)[Keyboard::Down]]; //Down
|
||||
case 8: return table[keyboard(0)[Keyboard::R]]; //R
|
||||
case 9: return table[keyboard(0)[Keyboard::L]]; //L
|
||||
}
|
||||
|
||||
return 0;
|
||||
int16_t Interface::inputPoll(unsigned port, unsigned device, unsigned input) {
|
||||
unsigned guid = system().port[port].device[device].input[input].guid;
|
||||
return inputManager->poll(guid);
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@ struct Interface {
|
|||
uint32_t videoColor(unsigned source, uint16_t red, uint16_t green, uint16_t blue);
|
||||
void videoRefresh(const uint32_t *data, unsigned pitch, unsigned width, unsigned height);
|
||||
void audioSample(int16_t lsample, int16_t rsample);
|
||||
int16_t inputPoll(unsigned port, unsigned device, unsigned id);
|
||||
int16_t inputPoll(unsigned port, unsigned device, unsigned input);
|
||||
};
|
||||
|
||||
extern Interface *interface;
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
AudioSettings *audioSettings = nullptr;
|
||||
|
||||
AudioSettings::AudioSettings() {
|
||||
title.setFont(application->titleFont);
|
||||
title.setText("Audio Settings");
|
||||
|
||||
append(title, {~0, 0}, 5);
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
struct AudioSettings : SettingsLayout {
|
||||
Label title;
|
||||
|
||||
AudioSettings();
|
||||
};
|
||||
|
||||
extern AudioSettings *audioSettings;
|
|
@ -0,0 +1,74 @@
|
|||
InputSettings *inputSettings = nullptr;
|
||||
|
||||
InputSettings::InputSettings() {
|
||||
title.setFont(application->titleFont);
|
||||
title.setText("Input Settings");
|
||||
inputList.setHeaderText("Name", "Mapping");
|
||||
inputList.setHeaderVisible();
|
||||
clearButton.setText("Clear");
|
||||
|
||||
append(title, {~0, 0}, 5);
|
||||
append(selectionLayout, {~0, 0}, 5);
|
||||
selectionLayout.append(systemList, {~0, 0}, 5);
|
||||
selectionLayout.append(portList, {~0, 0}, 5);
|
||||
selectionLayout.append(deviceList, {~0, 0});
|
||||
append(inputList, {~0, ~0}, 5);
|
||||
append(controlLayout, {~0, 0});
|
||||
controlLayout.append(assign[0], {100, 0}, 5);
|
||||
controlLayout.append(assign[1], {100, 0}, 5);
|
||||
controlLayout.append(assign[2], {100, 0}, 5);
|
||||
controlLayout.append(spacer, {~0, 0});
|
||||
controlLayout.append(clearButton, {80, 0});
|
||||
|
||||
for(auto &emulator : application->emulator) {
|
||||
systemList.append(emulator->information.name);
|
||||
}
|
||||
|
||||
systemList.onChange = {&InputSettings::systemChanged, this};
|
||||
portList.onChange = {&InputSettings::portChanged, this};
|
||||
deviceList.onChange = {&InputSettings::deviceChanged, this};
|
||||
inputList.onChange = {&InputSettings::synchronize, this};
|
||||
|
||||
systemChanged();
|
||||
}
|
||||
|
||||
void InputSettings::synchronize() {
|
||||
clearButton.setEnabled(inputList.selected());
|
||||
}
|
||||
|
||||
Emulator::Interface& InputSettings::activeSystem() {
|
||||
return *application->emulator[systemList.selection()];
|
||||
}
|
||||
|
||||
Emulator::Interface::Port& InputSettings::activePort() {
|
||||
return activeSystem().port[portList.selection()];
|
||||
}
|
||||
|
||||
Emulator::Interface::Port::Device& InputSettings::activeDevice() {
|
||||
return activePort().device[deviceList.selection()];
|
||||
}
|
||||
|
||||
void InputSettings::systemChanged() {
|
||||
portList.reset();
|
||||
for(auto &port : activeSystem().port) {
|
||||
portList.append(port.name);
|
||||
}
|
||||
portChanged();
|
||||
}
|
||||
|
||||
void InputSettings::portChanged() {
|
||||
deviceList.reset();
|
||||
for(auto &device : activePort().device) {
|
||||
deviceList.append(device.name);
|
||||
}
|
||||
deviceChanged();
|
||||
}
|
||||
|
||||
void InputSettings::deviceChanged() {
|
||||
inputList.reset();
|
||||
for(unsigned number : activeDevice().displayinput) {
|
||||
auto &input = activeDevice().input[number];
|
||||
inputList.append(input.name, inputManager->inputMap(input.guid).mapping);
|
||||
}
|
||||
synchronize();
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
struct InputSettings : SettingsLayout {
|
||||
Label title;
|
||||
HorizontalLayout selectionLayout;
|
||||
ComboBox systemList;
|
||||
ComboBox portList;
|
||||
ComboBox deviceList;
|
||||
ListView inputList;
|
||||
HorizontalLayout controlLayout;
|
||||
Button assign[3];
|
||||
Widget spacer;
|
||||
Button clearButton;
|
||||
|
||||
void synchronize();
|
||||
|
||||
Emulator::Interface& activeSystem();
|
||||
Emulator::Interface::Port& activePort();
|
||||
Emulator::Interface::Port::Device& activeDevice();
|
||||
|
||||
void systemChanged();
|
||||
void portChanged();
|
||||
void deviceChanged();
|
||||
InputSettings();
|
||||
};
|
||||
|
||||
extern InputSettings *inputSettings;
|
|
@ -0,0 +1,52 @@
|
|||
#include "../ethos.hpp"
|
||||
#include "video.cpp"
|
||||
#include "audio.cpp"
|
||||
#include "input.cpp"
|
||||
Settings *settings = nullptr;
|
||||
|
||||
void SettingsLayout::append(Sizable &sizable, const Size &size, unsigned spacing) {
|
||||
layout.append(sizable, size, spacing);
|
||||
}
|
||||
|
||||
SettingsLayout::SettingsLayout() {
|
||||
setMargin(5);
|
||||
HorizontalLayout::append(spacer, {120, ~0}, 5);
|
||||
HorizontalLayout::append(layout, { ~0, ~0});
|
||||
}
|
||||
|
||||
Settings::Settings() {
|
||||
setTitle("Configuration Settings");
|
||||
setGeometry({128, 128, 640, 360});
|
||||
setStatusFont(application->boldFont);
|
||||
setStatusVisible();
|
||||
|
||||
layout.setMargin(5);
|
||||
panelList.setFont(application->boldFont);
|
||||
panelList.append("Video");
|
||||
panelList.append("Audio");
|
||||
panelList.append("Input");
|
||||
|
||||
append(layout);
|
||||
layout.append(panelList, {120, ~0}, 5);
|
||||
append(*videoSettings);
|
||||
append(*audioSettings);
|
||||
append(*inputSettings);
|
||||
|
||||
panelList.onChange = {&Settings::panelChanged, this};
|
||||
|
||||
panelList.setSelection(2);
|
||||
panelChanged();
|
||||
}
|
||||
|
||||
void Settings::panelChanged() {
|
||||
videoSettings->setVisible(false);
|
||||
audioSettings->setVisible(false);
|
||||
inputSettings->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();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
struct SettingsLayout : HorizontalLayout {
|
||||
Widget spacer;
|
||||
VerticalLayout layout;
|
||||
|
||||
void append(Sizable &widget, const Size &size, unsigned spacing = 0);
|
||||
SettingsLayout();
|
||||
};
|
||||
|
||||
#include "video.hpp"
|
||||
#include "audio.hpp"
|
||||
#include "input.hpp"
|
||||
|
||||
struct Settings : Window {
|
||||
HorizontalLayout layout;
|
||||
ListView panelList;
|
||||
|
||||
void panelChanged();
|
||||
|
||||
Settings();
|
||||
};
|
||||
|
||||
extern Settings *settings;
|
|
@ -0,0 +1,8 @@
|
|||
VideoSettings *videoSettings = nullptr;
|
||||
|
||||
VideoSettings::VideoSettings() {
|
||||
title.setFont(application->titleFont);
|
||||
title.setText("Video Settings");
|
||||
|
||||
append(title, {~0, 0}, 5);
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
struct VideoSettings : SettingsLayout {
|
||||
Label title;
|
||||
|
||||
VideoSettings();
|
||||
};
|
||||
|
||||
extern VideoSettings *videoSettings;
|
|
@ -0,0 +1,51 @@
|
|||
#include "../ethos.hpp"
|
||||
|
||||
Utility *utility = nullptr;
|
||||
|
||||
void Utility::setInterface(Emulator::Interface *emulator) {
|
||||
application->active = emulator;
|
||||
presentation->synchronize();
|
||||
}
|
||||
|
||||
void Utility::loadMedia(Emulator::Interface *emulator, Emulator::Interface::Media &media, const string &pathname) {
|
||||
unload();
|
||||
setInterface(emulator);
|
||||
this->pathname = pathname;
|
||||
|
||||
string manifest;
|
||||
manifest.readfile({pathname, "manifest.xml"});
|
||||
auto memory = file::read({pathname, media.name});
|
||||
system().load(media.id, vectorstream{memory}, manifest);
|
||||
|
||||
for(auto &memory : system().memory) {
|
||||
filestream fs({pathname, memory.name});
|
||||
system().load(memory.id, fs);
|
||||
}
|
||||
}
|
||||
|
||||
void Utility::saveMedia() {
|
||||
for(auto &memory : system().memory) {
|
||||
file::write({pathname, memory.name}, memory.data, memory.size);
|
||||
}
|
||||
}
|
||||
|
||||
void Utility::power() {
|
||||
system().power();
|
||||
}
|
||||
|
||||
void Utility::reset() {
|
||||
system().reset();
|
||||
}
|
||||
|
||||
void Utility::unload() {
|
||||
if(application->active) {
|
||||
saveMedia();
|
||||
system().unload();
|
||||
setInterface(nullptr);
|
||||
}
|
||||
video.clear();
|
||||
}
|
||||
|
||||
void Utility::setStatusText(const string &text) {
|
||||
presentation->setStatusText(text);
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
struct Utility {
|
||||
string pathname;
|
||||
|
||||
void setInterface(Emulator::Interface *emulator);
|
||||
void loadMedia(Emulator::Interface *emulator, Emulator::Interface::Media &media, const string &pathname);
|
||||
void saveMedia();
|
||||
|
||||
void power();
|
||||
void reset();
|
||||
void unload();
|
||||
|
||||
void setStatusText(const string &text);
|
||||
};
|
||||
|
||||
extern Utility *utility;
|
Loading…
Reference in New Issue