mirror of https://github.com/bsnes-emu/bsnes.git
Update to v088r14 release.
byuu says: Changelog: - added NSS DIP switch settings window (when loading NSS carts with appropriate manifest.xml file) - added video shader selection (they go in ~/.config/bsnes/Video Shaders/ now) - added driver selection - added timing settings (not only allows video/audio settings, also has code to dynamically compute the values for you ... and it actually works pretty good!) - moved "None" controller device to bottom of list (it is the least likely to be used, after all) - added Interface::path() to support MSU1, USART, Link - input and hotkey mappings remember list position after assignment - and more! target-ethos now has all of the functionality of target-ui, and more. Final code size for the port is 101.2KB (ethos) vs 167.6KB (ui). A ~67% reduction in code size, yet it does even more! And you can add or remove an entire system with only three lines of code (Makefile include, header include, interface append.) The only problem left is that the BS-X BIOS won't load the BS Zelda no Densetsu file. I can't figure out why it's not working, would appreciate any assistance, but otherwise I'm probably just going to leave it broken for v089, sorry. So the show stoppers for a new release at this point are: - fix laevateinn to compile with the new interface changes (shouldn't be too hard, it'll still use the old, direct interface.) - clean up Emulator::Interface as much as possible (trim down Information, mediaRequest should use an alternate struct designed to load firmware / slots separately) - enhance purify to strip SNES ROM headers, and it really needs a GUI interface - it would be highly desirable to make a launcher that can create a cartridge folder from an existing ROM set (* ethos will need to accept command-line arguments for this.) - probably need to remember which controller was selected in each port for each system across runs - need to fix the cursor for Super Scope / Justifier games (move from 19-bit to 32-bit colors broke it) - have to refactor that cache.(hv)offset thing to fix ASP
This commit is contained in:
parent
3cb04b101b
commit
cb97d98ad2
|
@ -3,7 +3,7 @@
|
|||
|
||||
namespace Emulator {
|
||||
static const char Name[] = "bsnes";
|
||||
static const char Version[] = "088.13";
|
||||
static const char Version[] = "088.14";
|
||||
static const char Author[] = "byuu";
|
||||
static const char License[] = "GPLv3";
|
||||
}
|
||||
|
|
|
@ -59,6 +59,8 @@ struct Interface {
|
|||
function<void (int16_t, int16_t)> audioSample;
|
||||
function<int16_t (unsigned, unsigned, unsigned)> inputPoll;
|
||||
function<void (Media)> mediaRequest;
|
||||
function<unsigned (const XML::Node&)> dipSettings;
|
||||
function<string (unsigned)> path;
|
||||
} callback;
|
||||
|
||||
//callback bindings (provided by user interface)
|
||||
|
@ -84,6 +86,20 @@ struct Interface {
|
|||
if(callback.mediaRequest) return callback.mediaRequest(media);
|
||||
}
|
||||
|
||||
virtual unsigned dipSettings(const XML::Node &node) {
|
||||
if(callback.dipSettings) return callback.dipSettings(node);
|
||||
return 0u;
|
||||
}
|
||||
|
||||
virtual string path(unsigned group) {
|
||||
if(callback.path) return callback.path(group);
|
||||
return "";
|
||||
}
|
||||
|
||||
//information
|
||||
virtual double videoFrequency() = 0;
|
||||
virtual double audioFrequency() = 0;
|
||||
|
||||
//media interface
|
||||
virtual bool loaded() { return false; }
|
||||
virtual string sha256() { return ""; }
|
||||
|
|
|
@ -4,6 +4,14 @@ namespace Famicom {
|
|||
|
||||
Interface *interface = nullptr;
|
||||
|
||||
double Interface::videoFrequency() {
|
||||
return 21477272.0 / (262.0 * 1364.0 - 4.0);
|
||||
}
|
||||
|
||||
double Interface::audioFrequency() {
|
||||
return 21477272.0 / 12.0;
|
||||
}
|
||||
|
||||
bool Interface::loaded() {
|
||||
return cartridge.loaded();
|
||||
}
|
||||
|
|
|
@ -15,6 +15,9 @@ struct ID {
|
|||
};
|
||||
|
||||
struct Interface : Emulator::Interface {
|
||||
double videoFrequency();
|
||||
double audioFrequency();
|
||||
|
||||
bool loaded();
|
||||
string sha256();
|
||||
void load(unsigned id, const stream &stream, const string &markup = "");
|
||||
|
|
|
@ -4,6 +4,14 @@ namespace GameBoy {
|
|||
|
||||
Interface *interface = nullptr;
|
||||
|
||||
double Interface::videoFrequency() {
|
||||
return 4194304.0 / (154.0 * 456.0);
|
||||
}
|
||||
|
||||
double Interface::audioFrequency() {
|
||||
return 4194304.0;
|
||||
}
|
||||
|
||||
bool Interface::loaded() {
|
||||
return cartridge.loaded();
|
||||
}
|
||||
|
|
|
@ -22,6 +22,9 @@ struct Interface : Emulator::Interface {
|
|||
virtual void lcdScanline() {}
|
||||
virtual void joypWrite(bool p15, bool p14) {}
|
||||
|
||||
double videoFrequency();
|
||||
double audioFrequency();
|
||||
|
||||
bool loaded();
|
||||
string sha256();
|
||||
void load(unsigned id, const stream &stream, const string &markup = "");
|
||||
|
|
|
@ -4,6 +4,14 @@ namespace GameBoyAdvance {
|
|||
|
||||
Interface *interface = nullptr;
|
||||
|
||||
double Interface::videoFrequency() {
|
||||
return 16777216.0 / (228.0 * 1232.0);
|
||||
}
|
||||
|
||||
double Interface::audioFrequency() {
|
||||
return 16777216.0 / 512.0;
|
||||
}
|
||||
|
||||
bool Interface::loaded() {
|
||||
return cartridge.loaded();
|
||||
}
|
||||
|
|
|
@ -17,6 +17,9 @@ struct ID {
|
|||
};
|
||||
|
||||
struct Interface : Emulator::Interface {
|
||||
double videoFrequency();
|
||||
double audioFrequency();
|
||||
|
||||
bool loaded();
|
||||
void load(unsigned id, const stream &stream, const string &markup = "");
|
||||
void save(unsigned id, const stream &stream);
|
||||
|
|
|
@ -17,6 +17,8 @@
|
|||
namespace nall {
|
||||
|
||||
struct directory {
|
||||
static bool create(const string &pathname, unsigned permissions = 0755);
|
||||
static bool remove(const string &pathname);
|
||||
static bool exists(const string &pathname);
|
||||
static lstring folders(const string &pathname, const string &pattern = "*");
|
||||
static lstring files(const string &pathname, const string &pattern = "*");
|
||||
|
@ -24,6 +26,14 @@ struct directory {
|
|||
};
|
||||
|
||||
#if defined(PLATFORM_WINDOWS)
|
||||
inline bool directory::create(const string &pathname, unsigned permissions) {
|
||||
return _wmkdir(utf16_t(pathname)) == 0;
|
||||
}
|
||||
|
||||
inline bool directory::remove(const string &pathname) {
|
||||
return _wrmdir(utf16_t(pathname)) == 0;
|
||||
}
|
||||
|
||||
inline bool directory::exists(const string &pathname) {
|
||||
DWORD result = GetFileAttributes(utf16_t(pathname));
|
||||
if(result == INVALID_FILE_ATTRIBUTES) return false;
|
||||
|
@ -94,6 +104,14 @@ struct directory {
|
|||
return folders;
|
||||
}
|
||||
#else
|
||||
inline bool directory::create(const string &pathname, unsigned permissions) {
|
||||
return mkdir(pathname, permissions) == 0;
|
||||
}
|
||||
|
||||
inline bool directory::remove(const string &pathname) {
|
||||
return rmdir(pathname) == 0;
|
||||
}
|
||||
|
||||
inline bool directory::exists(const string &pathname) {
|
||||
DIR *dp = opendir(pathname);
|
||||
if(!dp) return false;
|
||||
|
|
|
@ -22,6 +22,24 @@ namespace nall {
|
|||
enum class index : unsigned { absolute, relative };
|
||||
enum class time : unsigned { create, modify, access };
|
||||
|
||||
static bool remove(const string &filename) {
|
||||
return unlink(filename) == 0;
|
||||
}
|
||||
|
||||
static bool truncate(const string &filename, unsigned size) {
|
||||
#if !defined(_WIN32)
|
||||
return truncate(filename, size) == 0;
|
||||
#else
|
||||
bool result = false;
|
||||
FILE *fp = fopen(filename, "rb+");
|
||||
if(fp) {
|
||||
result = _chsize(fileno(fp), size) == 0;
|
||||
fclose(fp);
|
||||
}
|
||||
return result;
|
||||
#endif
|
||||
}
|
||||
|
||||
static vector<uint8_t> read(const string &filename) {
|
||||
vector<uint8_t> memory;
|
||||
file fp;
|
||||
|
|
|
@ -60,10 +60,7 @@
|
|||
|
||||
#if defined(_WIN32)
|
||||
#define getcwd _getcwd
|
||||
#define ftruncate _chsize
|
||||
#define mkdir(n, m) _wmkdir(nall::utf16_t(n))
|
||||
#define putenv _putenv
|
||||
#define rmdir _rmdir
|
||||
#define vsnprintf _vsnprintf
|
||||
inline void usleep(unsigned milliseconds) { Sleep(milliseconds / 1000); }
|
||||
#endif
|
||||
|
|
|
@ -562,6 +562,10 @@ void RadioItem::setText(const string &text) {
|
|||
return p.setText(text);
|
||||
}
|
||||
|
||||
string RadioItem::text() {
|
||||
return state.text;
|
||||
}
|
||||
|
||||
RadioItem::RadioItem():
|
||||
state(*new State),
|
||||
base_from_member<pRadioItem&>(*new pRadioItem(*this)),
|
||||
|
@ -850,6 +854,14 @@ void ComboBox::setSelection(unsigned row) {
|
|||
return p.setSelection(row);
|
||||
}
|
||||
|
||||
string ComboBox::text() {
|
||||
return state.text(selection());
|
||||
}
|
||||
|
||||
string ComboBox::text(unsigned row) {
|
||||
return state.text(row);
|
||||
}
|
||||
|
||||
ComboBox::ComboBox():
|
||||
state(*new State),
|
||||
base_from_member<pComboBox&>(*new pComboBox(*this)),
|
||||
|
|
|
@ -289,6 +289,7 @@ struct RadioItem : private nall::base_from_member<pRadioItem&>, Action {
|
|||
bool checked();
|
||||
void setChecked();
|
||||
void setText(const nall::string &text);
|
||||
nall::string text();
|
||||
|
||||
RadioItem();
|
||||
~RadioItem();
|
||||
|
@ -403,6 +404,8 @@ struct ComboBox : private nall::base_from_member<pComboBox&>, Widget {
|
|||
void reset();
|
||||
unsigned selection();
|
||||
void setSelection(unsigned row);
|
||||
nall::string text();
|
||||
nall::string text(unsigned row);
|
||||
|
||||
ComboBox();
|
||||
~ComboBox();
|
||||
|
|
|
@ -9,7 +9,6 @@ namespace SuperFamicom {
|
|||
Cartridge cartridge;
|
||||
|
||||
void Cartridge::load(const string &markup, const stream &stream) {
|
||||
information.markup = markup;
|
||||
rom.copy(stream);
|
||||
|
||||
region = Region::NTSC;
|
||||
|
|
|
@ -68,14 +68,6 @@ struct Cartridge : property<Cartridge> {
|
|||
};
|
||||
linear_vector<Mapping> mapping;
|
||||
|
||||
struct Information {
|
||||
string markup;
|
||||
struct NSS {
|
||||
lstring setting;
|
||||
lstring option[16];
|
||||
} nss;
|
||||
} information;
|
||||
|
||||
void load(const string &markup, const stream &stream);
|
||||
void unload();
|
||||
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
|
||||
void Cartridge::parse_markup(const char *markup) {
|
||||
mapping.reset();
|
||||
information.nss.setting.reset();
|
||||
|
||||
XML::Document document(markup);
|
||||
auto &cartridge = document["cartridge"];
|
||||
|
@ -89,20 +88,8 @@ void Cartridge::parse_markup_ram(XML::Node &root) {
|
|||
void Cartridge::parse_markup_nss(XML::Node &root) {
|
||||
if(root.exists() == false) return;
|
||||
has_nss_dip = true;
|
||||
for(auto &node : root) {
|
||||
if(node.name != "setting") continue;
|
||||
unsigned number = information.nss.setting.size();
|
||||
if(number >= 16) break; //more than 16 DIP switches is not physically possible
|
||||
|
||||
information.nss.option[number].reset();
|
||||
information.nss.setting.append(node["name"].data);
|
||||
for(auto &leaf : node) {
|
||||
if(leaf.name != "option") continue;
|
||||
string name = leaf["name"].data;
|
||||
unsigned value = numeral(leaf["value"].data);
|
||||
information.nss.option[number].append({ hex<4>(value), ":", name });
|
||||
}
|
||||
}
|
||||
nss.dip = interface->dipSettings(root);
|
||||
}
|
||||
|
||||
void Cartridge::parse_markup_icd2(XML::Node &root) {
|
||||
|
@ -467,7 +454,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((unsigned)Cartridge::Slot::Base, "msu1.rom"));
|
||||
has_msu1 = file::exists({interface->path(0), "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,10 +22,7 @@ void Link::init() {
|
|||
|
||||
void Link::load() {
|
||||
if(opened()) close();
|
||||
string basename = interface->path((unsigned)Cartridge::Slot::Base, "");
|
||||
string name = program != "" ? program : notdir(basename);
|
||||
string path = dir(basename);
|
||||
if(open(name, path)) {
|
||||
if(open("link.so", interface->path(0))) {
|
||||
link_power = sym("link_power");
|
||||
link_reset = sym("link_reset");
|
||||
link_run = sym("link_run" );
|
||||
|
|
|
@ -52,7 +52,7 @@ void MSU1::init() {
|
|||
|
||||
void MSU1::load() {
|
||||
if(datafile.open()) datafile.close();
|
||||
datafile.open(interface->path((unsigned)Cartridge::Slot::Base, "msu1.rom"), file::mode::read);
|
||||
datafile.open({interface->path(0), "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((unsigned)Cartridge::Slot::Base, { "track-", (unsigned)mmio.audio_track, ".pcm" }), file::mode::read)) {
|
||||
if(audiofile.open({interface->path(0), "track-", 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((unsigned)Cartridge::Slot::Base, "msu1.rom"), file::mode::read)) {
|
||||
if(datafile.open({interface->path(0), "msu1.rom"}, file::mode::read)) {
|
||||
datafile.seek(mmio.data_offset);
|
||||
}
|
||||
|
||||
if(audiofile.open()) audiofile.close();
|
||||
if(audiofile.open(interface->path((unsigned)Cartridge::Slot::Base, { "track-", (unsigned)mmio.audio_track, ".pcm" }), file::mode::read)) {
|
||||
if(audiofile.open({interface->path(0), "track-", mmio.audio_track, ".pcm"}, file::mode::read)) {
|
||||
audiofile.seek(mmio.audio_offset);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,10 +6,10 @@ namespace SuperFamicom {
|
|||
NSS nss;
|
||||
|
||||
void NSS::init() {
|
||||
dip = 0x0000;
|
||||
}
|
||||
|
||||
void NSS::load() {
|
||||
dip = 0x0000;
|
||||
bus.map(Bus::MapMode::Direct, 0x00, 0x3f, 0x4100, 0x4101, { &NSS::read, this }, { &NSS::write, this });
|
||||
bus.map(Bus::MapMode::Direct, 0x80, 0xbf, 0x4100, 0x4101, { &NSS::read, this }, { &NSS::write, this });
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
class NSS {
|
||||
public:
|
||||
struct NSS {
|
||||
uint16 dip;
|
||||
|
||||
void init();
|
||||
void load();
|
||||
void unload();
|
||||
|
@ -9,9 +10,6 @@ public:
|
|||
void set_dip(uint16 dip);
|
||||
uint8 read(unsigned addr);
|
||||
void write(unsigned addr, uint8 data);
|
||||
|
||||
private:
|
||||
uint16 dip;
|
||||
};
|
||||
|
||||
extern NSS nss;
|
||||
|
|
|
@ -122,7 +122,7 @@ USART::USART(bool port) : Controller(port) {
|
|||
txlength = 0;
|
||||
txdata = 0;
|
||||
|
||||
string filename = interface->path((unsigned)Cartridge::Slot::Base, "usart.so");
|
||||
string filename = {interface->path(0), "usart.so"};
|
||||
if(open_absolute(filename)) {
|
||||
init = sym("usart_init");
|
||||
main = sym("usart_main");
|
||||
|
|
|
@ -4,6 +4,17 @@ namespace SuperFamicom {
|
|||
|
||||
Interface *interface = nullptr;
|
||||
|
||||
double Interface::videoFrequency() {
|
||||
switch(system.region()) { default:
|
||||
case System::Region::NTSC: return system.cpu_frequency() / (262.0 * 1364.0 - 4.0);
|
||||
case System::Region::PAL: return system.cpu_frequency() / (312.0 * 1364.0);
|
||||
}
|
||||
}
|
||||
|
||||
double Interface::audioFrequency() {
|
||||
return system.apu_frequency() / 768.0;
|
||||
}
|
||||
|
||||
bool Interface::loaded() {
|
||||
return cartridge.loaded();
|
||||
}
|
||||
|
@ -236,12 +247,7 @@ Interface::Interface() {
|
|||
media.append({ID::ROM, "Sufami Turbo", "sfc", "program.rom", "st" });
|
||||
|
||||
{
|
||||
Device device{0, ID::Port1 | ID::Port2, "None"};
|
||||
this->device.append(device);
|
||||
}
|
||||
|
||||
{
|
||||
Device device{1, ID::Port1 | ID::Port2, "Controller"};
|
||||
Device device{0, ID::Port1 | ID::Port2, "Controller"};
|
||||
device.input.append({ 0, 0, "B" });
|
||||
device.input.append({ 1, 0, "Y" });
|
||||
device.input.append({ 2, 0, "Select"});
|
||||
|
@ -259,7 +265,7 @@ Interface::Interface() {
|
|||
}
|
||||
|
||||
{
|
||||
Device device{2, ID::Port1 | ID::Port2, "Multitap"};
|
||||
Device device{1, ID::Port1 | ID::Port2, "Multitap"};
|
||||
for(unsigned p = 1, n = 0; p <= 4; p++, n += 12) {
|
||||
device.input.append({n + 0, 0, {"Port ", p, " - ", "B" }});
|
||||
device.input.append({n + 1, 0, {"Port ", p, " - ", "Y" }});
|
||||
|
@ -280,7 +286,7 @@ Interface::Interface() {
|
|||
}
|
||||
|
||||
{
|
||||
Device device{3, ID::Port1 | ID::Port2, "Mouse"};
|
||||
Device device{2, ID::Port1 | ID::Port2, "Mouse"};
|
||||
device.input.append({0, 1, "X-axis"});
|
||||
device.input.append({1, 1, "Y-axis"});
|
||||
device.input.append({2, 0, "Left" });
|
||||
|
@ -290,7 +296,7 @@ Interface::Interface() {
|
|||
}
|
||||
|
||||
{
|
||||
Device device{4, ID::Port2, "Super Scope"};
|
||||
Device device{3, ID::Port2, "Super Scope"};
|
||||
device.input.append({0, 1, "X-axis" });
|
||||
device.input.append({1, 1, "Y-axis" });
|
||||
device.input.append({2, 0, "Trigger"});
|
||||
|
@ -302,7 +308,7 @@ Interface::Interface() {
|
|||
}
|
||||
|
||||
{
|
||||
Device device{5, ID::Port2, "Justifier"};
|
||||
Device device{4, ID::Port2, "Justifier"};
|
||||
device.input.append({0, 1, "X-axis" });
|
||||
device.input.append({1, 1, "Y-axis" });
|
||||
device.input.append({2, 0, "Trigger"});
|
||||
|
@ -312,7 +318,7 @@ Interface::Interface() {
|
|||
}
|
||||
|
||||
{
|
||||
Device device{6, ID::Port2, "Justifiers"};
|
||||
Device device{5, ID::Port2, "Justifiers"};
|
||||
device.input.append({0, 1, "Port 1 - X-axis" });
|
||||
device.input.append({1, 1, "Port 1 - Y-axis" });
|
||||
device.input.append({2, 0, "Port 1 - Trigger"});
|
||||
|
@ -327,7 +333,12 @@ Interface::Interface() {
|
|||
}
|
||||
|
||||
{
|
||||
Device device{7, ID::Port1, "Serial USART"};
|
||||
Device device{6, ID::Port1, "Serial USART"};
|
||||
this->device.append(device);
|
||||
}
|
||||
|
||||
{
|
||||
Device device{7, ID::Port1 | ID::Port2, "None"};
|
||||
this->device.append(device);
|
||||
}
|
||||
|
||||
|
|
|
@ -33,8 +33,8 @@ struct ID {
|
|||
};
|
||||
|
||||
struct Interface : Emulator::Interface {
|
||||
virtual string path(unsigned slot, const string &hint) { return ""; }
|
||||
virtual void message(const string &text) {}
|
||||
double videoFrequency();
|
||||
double audioFrequency();
|
||||
|
||||
bool loaded();
|
||||
string sha256();
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
struct Input {
|
||||
enum class Device : unsigned {
|
||||
None,
|
||||
Joypad,
|
||||
Multitap,
|
||||
Mouse,
|
||||
|
@ -8,6 +7,7 @@ struct Input {
|
|||
Justifier,
|
||||
Justifiers,
|
||||
USART,
|
||||
None,
|
||||
};
|
||||
|
||||
enum class JoypadID : unsigned {
|
||||
|
|
|
@ -94,6 +94,15 @@ void System::term() {
|
|||
}
|
||||
|
||||
void System::load() {
|
||||
region = config.region;
|
||||
expansion = config.expansion_port;
|
||||
if(region == Region::Autodetect) {
|
||||
region = (cartridge.region() == Cartridge::Region::NTSC ? Region::NTSC : Region::PAL);
|
||||
}
|
||||
|
||||
cpu_frequency = region() == Region::NTSC ? config.cpu.ntsc_frequency : config.cpu.pal_frequency;
|
||||
apu_frequency = region() == Region::NTSC ? config.smp.ntsc_frequency : config.smp.pal_frequency;
|
||||
|
||||
audio.coprocessor_enable(false);
|
||||
|
||||
bus.map_reset();
|
||||
|
@ -151,15 +160,6 @@ void System::unload() {
|
|||
void System::power() {
|
||||
random.seed((unsigned)time(0));
|
||||
|
||||
region = config.region;
|
||||
expansion = config.expansion_port;
|
||||
if(region == Region::Autodetect) {
|
||||
region = (cartridge.region() == Cartridge::Region::NTSC ? Region::NTSC : Region::PAL);
|
||||
}
|
||||
|
||||
cpu_frequency = region() == Region::NTSC ? config.cpu.ntsc_frequency : config.cpu.pal_frequency;
|
||||
apu_frequency = region() == Region::NTSC ? config.smp.ntsc_frequency : config.smp.pal_frequency;
|
||||
|
||||
cpu.power();
|
||||
smp.power();
|
||||
dsp.power();
|
||||
|
|
|
@ -17,6 +17,8 @@ void Application::bootstrap() {
|
|||
system->callback.audioSample = {&Interface::audioSample, interface};
|
||||
system->callback.inputPoll = {&Interface::inputPoll, interface};
|
||||
system->callback.mediaRequest = {&Interface::mediaRequest, interface};
|
||||
system->callback.dipSettings = {&Interface::dipSettings, interface};
|
||||
system->callback.path = {&Interface::path, interface};
|
||||
|
||||
for(auto &firmware : system->firmware) {
|
||||
filestream fs{application->path({firmware.name, ".", firmware.type, "/", firmware.path})};
|
||||
|
|
|
@ -2,7 +2,9 @@
|
|||
Configuration *config = nullptr;
|
||||
|
||||
Configuration::Configuration() {
|
||||
append(video.driver = ruby::video.default_driver(), "Video::Driver");
|
||||
append(video.synchronize = false, "Video::Synchronize");
|
||||
append(video.shader = "Blur", "Video::Shader");
|
||||
append(video.scaleMode = 0, "Video::ScaleMode");
|
||||
append(video.aspectCorrection = true, "Video::AspectCorrection");
|
||||
append(video.maskOverscan = false, "Video::MaskOverscan");
|
||||
|
@ -11,15 +13,18 @@ Configuration::Configuration() {
|
|||
append(video.saturation = 100, "Video::Saturation");
|
||||
append(video.gamma = 150, "Video::Gamma");
|
||||
append(video.luminance = 100, "Video::Luminance");
|
||||
append(audio.driver = ruby::audio.default_driver(), "Audio::Driver");
|
||||
append(audio.synchronize = true, "Audio::Synchronize");
|
||||
append(audio.frequency = 48000, "Audio::Frequency");
|
||||
append(audio.latency = 60, "Audio::Latency");
|
||||
append(audio.resampler = 2, "Audio::Resampler");
|
||||
append(audio.volume = 100, "Audio::Volume");
|
||||
append(audio.mute = false, "Audio::Mute");
|
||||
append(input.driver = ruby::input.default_driver(), "Input::Driver");
|
||||
append(input.focusPause = false, "Input::Focus::Pause");
|
||||
append(input.focusAllow = false, "Input::Focus::AllowInput");
|
||||
|
||||
append(timing.video = 60.0, "Timing::Video");
|
||||
append(timing.audio = 48000.0, "Timing::Audio");
|
||||
load();
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
struct Configuration : configuration {
|
||||
struct Video {
|
||||
string driver;
|
||||
bool synchronize;
|
||||
string shader;
|
||||
unsigned scaleMode;
|
||||
bool aspectCorrection;
|
||||
bool maskOverscan;
|
||||
|
@ -12,6 +14,7 @@ struct Configuration : configuration {
|
|||
} video;
|
||||
|
||||
struct Audio {
|
||||
string driver;
|
||||
bool synchronize;
|
||||
unsigned frequency;
|
||||
unsigned latency;
|
||||
|
@ -21,10 +24,16 @@ struct Configuration : configuration {
|
|||
} audio;
|
||||
|
||||
struct Input {
|
||||
string driver;
|
||||
bool focusPause;
|
||||
bool focusAllow;
|
||||
} input;
|
||||
|
||||
struct Timing {
|
||||
double video;
|
||||
double audio;
|
||||
} timing;
|
||||
|
||||
void load();
|
||||
void save();
|
||||
Configuration();
|
||||
|
|
|
@ -5,8 +5,7 @@ Application *application = nullptr;
|
|||
DSP dspaudio;
|
||||
|
||||
Emulator::Interface& system() {
|
||||
struct application_interface_null{};
|
||||
if(application->active == nullptr) throw application_interface_null();
|
||||
if(application->active == nullptr) throw;
|
||||
return *application->active;
|
||||
}
|
||||
|
||||
|
@ -47,7 +46,7 @@ Application::Application(int argc, char **argv) {
|
|||
} else {
|
||||
userpath.append(".config/ethos/");
|
||||
}
|
||||
mkdir(userpath, 0755);
|
||||
directory::create(userpath);
|
||||
|
||||
bootstrap();
|
||||
active = nullptr;
|
||||
|
@ -64,20 +63,23 @@ Application::Application(int argc, char **argv) {
|
|||
monospaceFont = "Liberation Mono, 8";
|
||||
}
|
||||
|
||||
video.driver("OpenGL");
|
||||
audio.driver("ALSA");
|
||||
input.driver("SDL");
|
||||
|
||||
config = new Configuration;
|
||||
video.driver(config->video.driver);
|
||||
audio.driver(config->audio.driver);
|
||||
input.driver(config->input.driver);
|
||||
|
||||
utility = new Utility;
|
||||
inputManager = new InputManager;
|
||||
windowManager = new WindowManager;
|
||||
browser = new Browser;
|
||||
presentation = new Presentation;
|
||||
dipSwitches = new DipSwitches;
|
||||
videoSettings = new VideoSettings;
|
||||
audioSettings = new AudioSettings;
|
||||
inputSettings = new InputSettings;
|
||||
hotkeySettings = new HotkeySettings;
|
||||
timingSettings = new TimingSettings;
|
||||
driverSettings = new DriverSettings;
|
||||
settings = new Settings;
|
||||
cheatDatabase = new CheatDatabase;
|
||||
cheatEditor = new CheatEditor;
|
||||
|
@ -89,19 +91,20 @@ Application::Application(int argc, char **argv) {
|
|||
if(!video.cap(Video::Depth) || !video.set(Video::Depth, depth = 30u)) {
|
||||
video.set(Video::Depth, depth = 24u);
|
||||
}
|
||||
video.init();
|
||||
if(video.init() == false) { video.driver("None"); video.init(); }
|
||||
|
||||
audio.set(Audio::Handle, presentation->viewport.handle());
|
||||
audio.init();
|
||||
if(audio.init() == false) { audio.driver("None"); audio.init(); }
|
||||
|
||||
input.set(Input::Handle, presentation->viewport.handle());
|
||||
input.init();
|
||||
if(input.init() == false) { input.driver("None"); input.init(); }
|
||||
|
||||
dspaudio.setPrecision(16);
|
||||
dspaudio.setBalance(0.0);
|
||||
dspaudio.setFrequency(96000);
|
||||
|
||||
utility->synchronizeRuby();
|
||||
utility->updateShader();
|
||||
|
||||
while(quit == false) {
|
||||
OS::processEvents();
|
||||
|
|
|
@ -0,0 +1,68 @@
|
|||
DipSwitches *dipSwitches = nullptr;
|
||||
|
||||
DipSwitch::DipSwitch() {
|
||||
append(name, {100, 0}, 5);
|
||||
append(value, {~0, 0});
|
||||
}
|
||||
|
||||
DipSwitches::DipSwitches() {
|
||||
setTitle("DIP Switches");
|
||||
layout.setMargin(5);
|
||||
accept.setText("Accept");
|
||||
|
||||
append(layout);
|
||||
for(auto &dip : this->dip) layout.append(dip, {~0, 0}, 5);
|
||||
layout.append(controlLayout, {~0, 0});
|
||||
controlLayout.append(spacer, {~0, 0});
|
||||
controlLayout.append(accept, {80, 0});
|
||||
|
||||
setGeometry({128, 128, 250, layout.minimumGeometry().height});
|
||||
|
||||
onClose = accept.onActivate = [&] { quit = true; };
|
||||
}
|
||||
|
||||
unsigned DipSwitches::run(const XML::Node &node) {
|
||||
audio.clear();
|
||||
setModal(true);
|
||||
quit = false;
|
||||
|
||||
for(auto &dip : this->dip) {
|
||||
dip.name.setEnabled(false);
|
||||
dip.name.setText("(empty)");
|
||||
dip.value.setEnabled(false);
|
||||
dip.value.reset();
|
||||
dip.values.reset();
|
||||
}
|
||||
|
||||
unsigned index = 0;
|
||||
for(auto &setting : node) {
|
||||
if(setting.name != "setting") continue;
|
||||
dip[index].name.setEnabled();
|
||||
dip[index].name.setText(setting["name"].data);
|
||||
dip[index].value.setEnabled();
|
||||
for(auto &option : setting) {
|
||||
if(option.name != "option") continue;
|
||||
dip[index].value.append(option["name"].data);
|
||||
dip[index].values.append(fixedpoint::parse(option["value"].data));
|
||||
}
|
||||
|
||||
if(++index >= Slots) break;
|
||||
}
|
||||
|
||||
setVisible();
|
||||
accept.setFocused();
|
||||
|
||||
while(quit == false) {
|
||||
OS::processEvents();
|
||||
}
|
||||
|
||||
setModal(false);
|
||||
setVisible(false);
|
||||
|
||||
unsigned result = 0;
|
||||
for(auto &dip : this->dip) {
|
||||
if(dip.value.enabled() == false) continue;
|
||||
result |= dip.values[dip.value.selection()];
|
||||
}
|
||||
return result;
|
||||
}
|
|
@ -1,23 +1,25 @@
|
|||
struct DipSwitch : HorizontalLayout {
|
||||
Label name;
|
||||
ComboBox value;
|
||||
vector<unsigned> values;
|
||||
|
||||
DipSwitch();
|
||||
};
|
||||
|
||||
struct DipSwitches : Window {
|
||||
VerticalLayout layout;
|
||||
DipSwitch dip[8];
|
||||
HorizontalLayout controlLayout;
|
||||
Widget spacer;
|
||||
Button acceptButton;
|
||||
enum : unsigned { Slots = 8 };
|
||||
|
||||
void load();
|
||||
void accept();
|
||||
VerticalLayout layout;
|
||||
DipSwitch dip[Slots];
|
||||
HorizontalLayout controlLayout;
|
||||
Widget spacer;
|
||||
Button accept;
|
||||
|
||||
unsigned run(const XML::Node &node);
|
||||
DipSwitches();
|
||||
|
||||
private:
|
||||
unsigned values[8][16];
|
||||
bool quit;
|
||||
};
|
||||
|
||||
extern DipSwitches *dipSwitches;
|
|
@ -1,3 +1,4 @@
|
|||
#include "../ethos.hpp"
|
||||
#include "browser.cpp"
|
||||
#include "presentation.cpp"
|
||||
#include "dip-switches.cpp"
|
||||
|
|
|
@ -1,2 +1,3 @@
|
|||
#include "browser.hpp"
|
||||
#include "presentation.hpp"
|
||||
#include "dip-switches.hpp"
|
||||
|
|
|
@ -9,6 +9,15 @@ void Presentation::synchronize() {
|
|||
}
|
||||
}
|
||||
|
||||
shaderNone.setChecked();
|
||||
if(config->video.shader == "None") shaderNone.setChecked();
|
||||
if(config->video.shader == "Blur") shaderBlur.setChecked();
|
||||
for(auto &shader : shaderList) {
|
||||
string name = notdir(nall::basename(config->video.shader));
|
||||
if(auto position = name.position(".")) name[position()] = 0;
|
||||
if(name == shader->text()) shader->setChecked();
|
||||
}
|
||||
|
||||
switch(config->video.scaleMode) {
|
||||
case 0: centerVideo.setChecked(); break;
|
||||
case 1: scaleVideo.setChecked(); break;
|
||||
|
@ -29,6 +38,7 @@ void Presentation::setSystemName(const string &name) {
|
|||
|
||||
Presentation::Presentation() : active(nullptr) {
|
||||
bootstrap();
|
||||
loadShaders();
|
||||
setGeometry({1024, 600, 720, 480});
|
||||
windowManager->append(this, "Presentation");
|
||||
|
||||
|
@ -46,6 +56,9 @@ Presentation::Presentation() : active(nullptr) {
|
|||
RadioItem::group(centerVideo, scaleVideo, stretchVideo);
|
||||
aspectCorrection.setText("Aspect Correction");
|
||||
maskOverscan.setText("Mask Overscan");
|
||||
shaderMenu.setText("Shader");
|
||||
shaderNone.setText("None");
|
||||
shaderBlur.setText("Blur");
|
||||
synchronizeVideo.setText("Synchronize Video");
|
||||
synchronizeAudio.setText("Synchronize Audio");
|
||||
muteAudio.setText("Mute Audio");
|
||||
|
@ -69,6 +82,10 @@ Presentation::Presentation() : active(nullptr) {
|
|||
append(settingsMenu);
|
||||
settingsMenu.append(videoMenu);
|
||||
videoMenu.append(centerVideo, scaleVideo, stretchVideo, *new Separator, aspectCorrection, maskOverscan);
|
||||
settingsMenu.append(shaderMenu);
|
||||
shaderMenu.append(shaderNone, shaderBlur);
|
||||
if(shaderList.size() > 0) shaderMenu.append(*new Separator);
|
||||
for(auto &shader : shaderList) shaderMenu.append(*shader);
|
||||
settingsMenu.append(*new Separator);
|
||||
settingsMenu.append(synchronizeVideo, synchronizeAudio, muteAudio);
|
||||
settingsMenu.append(*new Separator);
|
||||
|
@ -87,6 +104,8 @@ Presentation::Presentation() : active(nullptr) {
|
|||
onSize = [&] { utility->resize(); };
|
||||
onClose = [&] { application->quit = true; };
|
||||
|
||||
shaderNone.onActivate = [&] { config->video.shader = "None"; utility->updateShader(); };
|
||||
shaderBlur.onActivate = [&] { config->video.shader = "Blur"; utility->updateShader(); };
|
||||
centerVideo.onActivate = [&] { config->video.scaleMode = 0; utility->resize(); };
|
||||
scaleVideo.onActivate = [&] { config->video.scaleMode = 1; utility->resize(); };
|
||||
stretchVideo.onActivate = [&] { config->video.scaleMode = 2; utility->resize(); };
|
||||
|
@ -135,7 +154,7 @@ void Presentation::bootstrap() {
|
|||
for(auto &device : port.device) {
|
||||
auto iDevice = new RadioItem;
|
||||
iDevice->setText(device.name);
|
||||
iDevice->onActivate = [=] { utility->connect(portNumber, deviceNumber); };
|
||||
iDevice->onActivate = [=] { utility->connect(portNumber, device.id); };
|
||||
iPort->group.append(*iDevice);
|
||||
iPort->device.append(iDevice);
|
||||
deviceNumber++;
|
||||
|
@ -169,3 +188,25 @@ void Presentation::bootstrap() {
|
|||
emulatorList.append(iEmulator);
|
||||
}
|
||||
}
|
||||
|
||||
void Presentation::loadShaders() {
|
||||
string pathname = application->path("Video Shaders/");
|
||||
lstring files = directory::files(pathname);
|
||||
for(auto &filename : files) {
|
||||
auto shader = new RadioItem;
|
||||
string name = filename;
|
||||
if(auto position = name.position(".")) name[position()] = 0;
|
||||
shader->setText(name);
|
||||
shader->onActivate = [=] {
|
||||
config->video.shader = {pathname, filename};
|
||||
utility->updateShader();
|
||||
};
|
||||
shaderList.append(shader);
|
||||
}
|
||||
|
||||
set<RadioItem&> group;
|
||||
group.append(shaderNone);
|
||||
group.append(shaderBlur);
|
||||
for(auto &shader : shaderList) group.append(*shader);
|
||||
RadioItem::group(group);
|
||||
}
|
||||
|
|
|
@ -30,6 +30,10 @@ struct Presentation : Window {
|
|||
RadioItem stretchVideo;
|
||||
CheckItem aspectCorrection;
|
||||
CheckItem maskOverscan;
|
||||
Menu shaderMenu;
|
||||
RadioItem shaderNone;
|
||||
RadioItem shaderBlur;
|
||||
vector<RadioItem*> shaderList;
|
||||
CheckItem synchronizeVideo;
|
||||
CheckItem synchronizeAudio;
|
||||
CheckItem muteAudio;
|
||||
|
@ -46,6 +50,7 @@ struct Presentation : Window {
|
|||
|
||||
void synchronize();
|
||||
void setSystemName(const string &name);
|
||||
void loadShaders();
|
||||
void bootstrap();
|
||||
Presentation();
|
||||
|
||||
|
|
|
@ -99,3 +99,11 @@ int16_t Interface::inputPoll(unsigned port, unsigned device, unsigned input) {
|
|||
void Interface::mediaRequest(Emulator::Interface::Media media) {
|
||||
utility->loadMedia(media);
|
||||
}
|
||||
|
||||
unsigned Interface::dipSettings(const XML::Node &node) {
|
||||
return dipSwitches->run(node);
|
||||
}
|
||||
|
||||
string Interface::path(unsigned group) {
|
||||
return utility->path(group);
|
||||
}
|
||||
|
|
|
@ -4,6 +4,8 @@ struct Interface {
|
|||
void audioSample(int16_t lsample, int16_t rsample);
|
||||
int16_t inputPoll(unsigned port, unsigned device, unsigned input);
|
||||
void mediaRequest(Emulator::Interface::Media media);
|
||||
unsigned dipSettings(const XML::Node &node);
|
||||
string path(unsigned group);
|
||||
};
|
||||
|
||||
extern Interface *interface;
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
DriverSettings *driverSettings = nullptr;
|
||||
|
||||
DriverSettings::DriverSettings() {
|
||||
title.setFont(application->titleFont);
|
||||
title.setText("Driver Configuration");
|
||||
videoLabel.setText("Video:");
|
||||
audioLabel.setText("Audio:");
|
||||
inputLabel.setText("Input:");
|
||||
|
||||
lstring list;
|
||||
|
||||
list.split(";", video.driver_list());
|
||||
for(unsigned n = 0; n < list.size(); n++) {
|
||||
videoDriver.append(list[n]);
|
||||
if(list[n] == config->video.driver) videoDriver.setSelection(n);
|
||||
}
|
||||
|
||||
list.split(";", audio.driver_list());
|
||||
for(unsigned n = 0; n < list.size(); n++) {
|
||||
audioDriver.append(list[n]);
|
||||
if(list[n] == config->audio.driver) audioDriver.setSelection(n);
|
||||
}
|
||||
|
||||
list.split(";", input.driver_list());
|
||||
for(unsigned n = 0; n < list.size(); n++) {
|
||||
inputDriver.append(list[n]);
|
||||
if(list[n] == config->input.driver) inputDriver.setSelection(n);
|
||||
}
|
||||
|
||||
append(title, {~0, 0}, 5);
|
||||
append(driverLayout, {~0, 0});
|
||||
driverLayout.append(videoLabel, {0, 0}, 5);
|
||||
driverLayout.append(videoDriver, {~0, 0}, 5);
|
||||
driverLayout.append(audioLabel, {0, 0}, 5);
|
||||
driverLayout.append(audioDriver, {~0, 0}, 5);
|
||||
driverLayout.append(inputLabel, {0, 0}, 5);
|
||||
driverLayout.append(inputDriver, {~0, 0});
|
||||
|
||||
videoDriver.onChange = [&] { config->video.driver = videoDriver.text(); };
|
||||
audioDriver.onChange = [&] { config->audio.driver = audioDriver.text(); };
|
||||
inputDriver.onChange = [&] { config->input.driver = inputDriver.text(); };
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
struct DriverSettings : SettingsLayout {
|
||||
Label title;
|
||||
HorizontalLayout driverLayout;
|
||||
Label videoLabel;
|
||||
ComboBox videoDriver;
|
||||
Label audioLabel;
|
||||
ComboBox audioDriver;
|
||||
Label inputLabel;
|
||||
ComboBox inputDriver;
|
||||
|
||||
DriverSettings();
|
||||
};
|
||||
|
||||
extern DriverSettings *driverSettings;
|
|
@ -18,6 +18,7 @@ HotkeySettings::HotkeySettings() : activeInput(nullptr) {
|
|||
inputList.onActivate = {&HotkeySettings::assignInput, this};
|
||||
eraseButton.onActivate = {&HotkeySettings::eraseInput, this};
|
||||
|
||||
for(auto &hotkey : inputManager->hotkeyMap) inputList.append("", "");
|
||||
refresh();
|
||||
}
|
||||
|
||||
|
@ -26,13 +27,13 @@ void HotkeySettings::synchronize() {
|
|||
}
|
||||
|
||||
void HotkeySettings::refresh() {
|
||||
inputList.reset();
|
||||
unsigned index = 0;
|
||||
for(auto &hotkey : inputManager->hotkeyMap) {
|
||||
string mapping = hotkey->mapping;
|
||||
mapping.replace("KB0::", "");
|
||||
mapping.replace("MS0::", "Mouse::");
|
||||
mapping.replace(",", " and ");
|
||||
inputList.append(hotkey->name, mapping);
|
||||
inputList.modify(index++, hotkey->name, mapping);
|
||||
}
|
||||
synchronize();
|
||||
}
|
||||
|
|
|
@ -114,6 +114,13 @@ void InputSettings::portChanged() {
|
|||
|
||||
void InputSettings::deviceChanged() {
|
||||
inputList.reset();
|
||||
for(unsigned number : activeDevice().order) inputList.append("", "");
|
||||
inputChanged();
|
||||
synchronize();
|
||||
}
|
||||
|
||||
void InputSettings::inputChanged() {
|
||||
unsigned index = 0;
|
||||
for(unsigned number : activeDevice().order) {
|
||||
auto &input = activeDevice().input[number];
|
||||
auto abstract = inputManager->inputMap(input.guid);
|
||||
|
@ -121,9 +128,8 @@ void InputSettings::deviceChanged() {
|
|||
mapping.replace("KB0::", "");
|
||||
mapping.replace("MS0::", "Mouse::");
|
||||
mapping.replace(",", " or ");
|
||||
inputList.append(input.name, mapping);
|
||||
inputList.modify(index++, input.name, mapping);
|
||||
}
|
||||
synchronize();
|
||||
}
|
||||
|
||||
void InputSettings::resetInput() {
|
||||
|
@ -176,7 +182,7 @@ void InputSettings::inputEvent(unsigned scancode, int16_t value, bool allowMouse
|
|||
if(activeInput->bind(scancode, value) == false) return;
|
||||
|
||||
activeInput = nullptr;
|
||||
deviceChanged();
|
||||
inputChanged();
|
||||
settings->setStatusText("");
|
||||
settings->layout.setEnabled(true);
|
||||
setEnabled(true);
|
||||
|
|
|
@ -24,6 +24,7 @@ struct InputSettings : SettingsLayout {
|
|||
void systemChanged();
|
||||
void portChanged();
|
||||
void deviceChanged();
|
||||
void inputChanged();
|
||||
void resetInput();
|
||||
void eraseInput();
|
||||
void assignInput();
|
||||
|
|
|
@ -3,6 +3,8 @@
|
|||
#include "audio.cpp"
|
||||
#include "input.cpp"
|
||||
#include "hotkey.cpp"
|
||||
#include "timing.cpp"
|
||||
#include "driver.cpp"
|
||||
Settings *settings = nullptr;
|
||||
|
||||
void SettingsLayout::append(Sizable &sizable, const Size &size, unsigned spacing) {
|
||||
|
@ -28,6 +30,8 @@ Settings::Settings() {
|
|||
panelList.append("Audio");
|
||||
panelList.append("Input");
|
||||
panelList.append("Hotkeys");
|
||||
panelList.append("Timing");
|
||||
panelList.append("Driver");
|
||||
|
||||
append(layout);
|
||||
layout.append(panelList, {120, ~0}, 5);
|
||||
|
@ -35,6 +39,8 @@ Settings::Settings() {
|
|||
append(*audioSettings);
|
||||
append(*inputSettings);
|
||||
append(*hotkeySettings);
|
||||
append(*timingSettings);
|
||||
append(*driverSettings);
|
||||
|
||||
panelList.onChange = {&Settings::panelChanged, this};
|
||||
|
||||
|
@ -43,10 +49,13 @@ Settings::Settings() {
|
|||
}
|
||||
|
||||
void Settings::panelChanged() {
|
||||
setStatusText("");
|
||||
videoSettings->setVisible(false);
|
||||
audioSettings->setVisible(false);
|
||||
inputSettings->setVisible(false);
|
||||
hotkeySettings->setVisible(false);
|
||||
timingSettings->setVisible(false);
|
||||
driverSettings->setVisible(false);
|
||||
if(panelList.selected() == false) return;
|
||||
|
||||
switch(panelList.selection()) {
|
||||
|
@ -54,5 +63,7 @@ void Settings::panelChanged() {
|
|||
case 1: return audioSettings->setVisible();
|
||||
case 2: return inputSettings->setVisible();
|
||||
case 3: return hotkeySettings->setVisible();
|
||||
case 4: return timingSettings->setVisible();
|
||||
case 5: return driverSettings->setVisible();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,6 +10,8 @@ struct SettingsLayout : HorizontalLayout {
|
|||
#include "audio.hpp"
|
||||
#include "input.hpp"
|
||||
#include "hotkey.hpp"
|
||||
#include "timing.hpp"
|
||||
#include "driver.hpp"
|
||||
|
||||
struct Settings : Window {
|
||||
HorizontalLayout layout;
|
||||
|
|
|
@ -0,0 +1,130 @@
|
|||
TimingSettings *timingSettings = nullptr;
|
||||
|
||||
TimingAdjustment::TimingAdjustment() {
|
||||
assign.setEnabled(false);
|
||||
assign.setText("Assign");
|
||||
analyze.setText("Analyze");
|
||||
stop.setEnabled(false);
|
||||
stop.setText("Stop");
|
||||
|
||||
append(name, {40, 0});
|
||||
append(value, {100, 0}, 5);
|
||||
append(assign, {80, 0}, 5);
|
||||
append(spacer, {~0, 0});
|
||||
append(analyze, {80, 0}, 5);
|
||||
append(stop, {80, 0});
|
||||
}
|
||||
|
||||
TimingSettings::TimingSettings() {
|
||||
title.setFont(application->titleFont);
|
||||
title.setText("Audiovisual Synchronization");
|
||||
videoAdjust.name.setText("Video:");
|
||||
videoAdjust.value.setText({config->timing.video});
|
||||
audioAdjust.name.setText("Audio:");
|
||||
audioAdjust.value.setText({config->timing.audio});
|
||||
|
||||
append(title, {~0, 0}, 5);
|
||||
append(videoAdjust, {~0, 0}, 5);
|
||||
append(audioAdjust, {~0, 0}, 5);
|
||||
|
||||
videoAdjust.value.onChange = [&] { videoAdjust.assign.setEnabled(true); };
|
||||
audioAdjust.value.onChange = [&] { audioAdjust.assign.setEnabled(true); };
|
||||
videoAdjust.assign.onActivate = [&] {
|
||||
config->timing.video = atof(videoAdjust.value.text());
|
||||
videoAdjust.value.setText({config->timing.video});
|
||||
videoAdjust.assign.setEnabled(false);
|
||||
utility->synchronizeDSP();
|
||||
};
|
||||
audioAdjust.assign.onActivate = [&] {
|
||||
config->timing.audio = atof(audioAdjust.value.text());
|
||||
audioAdjust.value.setText({config->timing.audio});
|
||||
audioAdjust.assign.setEnabled(false);
|
||||
utility->synchronizeDSP();
|
||||
};
|
||||
videoAdjust.analyze.onActivate = {&TimingSettings::analyzeVideoFrequency, this};
|
||||
audioAdjust.analyze.onActivate = {&TimingSettings::analyzeAudioFrequency, this};
|
||||
videoAdjust.stop.onActivate = audioAdjust.stop.onActivate = [&] { analysis.stop = true; };
|
||||
}
|
||||
|
||||
void TimingSettings::analyzeVideoFrequency() {
|
||||
video.set(Video::Synchronize, true);
|
||||
audio.set(Audio::Synchronize, false);
|
||||
videoAdjust.stop.setEnabled(true);
|
||||
analyzeStart();
|
||||
do {
|
||||
uint32_t *output;
|
||||
unsigned pitch;
|
||||
if(video.lock(output, pitch, 16, 16)) {
|
||||
pitch >>= 2;
|
||||
for(unsigned y = 0; y < 16; y++) memset(output + y * pitch, 0, 4 * 16);
|
||||
video.unlock();
|
||||
video.refresh();
|
||||
}
|
||||
} while(analyzeTick("Video"));
|
||||
analyzeStop();
|
||||
}
|
||||
|
||||
void TimingSettings::analyzeAudioFrequency() {
|
||||
video.set(Video::Synchronize, false);
|
||||
audio.set(Audio::Synchronize, true);
|
||||
audioAdjust.stop.setEnabled(true);
|
||||
analyzeStart();
|
||||
do {
|
||||
audio.sample(0, 0);
|
||||
} while(analyzeTick("Audio"));
|
||||
analyzeStop();
|
||||
}
|
||||
|
||||
void TimingSettings::analyzeStart() {
|
||||
audio.clear();
|
||||
settings->setModal(true);
|
||||
settings->panelList.setEnabled(false);
|
||||
videoAdjust.analyze.setEnabled(false);
|
||||
audioAdjust.analyze.setEnabled(false);
|
||||
settings->setStatusText("Initiailizing ...");
|
||||
OS::processEvents();
|
||||
|
||||
analysis.stop = false;
|
||||
analysis.seconds = 0;
|
||||
analysis.counter = 0;
|
||||
analysis.sample.reset();
|
||||
analysis.systemTime = time(0);
|
||||
}
|
||||
|
||||
bool TimingSettings::analyzeTick(const string &type) {
|
||||
analysis.counter++;
|
||||
|
||||
time_t systemTime = time(0);
|
||||
if(systemTime > analysis.systemTime) {
|
||||
analysis.systemTime = systemTime;
|
||||
OS::processEvents();
|
||||
|
||||
if(analysis.seconds < 3) {
|
||||
analysis.seconds++;
|
||||
} else {
|
||||
analysis.sample.append(analysis.counter);
|
||||
uintmax_t sum = 0;
|
||||
for(auto &point : analysis.sample) sum += point;
|
||||
settings->setStatusText({
|
||||
type, " sample rate: ", (double)sum / analysis.sample.size(), "hz",
|
||||
" (", analysis.sample.size(), " sample points)"
|
||||
});
|
||||
}
|
||||
|
||||
analysis.counter = 0;
|
||||
}
|
||||
|
||||
return !analysis.stop;
|
||||
}
|
||||
|
||||
void TimingSettings::analyzeStop() {
|
||||
video.set(Video::Synchronize, config->video.synchronize);
|
||||
audio.set(Audio::Synchronize, config->audio.synchronize);
|
||||
|
||||
settings->panelList.setEnabled(true);
|
||||
videoAdjust.analyze.setEnabled(true);
|
||||
audioAdjust.analyze.setEnabled(true);
|
||||
videoAdjust.stop.setEnabled(false);
|
||||
audioAdjust.stop.setEnabled(false);
|
||||
settings->setModal(false);
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
struct TimingAdjustment : HorizontalLayout {
|
||||
Label name;
|
||||
LineEdit value;
|
||||
Button assign;
|
||||
Widget spacer;
|
||||
Button analyze;
|
||||
Button stop;
|
||||
|
||||
TimingAdjustment();
|
||||
};
|
||||
|
||||
struct TimingSettings : SettingsLayout {
|
||||
Label title;
|
||||
TimingAdjustment videoAdjust;
|
||||
TimingAdjustment audioAdjust;
|
||||
|
||||
void analyzeVideoFrequency();
|
||||
void analyzeAudioFrequency();
|
||||
|
||||
void analyzeStart();
|
||||
bool analyzeTick(const string &type);
|
||||
void analyzeStop();
|
||||
|
||||
TimingSettings();
|
||||
|
||||
private:
|
||||
struct Analysis {
|
||||
bool stop;
|
||||
unsigned seconds;
|
||||
unsigned counter;
|
||||
vector<unsigned> sample;
|
||||
time_t systemTime;
|
||||
} analysis;
|
||||
};
|
||||
|
||||
extern TimingSettings *timingSettings;
|
|
@ -153,7 +153,7 @@ bool CheatEditor::save(const string &filename) {
|
|||
}
|
||||
|
||||
if(lastSave == -1) {
|
||||
unlink(filename);
|
||||
file::remove(filename);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
@ -97,7 +97,7 @@ bool StateManager::save(const string &filename, unsigned revision) {
|
|||
bool hasSave = false;
|
||||
for(auto &slot : this->slot) hasSave |= slot.capacity() > 0;
|
||||
if(hasSave == false) {
|
||||
unlink(filename);
|
||||
file::remove(filename);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
@ -67,7 +67,7 @@ void Utility::saveMemory() {
|
|||
system().save(memory.id, fs);
|
||||
}
|
||||
|
||||
mkdir(string{pathname[0], "bsnes/"}, 0755);
|
||||
directory::create({pathname[0], "bsnes/"});
|
||||
cheatEditor->save({pathname[0], "cheats.xml"});
|
||||
stateManager->save({pathname[0], "bsnes/states.bsa"}, 1);
|
||||
}
|
||||
|
@ -100,7 +100,7 @@ void Utility::load() {
|
|||
loadMemory();
|
||||
|
||||
system().updatePalette();
|
||||
dspaudio.setFrequency(system().information.frequency);
|
||||
synchronizeDSP();
|
||||
|
||||
resize();
|
||||
cheatEditor->synchronize();
|
||||
|
@ -118,15 +118,18 @@ void Utility::unload() {
|
|||
stateManager->reset();
|
||||
setInterface(nullptr);
|
||||
|
||||
presentation->setTitle({Emulator::Name, " v", Emulator::Version});
|
||||
video.clear();
|
||||
audio.clear();
|
||||
presentation->setTitle({Emulator::Name, " v", Emulator::Version});
|
||||
cheatEditor->synchronize();
|
||||
stateManager->synchronize();
|
||||
}
|
||||
|
||||
void Utility::saveState(unsigned slot) {
|
||||
if(application->active == nullptr) return;
|
||||
serializer s = system().serialize();
|
||||
if(s.size() == 0) return;
|
||||
mkdir(string{pathname[0], "bsnes/"}, 0755);
|
||||
directory::create({pathname[0], "bsnes/"});
|
||||
if(file::write({pathname[0], "bsnes/state-", slot, ".bsa"}, s.data(), s.size()) == false);
|
||||
showMessage({"Save to slot ", slot});
|
||||
}
|
||||
|
@ -140,6 +143,20 @@ void Utility::loadState(unsigned slot) {
|
|||
showMessage({"Loaded from slot ", slot});
|
||||
}
|
||||
|
||||
void Utility::synchronizeDSP() {
|
||||
if(application->active == nullptr) return;
|
||||
|
||||
if(config->video.synchronize == false) {
|
||||
return dspaudio.setFrequency(system().audioFrequency());
|
||||
}
|
||||
|
||||
double inputRatio = system().audioFrequency() / system().videoFrequency();
|
||||
double outputRatio = config->timing.audio / config->timing.video;
|
||||
double frequency = inputRatio / outputRatio * config->audio.frequency;
|
||||
|
||||
dspaudio.setFrequency(frequency);
|
||||
}
|
||||
|
||||
void Utility::synchronizeRuby() {
|
||||
video.set(Video::Synchronize, config->video.synchronize);
|
||||
audio.set(Audio::Synchronize, config->audio.synchronize);
|
||||
|
@ -155,6 +172,22 @@ void Utility::synchronizeRuby() {
|
|||
dspaudio.setVolume(config->audio.mute ? 0.0 : config->audio.volume * 0.01);
|
||||
}
|
||||
|
||||
void Utility::updateShader() {
|
||||
if(config->video.shader == "None") {
|
||||
video.set(Video::Shader, (const char*)"");
|
||||
video.set(Video::Filter, 0u);
|
||||
return;
|
||||
}
|
||||
if(config->video.shader == "Blur") {
|
||||
video.set(Video::Shader, (const char*)"");
|
||||
video.set(Video::Filter, 1u);
|
||||
return;
|
||||
}
|
||||
string data;
|
||||
data.readfile(config->video.shader);
|
||||
video.set(Video::Shader, (const char*)data);
|
||||
}
|
||||
|
||||
void Utility::resize(bool resizeWindow) {
|
||||
if(application->active == nullptr) return;
|
||||
Geometry geometry = presentation->geometry();
|
||||
|
|
|
@ -15,7 +15,9 @@ struct Utility {
|
|||
void saveState(unsigned slot);
|
||||
void loadState(unsigned slot);
|
||||
|
||||
void synchronizeDSP();
|
||||
void synchronizeRuby();
|
||||
void updateShader();
|
||||
void resize(bool resizeWindow = false);
|
||||
void toggleFullScreen();
|
||||
|
||||
|
|
|
@ -1,91 +0,0 @@
|
|||
processors := arm gsu hg51b lr35902 r6502 r65816 spc700 upd96050
|
||||
include processor/Makefile
|
||||
|
||||
include $(fc)/Makefile
|
||||
include $(sfc)/Makefile
|
||||
include $(gb)/Makefile
|
||||
include $(gba)/Makefile
|
||||
name := bsnes
|
||||
|
||||
ui_objects := ui-main ui-config ui-interface ui-input ui-utility
|
||||
ui_objects += ui-window ui-general ui-settings ui-tools
|
||||
ui_objects += phoenix ruby
|
||||
ui_objects += $(if $(call streq,$(platform),win),resource)
|
||||
|
||||
# platform
|
||||
ifeq ($(platform),x)
|
||||
ruby := video.glx video.xv video.sdl
|
||||
ruby += audio.alsa audio.openal audio.oss audio.pulseaudio audio.pulseaudiosimple audio.ao
|
||||
ruby += input.sdl input.x
|
||||
else ifeq ($(platform),osx)
|
||||
ruby :=
|
||||
ruby += audio.openal
|
||||
ruby += input.carbon
|
||||
else ifeq ($(platform),win)
|
||||
ruby := video.direct3d video.wgl video.directdraw video.gdi
|
||||
ruby += audio.directsound audio.xaudio2
|
||||
ruby += input.rawinput input.directinput
|
||||
endif
|
||||
|
||||
# phoenix
|
||||
include phoenix/Makefile
|
||||
link += $(phoenixlink)
|
||||
|
||||
# ruby
|
||||
include ruby/Makefile
|
||||
link += $(rubylink)
|
||||
|
||||
# rules
|
||||
objects := $(ui_objects) $(objects)
|
||||
objects := $(patsubst %,obj/%.o,$(objects))
|
||||
|
||||
obj/ui-main.o: $(ui)/main.cpp $(call rwildcard,$(ui)/)
|
||||
obj/ui-config.o: $(ui)/config/config.cpp $(call rwildcard,$(ui)/)
|
||||
obj/ui-interface.o: $(ui)/interface/interface.cpp $(call rwildcard,$(ui)/)
|
||||
obj/ui-input.o: $(ui)/input/input.cpp $(call rwildcard,$(ui)/)
|
||||
obj/ui-utility.o: $(ui)/utility/utility.cpp $(call rwildcard,$(ui)/)
|
||||
obj/ui-window.o: $(ui)/window/window.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/ui-tools.o: $(ui)/tools/tools.cpp $(call rwildcard,$(ui)/)
|
||||
|
||||
obj/ruby.o: ruby/ruby.cpp $(call rwildcard,ruby/*)
|
||||
$(call compile,$(rubyflags))
|
||||
|
||||
obj/phoenix.o: phoenix/phoenix.cpp $(call rwildcard,phoenix/*)
|
||||
$(call compile,$(phoenixflags))
|
||||
|
||||
obj/resource.o: $(ui)/resource.rc
|
||||
# windres --target=pe-i386 $(ui)/resource.rc obj/resource.o
|
||||
windres $(ui)/resource.rc obj/resource.o
|
||||
|
||||
# targets
|
||||
build: $(objects)
|
||||
ifeq ($(platform),osx)
|
||||
test -d ../$(name).app || mkdir -p ../$(name).app/Contents/MacOS
|
||||
$(strip $(cpp) -o ../$(name).app/Contents/MacOS/$(name) $(objects) $(link))
|
||||
else
|
||||
$(strip $(cpp) -o out/$(name) $(objects) $(link))
|
||||
endif
|
||||
|
||||
install:
|
||||
ifeq ($(USER),root)
|
||||
@echo Please do not run make install as root.
|
||||
@echo The installer needs to know your home directory to install important files.
|
||||
else ifeq ($(platform),x)
|
||||
sudo install -D -m 755 out/$(name) $(DESTDIR)$(prefix)/bin/$(name)
|
||||
sudo install -D -m 644 data/$(name).png $(DESTDIR)$(prefix)/share/pixmaps/$(name).png
|
||||
sudo install -D -m 644 data/$(name).desktop $(DESTDIR)$(prefix)/share/applications/$(name).desktop
|
||||
|
||||
mkdir -p ~/.config/$(name)
|
||||
cp -R profile/* ~/.config/$(name)
|
||||
cp data/cheats.xml ~/.config/$(name)/cheats.xml
|
||||
chmod -R 777 ~/.config/$(name)
|
||||
endif
|
||||
|
||||
uninstall:
|
||||
ifeq ($(platform),x)
|
||||
sudo rm $(DESTDIR)$(prefix)/bin/$(name)
|
||||
sudo rm $(DESTDIR)$(prefix)/share/pixmaps/$(name).png
|
||||
sudo rm $(DESTDIR)$(prefix)/share/applications/$(name).desktop
|
||||
endif
|
|
@ -1,66 +0,0 @@
|
|||
#include <fc/fc.hpp>
|
||||
#include <sfc/sfc.hpp>
|
||||
#include <gb/gb.hpp>
|
||||
#include <gba/gba.hpp>
|
||||
|
||||
namespace FC = Famicom;
|
||||
namespace SFC = SuperFamicom;
|
||||
namespace GB = GameBoy;
|
||||
namespace GBA = GameBoyAdvance;
|
||||
|
||||
#include <nall/compositor.hpp>
|
||||
#include <nall/config.hpp>
|
||||
#include <nall/directory.hpp>
|
||||
#include <nall/dsp.hpp>
|
||||
#include <nall/file.hpp>
|
||||
#include <nall/filemap.hpp>
|
||||
#include <nall/input.hpp>
|
||||
#include <nall/bps/patch.hpp>
|
||||
#include <nall/stream/file.hpp>
|
||||
#include <nall/stream/memory.hpp>
|
||||
#include <nall/stream/vector.hpp>
|
||||
#include <nall/nes/cartridge.hpp>
|
||||
#include <nall/snes/cartridge.hpp>
|
||||
#include <nall/gb/cartridge.hpp>
|
||||
#include <nall/gba/cartridge.hpp>
|
||||
using namespace nall;
|
||||
|
||||
#include <phoenix/phoenix.hpp>
|
||||
using namespace phoenix;
|
||||
|
||||
#include <ruby/ruby.hpp>
|
||||
using namespace ruby;
|
||||
|
||||
#include "config/config.hpp"
|
||||
#include "interface/interface.hpp"
|
||||
#include "input/input.hpp"
|
||||
#include "utility/utility.hpp"
|
||||
#include "window/window.hpp"
|
||||
#include "general/general.hpp"
|
||||
#include "settings/settings.hpp"
|
||||
#include "tools/tools.hpp"
|
||||
|
||||
struct Application {
|
||||
bool quit;
|
||||
bool pause;
|
||||
bool autopause;
|
||||
bool compositionEnable;
|
||||
unsigned depth;
|
||||
|
||||
string basepath;
|
||||
string userpath;
|
||||
string path(const string &filename);
|
||||
|
||||
string title;
|
||||
string normalFont;
|
||||
string boldFont;
|
||||
string titleFont;
|
||||
string monospaceFont;
|
||||
|
||||
void run();
|
||||
Application(int argc, char **argv);
|
||||
~Application();
|
||||
};
|
||||
|
||||
extern Application *application;
|
||||
extern nall::DSP dspaudio;
|
|
@ -1,52 +0,0 @@
|
|||
#include "../base.hpp"
|
||||
Config *config = nullptr;
|
||||
|
||||
Config::Config() {
|
||||
append(video.driver = "", "Video::Driver");
|
||||
append(video.filter = "None", "Video::Filter");
|
||||
append(video.shader = "Blur", "Video::Shader");
|
||||
append(video.synchronize = true, "Video::Synchronize");
|
||||
append(video.correctAspectRatio = true, "Video::CorrectAspectRatio");
|
||||
|
||||
append(video.maskOverscan = false, "Video::MaskOverscan");
|
||||
append(video.maskOverscanHorizontal = 8, "Video::MaskOverscanHorizontal");
|
||||
append(video.maskOverscanVertical = 8, "Video::MaskOverscanVertical");
|
||||
|
||||
append(video.brightness = 100, "Video::Brightness");
|
||||
append(video.contrast = 100, "Video::Contrast");
|
||||
append(video.gamma = 50, "Video::Gamma");
|
||||
|
||||
append(video.fullScreenMode = 0, "Video::FullScreenMode");
|
||||
|
||||
append(video.startFullScreen = false, "Video::StartFullScreen");
|
||||
append(video.compositionMode = 0, "Video::CompositionMode");
|
||||
|
||||
append(audio.driver = "", "Audio::Driver");
|
||||
append(audio.synchronize = true, "Audio::Synchronize");
|
||||
append(audio.mute = false, "Audio::Mute");
|
||||
append(audio.volume = 100, "Audio::Volume");
|
||||
append(audio.latency = 60, "Audio::Latency");
|
||||
append(audio.resampler = "sinc", "Audio::Resampler");
|
||||
|
||||
append(audio.frequency = 48000, "Audio::Frequency::Native");
|
||||
append(audio.frequencyNES = 1789772, "Audio::Frequency::NES");
|
||||
append(audio.frequencySNES = 32000, "Audio::Frequency::SNES");
|
||||
append(audio.frequencyGB = 4194304, "Audio::Frequency::GB");
|
||||
append(audio.frequencyGBA = 32768, "Audio::Frequency::GBA");
|
||||
|
||||
append(input.driver = "", "Input::Driver");
|
||||
append(input.focusPolicy = 1, "Input::FocusPolicy");
|
||||
|
||||
append(nes.controllerPort1Device = 1, "Famicom::Controller::Port1");
|
||||
append(nes.controllerPort2Device = 0, "Famicom::Controller::Port2");
|
||||
|
||||
append(snes.controllerPort1Device = 1, "SuperFamciom::Controller::Port1");
|
||||
append(snes.controllerPort2Device = 0, "SuperFamicom::Controller::Port2");
|
||||
|
||||
load(application->path("settings.cfg"));
|
||||
save(application->path("settings.cfg"));
|
||||
}
|
||||
|
||||
Config::~Config() {
|
||||
save(application->path("settings.cfg"));
|
||||
}
|
|
@ -1,59 +0,0 @@
|
|||
struct Config : public configuration {
|
||||
struct Video {
|
||||
string driver;
|
||||
|
||||
string filter;
|
||||
string shader;
|
||||
|
||||
bool synchronize;
|
||||
bool correctAspectRatio;
|
||||
|
||||
bool maskOverscan;
|
||||
unsigned maskOverscanHorizontal;
|
||||
unsigned maskOverscanVertical;
|
||||
|
||||
unsigned brightness;
|
||||
unsigned contrast;
|
||||
unsigned gamma;
|
||||
|
||||
unsigned fullScreenMode;
|
||||
unsigned compositionMode;
|
||||
|
||||
bool startFullScreen;
|
||||
} video;
|
||||
|
||||
struct Audio {
|
||||
string driver;
|
||||
bool synchronize;
|
||||
bool mute;
|
||||
unsigned volume;
|
||||
unsigned latency;
|
||||
string resampler;
|
||||
|
||||
unsigned frequency;
|
||||
unsigned frequencyNES;
|
||||
unsigned frequencySNES;
|
||||
unsigned frequencyGB;
|
||||
unsigned frequencyGBA;
|
||||
} audio;
|
||||
|
||||
struct Input {
|
||||
string driver;
|
||||
unsigned focusPolicy;
|
||||
} input;
|
||||
|
||||
struct NES {
|
||||
unsigned controllerPort1Device;
|
||||
unsigned controllerPort2Device;
|
||||
} nes;
|
||||
|
||||
struct SNES {
|
||||
unsigned controllerPort1Device;
|
||||
unsigned controllerPort2Device;
|
||||
} snes;
|
||||
|
||||
Config();
|
||||
~Config();
|
||||
};
|
||||
|
||||
extern Config *config;
|
|
@ -1,69 +0,0 @@
|
|||
DipSwitches *dipSwitches = nullptr;
|
||||
|
||||
DipSwitch::DipSwitch() {
|
||||
append(name, {~0, 0}, 5);
|
||||
append(value, {~0, 0}, 0);
|
||||
}
|
||||
|
||||
DipSwitches::DipSwitches() {
|
||||
setTitle("DIP Switches");
|
||||
|
||||
layout.setMargin(5);
|
||||
acceptButton.setText("Accept");
|
||||
|
||||
append(layout);
|
||||
for(unsigned n = 0; n < 8; n++)
|
||||
layout.append(dip[n], {~0, 0}, 5);
|
||||
layout.append(controlLayout, {~0, 0}, 5);
|
||||
controlLayout.append(spacer, {~0, 0}, 0);
|
||||
controlLayout.append(acceptButton, { 0, 0}, 0);
|
||||
|
||||
setGeometry({128, 128, 400, layout.minimumGeometry().height});
|
||||
windowManager->append(this, "DipSwitches");
|
||||
|
||||
acceptButton.onActivate = { &DipSwitches::accept, this };
|
||||
}
|
||||
|
||||
void DipSwitches::load() {
|
||||
if(interface->mode() != Interface::Mode::SFC || SFC::cartridge.has_nss_dip() == false) return;
|
||||
application->pause = true;
|
||||
|
||||
auto info = SFC::cartridge.information.nss;
|
||||
unsigned count = info.setting.size();
|
||||
|
||||
for(unsigned n = 0; n < min(8, count); n++) {
|
||||
dip[n].setEnabled(true);
|
||||
dip[n].name.setText(info.setting[n]);
|
||||
dip[n].value.reset();
|
||||
for(unsigned z = 0; z < min(16, info.option[n].size()); z++) {
|
||||
lstring part;
|
||||
part.split<1>(":", info.option[n][z]);
|
||||
values[n][z] = hex(part[0]);
|
||||
dip[n].value.append(part[1]);
|
||||
}
|
||||
}
|
||||
|
||||
for(unsigned n = count; n < 8; n++) {
|
||||
dip[n].setEnabled(false);
|
||||
dip[n].name.setText("(unused)");
|
||||
dip[n].value.reset();
|
||||
dip[n].value.append("(unused)");
|
||||
}
|
||||
|
||||
acceptButton.setFocused();
|
||||
setVisible();
|
||||
}
|
||||
|
||||
void DipSwitches::accept() {
|
||||
auto info = SFC::cartridge.information.nss;
|
||||
unsigned count = info.setting.size();
|
||||
|
||||
unsigned result = 0x0000;
|
||||
for(unsigned n = 0; n < min(8, count); n++) {
|
||||
result |= values[n][dip[n].value.selection()];
|
||||
}
|
||||
|
||||
setVisible(false);
|
||||
SFC::nss.set_dip(result);
|
||||
application->pause = false;
|
||||
}
|
|
@ -1,141 +0,0 @@
|
|||
FileBrowser *fileBrowser = nullptr;
|
||||
|
||||
FileBrowser::FileBrowser() {
|
||||
setGeometry({ 128, 128, 640, 400 });
|
||||
windowManager->append(this, "FileBrowser");
|
||||
|
||||
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, mode->path);
|
||||
if(path != "") setPath(path);
|
||||
};
|
||||
|
||||
pathUp.onActivate = [&] {
|
||||
if(mode->path == "/") return;
|
||||
string path = mode->path;
|
||||
path.rtrim<1>("/");
|
||||
path = dir(path);
|
||||
setPath(path);
|
||||
};
|
||||
|
||||
fileList.onChange = { &FileBrowser::synchronize, this };
|
||||
fileList.onActivate = openButton.onActivate = { &FileBrowser::fileListActivate, this };
|
||||
|
||||
filterModes.append({ "Default", "", { "*" } });
|
||||
filterModes.append({ "Famicom", "", { "*.fc", "*.nes" } });
|
||||
filterModes.append({ "SuperFamicom", "", { "*.sfc" } });
|
||||
filterModes.append({ "GameBoy", "", { "*.gb", "*.gbb" } });
|
||||
filterModes.append({ "GameBoyColor", "", { "*.gbc", "*.gbb" } });
|
||||
filterModes.append({ "GameBoyAdvance", "", { "*.gba" } });
|
||||
filterModes.append({ "Satellaview", "", { "*.bs" } });
|
||||
filterModes.append({ "SufamiTurbo", "", { "*.st" } });
|
||||
mode = &filterModes[Mode::Default];
|
||||
|
||||
for(auto &mode : filterModes) config.attach(mode.path, mode.name);
|
||||
config.load(application->path("paths.cfg"));
|
||||
config.save(application->path("paths.cfg"));
|
||||
synchronize();
|
||||
}
|
||||
|
||||
void FileBrowser::synchronize() {
|
||||
openButton.setEnabled(fileList.selected());
|
||||
}
|
||||
|
||||
FileBrowser::~FileBrowser() {
|
||||
config.save(application->path("paths.cfg"));
|
||||
}
|
||||
|
||||
void FileBrowser::open(const string &title, unsigned requestedMode, function<void (string)> requestedCallback) {
|
||||
callback = requestedCallback;
|
||||
if(mode == &filterModes[requestedMode]) {
|
||||
setVisible();
|
||||
fileList.setFocused();
|
||||
return;
|
||||
}
|
||||
mode = &filterModes[requestedMode];
|
||||
|
||||
setTitle(title);
|
||||
setPath(mode->path);
|
||||
|
||||
string filterText = "Files of type: ";
|
||||
for(auto &filter : mode->filter) filterText.append(filter, ", ");
|
||||
filterText.trim<1>(", ");
|
||||
filterLabel.setText(filterText);
|
||||
|
||||
setVisible();
|
||||
fileList.setFocused();
|
||||
}
|
||||
|
||||
void FileBrowser::setPath(const string &path) {
|
||||
mode->path = path;
|
||||
if(mode->path == "") mode->path = application->basepath;
|
||||
pathEdit.setText(mode->path);
|
||||
|
||||
fileList.reset();
|
||||
fileNameList.reset();
|
||||
|
||||
lstring contentsList = directory::contents(path);
|
||||
for(auto &fileName : contentsList) {
|
||||
if(fileName.endswith("/")) {
|
||||
fileNameList.append(fileName);
|
||||
} else for(auto &filter : mode->filter) {
|
||||
if(fileName.wildcard(filter)) {
|
||||
fileNameList.append(fileName);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for(auto &fileName : fileNameList) fileList.append(fileName);
|
||||
fileList.setSelection(0);
|
||||
fileList.setFocused();
|
||||
synchronize();
|
||||
}
|
||||
|
||||
void FileBrowser::fileListActivate() {
|
||||
unsigned selection = fileList.selection();
|
||||
string fileName = fileNameList[selection];
|
||||
if(fileName.endswith("/")) {
|
||||
if(loadFolder({ mode->path, fileName })) return;
|
||||
return setPath({ mode->path, fileName });
|
||||
}
|
||||
loadFile({ mode->path, fileName });
|
||||
}
|
||||
|
||||
bool FileBrowser::loadFolder(const string &requestedPath) {
|
||||
bool accept = false;
|
||||
string path = requestedPath;
|
||||
path.rtrim<1>("/");
|
||||
for(auto &filter : mode->filter) {
|
||||
if(path.wildcard(filter)) accept = true;
|
||||
}
|
||||
if(accept == false) return false;
|
||||
loadFile(requestedPath);
|
||||
return true;
|
||||
}
|
||||
|
||||
void FileBrowser::loadFile(const string &filename) {
|
||||
setVisible(false);
|
||||
if(callback) callback(filename);
|
||||
}
|
|
@ -1,37 +0,0 @@
|
|||
struct FileBrowser : Window {
|
||||
VerticalLayout layout;
|
||||
HorizontalLayout pathLayout;
|
||||
LineEdit pathEdit;
|
||||
Button pathBrowse;
|
||||
Button pathUp;
|
||||
ListView fileList;
|
||||
HorizontalLayout controlLayout;
|
||||
Label filterLabel;
|
||||
Button openButton;
|
||||
|
||||
struct Mode { enum : unsigned { Default, NES, SNES, GameBoy, GameBoyColor, GameBoyAdvance, Satellaview, SufamiTurbo }; };
|
||||
void open(const string &title, unsigned mode, function<void (string)> callback);
|
||||
|
||||
FileBrowser();
|
||||
~FileBrowser();
|
||||
|
||||
private:
|
||||
configuration config;
|
||||
struct FilterMode {
|
||||
string name;
|
||||
string path;
|
||||
lstring filter;
|
||||
} *mode;
|
||||
vector<FilterMode> filterModes;
|
||||
|
||||
lstring fileNameList;
|
||||
function<void (string)> callback;
|
||||
|
||||
void synchronize();
|
||||
void setPath(const string &path);
|
||||
void fileListActivate();
|
||||
bool loadFolder(const string &path);
|
||||
void loadFile(const string &filename);
|
||||
};
|
||||
|
||||
extern FileBrowser *fileBrowser;
|
|
@ -1,5 +0,0 @@
|
|||
#include "../base.hpp"
|
||||
#include "main-window.cpp"
|
||||
#include "file-browser.cpp"
|
||||
#include "dip-switches.cpp"
|
||||
#include "information-window.cpp"
|
|
@ -1,4 +0,0 @@
|
|||
#include "main-window.hpp"
|
||||
#include "file-browser.hpp"
|
||||
#include "dip-switches.hpp"
|
||||
#include "information-window.hpp"
|
|
@ -1,43 +0,0 @@
|
|||
InformationWindow *informationWindow = nullptr;
|
||||
|
||||
InformationWindow::InformationWindow() {
|
||||
setTitle("Information");
|
||||
|
||||
layout.setMargin(5);
|
||||
markup.setFont(application->monospaceFont);
|
||||
markupXML.setChecked();
|
||||
markupXML.setText("Show original XML markup");
|
||||
|
||||
append(layout);
|
||||
layout.append(markup, {~0, ~0}, 5);
|
||||
layout.append(markupXML, {~0, 0});
|
||||
|
||||
setGeometry({128, 128, 600, 360});
|
||||
windowManager->append(this, "InformationWindow");
|
||||
|
||||
markupXML.onToggle = { &InformationWindow::update, this };
|
||||
}
|
||||
|
||||
void InformationWindow::update() {
|
||||
string markupData = interface->markup();
|
||||
if(markupXML.checked()) {
|
||||
markup.setText(markupData);
|
||||
return;
|
||||
}
|
||||
|
||||
XML::Document document(markupData);
|
||||
markupData = "";
|
||||
parse(document, markupData, 0);
|
||||
markup.setText(markupData);
|
||||
}
|
||||
|
||||
void InformationWindow::parse(XML::Node &header, string &output, unsigned depth) {
|
||||
for(auto &node : header) {
|
||||
if(node.name.beginswith("?")) continue;
|
||||
for(unsigned n = 0; n < depth; n++) output.append(" ");
|
||||
output.append(node.name);
|
||||
if(node.children.size() == 0 && !node.data.empty()) output.append(": ", node.data);
|
||||
output.append("\n");
|
||||
parse(node, output, depth + 1);
|
||||
}
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
struct InformationWindow : Window {
|
||||
VerticalLayout layout;
|
||||
TextEdit markup;
|
||||
CheckBox markupXML;
|
||||
|
||||
void update();
|
||||
void parse(XML::Node &node, string &output, unsigned depth);
|
||||
InformationWindow();
|
||||
};
|
||||
|
||||
extern InformationWindow *informationWindow;
|
|
@ -1,446 +0,0 @@
|
|||
MainWindow *mainWindow = nullptr;
|
||||
|
||||
MainWindow::MainWindow() {
|
||||
setTitle(application->title);
|
||||
setGeometry({ 256, 256, 626, 480 });
|
||||
setBackgroundColor({ 0, 0, 0 });
|
||||
windowManager->append(this, "MainWindow");
|
||||
|
||||
cartridgeMenu.setText("&Load");
|
||||
cartridgeLoadNES.setText("&Famicom ...");
|
||||
cartridgeLoadSNES.setText("&Super Famicom ...");
|
||||
cartridgeLoadGameBoy.setText("&Game Boy ...");
|
||||
cartridgeLoadGameBoyColor.setText("Game Boy &Color ...");
|
||||
cartridgeLoadGameBoyAdvance.setText("Game Boy &Advance ...");
|
||||
cartridgeLoadSuperGameBoy.setText("Super Game Boy ...");
|
||||
cartridgeLoadSatellaview.setText("BS-X Satellaview ...");
|
||||
cartridgeLoadSufamiTurbo.setText("Sufami Turbo ...");
|
||||
|
||||
nesMenu.setText("&Famicom");
|
||||
nesPower.setText("&Power Cycle");
|
||||
nesReset.setText("&Reset");
|
||||
nesPort1.setText("Controller Port &1");
|
||||
nesPort1Device[0].setText("None");
|
||||
nesPort1Device[1].setText("Gamepad");
|
||||
RadioItem::group(nesPort1Device[0], nesPort1Device[1]);
|
||||
nesPort1Device[config->nes.controllerPort1Device].setChecked();
|
||||
nesPort2.setText("Controller Port &2");
|
||||
nesPort2Device[0].setText("None");
|
||||
nesPort2Device[1].setText("Gamepad");
|
||||
RadioItem::group(nesPort2Device[0], nesPort2Device[1]);
|
||||
nesPort2Device[config->nes.controllerPort2Device].setChecked();
|
||||
nesCartridgeUnload.setText("&Unload Cartridge");
|
||||
|
||||
snesMenu.setText("&Super Famicom");
|
||||
snesPower.setText("&Power Cycle");
|
||||
snesReset.setText("&Reset");
|
||||
snesPort1.setText("Controller Port &1");
|
||||
snesPort1Device[0].setText("None");
|
||||
snesPort1Device[1].setText("Gamepad");
|
||||
snesPort1Device[2].setText("Multitap");
|
||||
snesPort1Device[3].setText("Mouse");
|
||||
snesPort1Device[4].setText("Serial USART");
|
||||
RadioItem::group(snesPort1Device[0], snesPort1Device[1], snesPort1Device[2], snesPort1Device[3],
|
||||
snesPort1Device[4]);
|
||||
snesPort1Device[config->snes.controllerPort1Device].setChecked();
|
||||
snesPort2.setText("Controller Port &2");
|
||||
snesPort2Device[0].setText("None");
|
||||
snesPort2Device[1].setText("Gamepad");
|
||||
snesPort2Device[2].setText("Multitap");
|
||||
snesPort2Device[3].setText("Mouse");
|
||||
snesPort2Device[4].setText("Super Scope");
|
||||
snesPort2Device[5].setText("Justifier");
|
||||
snesPort2Device[6].setText("Dual Justifiers");
|
||||
snesPort2Device[7].setText("Serial USART");
|
||||
RadioItem::group(snesPort2Device[0], snesPort2Device[1], snesPort2Device[2], snesPort2Device[3],
|
||||
snesPort2Device[4], snesPort2Device[5], snesPort2Device[6], snesPort2Device[7]);
|
||||
snesPort2Device[config->snes.controllerPort2Device].setChecked();
|
||||
snesCartridgeUnload.setText("&Unload Cartridge");
|
||||
|
||||
gameBoyMenu.setText("&Game Boy");
|
||||
gameBoyPower.setText("&Power Cycle");
|
||||
gameBoyCartridgeUnload.setText("&Unload Cartridge");
|
||||
|
||||
gameBoyAdvanceMenu.setText("&Game Boy Advance");
|
||||
gameBoyAdvancePower.setText("&Power Cycle");
|
||||
gameBoyAdvanceCartridgeUnload.setText("&Unload Cartridge");
|
||||
|
||||
settingsMenu.setText("S&ettings");
|
||||
settingsVideoFilter.setText("Video &Filter");
|
||||
settingsVideoFilterNone.setText("None");
|
||||
setupVideoFilters();
|
||||
settingsVideoShader.setText("Video &Shader");
|
||||
settingsVideoShaderNone.setText("None");
|
||||
settingsVideoShaderBlur.setText("Blur");
|
||||
setupVideoShaders();
|
||||
settingsSynchronizeVideo.setText("Synchronize &Video");
|
||||
settingsSynchronizeVideo.setChecked(config->video.synchronize);
|
||||
settingsSynchronizeAudio.setText("Synchronize &Audio");
|
||||
settingsSynchronizeAudio.setChecked(config->audio.synchronize);
|
||||
settingsCorrectAspectRatio.setText("Correct Aspect &Ratio");
|
||||
settingsCorrectAspectRatio.setChecked(config->video.correctAspectRatio);
|
||||
settingsMaskOverscan.setText("Mask &Overscan");
|
||||
settingsMaskOverscan.setChecked(config->video.maskOverscan);
|
||||
settingsMuteAudio.setText("&Mute Audio");
|
||||
settingsMuteAudio.setChecked(config->audio.mute);
|
||||
settingsConfiguration.setText("&Configuration ...");
|
||||
|
||||
toolsMenu.setText("&Tools");
|
||||
toolsStateSave.setText("&Save State");
|
||||
toolsStateSave1.setText("Slot &1");
|
||||
toolsStateSave2.setText("Slot &2");
|
||||
toolsStateSave3.setText("Slot &3");
|
||||
toolsStateSave4.setText("Slot &4");
|
||||
toolsStateSave5.setText("Slot &5");
|
||||
toolsStateLoad.setText("&Load State");
|
||||
toolsStateLoad1.setText("Slot &1");
|
||||
toolsStateLoad2.setText("Slot &2");
|
||||
toolsStateLoad3.setText("Slot &3");
|
||||
toolsStateLoad4.setText("Slot &4");
|
||||
toolsStateLoad5.setText("Slot &5");
|
||||
toolsInformationWindow.setText("&Information ...");
|
||||
toolsShrinkWindow.setText("Shrink &Window");
|
||||
toolsCheatEditor.setText("&Cheat Editor ...");
|
||||
toolsStateManager.setText("State &Manager ...");
|
||||
|
||||
append(cartridgeMenu);
|
||||
cartridgeMenu.append(cartridgeLoadNES);
|
||||
cartridgeMenu.append(cartridgeLoadSNES);
|
||||
cartridgeMenu.append(cartridgeLoadGameBoy);
|
||||
cartridgeMenu.append(cartridgeLoadGameBoyColor);
|
||||
cartridgeMenu.append(cartridgeLoadGameBoyAdvance);
|
||||
cartridgeMenu.append(cartridgeSeparator);
|
||||
cartridgeMenu.append(cartridgeLoadSuperGameBoy);
|
||||
cartridgeMenu.append(cartridgeLoadSatellaview);
|
||||
cartridgeMenu.append(cartridgeLoadSufamiTurbo);
|
||||
|
||||
append(nesMenu);
|
||||
nesMenu.append(nesPower);
|
||||
nesMenu.append(nesReset);
|
||||
nesMenu.append(nesSeparator1);
|
||||
nesMenu.append(nesPort1);
|
||||
nesPort1.append(nesPort1Device[0]);
|
||||
nesPort1.append(nesPort1Device[1]);
|
||||
nesMenu.append(nesPort2);
|
||||
nesPort2.append(nesPort2Device[0]);
|
||||
nesPort2.append(nesPort2Device[1]);
|
||||
nesMenu.append(nesSeparator2);
|
||||
nesMenu.append(nesCartridgeUnload);
|
||||
|
||||
append(snesMenu);
|
||||
snesMenu.append(snesPower);
|
||||
snesMenu.append(snesReset);
|
||||
snesMenu.append(snesSeparator1);
|
||||
snesMenu.append(snesPort1);
|
||||
for(auto &item : snesPort1Device) snesPort1.append(item);
|
||||
snesMenu.append(snesPort2);
|
||||
for(auto &item : snesPort2Device) snesPort2.append(item);
|
||||
snesMenu.append(snesSeparator2);
|
||||
snesMenu.append(snesCartridgeUnload);
|
||||
|
||||
append(gameBoyMenu);
|
||||
gameBoyMenu.append(gameBoyPower);
|
||||
gameBoyMenu.append(gameBoySeparator);
|
||||
gameBoyMenu.append(gameBoyCartridgeUnload);
|
||||
|
||||
append(gameBoyAdvanceMenu);
|
||||
gameBoyAdvanceMenu.append(gameBoyAdvancePower);
|
||||
gameBoyAdvanceMenu.append(gameBoyAdvanceSeparator);
|
||||
gameBoyAdvanceMenu.append(gameBoyAdvanceCartridgeUnload);
|
||||
|
||||
append(settingsMenu);
|
||||
settingsMenu.append(settingsVideoFilter);
|
||||
settingsVideoFilter.append(settingsVideoFilterNone);
|
||||
if(videoFilterName.size())
|
||||
settingsVideoFilter.append(settingsVideoFilterSeparator);
|
||||
for(unsigned n = 0; n < videoFilterName.size(); n++)
|
||||
settingsVideoFilter.append(settingsVideoFilterList[n]);
|
||||
settingsMenu.append(settingsVideoShader);
|
||||
settingsVideoShader.append(settingsVideoShaderNone);
|
||||
settingsVideoShader.append(settingsVideoShaderBlur);
|
||||
if(videoShaderName.size())
|
||||
settingsVideoShader.append(settingsVideoShaderSeparator);
|
||||
for(unsigned n = 0; n < videoShaderName.size(); n++)
|
||||
settingsVideoShader.append(settingsVideoShaderList[n]);
|
||||
settingsMenu.append(settingsSeparator1);
|
||||
settingsMenu.append(settingsSynchronizeVideo);
|
||||
settingsMenu.append(settingsSynchronizeAudio);
|
||||
settingsMenu.append(settingsSeparator2);
|
||||
settingsMenu.append(settingsCorrectAspectRatio);
|
||||
settingsMenu.append(settingsMaskOverscan);
|
||||
settingsMenu.append(settingsMuteAudio);
|
||||
settingsMenu.append(settingsSeparator3);
|
||||
settingsMenu.append(settingsConfiguration);
|
||||
|
||||
append(toolsMenu);
|
||||
toolsMenu.append(toolsStateSave);
|
||||
toolsStateSave.append(toolsStateSave1);
|
||||
toolsStateSave.append(toolsStateSave2);
|
||||
toolsStateSave.append(toolsStateSave3);
|
||||
toolsStateSave.append(toolsStateSave4);
|
||||
toolsStateSave.append(toolsStateSave5);
|
||||
toolsMenu.append(toolsStateLoad);
|
||||
toolsStateLoad.append(toolsStateLoad1);
|
||||
toolsStateLoad.append(toolsStateLoad2);
|
||||
toolsStateLoad.append(toolsStateLoad3);
|
||||
toolsStateLoad.append(toolsStateLoad4);
|
||||
toolsStateLoad.append(toolsStateLoad5);
|
||||
toolsMenu.append(toolsSeparator);
|
||||
toolsMenu.append(toolsInformationWindow);
|
||||
toolsMenu.append(toolsShrinkWindow);
|
||||
toolsMenu.append(toolsCheatEditor);
|
||||
toolsMenu.append(toolsStateManager);
|
||||
|
||||
setMenuVisible();
|
||||
|
||||
setStatusText("No cartridge loaded");
|
||||
setStatusVisible();
|
||||
|
||||
layout.append(viewport, { 0, 0, 512, 480 });
|
||||
append(layout);
|
||||
|
||||
onClose = [&] { application->quit = true; };
|
||||
onSize = [&] { utility->resizeMainWindow(); };
|
||||
|
||||
cartridgeLoadNES.onActivate = [&] {
|
||||
fileBrowser->open("Load Cartridge - Famicom", FileBrowser::Mode::NES, [](string filename) {
|
||||
interface->nes.loadCartridge(filename);
|
||||
});
|
||||
};
|
||||
|
||||
cartridgeLoadSNES.onActivate = [&] {
|
||||
fileBrowser->open("Load Cartridge - Super Famicom", FileBrowser::Mode::SNES, [](string filename) {
|
||||
string filedata;
|
||||
filedata.readfile({dir(filename),"manifest.xml"});
|
||||
XML::Document document(filedata);
|
||||
if(document["cartridge"]["bsx"]["slot"].exists()
|
||||
&& MessageWindow::question(*mainWindow, "Load BS-X Satellaview data pack?") == MessageWindow::Response::Yes) {
|
||||
mainWindow->filename = filename;
|
||||
fileBrowser->open("Load Cartridge - BS-X Satellaview", FileBrowser::Mode::Satellaview, [](string filename) {
|
||||
interface->snes.loadSatellaviewSlottedCartridge(mainWindow->filename, filename);
|
||||
});
|
||||
} else {
|
||||
interface->snes.loadCartridge(filename);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
cartridgeLoadGameBoy.onActivate = [&] {
|
||||
fileBrowser->open("Load Cartridge - Game Boy", FileBrowser::Mode::GameBoy, [](string filename) {
|
||||
interface->gb.loadCartridge(GB::System::Revision::GameBoy, filename);
|
||||
});
|
||||
};
|
||||
|
||||
cartridgeLoadGameBoyColor.onActivate = [&] {
|
||||
fileBrowser->open("Load Cartridge - Game Boy Color", FileBrowser::Mode::GameBoyColor, [](string filename) {
|
||||
interface->gb.loadCartridge(GB::System::Revision::GameBoyColor, filename);
|
||||
});
|
||||
};
|
||||
|
||||
cartridgeLoadGameBoyAdvance.onActivate = [&] {
|
||||
fileBrowser->open("Load Cartridge - Game Boy Advance", FileBrowser::Mode::GameBoyAdvance, [](string filename) {
|
||||
interface->gba.loadCartridge(filename);
|
||||
});
|
||||
};
|
||||
|
||||
cartridgeLoadSuperGameBoy.onActivate = [&] {
|
||||
fileBrowser->open("Load Cartridge - Super Game Boy", FileBrowser::Mode::GameBoy, [](string filename) {
|
||||
interface->snes.loadSuperGameBoyCartridge(application->path("Super Game Boy.sfc/"), filename);
|
||||
});
|
||||
};
|
||||
|
||||
cartridgeLoadSatellaview.onActivate = [&] {
|
||||
fileBrowser->open("Load Cartridge - BS-X Satellaview", FileBrowser::Mode::Satellaview, [](string filename) {
|
||||
interface->snes.loadSatellaviewCartridge(application->path("BS-X Satellaview.sfc/"), filename);
|
||||
});
|
||||
};
|
||||
|
||||
cartridgeLoadSufamiTurbo.onActivate = [&] {
|
||||
fileBrowser->open("Load Cartridge - Sufami Turbo", FileBrowser::Mode::SufamiTurbo, [](string filename) {
|
||||
string filedata;
|
||||
filedata.readfile({dir(filename),"manifest.xml"});
|
||||
XML::Document document(filedata);
|
||||
if(document["cartridge"]["linkable"].data == "true"
|
||||
&& MessageWindow::question(*mainWindow, "Load linkable cartridge?") == MessageWindow::Response::Yes) {
|
||||
mainWindow->filename = filename;
|
||||
fileBrowser->open("Load Cartridge - Sufami Turbo", FileBrowser::Mode::SufamiTurbo, [](string filename) {
|
||||
if(mainWindow->filename == filename) {
|
||||
MessageWindow::critical(*mainWindow, "It is physically impossible to have the same cartridge in two slots at the same time.");
|
||||
} else {
|
||||
interface->snes.loadSufamiTurboCartridge(application->path("Sufami Turbo.sfc/"), mainWindow->filename, filename);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
interface->snes.loadSufamiTurboCartridge(application->path("Sufami Turbo.sfc/"), filename, "");
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
nesPower.onActivate = { &Interface::power, interface };
|
||||
nesReset.onActivate = { &Interface::reset, interface };
|
||||
|
||||
nesPort1Device[0].onActivate = [&] { interface->setController(0, 0); };
|
||||
nesPort1Device[1].onActivate = [&] { interface->setController(0, 1); };
|
||||
|
||||
nesPort2Device[0].onActivate = [&] { interface->setController(1, 0); };
|
||||
nesPort2Device[1].onActivate = [&] { interface->setController(1, 1); };
|
||||
|
||||
nesCartridgeUnload.onActivate = { &Interface::unloadCartridge, interface };
|
||||
|
||||
snesPower.onActivate = { &Interface::power, interface };
|
||||
snesReset.onActivate = { &Interface::reset, interface };
|
||||
|
||||
snesPort1Device[0].onActivate = [&] { interface->setController(0, 0); };
|
||||
snesPort1Device[1].onActivate = [&] { interface->setController(0, 1); };
|
||||
snesPort1Device[2].onActivate = [&] { interface->setController(0, 2); };
|
||||
snesPort1Device[3].onActivate = [&] { interface->setController(0, 3); };
|
||||
snesPort1Device[4].onActivate = [&] { interface->setController(0, 4); };
|
||||
|
||||
snesPort2Device[0].onActivate = [&] { interface->setController(1, 0); };
|
||||
snesPort2Device[1].onActivate = [&] { interface->setController(1, 1); };
|
||||
snesPort2Device[2].onActivate = [&] { interface->setController(1, 2); };
|
||||
snesPort2Device[3].onActivate = [&] { interface->setController(1, 3); };
|
||||
snesPort2Device[4].onActivate = [&] { interface->setController(1, 4); };
|
||||
snesPort2Device[5].onActivate = [&] { interface->setController(1, 5); };
|
||||
snesPort2Device[6].onActivate = [&] { interface->setController(1, 6); };
|
||||
snesPort2Device[7].onActivate = [&] { interface->setController(1, 7); };
|
||||
|
||||
snesCartridgeUnload.onActivate = { &Interface::unloadCartridge, interface };
|
||||
|
||||
gameBoyPower.onActivate = { &Interface::power, interface };
|
||||
gameBoyCartridgeUnload.onActivate = { &Interface::unloadCartridge, interface };
|
||||
|
||||
gameBoyAdvancePower.onActivate = { &Interface::power, interface };
|
||||
gameBoyAdvanceCartridgeUnload.onActivate = { &Interface::unloadCartridge, interface };
|
||||
|
||||
settingsVideoFilterNone.onActivate = [&] {
|
||||
config->video.filter = "None";
|
||||
utility->bindVideoFilter();
|
||||
};
|
||||
|
||||
settingsVideoShaderNone.onActivate = [&] {
|
||||
config->video.shader = "None";
|
||||
utility->bindVideoShader();
|
||||
};
|
||||
|
||||
settingsVideoShaderBlur.onActivate = [&] {
|
||||
config->video.shader = "Blur";
|
||||
utility->bindVideoShader();
|
||||
};
|
||||
|
||||
settingsSynchronizeVideo.onToggle = [&] {
|
||||
config->video.synchronize = settingsSynchronizeVideo.checked();
|
||||
video.set(Video::Synchronize, config->video.synchronize);
|
||||
};
|
||||
|
||||
settingsSynchronizeAudio.onToggle = [&] {
|
||||
config->audio.synchronize = settingsSynchronizeAudio.checked();
|
||||
audio.set(Audio::Synchronize, config->audio.synchronize);
|
||||
};
|
||||
|
||||
settingsCorrectAspectRatio.onToggle = [&] {
|
||||
config->video.correctAspectRatio = settingsCorrectAspectRatio.checked();
|
||||
utility->resizeMainWindow();
|
||||
};
|
||||
|
||||
settingsMaskOverscan.onToggle = [&] {
|
||||
config->video.maskOverscan = settingsMaskOverscan.checked();
|
||||
};
|
||||
|
||||
settingsMuteAudio.onToggle = [&] {
|
||||
config->audio.mute = settingsMuteAudio.checked();
|
||||
dspaudio.setVolume(config->audio.mute == false ? (double)config->audio.volume / 100.0 : 0.0);
|
||||
};
|
||||
|
||||
settingsConfiguration.onActivate = [&] { settingsWindow->setVisible(); };
|
||||
|
||||
toolsStateSave1.onActivate = [&] { interface->saveState(1); };
|
||||
toolsStateSave2.onActivate = [&] { interface->saveState(2); };
|
||||
toolsStateSave3.onActivate = [&] { interface->saveState(3); };
|
||||
toolsStateSave4.onActivate = [&] { interface->saveState(4); };
|
||||
toolsStateSave5.onActivate = [&] { interface->saveState(5); };
|
||||
|
||||
toolsStateLoad1.onActivate = [&] { interface->loadState(1); };
|
||||
toolsStateLoad2.onActivate = [&] { interface->loadState(2); };
|
||||
toolsStateLoad3.onActivate = [&] { interface->loadState(3); };
|
||||
toolsStateLoad4.onActivate = [&] { interface->loadState(4); };
|
||||
toolsStateLoad5.onActivate = [&] { interface->loadState(5); };
|
||||
|
||||
toolsInformationWindow.onActivate = [&] { informationWindow->setVisible(); };
|
||||
toolsShrinkWindow.onActivate = [&] { utility->resizeMainWindow(true); };
|
||||
toolsCheatEditor.onActivate = [&] { cheatEditor->setVisible(); };
|
||||
toolsStateManager.onActivate = [&] { stateManager->setVisible(); };
|
||||
|
||||
synchronize();
|
||||
}
|
||||
|
||||
void MainWindow::synchronize() {
|
||||
bool enable = interface->cartridgeLoaded();
|
||||
toolsStateSave.setEnabled(enable);
|
||||
toolsStateLoad.setEnabled(enable);
|
||||
toolsInformationWindow.setEnabled(enable);
|
||||
}
|
||||
|
||||
void MainWindow::setupVideoFilters() {
|
||||
string path = { application->basepath, "filters/" };
|
||||
lstring files = directory::files(path, "*.filter");
|
||||
if(files.size() == 0) {
|
||||
path = { application->userpath, "filters/" };
|
||||
files = directory::files(path, "*.filter");
|
||||
}
|
||||
set<RadioItem&> group;
|
||||
|
||||
settingsVideoFilterList = new RadioItem[files.size()];
|
||||
for(unsigned n = 0; n < files.size(); n++) {
|
||||
string name = files[n];
|
||||
videoFilterName.append({ path, name });
|
||||
if(auto position = name.position(".filter")) name[position()] = 0;
|
||||
|
||||
settingsVideoFilterList[n].setText(name);
|
||||
settingsVideoFilterList[n].onActivate = [&, n] {
|
||||
config->video.filter = videoFilterName[n];
|
||||
utility->bindVideoFilter();
|
||||
};
|
||||
}
|
||||
|
||||
group.append(settingsVideoFilterNone);
|
||||
for(unsigned n = 0; n < files.size(); n++) group.append(settingsVideoFilterList[n]);
|
||||
RadioItem::group(group);
|
||||
|
||||
if(config->video.filter == "None") settingsVideoFilterNone.setChecked();
|
||||
for(unsigned n = 0; n < files.size(); n++)
|
||||
if(config->video.filter == videoFilterName[n]) settingsVideoFilterList[n].setChecked();
|
||||
}
|
||||
|
||||
void MainWindow::setupVideoShaders() {
|
||||
string path = { application->basepath, "shaders/" };
|
||||
lstring files = directory::files(path, { "*.", config->video.driver, ".shader" });
|
||||
if(files.size() == 0) {
|
||||
path = { application->userpath, "shaders/" };
|
||||
files = directory::files(path, { "*.", config->video.driver, ".shader" });
|
||||
}
|
||||
set<RadioItem&> group;
|
||||
|
||||
settingsVideoShaderList = new RadioItem[files.size()];
|
||||
for(unsigned n = 0; n < files.size(); n++) {
|
||||
string name = files[n];
|
||||
videoShaderName.append({ path, name });
|
||||
if(auto position = name.position(string{ ".", config->video.driver, ".shader" })) name[position()] = 0;
|
||||
|
||||
settingsVideoShaderList[n].setText(name);
|
||||
settingsVideoShaderList[n].onActivate = [&, n] {
|
||||
config->video.shader = videoShaderName[n];
|
||||
utility->bindVideoShader();
|
||||
};
|
||||
}
|
||||
|
||||
group.append(settingsVideoShaderNone);
|
||||
group.append(settingsVideoShaderBlur);
|
||||
for(unsigned n = 0; n < files.size(); n++) group.append(settingsVideoShaderList[n]);
|
||||
RadioItem::group(group);
|
||||
|
||||
if(config->video.shader == "None") settingsVideoShaderNone.setChecked();
|
||||
if(config->video.shader == "Blur") settingsVideoShaderBlur.setChecked();
|
||||
for(unsigned n = 0; n < files.size(); n++)
|
||||
if(config->video.shader == videoShaderName[n]) settingsVideoShaderList[n].setChecked();
|
||||
}
|
|
@ -1,99 +0,0 @@
|
|||
struct MainWindow : Window {
|
||||
FixedLayout layout;
|
||||
Viewport viewport;
|
||||
|
||||
Menu cartridgeMenu;
|
||||
Item cartridgeLoadSNES;
|
||||
Item cartridgeLoadNES;
|
||||
Item cartridgeLoadGameBoy;
|
||||
Item cartridgeLoadGameBoyColor;
|
||||
Item cartridgeLoadGameBoyAdvance;
|
||||
Separator cartridgeSeparator;
|
||||
Item cartridgeLoadSuperGameBoy;
|
||||
Item cartridgeLoadSatellaview;
|
||||
Item cartridgeLoadSufamiTurbo;
|
||||
|
||||
Menu nesMenu;
|
||||
Item nesPower;
|
||||
Item nesReset;
|
||||
Separator nesSeparator1;
|
||||
Menu nesPort1;
|
||||
RadioItem nesPort1Device[2];
|
||||
Menu nesPort2;
|
||||
RadioItem nesPort2Device[2];
|
||||
Separator nesSeparator2;
|
||||
Item nesCartridgeUnload;
|
||||
|
||||
Menu snesMenu;
|
||||
Item snesPower;
|
||||
Item snesReset;
|
||||
Separator snesSeparator1;
|
||||
Menu snesPort1;
|
||||
RadioItem snesPort1Device[5];
|
||||
Menu snesPort2;
|
||||
RadioItem snesPort2Device[8];
|
||||
Separator snesSeparator2;
|
||||
Item snesCartridgeUnload;
|
||||
|
||||
Menu gameBoyMenu;
|
||||
Item gameBoyPower;
|
||||
Separator gameBoySeparator;
|
||||
Item gameBoyCartridgeUnload;
|
||||
|
||||
Menu gameBoyAdvanceMenu;
|
||||
Item gameBoyAdvancePower;
|
||||
Separator gameBoyAdvanceSeparator;
|
||||
Item gameBoyAdvanceCartridgeUnload;
|
||||
|
||||
Menu settingsMenu;
|
||||
Menu settingsVideoFilter;
|
||||
RadioItem settingsVideoFilterNone;
|
||||
Separator settingsVideoFilterSeparator;
|
||||
RadioItem *settingsVideoFilterList;
|
||||
Menu settingsVideoShader;
|
||||
RadioItem settingsVideoShaderNone;
|
||||
RadioItem settingsVideoShaderBlur;
|
||||
Separator settingsVideoShaderSeparator;
|
||||
RadioItem *settingsVideoShaderList;
|
||||
Separator settingsSeparator1;
|
||||
CheckItem settingsSynchronizeVideo;
|
||||
CheckItem settingsSynchronizeAudio;
|
||||
Separator settingsSeparator2;
|
||||
CheckItem settingsCorrectAspectRatio;
|
||||
CheckItem settingsMaskOverscan;
|
||||
CheckItem settingsMuteAudio;
|
||||
Separator settingsSeparator3;
|
||||
Item settingsConfiguration;
|
||||
|
||||
Menu toolsMenu;
|
||||
Menu toolsStateSave;
|
||||
Item toolsStateSave1;
|
||||
Item toolsStateSave2;
|
||||
Item toolsStateSave3;
|
||||
Item toolsStateSave4;
|
||||
Item toolsStateSave5;
|
||||
Menu toolsStateLoad;
|
||||
Item toolsStateLoad1;
|
||||
Item toolsStateLoad2;
|
||||
Item toolsStateLoad3;
|
||||
Item toolsStateLoad4;
|
||||
Item toolsStateLoad5;
|
||||
Separator toolsSeparator;
|
||||
Item toolsInformationWindow;
|
||||
Item toolsShrinkWindow;
|
||||
Item toolsCheatEditor;
|
||||
Item toolsStateManager;
|
||||
|
||||
void synchronize();
|
||||
MainWindow();
|
||||
|
||||
private:
|
||||
lstring videoFilterName;
|
||||
lstring videoShaderName;
|
||||
string filename;
|
||||
|
||||
void setupVideoFilters();
|
||||
void setupVideoShaders();
|
||||
};
|
||||
|
||||
extern MainWindow *mainWindow;
|
|
@ -1,53 +0,0 @@
|
|||
int16_t GbController::poll(unsigned n) {
|
||||
switch(n) {
|
||||
case 0: return up.poll() & !down.poll();
|
||||
case 1: return down.poll() & !up.poll();
|
||||
case 2: return left.poll() & !right.poll();
|
||||
case 3: return right.poll() & !left.poll();
|
||||
case 4: return b.poll() | bTurbo.poll();
|
||||
case 5: return a.poll() | aTurbo.poll();
|
||||
case 6: return select.poll();
|
||||
case 7: return start.poll();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
GbController::GbController() {
|
||||
name = "Controller";
|
||||
|
||||
up.name = "Up";
|
||||
down.name = "Down";
|
||||
left.name = "Left";
|
||||
right.name = "Right";
|
||||
b.name = "B";
|
||||
a.name = "A";
|
||||
select.name = "Select";
|
||||
start.name = "Start";
|
||||
bTurbo.name = "Turbo B";
|
||||
aTurbo.name = "Turbo A";
|
||||
|
||||
up.mapping = "KB0::Up";
|
||||
down.mapping = "KB0::Down";
|
||||
left.mapping = "KB0::Left";
|
||||
right.mapping = "KB0::Right";
|
||||
b.mapping = "KB0::Z";
|
||||
a.mapping = "KB0::X";
|
||||
select.mapping = "KB0::Apostrophe";
|
||||
start.mapping = "KB0::Return";
|
||||
|
||||
append(up, down, left, right, b, a, select, start, bTurbo, aTurbo);
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
GbDevice::GbDevice() {
|
||||
name = "Device";
|
||||
append(controller);
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
GbInput::GbInput() {
|
||||
name = "Game Boy";
|
||||
append(device);
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue