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:
Tim Allen 2012-05-06 16:34:46 +10:00
parent 5d273c5265
commit 3cb04b101b
41 changed files with 1067 additions and 394 deletions

View File

@ -3,7 +3,7 @@
namespace Emulator {
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 License[] = "GPLv3";
}

View File

@ -12,29 +12,18 @@ struct Interface {
double aspectRatio;
unsigned frequency;
bool resettable;
struct Media {
string name;
string extension;
};
vector<Media> media;
} information;
struct Media {
unsigned id;
string name;
string extension;
string type;
string path;
string extension;
};
vector<Media> firmware;
struct Schema : Media {
vector<Media> slot;
Schema(const Media &media) {
id = media.id, name = media.name, extension = media.extension, path = media.path;
}
};
vector<Schema> schema;
vector<Media> firmware;
vector<Media> media;
struct Memory {
unsigned id;
@ -42,21 +31,24 @@ struct Interface {
};
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 {
unsigned id;
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<Port> port;
@ -94,6 +86,8 @@ struct Interface {
//media interface
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 save(unsigned id, const stream &memory) {}
virtual void unload() {}
@ -108,6 +102,9 @@ struct Interface {
virtual serializer serialize() = 0;
virtual bool unserialize(serializer&) = 0;
//cheat functions
virtual void cheatSet(const lstring& = lstring{}) {}
//utility functions
virtual void updatePalette() {}
};

View File

@ -8,6 +8,10 @@ bool Interface::loaded() {
return cartridge.loaded();
}
string Interface::sha256() {
return cartridge.sha256();
}
void Interface::load(unsigned id, const stream &stream, const string &markup) {
if(id == ID::ROM) {
cartridge.load(markup, stream);
@ -52,6 +56,18 @@ bool Interface::unserialize(serializer &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() {
video.generate_palette();
}
@ -67,35 +83,32 @@ Interface::Interface() {
information.frequency = 1789772;
information.resettable = true;
information.media.append({"Famicom", "fc"});
schema.append(Media{ID::ROM, "Famicom", "fc", "program.rom"});
media.append({ID::ROM, "Famicom", "sys", "program.rom", "fc"});
{
Port port{0, "Port 1"};
port.device.append(controller());
this->port.append(port);
Device device{0, ID::Port1 | ID::Port2, "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};
this->device.append(device);
}
{
Port port{1, "Port 2"};
port.device.append(controller());
this->port.append(port);
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::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;
}
}

View File

@ -7,10 +7,16 @@ struct ID {
ROM,
RAM,
};
enum : unsigned {
Port1 = 1,
Port2 = 2,
};
};
struct Interface : Emulator::Interface {
bool loaded();
string sha256();
void load(unsigned id, const stream &stream, const string &markup = "");
void save(unsigned id, const stream &stream);
void unload();
@ -22,12 +28,11 @@ struct Interface : Emulator::Interface {
serializer serialize();
bool unserialize(serializer&);
void cheatSet(const lstring&);
void updatePalette();
Interface();
private:
Port::Device controller();
};
extern Interface *interface;

View File

@ -8,6 +8,10 @@ bool Interface::loaded() {
return cartridge.loaded();
}
string Interface::sha256() {
return cartridge.sha256();
}
void Interface::load(unsigned id, const stream &stream, const string &markup) {
if(id == ID::GameBoyBootROM) {
stream.read(system.bootROM.dmg, min( 256u, stream.size()));
@ -67,6 +71,18 @@ bool Interface::unserialize(serializer &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() {
video.generate_palette();
}
@ -82,33 +98,28 @@ Interface::Interface() {
information.frequency = 4194304;
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::SuperGameBoyBootROM, "Super Game Boy", "sfc", "boot.rom"});
firmware.append({ID::GameBoyColorBootROM, "Game Boy Color", "sys", "boot.rom"});
schema.append(Media{ID::GameBoyROM, "Game Boy", "gb", "program.rom"});
schema.append(Media{ID::GameBoyColorROM, "Game Boy Color", "gbc", "program.rom"});
media.append({ID::GameBoyROM, "Game Boy", "sys", "program.rom", "gb" });
media.append({ID::GameBoyColorROM, "Game Boy Color", "sys", "program.rom", "gbc"});
{
Port port{0, "Device"};
{
Port::Device device{0, "Controller"};
device.input.append({0, 0, "Up" });
device.input.append({1, 0, "Down" });
device.input.append({2, 0, "Left" });
device.input.append({3, 0, "Right" });
device.input.append({4, 0, "B" });
device.input.append({5, 0, "A" });
device.input.append({6, 0, "Select"});
device.input.append({7, 0, "Start" });
device.order = {0, 1, 2, 3, 4, 5, 6, 7};
port.device.append(device);
}
this->port.append(port);
Device device{0, ID::Device, "Controller"};
device.input.append({0, 0, "Up" });
device.input.append({1, 0, "Down" });
device.input.append({2, 0, "Left" });
device.input.append({3, 0, "Right" });
device.input.append({4, 0, "B" });
device.input.append({5, 0, "A" });
device.input.append({6, 0, "Select"});
device.input.append({7, 0, "Start" });
device.order = {0, 1, 2, 3, 4, 5, 6, 7};
this->device.append(device);
}
port.append({ID::Device, "Device", {device[0]}});
}
}

View File

@ -11,6 +11,10 @@ struct ID {
GameBoyColorROM,
RAM,
};
enum : unsigned {
Device = 1,
};
};
struct Interface : Emulator::Interface {
@ -19,6 +23,7 @@ struct Interface : Emulator::Interface {
virtual void joypWrite(bool p15, bool p14) {}
bool loaded();
string sha256();
void load(unsigned id, const stream &stream, const string &markup = "");
void save(unsigned id, const stream &stream);
void unload();
@ -30,6 +35,8 @@ struct Interface : Emulator::Interface {
serializer serialize();
bool unserialize(serializer&);
void cheatSet(const lstring&);
void updatePalette();
Interface();

View File

@ -86,31 +86,27 @@ Interface::Interface() {
information.frequency = 32768;
information.resettable = false;
information.media.append({"Game Boy Advance", "gba"});
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"};
{
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, "Right" });
device.input.append({5, 0, "Left" });
device.input.append({6, 0, "Up" });
device.input.append({7, 0, "Down" });
device.input.append({8, 0, "R" });
device.input.append({9, 0, "L" });
device.order = {6, 7, 5, 4, 1, 0, 9, 8, 2, 3};
port.device.append(device);
}
this->port.append(port);
Device device{0, ID::Device, "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, "Right" });
device.input.append({5, 0, "Left" });
device.input.append({6, 0, "Up" });
device.input.append({7, 0, "Down" });
device.input.append({8, 0, "R" });
device.input.append({9, 0, "L" });
device.order = {6, 7, 5, 4, 1, 0, 9, 8, 2, 3};
this->device.append(device);
}
port.append({ID::Device, "Device", {device[0]}});
}
}

View File

@ -10,6 +10,10 @@ struct ID {
EEPROM,
FlashROM,
};
enum : unsigned {
Device = 1,
};
};
struct Interface : Emulator::Interface {

View File

@ -11,7 +11,6 @@ Cartridge cartridge;
void Cartridge::load(const string &markup, const stream &stream) {
information.markup = markup;
rom.copy(stream);
sha256 = nall::sha256(rom.data(), rom.size()); //TODO: special case SGB, BSX, ST mode SHA256 sums
region = Region::NTSC;
ram_size = 0;
@ -19,7 +18,7 @@ void Cartridge::load(const string &markup, const stream &stream) {
has_gb_slot = false;
has_bs_cart = false;
has_bs_slot = false;
has_st_slot = false;
has_st_slots = false;
has_nss_dip = false;
has_superfx = false;
has_sa1 = false;
@ -39,6 +38,26 @@ void Cartridge::load(const string &markup, const stream &stream) {
parse_markup(markup);
//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) {
ram.map(allocate<uint8>(ram_size, 0xff), ram_size);
interface->memory.append({ID::RAM, "save.ram"});

View File

@ -25,7 +25,7 @@ struct Cartridge : property<Cartridge> {
readonly<bool> has_gb_slot;
readonly<bool> has_bs_cart;
readonly<bool> has_bs_slot;
readonly<bool> has_st_slot;
readonly<bool> has_st_slots;
readonly<bool> has_nss_dip;
readonly<bool> has_superfx;
readonly<bool> has_sa1;

View File

@ -110,7 +110,7 @@ void Cartridge::parse_markup_icd2(XML::Node &root) {
if(root.exists() == false) return;
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));
@ -225,33 +225,12 @@ void Cartridge::parse_markup_necdsp(XML::Node &root) {
string firmware = root["firmware"].data;
string sha256 = root["sha256"].data;
string path = interface->path((unsigned)Slot::Base, firmware);
unsigned promsize = (necdsp.revision == NECDSP::Revision::uPD7725 ? 2048 : 16384);
unsigned dromsize = (necdsp.revision == NECDSP::Revision::uPD7725 ? 1024 : 2048);
unsigned filesize = promsize * 3 + dromsize * 2;
if(necdsp.revision == NECDSP::Revision::uPD7725) {
interface->mediaRequest({ID::Nec7725DSP, "", "", firmware});
}
file fp;
if(fp.open(path, file::mode::read) == false) {
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();
if(necdsp.revision == NECDSP::Revision::uPD96050) {
interface->mediaRequest({ID::Nec96050DSP, "", "", firmware});
}
for(auto &node : root) {
@ -290,29 +269,7 @@ void Cartridge::parse_markup_hitachidsp(XML::Node &root) {
string firmware = root["firmware"].data;
string sha256 = root["sha256"].data;
string path = interface->path((unsigned)Slot::Base, firmware);
file fp;
if(fp.open(path, file::mode::read) == false) {
interface->message({ "Warning: Hitachi DSP firmware ", firmware, " is missing." });
} 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();
}
interface->mediaRequest({ID::HitachiDSP, "", "", firmware});
for(auto &node : root) {
if(node.name == "rom") {
@ -340,24 +297,7 @@ void Cartridge::parse_markup_armdsp(XML::Node &root) {
string firmware = root["firmware"].data;
string sha256 = root["sha256"].data;
string path = interface->path((unsigned)Slot::Base, firmware);
file fp;
if(fp.open(path, file::mode::read) == false) {
interface->message({ "Warning: ARM DSP firmware ", firmware, " is missing." });
} 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();
}
interface->mediaRequest({ID::ArmDSP, "", "", firmware});
for(auto &node : root) {
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_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"]) {
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) {
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) {
if(slot.name != "slot") continue;

View File

@ -3,7 +3,7 @@ struct CheatCode {
unsigned data;
};
struct Cheat : public linear_vector<CheatCode> {
struct Cheat : public vector<CheatCode> {
uint8 *override;
bool enabled() const;

View File

@ -96,21 +96,21 @@ int16_t ICD2::inputPoll(unsigned port, unsigned device, unsigned id) {
unsigned data = 0x00;
switch(joyp_id & mlt_req) {
case 0: data = ~r6004; break;
case 1: data = ~r6005; break;
case 2: data = ~r6006; break;
case 3: data = ~r6007; break;
case 0: data = ~r6004; break;
case 1: data = ~r6005; break;
case 2: data = ~r6006; break;
case 3: data = ~r6007; break;
}
switch((GameBoy::Input)id) {
case GameBoy::Input::Start: return data & 0x80;
case GameBoy::Input::Select: return data & 0x40;
case GameBoy::Input::B: return data & 0x20;
case GameBoy::Input::A: return data & 0x10;
case GameBoy::Input::Down: return data & 0x08;
case GameBoy::Input::Up: return data & 0x04;
case GameBoy::Input::Left: return data & 0x02;
case GameBoy::Input::Right: return data & 0x01;
case GameBoy::Input::Start: return (bool)(data & 0x80);
case GameBoy::Input::Select: return (bool)(data & 0x40);
case GameBoy::Input::B: return (bool)(data & 0x20);
case GameBoy::Input::A: return (bool)(data & 0x10);
case GameBoy::Input::Down: return (bool)(data & 0x08);
case GameBoy::Input::Up: return (bool)(data & 0x04);
case GameBoy::Input::Left: return (bool)(data & 0x02);
case GameBoy::Input::Right: return (bool)(data & 0x01);
}
return 0;

View File

@ -34,7 +34,7 @@ void NECDSP::init() {
void NECDSP::load() {
if(revision == Revision::uPD96050) {
cartridge.nvram.append({ "upd96050.ram", (uint8_t*)dataRAM, 4096 });
interface->memory.append({ID::NecDSPRAM, "upd96050.ram"});
}
}

View File

@ -11,13 +11,13 @@ void SufamiTurbo::load() {
slotB.ram.map(allocate<uint8>(128 * 1024, 0xff), 128 * 1024);
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 {
slotA.rom.map(allocate<uint8>(128 * 1024, 0xff), 128 * 1024);
}
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 {
slotB.rom.map(allocate<uint8>(128 * 1024, 0xff), 128 * 1024);
}

View File

@ -8,11 +8,63 @@ bool Interface::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) {
if(id == ID::IPLROM) {
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) {
cartridge.load(markup, stream);
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()));
}
if(id == ID::NecDSPRAM) {
for(unsigned n = 0; n < 2048; n++) necdsp.dataRAM[n] = stream.readl(2);
}
if(id == ID::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) {
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) {
@ -62,6 +126,10 @@ void Interface::save(unsigned id, const stream &stream) {
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) {
stream.write(srtc.rtc, sizeof srtc.rtc);
}
@ -77,6 +145,14 @@ void Interface::save(unsigned id, const stream &stream) {
if(id == ID::BsxPSRAM) {
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() {
@ -108,6 +184,35 @@ bool Interface::unserialize(serializer &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() {
video.generate_palette();
}
@ -123,154 +228,119 @@ Interface::Interface() {
information.frequency = 32040;
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"});
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"});
this->schema.append(schema);
Device device{0, ID::Port1 | ID::Port2, "None"};
this->device.append(device);
}
{
Schema schema(Media{ID::ROM, "Super Game Boy", "sfc", "program.rom"});
schema.slot.append({ID::SuperGameBoyROM, "Game Boy", "gb", "program.rom"});
this->schema.append(schema);
Device device{1, ID::Port1 | ID::Port2, "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};
this->device.append(device);
}
{
Schema schema(Media{ID::ROM, "BS-X Satellaview", "sfc", "program.rom"});
schema.slot.append({ID::BsxFlashROM, "BS-X Satellaview", "bs", "program.rom"});
this->schema.append(schema);
Device device{2, ID::Port1 | ID::Port2, "Multitap"};
for(unsigned p = 1, n = 0; p <= 4; p++, n += 12) {
device.input.append({n + 0, 0, {"Port ", p, " - ", "B" }});
device.input.append({n + 1, 0, {"Port ", p, " - ", "Y" }});
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"});
schema.slot.append({ID::SufamiTurboSlotAROM, "Sufami Turbo - Slot A", "st", "program.rom"});
schema.slot.append({ID::SufamiTurboSlotBROM, "Sufami Turbo - Slot B", "st", "program.rom"});
this->schema.append(schema);
Device device{3, ID::Port1 | ID::Port2, "Mouse"};
device.input.append({0, 1, "X-axis"});
device.input.append({1, 1, "Y-axis"});
device.input.append({2, 0, "Left" });
device.input.append({3, 0, "Right" });
device.order = {0, 1, 2, 3};
this->device.append(device);
}
{
Port port{0, "Port 1"};
port.device.append(none());
port.device.append(controller());
port.device.append(multitap());
port.device.append(mouse());
port.device.append(usart());
this->port.append(port);
Device device{4, ID::Port2, "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};
this->device.append(device);
}
{
Port port{1, "Port 2"};
port.device.append(none());
port.device.append(controller());
port.device.append(multitap());
port.device.append(mouse());
port.device.append(superScope());
port.device.append(justifier());
port.device.append(justifiers());
this->port.append(port);
Device device{5, ID::Port2, "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};
this->device.append(device);
}
{
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;
}
}

View File

@ -5,16 +5,30 @@ namespace SuperFamicom {
struct ID {
enum : unsigned {
IPLROM,
Nec7725DSP,
Nec96050DSP,
HitachiDSP,
ArmDSP,
ROM,
SuperGameBoyROM,
BsxFlashROM,
SufamiTurboSlotAROM,
SufamiTurboSlotBROM,
RAM,
NecDSPRAM,
RTC,
SPC7110RTC,
BsxRAM,
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) {}
bool loaded();
string sha256();
unsigned group(unsigned id);
void load(unsigned id, const stream &stream, const string &markup = "");
void save(unsigned id, const stream &stream);
void unload();
@ -35,19 +51,11 @@ struct Interface : Emulator::Interface {
serializer serialize();
bool unserialize(serializer&);
void cheatSet(const lstring&);
void updatePalette();
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;

View File

@ -60,7 +60,7 @@ void System::serialize_all(serializer &s) {
#if defined(GAMEBOY)
if(cartridge.has_gb_slot()) icd2.serialize(s);
#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_sa1()) sa1.serialize(s);
if(cartridge.has_necdsp()) necdsp.serialize(s);

View File

@ -108,7 +108,7 @@ void System::load() {
#endif
if(cartridge.has_bs_cart()) bsxcartridge.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_superfx()) superfx.load();
if(cartridge.has_sa1()) sa1.load();
@ -133,7 +133,7 @@ void System::unload() {
#endif
if(cartridge.has_bs_cart()) bsxcartridge.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_superfx()) superfx.unload();
if(cartridge.has_sa1()) sa1.unload();

View File

@ -7,7 +7,8 @@ include gb/Makefile
include gba/Makefile
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 += $(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-general.o: $(ui)/general/general.cpp $(call rwildcard,$(ui)/)
obj/ui-settings.o: $(ui)/settings/settings.cpp $(call rwildcard,$(ui)/)
obj/ui-tools.o: $(ui)/tools/tools.cpp $(call rwildcard,$(ui)/)
obj/ruby.o: ruby/ruby.cpp $(call rwildcard,ruby/*)
$(call compile,$(rubyflags))

View File

@ -19,7 +19,7 @@ void Application::bootstrap() {
system->callback.mediaRequest = {&Interface::mediaRequest, interface};
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);
}
}

View File

@ -79,6 +79,9 @@ Application::Application(int argc, char **argv) {
inputSettings = new InputSettings;
hotkeySettings = new HotkeySettings;
settings = new Settings;
cheatDatabase = new CheatDatabase;
cheatEditor = new CheatEditor;
stateManager = new StateManager;
windowManager->loadGeometry();
presentation->setVisible();
@ -105,7 +108,7 @@ Application::Application(int argc, char **argv) {
run();
}
if(active && system().loaded()) utility->unload();
utility->unload();
config->save();
browser->saveConfiguration();
inputManager->saveConfiguration();

View File

@ -24,6 +24,7 @@ using namespace ruby;
#include "window/window.hpp"
#include "general/general.hpp"
#include "settings/settings.hpp"
#include "tools/tools.hpp"
Emulator::Interface& system();

View File

@ -64,7 +64,7 @@ void Browser::saveConfiguration() {
void Browser::bootstrap() {
for(auto &emulator : application->emulator) {
for(auto &media : emulator->information.media) {
for(auto &media : emulator->media) {
bool found = false;
for(auto &folder : folderList) {
if(folder.extension == media.extension) {

View File

@ -56,12 +56,14 @@ Presentation::Presentation() : active(nullptr) {
loadStateMenu.setText("Load State");
for(unsigned n = 0; n < 5; n++) loadStateItem[n].setText({"Slot ", 1 + n});
resizeWindow.setText("Resize Window");
cheatEditor.setText("Cheat Editor");
stateManager.setText("State Manager");
append(loadMenu);
for(auto &item : loadListDirect) loadMenu.append(*item);
if(loadListSlotted.size() > 0) {
for(auto &item : loadListSystem) loadMenu.append(*item);
if(loadListSubsystem.size() > 0) {
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);
append(settingsMenu);
@ -77,7 +79,7 @@ Presentation::Presentation() : active(nullptr) {
toolsMenu.append(loadStateMenu);
for(unsigned n = 0; n < 5; n++) loadStateMenu.append(loadStateItem[n]);
toolsMenu.append(stateMenuSeparator);
toolsMenu.append(resizeWindow);
toolsMenu.append(resizeWindow, cheatEditor, stateManager);
append(layout);
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++) loadStateItem[n].onActivate = [=] { utility->loadState(1 + n); };
resizeWindow.onActivate = [&] { utility->resize(true); };
cheatEditor.onActivate = [&] { ::cheatEditor->setVisible(); };
stateManager.onActivate = [&] { ::stateManager->setVisible(); };
synchronize();
}
@ -106,14 +110,14 @@ void Presentation::bootstrap() {
auto iEmulator = new Emulator;
iEmulator->interface = emulator;
for(auto &schema : emulator->schema) {
for(auto &media : emulator->media) {
Item *item = new Item;
item->setText({schema.name, " ..."});
item->onActivate = [=, &schema] {
utility->loadSchema(iEmulator->interface, schema);
item->setText({media.name, " ..."});
item->onActivate = [=, &media] {
utility->loadMedia(iEmulator->interface, media);
};
if(schema.slot.size() == 0) loadListDirect.append(item);
if(schema.slot.size() >= 1) loadListSlotted.append(item);
if(media.type == "sys") loadListSystem.append(item);
if(media.type != "sys") loadListSubsystem.append(item);
}
iEmulator->menu.setText(emulator->information.name);

View File

@ -21,8 +21,8 @@ struct Presentation : Window {
vector<Emulator*> emulatorList;
Menu loadMenu;
vector<Item*> loadListDirect;
vector<Item*> loadListSlotted;
vector<Item*> loadListSystem;
vector<Item*> loadListSubsystem;
Menu settingsMenu;
Menu videoMenu;
RadioItem centerVideo;
@ -41,6 +41,8 @@ struct Presentation : Window {
Item loadStateItem[5];
Separator stateMenuSeparator;
Item resizeWindow;
Item cheatEditor;
Item stateManager;
void synchronize();
void setSystemName(const string &name);

View File

@ -97,11 +97,5 @@ int16_t Interface::inputPoll(unsigned port, unsigned device, unsigned input) {
}
void Interface::mediaRequest(Emulator::Interface::Media media) {
string pathname = browser->select({"Load ", media.name}, media.extension);
if(pathname.empty()) return;
string markup;
markup.readfile({pathname, "manifest.xml"});
mmapstream stream({pathname, media.path});
system().load(media.id, stream, markup);
utility->loadMedia(media);
}

View File

@ -6,23 +6,23 @@ HotkeySettings::HotkeySettings() : activeInput(nullptr) {
inputList.setHeaderText("Name", "Mapping");
inputList.setHeaderVisible();
clearButton.setText("Clear");
eraseButton.setText("Erase");
append(title, {~0, 0}, 5);
append(inputList, {~0, ~0}, 5);
append(controlLayout, {~0, 0});
controlLayout.append(spacer, {~0, 0});
controlLayout.append(clearButton, {80, 0});
controlLayout.append(eraseButton, {80, 0});
inputList.onChange = {&HotkeySettings::synchronize, this};
inputList.onActivate = {&HotkeySettings::assignInput, this};
clearButton.onActivate = {&HotkeySettings::clearInput, this};
eraseButton.onActivate = {&HotkeySettings::eraseInput, this};
refresh();
}
void HotkeySettings::synchronize() {
clearButton.setEnabled(inputList.selected());
eraseButton.setEnabled(inputList.selected());
}
void HotkeySettings::refresh() {
@ -37,7 +37,7 @@ void HotkeySettings::refresh() {
synchronize();
}
void HotkeySettings::clearInput() {
void HotkeySettings::eraseInput() {
activeInput = inputManager->hotkeyMap[inputList.selection()];
inputEvent(Scancode::None, 1);
}

View File

@ -3,11 +3,11 @@ struct HotkeySettings : SettingsLayout {
ListView inputList;
HorizontalLayout controlLayout;
Widget spacer;
Button clearButton;
Button eraseButton;
void synchronize();
void refresh();
void clearInput();
void eraseInput();
void assignInput();
void inputEvent(unsigned scancode, int16_t value);
HotkeySettings();

View File

@ -8,7 +8,8 @@ InputSettings::InputSettings() : activeInput(nullptr) {
focusAllow.setText("Allow Input");
inputList.setHeaderText("Name", "Mapping");
inputList.setHeaderVisible();
clearButton.setText("Clear");
resetButton.setText("Reset");
eraseButton.setText("Erase");
append(title, {~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[2], {100, 0}, 5);
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) {
systemList.append(emulator->information.name);
@ -45,7 +47,8 @@ InputSettings::InputSettings() : activeInput(nullptr) {
assign[0].onActivate = [&] { assignMouseInput(0); };
assign[1].onActivate = [&] { assignMouseInput(1); };
assign[2].onActivate = [&] { assignMouseInput(2); };
clearButton.onActivate = {&InputSettings::clearInput, this};
resetButton.onActivate = {&InputSettings::resetInput, this};
eraseButton.onActivate = {&InputSettings::eraseInput, this};
systemChanged();
}
@ -78,7 +81,7 @@ void InputSettings::synchronize() {
}
}
clearButton.setEnabled(inputList.selected());
eraseButton.setEnabled(inputList.selected());
}
Emulator::Interface& InputSettings::activeSystem() {
@ -89,7 +92,7 @@ Emulator::Interface::Port& InputSettings::activePort() {
return activeSystem().port[portList.selection()];
}
Emulator::Interface::Port::Device& InputSettings::activeDevice() {
Emulator::Interface::Device& InputSettings::activeDevice() {
return activePort().device[deviceList.selection()];
}
@ -123,7 +126,19 @@ void InputSettings::deviceChanged() {
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()];
auto &input = activeDevice().input[number];
activeInput = inputManager->inputMap[input.guid];

View File

@ -12,18 +12,20 @@ struct InputSettings : SettingsLayout {
HorizontalLayout controlLayout;
Button assign[3];
Widget spacer;
Button clearButton;
Button resetButton;
Button eraseButton;
void synchronize();
Emulator::Interface& activeSystem();
Emulator::Interface::Port& activePort();
Emulator::Interface::Port::Device& activeDevice();
Emulator::Interface::Device& activeDevice();
void systemChanged();
void portChanged();
void deviceChanged();
void clearInput();
void resetInput();
void eraseInput();
void assignInput();
void assignMouseInput(unsigned n);
void inputEvent(unsigned scancode, int16_t value, bool allowMouseInput = false);

View File

@ -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();
}

View File

@ -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;

View File

@ -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;
}

View File

@ -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;

View File

@ -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();
}

View File

@ -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;

View File

@ -0,0 +1,4 @@
#include "../ethos.hpp"
#include "cheat-database.cpp"
#include "cheat-editor.cpp"
#include "state-manager.cpp"

View File

@ -0,0 +1,3 @@
#include "cheat-database.hpp"
#include "cheat-editor.hpp"
#include "state-manager.hpp"

View File

@ -7,44 +7,69 @@ void Utility::setInterface(Emulator::Interface *emulator) {
presentation->synchronize();
}
void Utility::loadSchema(Emulator::Interface *emulator, Emulator::Interface::Schema &schema) {
string pathname = application->path({schema.name, ".", schema.extension, "/"});
if(!directory::exists(pathname)) pathname = browser->select({"Load ", schema.name}, schema.extension);
if(!directory::exists(pathname)) return;
loadMedia(emulator, schema, pathname);
void Utility::loadMedia(Emulator::Interface *emulator, Emulator::Interface::Media &media) {
string pathname = application->path({media.name, ".", media.type, "/"});
if(!file::exists({pathname, media.path})) pathname = browser->select({"Load ", media.name}, media.extension);
if(!file::exists({pathname, media.path})) return;
loadMedia(emulator, media, pathname);
}
void Utility::loadMedia(Emulator::Interface *emulator, Emulator::Interface::Media &media, const string &pathname) {
unload();
setInterface(emulator);
this->pathname = pathname;
path(system().group(media.id)) = pathname;
if(media.type == "sys") this->pathname.append(pathname);
string manifest;
manifest.readfile({pathname, "manifest.xml"});
auto memory = file::read({pathname, media.path});
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) {
string pathname = path(system().group(memory.id));
filestream fs({pathname, memory.name});
system().load(memory.id, fs);
}
system().updatePalette();
dspaudio.setFrequency(emulator->information.frequency);
string displayname = pathname;
displayname.rtrim<1>("/");
presentation->setTitle(notdir(nall::basename(displayname)));
presentation->setSystemName(media.name);
resize();
cheatEditor->load({pathname[0], "cheats.xml"});
stateManager->load({pathname[0], "bsnes/states.bsa"}, 1);
}
void Utility::saveMedia() {
void Utility::saveMemory() {
for(auto &memory : system().memory) {
string pathname = path(system().group(memory.id));
filestream fs({pathname, memory.name}, file::mode::write);
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) {
@ -62,12 +87,37 @@ void Utility::reset() {
system().reset();
}
void Utility::unload() {
if(application->active) {
saveMedia();
system().unload();
setInterface(nullptr);
void Utility::load() {
string title;
for(auto &path : pathname) {
string name = path;
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});
video.clear();
}
@ -76,14 +126,14 @@ void Utility::saveState(unsigned slot) {
if(application->active == nullptr) return;
serializer s = system().serialize();
if(s.size() == 0) return;
mkdir(string{pathname, "bsnes/"}, 0755);
if(file::write({pathname, "bsnes/state-", slot, ".bsa"}, s.data(), s.size()) == false);
mkdir(string{pathname[0], "bsnes/"}, 0755);
if(file::write({pathname[0], "bsnes/state-", slot, ".bsa"}, s.data(), s.size()) == false);
showMessage({"Save to slot ", slot});
}
void Utility::loadState(unsigned slot) {
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;
serializer s(memory.data(), memory.size());
if(system().unserialize(s) == false) return;

View File

@ -1,14 +1,15 @@
struct Utility {
string pathname;
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 saveMedia();
void loadMedia(Emulator::Interface::Media &media);
void loadMemory();
void saveMemory();
void connect(unsigned port, unsigned device);
void power();
void reset();
void load();
void unload();
void saveState(unsigned slot);
@ -24,6 +25,9 @@ struct Utility {
Utility();
lstring path;
lstring pathname;
private:
string statusText;
string statusMessage;