Update to v082r10 release.

byuu says:

Emulated the Game Genie for the NES and Game Boy, and wrote a new cheat
editor that doesn't reach into specific emulation cores so that it would
work.
Before you ask: yes, long-term I'd like Super Game Boy mode to accept
Game Boy codes. But that's not high on the priority list.
Renamed the mappers toward board names, LZ...->BandaiFCG,
LS161...->AOROM, added CNROM emulation (Adventure Island, Gradius, etc.)
Added the tools menu load/save state stuff, but note that the NES
doesn't have save state support yet (waiting for the interface to
stabilize a bit more first.)

Note: this will be the last release to have the ui-gameboy folder, it's
been deleted locally from my end, as the new multi-GUI does all that it
does and more now.
This commit is contained in:
Tim Allen 2011-09-15 22:23:13 +10:00
parent 7f4381b505
commit 278cf8462c
40 changed files with 795 additions and 50 deletions

View File

@ -1,6 +1,7 @@
gameboy_objects := gameboy-system gameboy-scheduler gameboy_objects := gameboy-system gameboy-scheduler
gameboy_objects += gameboy-memory gameboy-cartridge gameboy_objects += gameboy-memory gameboy-cartridge
gameboy_objects += gameboy-cpu gameboy-apu gameboy-lcd gameboy_objects += gameboy-cpu gameboy-apu gameboy-lcd
gameboy_objects += gameboy-cheat
objects += $(gameboy_objects) objects += $(gameboy_objects)
obj/gameboy-system.o: $(gameboy)/system/system.cpp $(call rwildcard,$(gameboy)/system/) obj/gameboy-system.o: $(gameboy)/system/system.cpp $(call rwildcard,$(gameboy)/system/)
@ -10,3 +11,4 @@ obj/gameboy-memory.o: $(gameboy)/memory/memory.cpp $(call rwildcard,$(gameboy)/m
obj/gameboy-cpu.o: $(gameboy)/cpu/cpu.cpp $(call rwildcard,$(gameboy)/cpu/) obj/gameboy-cpu.o: $(gameboy)/cpu/cpu.cpp $(call rwildcard,$(gameboy)/cpu/)
obj/gameboy-apu.o: $(gameboy)/apu/apu.cpp $(call rwildcard,$(gameboy)/apu/) obj/gameboy-apu.o: $(gameboy)/apu/apu.cpp $(call rwildcard,$(gameboy)/apu/)
obj/gameboy-lcd.o: $(gameboy)/lcd/lcd.cpp $(call rwildcard,$(gameboy)/lcd/) obj/gameboy-lcd.o: $(gameboy)/lcd/lcd.cpp $(call rwildcard,$(gameboy)/lcd/)
obj/gameboy-cheat.o: $(gameboy)/cheat/cheat.cpp $(call rwildcard,$(gameboy)/cheat/)

91
bsnes/gameboy/cheat/cheat.cpp Executable file
View File

@ -0,0 +1,91 @@
#include <gameboy/gameboy.hpp>
namespace GameBoy {
Cheat cheat;
bool Cheat::decode(const string &code, unsigned &addr, unsigned &data, unsigned &comp) {
static bool initialize = false;
static uint8 asciiMap[256];
if(initialize == false) {
initialize = true;
foreach(n, asciiMap) n = ~0;
asciiMap['0'] = 0; asciiMap['1'] = 1; asciiMap['2'] = 2; asciiMap['3'] = 3;
asciiMap['4'] = 4; asciiMap['5'] = 5; asciiMap['6'] = 6; asciiMap['7'] = 7;
asciiMap['8'] = 8; asciiMap['9'] = 9; asciiMap['A'] = 10; asciiMap['B'] = 11;
asciiMap['C'] = 12; asciiMap['D'] = 13; asciiMap['E'] = 14; asciiMap['F'] = 15;
}
unsigned length = code.length(), bits = 0;
for(unsigned n = 0; n < length; n++) if(asciiMap[code[n]] > 15 && code[n] != '-') return false;
if(code.wildcard("???" "-" "???")) {
string text = string(code).replace("-", "");
for(unsigned n = 0; n < 6; n++) bits |= asciiMap[text[n]] << (20 - n * 4);
addr = (bits >> 0) & 0xffff;
data = (bits >> 16) & 0xff;
comp = ~0;
addr = (((addr >> 4) | (addr << 12)) & 0xffff) ^ 0xf000;
return true;
}
if(code.wildcard("???" "-" "???" "-" "???")) {
string text = string(code).replace("-", "");
for(unsigned n = 0; n < 8; n++) bits |= asciiMap[text[n == 7 ? 8 : n]] << (28 - n * 4);
addr = (bits >> 8) & 0xffff;
data = (bits >> 24) & 0xff;
comp = (bits >> 0) & 0xff;
addr = (((addr >> 4) | (addr << 12)) & 0xffff) ^ 0xf000;
comp = (((comp >> 2) | (comp << 6)) & 0xff) ^ 0xba;
return true;
}
return false;
}
void Cheat::synchronize() {
foreach(n, override) n = false;
for(unsigned n = 0; n < size(); n++) {
const CheatCode &code = operator[](n);
for(unsigned n = 0; n < code.addr.size(); n++) {
override[code.addr[n]] = true;
}
}
}
bool CheatCode::operator=(const string &s) {
addr.reset(), data.reset(), comp.reset();
lstring list;
list.split("+", s);
for(unsigned n = 0; n < list.size(); n++) {
unsigned codeaddr, codedata, codecomp;
if(Cheat::decode(list[n], codeaddr, codedata, codecomp) == false) {
addr.reset(), data.reset(), comp.reset();
return false;
}
addr.append(codeaddr), data.append(codedata), comp.append(codecomp);
}
return true;
}
CheatCode::CheatCode(const string &s) {
operator=(s);
}
CheatCode::CheatCode() {
enable = false;
}
}

19
bsnes/gameboy/cheat/cheat.hpp Executable file
View File

@ -0,0 +1,19 @@
struct CheatCode {
bool enable;
array<unsigned> addr;
array<unsigned> data;
array<unsigned> comp;
bool operator=(const string&);
CheatCode(const string&);
CheatCode();
};
struct Cheat : public linear_vector<CheatCode> {
static bool decode(const string &code, unsigned &addr, unsigned &data, unsigned &comp);
void synchronize();
bool override[65536];
};
extern Cheat cheat;

View File

@ -99,6 +99,7 @@ namespace GameBoy {
#include <gameboy/cpu/cpu.hpp> #include <gameboy/cpu/cpu.hpp>
#include <gameboy/apu/apu.hpp> #include <gameboy/apu/apu.hpp>
#include <gameboy/lcd/lcd.hpp> #include <gameboy/lcd/lcd.hpp>
#include <gameboy/cheat/cheat.hpp>
}; };
#endif #endif

View File

@ -42,7 +42,22 @@ Memory::~Memory() {
// //
uint8 Bus::read(uint16 addr) { uint8 Bus::read(uint16 addr) {
return mmio[addr]->mmio_read(addr); uint8 data = mmio[addr]->mmio_read(addr);
if(cheat.override[addr]) {
for(unsigned x = 0; x < cheat.size(); x++) {
const CheatCode &code = cheat[x];
for(unsigned y = 0; y < code.addr.size(); y++) {
if(code.addr[y] == addr) {
if(code.comp[y] > 255 || code.comp[y] == data) {
return code.data[y];
}
}
}
}
}
return data;
} }
void Bus::write(uint16 addr, uint8 data) { void Bus::write(uint16 addr, uint8 data) {

View File

@ -49,6 +49,11 @@ void System::power() {
lcd.power(); lcd.power();
scheduler.init(); scheduler.init();
// cheat.reset();
// cheat.append(CheatCode("3EB-81B-4CA"));
// cheat[0].enable = true;
// cheat.synchronize();
clocks_executed = 0; clocks_executed = 0;
} }

View File

@ -1,6 +1,7 @@
nes_objects := nes-system nes-scheduler nes_objects := nes-system nes-scheduler
nes_objects += nes-mapper nes-cartridge nes-memory nes_objects += nes-mapper nes-cartridge nes-memory
nes_objects += nes-cpu nes-apu nes-ppu nes_objects += nes-cpu nes-apu nes-ppu
nes_objects += nes-cheat
objects += $(nes_objects) objects += $(nes_objects)
obj/nes-system.o: $(nes)/system/system.cpp $(call rwildcard,$(nes)/system/) obj/nes-system.o: $(nes)/system/system.cpp $(call rwildcard,$(nes)/system/)
@ -11,3 +12,4 @@ obj/nes-memory.o: $(nes)/memory/memory.cpp $(call rwildcard,$(nes)/memory/)
obj/nes-cpu.o: $(nes)/cpu/cpu.cpp $(call rwildcard,$(nes)/cpu/) obj/nes-cpu.o: $(nes)/cpu/cpu.cpp $(call rwildcard,$(nes)/cpu/)
obj/nes-apu.o: $(nes)/apu/apu.cpp $(call rwildcard,$(nes)/apu/) obj/nes-apu.o: $(nes)/apu/apu.cpp $(call rwildcard,$(nes)/apu/)
obj/nes-ppu.o: $(nes)/ppu/ppu.cpp $(call rwildcard,$(nes)/ppu/) obj/nes-ppu.o: $(nes)/ppu/ppu.cpp $(call rwildcard,$(nes)/ppu/)
obj/nes-cheat.o: $(nes)/cheat/cheat.cpp $(call rwildcard,$(nes)/cheat/)

View File

@ -31,8 +31,9 @@ void Cartridge::load(const string &xml, const uint8_t *data, unsigned size) {
switch(mapperNumber) { switch(mapperNumber) {
default : mapper = &Mapper::none; break; default : mapper = &Mapper::none; break;
case 1: mapper = &Mapper::mmc1; break; case 1: mapper = &Mapper::mmc1; break;
case 7: mapper = &Mapper::sn74hc161n; break; case 3: mapper = &Mapper::cnrom; break;
case 16: mapper = &Mapper::lz93d50; break; case 7: mapper = &Mapper::aorom; break;
case 16: mapper = &Mapper::bandaiFCG; break;
} }
loaded = true; loaded = true;

88
bsnes/nes/cheat/cheat.cpp Executable file
View File

@ -0,0 +1,88 @@
#include <nes/nes.hpp>
namespace NES {
Cheat cheat;
bool Cheat::decode(const string &code, unsigned &addr, unsigned &data, unsigned &comp) {
static bool initialize = false;
static uint8 asciiMap[256];
if(initialize == false) {
initialize = true;
foreach(n, asciiMap) n = ~0;
asciiMap['A'] = 0; asciiMap['P'] = 1; asciiMap['Z'] = 2; asciiMap['L'] = 3;
asciiMap['G'] = 4; asciiMap['I'] = 5; asciiMap['T'] = 6; asciiMap['Y'] = 7;
asciiMap['E'] = 8; asciiMap['O'] = 9; asciiMap['X'] = 10; asciiMap['U'] = 11;
asciiMap['K'] = 12; asciiMap['S'] = 13; asciiMap['V'] = 14; asciiMap['N'] = 15;
}
unsigned length = code.length(), bits = 0;
for(unsigned n = 0; n < length; n++) if(asciiMap[code[n]] > 15) return false;
if(code.length() == 6) {
for(unsigned n = 0; n < 6; n++) bits |= asciiMap[code[n]] << (20 - n * 4);
unsigned addrTable[] = { 10, 9, 8, 7, 2, 1, 0, 19, 14, 13, 12, 11, 6, 5, 4 };
unsigned dataTable[] = { 23, 18, 17, 16, 3, 22, 21, 20 };
addr = 0x8000, data = 0x00, comp = ~0;
for(unsigned n = 0; n < 15; n++) addr |= bits & (1 << addrTable[n]) ? 0x4000 >> n : 0;
for(unsigned n = 0; n < 8; n++) data |= bits & (1 << dataTable[n]) ? 0x80 >> n : 0;
return true;
}
if(code.length() == 8) {
for(unsigned n = 0; n < 8; n++) bits |= asciiMap[code[n]] << (28 - n * 4);
unsigned addrTable[] = { 18, 17, 16, 15, 10, 9, 8, 27, 22, 21, 20, 19, 14, 13, 12 };
unsigned dataTable[] = { 31, 26, 25, 24, 3, 30, 29, 28 };
unsigned compTable[] = { 7, 2, 1, 0, 11, 6, 5,4 };
addr = 0x8000, data = 0x00, comp = 0x00;
for(unsigned n = 0; n < 15; n++) addr |= bits & (1 << addrTable[n]) ? 0x4000 >> n : 0;
for(unsigned n = 0; n < 8; n++) data |= bits & (1 << dataTable[n]) ? 0x80 >> n : 0;
for(unsigned n = 0; n < 8; n++) comp |= bits & (1 << compTable[n]) ? 0x80 >> n : 0;
return true;
}
return false;
}
void Cheat::synchronize() {
foreach(n, override) n = false;
for(unsigned n = 0; n < size(); n++) {
const CheatCode &code = operator[](n);
for(unsigned n = 0; n < code.addr.size(); n++) {
override[code.addr[n]] = true;
}
}
}
bool CheatCode::operator=(const string &s) {
addr.reset(), data.reset(), comp.reset();
lstring list;
list.split("+", s);
for(unsigned n = 0; n < list.size(); n++) {
unsigned codeaddr, codedata, codecomp;
if(Cheat::decode(list[n], codeaddr, codedata, codecomp) == false) {
addr.reset(), data.reset(), comp.reset();
return false;
}
addr.append(codeaddr), data.append(codedata), comp.append(codecomp);
}
return true;
}
CheatCode::CheatCode(const string &s) {
operator=(s);
}
CheatCode::CheatCode() {
enable = false;
}
}

19
bsnes/nes/cheat/cheat.hpp Executable file
View File

@ -0,0 +1,19 @@
struct CheatCode {
bool enable;
array<unsigned> addr;
array<unsigned> data;
array<unsigned> comp;
bool operator=(const string&);
CheatCode(const string&);
CheatCode();
};
struct Cheat : public linear_vector<CheatCode> {
static bool decode(const string &code, unsigned &addr, unsigned &data, unsigned &comp);
void synchronize();
bool override[65536];
};
extern Cheat cheat;

View File

@ -1,10 +1,6 @@
//TODO: this naming nomenclature does not work. AOROM aorom;
//AOROM and CNROM use this same chip in different pin configurations.
//this emulates AOROM currently ...
SN74HC161N sn74hc161n; uint8 AOROM::prg_read(uint16 addr) {
uint8 SN74HC161N::prg_read(uint16 addr) {
if(addr & 0x8000) { if(addr & 0x8000) {
unsigned rom_addr = (prg_bank << 15) | (addr & 0x7fff); unsigned rom_addr = (prg_bank << 15) | (addr & 0x7fff);
return cartridge.prg_data[mirror(rom_addr, cartridge.prg_size)]; return cartridge.prg_data[mirror(rom_addr, cartridge.prg_size)];
@ -13,35 +9,35 @@ uint8 SN74HC161N::prg_read(uint16 addr) {
return cpu.mdr(); return cpu.mdr();
} }
void SN74HC161N::prg_write(uint16 addr, uint8 data) { void AOROM::prg_write(uint16 addr, uint8 data) {
if(addr & 0x8000) { if(addr & 0x8000) {
prg_bank = data & 0x0f; prg_bank = data & 0x0f;
mirror_select = data & 0x10; mirror_select = data & 0x10;
} }
} }
uint8 SN74HC161N::chr_read(uint16 addr) { uint8 AOROM::chr_read(uint16 addr) {
return cartridge.chr_data[mirror(addr, cartridge.chr_size)]; return cartridge.chr_data[mirror(addr, cartridge.chr_size)];
} }
void SN74HC161N::chr_write(uint16 addr, uint8 data) { void AOROM::chr_write(uint16 addr, uint8 data) {
if(cartridge.chr_ram == false) return; if(cartridge.chr_ram == false) return;
cartridge.chr_data[mirror(addr, cartridge.chr_size)] = data; cartridge.chr_data[mirror(addr, cartridge.chr_size)] = data;
} }
uint8 SN74HC161N::ciram_read(uint13 addr) { uint8 AOROM::ciram_read(uint13 addr) {
return ppu.ciram_read((mirror_select << 10) | (addr & 0x03ff)); return ppu.ciram_read((mirror_select << 10) | (addr & 0x03ff));
} }
void SN74HC161N::ciram_write(uint13 addr, uint8 data) { void AOROM::ciram_write(uint13 addr, uint8 data) {
return ppu.ciram_write((mirror_select << 10) | (addr & 0x03ff), data); return ppu.ciram_write((mirror_select << 10) | (addr & 0x03ff), data);
} }
void SN74HC161N::power() { void AOROM::power() {
reset(); reset();
} }
void SN74HC161N::reset() { void AOROM::reset() {
prg_bank = 0x0f; prg_bank = 0x0f;
mirror_select = 0; mirror_select = 0;
} }

View File

@ -1,4 +1,4 @@
struct SN74HC161N : Mapper { struct AOROM : Mapper {
uint8 prg_read(uint16 addr); uint8 prg_read(uint16 addr);
void prg_write(uint16 addr, uint8 data); void prg_write(uint16 addr, uint8 data);
@ -16,4 +16,4 @@ private:
bool mirror_select; bool mirror_select;
}; };
extern SN74HC161N sn74hc161n; extern AOROM aorom;

View File

@ -1,6 +1,6 @@
LZ93D50 lz93d50; BandaiFCG bandaiFCG;
uint8 LZ93D50::prg_read(uint16 addr) { uint8 BandaiFCG::prg_read(uint16 addr) {
clock(); clock();
if(addr >= 0x8000 && addr <= 0xbfff) { if(addr >= 0x8000 && addr <= 0xbfff) {
@ -16,7 +16,7 @@ uint8 LZ93D50::prg_read(uint16 addr) {
return cpu.mdr(); return cpu.mdr();
} }
void LZ93D50::prg_write(uint16 addr, uint8 data) { void BandaiFCG::prg_write(uint16 addr, uint8 data) {
clock(); clock();
if(addr >= 0x6000) { if(addr >= 0x6000) {
@ -49,32 +49,32 @@ void LZ93D50::prg_write(uint16 addr, uint8 data) {
} }
} }
uint8 LZ93D50::chr_read(uint16 addr) { uint8 BandaiFCG::chr_read(uint16 addr) {
unsigned rom_addr = (chr_bank[addr >> 10] << 10) | (addr & 0x03ff); unsigned rom_addr = (chr_bank[addr >> 10] << 10) | (addr & 0x03ff);
return cartridge.chr_data[mirror(rom_addr, cartridge.chr_size)]; return cartridge.chr_data[mirror(rom_addr, cartridge.chr_size)];
} }
void LZ93D50::chr_write(uint16 addr, uint8 data) { void BandaiFCG::chr_write(uint16 addr, uint8 data) {
if(cartridge.chr_ram == false) return; if(cartridge.chr_ram == false) return;
unsigned rom_addr = (chr_bank[addr >> 10] << 10) | (addr & 0x03ff); unsigned rom_addr = (chr_bank[addr >> 10] << 10) | (addr & 0x03ff);
cartridge.chr_data[mirror(rom_addr, cartridge.chr_size)] = data; cartridge.chr_data[mirror(rom_addr, cartridge.chr_size)] = data;
} }
uint8 LZ93D50::ciram_read(uint13 addr) { uint8 BandaiFCG::ciram_read(uint13 addr) {
addr = ciram_addr(addr); addr = ciram_addr(addr);
return ppu.ciram_read(addr); return ppu.ciram_read(addr);
} }
void LZ93D50::ciram_write(uint13 addr, uint8 data) { void BandaiFCG::ciram_write(uint13 addr, uint8 data) {
addr = ciram_addr(addr); addr = ciram_addr(addr);
return ppu.ciram_write(addr, data); return ppu.ciram_write(addr, data);
} }
void LZ93D50::power() { void BandaiFCG::power() {
reset(); reset();
} }
void LZ93D50::reset() { void BandaiFCG::reset() {
for(unsigned n = 0; n < 8; n++) chr_bank[n] = 0x00; for(unsigned n = 0; n < 8; n++) chr_bank[n] = 0x00;
prg_bank = 0x00; prg_bank = 0x00;
mirror_select = 0; mirror_select = 0;
@ -85,7 +85,7 @@ void LZ93D50::reset() {
// //
unsigned LZ93D50::ciram_addr(unsigned addr) const { unsigned BandaiFCG::ciram_addr(unsigned addr) const {
switch(mirror_select & 0x03) { switch(mirror_select & 0x03) {
case 0: return ((addr & 0x0400) >> 0) | (addr & 0x03ff); //vertical mirroring case 0: return ((addr & 0x0400) >> 0) | (addr & 0x03ff); //vertical mirroring
case 1: return ((addr & 0x0800) >> 1) | (addr & 0x03ff); //horizontal mirroring case 1: return ((addr & 0x0800) >> 1) | (addr & 0x03ff); //horizontal mirroring
@ -95,7 +95,7 @@ unsigned LZ93D50::ciram_addr(unsigned addr) const {
throw; throw;
} }
void LZ93D50::clock() { void BandaiFCG::clock() {
if(irq_counter_enable) { if(irq_counter_enable) {
if(--irq_counter == 0xffff) { if(--irq_counter == 0xffff) {
cpu.set_irq_line(1); cpu.set_irq_line(1);

View File

@ -1,4 +1,4 @@
struct LZ93D50 : Mapper { struct BandaiFCG : Mapper {
uint8 prg_read(uint16 addr); uint8 prg_read(uint16 addr);
void prg_write(uint16 addr, uint8 data); void prg_write(uint16 addr, uint8 data);
@ -23,4 +23,4 @@ private:
uint16 irq_latch; uint16 irq_latch;
}; };
extern LZ93D50 lz93d50; extern BandaiFCG bandaiFCG;

View File

@ -0,0 +1,42 @@
CNROM cnrom;
uint8 CNROM::prg_read(uint16 addr) {
if(addr & 0x8000) {
return cartridge.prg_data[mirror(addr & 0x7fff, cartridge.prg_size)];
}
return cpu.mdr();
}
void CNROM::prg_write(uint16 addr, uint8 data) {
if(addr & 0x8000) chr_bank = data & 0x03;
}
uint8 CNROM::chr_read(uint16 addr) {
unsigned chr_addr = (chr_bank * 0x2000) + (addr & 0x1fff);
return cartridge.chr_data[mirror(chr_addr, cartridge.chr_size)];
}
void CNROM::chr_write(uint16 addr, uint8 data) {
if(cartridge.chr_ram == false) return;
unsigned chr_addr = (chr_bank * 0x2000) + (addr & 0x1fff);
cartridge.chr_data[mirror(chr_addr, cartridge.chr_size)] = data;
}
uint8 CNROM::ciram_read(uint13 addr) {
if(cartridge.mirroring == 0) addr = ((addr & 0x0800) >> 1) | (addr & 0x03ff);
return ppu.ciram_read(addr & 0x07ff);
}
void CNROM::ciram_write(uint13 addr, uint8 data) {
if(cartridge.mirroring == 0) addr = ((addr & 0x0800) >> 1) | (addr & 0x03ff);
return ppu.ciram_write(addr & 0x07ff, data);
}
void CNROM::power() {
reset();
}
void CNROM::reset() {
chr_bank = 0;
}

View File

@ -0,0 +1,18 @@
struct CNROM : Mapper {
uint8 prg_read(uint16 addr);
void prg_write(uint16 addr, uint8 data);
uint8 chr_read(uint16 addr);
void chr_write(uint16 addr, uint8 data);
uint8 ciram_read(uint13 addr);
void ciram_write(uint13 addr, uint8 data);
void power();
void reset();
private:
uint2 chr_bank;
};
extern CNROM cnrom;

View File

@ -22,9 +22,10 @@ namespace Mapper {
} }
#include "none/none.cpp" #include "none/none.cpp"
#include "lz93d50/lz93d50.cpp" #include "aorom/aorom.cpp"
#include "bandai-fcg/bandai-fcg.cpp"
#include "cnrom/cnrom.cpp"
#include "mmc1/mmc1.cpp" #include "mmc1/mmc1.cpp"
#include "sn74hc161n/sn74hc161n.cpp"
} }
} }

View File

@ -16,7 +16,8 @@ namespace Mapper {
}; };
#include "none/none.hpp" #include "none/none.hpp"
#include "lz93d50/lz93d50.hpp" #include "aorom/aorom.hpp"
#include "bandai-fcg/bandai-fcg.hpp"
#include "cnrom/cnrom.hpp"
#include "mmc1/mmc1.hpp" #include "mmc1/mmc1.hpp"
#include "sn74hc161n/sn74hc161n.hpp"
} }

View File

@ -16,6 +16,20 @@ uint8 Bus::read(uint16 addr) {
if(addr <= 0x1fff) return cpu.ram_read(addr); if(addr <= 0x1fff) return cpu.ram_read(addr);
if(addr <= 0x3fff) return ppu.read(addr); if(addr <= 0x3fff) return ppu.read(addr);
if(addr <= 0x4017) return cpu.read(addr); if(addr <= 0x4017) return cpu.read(addr);
if(cheat.override[addr]) {
for(unsigned x = 0; x < cheat.size(); x++) {
const CheatCode &code = cheat[x];
for(unsigned y = 0; y < code.addr.size(); y++) {
if(code.addr[y] == addr) {
if(code.comp[y] > 255 || code.comp[y] == data) {
return code.data[y];
}
}
}
}
}
return data; return data;
} }

View File

@ -4,7 +4,7 @@
namespace NES { namespace NES {
namespace Info { namespace Info {
static const char Name[] = "bnes"; static const char Name[] = "bnes";
static const char Version[] = "000.05"; static const char Version[] = "000.06";
} }
} }
@ -107,6 +107,7 @@ namespace NES {
#include <nes/cpu/cpu.hpp> #include <nes/cpu/cpu.hpp>
#include <nes/apu/apu.hpp> #include <nes/apu/apu.hpp>
#include <nes/ppu/ppu.hpp> #include <nes/ppu/ppu.hpp>
#include <nes/cheat/cheat.hpp>
} }
#endif #endif

View File

@ -14,6 +14,11 @@ void System::power() {
apu.power(); apu.power();
ppu.power(); ppu.power();
scheduler.power(); scheduler.power();
// cheat.reset();
// cheat.append(CheatCode("GXXZZLVI"));
// cheat[0].enable = true;
// cheat.synchronize();
} }
void System::reset() { void System::reset() {

View File

@ -4,7 +4,7 @@
namespace SNES { namespace SNES {
namespace Info { namespace Info {
static const char Name[] = "bsnes"; static const char Name[] = "bsnes";
static const char Version[] = "082.09"; static const char Version[] = "082.10";
static const unsigned SerializerVersion = 21; static const unsigned SerializerVersion = 21;
} }
} }

View File

@ -3,7 +3,8 @@ include $(snes)/Makefile
include $(gameboy)/Makefile include $(gameboy)/Makefile
name := batch name := batch
ui_objects := ui-main ui-config ui-interface ui-utility ui-general ui_objects := ui-main ui-config ui-interface ui-utility
ui_objects += ui-general 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)
@ -71,6 +72,7 @@ obj/ui-config.o: $(ui)/config/config.cpp $(call rwildcard,$(ui)/)
obj/ui-interface.o: $(ui)/interface/interface.cpp $(call rwildcard,$(ui)/) obj/ui-interface.o: $(ui)/interface/interface.cpp $(call rwildcard,$(ui)/)
obj/ui-utility.o: $(ui)/utility/utility.cpp $(call rwildcard,$(ui)/) obj/ui-utility.o: $(ui)/utility/utility.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-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,$(rubydef) $(rubyflags)) $(call compile,$(rubydef) $(rubyflags))

View File

@ -23,6 +23,7 @@ using namespace ruby;
#include "interface/interface.hpp" #include "interface/interface.hpp"
#include "utility/utility.hpp" #include "utility/utility.hpp"
#include "general/general.hpp" #include "general/general.hpp"
#include "tools/tools.hpp"
struct Application { struct Application {
bool quit; bool quit;

View File

@ -32,7 +32,20 @@ MainWindow::MainWindow() {
settingsMuteAudio.setText("Mute Audio"); settingsMuteAudio.setText("Mute Audio");
toolsMenu.setText("Tools"); toolsMenu.setText("Tools");
toolsStateSave.setText("Save State");
toolsStateSave1.setText("Slot 1");
toolsStateSave2.setText("Slot 2");
toolsStateSave3.setText("Slot 3");
toolsStateSave4.setText("Slot 4");
toolsStateSave5.setText("Slot 5");
toolsStateLoad.setText("Load State");
toolsStateLoad1.setText("Slot 1");
toolsStateLoad2.setText("Slot 2");
toolsStateLoad3.setText("Slot 3");
toolsStateLoad4.setText("Slot 4");
toolsStateLoad5.setText("Slot 5");
toolsShrinkWindow.setText("Shrink Window"); toolsShrinkWindow.setText("Shrink Window");
toolsCheatEditor.setText("Cheat Editor ...");
toolsTest.setText("Test"); toolsTest.setText("Test");
helpMenu.setText("Help"); helpMenu.setText("Help");
@ -66,7 +79,21 @@ MainWindow::MainWindow() {
settingsMenu.append(settingsMuteAudio); settingsMenu.append(settingsMuteAudio);
append(toolsMenu); append(toolsMenu);
toolsMenu.append(toolsStateSave);
toolsStateSave.append(toolsStateSave1);
toolsStateSave.append(toolsStateSave2);
toolsStateSave.append(toolsStateSave3);
toolsStateSave.append(toolsStateSave4);
toolsStateSave.append(toolsStateSave5);
toolsMenu.append(toolsStateLoad);
toolsStateLoad.append(toolsStateLoad1);
toolsStateLoad.append(toolsStateLoad2);
toolsStateLoad.append(toolsStateLoad3);
toolsStateLoad.append(toolsStateLoad4);
toolsStateLoad.append(toolsStateLoad5);
toolsMenu.append(toolsSeparator);
toolsMenu.append(toolsShrinkWindow); toolsMenu.append(toolsShrinkWindow);
toolsMenu.append(toolsCheatEditor);
toolsMenu.append(toolsTest); toolsMenu.append(toolsTest);
append(helpMenu); append(helpMenu);
@ -87,32 +114,32 @@ MainWindow::MainWindow() {
cartridgeLoadNES.onTick = [&] { cartridgeLoadNES.onTick = [&] {
fileBrowser->open("Load NES Cartridge", { "*.nes" }, [](string filename) { fileBrowser->open("Load NES Cartridge", { "*.nes" }, [](string filename) {
interface->loadCartridgeNES(filename); interface->loadCartridge(filename);
}); });
}; };
cartridgeLoadSNES.onTick = [&] { cartridgeLoadSNES.onTick = [&] {
fileBrowser->open("Load SNES Cartridge", { "*.sfc" }, [](string filename) { fileBrowser->open("Load SNES Cartridge", { "*.sfc" }, [](string filename) {
interface->loadCartridgeSNES(filename); interface->loadCartridge(filename);
}); });
}; };
cartridgeLoadGameBoy.onTick = [&] { cartridgeLoadGameBoy.onTick = [&] {
fileBrowser->open("Load Game Boy Cartridge", { "*.gb", "*.gbc" }, [](string filename) { fileBrowser->open("Load Game Boy Cartridge", { "*.gb", "*.gbc" }, [](string filename) {
interface->loadCartridgeGameBoy(filename); interface->loadCartridge(filename);
}); });
}; };
nesPower.onTick = { &Interface::power, interface }; nesPower.onTick = { &Interface::power, interface };
nesReset.onTick = { &Interface::reset, interface }; nesReset.onTick = { &Interface::reset, interface };
nesCartridgeUnload.onTick = { &Interface::unloadCartridgeNES, interface }; nesCartridgeUnload.onTick = { &Interface::unloadCartridge, interface };
snesPower.onTick = { &Interface::power, interface }; snesPower.onTick = { &Interface::power, interface };
snesReset.onTick = { &Interface::reset, interface }; snesReset.onTick = { &Interface::reset, interface };
snesCartridgeUnload.onTick = { &Interface::unloadCartridgeSNES, interface }; snesCartridgeUnload.onTick = { &Interface::unloadCartridge, interface };
gameBoyPower.onTick = { &Interface::power, interface }; gameBoyPower.onTick = { &Interface::power, interface };
gameBoyCartridgeUnload.onTick = { &Interface::unloadCartridgeGameBoy, interface }; gameBoyCartridgeUnload.onTick = { &Interface::unloadCartridge, interface };
settingsSynchronizeVideo.onTick = [&] { settingsSynchronizeVideo.onTick = [&] {
video.set(Video::Synchronize, settingsSynchronizeVideo.checked()); video.set(Video::Synchronize, settingsSynchronizeVideo.checked());
@ -126,8 +153,22 @@ MainWindow::MainWindow() {
dspaudio.setVolume(settingsMuteAudio.checked() ? 0.0 : 1.0); dspaudio.setVolume(settingsMuteAudio.checked() ? 0.0 : 1.0);
}; };
toolsStateSave1.onTick = [&] { interface->saveState({ interface->baseName, "-1.bst" }); };
toolsStateSave2.onTick = [&] { interface->saveState({ interface->baseName, "-2.bst" }); };
toolsStateSave3.onTick = [&] { interface->saveState({ interface->baseName, "-3.bst" }); };
toolsStateSave4.onTick = [&] { interface->saveState({ interface->baseName, "-4.bst" }); };
toolsStateSave5.onTick = [&] { interface->saveState({ interface->baseName, "-5.bst" }); };
toolsStateLoad1.onTick = [&] { interface->loadState({ interface->baseName, "-1.bst" }); };
toolsStateLoad2.onTick = [&] { interface->loadState({ interface->baseName, "-2.bst" }); };
toolsStateLoad3.onTick = [&] { interface->loadState({ interface->baseName, "-3.bst" }); };
toolsStateLoad4.onTick = [&] { interface->loadState({ interface->baseName, "-4.bst" }); };
toolsStateLoad5.onTick = [&] { interface->loadState({ interface->baseName, "-5.bst" }); };
toolsShrinkWindow.onTick = [&] { utility->resizeMainWindow(true); }; toolsShrinkWindow.onTick = [&] { utility->resizeMainWindow(true); };
toolsCheatEditor.onTick = [&] { cheatEditor->setVisible(); };
toolsTest.onTick = [&] { toolsTest.onTick = [&] {
NES::cpu.trace = toolsTest.checked(); NES::cpu.trace = toolsTest.checked();
}; };
@ -139,4 +180,16 @@ MainWindow::MainWindow() {
"Website: http://byuu.org/" "Website: http://byuu.org/"
}); });
}; };
synchronize();
}
void MainWindow::synchronize() {
if(interface->loaded()) {
toolsStateSave.setEnabled(true);
toolsStateLoad.setEnabled(true);
} else {
toolsStateSave.setEnabled(false);
toolsStateLoad.setEnabled(false);
}
} }

View File

@ -30,12 +30,27 @@ struct MainWindow : Window {
CheckItem settingsMuteAudio; CheckItem settingsMuteAudio;
Menu toolsMenu; Menu toolsMenu;
Menu toolsStateSave;
Item toolsStateSave1;
Item toolsStateSave2;
Item toolsStateSave3;
Item toolsStateSave4;
Item toolsStateSave5;
Menu toolsStateLoad;
Item toolsStateLoad1;
Item toolsStateLoad2;
Item toolsStateLoad3;
Item toolsStateLoad4;
Item toolsStateLoad5;
Separator toolsSeparator;
Item toolsShrinkWindow; Item toolsShrinkWindow;
Item toolsCheatEditor;
CheckItem toolsTest; CheckItem toolsTest;
Menu helpMenu; Menu helpMenu;
Item helpAbout; Item helpAbout;
void synchronize();
MainWindow(); MainWindow();
}; };

View File

@ -17,6 +17,30 @@ void InterfaceGameBoy::unloadCartridge() {
interface->baseName = ""; interface->baseName = "";
} }
bool InterfaceGameBoy::saveState(const string &filename) {
GameBoy::system.runtosave();
serializer s = GameBoy::system.serialize();
return file::write(filename, s.data(), s.size());
}
bool InterfaceGameBoy::loadState(const string &filename) {
uint8_t *data;
unsigned size;
if(file::read(filename, data, size) == false) return false;
serializer s(data, size);
delete[] data;
return GameBoy::system.unserialize(s);
}
void InterfaceGameBoy::setCheatCodes(const lstring &list) {
GameBoy::cheat.reset();
for(unsigned n = 0; n < list.size(); n++) {
GameBoy::cheat[n] = list[n];
GameBoy::cheat[n].enable = true;
}
GameBoy::cheat.synchronize();
}
// //
void InterfaceGameBoy::video_refresh(const uint8_t *data) { void InterfaceGameBoy::video_refresh(const uint8_t *data) {

View File

@ -2,6 +2,10 @@ struct InterfaceGameBoy : GameBoy::Interface {
bool loadCartridge(const string &filename); bool loadCartridge(const string &filename);
void unloadCartridge(); void unloadCartridge();
bool saveState(const string &filename);
bool loadState(const string &filename);
void setCheatCodes(const lstring &list);
void video_refresh(const uint8_t *data); void video_refresh(const uint8_t *data);
void audio_sample(int16_t csample, int16_t lsample, int16_t rsample); void audio_sample(int16_t csample, int16_t lsample, int16_t rsample);
bool input_poll(unsigned id); bool input_poll(unsigned id);

View File

@ -14,11 +14,14 @@ bool Interface::loaded() {
} }
bool Interface::loadCartridge(const string &filename) { bool Interface::loadCartridge(const string &filename) {
if(filename.endswith(".nes")) return loadCartridgeNES(filename); bool result = false;
if(filename.endswith(".sfc")) return loadCartridgeSNES(filename); unloadCartridge();
if(filename.endswith(".gb" )) return loadCartridgeGameBoy(filename); if(filename.endswith(".nes")) result = loadCartridgeNES(filename);
if(filename.endswith(".gbc")) return loadCartridgeGameBoy(filename); if(filename.endswith(".sfc")) result = loadCartridgeSNES(filename);
return true; if(filename.endswith(".gb" )) result = loadCartridgeGameBoy(filename);
if(filename.endswith(".gbc")) result = loadCartridgeGameBoy(filename);
if(result == true) cheatEditor->load({ baseName, ".cht" });
return result;
} }
bool Interface::loadCartridgeNES(const string &filename) { bool Interface::loadCartridgeNES(const string &filename) {
@ -27,6 +30,19 @@ bool Interface::loadCartridgeNES(const string &filename) {
return true; return true;
} }
void Interface::unloadCartridge() {
if(loaded() == false) return;
cheatEditor->save({ baseName, ".cht" });
switch(mode()) {
case Mode::NES: nes.unloadCartridge(); break;
case Mode::SNES: snes.unloadCartridge(); break;
case Mode::GameBoy: gameBoy.unloadCartridge(); break;
}
utility->setMode(mode = Mode::None);
}
void Interface::unloadCartridgeNES() { void Interface::unloadCartridgeNES() {
nes.unloadCartridge(); nes.unloadCartridge();
utility->setMode(mode = Mode::None); utility->setMode(mode = Mode::None);
@ -86,6 +102,30 @@ void Interface::run() {
} }
} }
bool Interface::saveState(const string &filename) {
switch(mode()) {
case Mode::SNES: return snes.saveState(filename);
case Mode::GameBoy: return gameBoy.saveState(filename);
}
return false;
}
bool Interface::loadState(const string &filename) {
switch(mode()) {
case Mode::SNES: return snes.loadState(filename);
case Mode::GameBoy: return gameBoy.loadState(filename);
}
return false;
}
void Interface::setCheatCodes(const lstring &list) {
switch(mode()) {
case Mode::NES: return nes.setCheatCodes(list);
case Mode::SNES: return snes.setCheatCodes(list);
case Mode::GameBoy: return gameBoy.setCheatCodes(list);
}
}
Interface::Interface() { Interface::Interface() {
mode = Mode::None; mode = Mode::None;
NES::system.init(&nes); NES::system.init(&nes);

View File

@ -13,6 +13,7 @@ struct Interface : property<Interface> {
bool loadCartridgeSNES(const string &filename); bool loadCartridgeSNES(const string &filename);
bool loadCartridgeGameBoy(const string &filename); bool loadCartridgeGameBoy(const string &filename);
void unloadCartridge();
void unloadCartridgeNES(); void unloadCartridgeNES();
void unloadCartridgeSNES(); void unloadCartridgeSNES();
void unloadCartridgeGameBoy(); void unloadCartridgeGameBoy();
@ -21,6 +22,10 @@ struct Interface : property<Interface> {
void reset(); void reset();
void run(); void run();
bool saveState(const string &filename);
bool loadState(const string &filename);
void setCheatCodes(const lstring &list);
Interface(); Interface();
int16_t inputState[Scancode::Limit]; int16_t inputState[Scancode::Limit];

View File

@ -15,6 +15,15 @@ void InterfaceNES::unloadCartridge() {
interface->baseName = ""; interface->baseName = "";
} }
void InterfaceNES::setCheatCodes(const lstring &list) {
NES::cheat.reset();
for(unsigned n = 0; n < list.size(); n++) {
NES::cheat[n] = list[n];
NES::cheat[n].enable = true;
}
NES::cheat.synchronize();
}
// //
void InterfaceNES::video_refresh(const uint32_t *data) { void InterfaceNES::video_refresh(const uint32_t *data) {

View File

@ -2,6 +2,8 @@ struct InterfaceNES : NES::Interface {
bool loadCartridge(const string &filename); bool loadCartridge(const string &filename);
void unloadCartridge(); void unloadCartridge();
void setCheatCodes(const lstring &list);
void video_refresh(const uint32_t *data); void video_refresh(const uint32_t *data);
void audio_sample(int16_t sample); void audio_sample(int16_t sample);
int16_t input_poll(bool port, unsigned device, unsigned id); int16_t input_poll(bool port, unsigned device, unsigned id);

View File

@ -18,6 +18,30 @@ void InterfaceSNES::unloadCartridge() {
interface->baseName = ""; interface->baseName = "";
} }
bool InterfaceSNES::saveState(const string &filename) {
SNES::system.runtosave();
serializer s = SNES::system.serialize();
return file::write(filename, s.data(), s.size());
}
bool InterfaceSNES::loadState(const string &filename) {
uint8_t *data;
unsigned size;
if(file::read(filename, data, size) == false) return false;
serializer s(data, size);
delete[] data;
return SNES::system.unserialize(s);
}
void InterfaceSNES::setCheatCodes(const lstring &list) {
SNES::cheat.reset();
for(unsigned n = 0; n < list.size(); n++) {
SNES::cheat[n] = list[n];
SNES::cheat[n].enabled = true;
}
SNES::cheat.synchronize();
}
// //
void InterfaceSNES::video_refresh(const uint16_t *data, bool hires, bool interlace, bool overscan) { void InterfaceSNES::video_refresh(const uint16_t *data, bool hires, bool interlace, bool overscan) {
@ -87,5 +111,5 @@ int16_t InterfaceSNES::input_poll(bool port, SNES::Input::Device device, unsigne
} }
string InterfaceSNES::path(SNES::Cartridge::Slot slot, const string &hint) { string InterfaceSNES::path(SNES::Cartridge::Slot slot, const string &hint) {
return "/home/byuu/Desktop/test"; return dir(interface->baseName);
} }

View File

@ -2,6 +2,10 @@ struct InterfaceSNES : SNES::Interface {
bool loadCartridge(const string &filename); bool loadCartridge(const string &filename);
void unloadCartridge(); void unloadCartridge();
bool saveState(const string &filename);
bool loadState(const string &filename);
void setCheatCodes(const lstring &list);
void video_refresh(const uint16_t *data, bool hires, bool interlace, bool overscan); void video_refresh(const uint16_t *data, bool hires, bool interlace, bool overscan);
void audio_sample(int16_t lsample, int16_t rsample); void audio_sample(int16_t lsample, int16_t rsample);
int16_t input_poll(bool port, SNES::Input::Device device, unsigned index, unsigned id); int16_t input_poll(bool port, SNES::Input::Device device, unsigned index, unsigned id);

View File

@ -41,6 +41,7 @@ Application::Application(int argc, char **argv) : quit(false) {
mainWindow = new MainWindow; mainWindow = new MainWindow;
fileBrowser = new FileBrowser; fileBrowser = new FileBrowser;
cheatEditor = new CheatEditor;
utility->setMode(Interface::Mode::None); utility->setMode(Interface::Mode::None);
mainWindow->setVisible(); mainWindow->setVisible();
@ -73,9 +74,12 @@ Application::Application(int argc, char **argv) : quit(false) {
OS::processEvents(); OS::processEvents();
Application::run(); Application::run();
} }
interface->unloadCartridge();
} }
Application::~Application() { Application::~Application() {
delete cheatEditor;
delete fileBrowser; delete fileBrowser;
delete mainWindow; delete mainWindow;
delete utility; delete utility;

197
bsnes/ui/tools/cheat-editor.cpp Executable file
View File

@ -0,0 +1,197 @@
CheatEditor *cheatEditor = 0;
CheatEditor::CheatEditor() {
setTitle("Cheat Editor");
setGeometry({ 128, 128, 600, 360 });
cheatList.setHeaderText("Slot", "Code", "Description");
cheatList.setHeaderVisible();
cheatList.setCheckable();
codeLabel.setText("Code(s):");
descLabel.setText("Description:");
findButton.setText("Find Codes ...");
findButton.setEnabled(false);
clearAllButton.setText("Clear All");
clearButton.setText("Clear");
append(layout);
layout.setMargin(5);
layout.append(cheatList, ~0, ~0, 5);
layout.append(codeLayout, ~0, 0, 5);
codeLayout.append(codeLabel, 80, 0);
codeLayout.append(codeEdit, ~0, 0);
layout.append(descLayout, ~0, 0, 5);
descLayout.append(descLabel, 80, 0);
descLayout.append(descEdit, ~0, 0);
layout.append(controlLayout, ~0, 0);
controlLayout.append(findButton, 100, 0);
controlLayout.append(spacer, ~0, 0);
controlLayout.append(clearAllButton, 80, 0, 5);
controlLayout.append(clearButton, 80, 0);
for(unsigned n = 0; n < 128; n++) cheatList.append("", "", "");
updateUI();
synchronize();
cheatList.onChange = { &CheatEditor::synchronize, this };
cheatList.onTick = [&](unsigned) { updateInterface(); };
codeEdit.onChange = { &CheatEditor::updateCode, this };
descEdit.onChange = { &CheatEditor::updateDesc, this };
clearAllButton.onTick = { &CheatEditor::clearAll, this };
clearButton.onTick = { &CheatEditor::clearSelected, this };
}
void CheatEditor::synchronize() {
layout.setEnabled(interface->loaded());
if(cheatList.selected()) {
unsigned n = cheatList.selection();
codeEdit.setText(cheatText[n][Code]);
descEdit.setText(cheatText[n][Desc]);
codeEdit.setEnabled(true);
descEdit.setEnabled(true);
clearButton.setEnabled(true);
} else {
codeEdit.setText("");
codeEdit.setEnabled(false);
descEdit.setText("");
descEdit.setEnabled(false);
clearButton.setEnabled(false);
}
}
void CheatEditor::updateUI() {
for(unsigned n = 0; n < 128; n++) {
string code = cheatText[n][Code];
string description = cheatText[n][Code] == "" && cheatText[n][Desc] == "" ? "(empty)" : cheatText[n][Desc];
lstring codes;
codes.split("+", code);
cheatList.modify(n, decimal<3>(n + 1), codes.size() == 1 ? code : string{ codes[0], "+..." }, description);
}
cheatList.autoSizeColumns();
}
void CheatEditor::updateInterface() {
lstring cheatCodes;
for(unsigned n = 0; n < 128; n++) {
string code = cheatText[n][Code];
if(cheatList.checked(n) && code != "") cheatCodes.append(code.replace(" ", ""));
}
interface->setCheatCodes(cheatCodes);
}
void CheatEditor::updateCode() {
unsigned n = cheatList.selection();
cheatText[n][Code] = codeEdit.text();
updateUI(), updateInterface();
}
void CheatEditor::updateDesc() {
unsigned n = cheatList.selection();
cheatText[n][Desc] = descEdit.text();
updateUI(), updateInterface();
}
void CheatEditor::clearAll() {
if(MessageWindow::question(*this, "Warning: all cheat codes will be permanently erased!\nAre you sure?")
== MessageWindow::Response::Yes) {
reset();
}
}
void CheatEditor::clearSelected() {
unsigned n = cheatList.selection();
cheatList.setChecked(n, false);
cheatText[n][Code] = "";
cheatText[n][Desc] = "";
codeEdit.setText("");
descEdit.setText("");
updateUI(), updateInterface();
}
void CheatEditor::reset() {
synchronize();
for(unsigned n = 0; n < 128; n++) {
cheatList.setChecked(n, false);
cheatText[n][Code] = "";
cheatText[n][Desc] = "";
}
codeEdit.setText("");
descEdit.setText("");
updateUI(), updateInterface();
}
bool CheatEditor::load(const string &filename) {
synchronize();
string data;
if(data.readfile(filename) == false) return false;
unsigned n = 0;
xml_element document = xml_parse(data);
foreach(head, document.element) {
if(head.name == "cartridge") {
foreach(node, head.element) {
if(node.name == "cheat") {
bool enable = false;
string description;
string code;
foreach(attribute, node.attribute) {
if(attribute.name == "enabled") enable = (attribute.parse() == "true");
}
foreach(element, node.element) {
if(element.name == "description") description = element.parse();
else if(element.name == "code") code.append(element.parse(), "+");
}
code.rtrim<1>("+");
cheatList.setChecked(n, enable);
cheatText[n][Code] = code;
cheatText[n][Desc] = description;
if(++n >= 128) break;
}
}
}
}
updateUI(), updateInterface();
return true;
}
bool CheatEditor::save(const string &filename) {
synchronize();
signed lastSave = -1;
for(signed n = 127; n >= 0; n--) {
if(cheatText[n][Code] != "" || cheatText[n][Desc] != "") {
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>\n");
for(unsigned n = 0; n <= lastSave; n++) {
fp.print(" <cheat enabled=\"", cheatList.checked(n) ? "true" : "false", "\">\n");
fp.print(" <description>", cheatText[n][Desc], "</description>\n");
lstring list;
list.split("+", cheatText[n][Code]);
foreach(code, list) {
fp.print(" <code>", code, "</code>\n");
}
fp.print(" </cheat>\n");
}
fp.print("</cartridge>\n");
fp.close();
return true;
}

35
bsnes/ui/tools/cheat-editor.hpp Executable file
View File

@ -0,0 +1,35 @@
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 clearAllButton;
Button clearButton;
void synchronize();
void updateUI();
void updateInterface();
void updateCode();
void updateDesc();
void clearAll();
void clearSelected();
void reset();
bool load(const string &filename);
bool save(const string &filename);
CheatEditor();
private:
enum : unsigned { Code = 0, Desc = 1 };
string cheatText[128][2];
};
extern CheatEditor *cheatEditor;

2
bsnes/ui/tools/tools.cpp Executable file
View File

@ -0,0 +1,2 @@
#include "../base.hpp"
#include "cheat-editor.cpp"

1
bsnes/ui/tools/tools.hpp Executable file
View File

@ -0,0 +1 @@
#include "cheat-editor.hpp"

View File

@ -12,6 +12,7 @@ void Utility::setMode(Interface::Mode mode) {
if(mode == Interface::Mode::None) { if(mode == Interface::Mode::None) {
mainWindow->setTitle(application->title); mainWindow->setTitle(application->title);
mainWindow->setStatusText("No cartridge loaded"); mainWindow->setStatusText("No cartridge loaded");
cheatEditor->reset();
} }
else if(mode == Interface::Mode::NES) { else if(mode == Interface::Mode::NES) {
@ -32,6 +33,7 @@ void Utility::setMode(Interface::Mode mode) {
dspaudio.setFrequency(4194304.0); dspaudio.setFrequency(4194304.0);
} }
mainWindow->synchronize();
resizeMainWindow(); resizeMainWindow();
} }