mirror of https://github.com/bsnes-emu/bsnes.git
Update to v088r13 release.
byuu says: Changelog: - fixed Super Game Boy input - Sufami Turbo prompts to load second slot now (you can cancel to leave it empty) - NEC/Hitachi/ARM DSP firmware is loaded; NEC RAM is saved - folders are grouped properly: Sufami Turbo save RAM saves to its slot folder, etc. - title shows properly (SGB shows GB game name only, BS-X slotted shows game name and optional slot name, etc.) - above extends to saving cheats and such in their correct folders as well - added cheat editor and cheat database - and hooked up the requisite SGB mode loads and can use GB cheats, because that's kinda cool - added state manager - input settings, cheat editor and state manager all have erase (one) and reset (all) buttons now - lots of cleanup and restructuring on Emulator::Interface; *almost* finished with it now Remaining: - BS-X BIOS won't show the data pack - need XML mapping information window - need NSS DIP switch settings window - need video shaders - need driver selection - need to hide controllers that have no inputs from the input mapping list (tempted to just remove "None" as a controller option ...) ethos is currently 88KB of code, ui is 167KB. We're missing about 5-10KB of code in ethos to complete it, so the rewrite nearly cut the GUI code size in half, while support all of the same functionality and allowing the easy addition and removal of entire systems.
This commit is contained in:
parent
5d273c5265
commit
3cb04b101b
|
@ -3,7 +3,7 @@
|
||||||
|
|
||||||
namespace Emulator {
|
namespace Emulator {
|
||||||
static const char Name[] = "bsnes";
|
static const char Name[] = "bsnes";
|
||||||
static const char Version[] = "088.12";
|
static const char Version[] = "088.13";
|
||||||
static const char Author[] = "byuu";
|
static const char Author[] = "byuu";
|
||||||
static const char License[] = "GPLv3";
|
static const char License[] = "GPLv3";
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,29 +12,18 @@ struct Interface {
|
||||||
double aspectRatio;
|
double aspectRatio;
|
||||||
unsigned frequency;
|
unsigned frequency;
|
||||||
bool resettable;
|
bool resettable;
|
||||||
|
|
||||||
struct Media {
|
|
||||||
string name;
|
|
||||||
string extension;
|
|
||||||
};
|
|
||||||
vector<Media> media;
|
|
||||||
} information;
|
} information;
|
||||||
|
|
||||||
struct Media {
|
struct Media {
|
||||||
unsigned id;
|
unsigned id;
|
||||||
string name;
|
string name;
|
||||||
string extension;
|
string type;
|
||||||
string path;
|
string path;
|
||||||
|
string extension;
|
||||||
};
|
};
|
||||||
vector<Media> firmware;
|
|
||||||
|
|
||||||
struct Schema : Media {
|
vector<Media> firmware;
|
||||||
vector<Media> slot;
|
vector<Media> media;
|
||||||
Schema(const Media &media) {
|
|
||||||
id = media.id, name = media.name, extension = media.extension, path = media.path;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
vector<Schema> schema;
|
|
||||||
|
|
||||||
struct Memory {
|
struct Memory {
|
||||||
unsigned id;
|
unsigned id;
|
||||||
|
@ -42,21 +31,24 @@ struct Interface {
|
||||||
};
|
};
|
||||||
vector<Memory> memory;
|
vector<Memory> memory;
|
||||||
|
|
||||||
|
struct Device {
|
||||||
|
unsigned id;
|
||||||
|
unsigned portmask;
|
||||||
|
string name;
|
||||||
|
struct Input {
|
||||||
|
unsigned id;
|
||||||
|
unsigned type; //0 = digital, 1 = analog
|
||||||
|
string name;
|
||||||
|
unsigned guid;
|
||||||
|
};
|
||||||
|
vector<Input> input;
|
||||||
|
vector<unsigned> order;
|
||||||
|
};
|
||||||
|
vector<Device> device;
|
||||||
|
|
||||||
struct Port {
|
struct Port {
|
||||||
unsigned id;
|
unsigned id;
|
||||||
string name;
|
string name;
|
||||||
struct Device {
|
|
||||||
unsigned id;
|
|
||||||
string name;
|
|
||||||
struct Input {
|
|
||||||
unsigned id;
|
|
||||||
unsigned type; //0 = digital, 1 = analog
|
|
||||||
string name;
|
|
||||||
unsigned guid;
|
|
||||||
};
|
|
||||||
vector<Input> input;
|
|
||||||
vector<unsigned> order;
|
|
||||||
};
|
|
||||||
vector<Device> device;
|
vector<Device> device;
|
||||||
};
|
};
|
||||||
vector<Port> port;
|
vector<Port> port;
|
||||||
|
@ -94,6 +86,8 @@ struct Interface {
|
||||||
|
|
||||||
//media interface
|
//media interface
|
||||||
virtual bool loaded() { return false; }
|
virtual bool loaded() { return false; }
|
||||||
|
virtual string sha256() { return ""; }
|
||||||
|
virtual unsigned group(unsigned id) { return 0u; }
|
||||||
virtual void load(unsigned id, const stream &memory, const string &markup = "") {}
|
virtual void load(unsigned id, const stream &memory, const string &markup = "") {}
|
||||||
virtual void save(unsigned id, const stream &memory) {}
|
virtual void save(unsigned id, const stream &memory) {}
|
||||||
virtual void unload() {}
|
virtual void unload() {}
|
||||||
|
@ -108,6 +102,9 @@ struct Interface {
|
||||||
virtual serializer serialize() = 0;
|
virtual serializer serialize() = 0;
|
||||||
virtual bool unserialize(serializer&) = 0;
|
virtual bool unserialize(serializer&) = 0;
|
||||||
|
|
||||||
|
//cheat functions
|
||||||
|
virtual void cheatSet(const lstring& = lstring{}) {}
|
||||||
|
|
||||||
//utility functions
|
//utility functions
|
||||||
virtual void updatePalette() {}
|
virtual void updatePalette() {}
|
||||||
};
|
};
|
||||||
|
|
|
@ -8,6 +8,10 @@ bool Interface::loaded() {
|
||||||
return cartridge.loaded();
|
return cartridge.loaded();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
string Interface::sha256() {
|
||||||
|
return cartridge.sha256();
|
||||||
|
}
|
||||||
|
|
||||||
void Interface::load(unsigned id, const stream &stream, const string &markup) {
|
void Interface::load(unsigned id, const stream &stream, const string &markup) {
|
||||||
if(id == ID::ROM) {
|
if(id == ID::ROM) {
|
||||||
cartridge.load(markup, stream);
|
cartridge.load(markup, stream);
|
||||||
|
@ -52,6 +56,18 @@ bool Interface::unserialize(serializer &s) {
|
||||||
return system.unserialize(s);
|
return system.unserialize(s);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Interface::cheatSet(const lstring &list) {
|
||||||
|
cheat.reset();
|
||||||
|
for(auto &code : list) {
|
||||||
|
lstring codelist = code.split("+");
|
||||||
|
for(auto &part : codelist) {
|
||||||
|
unsigned addr, data, comp;
|
||||||
|
if(Cheat::decode(part, addr, data, comp)) cheat.append({addr, data, comp});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cheat.synchronize();
|
||||||
|
}
|
||||||
|
|
||||||
void Interface::updatePalette() {
|
void Interface::updatePalette() {
|
||||||
video.generate_palette();
|
video.generate_palette();
|
||||||
}
|
}
|
||||||
|
@ -67,35 +83,32 @@ Interface::Interface() {
|
||||||
information.frequency = 1789772;
|
information.frequency = 1789772;
|
||||||
information.resettable = true;
|
information.resettable = true;
|
||||||
|
|
||||||
information.media.append({"Famicom", "fc"});
|
media.append({ID::ROM, "Famicom", "sys", "program.rom", "fc"});
|
||||||
|
|
||||||
schema.append(Media{ID::ROM, "Famicom", "fc", "program.rom"});
|
|
||||||
|
|
||||||
{
|
{
|
||||||
Port port{0, "Port 1"};
|
Device device{0, ID::Port1 | ID::Port2, "Controller"};
|
||||||
port.device.append(controller());
|
device.input.append({0, 0, "A" });
|
||||||
this->port.append(port);
|
device.input.append({1, 0, "B" });
|
||||||
|
device.input.append({2, 0, "Select"});
|
||||||
|
device.input.append({3, 0, "Start" });
|
||||||
|
device.input.append({4, 0, "Up" });
|
||||||
|
device.input.append({5, 0, "Down" });
|
||||||
|
device.input.append({6, 0, "Left" });
|
||||||
|
device.input.append({7, 0, "Right" });
|
||||||
|
device.order = {4, 5, 6, 7, 1, 0, 2, 3};
|
||||||
|
this->device.append(device);
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
port.append({ID::Port1, "Port 1"});
|
||||||
Port port{1, "Port 2"};
|
port.append({ID::Port2, "Port 2"});
|
||||||
port.device.append(controller());
|
|
||||||
this->port.append(port);
|
for(auto &device : this->device) {
|
||||||
|
for(auto &port : this->port) {
|
||||||
|
if(device.portmask & port.id) {
|
||||||
|
port.device.append(device);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Emulator::Interface::Port::Device Interface::controller() {
|
|
||||||
Port::Device device{0, "Controller"};
|
|
||||||
device.input.append({0, 0, "A" });
|
|
||||||
device.input.append({1, 0, "B" });
|
|
||||||
device.input.append({2, 0, "Select"});
|
|
||||||
device.input.append({3, 0, "Start" });
|
|
||||||
device.input.append({4, 0, "Up" });
|
|
||||||
device.input.append({5, 0, "Down" });
|
|
||||||
device.input.append({6, 0, "Left" });
|
|
||||||
device.input.append({7, 0, "Right" });
|
|
||||||
device.order = {4, 5, 6, 7, 1, 0, 2, 3};
|
|
||||||
return device;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,10 +7,16 @@ struct ID {
|
||||||
ROM,
|
ROM,
|
||||||
RAM,
|
RAM,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum : unsigned {
|
||||||
|
Port1 = 1,
|
||||||
|
Port2 = 2,
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
struct Interface : Emulator::Interface {
|
struct Interface : Emulator::Interface {
|
||||||
bool loaded();
|
bool loaded();
|
||||||
|
string sha256();
|
||||||
void load(unsigned id, const stream &stream, const string &markup = "");
|
void load(unsigned id, const stream &stream, const string &markup = "");
|
||||||
void save(unsigned id, const stream &stream);
|
void save(unsigned id, const stream &stream);
|
||||||
void unload();
|
void unload();
|
||||||
|
@ -22,12 +28,11 @@ struct Interface : Emulator::Interface {
|
||||||
serializer serialize();
|
serializer serialize();
|
||||||
bool unserialize(serializer&);
|
bool unserialize(serializer&);
|
||||||
|
|
||||||
|
void cheatSet(const lstring&);
|
||||||
|
|
||||||
void updatePalette();
|
void updatePalette();
|
||||||
|
|
||||||
Interface();
|
Interface();
|
||||||
|
|
||||||
private:
|
|
||||||
Port::Device controller();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
extern Interface *interface;
|
extern Interface *interface;
|
||||||
|
|
|
@ -8,6 +8,10 @@ bool Interface::loaded() {
|
||||||
return cartridge.loaded();
|
return cartridge.loaded();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
string Interface::sha256() {
|
||||||
|
return cartridge.sha256();
|
||||||
|
}
|
||||||
|
|
||||||
void Interface::load(unsigned id, const stream &stream, const string &markup) {
|
void Interface::load(unsigned id, const stream &stream, const string &markup) {
|
||||||
if(id == ID::GameBoyBootROM) {
|
if(id == ID::GameBoyBootROM) {
|
||||||
stream.read(system.bootROM.dmg, min( 256u, stream.size()));
|
stream.read(system.bootROM.dmg, min( 256u, stream.size()));
|
||||||
|
@ -67,6 +71,18 @@ bool Interface::unserialize(serializer &s) {
|
||||||
return system.unserialize(s);
|
return system.unserialize(s);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Interface::cheatSet(const lstring &list) {
|
||||||
|
cheat.reset();
|
||||||
|
for(auto &code : list) {
|
||||||
|
lstring codelist = code.split("+");
|
||||||
|
for(auto &part : codelist) {
|
||||||
|
unsigned addr, data, comp;
|
||||||
|
if(Cheat::decode(part, addr, data, comp)) cheat.append({addr, data, comp});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cheat.synchronize();
|
||||||
|
}
|
||||||
|
|
||||||
void Interface::updatePalette() {
|
void Interface::updatePalette() {
|
||||||
video.generate_palette();
|
video.generate_palette();
|
||||||
}
|
}
|
||||||
|
@ -82,33 +98,28 @@ Interface::Interface() {
|
||||||
information.frequency = 4194304;
|
information.frequency = 4194304;
|
||||||
information.resettable = false;
|
information.resettable = false;
|
||||||
|
|
||||||
information.media.append({"Game Boy", "gb" });
|
|
||||||
information.media.append({"Game Boy Color", "gbc"});
|
|
||||||
|
|
||||||
firmware.append({ID::GameBoyBootROM, "Game Boy", "sys", "boot.rom"});
|
firmware.append({ID::GameBoyBootROM, "Game Boy", "sys", "boot.rom"});
|
||||||
firmware.append({ID::SuperGameBoyBootROM, "Super Game Boy", "sfc", "boot.rom"});
|
firmware.append({ID::SuperGameBoyBootROM, "Super Game Boy", "sfc", "boot.rom"});
|
||||||
firmware.append({ID::GameBoyColorBootROM, "Game Boy Color", "sys", "boot.rom"});
|
firmware.append({ID::GameBoyColorBootROM, "Game Boy Color", "sys", "boot.rom"});
|
||||||
|
|
||||||
schema.append(Media{ID::GameBoyROM, "Game Boy", "gb", "program.rom"});
|
media.append({ID::GameBoyROM, "Game Boy", "sys", "program.rom", "gb" });
|
||||||
schema.append(Media{ID::GameBoyColorROM, "Game Boy Color", "gbc", "program.rom"});
|
media.append({ID::GameBoyColorROM, "Game Boy Color", "sys", "program.rom", "gbc"});
|
||||||
|
|
||||||
{
|
{
|
||||||
Port port{0, "Device"};
|
Device device{0, ID::Device, "Controller"};
|
||||||
{
|
device.input.append({0, 0, "Up" });
|
||||||
Port::Device device{0, "Controller"};
|
device.input.append({1, 0, "Down" });
|
||||||
device.input.append({0, 0, "Up" });
|
device.input.append({2, 0, "Left" });
|
||||||
device.input.append({1, 0, "Down" });
|
device.input.append({3, 0, "Right" });
|
||||||
device.input.append({2, 0, "Left" });
|
device.input.append({4, 0, "B" });
|
||||||
device.input.append({3, 0, "Right" });
|
device.input.append({5, 0, "A" });
|
||||||
device.input.append({4, 0, "B" });
|
device.input.append({6, 0, "Select"});
|
||||||
device.input.append({5, 0, "A" });
|
device.input.append({7, 0, "Start" });
|
||||||
device.input.append({6, 0, "Select"});
|
device.order = {0, 1, 2, 3, 4, 5, 6, 7};
|
||||||
device.input.append({7, 0, "Start" });
|
this->device.append(device);
|
||||||
device.order = {0, 1, 2, 3, 4, 5, 6, 7};
|
|
||||||
port.device.append(device);
|
|
||||||
}
|
|
||||||
this->port.append(port);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
port.append({ID::Device, "Device", {device[0]}});
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,6 +11,10 @@ struct ID {
|
||||||
GameBoyColorROM,
|
GameBoyColorROM,
|
||||||
RAM,
|
RAM,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum : unsigned {
|
||||||
|
Device = 1,
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
struct Interface : Emulator::Interface {
|
struct Interface : Emulator::Interface {
|
||||||
|
@ -19,6 +23,7 @@ struct Interface : Emulator::Interface {
|
||||||
virtual void joypWrite(bool p15, bool p14) {}
|
virtual void joypWrite(bool p15, bool p14) {}
|
||||||
|
|
||||||
bool loaded();
|
bool loaded();
|
||||||
|
string sha256();
|
||||||
void load(unsigned id, const stream &stream, const string &markup = "");
|
void load(unsigned id, const stream &stream, const string &markup = "");
|
||||||
void save(unsigned id, const stream &stream);
|
void save(unsigned id, const stream &stream);
|
||||||
void unload();
|
void unload();
|
||||||
|
@ -30,6 +35,8 @@ struct Interface : Emulator::Interface {
|
||||||
serializer serialize();
|
serializer serialize();
|
||||||
bool unserialize(serializer&);
|
bool unserialize(serializer&);
|
||||||
|
|
||||||
|
void cheatSet(const lstring&);
|
||||||
|
|
||||||
void updatePalette();
|
void updatePalette();
|
||||||
|
|
||||||
Interface();
|
Interface();
|
||||||
|
|
|
@ -86,31 +86,27 @@ Interface::Interface() {
|
||||||
information.frequency = 32768;
|
information.frequency = 32768;
|
||||||
information.resettable = false;
|
information.resettable = false;
|
||||||
|
|
||||||
information.media.append({"Game Boy Advance", "gba"});
|
|
||||||
|
|
||||||
firmware.append({ID::BIOS, "Game Boy Advance", "sys", "bios.rom"});
|
firmware.append({ID::BIOS, "Game Boy Advance", "sys", "bios.rom"});
|
||||||
|
|
||||||
schema.append(Media{ID::ROM, "Game Boy Advance", "gba", "program.rom"});
|
media.append({ID::ROM, "Game Boy Advance", "sys", "program.rom", "gba"});
|
||||||
|
|
||||||
{
|
{
|
||||||
Port port{0, "Device"};
|
Device device{0, ID::Device, "Controller"};
|
||||||
{
|
device.input.append({0, 0, "A" });
|
||||||
Port::Device device{0, "Controller"};
|
device.input.append({1, 0, "B" });
|
||||||
device.input.append({0, 0, "A" });
|
device.input.append({2, 0, "Select"});
|
||||||
device.input.append({1, 0, "B" });
|
device.input.append({3, 0, "Start" });
|
||||||
device.input.append({2, 0, "Select"});
|
device.input.append({4, 0, "Right" });
|
||||||
device.input.append({3, 0, "Start" });
|
device.input.append({5, 0, "Left" });
|
||||||
device.input.append({4, 0, "Right" });
|
device.input.append({6, 0, "Up" });
|
||||||
device.input.append({5, 0, "Left" });
|
device.input.append({7, 0, "Down" });
|
||||||
device.input.append({6, 0, "Up" });
|
device.input.append({8, 0, "R" });
|
||||||
device.input.append({7, 0, "Down" });
|
device.input.append({9, 0, "L" });
|
||||||
device.input.append({8, 0, "R" });
|
device.order = {6, 7, 5, 4, 1, 0, 9, 8, 2, 3};
|
||||||
device.input.append({9, 0, "L" });
|
this->device.append(device);
|
||||||
device.order = {6, 7, 5, 4, 1, 0, 9, 8, 2, 3};
|
|
||||||
port.device.append(device);
|
|
||||||
}
|
|
||||||
this->port.append(port);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
port.append({ID::Device, "Device", {device[0]}});
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,6 +10,10 @@ struct ID {
|
||||||
EEPROM,
|
EEPROM,
|
||||||
FlashROM,
|
FlashROM,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum : unsigned {
|
||||||
|
Device = 1,
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
struct Interface : Emulator::Interface {
|
struct Interface : Emulator::Interface {
|
||||||
|
|
|
@ -11,7 +11,6 @@ Cartridge cartridge;
|
||||||
void Cartridge::load(const string &markup, const stream &stream) {
|
void Cartridge::load(const string &markup, const stream &stream) {
|
||||||
information.markup = markup;
|
information.markup = markup;
|
||||||
rom.copy(stream);
|
rom.copy(stream);
|
||||||
sha256 = nall::sha256(rom.data(), rom.size()); //TODO: special case SGB, BSX, ST mode SHA256 sums
|
|
||||||
|
|
||||||
region = Region::NTSC;
|
region = Region::NTSC;
|
||||||
ram_size = 0;
|
ram_size = 0;
|
||||||
|
@ -19,7 +18,7 @@ void Cartridge::load(const string &markup, const stream &stream) {
|
||||||
has_gb_slot = false;
|
has_gb_slot = false;
|
||||||
has_bs_cart = false;
|
has_bs_cart = false;
|
||||||
has_bs_slot = false;
|
has_bs_slot = false;
|
||||||
has_st_slot = false;
|
has_st_slots = false;
|
||||||
has_nss_dip = false;
|
has_nss_dip = false;
|
||||||
has_superfx = false;
|
has_superfx = false;
|
||||||
has_sa1 = false;
|
has_sa1 = false;
|
||||||
|
@ -39,6 +38,26 @@ void Cartridge::load(const string &markup, const stream &stream) {
|
||||||
parse_markup(markup);
|
parse_markup(markup);
|
||||||
//print(markup, "\n\n");
|
//print(markup, "\n\n");
|
||||||
|
|
||||||
|
//Super Game Boy
|
||||||
|
if(cartridge.has_gb_slot()) {
|
||||||
|
sha256 = nall::sha256(GameBoy::cartridge.romdata, GameBoy::cartridge.romsize);
|
||||||
|
}
|
||||||
|
|
||||||
|
//Broadcast Satellaview
|
||||||
|
else if(cartridge.has_bs_cart() && cartridge.has_bs_slot()) {
|
||||||
|
sha256 = nall::sha256(bsxflash.memory.data(), bsxflash.memory.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
//Sufami Turbo
|
||||||
|
else if(cartridge.has_st_slots()) {
|
||||||
|
sha256 = nall::sha256(sufamiturbo.slotA.rom.data(), sufamiturbo.slotA.rom.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
//Super Famicom
|
||||||
|
else {
|
||||||
|
sha256 = nall::sha256(rom.data(), rom.size());
|
||||||
|
}
|
||||||
|
|
||||||
if(ram_size > 0) {
|
if(ram_size > 0) {
|
||||||
ram.map(allocate<uint8>(ram_size, 0xff), ram_size);
|
ram.map(allocate<uint8>(ram_size, 0xff), ram_size);
|
||||||
interface->memory.append({ID::RAM, "save.ram"});
|
interface->memory.append({ID::RAM, "save.ram"});
|
||||||
|
|
|
@ -25,7 +25,7 @@ struct Cartridge : property<Cartridge> {
|
||||||
readonly<bool> has_gb_slot;
|
readonly<bool> has_gb_slot;
|
||||||
readonly<bool> has_bs_cart;
|
readonly<bool> has_bs_cart;
|
||||||
readonly<bool> has_bs_slot;
|
readonly<bool> has_bs_slot;
|
||||||
readonly<bool> has_st_slot;
|
readonly<bool> has_st_slots;
|
||||||
readonly<bool> has_nss_dip;
|
readonly<bool> has_nss_dip;
|
||||||
readonly<bool> has_superfx;
|
readonly<bool> has_superfx;
|
||||||
readonly<bool> has_sa1;
|
readonly<bool> has_sa1;
|
||||||
|
|
|
@ -110,7 +110,7 @@ void Cartridge::parse_markup_icd2(XML::Node &root) {
|
||||||
if(root.exists() == false) return;
|
if(root.exists() == false) return;
|
||||||
has_gb_slot = true;
|
has_gb_slot = true;
|
||||||
|
|
||||||
interface->mediaRequest({ID::SuperGameBoyROM, "Game Boy", "gb", "program.rom"});
|
interface->mediaRequest({ID::SuperGameBoyROM, "Game Boy", "", "program.rom", "gb"});
|
||||||
|
|
||||||
icd2.revision = max(1, numeral(root["revision"].data));
|
icd2.revision = max(1, numeral(root["revision"].data));
|
||||||
|
|
||||||
|
@ -225,33 +225,12 @@ void Cartridge::parse_markup_necdsp(XML::Node &root) {
|
||||||
string firmware = root["firmware"].data;
|
string firmware = root["firmware"].data;
|
||||||
string sha256 = root["sha256"].data;
|
string sha256 = root["sha256"].data;
|
||||||
|
|
||||||
string path = interface->path((unsigned)Slot::Base, firmware);
|
if(necdsp.revision == NECDSP::Revision::uPD7725) {
|
||||||
unsigned promsize = (necdsp.revision == NECDSP::Revision::uPD7725 ? 2048 : 16384);
|
interface->mediaRequest({ID::Nec7725DSP, "", "", firmware});
|
||||||
unsigned dromsize = (necdsp.revision == NECDSP::Revision::uPD7725 ? 1024 : 2048);
|
}
|
||||||
unsigned filesize = promsize * 3 + dromsize * 2;
|
|
||||||
|
|
||||||
file fp;
|
if(necdsp.revision == NECDSP::Revision::uPD96050) {
|
||||||
if(fp.open(path, file::mode::read) == false) {
|
interface->mediaRequest({ID::Nec96050DSP, "", "", firmware});
|
||||||
interface->message({ "Warning: NEC DSP firmware ", firmware, " is missing." });
|
|
||||||
} else if(fp.size() != filesize) {
|
|
||||||
interface->message({ "Warning: NEC DSP firmware ", firmware, " is of the wrong file size." });
|
|
||||||
fp.close();
|
|
||||||
} else {
|
|
||||||
for(unsigned n = 0; n < promsize; n++) necdsp.programROM[n] = fp.readl(3);
|
|
||||||
for(unsigned n = 0; n < dromsize; n++) necdsp.dataROM[n] = fp.readl(2);
|
|
||||||
|
|
||||||
if(!sha256.empty()) {
|
|
||||||
//XML file specified SHA256 sum for program. Verify file matches the hash.
|
|
||||||
fp.seek(0);
|
|
||||||
uint8_t data[filesize];
|
|
||||||
fp.read(data, filesize);
|
|
||||||
|
|
||||||
if(sha256 != nall::sha256(data, filesize)) {
|
|
||||||
interface->message({ "Warning: NEC DSP firmware ", firmware, " SHA256 sum is incorrect." });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fp.close();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for(auto &node : root) {
|
for(auto &node : root) {
|
||||||
|
@ -290,29 +269,7 @@ void Cartridge::parse_markup_hitachidsp(XML::Node &root) {
|
||||||
string firmware = root["firmware"].data;
|
string firmware = root["firmware"].data;
|
||||||
string sha256 = root["sha256"].data;
|
string sha256 = root["sha256"].data;
|
||||||
|
|
||||||
string path = interface->path((unsigned)Slot::Base, firmware);
|
interface->mediaRequest({ID::HitachiDSP, "", "", firmware});
|
||||||
file fp;
|
|
||||||
if(fp.open(path, file::mode::read) == false) {
|
|
||||||
interface->message({ "Warning: Hitachi DSP firmware ", firmware, " is missing." });
|
|
||||||
} else if(fp.size() != 1024 * 3) {
|
|
||||||
interface->message({ "Warning: Hitachi DSP firmware ", firmware, " is of the wrong file size." });
|
|
||||||
fp.close();
|
|
||||||
} else {
|
|
||||||
for(unsigned n = 0; n < 1024; n++) hitachidsp.dataROM[n] = fp.readl(3);
|
|
||||||
|
|
||||||
if(!sha256.empty()) {
|
|
||||||
//XML file specified SHA256 sum for program. Verify file matches the hash.
|
|
||||||
fp.seek(0);
|
|
||||||
uint8 data[3072];
|
|
||||||
fp.read(data, 3072);
|
|
||||||
|
|
||||||
if(sha256 != nall::sha256(data, 3072)) {
|
|
||||||
interface->message({ "Warning: Hitachi DSP firmware ", firmware, " SHA256 sum is incorrect." });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fp.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
for(auto &node : root) {
|
for(auto &node : root) {
|
||||||
if(node.name == "rom") {
|
if(node.name == "rom") {
|
||||||
|
@ -340,24 +297,7 @@ void Cartridge::parse_markup_armdsp(XML::Node &root) {
|
||||||
string firmware = root["firmware"].data;
|
string firmware = root["firmware"].data;
|
||||||
string sha256 = root["sha256"].data;
|
string sha256 = root["sha256"].data;
|
||||||
|
|
||||||
string path = interface->path((unsigned)Slot::Base, firmware);
|
interface->mediaRequest({ID::ArmDSP, "", "", firmware});
|
||||||
file fp;
|
|
||||||
if(fp.open(path, file::mode::read) == false) {
|
|
||||||
interface->message({ "Warning: ARM DSP firmware ", firmware, " is missing." });
|
|
||||||
} else if(fp.size() != 160 * 1024) {
|
|
||||||
interface->message({ "Warning: ARM DSP firmware ", firmware, " is of the wrong file size." });
|
|
||||||
fp.close();
|
|
||||||
} else {
|
|
||||||
fp.read(armdsp.firmware, fp.size());
|
|
||||||
|
|
||||||
if(!sha256.empty()) {
|
|
||||||
if(sha256 != nall::sha256(armdsp.firmware, fp.size())) {
|
|
||||||
interface->message({ "Warning: ARM DSP firmware ", firmware, " SHA256 sum is incorrect." });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fp.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
for(auto &node : root) {
|
for(auto &node : root) {
|
||||||
if(node.name != "map") continue;
|
if(node.name != "map") continue;
|
||||||
|
@ -372,7 +312,7 @@ void Cartridge::parse_markup_bsx(XML::Node &root) {
|
||||||
has_bs_cart = root["mmio"].exists();
|
has_bs_cart = root["mmio"].exists();
|
||||||
has_bs_slot = true;
|
has_bs_slot = true;
|
||||||
|
|
||||||
interface->mediaRequest({ID::BsxFlashROM, "BS-X Satellaview", "bs", "program.rom"});
|
interface->mediaRequest({ID::BsxFlashROM, "BS-X Satellaview", "", "program.rom", "bs"});
|
||||||
|
|
||||||
for(auto &node : root["slot"]) {
|
for(auto &node : root["slot"]) {
|
||||||
if(node.name != "map") continue;
|
if(node.name != "map") continue;
|
||||||
|
@ -398,9 +338,10 @@ void Cartridge::parse_markup_bsx(XML::Node &root) {
|
||||||
|
|
||||||
void Cartridge::parse_markup_sufamiturbo(XML::Node &root) {
|
void Cartridge::parse_markup_sufamiturbo(XML::Node &root) {
|
||||||
if(root.exists() == false) return;
|
if(root.exists() == false) return;
|
||||||
has_st_slot = true;
|
has_st_slots = true;
|
||||||
|
|
||||||
interface->mediaRequest({ID::SufamiTurboSlotAROM, "Sufami Turbo", "st", "program.rom"});
|
interface->mediaRequest({ID::SufamiTurboSlotAROM, "Sufami Turbo - Slot A", "", "program.rom", "st"});
|
||||||
|
interface->mediaRequest({ID::SufamiTurboSlotBROM, "Sufami Turbo - Slot B", "", "program.rom", "st"});
|
||||||
|
|
||||||
for(auto &slot : root) {
|
for(auto &slot : root) {
|
||||||
if(slot.name != "slot") continue;
|
if(slot.name != "slot") continue;
|
||||||
|
|
|
@ -3,7 +3,7 @@ struct CheatCode {
|
||||||
unsigned data;
|
unsigned data;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct Cheat : public linear_vector<CheatCode> {
|
struct Cheat : public vector<CheatCode> {
|
||||||
uint8 *override;
|
uint8 *override;
|
||||||
|
|
||||||
bool enabled() const;
|
bool enabled() const;
|
||||||
|
|
|
@ -96,21 +96,21 @@ int16_t ICD2::inputPoll(unsigned port, unsigned device, unsigned id) {
|
||||||
|
|
||||||
unsigned data = 0x00;
|
unsigned data = 0x00;
|
||||||
switch(joyp_id & mlt_req) {
|
switch(joyp_id & mlt_req) {
|
||||||
case 0: data = ~r6004; break;
|
case 0: data = ~r6004; break;
|
||||||
case 1: data = ~r6005; break;
|
case 1: data = ~r6005; break;
|
||||||
case 2: data = ~r6006; break;
|
case 2: data = ~r6006; break;
|
||||||
case 3: data = ~r6007; break;
|
case 3: data = ~r6007; break;
|
||||||
}
|
}
|
||||||
|
|
||||||
switch((GameBoy::Input)id) {
|
switch((GameBoy::Input)id) {
|
||||||
case GameBoy::Input::Start: return data & 0x80;
|
case GameBoy::Input::Start: return (bool)(data & 0x80);
|
||||||
case GameBoy::Input::Select: return data & 0x40;
|
case GameBoy::Input::Select: return (bool)(data & 0x40);
|
||||||
case GameBoy::Input::B: return data & 0x20;
|
case GameBoy::Input::B: return (bool)(data & 0x20);
|
||||||
case GameBoy::Input::A: return data & 0x10;
|
case GameBoy::Input::A: return (bool)(data & 0x10);
|
||||||
case GameBoy::Input::Down: return data & 0x08;
|
case GameBoy::Input::Down: return (bool)(data & 0x08);
|
||||||
case GameBoy::Input::Up: return data & 0x04;
|
case GameBoy::Input::Up: return (bool)(data & 0x04);
|
||||||
case GameBoy::Input::Left: return data & 0x02;
|
case GameBoy::Input::Left: return (bool)(data & 0x02);
|
||||||
case GameBoy::Input::Right: return data & 0x01;
|
case GameBoy::Input::Right: return (bool)(data & 0x01);
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
|
|
@ -34,7 +34,7 @@ void NECDSP::init() {
|
||||||
|
|
||||||
void NECDSP::load() {
|
void NECDSP::load() {
|
||||||
if(revision == Revision::uPD96050) {
|
if(revision == Revision::uPD96050) {
|
||||||
cartridge.nvram.append({ "upd96050.ram", (uint8_t*)dataRAM, 4096 });
|
interface->memory.append({ID::NecDSPRAM, "upd96050.ram"});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -11,13 +11,13 @@ void SufamiTurbo::load() {
|
||||||
slotB.ram.map(allocate<uint8>(128 * 1024, 0xff), 128 * 1024);
|
slotB.ram.map(allocate<uint8>(128 * 1024, 0xff), 128 * 1024);
|
||||||
|
|
||||||
if(slotA.rom.data()) {
|
if(slotA.rom.data()) {
|
||||||
cartridge.nvram.append({ "save.ram", slotA.ram.data(), slotA.ram.size(), Cartridge::Slot::SufamiTurboA });
|
interface->memory.append({ID::SufamiTurboSlotARAM, "save.ram"});
|
||||||
} else {
|
} else {
|
||||||
slotA.rom.map(allocate<uint8>(128 * 1024, 0xff), 128 * 1024);
|
slotA.rom.map(allocate<uint8>(128 * 1024, 0xff), 128 * 1024);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(slotB.rom.data()) {
|
if(slotB.rom.data()) {
|
||||||
cartridge.nvram.append({ "save.ram", slotB.ram.data(), slotB.ram.size(), Cartridge::Slot::SufamiTurboB });
|
interface->memory.append({ID::SufamiTurboSlotBRAM, "save.ram"});
|
||||||
} else {
|
} else {
|
||||||
slotB.rom.map(allocate<uint8>(128 * 1024, 0xff), 128 * 1024);
|
slotB.rom.map(allocate<uint8>(128 * 1024, 0xff), 128 * 1024);
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,11 +8,63 @@ bool Interface::loaded() {
|
||||||
return cartridge.loaded();
|
return cartridge.loaded();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
string Interface::sha256() {
|
||||||
|
return cartridge.sha256();
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned Interface::group(unsigned id) {
|
||||||
|
switch(id) {
|
||||||
|
case ID::Nec7725DSP:
|
||||||
|
case ID::Nec96050DSP:
|
||||||
|
case ID::HitachiDSP:
|
||||||
|
case ID::ArmDSP:
|
||||||
|
case ID::ROM:
|
||||||
|
case ID::RAM:
|
||||||
|
case ID::NecDSPRAM:
|
||||||
|
case ID::RTC:
|
||||||
|
case ID::SPC7110RTC:
|
||||||
|
case ID::BsxRAM:
|
||||||
|
case ID::BsxPSRAM:
|
||||||
|
return 0;
|
||||||
|
case ID::SuperGameBoyROM:
|
||||||
|
case ID::SuperGameBoyRAM:
|
||||||
|
case ID::SuperGameBoyRTC:
|
||||||
|
return 1;
|
||||||
|
case ID::BsxFlashROM:
|
||||||
|
return 2;
|
||||||
|
case ID::SufamiTurboSlotAROM:
|
||||||
|
case ID::SufamiTurboSlotARAM:
|
||||||
|
return 3;
|
||||||
|
case ID::SufamiTurboSlotBROM:
|
||||||
|
case ID::SufamiTurboSlotBRAM:
|
||||||
|
return 4;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
void Interface::load(unsigned id, const stream &stream, const string &markup) {
|
void Interface::load(unsigned id, const stream &stream, const string &markup) {
|
||||||
if(id == ID::IPLROM) {
|
if(id == ID::IPLROM) {
|
||||||
stream.read(smp.iplrom, min(64u, stream.size()));
|
stream.read(smp.iplrom, min(64u, stream.size()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(id == ID::Nec7725DSP) {
|
||||||
|
for(unsigned n = 0; n < 2048; n++) necdsp.programROM[n] = stream.readl(3);
|
||||||
|
for(unsigned n = 0; n < 1024; n++) necdsp.dataROM[n] = stream.readl(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(id == ID::Nec96050DSP) {
|
||||||
|
for(unsigned n = 0; n < 16384; n++) necdsp.programROM[n] = stream.readl(3);
|
||||||
|
for(unsigned n = 0; n < 2048; n++) necdsp.dataROM[n] = stream.readl(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(id == ID::HitachiDSP) {
|
||||||
|
for(unsigned n = 0; n < 1024; n++) hitachidsp.dataROM[n] = stream.readl(3);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(id == ID::ArmDSP) {
|
||||||
|
stream.read(armdsp.firmware, stream.size());
|
||||||
|
}
|
||||||
|
|
||||||
if(id == ID::ROM) {
|
if(id == ID::ROM) {
|
||||||
cartridge.load(markup, stream);
|
cartridge.load(markup, stream);
|
||||||
system.power();
|
system.power();
|
||||||
|
@ -40,6 +92,10 @@ void Interface::load(unsigned id, const stream &stream, const string &markup) {
|
||||||
stream.read(cartridge.ram.data(), min(cartridge.ram.size(), stream.size()));
|
stream.read(cartridge.ram.data(), min(cartridge.ram.size(), stream.size()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(id == ID::NecDSPRAM) {
|
||||||
|
for(unsigned n = 0; n < 2048; n++) necdsp.dataRAM[n] = stream.readl(2);
|
||||||
|
}
|
||||||
|
|
||||||
if(id == ID::RTC) {
|
if(id == ID::RTC) {
|
||||||
stream.read(srtc.rtc, min(stream.size(), sizeof srtc.rtc));
|
stream.read(srtc.rtc, min(stream.size(), sizeof srtc.rtc));
|
||||||
}
|
}
|
||||||
|
@ -55,6 +111,14 @@ void Interface::load(unsigned id, const stream &stream, const string &markup) {
|
||||||
if(id == ID::BsxPSRAM) {
|
if(id == ID::BsxPSRAM) {
|
||||||
stream.read(bsxcartridge.psram.data(), min(stream.size(), bsxcartridge.psram.size()));
|
stream.read(bsxcartridge.psram.data(), min(stream.size(), bsxcartridge.psram.size()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(id == ID::SufamiTurboSlotARAM) {
|
||||||
|
sufamiturbo.slotA.ram.copy(stream);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(id == ID::SufamiTurboSlotBRAM) {
|
||||||
|
sufamiturbo.slotB.ram.copy(stream);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Interface::save(unsigned id, const stream &stream) {
|
void Interface::save(unsigned id, const stream &stream) {
|
||||||
|
@ -62,6 +126,10 @@ void Interface::save(unsigned id, const stream &stream) {
|
||||||
stream.write(cartridge.ram.data(), cartridge.ram.size());
|
stream.write(cartridge.ram.data(), cartridge.ram.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(id == ID::NecDSPRAM) {
|
||||||
|
for(unsigned n = 0; n < 2048; n++) stream.writel(necdsp.dataRAM[n], 2);
|
||||||
|
}
|
||||||
|
|
||||||
if(id == ID::RTC) {
|
if(id == ID::RTC) {
|
||||||
stream.write(srtc.rtc, sizeof srtc.rtc);
|
stream.write(srtc.rtc, sizeof srtc.rtc);
|
||||||
}
|
}
|
||||||
|
@ -77,6 +145,14 @@ void Interface::save(unsigned id, const stream &stream) {
|
||||||
if(id == ID::BsxPSRAM) {
|
if(id == ID::BsxPSRAM) {
|
||||||
stream.write(bsxcartridge.psram.data(), bsxcartridge.psram.size());
|
stream.write(bsxcartridge.psram.data(), bsxcartridge.psram.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(id == ID::SufamiTurboSlotARAM) {
|
||||||
|
stream.write(sufamiturbo.slotA.ram.data(), sufamiturbo.slotA.ram.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
if(id == ID::SufamiTurboSlotBRAM) {
|
||||||
|
stream.write(sufamiturbo.slotB.ram.data(), sufamiturbo.slotB.ram.size());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Interface::unload() {
|
void Interface::unload() {
|
||||||
|
@ -108,6 +184,35 @@ bool Interface::unserialize(serializer &s) {
|
||||||
return system.unserialize(s);
|
return system.unserialize(s);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Interface::cheatSet(const lstring &list) {
|
||||||
|
//Super Game Boy
|
||||||
|
if(cartridge.has_gb_slot()) {
|
||||||
|
GameBoy::cheat.reset();
|
||||||
|
for(auto &code : list) {
|
||||||
|
lstring codelist = code.split("+");
|
||||||
|
for(auto &part : codelist) {
|
||||||
|
unsigned addr, data, comp;
|
||||||
|
part.trim();
|
||||||
|
if(GameBoy::Cheat::decode(part, addr, data, comp)) GameBoy::cheat.append({addr, data, comp});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
GameBoy::cheat.synchronize();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Super Famicom, Broadcast Satellaview, Sufami Turbo
|
||||||
|
cheat.reset();
|
||||||
|
for(auto &code : list) {
|
||||||
|
lstring codelist = code.split("+");
|
||||||
|
for(auto &part : codelist) {
|
||||||
|
unsigned addr, data;
|
||||||
|
part.trim();
|
||||||
|
if(Cheat::decode(part, addr, data)) cheat.append({addr, data});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cheat.synchronize();
|
||||||
|
}
|
||||||
|
|
||||||
void Interface::updatePalette() {
|
void Interface::updatePalette() {
|
||||||
video.generate_palette();
|
video.generate_palette();
|
||||||
}
|
}
|
||||||
|
@ -123,154 +228,119 @@ Interface::Interface() {
|
||||||
information.frequency = 32040;
|
information.frequency = 32040;
|
||||||
information.resettable = true;
|
information.resettable = true;
|
||||||
|
|
||||||
information.media.append({"Super Famicom", "sfc"});
|
|
||||||
information.media.append({"BS-X Satellaview", "bs" });
|
|
||||||
information.media.append({"Sufami Turbo", "st" });
|
|
||||||
information.media.append({"Super Game Boy", "gb" });
|
|
||||||
|
|
||||||
firmware.append({ID::IPLROM, "Super Famicom", "sys", "spc700.rom"});
|
firmware.append({ID::IPLROM, "Super Famicom", "sys", "spc700.rom"});
|
||||||
|
|
||||||
|
media.append({ID::ROM, "Super Famicom", "sys", "program.rom", "sfc"});
|
||||||
|
media.append({ID::ROM, "Super Game Boy", "sfc", "program.rom", "gb" });
|
||||||
|
media.append({ID::ROM, "BS-X Satellaview", "sfc", "program.rom", "bs" });
|
||||||
|
media.append({ID::ROM, "Sufami Turbo", "sfc", "program.rom", "st" });
|
||||||
|
|
||||||
{
|
{
|
||||||
Schema schema(Media{ID::ROM, "Super Famicom", "sfc", "program.rom"});
|
Device device{0, ID::Port1 | ID::Port2, "None"};
|
||||||
this->schema.append(schema);
|
this->device.append(device);
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
Schema schema(Media{ID::ROM, "Super Game Boy", "sfc", "program.rom"});
|
Device device{1, ID::Port1 | ID::Port2, "Controller"};
|
||||||
schema.slot.append({ID::SuperGameBoyROM, "Game Boy", "gb", "program.rom"});
|
device.input.append({ 0, 0, "B" });
|
||||||
this->schema.append(schema);
|
device.input.append({ 1, 0, "Y" });
|
||||||
|
device.input.append({ 2, 0, "Select"});
|
||||||
|
device.input.append({ 3, 0, "Start" });
|
||||||
|
device.input.append({ 4, 0, "Up" });
|
||||||
|
device.input.append({ 5, 0, "Down" });
|
||||||
|
device.input.append({ 6, 0, "Left" });
|
||||||
|
device.input.append({ 7, 0, "Right" });
|
||||||
|
device.input.append({ 8, 0, "A" });
|
||||||
|
device.input.append({ 9, 0, "X" });
|
||||||
|
device.input.append({10, 0, "L" });
|
||||||
|
device.input.append({11, 0, "R" });
|
||||||
|
device.order = {4, 5, 6, 7, 0, 8, 1, 9, 10, 11, 2, 3};
|
||||||
|
this->device.append(device);
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
Schema schema(Media{ID::ROM, "BS-X Satellaview", "sfc", "program.rom"});
|
Device device{2, ID::Port1 | ID::Port2, "Multitap"};
|
||||||
schema.slot.append({ID::BsxFlashROM, "BS-X Satellaview", "bs", "program.rom"});
|
for(unsigned p = 1, n = 0; p <= 4; p++, n += 12) {
|
||||||
this->schema.append(schema);
|
device.input.append({n + 0, 0, {"Port ", p, " - ", "B" }});
|
||||||
|
device.input.append({n + 1, 0, {"Port ", p, " - ", "Y" }});
|
||||||
|
device.input.append({n + 2, 0, {"Port ", p, " - ", "Select"}});
|
||||||
|
device.input.append({n + 3, 0, {"Port ", p, " - ", "Start" }});
|
||||||
|
device.input.append({n + 4, 0, {"Port ", p, " - ", "Up" }});
|
||||||
|
device.input.append({n + 5, 0, {"Port ", p, " - ", "Down" }});
|
||||||
|
device.input.append({n + 6, 0, {"Port ", p, " - ", "Left" }});
|
||||||
|
device.input.append({n + 7, 0, {"Port ", p, " - ", "Right" }});
|
||||||
|
device.input.append({n + 8, 0, {"Port ", p, " - ", "A" }});
|
||||||
|
device.input.append({n + 9, 0, {"Port ", p, " - ", "X" }});
|
||||||
|
device.input.append({n + 10, 0, {"Port ", p, " - ", "L" }});
|
||||||
|
device.input.append({n + 11, 0, {"Port ", p, " - ", "R" }});
|
||||||
|
device.order.append(n + 4, n + 5, n + 6, n + 7, n + 0, n + 8);
|
||||||
|
device.order.append(n + 1, n + 9, n + 10, n + 11, n + 2, n + 3);
|
||||||
|
}
|
||||||
|
this->device.append(device);
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
Schema schema(Media{ID::ROM, "Sufami Turbo", "sfc", "program.rom"});
|
Device device{3, ID::Port1 | ID::Port2, "Mouse"};
|
||||||
schema.slot.append({ID::SufamiTurboSlotAROM, "Sufami Turbo - Slot A", "st", "program.rom"});
|
device.input.append({0, 1, "X-axis"});
|
||||||
schema.slot.append({ID::SufamiTurboSlotBROM, "Sufami Turbo - Slot B", "st", "program.rom"});
|
device.input.append({1, 1, "Y-axis"});
|
||||||
this->schema.append(schema);
|
device.input.append({2, 0, "Left" });
|
||||||
|
device.input.append({3, 0, "Right" });
|
||||||
|
device.order = {0, 1, 2, 3};
|
||||||
|
this->device.append(device);
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
Port port{0, "Port 1"};
|
Device device{4, ID::Port2, "Super Scope"};
|
||||||
port.device.append(none());
|
device.input.append({0, 1, "X-axis" });
|
||||||
port.device.append(controller());
|
device.input.append({1, 1, "Y-axis" });
|
||||||
port.device.append(multitap());
|
device.input.append({2, 0, "Trigger"});
|
||||||
port.device.append(mouse());
|
device.input.append({3, 0, "Cursor" });
|
||||||
port.device.append(usart());
|
device.input.append({4, 0, "Turbo" });
|
||||||
this->port.append(port);
|
device.input.append({5, 0, "Pause" });
|
||||||
|
device.order = {0, 1, 2, 3, 4, 5};
|
||||||
|
this->device.append(device);
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
Port port{1, "Port 2"};
|
Device device{5, ID::Port2, "Justifier"};
|
||||||
port.device.append(none());
|
device.input.append({0, 1, "X-axis" });
|
||||||
port.device.append(controller());
|
device.input.append({1, 1, "Y-axis" });
|
||||||
port.device.append(multitap());
|
device.input.append({2, 0, "Trigger"});
|
||||||
port.device.append(mouse());
|
device.input.append({3, 0, "Start" });
|
||||||
port.device.append(superScope());
|
device.order = {0, 1, 2, 3};
|
||||||
port.device.append(justifier());
|
this->device.append(device);
|
||||||
port.device.append(justifiers());
|
}
|
||||||
this->port.append(port);
|
|
||||||
|
{
|
||||||
|
Device device{6, 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"});
|
||||||
|
device.input.append({3, 0, "Port 1 - Start" });
|
||||||
|
device.order.append(0, 1, 2, 3);
|
||||||
|
device.input.append({4, 1, "Port 2 - X-axis" });
|
||||||
|
device.input.append({5, 1, "Port 2 - Y-axis" });
|
||||||
|
device.input.append({6, 0, "Port 2 - Trigger"});
|
||||||
|
device.input.append({7, 0, "Port 2 - Start" });
|
||||||
|
device.order.append(4, 5, 6, 7);
|
||||||
|
this->device.append(device);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
Device device{7, ID::Port1, "Serial USART"};
|
||||||
|
this->device.append(device);
|
||||||
|
}
|
||||||
|
|
||||||
|
port.append({ID::Port1, "Port 1"});
|
||||||
|
port.append({ID::Port2, "Port 2"});
|
||||||
|
|
||||||
|
for(auto &device : this->device) {
|
||||||
|
for(auto &port : this->port) {
|
||||||
|
if(device.portmask & port.id) {
|
||||||
|
port.device.append(device);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Emulator::Interface::Port::Device Interface::none() {
|
|
||||||
Port::Device device{0, "None"};
|
|
||||||
return device;
|
|
||||||
}
|
|
||||||
|
|
||||||
Emulator::Interface::Port::Device Interface::controller() {
|
|
||||||
Port::Device device{1, "Controller"};
|
|
||||||
device.input.append({ 0, 0, "B" });
|
|
||||||
device.input.append({ 1, 0, "Y" });
|
|
||||||
device.input.append({ 2, 0, "Select"});
|
|
||||||
device.input.append({ 3, 0, "Start" });
|
|
||||||
device.input.append({ 4, 0, "Up" });
|
|
||||||
device.input.append({ 5, 0, "Down" });
|
|
||||||
device.input.append({ 6, 0, "Left" });
|
|
||||||
device.input.append({ 7, 0, "Right" });
|
|
||||||
device.input.append({ 8, 0, "A" });
|
|
||||||
device.input.append({ 9, 0, "X" });
|
|
||||||
device.input.append({10, 0, "L" });
|
|
||||||
device.input.append({11, 0, "R" });
|
|
||||||
device.order = {4, 5, 6, 7, 0, 8, 1, 9, 10, 11, 2, 3};
|
|
||||||
return device;
|
|
||||||
}
|
|
||||||
|
|
||||||
Emulator::Interface::Port::Device Interface::multitap() {
|
|
||||||
Port::Device device{2, "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" }});
|
|
||||||
device.input.append({n + 2, 0, {"Port ", p, " - ", "Select"}});
|
|
||||||
device.input.append({n + 3, 0, {"Port ", p, " - ", "Start" }});
|
|
||||||
device.input.append({n + 4, 0, {"Port ", p, " - ", "Up" }});
|
|
||||||
device.input.append({n + 5, 0, {"Port ", p, " - ", "Down" }});
|
|
||||||
device.input.append({n + 6, 0, {"Port ", p, " - ", "Left" }});
|
|
||||||
device.input.append({n + 7, 0, {"Port ", p, " - ", "Right" }});
|
|
||||||
device.input.append({n + 8, 0, {"Port ", p, " - ", "A" }});
|
|
||||||
device.input.append({n + 9, 0, {"Port ", p, " - ", "X" }});
|
|
||||||
device.input.append({n + 10, 0, {"Port ", p, " - ", "L" }});
|
|
||||||
device.input.append({n + 11, 0, {"Port ", p, " - ", "R" }});
|
|
||||||
device.order.append(n + 4, n + 5, n + 6, n + 7, n + 0, n + 8);
|
|
||||||
device.order.append(n + 1, n + 9, n + 10, n + 11, n + 2, n + 3);
|
|
||||||
}
|
|
||||||
return device;
|
|
||||||
}
|
|
||||||
|
|
||||||
Emulator::Interface::Port::Device Interface::mouse() {
|
|
||||||
Port::Device device{3, "Mouse"};
|
|
||||||
device.input.append({0, 1, "X-axis"});
|
|
||||||
device.input.append({1, 1, "Y-axis"});
|
|
||||||
device.input.append({2, 0, "Left" });
|
|
||||||
device.input.append({3, 0, "Right" });
|
|
||||||
device.order = {0, 1, 2, 3};
|
|
||||||
return device;
|
|
||||||
}
|
|
||||||
|
|
||||||
Emulator::Interface::Port::Device Interface::superScope() {
|
|
||||||
Port::Device device{4, "Super Scope"};
|
|
||||||
device.input.append({0, 1, "X-axis" });
|
|
||||||
device.input.append({1, 1, "Y-axis" });
|
|
||||||
device.input.append({2, 0, "Trigger"});
|
|
||||||
device.input.append({3, 0, "Cursor" });
|
|
||||||
device.input.append({4, 0, "Turbo" });
|
|
||||||
device.input.append({5, 0, "Pause" });
|
|
||||||
device.order = {0, 1, 2, 3, 4, 5};
|
|
||||||
return device;
|
|
||||||
}
|
|
||||||
|
|
||||||
Emulator::Interface::Port::Device Interface::justifier() {
|
|
||||||
Port::Device device{5, "Justifier"};
|
|
||||||
device.input.append({0, 1, "X-axis" });
|
|
||||||
device.input.append({1, 1, "Y-axis" });
|
|
||||||
device.input.append({2, 0, "Trigger"});
|
|
||||||
device.input.append({3, 0, "Start" });
|
|
||||||
device.order = {0, 1, 2, 3};
|
|
||||||
return device;
|
|
||||||
}
|
|
||||||
|
|
||||||
Emulator::Interface::Port::Device Interface::justifiers() {
|
|
||||||
Port::Device device{6, "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"});
|
|
||||||
device.input.append({3, 0, "Port 1 - Start" });
|
|
||||||
device.order.append(0, 1, 2, 3);
|
|
||||||
device.input.append({4, 1, "Port 2 - X-axis" });
|
|
||||||
device.input.append({5, 1, "Port 2 - Y-axis" });
|
|
||||||
device.input.append({6, 0, "Port 2 - Trigger"});
|
|
||||||
device.input.append({7, 0, "Port 2 - Start" });
|
|
||||||
device.order.append(4, 5, 6, 7);
|
|
||||||
return device;
|
|
||||||
}
|
|
||||||
|
|
||||||
Emulator::Interface::Port::Device Interface::usart() {
|
|
||||||
Port::Device device{7, "Serial USART"};
|
|
||||||
return device;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,16 +5,30 @@ namespace SuperFamicom {
|
||||||
struct ID {
|
struct ID {
|
||||||
enum : unsigned {
|
enum : unsigned {
|
||||||
IPLROM,
|
IPLROM,
|
||||||
|
Nec7725DSP,
|
||||||
|
Nec96050DSP,
|
||||||
|
HitachiDSP,
|
||||||
|
ArmDSP,
|
||||||
ROM,
|
ROM,
|
||||||
SuperGameBoyROM,
|
SuperGameBoyROM,
|
||||||
BsxFlashROM,
|
BsxFlashROM,
|
||||||
SufamiTurboSlotAROM,
|
SufamiTurboSlotAROM,
|
||||||
SufamiTurboSlotBROM,
|
SufamiTurboSlotBROM,
|
||||||
RAM,
|
RAM,
|
||||||
|
NecDSPRAM,
|
||||||
RTC,
|
RTC,
|
||||||
SPC7110RTC,
|
SPC7110RTC,
|
||||||
BsxRAM,
|
BsxRAM,
|
||||||
BsxPSRAM,
|
BsxPSRAM,
|
||||||
|
SuperGameBoyRAM,
|
||||||
|
SuperGameBoyRTC,
|
||||||
|
SufamiTurboSlotARAM,
|
||||||
|
SufamiTurboSlotBRAM,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum : unsigned {
|
||||||
|
Port1 = 1,
|
||||||
|
Port2 = 2,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -23,6 +37,8 @@ struct Interface : Emulator::Interface {
|
||||||
virtual void message(const string &text) {}
|
virtual void message(const string &text) {}
|
||||||
|
|
||||||
bool loaded();
|
bool loaded();
|
||||||
|
string sha256();
|
||||||
|
unsigned group(unsigned id);
|
||||||
void load(unsigned id, const stream &stream, const string &markup = "");
|
void load(unsigned id, const stream &stream, const string &markup = "");
|
||||||
void save(unsigned id, const stream &stream);
|
void save(unsigned id, const stream &stream);
|
||||||
void unload();
|
void unload();
|
||||||
|
@ -35,19 +51,11 @@ struct Interface : Emulator::Interface {
|
||||||
serializer serialize();
|
serializer serialize();
|
||||||
bool unserialize(serializer&);
|
bool unserialize(serializer&);
|
||||||
|
|
||||||
|
void cheatSet(const lstring&);
|
||||||
|
|
||||||
void updatePalette();
|
void updatePalette();
|
||||||
|
|
||||||
Interface();
|
Interface();
|
||||||
|
|
||||||
private:
|
|
||||||
Port::Device none();
|
|
||||||
Port::Device controller();
|
|
||||||
Port::Device multitap();
|
|
||||||
Port::Device mouse();
|
|
||||||
Port::Device superScope();
|
|
||||||
Port::Device justifier();
|
|
||||||
Port::Device justifiers();
|
|
||||||
Port::Device usart();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
extern Interface *interface;
|
extern Interface *interface;
|
||||||
|
|
|
@ -60,7 +60,7 @@ void System::serialize_all(serializer &s) {
|
||||||
#if defined(GAMEBOY)
|
#if defined(GAMEBOY)
|
||||||
if(cartridge.has_gb_slot()) icd2.serialize(s);
|
if(cartridge.has_gb_slot()) icd2.serialize(s);
|
||||||
#endif
|
#endif
|
||||||
if(cartridge.has_st_slot()) sufamiturbo.serialize(s);
|
if(cartridge.has_st_slots()) sufamiturbo.serialize(s);
|
||||||
if(cartridge.has_superfx()) superfx.serialize(s);
|
if(cartridge.has_superfx()) superfx.serialize(s);
|
||||||
if(cartridge.has_sa1()) sa1.serialize(s);
|
if(cartridge.has_sa1()) sa1.serialize(s);
|
||||||
if(cartridge.has_necdsp()) necdsp.serialize(s);
|
if(cartridge.has_necdsp()) necdsp.serialize(s);
|
||||||
|
|
|
@ -108,7 +108,7 @@ void System::load() {
|
||||||
#endif
|
#endif
|
||||||
if(cartridge.has_bs_cart()) bsxcartridge.load();
|
if(cartridge.has_bs_cart()) bsxcartridge.load();
|
||||||
if(cartridge.has_bs_slot()) bsxflash.load();
|
if(cartridge.has_bs_slot()) bsxflash.load();
|
||||||
if(cartridge.has_st_slot()) sufamiturbo.load();
|
if(cartridge.has_st_slots()) sufamiturbo.load();
|
||||||
if(cartridge.has_nss_dip()) nss.load();
|
if(cartridge.has_nss_dip()) nss.load();
|
||||||
if(cartridge.has_superfx()) superfx.load();
|
if(cartridge.has_superfx()) superfx.load();
|
||||||
if(cartridge.has_sa1()) sa1.load();
|
if(cartridge.has_sa1()) sa1.load();
|
||||||
|
@ -133,7 +133,7 @@ void System::unload() {
|
||||||
#endif
|
#endif
|
||||||
if(cartridge.has_bs_cart()) bsxcartridge.unload();
|
if(cartridge.has_bs_cart()) bsxcartridge.unload();
|
||||||
if(cartridge.has_bs_slot()) bsxflash.unload();
|
if(cartridge.has_bs_slot()) bsxflash.unload();
|
||||||
if(cartridge.has_st_slot()) sufamiturbo.unload();
|
if(cartridge.has_st_slots()) sufamiturbo.unload();
|
||||||
if(cartridge.has_nss_dip()) nss.unload();
|
if(cartridge.has_nss_dip()) nss.unload();
|
||||||
if(cartridge.has_superfx()) superfx.unload();
|
if(cartridge.has_superfx()) superfx.unload();
|
||||||
if(cartridge.has_sa1()) sa1.unload();
|
if(cartridge.has_sa1()) sa1.unload();
|
||||||
|
|
|
@ -7,7 +7,8 @@ include gb/Makefile
|
||||||
include gba/Makefile
|
include gba/Makefile
|
||||||
name := ethos
|
name := ethos
|
||||||
|
|
||||||
ui_objects := ui-ethos ui-configuration ui-interface ui-utility ui-input ui-window ui-general ui-settings
|
ui_objects := ui-ethos ui-configuration ui-interface ui-utility
|
||||||
|
ui_objects += ui-input ui-window ui-general ui-settings ui-tools
|
||||||
ui_objects += phoenix ruby
|
ui_objects += phoenix ruby
|
||||||
ui_objects += $(if $(call streq,$(platform),win),resource)
|
ui_objects += $(if $(call streq,$(platform),win),resource)
|
||||||
|
|
||||||
|
@ -46,6 +47,7 @@ obj/ui-input.o: $(ui)/input/input.cpp $(call rwildcard,$(ui)/)
|
||||||
obj/ui-window.o: $(ui)/window/window.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-general.o: $(ui)/general/general.cpp $(call rwildcard,$(ui)/)
|
||||||
obj/ui-settings.o: $(ui)/settings/settings.cpp $(call rwildcard,$(ui)/)
|
obj/ui-settings.o: $(ui)/settings/settings.cpp $(call rwildcard,$(ui)/)
|
||||||
|
obj/ui-tools.o: $(ui)/tools/tools.cpp $(call rwildcard,$(ui)/)
|
||||||
|
|
||||||
obj/ruby.o: ruby/ruby.cpp $(call rwildcard,ruby/*)
|
obj/ruby.o: ruby/ruby.cpp $(call rwildcard,ruby/*)
|
||||||
$(call compile,$(rubyflags))
|
$(call compile,$(rubyflags))
|
||||||
|
|
|
@ -19,7 +19,7 @@ void Application::bootstrap() {
|
||||||
system->callback.mediaRequest = {&Interface::mediaRequest, interface};
|
system->callback.mediaRequest = {&Interface::mediaRequest, interface};
|
||||||
|
|
||||||
for(auto &firmware : system->firmware) {
|
for(auto &firmware : system->firmware) {
|
||||||
filestream fs{application->path({firmware.name, ".", firmware.extension, "/", firmware.path})};
|
filestream fs{application->path({firmware.name, ".", firmware.type, "/", firmware.path})};
|
||||||
system->load(firmware.id, fs);
|
system->load(firmware.id, fs);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -79,6 +79,9 @@ Application::Application(int argc, char **argv) {
|
||||||
inputSettings = new InputSettings;
|
inputSettings = new InputSettings;
|
||||||
hotkeySettings = new HotkeySettings;
|
hotkeySettings = new HotkeySettings;
|
||||||
settings = new Settings;
|
settings = new Settings;
|
||||||
|
cheatDatabase = new CheatDatabase;
|
||||||
|
cheatEditor = new CheatEditor;
|
||||||
|
stateManager = new StateManager;
|
||||||
windowManager->loadGeometry();
|
windowManager->loadGeometry();
|
||||||
presentation->setVisible();
|
presentation->setVisible();
|
||||||
|
|
||||||
|
@ -105,7 +108,7 @@ Application::Application(int argc, char **argv) {
|
||||||
run();
|
run();
|
||||||
}
|
}
|
||||||
|
|
||||||
if(active && system().loaded()) utility->unload();
|
utility->unload();
|
||||||
config->save();
|
config->save();
|
||||||
browser->saveConfiguration();
|
browser->saveConfiguration();
|
||||||
inputManager->saveConfiguration();
|
inputManager->saveConfiguration();
|
||||||
|
|
|
@ -24,6 +24,7 @@ using namespace ruby;
|
||||||
#include "window/window.hpp"
|
#include "window/window.hpp"
|
||||||
#include "general/general.hpp"
|
#include "general/general.hpp"
|
||||||
#include "settings/settings.hpp"
|
#include "settings/settings.hpp"
|
||||||
|
#include "tools/tools.hpp"
|
||||||
|
|
||||||
Emulator::Interface& system();
|
Emulator::Interface& system();
|
||||||
|
|
||||||
|
|
|
@ -64,7 +64,7 @@ void Browser::saveConfiguration() {
|
||||||
|
|
||||||
void Browser::bootstrap() {
|
void Browser::bootstrap() {
|
||||||
for(auto &emulator : application->emulator) {
|
for(auto &emulator : application->emulator) {
|
||||||
for(auto &media : emulator->information.media) {
|
for(auto &media : emulator->media) {
|
||||||
bool found = false;
|
bool found = false;
|
||||||
for(auto &folder : folderList) {
|
for(auto &folder : folderList) {
|
||||||
if(folder.extension == media.extension) {
|
if(folder.extension == media.extension) {
|
||||||
|
|
|
@ -56,12 +56,14 @@ Presentation::Presentation() : active(nullptr) {
|
||||||
loadStateMenu.setText("Load State");
|
loadStateMenu.setText("Load State");
|
||||||
for(unsigned n = 0; n < 5; n++) loadStateItem[n].setText({"Slot ", 1 + n});
|
for(unsigned n = 0; n < 5; n++) loadStateItem[n].setText({"Slot ", 1 + n});
|
||||||
resizeWindow.setText("Resize Window");
|
resizeWindow.setText("Resize Window");
|
||||||
|
cheatEditor.setText("Cheat Editor");
|
||||||
|
stateManager.setText("State Manager");
|
||||||
|
|
||||||
append(loadMenu);
|
append(loadMenu);
|
||||||
for(auto &item : loadListDirect) loadMenu.append(*item);
|
for(auto &item : loadListSystem) loadMenu.append(*item);
|
||||||
if(loadListSlotted.size() > 0) {
|
if(loadListSubsystem.size() > 0) {
|
||||||
loadMenu.append(*new Separator);
|
loadMenu.append(*new Separator);
|
||||||
for(auto &item : loadListSlotted) loadMenu.append(*item);
|
for(auto &item : loadListSubsystem) loadMenu.append(*item);
|
||||||
}
|
}
|
||||||
for(auto &system : emulatorList) append(system->menu);
|
for(auto &system : emulatorList) append(system->menu);
|
||||||
append(settingsMenu);
|
append(settingsMenu);
|
||||||
|
@ -77,7 +79,7 @@ Presentation::Presentation() : active(nullptr) {
|
||||||
toolsMenu.append(loadStateMenu);
|
toolsMenu.append(loadStateMenu);
|
||||||
for(unsigned n = 0; n < 5; n++) loadStateMenu.append(loadStateItem[n]);
|
for(unsigned n = 0; n < 5; n++) loadStateMenu.append(loadStateItem[n]);
|
||||||
toolsMenu.append(stateMenuSeparator);
|
toolsMenu.append(stateMenuSeparator);
|
||||||
toolsMenu.append(resizeWindow);
|
toolsMenu.append(resizeWindow, cheatEditor, stateManager);
|
||||||
|
|
||||||
append(layout);
|
append(layout);
|
||||||
layout.append(viewport, {0, 0, 720, 480});
|
layout.append(viewport, {0, 0, 720, 480});
|
||||||
|
@ -97,6 +99,8 @@ Presentation::Presentation() : active(nullptr) {
|
||||||
for(unsigned n = 0; n < 5; n++) saveStateItem[n].onActivate = [=] { utility->saveState(1 + n); };
|
for(unsigned n = 0; n < 5; n++) saveStateItem[n].onActivate = [=] { utility->saveState(1 + n); };
|
||||||
for(unsigned n = 0; n < 5; n++) loadStateItem[n].onActivate = [=] { utility->loadState(1 + n); };
|
for(unsigned n = 0; n < 5; n++) loadStateItem[n].onActivate = [=] { utility->loadState(1 + n); };
|
||||||
resizeWindow.onActivate = [&] { utility->resize(true); };
|
resizeWindow.onActivate = [&] { utility->resize(true); };
|
||||||
|
cheatEditor.onActivate = [&] { ::cheatEditor->setVisible(); };
|
||||||
|
stateManager.onActivate = [&] { ::stateManager->setVisible(); };
|
||||||
|
|
||||||
synchronize();
|
synchronize();
|
||||||
}
|
}
|
||||||
|
@ -106,14 +110,14 @@ void Presentation::bootstrap() {
|
||||||
auto iEmulator = new Emulator;
|
auto iEmulator = new Emulator;
|
||||||
iEmulator->interface = emulator;
|
iEmulator->interface = emulator;
|
||||||
|
|
||||||
for(auto &schema : emulator->schema) {
|
for(auto &media : emulator->media) {
|
||||||
Item *item = new Item;
|
Item *item = new Item;
|
||||||
item->setText({schema.name, " ..."});
|
item->setText({media.name, " ..."});
|
||||||
item->onActivate = [=, &schema] {
|
item->onActivate = [=, &media] {
|
||||||
utility->loadSchema(iEmulator->interface, schema);
|
utility->loadMedia(iEmulator->interface, media);
|
||||||
};
|
};
|
||||||
if(schema.slot.size() == 0) loadListDirect.append(item);
|
if(media.type == "sys") loadListSystem.append(item);
|
||||||
if(schema.slot.size() >= 1) loadListSlotted.append(item);
|
if(media.type != "sys") loadListSubsystem.append(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
iEmulator->menu.setText(emulator->information.name);
|
iEmulator->menu.setText(emulator->information.name);
|
||||||
|
|
|
@ -21,8 +21,8 @@ struct Presentation : Window {
|
||||||
vector<Emulator*> emulatorList;
|
vector<Emulator*> emulatorList;
|
||||||
|
|
||||||
Menu loadMenu;
|
Menu loadMenu;
|
||||||
vector<Item*> loadListDirect;
|
vector<Item*> loadListSystem;
|
||||||
vector<Item*> loadListSlotted;
|
vector<Item*> loadListSubsystem;
|
||||||
Menu settingsMenu;
|
Menu settingsMenu;
|
||||||
Menu videoMenu;
|
Menu videoMenu;
|
||||||
RadioItem centerVideo;
|
RadioItem centerVideo;
|
||||||
|
@ -41,6 +41,8 @@ struct Presentation : Window {
|
||||||
Item loadStateItem[5];
|
Item loadStateItem[5];
|
||||||
Separator stateMenuSeparator;
|
Separator stateMenuSeparator;
|
||||||
Item resizeWindow;
|
Item resizeWindow;
|
||||||
|
Item cheatEditor;
|
||||||
|
Item stateManager;
|
||||||
|
|
||||||
void synchronize();
|
void synchronize();
|
||||||
void setSystemName(const string &name);
|
void setSystemName(const string &name);
|
||||||
|
|
|
@ -97,11 +97,5 @@ int16_t Interface::inputPoll(unsigned port, unsigned device, unsigned input) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void Interface::mediaRequest(Emulator::Interface::Media media) {
|
void Interface::mediaRequest(Emulator::Interface::Media media) {
|
||||||
string pathname = browser->select({"Load ", media.name}, media.extension);
|
utility->loadMedia(media);
|
||||||
if(pathname.empty()) return;
|
|
||||||
|
|
||||||
string markup;
|
|
||||||
markup.readfile({pathname, "manifest.xml"});
|
|
||||||
mmapstream stream({pathname, media.path});
|
|
||||||
system().load(media.id, stream, markup);
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,23 +6,23 @@ HotkeySettings::HotkeySettings() : activeInput(nullptr) {
|
||||||
|
|
||||||
inputList.setHeaderText("Name", "Mapping");
|
inputList.setHeaderText("Name", "Mapping");
|
||||||
inputList.setHeaderVisible();
|
inputList.setHeaderVisible();
|
||||||
clearButton.setText("Clear");
|
eraseButton.setText("Erase");
|
||||||
|
|
||||||
append(title, {~0, 0}, 5);
|
append(title, {~0, 0}, 5);
|
||||||
append(inputList, {~0, ~0}, 5);
|
append(inputList, {~0, ~0}, 5);
|
||||||
append(controlLayout, {~0, 0});
|
append(controlLayout, {~0, 0});
|
||||||
controlLayout.append(spacer, {~0, 0});
|
controlLayout.append(spacer, {~0, 0});
|
||||||
controlLayout.append(clearButton, {80, 0});
|
controlLayout.append(eraseButton, {80, 0});
|
||||||
|
|
||||||
inputList.onChange = {&HotkeySettings::synchronize, this};
|
inputList.onChange = {&HotkeySettings::synchronize, this};
|
||||||
inputList.onActivate = {&HotkeySettings::assignInput, this};
|
inputList.onActivate = {&HotkeySettings::assignInput, this};
|
||||||
clearButton.onActivate = {&HotkeySettings::clearInput, this};
|
eraseButton.onActivate = {&HotkeySettings::eraseInput, this};
|
||||||
|
|
||||||
refresh();
|
refresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
void HotkeySettings::synchronize() {
|
void HotkeySettings::synchronize() {
|
||||||
clearButton.setEnabled(inputList.selected());
|
eraseButton.setEnabled(inputList.selected());
|
||||||
}
|
}
|
||||||
|
|
||||||
void HotkeySettings::refresh() {
|
void HotkeySettings::refresh() {
|
||||||
|
@ -37,7 +37,7 @@ void HotkeySettings::refresh() {
|
||||||
synchronize();
|
synchronize();
|
||||||
}
|
}
|
||||||
|
|
||||||
void HotkeySettings::clearInput() {
|
void HotkeySettings::eraseInput() {
|
||||||
activeInput = inputManager->hotkeyMap[inputList.selection()];
|
activeInput = inputManager->hotkeyMap[inputList.selection()];
|
||||||
inputEvent(Scancode::None, 1);
|
inputEvent(Scancode::None, 1);
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,11 +3,11 @@ struct HotkeySettings : SettingsLayout {
|
||||||
ListView inputList;
|
ListView inputList;
|
||||||
HorizontalLayout controlLayout;
|
HorizontalLayout controlLayout;
|
||||||
Widget spacer;
|
Widget spacer;
|
||||||
Button clearButton;
|
Button eraseButton;
|
||||||
|
|
||||||
void synchronize();
|
void synchronize();
|
||||||
void refresh();
|
void refresh();
|
||||||
void clearInput();
|
void eraseInput();
|
||||||
void assignInput();
|
void assignInput();
|
||||||
void inputEvent(unsigned scancode, int16_t value);
|
void inputEvent(unsigned scancode, int16_t value);
|
||||||
HotkeySettings();
|
HotkeySettings();
|
||||||
|
|
|
@ -8,7 +8,8 @@ InputSettings::InputSettings() : activeInput(nullptr) {
|
||||||
focusAllow.setText("Allow Input");
|
focusAllow.setText("Allow Input");
|
||||||
inputList.setHeaderText("Name", "Mapping");
|
inputList.setHeaderText("Name", "Mapping");
|
||||||
inputList.setHeaderVisible();
|
inputList.setHeaderVisible();
|
||||||
clearButton.setText("Clear");
|
resetButton.setText("Reset");
|
||||||
|
eraseButton.setText("Erase");
|
||||||
|
|
||||||
append(title, {~0, 0}, 5);
|
append(title, {~0, 0}, 5);
|
||||||
append(focusLayout, {~0, 0}, 5);
|
append(focusLayout, {~0, 0}, 5);
|
||||||
|
@ -25,7 +26,8 @@ InputSettings::InputSettings() : activeInput(nullptr) {
|
||||||
controlLayout.append(assign[1], {100, 0}, 5);
|
controlLayout.append(assign[1], {100, 0}, 5);
|
||||||
controlLayout.append(assign[2], {100, 0}, 5);
|
controlLayout.append(assign[2], {100, 0}, 5);
|
||||||
controlLayout.append(spacer, {~0, 0});
|
controlLayout.append(spacer, {~0, 0});
|
||||||
controlLayout.append(clearButton, {80, 0});
|
controlLayout.append(resetButton, {80, 0}, 5);
|
||||||
|
controlLayout.append(eraseButton, {80, 0});
|
||||||
|
|
||||||
for(auto &emulator : application->emulator) {
|
for(auto &emulator : application->emulator) {
|
||||||
systemList.append(emulator->information.name);
|
systemList.append(emulator->information.name);
|
||||||
|
@ -45,7 +47,8 @@ InputSettings::InputSettings() : activeInput(nullptr) {
|
||||||
assign[0].onActivate = [&] { assignMouseInput(0); };
|
assign[0].onActivate = [&] { assignMouseInput(0); };
|
||||||
assign[1].onActivate = [&] { assignMouseInput(1); };
|
assign[1].onActivate = [&] { assignMouseInput(1); };
|
||||||
assign[2].onActivate = [&] { assignMouseInput(2); };
|
assign[2].onActivate = [&] { assignMouseInput(2); };
|
||||||
clearButton.onActivate = {&InputSettings::clearInput, this};
|
resetButton.onActivate = {&InputSettings::resetInput, this};
|
||||||
|
eraseButton.onActivate = {&InputSettings::eraseInput, this};
|
||||||
|
|
||||||
systemChanged();
|
systemChanged();
|
||||||
}
|
}
|
||||||
|
@ -78,7 +81,7 @@ void InputSettings::synchronize() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
clearButton.setEnabled(inputList.selected());
|
eraseButton.setEnabled(inputList.selected());
|
||||||
}
|
}
|
||||||
|
|
||||||
Emulator::Interface& InputSettings::activeSystem() {
|
Emulator::Interface& InputSettings::activeSystem() {
|
||||||
|
@ -89,7 +92,7 @@ Emulator::Interface::Port& InputSettings::activePort() {
|
||||||
return activeSystem().port[portList.selection()];
|
return activeSystem().port[portList.selection()];
|
||||||
}
|
}
|
||||||
|
|
||||||
Emulator::Interface::Port::Device& InputSettings::activeDevice() {
|
Emulator::Interface::Device& InputSettings::activeDevice() {
|
||||||
return activePort().device[deviceList.selection()];
|
return activePort().device[deviceList.selection()];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -123,7 +126,19 @@ void InputSettings::deviceChanged() {
|
||||||
synchronize();
|
synchronize();
|
||||||
}
|
}
|
||||||
|
|
||||||
void InputSettings::clearInput() {
|
void InputSettings::resetInput() {
|
||||||
|
if(MessageWindow::question(*settings, "All inputs will be erased. Are you sure you want to do this?")
|
||||||
|
== MessageWindow::Response::No) return;
|
||||||
|
|
||||||
|
auto &device = activeDevice();
|
||||||
|
unsigned length = device.input.size();
|
||||||
|
for(unsigned n = 0; n < length; n++) {
|
||||||
|
activeInput = inputManager->inputMap[device.input[n].guid];
|
||||||
|
inputEvent(Scancode::None, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void InputSettings::eraseInput() {
|
||||||
unsigned number = activeDevice().order[inputList.selection()];
|
unsigned number = activeDevice().order[inputList.selection()];
|
||||||
auto &input = activeDevice().input[number];
|
auto &input = activeDevice().input[number];
|
||||||
activeInput = inputManager->inputMap[input.guid];
|
activeInput = inputManager->inputMap[input.guid];
|
||||||
|
|
|
@ -12,18 +12,20 @@ struct InputSettings : SettingsLayout {
|
||||||
HorizontalLayout controlLayout;
|
HorizontalLayout controlLayout;
|
||||||
Button assign[3];
|
Button assign[3];
|
||||||
Widget spacer;
|
Widget spacer;
|
||||||
Button clearButton;
|
Button resetButton;
|
||||||
|
Button eraseButton;
|
||||||
|
|
||||||
void synchronize();
|
void synchronize();
|
||||||
|
|
||||||
Emulator::Interface& activeSystem();
|
Emulator::Interface& activeSystem();
|
||||||
Emulator::Interface::Port& activePort();
|
Emulator::Interface::Port& activePort();
|
||||||
Emulator::Interface::Port::Device& activeDevice();
|
Emulator::Interface::Device& activeDevice();
|
||||||
|
|
||||||
void systemChanged();
|
void systemChanged();
|
||||||
void portChanged();
|
void portChanged();
|
||||||
void deviceChanged();
|
void deviceChanged();
|
||||||
void clearInput();
|
void resetInput();
|
||||||
|
void eraseInput();
|
||||||
void assignInput();
|
void assignInput();
|
||||||
void assignMouseInput(unsigned n);
|
void assignMouseInput(unsigned n);
|
||||||
void inputEvent(unsigned scancode, int16_t value, bool allowMouseInput = false);
|
void inputEvent(unsigned scancode, int16_t value, bool allowMouseInput = false);
|
||||||
|
|
|
@ -0,0 +1,78 @@
|
||||||
|
CheatDatabase *cheatDatabase = nullptr;
|
||||||
|
|
||||||
|
CheatDatabase::CheatDatabase() {
|
||||||
|
setGeometry({256, 256, 600, 360});
|
||||||
|
windowManager->append(this, "CheatDatabase");
|
||||||
|
|
||||||
|
layout.setMargin(5);
|
||||||
|
cheatList.setCheckable();
|
||||||
|
selectAllButton.setText("Select All");
|
||||||
|
unselectAllButton.setText("Unselect All");
|
||||||
|
acceptButton.setText("Add Codes");
|
||||||
|
|
||||||
|
append(layout);
|
||||||
|
layout.append(cheatList, {~0, ~0}, 5);
|
||||||
|
layout.append(controlLayout, {~0, 0});
|
||||||
|
controlLayout.append(selectAllButton, {100, 0}, 5);
|
||||||
|
controlLayout.append(unselectAllButton, {100, 0}, 5);
|
||||||
|
controlLayout.append(spacer, {~0, 0});
|
||||||
|
controlLayout.append(acceptButton, {80, 0});
|
||||||
|
|
||||||
|
selectAllButton.onActivate = [&] {
|
||||||
|
for(unsigned n = 0; n < cheat.size(); n++) cheatList.setChecked(n, true);
|
||||||
|
};
|
||||||
|
|
||||||
|
unselectAllButton.onActivate = [&] {
|
||||||
|
for(unsigned n = 0; n < cheat.size(); n++) cheatList.setChecked(n, false);
|
||||||
|
};
|
||||||
|
|
||||||
|
acceptButton.onActivate = {&CheatDatabase::addCodes, this};
|
||||||
|
}
|
||||||
|
|
||||||
|
void CheatDatabase::findCodes() {
|
||||||
|
const string sha256 = system().sha256();
|
||||||
|
cheatList.reset();
|
||||||
|
cheat.reset();
|
||||||
|
|
||||||
|
string data;
|
||||||
|
data.readfile(application->path("cheats.xml"));
|
||||||
|
XML::Document document(data);
|
||||||
|
for(auto &node : document["database"]) {
|
||||||
|
if(node.name != "cartridge") continue;
|
||||||
|
if(node["sha256"].data != sha256) continue;
|
||||||
|
|
||||||
|
setTitle(node["name"].data);
|
||||||
|
for(auto &cheat : node) {
|
||||||
|
if(cheat.name != "cheat") continue;
|
||||||
|
cheatList.append(cheat["description"].data);
|
||||||
|
|
||||||
|
string codeList;
|
||||||
|
for(auto &code : cheat) {
|
||||||
|
if(code.name != "code") continue;
|
||||||
|
codeList.append(code.data, "+");
|
||||||
|
}
|
||||||
|
codeList.rtrim<1>("+");
|
||||||
|
|
||||||
|
this->cheat.append({codeList, cheat["description"].data});
|
||||||
|
}
|
||||||
|
|
||||||
|
setVisible();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
MessageWindow::information(*cheatEditor, "Sorry, no cheat codes were found.");
|
||||||
|
}
|
||||||
|
|
||||||
|
void CheatDatabase::addCodes() {
|
||||||
|
for(unsigned n = 0; n < cheat.size(); n++) {
|
||||||
|
if(cheatList.checked(n) == false) continue;
|
||||||
|
if(cheatEditor->import(cheat[n].code, cheat[n].desc) == false) {
|
||||||
|
MessageWindow::warning(*this, "Ran out of empty slots for cheat codes.\nNot all cheat codes were added.");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setVisible(false);
|
||||||
|
cheatEditor->synchronize();
|
||||||
|
cheatEditor->refresh();
|
||||||
|
}
|
|
@ -0,0 +1,22 @@
|
||||||
|
struct CheatDatabase : Window {
|
||||||
|
VerticalLayout layout;
|
||||||
|
ListView cheatList;
|
||||||
|
HorizontalLayout controlLayout;
|
||||||
|
Button selectAllButton;
|
||||||
|
Button unselectAllButton;
|
||||||
|
Widget spacer;
|
||||||
|
Button acceptButton;
|
||||||
|
|
||||||
|
void findCodes();
|
||||||
|
void addCodes();
|
||||||
|
CheatDatabase();
|
||||||
|
|
||||||
|
private:
|
||||||
|
struct Cheat {
|
||||||
|
string code;
|
||||||
|
string desc;
|
||||||
|
};
|
||||||
|
vector<Cheat> cheat;
|
||||||
|
};
|
||||||
|
|
||||||
|
extern CheatDatabase *cheatDatabase;
|
|
@ -0,0 +1,187 @@
|
||||||
|
CheatEditor *cheatEditor = nullptr;
|
||||||
|
|
||||||
|
CheatEditor::CheatEditor() {
|
||||||
|
setGeometry({128, 128, 600, 360});
|
||||||
|
windowManager->append(this, "CheatEditor");
|
||||||
|
|
||||||
|
setTitle("Cheat Editor");
|
||||||
|
layout.setMargin(5);
|
||||||
|
cheatList.setHeaderText("Slot", "Code", "Description");
|
||||||
|
cheatList.setHeaderVisible();
|
||||||
|
cheatList.setCheckable();
|
||||||
|
for(unsigned n = 0; n < Codes; n++) cheatList.append("", "", "");
|
||||||
|
codeLabel.setText("Code(s):");
|
||||||
|
descLabel.setText("Description:");
|
||||||
|
findButton.setText("Find Codes ...");
|
||||||
|
resetButton.setText("Reset");
|
||||||
|
eraseButton.setText("Erase");
|
||||||
|
unsigned width = max(
|
||||||
|
Font(application->normalFont).geometry("Codes(s)" ).width,
|
||||||
|
Font(application->normalFont).geometry("Description:").width
|
||||||
|
);
|
||||||
|
|
||||||
|
append(layout);
|
||||||
|
layout.append(cheatList, {~0, ~0}, 5);
|
||||||
|
layout.append(codeLayout, {~0, 0}, 5);
|
||||||
|
codeLayout.append(codeLabel, {width, 0}, 5);
|
||||||
|
codeLayout.append(codeEdit, {~0, 0});
|
||||||
|
layout.append(descLayout, {~0, 0}, 5);
|
||||||
|
descLayout.append(descLabel, {width, 0}, 5);
|
||||||
|
descLayout.append(descEdit, {~0, 0});
|
||||||
|
layout.append(controlLayout, {~0, 0});
|
||||||
|
controlLayout.append(findButton, {0, 0}, 5);
|
||||||
|
controlLayout.append(spacer, {~0, 0});
|
||||||
|
controlLayout.append(resetButton, {80, 0}, 5);
|
||||||
|
controlLayout.append(eraseButton, {80, 0});
|
||||||
|
|
||||||
|
cheatList.onChange = {&CheatEditor::synchronize, this};
|
||||||
|
cheatList.onToggle = [&](unsigned) { update(); };
|
||||||
|
codeEdit.onChange = {&CheatEditor::updateCode, this};
|
||||||
|
descEdit.onChange = {&CheatEditor::updateDesc, this};
|
||||||
|
findButton.onActivate = {&CheatDatabase::findCodes, cheatDatabase};
|
||||||
|
resetButton.onActivate = [&] {
|
||||||
|
if(MessageWindow::question(*this, "All codes will be erased. Are you sure you want to do this?")
|
||||||
|
== MessageWindow::Response::Yes) reset();
|
||||||
|
};
|
||||||
|
eraseButton.onActivate = {&CheatEditor::erase, this};
|
||||||
|
|
||||||
|
cheatList.setSelection(0);
|
||||||
|
synchronize();
|
||||||
|
}
|
||||||
|
|
||||||
|
void CheatEditor::synchronize() {
|
||||||
|
layout.setEnabled(application->active);
|
||||||
|
|
||||||
|
if(cheatList.selected()) {
|
||||||
|
unsigned n = cheatList.selection();
|
||||||
|
codeEdit.setText(cheat[n].code);
|
||||||
|
descEdit.setText(cheat[n].desc);
|
||||||
|
codeEdit.setEnabled(true);
|
||||||
|
descEdit.setEnabled(true);
|
||||||
|
eraseButton.setEnabled(true);
|
||||||
|
} else {
|
||||||
|
codeEdit.setText("");
|
||||||
|
descEdit.setText("");
|
||||||
|
codeEdit.setEnabled(false);
|
||||||
|
descEdit.setEnabled(false);
|
||||||
|
eraseButton.setEnabled(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void CheatEditor::refresh() {
|
||||||
|
for(unsigned n = 0; n < Codes; n++) {
|
||||||
|
string code = cheat[n].code;
|
||||||
|
string desc = cheat[n].code.empty() && cheat[n].desc.empty() ? "(empty)" : cheat[n].desc;
|
||||||
|
lstring codes = code.split("+");
|
||||||
|
if(codes.size() > 1) code = {codes[0], "+..."};
|
||||||
|
cheatList.modify(n, decimal<3>(1 + n), code, desc);
|
||||||
|
}
|
||||||
|
cheatList.autoSizeColumns();
|
||||||
|
}
|
||||||
|
|
||||||
|
void CheatEditor::update() {
|
||||||
|
lstring codes;
|
||||||
|
for(unsigned n = 0; n < Codes; n++) {
|
||||||
|
string code = cheat[n].code;
|
||||||
|
if(cheatList.checked(n) && !code.empty()) codes.append(code);
|
||||||
|
}
|
||||||
|
system().cheatSet(codes);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CheatEditor::reset() {
|
||||||
|
for(unsigned n = 0; n < Codes; n++) {
|
||||||
|
cheatList.setChecked(n, false);
|
||||||
|
cheat[n].code = "";
|
||||||
|
cheat[n].desc = "";
|
||||||
|
}
|
||||||
|
codeEdit.setText("");
|
||||||
|
descEdit.setText("");
|
||||||
|
refresh();
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
|
||||||
|
void CheatEditor::erase() {
|
||||||
|
unsigned n = cheatList.selection();
|
||||||
|
cheatList.setChecked(n, false);
|
||||||
|
cheat[n].code = "";
|
||||||
|
cheat[n].desc = "";
|
||||||
|
codeEdit.setText("");
|
||||||
|
descEdit.setText("");
|
||||||
|
refresh();
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
|
||||||
|
void CheatEditor::updateCode() {
|
||||||
|
unsigned n = cheatList.selection();
|
||||||
|
cheat[n].code = codeEdit.text();
|
||||||
|
refresh();
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
|
||||||
|
void CheatEditor::updateDesc() {
|
||||||
|
unsigned n = cheatList.selection();
|
||||||
|
cheat[n].desc = descEdit.text();
|
||||||
|
refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CheatEditor::load(const string &filename) {
|
||||||
|
string data;
|
||||||
|
if(data.readfile(filename) == false) return false;
|
||||||
|
|
||||||
|
unsigned n = 0;
|
||||||
|
XML::Document document(data);
|
||||||
|
for(auto &node : document["cartridge"]) {
|
||||||
|
if(node.name != "cheat") continue;
|
||||||
|
cheatList.setChecked(n, node["enable"].data == "true");
|
||||||
|
cheat[n].code = node["code"].data;
|
||||||
|
cheat[n].desc = node["description"].data;
|
||||||
|
if(++n >= Codes) break;
|
||||||
|
}
|
||||||
|
|
||||||
|
refresh();
|
||||||
|
update();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CheatEditor::save(const string &filename) {
|
||||||
|
signed lastSave = -1;
|
||||||
|
for(signed n = 127; n >= 0; n--) {
|
||||||
|
if(!cheat[n].code.empty() || !cheat[n].desc.empty()) {
|
||||||
|
lastSave = n;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(lastSave == -1) {
|
||||||
|
unlink(filename);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
file fp;
|
||||||
|
if(fp.open(filename, file::mode::write) == false) return false;
|
||||||
|
|
||||||
|
fp.print("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
|
||||||
|
fp.print("<cartridge sha256=\"", system().sha256(), "\">\n");
|
||||||
|
for(unsigned n = 0; n <= lastSave; n++) {
|
||||||
|
fp.print(" <cheat enable=\"", cheatList.checked(n) ? "true" : "false", "\">\n");
|
||||||
|
fp.print(" <description><![CDATA[", cheat[n].desc, "]]></description>\n");
|
||||||
|
fp.print(" <code><![CDATA[", cheat[n].code, "]]></code>\n");
|
||||||
|
fp.print(" </cheat>\n");
|
||||||
|
}
|
||||||
|
fp.print("</cartridge>\n");
|
||||||
|
fp.close();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CheatEditor::import(const string &code, const string &desc) {
|
||||||
|
for(unsigned n = 0; n < Codes; n++) {
|
||||||
|
if(cheat[n].code.empty() && cheat[n].desc.empty()) {
|
||||||
|
cheatList.setChecked(n, false);
|
||||||
|
cheat[n].code = code;
|
||||||
|
cheat[n].desc = desc;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
|
@ -0,0 +1,39 @@
|
||||||
|
struct CheatEditor : Window {
|
||||||
|
VerticalLayout layout;
|
||||||
|
ListView cheatList;
|
||||||
|
HorizontalLayout codeLayout;
|
||||||
|
Label codeLabel;
|
||||||
|
LineEdit codeEdit;
|
||||||
|
HorizontalLayout descLayout;
|
||||||
|
Label descLabel;
|
||||||
|
LineEdit descEdit;
|
||||||
|
HorizontalLayout controlLayout;
|
||||||
|
Button findButton;
|
||||||
|
Widget spacer;
|
||||||
|
Button resetButton;
|
||||||
|
Button eraseButton;
|
||||||
|
|
||||||
|
void reset();
|
||||||
|
void erase();
|
||||||
|
void updateCode();
|
||||||
|
void updateDesc();
|
||||||
|
|
||||||
|
bool load(const string &filename);
|
||||||
|
bool save(const string &filename);
|
||||||
|
bool import(const string &code, const string &desc);
|
||||||
|
|
||||||
|
void update();
|
||||||
|
void refresh();
|
||||||
|
void synchronize();
|
||||||
|
CheatEditor();
|
||||||
|
|
||||||
|
private:
|
||||||
|
enum : unsigned { Codes = 128 };
|
||||||
|
struct Cheat {
|
||||||
|
string code;
|
||||||
|
string desc;
|
||||||
|
};
|
||||||
|
Cheat cheat[Codes];
|
||||||
|
};
|
||||||
|
|
||||||
|
extern CheatEditor *cheatEditor;
|
|
@ -0,0 +1,155 @@
|
||||||
|
StateManager *stateManager = nullptr;
|
||||||
|
|
||||||
|
StateManager::StateManager() {
|
||||||
|
setGeometry({128, 128, 600, 360});
|
||||||
|
windowManager->append(this, "StateManager");
|
||||||
|
|
||||||
|
setTitle("State Manager");
|
||||||
|
layout.setMargin(5);
|
||||||
|
stateList.setHeaderText("Slot", "Description");
|
||||||
|
stateList.setHeaderVisible();
|
||||||
|
for(unsigned n = 0; n < Slots; n++) stateList.append(decimal<2>(1 + n), "(empty)");
|
||||||
|
stateList.autoSizeColumns();
|
||||||
|
descLabel.setText("Description:");
|
||||||
|
saveButton.setText("Save");
|
||||||
|
loadButton.setText("Load");
|
||||||
|
resetButton.setText("Reset");
|
||||||
|
eraseButton.setText("Erase");
|
||||||
|
|
||||||
|
append(layout);
|
||||||
|
layout.append(stateList, {~0, ~0}, 5);
|
||||||
|
layout.append(descLayout, {~0, 0}, 5);
|
||||||
|
descLayout.append(descLabel, {0, 0}, 5);
|
||||||
|
descLayout.append(descEdit, {~0, 0});
|
||||||
|
layout.append(controlLayout, {~0, 0});
|
||||||
|
controlLayout.append(saveButton, {80, 0}, 5);
|
||||||
|
controlLayout.append(loadButton, {80, 0}, 5);
|
||||||
|
controlLayout.append(spacer, {~0, 0});
|
||||||
|
controlLayout.append(resetButton, {80, 0}, 5);
|
||||||
|
controlLayout.append(eraseButton, {80, 0});
|
||||||
|
|
||||||
|
stateList.onChange = {&StateManager::synchronize, this};
|
||||||
|
stateList.onActivate = {&StateManager::slotLoad, this};
|
||||||
|
descEdit.onChange = {&StateManager::slotSaveDescription, this};
|
||||||
|
saveButton.onActivate = {&StateManager::slotSave, this};
|
||||||
|
loadButton.onActivate = {&StateManager::slotLoad, this};
|
||||||
|
resetButton.onActivate = [&] {
|
||||||
|
if(MessageWindow::question(*this, "All states will be erased. Are you sure you want to do this?")
|
||||||
|
== MessageWindow::Response::Yes) reset();
|
||||||
|
};
|
||||||
|
eraseButton.onActivate = {&StateManager::slotErase, this};
|
||||||
|
|
||||||
|
stateList.setSelection(0);
|
||||||
|
synchronize();
|
||||||
|
}
|
||||||
|
|
||||||
|
void StateManager::synchronize() {
|
||||||
|
layout.setEnabled(application->active);
|
||||||
|
|
||||||
|
descEdit.setText("");
|
||||||
|
descEdit.setEnabled(false);
|
||||||
|
controlLayout.setEnabled(stateList.selected());
|
||||||
|
if(stateList.selected() == false) return;
|
||||||
|
|
||||||
|
if(slot[stateList.selection()].capacity() > 0) {
|
||||||
|
descEdit.setText(slotLoadDescription(stateList.selection()));
|
||||||
|
descEdit.setEnabled(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void StateManager::refresh() {
|
||||||
|
for(unsigned n = 0; n < Slots; n++) {
|
||||||
|
stateList.modify(n, decimal<2>(1 + n), slotLoadDescription(n));
|
||||||
|
}
|
||||||
|
stateList.autoSizeColumns();
|
||||||
|
}
|
||||||
|
|
||||||
|
void StateManager::reset() {
|
||||||
|
for(auto &slot : this->slot) slot = serializer();
|
||||||
|
synchronize();
|
||||||
|
refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool StateManager::load(const string &filename, unsigned revision) {
|
||||||
|
for(auto &slot : this->slot) slot = serializer();
|
||||||
|
synchronize();
|
||||||
|
|
||||||
|
file fp;
|
||||||
|
if(fp.open(filename, file::mode::read) == false) return false;
|
||||||
|
|
||||||
|
if(fp.readl(4) == 0x31415342 /* 'BSA1' */ && fp.readl(4) == revision) {
|
||||||
|
for(auto &slot : this->slot) {
|
||||||
|
if(fp.read() == false) continue; //slot is empty
|
||||||
|
unsigned size = fp.readl(4);
|
||||||
|
uint8_t *data = new uint8_t[size];
|
||||||
|
fp.read(data, size);
|
||||||
|
slot = serializer(data, size);
|
||||||
|
delete[] data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
refresh();
|
||||||
|
synchronize();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
file fp;
|
||||||
|
if(fp.open(filename, file::mode::write) == false) return false;
|
||||||
|
|
||||||
|
fp.writel(0x31415342, 4); //'BSA1'
|
||||||
|
fp.writel(revision, 4);
|
||||||
|
for(auto &slot : this->slot) {
|
||||||
|
fp.write(slot.capacity() > 0);
|
||||||
|
if(slot.capacity()) {
|
||||||
|
fp.writel(slot.capacity(), 4);
|
||||||
|
fp.write(slot.data(), slot.capacity());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void StateManager::slotLoad() {
|
||||||
|
if(stateList.selected() == false) return;
|
||||||
|
serializer s(slot[stateList.selection()].data(), slot[stateList.selection()].capacity());
|
||||||
|
system().unserialize(s);
|
||||||
|
}
|
||||||
|
|
||||||
|
void StateManager::slotSave() {
|
||||||
|
if(stateList.selected()) {
|
||||||
|
slot[stateList.selection()] = system().serialize();
|
||||||
|
}
|
||||||
|
refresh();
|
||||||
|
synchronize();
|
||||||
|
descEdit.setFocused();
|
||||||
|
}
|
||||||
|
|
||||||
|
void StateManager::slotErase() {
|
||||||
|
if(stateList.selected()) {
|
||||||
|
slot[stateList.selection()] = serializer();
|
||||||
|
}
|
||||||
|
refresh();
|
||||||
|
synchronize();
|
||||||
|
}
|
||||||
|
|
||||||
|
string StateManager::slotLoadDescription(unsigned id) {
|
||||||
|
if(slot[id].capacity() == 0) return "(empty)";
|
||||||
|
char text[DescriptionLength];
|
||||||
|
strmcpy(text, (const char*)slot[id].data() + HeaderLength, DescriptionLength);
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
|
void StateManager::slotSaveDescription() {
|
||||||
|
if(stateList.selected() == false) return;
|
||||||
|
string text = descEdit.text();
|
||||||
|
if(slot[stateList.selection()].capacity() > 0) {
|
||||||
|
strmcpy((char*)slot[stateList.selection()].data() + HeaderLength, (const char*)text, DescriptionLength);
|
||||||
|
}
|
||||||
|
refresh();
|
||||||
|
}
|
|
@ -0,0 +1,37 @@
|
||||||
|
struct StateManager : Window {
|
||||||
|
VerticalLayout layout;
|
||||||
|
ListView stateList;
|
||||||
|
HorizontalLayout descLayout;
|
||||||
|
Label descLabel;
|
||||||
|
LineEdit descEdit;
|
||||||
|
HorizontalLayout controlLayout;
|
||||||
|
Button saveButton;
|
||||||
|
Button loadButton;
|
||||||
|
Widget spacer;
|
||||||
|
Button resetButton;
|
||||||
|
Button eraseButton;
|
||||||
|
|
||||||
|
void reset();
|
||||||
|
bool load(const string &filename, unsigned revision);
|
||||||
|
bool save(const string &filename, unsigned revision);
|
||||||
|
|
||||||
|
void slotLoad();
|
||||||
|
void slotSave();
|
||||||
|
void slotErase();
|
||||||
|
string slotLoadDescription(unsigned id);
|
||||||
|
void slotSaveDescription();
|
||||||
|
|
||||||
|
void refresh();
|
||||||
|
void synchronize();
|
||||||
|
StateManager();
|
||||||
|
|
||||||
|
private:
|
||||||
|
enum : unsigned {
|
||||||
|
Slots = 32,
|
||||||
|
HeaderLength = 72,
|
||||||
|
DescriptionLength = 512,
|
||||||
|
};
|
||||||
|
serializer slot[Slots];
|
||||||
|
};
|
||||||
|
|
||||||
|
extern StateManager *stateManager;
|
|
@ -0,0 +1,4 @@
|
||||||
|
#include "../ethos.hpp"
|
||||||
|
#include "cheat-database.cpp"
|
||||||
|
#include "cheat-editor.cpp"
|
||||||
|
#include "state-manager.cpp"
|
|
@ -0,0 +1,3 @@
|
||||||
|
#include "cheat-database.hpp"
|
||||||
|
#include "cheat-editor.hpp"
|
||||||
|
#include "state-manager.hpp"
|
|
@ -7,44 +7,69 @@ void Utility::setInterface(Emulator::Interface *emulator) {
|
||||||
presentation->synchronize();
|
presentation->synchronize();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Utility::loadSchema(Emulator::Interface *emulator, Emulator::Interface::Schema &schema) {
|
void Utility::loadMedia(Emulator::Interface *emulator, Emulator::Interface::Media &media) {
|
||||||
string pathname = application->path({schema.name, ".", schema.extension, "/"});
|
string pathname = application->path({media.name, ".", media.type, "/"});
|
||||||
if(!directory::exists(pathname)) pathname = browser->select({"Load ", schema.name}, schema.extension);
|
if(!file::exists({pathname, media.path})) pathname = browser->select({"Load ", media.name}, media.extension);
|
||||||
if(!directory::exists(pathname)) return;
|
if(!file::exists({pathname, media.path})) return;
|
||||||
|
loadMedia(emulator, media, pathname);
|
||||||
loadMedia(emulator, schema, pathname);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Utility::loadMedia(Emulator::Interface *emulator, Emulator::Interface::Media &media, const string &pathname) {
|
void Utility::loadMedia(Emulator::Interface *emulator, Emulator::Interface::Media &media, const string &pathname) {
|
||||||
unload();
|
unload();
|
||||||
setInterface(emulator);
|
setInterface(emulator);
|
||||||
this->pathname = pathname;
|
path(system().group(media.id)) = pathname;
|
||||||
|
if(media.type == "sys") this->pathname.append(pathname);
|
||||||
|
|
||||||
string manifest;
|
string manifest;
|
||||||
manifest.readfile({pathname, "manifest.xml"});
|
manifest.readfile({pathname, "manifest.xml"});
|
||||||
auto memory = file::read({pathname, media.path});
|
auto memory = file::read({pathname, media.path});
|
||||||
system().load(media.id, vectorstream{memory}, manifest);
|
system().load(media.id, vectorstream{memory}, manifest);
|
||||||
|
|
||||||
|
if(this->pathname.size() == 0) this->pathname.append(pathname);
|
||||||
|
presentation->setSystemName(media.name);
|
||||||
|
load();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Utility::loadMedia(Emulator::Interface::Media &media) {
|
||||||
|
string pathname = {path(system().group(media.id)), media.path};
|
||||||
|
if(file::exists(pathname)) {
|
||||||
|
mmapstream stream(pathname);
|
||||||
|
return system().load(media.id, stream);
|
||||||
|
}
|
||||||
|
if(media.name.empty()) return;
|
||||||
|
|
||||||
|
pathname = browser->select({"Load ", media.name}, media.extension);
|
||||||
|
if(pathname.empty()) return;
|
||||||
|
path(system().group(media.id)) = pathname;
|
||||||
|
this->pathname.append(pathname);
|
||||||
|
|
||||||
|
string markup;
|
||||||
|
markup.readfile({pathname, "manifest.xml"});
|
||||||
|
mmapstream stream({pathname, media.path});
|
||||||
|
system().load(media.id, stream, markup);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Utility::loadMemory() {
|
||||||
for(auto &memory : system().memory) {
|
for(auto &memory : system().memory) {
|
||||||
|
string pathname = path(system().group(memory.id));
|
||||||
filestream fs({pathname, memory.name});
|
filestream fs({pathname, memory.name});
|
||||||
system().load(memory.id, fs);
|
system().load(memory.id, fs);
|
||||||
}
|
}
|
||||||
|
|
||||||
system().updatePalette();
|
cheatEditor->load({pathname[0], "cheats.xml"});
|
||||||
dspaudio.setFrequency(emulator->information.frequency);
|
stateManager->load({pathname[0], "bsnes/states.bsa"}, 1);
|
||||||
|
|
||||||
string displayname = pathname;
|
|
||||||
displayname.rtrim<1>("/");
|
|
||||||
presentation->setTitle(notdir(nall::basename(displayname)));
|
|
||||||
presentation->setSystemName(media.name);
|
|
||||||
resize();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Utility::saveMedia() {
|
void Utility::saveMemory() {
|
||||||
for(auto &memory : system().memory) {
|
for(auto &memory : system().memory) {
|
||||||
|
string pathname = path(system().group(memory.id));
|
||||||
filestream fs({pathname, memory.name}, file::mode::write);
|
filestream fs({pathname, memory.name}, file::mode::write);
|
||||||
system().save(memory.id, fs);
|
system().save(memory.id, fs);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mkdir(string{pathname[0], "bsnes/"}, 0755);
|
||||||
|
cheatEditor->save({pathname[0], "cheats.xml"});
|
||||||
|
stateManager->save({pathname[0], "bsnes/states.bsa"}, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Utility::connect(unsigned port, unsigned device) {
|
void Utility::connect(unsigned port, unsigned device) {
|
||||||
|
@ -62,12 +87,37 @@ void Utility::reset() {
|
||||||
system().reset();
|
system().reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Utility::unload() {
|
void Utility::load() {
|
||||||
if(application->active) {
|
string title;
|
||||||
saveMedia();
|
for(auto &path : pathname) {
|
||||||
system().unload();
|
string name = path;
|
||||||
setInterface(nullptr);
|
name.rtrim<1>("/");
|
||||||
|
title.append(notdir(nall::basename(name)), " + ");
|
||||||
}
|
}
|
||||||
|
title.rtrim<1>(" + ");
|
||||||
|
presentation->setTitle(title);
|
||||||
|
|
||||||
|
loadMemory();
|
||||||
|
|
||||||
|
system().updatePalette();
|
||||||
|
dspaudio.setFrequency(system().information.frequency);
|
||||||
|
|
||||||
|
resize();
|
||||||
|
cheatEditor->synchronize();
|
||||||
|
cheatEditor->refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Utility::unload() {
|
||||||
|
if(application->active == nullptr) return;
|
||||||
|
|
||||||
|
saveMemory();
|
||||||
|
system().unload();
|
||||||
|
path.reset();
|
||||||
|
pathname.reset();
|
||||||
|
cheatEditor->reset();
|
||||||
|
stateManager->reset();
|
||||||
|
setInterface(nullptr);
|
||||||
|
|
||||||
presentation->setTitle({Emulator::Name, " v", Emulator::Version});
|
presentation->setTitle({Emulator::Name, " v", Emulator::Version});
|
||||||
video.clear();
|
video.clear();
|
||||||
}
|
}
|
||||||
|
@ -76,14 +126,14 @@ void Utility::saveState(unsigned slot) {
|
||||||
if(application->active == nullptr) return;
|
if(application->active == nullptr) return;
|
||||||
serializer s = system().serialize();
|
serializer s = system().serialize();
|
||||||
if(s.size() == 0) return;
|
if(s.size() == 0) return;
|
||||||
mkdir(string{pathname, "bsnes/"}, 0755);
|
mkdir(string{pathname[0], "bsnes/"}, 0755);
|
||||||
if(file::write({pathname, "bsnes/state-", slot, ".bsa"}, s.data(), s.size()) == false);
|
if(file::write({pathname[0], "bsnes/state-", slot, ".bsa"}, s.data(), s.size()) == false);
|
||||||
showMessage({"Save to slot ", slot});
|
showMessage({"Save to slot ", slot});
|
||||||
}
|
}
|
||||||
|
|
||||||
void Utility::loadState(unsigned slot) {
|
void Utility::loadState(unsigned slot) {
|
||||||
if(application->active == nullptr) return;
|
if(application->active == nullptr) return;
|
||||||
auto memory = file::read({pathname, "bsnes/state-", slot, ".bsa"});
|
auto memory = file::read({pathname[0], "bsnes/state-", slot, ".bsa"});
|
||||||
if(memory.size() == 0) return;
|
if(memory.size() == 0) return;
|
||||||
serializer s(memory.data(), memory.size());
|
serializer s(memory.data(), memory.size());
|
||||||
if(system().unserialize(s) == false) return;
|
if(system().unserialize(s) == false) return;
|
||||||
|
|
|
@ -1,14 +1,15 @@
|
||||||
struct Utility {
|
struct Utility {
|
||||||
string pathname;
|
|
||||||
|
|
||||||
void setInterface(Emulator::Interface *emulator);
|
void setInterface(Emulator::Interface *emulator);
|
||||||
void loadSchema(Emulator::Interface *emulator, Emulator::Interface::Schema &schema);
|
void loadMedia(Emulator::Interface *emulator, Emulator::Interface::Media &media);
|
||||||
void loadMedia(Emulator::Interface *emulator, Emulator::Interface::Media &media, const string &pathname);
|
void loadMedia(Emulator::Interface *emulator, Emulator::Interface::Media &media, const string &pathname);
|
||||||
void saveMedia();
|
void loadMedia(Emulator::Interface::Media &media);
|
||||||
|
void loadMemory();
|
||||||
|
void saveMemory();
|
||||||
|
|
||||||
void connect(unsigned port, unsigned device);
|
void connect(unsigned port, unsigned device);
|
||||||
void power();
|
void power();
|
||||||
void reset();
|
void reset();
|
||||||
|
void load();
|
||||||
void unload();
|
void unload();
|
||||||
|
|
||||||
void saveState(unsigned slot);
|
void saveState(unsigned slot);
|
||||||
|
@ -24,6 +25,9 @@ struct Utility {
|
||||||
|
|
||||||
Utility();
|
Utility();
|
||||||
|
|
||||||
|
lstring path;
|
||||||
|
lstring pathname;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
string statusText;
|
string statusText;
|
||||||
string statusMessage;
|
string statusMessage;
|
||||||
|
|
Loading…
Reference in New Issue