mirror of https://github.com/bsnes-emu/bsnes.git
Update to v088r10 release.
byuu says: ethos is going to be absolutely amazing. You guys are in for a treat :D I'm impressing the hell out of myself with how well-structured this code is, it's allowing me to do amazing new things. Just a small sampling of what's in store (and already implemented): The file browser will display folders as "[ folder name ]", and cartridge folders as "Game Name" (no extension, no /) [icons would be nicer, but well ... phoenix.] Folders are sorted above cartridge folders. Cartridge folders for other systems do not show up in the list. Not only are unique paths stored for each image type, your position in the list is saved across runs. Some voodoo was added to GTK+ so that all targets even scroll directly to that item when you open the list. Load->System->Enter restarts your last game. That sounds really simple and obvious, but it makes an -incredible- difference. Didn't realize it until I tried an implementation of it, wow. The input mapping list now lets you bind as many hotkeys as you want to any given input. So SFC::Port1::Joypad::B = Keyboard::Z or Joypad::Button1 ... no need to remap everything to switch between keyboard and joypad. Either one activates the key. There is a separate Hotkeys tab now. This should hopefully end the confusion about how to remap hotkeys that users experience. Hotkeys are different, too. Instead of OR logic, they use AND logic. So Fullscreen = Keyboard::Alt and Keyboard::Enter. Both must be pressed to enter the key. This lets you easily implement "super" modifier keys. The actual codebase has new features the old UI never had, and has about ~50% of the old functionality (so far, of course), yet is only ~25% as much code. The entire GUI no longer needs to pull in all the headers for each emulated system. It just needs a small interface header file. Then bind the entire system with exactly **two** lines of code. Everything is dynamically generated for you after that.
This commit is contained in:
parent
76553756a2
commit
9ad8b7eaac
|
@ -6,7 +6,7 @@ gb := gb
|
|||
gba := gba
|
||||
|
||||
profile := accuracy
|
||||
target := ui
|
||||
target := ethos
|
||||
|
||||
# options += console
|
||||
|
||||
|
|
|
@ -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 <nall/platform.hpp>
|
||||
#include <nall/algorithm.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;
|
||||
};
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
#ifndef GAMEBOY_HPP
|
||||
#define GAMEBOY_HPP
|
||||
#ifndef GB_HPP
|
||||
#define GB_HPP
|
||||
|
||||
#include <emulator/emulator.hpp>
|
||||
#include <processor/lr35902/lr35902.hpp>
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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)) {
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -1,8 +1,14 @@
|
|||
#include <fc/interface/interface.hpp>
|
||||
#include <sfc/interface/interface.hpp>
|
||||
#include <gb/interface/interface.hpp>
|
||||
#include <gba/interface/interface.hpp>
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
#include <nall/platform.hpp>
|
||||
#include <nall/config.hpp>
|
||||
#include <nall/directory.hpp>
|
||||
#include <nall/dsp.hpp>
|
||||
#include <nall/map.hpp>
|
||||
#include <nall/stream/file.hpp>
|
||||
#include <nall/stream/memory.hpp>
|
||||
|
@ -48,3 +49,4 @@ struct Application {
|
|||
};
|
||||
|
||||
extern Application *application;
|
||||
extern DSP dspaudio;
|
||||
|
|
|
@ -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<void (string)> 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});
|
||||
}
|
||||
|
|
|
@ -10,19 +10,27 @@ struct Browser : Window {
|
|||
Button openButton;
|
||||
|
||||
void open(Emulator::Interface::Media &media, function<void (string)> callback);
|
||||
void saveConfiguration();
|
||||
void synchronize();
|
||||
void bootstrap();
|
||||
Browser();
|
||||
|
||||
public:
|
||||
private:
|
||||
configuration config;
|
||||
struct Folder {
|
||||
string filter;
|
||||
string path;
|
||||
unsigned selection;
|
||||
};
|
||||
vector<Folder> folderList;
|
||||
|
||||
Emulator::Interface::Media media;
|
||||
function<void (string)> 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;
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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<System*> emulatorList;
|
||||
|
||||
Menu loadMenu;
|
||||
vector<Item*> loadList;
|
||||
Menu settingsMenu;
|
||||
Item configurationSettings;
|
||||
Menu toolsMenu;
|
||||
|
||||
void synchronize();
|
||||
void setSystemName(const string &name);
|
||||
void bootstrap();
|
||||
Presentation();
|
||||
|
||||
private:
|
||||
System *activeSystem;
|
||||
};
|
||||
|
||||
extern Presentation *presentation;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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"));
|
||||
|
||||
|
|
|
@ -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<Input> 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<void ()> press;
|
||||
function<void ()> release;
|
||||
};
|
||||
|
||||
struct InputManager {
|
||||
int16_t table[Scancode::Limit];
|
||||
|
||||
map<unsigned, AbstractInput> inputMap;
|
||||
vector<AbstractInput*> inputMap;
|
||||
vector<HotkeyInput*> 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;
|
||||
};
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
|
@ -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;
|
|
@ -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<DigitalInput*>(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<AnalogInput*>(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<DigitalInput*>(activeInput)) {
|
||||
return inputEvent(mouse(0).button(n), 1, true);
|
||||
}
|
||||
|
||||
if(dynamic_cast<AnalogInput*>(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();
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@ struct SettingsLayout : HorizontalLayout {
|
|||
#include "video.hpp"
|
||||
#include "audio.hpp"
|
||||
#include "input.hpp"
|
||||
#include "hotkey.hpp"
|
||||
|
||||
struct Settings : Window {
|
||||
HorizontalLayout layout;
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue