mirror of https://github.com/bsnes-emu/bsnes.git
Update to v082r04 release.
byuu says: So, here's the deal. I now have three emulators. I don't think the NES/GB ones are at all useful, but I do want them to be eventually. And having them have those pathetic little GUIs like ui-gameboy, and keeping everything in separate project folders, just doesn't work well for me. I kind of "got around" the issue with the Game Boy, by only allowing SGB mode emulation. But there is no "Super Nintendo" ... er ... wait ... uhmm ... well, you know what I mean anyway. So, my idea is to write a multi-emulator GUI, and keep the projects together. The GUI is not going to change much. The way I envision this working: At startup, you have a menubar with: "Cartridge, Settings, Tools, Help". Cartridge has "Load NES Cartridge", "Load SNES Cartridge", etc. When you load something, Cartridge is replaced with the appropriate system menu, eg "SNES". Here you have all your regular items: "power, reset, controller port selection, etc." There is also a new "Unload Cartridge" option, which is how you restore the "Cartridge" menu again. I have no plans to emulate any other systems, but if I ever do emulate something that doesn't take cartridges, I'll change the name to just "Load" or something. The cheat editor / state manager will look and act exactly the same. The settings panel will look exactly the same. I'll simply show/hide system-specific options as needed, like NES/SNES aspect ratio correction, etc. The input mapping window will just have settings for the currently loaded system. Video and audio tweaking will apply cross-system, as will hotkey mapping. The GUI stuff is mostly copy-paste, so it should only take me a week to get it 95% back to where it was, so don't worry, this isn't total GUI rewrite #80. I am, however, making all the objects pointers, so that I can destruct them all prior to main() returning, which is certainly one way of fixing that annoying Windows/Qt crash. Please only test on Linux. The Windows port is broken to hell, and will give you a bad impression of the idea: - menu groups are not hiding for some reason (all groups are showing, it looks hideous) - Timer interval(0) is taking 16ms per call, capping the FPS to ~64 tops [FWIW, bsnes/accuracy gets 130fps, bgameboy gets 450fps, bnes gets 800fps; all run at lowest possible granularity] - the OS keeps beeping when you press keys (AGAIN) Of course, Qt and GTK+ don't let you shrink a window from the requested geometry size, because they suck. So the video scaling stuff doesn't work all that great yet. Man, a metric fuckton of things need to be fixed in phoenix, and I really don't know how to fix any of them :/
This commit is contained in:
parent
496708cffe
commit
ec7e4087fb
|
@ -1,9 +1,10 @@
|
|||
include nall/Makefile
|
||||
|
||||
nes := nes
|
||||
snes := snes
|
||||
gameboy := gameboy
|
||||
profile := accuracy
|
||||
ui := ui-gameboy
|
||||
ui := ui
|
||||
|
||||
# options += console
|
||||
# options += debugger
|
||||
|
@ -74,6 +75,6 @@ clean:
|
|||
-@$(call delete,*.manifest)
|
||||
|
||||
archive-all:
|
||||
tar -cjf bsnes.tar.bz2 data gameboy libco nall obj out phoenix ruby snes ui ui-gameboy ui-libsnes Makefile cc.bat clean.bat sync.sh
|
||||
tar -cjf bsnes.tar.bz2 data gameboy libco nall nes obj out phoenix ruby snes ui ui-gameboy ui-libsnes ui-snes Makefile cc.bat clean.bat sync.sh
|
||||
|
||||
help:;
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
//bgameboy
|
||||
//author: byuu
|
||||
//project started: 2010-12-27
|
||||
#ifndef GAMEBOY_HPP
|
||||
#define GAMEBOY_HPP
|
||||
|
||||
namespace GameBoy {
|
||||
namespace Info {
|
||||
|
@ -10,6 +9,13 @@ namespace GameBoy {
|
|||
}
|
||||
}
|
||||
|
||||
/*
|
||||
bgameboy - Game Boy emulator
|
||||
author: byuu
|
||||
license: GPLv2
|
||||
project started: 2010-12-27
|
||||
*/
|
||||
|
||||
#include <libco/libco.h>
|
||||
|
||||
#include <nall/foreach.hpp>
|
||||
|
@ -94,3 +100,5 @@ namespace GameBoy {
|
|||
#include <gameboy/apu/apu.hpp>
|
||||
#include <gameboy/lcd/lcd.hpp>
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
#define PLATFORM_WIN
|
||||
#elif defined(__APPLE__)
|
||||
#define PLATFORM_OSX
|
||||
#elif defined(linux) || defined(__sun__) || defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__)
|
||||
#elif defined(linux) || defined(__sun__) || defined(__FreeBSD__) || defined(__FreeBSD_kernel__) || defined(__NetBSD__) || defined(__OpenBSD__)
|
||||
#define PLATFORM_X
|
||||
#endif
|
||||
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
nes_objects := nes-system nes-scheduler
|
||||
nes_objects += nes-cartridge nes-memory
|
||||
nes_objects += nes-cpu nes-ppu
|
||||
objects += $(nes_objects)
|
||||
|
||||
obj/nes-system.o: $(nes)/system/system.cpp $(call rwildcard,$(nes)/system/)
|
||||
obj/nes-scheduler.o: $(nes)/scheduler/scheduler.cpp $(call rwildcard,$(nes)/scheduler/)
|
||||
obj/nes-cartridge.o: $(nes)/cartridge/cartridge.cpp $(call rwildcard,$(nes)/cartridge/)
|
||||
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-ppu.o: $(nes)/ppu/ppu.cpp $(call rwildcard,$(nes)/ppu/)
|
|
@ -0,0 +1,60 @@
|
|||
#include <nes/nes.hpp>
|
||||
|
||||
namespace NES {
|
||||
|
||||
Cartridge cartridge;
|
||||
|
||||
void Cartridge::load(const string &xml, const uint8_t *data, unsigned size) {
|
||||
prg_size = data[4] * 0x4000;
|
||||
chr_size = data[5] * 0x2000;
|
||||
|
||||
prg_data = new uint8[prg_size];
|
||||
memcpy(prg_data, data + 16, prg_size);
|
||||
|
||||
if(chr_size) {
|
||||
chr_ram = false;
|
||||
chr_data = new uint8[chr_size];
|
||||
memcpy(chr_data, data + 16 + prg_size, chr_size);
|
||||
} else {
|
||||
chr_ram = true;
|
||||
chr_size = 0x2000;
|
||||
chr_data = new uint8[chr_size]();
|
||||
}
|
||||
|
||||
loaded = true;
|
||||
}
|
||||
|
||||
void Cartridge::unload() {
|
||||
if(loaded == false) return;
|
||||
|
||||
delete[] prg_data;
|
||||
delete[] chr_data;
|
||||
|
||||
loaded = false;
|
||||
}
|
||||
|
||||
Cartridge::Cartridge() {
|
||||
loaded = false;
|
||||
}
|
||||
|
||||
uint8 Cartridge::prg_read(uint16 addr) {
|
||||
if(addr >= 0x8000 && addr <= 0xffff) {
|
||||
addr &= 0x7fff;
|
||||
if(addr >= prg_size) addr &= prg_size - 1;
|
||||
return prg_data[addr];
|
||||
}
|
||||
}
|
||||
|
||||
void Cartridge::prg_write(uint16 addr, uint8 data) {
|
||||
}
|
||||
|
||||
uint8 Cartridge::chr_read(uint16 addr) {
|
||||
return chr_data[addr & 0x1fff];
|
||||
}
|
||||
|
||||
void Cartridge::chr_write(uint16 addr, uint8 data) {
|
||||
if(chr_ram == false) return;
|
||||
chr_data[addr & 0x1fff] = data;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
struct Cartridge : property<Cartridge> {
|
||||
void load(const string &xml, const uint8_t *data, unsigned size);
|
||||
void unload();
|
||||
|
||||
readonly<bool> loaded;
|
||||
|
||||
Cartridge();
|
||||
|
||||
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 *prg_data;
|
||||
unsigned prg_size;
|
||||
|
||||
uint8 *chr_data;
|
||||
unsigned chr_size;
|
||||
|
||||
bool chr_ram;
|
||||
};
|
||||
|
||||
extern Cartridge cartridge;
|
|
@ -0,0 +1,3 @@
|
|||
#include "opcodes.cpp"
|
||||
#include "exec.cpp"
|
||||
#include "disassembler.cpp"
|
|
@ -0,0 +1,102 @@
|
|||
struct Flags {
|
||||
bool n, v, p, b, d, i, z, c;
|
||||
|
||||
inline operator unsigned() {
|
||||
return (n << 7) | (v << 6) | (p << 5) | (b << 4)
|
||||
| (d << 3) | (i << 2) | (z << 1) | (c << 0);
|
||||
}
|
||||
|
||||
inline Flags& operator=(uint8 data) {
|
||||
n = data & 0x80; v = data & 0x40; p = data & 0x20; b = data & 0x10;
|
||||
d = data & 0x08; i = data & 0x04; z = data & 0x02; c = data & 0x01;
|
||||
return *this;
|
||||
}
|
||||
};
|
||||
|
||||
struct Registers {
|
||||
uint16 pc;
|
||||
uint8 a, x, y, s;
|
||||
Flags p;
|
||||
} regs;
|
||||
|
||||
struct Register16 {
|
||||
union {
|
||||
uint16 w;
|
||||
struct { uint8 order_lsb2(l, h); };
|
||||
};
|
||||
} abs, iabs;
|
||||
|
||||
uint8 rd;
|
||||
uint8 zp;
|
||||
uint16 aa;
|
||||
|
||||
//opcodes.cpp
|
||||
void opf_asl();
|
||||
void opf_adc();
|
||||
void opf_and();
|
||||
void opf_bit();
|
||||
void opf_cmp();
|
||||
void opf_cpx();
|
||||
void opf_cpy();
|
||||
void opf_dec();
|
||||
void opf_eor();
|
||||
void opf_inc();
|
||||
void opf_lda();
|
||||
void opf_ldx();
|
||||
void opf_ldy();
|
||||
void opf_lsr();
|
||||
void opf_ora();
|
||||
void opf_rla();
|
||||
void opf_rol();
|
||||
void opf_ror();
|
||||
void opf_rra();
|
||||
void opf_sbc();
|
||||
void opf_sla();
|
||||
void opf_sra();
|
||||
|
||||
void opi_branch(bool condition);
|
||||
void opi_clear_flag(bool &flag);
|
||||
void opi_decrement(uint8 &r);
|
||||
void opi_increment(uint8 &r);
|
||||
void opi_pull(uint8 &r);
|
||||
void opi_push(uint8 &r);
|
||||
template<void (CPU::*op)()> void opi_read_absolute();
|
||||
template<void (CPU::*op)()> void opi_read_absolute_x();
|
||||
template<void (CPU::*op)()> void opi_read_absolute_y();
|
||||
template<void (CPU::*op)()> void opi_read_immediate();
|
||||
template<void (CPU::*op)()> void opi_read_indirect_zero_page_x();
|
||||
template<void (CPU::*op)()> void opi_read_indirect_zero_page_y();
|
||||
template<void (CPU::*op)()> void opi_read_zero_page();
|
||||
template<void (CPU::*op)()> void opi_read_zero_page_x();
|
||||
template<void (CPU::*op)()> void opi_read_zero_page_y();
|
||||
template<void (CPU::*op)()> void opi_rmw_absolute();
|
||||
template<void (CPU::*op)()> void opi_rmw_absolute_x();
|
||||
template<void (CPU::*op)()> void opi_rmw_zero_page();
|
||||
template<void (CPU::*op)()> void opi_rmw_zero_page_x();
|
||||
void opi_set_flag(bool &flag);
|
||||
template<void (CPU::*op)()> void opi_shift();
|
||||
void opi_store_absolute(uint8 &r);
|
||||
void opi_store_absolute_x(uint8 &r);
|
||||
void opi_store_absolute_y(uint8 &r);
|
||||
void opi_store_indirect_zero_page_x(uint8 &r);
|
||||
void opi_store_indirect_zero_page_y(uint8 &r);
|
||||
void opi_store_zero_page(uint8 &r);
|
||||
void opi_store_zero_page_x(uint8 &r);
|
||||
void opi_store_zero_page_y(uint8 &r);
|
||||
void opi_transfer(uint8 &s, uint8 &d, bool flag);
|
||||
|
||||
void op_brk();
|
||||
void op_jmp_absolute();
|
||||
void op_jmp_indirect_absolute();
|
||||
void op_jsr_absolute();
|
||||
void op_nop();
|
||||
void op_php();
|
||||
void op_plp();
|
||||
void op_rti();
|
||||
void op_rts();
|
||||
|
||||
//exec.cpp
|
||||
void op_exec();
|
||||
|
||||
//disassembler.cpp
|
||||
string disassemble();
|
|
@ -0,0 +1,193 @@
|
|||
string CPU::disassemble() {
|
||||
string output = { hex<4>(regs.pc), " " };
|
||||
|
||||
auto abs = [&]() -> string { return { "$", hex<2>(bus.read(regs.pc + 2)), hex<2>(bus.read(regs.pc + 1)) }; };
|
||||
auto abx = [&]() -> string { return { "$", hex<2>(bus.read(regs.pc + 2)), hex<2>(bus.read(regs.pc + 1)), ",x" }; };
|
||||
auto aby = [&]() -> string { return { "$", hex<2>(bus.read(regs.pc + 2)), hex<2>(bus.read(regs.pc + 1)), ",y" }; };
|
||||
auto iab = [&]() -> string { return { "($", hex<2>(bus.read(regs.pc + 2)), hex<2>(bus.read(regs.pc + 1)), ")" }; };
|
||||
auto imm = [&]() -> string { return { "#$", hex<2>(bus.read(regs.pc + 1)) }; };
|
||||
auto imp = [&]() -> string { return ""; };
|
||||
auto izx = [&]() -> string { return { "($", hex<2>(bus.read(regs.pc + 1)), ",x)" }; };
|
||||
auto izy = [&]() -> string { return { "($", hex<2>(bus.read(regs.pc + 1)), "),y" }; };
|
||||
auto rel = [&]() -> string { return { "$", hex<4>((regs.pc + 2) + (int8)bus.read(regs.pc + 1)) }; };
|
||||
auto zpg = [&]() -> string { return { "$", hex<2>(bus.read(regs.pc + 1)) }; };
|
||||
auto zpx = [&]() -> string { return { "$", hex<2>(bus.read(regs.pc + 1)), ",x" }; };
|
||||
auto zpy = [&]() -> string { return { "$", hex<2>(bus.read(regs.pc + 1)), ",y" }; };
|
||||
|
||||
#define op(byte, prefix, mode) \
|
||||
case byte: output.append(#prefix, " ", mode()); \
|
||||
break
|
||||
|
||||
uint8 opcode = bus.read(regs.pc);
|
||||
switch(opcode) {
|
||||
op(0x00, brk, imm);
|
||||
op(0x01, ora, izx);
|
||||
op(0x05, ora, zpg);
|
||||
op(0x06, asl, zpg);
|
||||
op(0x08, php, imp);
|
||||
op(0x09, ora, imm);
|
||||
op(0x0a, asl, imp);
|
||||
op(0x0d, ora, abs);
|
||||
op(0x0e, asl, abs);
|
||||
op(0x10, bpl, rel);
|
||||
op(0x11, ora, izy);
|
||||
op(0x15, ora, zpx);
|
||||
op(0x16, asl, zpx);
|
||||
op(0x18, clc, imp);
|
||||
op(0x19, ora, aby);
|
||||
op(0x1d, ora, abx);
|
||||
op(0x1e, asl, abx);
|
||||
op(0x20, jsr, abs);
|
||||
op(0x21, and, izx);
|
||||
op(0x24, bit, zpg);
|
||||
op(0x25, and, zpg);
|
||||
op(0x26, rol, zpg);
|
||||
op(0x28, plp, imp);
|
||||
op(0x29, and, imm);
|
||||
op(0x2a, rol, imp);
|
||||
op(0x2c, bit, abs);
|
||||
op(0x2d, and, abs);
|
||||
op(0x2e, rol, abs);
|
||||
op(0x30, bmi, rel);
|
||||
op(0x31, and, izy);
|
||||
op(0x35, and, zpx);
|
||||
op(0x36, rol, zpx);
|
||||
op(0x38, sec, imp);
|
||||
op(0x39, and, aby);
|
||||
op(0x3d, and, abx);
|
||||
op(0x3e, rol, abx);
|
||||
op(0x40, rti, imp);
|
||||
op(0x41, eor, izx);
|
||||
op(0x45, eor, zpg);
|
||||
op(0x46, lsr, zpg);
|
||||
op(0x48, pha, imp);
|
||||
op(0x49, eor, imm);
|
||||
op(0x4a, lsr, imp);
|
||||
op(0x4c, jmp, abs);
|
||||
op(0x4d, eor, abs);
|
||||
op(0x4e, lsr, abs);
|
||||
op(0x50, bvc, rel);
|
||||
op(0x51, eor, izy);
|
||||
op(0x55, eor, zpx);
|
||||
op(0x56, lsr, zpx);
|
||||
op(0x58, cli, imp);
|
||||
op(0x59, eor, aby);
|
||||
op(0x5a, phy, imp);
|
||||
op(0x5d, eor, abx);
|
||||
op(0x5e, lsr, abx);
|
||||
op(0x60, rts, imp);
|
||||
op(0x61, adc, izx);
|
||||
op(0x65, adc, zpg);
|
||||
op(0x66, ror, zpg);
|
||||
op(0x68, pla, imp);
|
||||
op(0x69, adc, imm);
|
||||
op(0x6a, ror, imp);
|
||||
op(0x6c, jmp, iab);
|
||||
op(0x6d, adc, abs);
|
||||
op(0x6e, ror, abs);
|
||||
op(0x70, bvs, rel);
|
||||
op(0x71, adc, izy);
|
||||
op(0x75, adc, zpx);
|
||||
op(0x76, ror, zpx);
|
||||
op(0x78, sei, imp);
|
||||
op(0x79, adc, aby);
|
||||
op(0x7a, ply, imp);
|
||||
op(0x7d, adc, abx);
|
||||
op(0x7e, ror, abx);
|
||||
op(0x81, sta, izx);
|
||||
op(0x84, sty, zpg);
|
||||
op(0x85, sta, zpg);
|
||||
op(0x86, stx, zpg);
|
||||
op(0x88, dey, imp);
|
||||
op(0x8a, txa, imp);
|
||||
op(0x8c, sty, abs);
|
||||
op(0x8d, sta, abs);
|
||||
op(0x8e, stx, abs);
|
||||
op(0x90, bcc, rel);
|
||||
op(0x91, sta, izy);
|
||||
op(0x94, sty, zpx);
|
||||
op(0x95, sta, zpx);
|
||||
op(0x96, stx, zpy);
|
||||
op(0x98, tya, imp);
|
||||
op(0x99, sta, aby);
|
||||
op(0x9a, txs, imp);
|
||||
op(0x9d, sta, abx);
|
||||
op(0xa0, ldy, imm);
|
||||
op(0xa1, lda, izx);
|
||||
op(0xa2, ldx, imm);
|
||||
op(0xa4, ldy, zpg);
|
||||
op(0xa5, lda, zpg);
|
||||
op(0xa6, ldx, zpg);
|
||||
op(0xa8, tay, imp);
|
||||
op(0xa9, lda, imm);
|
||||
op(0xaa, tax, imp);
|
||||
op(0xac, ldy, abs);
|
||||
op(0xad, lda, abs);
|
||||
op(0xae, ldx, abs);
|
||||
op(0xb0, bcs, rel);
|
||||
op(0xb1, lda, izy);
|
||||
op(0xb4, ldy, zpx);
|
||||
op(0xb5, lda, zpx);
|
||||
op(0xb6, ldx, zpy);
|
||||
op(0xb8, clv, imp);
|
||||
op(0xb9, lda, aby);
|
||||
op(0xba, tsx, imp);
|
||||
op(0xbc, ldy, abx);
|
||||
op(0xbd, lda, abx);
|
||||
op(0xbe, ldx, aby);
|
||||
op(0xc0, cpy, imm);
|
||||
op(0xc1, cmp, izx);
|
||||
op(0xc4, cpy, zpg);
|
||||
op(0xc5, cmp, zpg);
|
||||
op(0xc6, dec, zpg);
|
||||
op(0xc8, iny, imp);
|
||||
op(0xc9, cmp, imm);
|
||||
op(0xca, dex, imp);
|
||||
op(0xcc, cpy, abs);
|
||||
op(0xcd, cmp, abs);
|
||||
op(0xce, dec, abs);
|
||||
op(0xd0, bne, rel);
|
||||
op(0xd1, cmp, izy);
|
||||
op(0xd5, cmp, zpx);
|
||||
op(0xd6, dec, zpx);
|
||||
op(0xd8, cld, imp);
|
||||
op(0xd9, cmp, aby);
|
||||
op(0xda, phx, imp);
|
||||
op(0xdd, cmp, abx);
|
||||
op(0xde, dec, abx);
|
||||
op(0xe0, cpx, imm);
|
||||
op(0xe1, sbc, izx);
|
||||
op(0xe4, cpx, zpg);
|
||||
op(0xe5, sbc, zpg);
|
||||
op(0xe6, inc, zpg);
|
||||
op(0xe8, inx, imp);
|
||||
op(0xe9, sbc, imm);
|
||||
op(0xec, cpx, abs);
|
||||
op(0xed, sbc, abs);
|
||||
op(0xee, inc, abs);
|
||||
op(0xf0, beq, rel);
|
||||
op(0xf1, sbc, izy);
|
||||
op(0xf5, sbc, zpx);
|
||||
op(0xf6, inc, zpx);
|
||||
op(0xf8, sed, imp);
|
||||
op(0xf9, sbc, aby);
|
||||
op(0xfa, plx, imp);
|
||||
op(0xfd, sbc, abx);
|
||||
op(0xfe, inc, abx);
|
||||
|
||||
default: output.append("$", hex<2>(opcode)); break;
|
||||
}
|
||||
|
||||
#undef op
|
||||
|
||||
output.append(" ");
|
||||
output[20] = 0;
|
||||
|
||||
output.append(
|
||||
"A:", hex<2>(regs.a), " X:", hex<2>(regs.x), " Y:", hex<2>(regs.y), " S:", hex<2>(regs.s), " ",
|
||||
regs.p.n ? "N" : "n", regs.p.v ? "V" : "v", regs.p.p ? "P" : "p", regs.p.b ? "B" : "b",
|
||||
regs.p.d ? "D" : "d", regs.p.i ? "I" : "i", regs.p.z ? "Z" : "z", regs.p.c ? "C" : "c"
|
||||
);
|
||||
|
||||
return output;
|
||||
}
|
|
@ -0,0 +1,167 @@
|
|||
void CPU::op_exec() {
|
||||
uint8 opcode = op_readpci();
|
||||
switch(opcode) {
|
||||
case 0x00: return op_brk();
|
||||
case 0x01: return opi_read_indirect_zero_page_x<&CPU::opf_ora>();
|
||||
case 0x05: return opi_read_zero_page<&CPU::opf_ora>();
|
||||
case 0x06: return opi_rmw_zero_page<&CPU::opf_asl>();
|
||||
case 0x08: return op_php();
|
||||
case 0x09: return opi_read_immediate<&CPU::opf_ora>();
|
||||
case 0x0a: return opi_shift<&CPU::opf_sla>();
|
||||
case 0x0d: return opi_read_absolute<&CPU::opf_ora>();
|
||||
case 0x0e: return opi_rmw_absolute<&CPU::opf_asl>();
|
||||
case 0x10: return opi_branch(regs.p.n == 0);
|
||||
case 0x11: return opi_read_indirect_zero_page_y<&CPU::opf_ora>();
|
||||
case 0x15: return opi_read_zero_page_x<&CPU::opf_ora>();
|
||||
case 0x16: return opi_rmw_zero_page_x<&CPU::opf_asl>();
|
||||
case 0x18: return opi_clear_flag(regs.p.c);
|
||||
case 0x19: return opi_read_absolute_y<&CPU::opf_ora>();
|
||||
case 0x1d: return opi_read_absolute_x<&CPU::opf_ora>();
|
||||
case 0x1e: return opi_rmw_absolute_x<&CPU::opf_asl>();
|
||||
case 0x20: return op_jsr_absolute();
|
||||
case 0x21: return opi_read_indirect_zero_page_x<&CPU::opf_and>();
|
||||
case 0x24: return opi_read_zero_page<&CPU::opf_bit>();
|
||||
case 0x25: return opi_read_zero_page<&CPU::opf_and>();
|
||||
case 0x26: return opi_rmw_zero_page<&CPU::opf_rol>();
|
||||
case 0x28: return op_plp();
|
||||
case 0x29: return opi_read_immediate<&CPU::opf_and>();
|
||||
case 0x2a: return opi_shift<&CPU::opf_rla>();
|
||||
case 0x2c: return opi_read_absolute<&CPU::opf_bit>();
|
||||
case 0x2d: return opi_read_absolute<&CPU::opf_and>();
|
||||
case 0x2e: return opi_rmw_absolute<&CPU::opf_rol>();
|
||||
case 0x30: return opi_branch(regs.p.n == 1);
|
||||
case 0x31: return opi_read_indirect_zero_page_y<&CPU::opf_and>();
|
||||
case 0x35: return opi_read_zero_page_x<&CPU::opf_and>();
|
||||
case 0x36: return opi_rmw_zero_page_x<&CPU::opf_rol>();
|
||||
case 0x38: return opi_set_flag(regs.p.c);
|
||||
case 0x39: return opi_read_absolute_y<&CPU::opf_and>();
|
||||
case 0x3d: return opi_read_absolute_x<&CPU::opf_and>();
|
||||
case 0x3e: return opi_rmw_absolute_x<&CPU::opf_rol>();
|
||||
case 0x40: return op_rti();
|
||||
case 0x41: return opi_read_indirect_zero_page_x<&CPU::opf_eor>();
|
||||
case 0x45: return opi_read_zero_page<&CPU::opf_eor>();
|
||||
case 0x46: return opi_rmw_zero_page<&CPU::opf_lsr>();
|
||||
case 0x48: return opi_push(regs.a);
|
||||
case 0x49: return opi_read_immediate<&CPU::opf_eor>();
|
||||
case 0x4a: return opi_shift<&CPU::opf_sra>();
|
||||
case 0x4c: return op_jmp_absolute();
|
||||
case 0x4d: return opi_read_absolute<&CPU::opf_eor>();
|
||||
case 0x4e: return opi_rmw_absolute<&CPU::opf_lsr>();
|
||||
case 0x50: return opi_branch(regs.p.v == 0);
|
||||
case 0x51: return opi_read_indirect_zero_page_y<&CPU::opf_eor>();
|
||||
case 0x55: return opi_read_zero_page_x<&CPU::opf_eor>();
|
||||
case 0x56: return opi_rmw_zero_page_x<&CPU::opf_lsr>();
|
||||
case 0x58: return opi_clear_flag(regs.p.i);
|
||||
case 0x59: return opi_read_absolute_y<&CPU::opf_eor>();
|
||||
case 0x5a: return opi_push(regs.y);
|
||||
case 0x5d: return opi_read_absolute_x<&CPU::opf_eor>();
|
||||
case 0x5e: return opi_rmw_absolute_x<&CPU::opf_lsr>();
|
||||
case 0x60: return op_rts();
|
||||
case 0x61: return opi_read_indirect_zero_page_x<&CPU::opf_adc>();
|
||||
case 0x65: return opi_read_zero_page<&CPU::opf_adc>();
|
||||
case 0x66: return opi_rmw_zero_page<&CPU::opf_ror>();
|
||||
case 0x68: return opi_pull(regs.a);
|
||||
case 0x69: return opi_read_immediate<&CPU::opf_adc>();
|
||||
case 0x6a: return opi_shift<&CPU::opf_rra>();
|
||||
case 0x6c: return op_jmp_indirect_absolute();
|
||||
case 0x6d: return opi_read_absolute<&CPU::opf_adc>();
|
||||
case 0x6e: return opi_rmw_absolute<&CPU::opf_ror>();
|
||||
case 0x70: return opi_branch(regs.p.v == 1);
|
||||
case 0x71: return opi_read_indirect_zero_page_y<&CPU::opf_adc>();
|
||||
case 0x75: return opi_read_zero_page_x<&CPU::opf_adc>();
|
||||
case 0x76: return opi_rmw_zero_page_x<&CPU::opf_ror>();
|
||||
case 0x78: return opi_set_flag(regs.p.i);
|
||||
case 0x79: return opi_read_absolute_y<&CPU::opf_adc>();
|
||||
case 0x7a: return opi_pull(regs.y);
|
||||
case 0x7d: return opi_read_absolute_x<&CPU::opf_adc>();
|
||||
case 0x7e: return opi_rmw_absolute_x<&CPU::opf_ror>();
|
||||
case 0x81: return opi_store_indirect_zero_page_x(regs.a);
|
||||
case 0x84: return opi_store_zero_page(regs.y);
|
||||
case 0x85: return opi_store_zero_page(regs.a);
|
||||
case 0x86: return opi_store_zero_page(regs.x);
|
||||
case 0x88: return opi_decrement(regs.y);
|
||||
case 0x8a: return opi_transfer(regs.x, regs.a, 1);
|
||||
case 0x8c: return opi_store_absolute(regs.y);
|
||||
case 0x8d: return opi_store_absolute(regs.a);
|
||||
case 0x8e: return opi_store_absolute(regs.x);
|
||||
case 0x90: return opi_branch(regs.p.c == 0);
|
||||
case 0x91: return opi_store_indirect_zero_page_y(regs.a);
|
||||
case 0x94: return opi_store_zero_page_x(regs.y);
|
||||
case 0x95: return opi_store_zero_page_x(regs.a);
|
||||
case 0x96: return opi_store_zero_page_y(regs.x);
|
||||
case 0x98: return opi_transfer(regs.y, regs.a, 1);
|
||||
case 0x99: return opi_store_absolute_y(regs.a);
|
||||
case 0x9a: return opi_transfer(regs.x, regs.s, 0);
|
||||
case 0x9d: return opi_store_absolute_x(regs.a);
|
||||
case 0xa0: return opi_read_immediate<&CPU::opf_ldy>();
|
||||
case 0xa1: return opi_read_indirect_zero_page_x<&CPU::opf_lda>();
|
||||
case 0xa2: return opi_read_immediate<&CPU::opf_ldx>();
|
||||
case 0xa4: return opi_read_zero_page<&CPU::opf_ldy>();
|
||||
case 0xa5: return opi_read_zero_page<&CPU::opf_lda>();
|
||||
case 0xa6: return opi_read_zero_page<&CPU::opf_ldx>();
|
||||
case 0xa8: return opi_transfer(regs.a, regs.y, 1);
|
||||
case 0xa9: return opi_read_immediate<&CPU::opf_lda>();
|
||||
case 0xaa: return opi_transfer(regs.a, regs.x, 1);
|
||||
case 0xac: return opi_read_absolute<&CPU::opf_ldy>();
|
||||
case 0xad: return opi_read_absolute<&CPU::opf_lda>();
|
||||
case 0xae: return opi_read_absolute<&CPU::opf_ldx>();
|
||||
case 0xb0: return opi_branch(regs.p.c == 1);
|
||||
case 0xb1: return opi_read_indirect_zero_page_y<&CPU::opf_lda>();
|
||||
case 0xb4: return opi_read_zero_page_x<&CPU::opf_ldy>();
|
||||
case 0xb5: return opi_read_zero_page_x<&CPU::opf_lda>();
|
||||
case 0xb6: return opi_read_zero_page_y<&CPU::opf_ldx>();
|
||||
case 0xb8: return opi_clear_flag(regs.p.v);
|
||||
case 0xb9: return opi_read_absolute_y<&CPU::opf_lda>();
|
||||
case 0xba: return opi_transfer(regs.s, regs.x, 1);
|
||||
case 0xbc: return opi_read_absolute_x<&CPU::opf_ldy>();
|
||||
case 0xbd: return opi_read_absolute_x<&CPU::opf_lda>();
|
||||
case 0xbe: return opi_read_absolute_y<&CPU::opf_ldx>();
|
||||
case 0xc0: return opi_read_immediate<&CPU::opf_cpy>();
|
||||
case 0xc1: return opi_read_indirect_zero_page_x<&CPU::opf_cmp>();
|
||||
case 0xc4: return opi_read_zero_page<&CPU::opf_cpy>();
|
||||
case 0xc5: return opi_read_zero_page<&CPU::opf_cmp>();
|
||||
case 0xc6: return opi_rmw_zero_page<&CPU::opf_dec>();
|
||||
case 0xc8: return opi_increment(regs.y);
|
||||
case 0xc9: return opi_read_immediate<&CPU::opf_cmp>();
|
||||
case 0xca: return opi_decrement(regs.x);
|
||||
case 0xcc: return opi_read_absolute<&CPU::opf_cpy>();
|
||||
case 0xcd: return opi_read_absolute<&CPU::opf_cmp>();
|
||||
case 0xce: return opi_rmw_absolute<&CPU::opf_dec>();
|
||||
case 0xd0: return opi_branch(regs.p.z == 0);
|
||||
case 0xd1: return opi_read_indirect_zero_page_y<&CPU::opf_cmp>();
|
||||
case 0xd5: return opi_read_zero_page_x<&CPU::opf_cmp>();
|
||||
case 0xd6: return opi_rmw_zero_page_x<&CPU::opf_dec>();
|
||||
case 0xd8: return opi_clear_flag(regs.p.d);
|
||||
case 0xd9: return opi_read_absolute_y<&CPU::opf_cmp>();
|
||||
case 0xda: return opi_push(regs.x);
|
||||
case 0xdd: return opi_read_absolute_x<&CPU::opf_cmp>();
|
||||
case 0xde: return opi_rmw_absolute_x<&CPU::opf_dec>();
|
||||
case 0xe0: return opi_read_immediate<&CPU::opf_cpx>();
|
||||
case 0xe1: return opi_read_indirect_zero_page_x<&CPU::opf_sbc>();
|
||||
case 0xe4: return opi_read_zero_page<&CPU::opf_cpx>();
|
||||
case 0xe5: return opi_read_zero_page<&CPU::opf_sbc>();
|
||||
case 0xe6: return opi_rmw_zero_page<&CPU::opf_inc>();
|
||||
case 0xe8: return opi_increment(regs.x);
|
||||
case 0xe9: return opi_read_immediate<&CPU::opf_sbc>();
|
||||
case 0xea: return op_nop();
|
||||
case 0xec: return opi_read_absolute<&CPU::opf_cpx>();
|
||||
case 0xed: return opi_read_absolute<&CPU::opf_sbc>();
|
||||
case 0xee: return opi_rmw_absolute<&CPU::opf_inc>();
|
||||
case 0xf0: return opi_branch(regs.p.z == 1);
|
||||
case 0xf1: return opi_read_indirect_zero_page_y<&CPU::opf_sbc>();
|
||||
case 0xf5: return opi_read_zero_page_x<&CPU::opf_sbc>();
|
||||
case 0xf6: return opi_rmw_zero_page_x<&CPU::opf_inc>();
|
||||
case 0xf8: return opi_set_flag(regs.p.d);
|
||||
case 0xf9: return opi_read_absolute_y<&CPU::opf_sbc>();
|
||||
case 0xfa: return opi_pull(regs.x);
|
||||
case 0xfd: return opi_read_absolute_x<&CPU::opf_sbc>();
|
||||
case 0xfe: return opi_rmw_absolute_x<&CPU::opf_inc>();
|
||||
}
|
||||
|
||||
return op_nop();
|
||||
|
||||
regs.pc--;
|
||||
print("Unimplemented opcode: ", hex<4>(regs.pc), " = ", hex<2>(bus.read(regs.pc)), "\n");
|
||||
print("Counter = ", opcodeCounter, "\n");
|
||||
while(true) scheduler.exit();
|
||||
}
|
|
@ -0,0 +1,447 @@
|
|||
#define call(op) (this->*op)()
|
||||
#define L
|
||||
|
||||
//opcode functions
|
||||
//================
|
||||
|
||||
void CPU::opf_adc() {
|
||||
signed result = regs.a + rd + regs.p.c;
|
||||
regs.p.v = ~(regs.a ^ rd) & (regs.a ^ result) & 0x80;
|
||||
regs.p.c = (result > 0xff);
|
||||
regs.p.n = (result & 0x80);
|
||||
regs.p.z = ((uint8)result == 0);
|
||||
regs.a = result;
|
||||
}
|
||||
|
||||
void CPU::opf_and() {
|
||||
regs.a &= rd;
|
||||
regs.p.n = (regs.a & 0x80);
|
||||
regs.p.z = (regs.a == 0);
|
||||
}
|
||||
|
||||
void CPU::opf_asl() {
|
||||
regs.p.c = rd & 0x80;
|
||||
rd <<= 1;
|
||||
regs.p.n = (rd & 0x80);
|
||||
regs.p.z = (rd == 0);
|
||||
}
|
||||
|
||||
void CPU::opf_bit() {
|
||||
regs.p.n = (rd & 0x80);
|
||||
regs.p.v = (rd & 0x40);
|
||||
regs.p.z = ((rd & regs.a) == 0);
|
||||
}
|
||||
|
||||
void CPU::opf_cmp() {
|
||||
signed r = regs.a - rd;
|
||||
regs.p.n = (r & 0x80);
|
||||
regs.p.z = (uint8)(r == 0);
|
||||
regs.p.c = (r >= 0);
|
||||
}
|
||||
|
||||
void CPU::opf_cpx() {
|
||||
signed r = regs.x - rd;
|
||||
regs.p.n = (r & 0x80);
|
||||
regs.p.z = (uint8)(r == 0);
|
||||
regs.p.c = (r >= 0);
|
||||
}
|
||||
|
||||
void CPU::opf_cpy() {
|
||||
signed r = regs.y - rd;
|
||||
regs.p.n = (r & 0x80);
|
||||
regs.p.z = (uint8)(r == 0);
|
||||
regs.p.c = (r >= 0);
|
||||
}
|
||||
|
||||
void CPU::opf_dec() {
|
||||
rd--;
|
||||
regs.p.n = (rd & 0x80);
|
||||
regs.p.z = (rd == 0);
|
||||
}
|
||||
|
||||
void CPU::opf_eor() {
|
||||
regs.a ^= rd;
|
||||
regs.p.n = (regs.a & 0x80);
|
||||
regs.p.z = (regs.a == 0);
|
||||
}
|
||||
|
||||
void CPU::opf_inc() {
|
||||
rd++;
|
||||
regs.p.n = (rd & 0x80);
|
||||
regs.p.z = (rd == 0);
|
||||
}
|
||||
|
||||
void CPU::opf_lda() {
|
||||
regs.a = rd;
|
||||
regs.p.n = (regs.a & 0x80);
|
||||
regs.p.z = (regs.a == 0);
|
||||
}
|
||||
|
||||
void CPU::opf_ldx() {
|
||||
regs.x = rd;
|
||||
regs.p.n = (regs.x & 0x80);
|
||||
regs.p.z = (regs.x == 0);
|
||||
}
|
||||
|
||||
void CPU::opf_ldy() {
|
||||
regs.y = rd;
|
||||
regs.p.n = (regs.y & 0x80);
|
||||
regs.p.z = (regs.y == 0);
|
||||
}
|
||||
|
||||
void CPU::opf_lsr() {
|
||||
regs.p.c = rd & 0x01;
|
||||
rd >>= 1;
|
||||
regs.p.n = (rd & 0x80);
|
||||
regs.p.z = (rd == 0);
|
||||
}
|
||||
|
||||
void CPU::opf_ora() {
|
||||
regs.a |= rd;
|
||||
regs.p.n = (regs.a & 0x80);
|
||||
regs.p.z = (regs.a == 0);
|
||||
}
|
||||
|
||||
void CPU::opf_rla() {
|
||||
unsigned carry = (unsigned)regs.p.c;
|
||||
regs.p.c = regs.a & 0x80;
|
||||
regs.a = (regs.a << 1) | carry;
|
||||
regs.p.n = (regs.a & 0x80);
|
||||
regs.p.z = (regs.a == 0);
|
||||
}
|
||||
|
||||
void CPU::opf_rol() {
|
||||
unsigned carry = (unsigned)regs.p.c;
|
||||
regs.p.c = rd & 0x80;
|
||||
rd = (rd << 1) | carry;
|
||||
regs.p.n = (rd & 0x80);
|
||||
regs.p.z = (rd == 0);
|
||||
}
|
||||
|
||||
void CPU::opf_ror() {
|
||||
unsigned carry = (unsigned)regs.p.c << 7;
|
||||
regs.p.c = rd & 0x01;
|
||||
rd = carry | (rd >> 1);
|
||||
regs.p.n = (rd & 0x80);
|
||||
regs.p.z = (rd == 0);
|
||||
}
|
||||
|
||||
void CPU::opf_rra() {
|
||||
unsigned carry = (unsigned)regs.p.c << 7;
|
||||
regs.p.c = regs.a & 0x01;
|
||||
regs.a = carry | (regs.a >> 1);
|
||||
regs.p.n = (regs.a & 0x80);
|
||||
regs.p.z = (regs.a == 0);
|
||||
}
|
||||
|
||||
void CPU::opf_sbc() {
|
||||
rd ^= 0xff;
|
||||
return opf_adc();
|
||||
}
|
||||
|
||||
void CPU::opf_sla() {
|
||||
regs.p.c = regs.a & 0x80;
|
||||
regs.a <<= 1;
|
||||
regs.p.n = (regs.a & 0x80);
|
||||
regs.p.z = (regs.a == 0);
|
||||
}
|
||||
|
||||
void CPU::opf_sra() {
|
||||
regs.p.c = regs.a & 0x01;
|
||||
regs.a >>= 1;
|
||||
regs.p.n = (regs.a & 0x80);
|
||||
regs.p.z = (regs.a == 0);
|
||||
}
|
||||
|
||||
//opcode implementations
|
||||
//======================
|
||||
|
||||
void CPU::opi_branch(bool condition) {
|
||||
if(condition == false) {
|
||||
L rd = op_readpci();
|
||||
} else {
|
||||
rd = op_readpci();
|
||||
aa = regs.pc + (int8)rd;
|
||||
op_page(regs.pc, aa);
|
||||
L op_readpc();
|
||||
regs.pc = aa;
|
||||
}
|
||||
}
|
||||
|
||||
void CPU::opi_clear_flag(bool &flag) {
|
||||
L op_readpc();
|
||||
flag = 0;
|
||||
}
|
||||
|
||||
void CPU::opi_decrement(uint8 &r) {
|
||||
L op_readpc();
|
||||
r--;
|
||||
regs.p.n = (r & 0x80);
|
||||
regs.p.z = (r == 0);
|
||||
}
|
||||
|
||||
void CPU::opi_increment(uint8 &r) {
|
||||
L op_readpc();
|
||||
r++;
|
||||
regs.p.n = (r & 0x80);
|
||||
regs.p.z = (r == 0);
|
||||
}
|
||||
|
||||
void CPU::opi_pull(uint8 &r) {
|
||||
L r = op_readsp();
|
||||
regs.p.n = (r & 0x80);
|
||||
regs.p.z = (r == 0);
|
||||
}
|
||||
|
||||
void CPU::opi_push(uint8 &r) {
|
||||
L op_writesp(r);
|
||||
}
|
||||
|
||||
template<void (CPU::*op)()>
|
||||
void CPU::opi_read_absolute() {
|
||||
abs.l = op_readpci();
|
||||
abs.h = op_readpci();
|
||||
L rd = op_read(abs.w);
|
||||
call(op);
|
||||
}
|
||||
|
||||
template<void (CPU::*op)()>
|
||||
void CPU::opi_read_absolute_x() {
|
||||
abs.l = op_readpci();
|
||||
abs.h = op_readpci();
|
||||
op_page(abs.w, abs.w + regs.x);
|
||||
L rd = op_read(abs.w + regs.x);
|
||||
call(op);
|
||||
}
|
||||
|
||||
template<void (CPU::*op)()>
|
||||
void CPU::opi_read_absolute_y() {
|
||||
abs.l = op_readpci();
|
||||
abs.h = op_readpci();
|
||||
op_page(abs.w, abs.w + regs.y);
|
||||
L rd = op_read(abs.w + regs.y);
|
||||
call(op);
|
||||
}
|
||||
|
||||
template<void (CPU::*op)()>
|
||||
void CPU::opi_read_immediate() {
|
||||
L rd = op_readpci();
|
||||
call(op);
|
||||
}
|
||||
|
||||
template<void (CPU::*op)()>
|
||||
void CPU::opi_read_indirect_zero_page_x() {
|
||||
zp = op_readpci();
|
||||
op_readpc();
|
||||
abs.l = op_readdp(zp++ + regs.x);
|
||||
abs.h = op_readdp(zp++ + regs.x);
|
||||
L rd = op_read(abs.w);
|
||||
call(op);
|
||||
}
|
||||
|
||||
template<void (CPU::*op)()>
|
||||
void CPU::opi_read_indirect_zero_page_y() {
|
||||
rd = op_readpci();
|
||||
abs.l = op_read(rd++);
|
||||
abs.h = op_read(rd++);
|
||||
op_page(abs.w, abs.w + regs.y);
|
||||
L rd = op_read(abs.w + regs.y);
|
||||
call(op);
|
||||
}
|
||||
|
||||
template<void (CPU::*op)()>
|
||||
void CPU::opi_read_zero_page() {
|
||||
zp = op_readpci();
|
||||
L rd = op_read(zp);
|
||||
call(op);
|
||||
}
|
||||
|
||||
template<void (CPU::*op)()>
|
||||
void CPU::opi_read_zero_page_x() {
|
||||
zp = op_readpci();
|
||||
op_readpc();
|
||||
L rd = op_readdp(zp + regs.x);
|
||||
call(op);
|
||||
}
|
||||
|
||||
template<void (CPU::*op)()>
|
||||
void CPU::opi_read_zero_page_y() {
|
||||
zp = op_readpci();
|
||||
op_readpc();
|
||||
L rd = op_readdp(zp + regs.y);
|
||||
call(op);
|
||||
}
|
||||
|
||||
template<void (CPU::*op)()>
|
||||
void CPU::opi_rmw_absolute() {
|
||||
abs.l = op_readpci();
|
||||
abs.h = op_readpci();
|
||||
rd = op_read(abs.w);
|
||||
call(op);
|
||||
L op_write(abs.w, rd);
|
||||
}
|
||||
|
||||
template<void (CPU::*op)()>
|
||||
void CPU::opi_rmw_absolute_x() {
|
||||
abs.l = op_readpci();
|
||||
abs.h = op_readpci();
|
||||
op_page(abs.w, abs.w + regs.x);
|
||||
rd = op_read(abs.w + regs.x);
|
||||
call(op);
|
||||
L op_write(abs.w + regs.x, rd);
|
||||
}
|
||||
|
||||
template<void (CPU::*op)()>
|
||||
void CPU::opi_rmw_zero_page() {
|
||||
zp = op_readpci();
|
||||
rd = op_read(zp);
|
||||
call(op);
|
||||
op_readpc();
|
||||
L op_write(zp, rd);
|
||||
}
|
||||
|
||||
template<void (CPU::*op)()>
|
||||
void CPU::opi_rmw_zero_page_x() {
|
||||
zp = op_readpci();
|
||||
op_readpc();
|
||||
rd = op_readdp(zp + regs.x);
|
||||
call(op);
|
||||
op_readpc();
|
||||
L op_writedp(zp + regs.x, rd);
|
||||
}
|
||||
|
||||
void CPU::opi_set_flag(bool &flag) {
|
||||
L op_readpc();
|
||||
flag = 1;
|
||||
}
|
||||
|
||||
template<void (CPU::*op)()>
|
||||
void CPU::opi_shift() {
|
||||
L op_readpc();
|
||||
call(op);
|
||||
}
|
||||
|
||||
void CPU::opi_store_absolute(uint8 &r) {
|
||||
abs.l = op_readpci();
|
||||
abs.h = op_readpci();
|
||||
L op_write(abs.w, r);
|
||||
}
|
||||
|
||||
void CPU::opi_store_absolute_x(uint8 &r) {
|
||||
abs.l = op_readpci();
|
||||
abs.h = op_readpci();
|
||||
op_page(abs.w, abs.w + regs.x);
|
||||
L op_write(abs.w + regs.x, r);
|
||||
}
|
||||
|
||||
void CPU::opi_store_absolute_y(uint8 &r) {
|
||||
abs.l = op_readpci();
|
||||
abs.h = op_readpci();
|
||||
op_page(abs.w, abs.w + regs.y);
|
||||
L op_write(abs.w + regs.y, r);
|
||||
}
|
||||
|
||||
void CPU::opi_store_indirect_zero_page_x(uint8 &r) {
|
||||
zp = op_readpci();
|
||||
op_readpc();
|
||||
abs.l = op_readdp(zp++ + regs.x);
|
||||
abs.h = op_readdp(zp++ + regs.x);
|
||||
L op_write(abs.w, r);
|
||||
}
|
||||
|
||||
void CPU::opi_store_indirect_zero_page_y(uint8 &r) {
|
||||
rd = op_readpci();
|
||||
abs.l = op_read(rd++);
|
||||
abs.h = op_read(rd++);
|
||||
op_page(abs.w, abs.w + regs.y);
|
||||
L op_write(abs.w + regs.y, r);
|
||||
}
|
||||
|
||||
void CPU::opi_store_zero_page(uint8 &r) {
|
||||
rd = op_readpci();
|
||||
L op_write(rd, r);
|
||||
}
|
||||
|
||||
void CPU::opi_store_zero_page_x(uint8 &r) {
|
||||
zp = op_readpci();
|
||||
op_readpc();
|
||||
L op_writedp(zp + regs.x, r);
|
||||
}
|
||||
|
||||
void CPU::opi_store_zero_page_y(uint8 &r) {
|
||||
zp = op_readpci();
|
||||
op_readpc();
|
||||
L op_writedp(zp + regs.y, r);
|
||||
}
|
||||
|
||||
void CPU::opi_transfer(uint8 &s, uint8 &d, bool flag) {
|
||||
L op_readpc();
|
||||
d = s;
|
||||
if(flag == false) return;
|
||||
regs.p.n = (d & 0x80);
|
||||
regs.p.z = (d == 0);
|
||||
}
|
||||
|
||||
//opcodes
|
||||
//=======
|
||||
|
||||
void CPU::op_brk() {
|
||||
op_readpci();
|
||||
interrupt(0xfffe);
|
||||
}
|
||||
|
||||
void CPU::op_jmp_absolute() {
|
||||
abs.l = op_readpci();
|
||||
L abs.h = op_readpci();
|
||||
regs.pc = abs.w;
|
||||
}
|
||||
|
||||
void CPU::op_jmp_indirect_absolute() {
|
||||
abs.l = op_readpci();
|
||||
abs.h = op_readpci();
|
||||
iabs.l = op_read(abs.w); abs.l++;
|
||||
L iabs.h = op_read(abs.w); abs.l++;
|
||||
regs.pc = iabs.w;
|
||||
}
|
||||
|
||||
void CPU::op_jsr_absolute() {
|
||||
abs.l = op_readpci();
|
||||
abs.h = op_readpci();
|
||||
regs.pc--;
|
||||
op_writesp(regs.pc >> 8);
|
||||
L op_writesp(regs.pc >> 0);
|
||||
regs.pc = abs.w;
|
||||
}
|
||||
|
||||
void CPU::op_nop() {
|
||||
L op_readpc();
|
||||
}
|
||||
|
||||
void CPU::op_php() {
|
||||
L op_writesp(regs.p);
|
||||
}
|
||||
|
||||
void CPU::op_plp() {
|
||||
L regs.p = op_readsp() | 0x30;
|
||||
}
|
||||
|
||||
void CPU::op_rti() {
|
||||
op_readpc();
|
||||
op_readpc();
|
||||
regs.p = op_readsp() | 0x30;
|
||||
abs.l = op_readsp();
|
||||
L abs.h = op_readsp();
|
||||
regs.pc = abs.w;
|
||||
}
|
||||
|
||||
void CPU::op_rts() {
|
||||
op_readpc();
|
||||
op_readpc();
|
||||
abs.l = op_readsp();
|
||||
abs.h = op_readsp();
|
||||
L op_readpc();
|
||||
regs.pc = ++abs.w;
|
||||
}
|
||||
|
||||
#undef call
|
||||
#undef L
|
|
@ -0,0 +1,130 @@
|
|||
#include <nes/nes.hpp>
|
||||
|
||||
namespace NES {
|
||||
|
||||
static unsigned opcodeCounter = 0;
|
||||
|
||||
#include "core/core.cpp"
|
||||
#include "memory/memory.cpp"
|
||||
CPU cpu;
|
||||
|
||||
void CPU::Main() {
|
||||
cpu.main();
|
||||
}
|
||||
|
||||
void CPU::main() {
|
||||
//FILE *fp = fopen("/home/byuu/Desktop/log.txt", "wb");
|
||||
|
||||
unsigned lpc = 0xffff;
|
||||
while(true) {
|
||||
if(status.nmi_line) {
|
||||
status.nmi_line = 0;
|
||||
interrupt(0xfffa);
|
||||
continue;
|
||||
}
|
||||
|
||||
if(status.irq_line) {
|
||||
status.irq_line = 0;
|
||||
interrupt(0xfffe);
|
||||
continue;
|
||||
}
|
||||
|
||||
// if(lpc != regs.pc) { print(disassemble(), "\n"); } lpc = regs.pc;
|
||||
// if(lpc != regs.pc) { fprintf(fp, "%s\n", (const char*)disassemble()); fflush(fp); } lpc = regs.pc;
|
||||
|
||||
op_exec();
|
||||
opcodeCounter++;
|
||||
}
|
||||
}
|
||||
|
||||
void CPU::add_clocks(unsigned clocks) {
|
||||
ppu.clock -= clocks;
|
||||
if(ppu.clock < 0) co_switch(ppu.thread);
|
||||
}
|
||||
|
||||
void CPU::interrupt(uint16 vector) {
|
||||
op_writesp(regs.pc >> 8);
|
||||
op_writesp(regs.pc >> 0);
|
||||
op_writesp(regs.p);
|
||||
abs.l = op_read(vector + 0);
|
||||
regs.p.i = 1;
|
||||
regs.p.d = 0;
|
||||
abs.h = op_read(vector + 1);
|
||||
regs.pc = abs.w;
|
||||
}
|
||||
|
||||
void CPU::power() {
|
||||
regs.a = 0x00;
|
||||
regs.x = 0x00;
|
||||
regs.y = 0x00;
|
||||
regs.s = 0x00;
|
||||
regs.p = 0x34;
|
||||
|
||||
for(unsigned addr = 0; addr < 0x0800; addr++) ram[addr] = 0xff;
|
||||
ram[0x0008] = 0xf7;
|
||||
ram[0x0009] = 0xef;
|
||||
ram[0x000a] = 0xdf;
|
||||
ram[0x000f] = 0xbf;
|
||||
|
||||
reset();
|
||||
}
|
||||
|
||||
void CPU::reset() {
|
||||
Processor::create(CPU::Main, 21477272);
|
||||
|
||||
regs.s -= 3;
|
||||
regs.p.i = 1;
|
||||
|
||||
regs.pc = bus.read(0xfffc) << 0;
|
||||
regs.pc |= bus.read(0xfffd) << 8;
|
||||
|
||||
status.nmi_line = false;
|
||||
status.irq_line = false;
|
||||
|
||||
status.controller_latch = false;
|
||||
status.controller_port0 = 0;
|
||||
status.controller_port1 = 0;
|
||||
}
|
||||
|
||||
uint8 CPU::ram_read(uint16 addr) {
|
||||
return ram[addr & 0x07ff];
|
||||
}
|
||||
|
||||
void CPU::ram_write(uint16 addr, uint8 data) {
|
||||
ram[addr & 0x07ff] = data;
|
||||
}
|
||||
|
||||
uint8 CPU::read(uint16 addr) {
|
||||
if(addr == 0x4016) {
|
||||
if(status.controller_port0 >= 8) return 1;
|
||||
return system.interface->input_poll(0, 0u, status.controller_port0++);
|
||||
}
|
||||
|
||||
if(addr == 0x4017) {
|
||||
if(status.controller_port1 >= 8) return 1;
|
||||
return system.interface->input_poll(1, 0u, status.controller_port1++);
|
||||
}
|
||||
|
||||
return 0x00;
|
||||
}
|
||||
|
||||
void CPU::write(uint16 addr, uint8 data) {
|
||||
if(addr == 0x4014) return oam_dma(data << 8);
|
||||
if(addr == 0x4016) {
|
||||
status.controller_latch = data & 0x01;
|
||||
if(status.controller_latch) {
|
||||
status.controller_port0 = 0;
|
||||
status.controller_port1 = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CPU::oam_dma(uint16 addr) {
|
||||
// op_readpc();
|
||||
for(unsigned n = 0; n < 256; n++) {
|
||||
uint8 data = bus.read(addr + n);
|
||||
op_write(0x2004, data);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
struct CPU : Processor {
|
||||
#include "core/core.hpp"
|
||||
#include "memory/memory.hpp"
|
||||
uint8 ram[0x0800];
|
||||
|
||||
struct Status {
|
||||
bool nmi_line;
|
||||
bool irq_line;
|
||||
|
||||
bool controller_latch;
|
||||
unsigned controller_port0;
|
||||
unsigned controller_port1;
|
||||
} status;
|
||||
|
||||
static void Main();
|
||||
void main();
|
||||
void add_clocks(unsigned clocks);
|
||||
void interrupt(uint16 vector);
|
||||
|
||||
void power();
|
||||
void reset();
|
||||
|
||||
uint8 ram_read(uint16 addr);
|
||||
void ram_write(uint16 addr, uint8 data);
|
||||
|
||||
uint8 read(uint16 addr);
|
||||
void write(uint16 addr, uint8 data);
|
||||
|
||||
void oam_dma(uint16 addr);
|
||||
};
|
||||
|
||||
extern CPU cpu;
|
|
@ -0,0 +1,44 @@
|
|||
uint8 CPU::op_read(uint16 addr) {
|
||||
uint8 data = bus.read(addr);
|
||||
add_clocks(12);
|
||||
return data;
|
||||
}
|
||||
|
||||
void CPU::op_write(uint16 addr, uint8 data) {
|
||||
add_clocks(12);
|
||||
bus.write(addr, data);
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
uint8 CPU::op_readpc() {
|
||||
return op_read(regs.pc);
|
||||
}
|
||||
|
||||
uint8 CPU::op_readpci() {
|
||||
return op_read(regs.pc++);
|
||||
}
|
||||
|
||||
uint8 CPU::op_readsp() {
|
||||
return op_read(0x0100 | ++regs.s);
|
||||
}
|
||||
|
||||
uint8 CPU::op_readdp(uint8 addr) {
|
||||
return op_read(addr);
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
void CPU::op_writesp(uint8 data) {
|
||||
op_write(0x0100 | regs.s--, data);
|
||||
}
|
||||
|
||||
void CPU::op_writedp(uint8 addr, uint8 data) {
|
||||
op_write(addr, data);
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
void CPU::op_page(uint16 x, uint16 y) {
|
||||
if((x & 0xff00) != (y & 0xff00)) op_readpc();
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
uint8 op_read(uint16 addr);
|
||||
void op_write(uint16 addr, uint8 data);
|
||||
|
||||
uint8 op_readpc();
|
||||
uint8 op_readpci();
|
||||
uint8 op_readsp();
|
||||
uint8 op_readdp(uint8 addr);
|
||||
|
||||
void op_writesp(uint8 data);
|
||||
void op_writedp(uint8 addr, uint8 data);
|
||||
|
||||
void op_page(uint16 x, uint16 y);
|
|
@ -0,0 +1,7 @@
|
|||
struct Interface {
|
||||
virtual void video_refresh(const uint32_t *data) {}
|
||||
virtual void audio_sample(int16_t lsample, int16_t rsample) {}
|
||||
virtual int16_t input_poll(bool port, unsigned device, unsigned id) { return 0; }
|
||||
|
||||
virtual void message(const string &text) { print(text, "\n"); }
|
||||
};
|
|
@ -0,0 +1,28 @@
|
|||
#include <nes/nes.hpp>
|
||||
|
||||
namespace NES {
|
||||
|
||||
Bus bus;
|
||||
|
||||
//$0000-07ff = RAM (2KB)
|
||||
//$0800-1fff = RAM (mirror)
|
||||
//$2000-2007 = PPU
|
||||
//$2008-3fff = PPU (mirror)
|
||||
//$4000-4017 = APU + I/O
|
||||
//$4018-ffff = Cartridge
|
||||
|
||||
uint8 Bus::read(uint16 addr) {
|
||||
if(addr <= 0x1fff) return cpu.ram_read(addr);
|
||||
if(addr <= 0x3fff) return ppu.read(addr);
|
||||
if(addr <= 0x4017) return cpu.read(addr);
|
||||
return cartridge.prg_read(addr);
|
||||
}
|
||||
|
||||
void Bus::write(uint16 addr, uint8 data) {
|
||||
if(addr <= 0x1fff) return cpu.ram_write(addr, data);
|
||||
if(addr <= 0x3fff) return ppu.write(addr, data);
|
||||
if(addr <= 0x4017) return cpu.write(addr, data);
|
||||
return cartridge.prg_write(addr, data);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
struct Bus {
|
||||
uint8 read(uint16 addr);
|
||||
void write(uint16 addr, uint8 data);
|
||||
};
|
||||
|
||||
extern Bus bus;
|
|
@ -0,0 +1,80 @@
|
|||
#ifndef NES_HPP
|
||||
#define NES_HPP
|
||||
|
||||
namespace NES {
|
||||
namespace Info {
|
||||
static const char Name[] = "bnes";
|
||||
static const char Version[] = "000.01";
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
bnes - NES emulator
|
||||
author: byuu
|
||||
license: GPLv2
|
||||
project started: 2011-09-05
|
||||
*/
|
||||
|
||||
#include <libco/libco.h>
|
||||
|
||||
#include <nall/algorithm.hpp>
|
||||
#include <nall/array.hpp>
|
||||
#include <nall/detect.hpp>
|
||||
#include <nall/dl.hpp>
|
||||
#include <nall/endian.hpp>
|
||||
#include <nall/file.hpp>
|
||||
#include <nall/foreach.hpp>
|
||||
#include <nall/function.hpp>
|
||||
#include <nall/platform.hpp>
|
||||
#include <nall/property.hpp>
|
||||
#include <nall/serializer.hpp>
|
||||
#include <nall/stdint.hpp>
|
||||
#include <nall/string.hpp>
|
||||
#include <nall/utility.hpp>
|
||||
#include <nall/varint.hpp>
|
||||
#include <nall/vector.hpp>
|
||||
using namespace nall;
|
||||
|
||||
namespace NES {
|
||||
typedef int8_t int8;
|
||||
typedef int16_t int16;
|
||||
typedef int32_t int32;
|
||||
typedef int64_t int64;
|
||||
|
||||
typedef uint8_t uint8;
|
||||
typedef uint16_t uint16;
|
||||
typedef uint32_t uint32;
|
||||
typedef uint64_t uint64;
|
||||
|
||||
typedef uint_t<15> uint15;
|
||||
|
||||
struct Processor {
|
||||
cothread_t thread;
|
||||
unsigned frequency;
|
||||
signed clock;
|
||||
|
||||
inline void create(void (*entrypoint)(), unsigned frequency) {
|
||||
if(thread) co_delete(thread);
|
||||
thread = co_create(65536 * sizeof(void*), entrypoint);
|
||||
this->frequency = frequency;
|
||||
clock = 0;
|
||||
}
|
||||
|
||||
inline void serialize(serializer &s) {
|
||||
s.integer(frequency);
|
||||
s.integer(clock);
|
||||
}
|
||||
|
||||
inline Processor() : thread(0) {}
|
||||
};
|
||||
|
||||
#include <nes/interface/interface.hpp>
|
||||
#include <nes/system/system.hpp>
|
||||
#include <nes/scheduler/scheduler.hpp>
|
||||
#include <nes/cartridge/cartridge.hpp>
|
||||
#include <nes/memory/memory.hpp>
|
||||
#include <nes/cpu/cpu.hpp>
|
||||
#include <nes/ppu/ppu.hpp>
|
||||
}
|
||||
|
||||
#endif
|
|
@ -0,0 +1,348 @@
|
|||
#include <nes/nes.hpp>
|
||||
|
||||
namespace NES {
|
||||
|
||||
PPU ppu;
|
||||
|
||||
void PPU::Main() {
|
||||
ppu.main();
|
||||
}
|
||||
|
||||
void PPU::main() {
|
||||
unsigned x = 0, y = 0;
|
||||
while(true) {
|
||||
add_clocks(4);
|
||||
|
||||
status.lx++;
|
||||
|
||||
if(status.ly >= 0 && status.ly <= 239) {
|
||||
if(status.lx == 257) {
|
||||
if(raster_enable()) {
|
||||
status.vaddr = (status.vaddr & 0x7be0) | (status.taddr & 0x041f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(status.ly == 261 && status.lx == 304) {
|
||||
if(raster_enable()) {
|
||||
status.vaddr = status.taddr;
|
||||
}
|
||||
}
|
||||
|
||||
if(status.lx == 341) {
|
||||
status.lx = 0;
|
||||
status.ly++;
|
||||
|
||||
if(status.ly == 262) {
|
||||
status.ly = 0;
|
||||
status.nmi = 0;
|
||||
status.sprite_zero_hit = 0;
|
||||
system.interface->video_refresh(buffer);
|
||||
scheduler.exit();
|
||||
}
|
||||
|
||||
if(status.ly >= 0 && status.ly <= 239) {
|
||||
render_scanline();
|
||||
}
|
||||
|
||||
if(status.ly == 240) {
|
||||
status.nmi = 1;
|
||||
if(status.nmi_enable) cpu.status.nmi_line = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PPU::add_clocks(unsigned clocks) {
|
||||
clock += clocks;
|
||||
if(clock >= 0) co_switch(cpu.thread);
|
||||
}
|
||||
|
||||
void PPU::power() {
|
||||
paletteRGB = {
|
||||
0x7c7c7c, 0x0000fc, 0x0000bc, 0x4428bc,
|
||||
0x940084, 0xa80020, 0xa81000, 0x881400,
|
||||
0x503000, 0x007800, 0x006800, 0x005800,
|
||||
0x004058, 0x000000, 0x000000, 0x000000,
|
||||
0xbcbcbc, 0x0078f8, 0x0058f8, 0x6844fc,
|
||||
0xd800cc, 0xe40058, 0xf83800, 0xe45c10,
|
||||
0xac7c00, 0x00b800, 0x00a800, 0x00a844,
|
||||
0x008888, 0x000000, 0x000000, 0x000000,
|
||||
0xf8f8f8, 0x3cbcfc, 0x6888fc, 0x9878f8,
|
||||
0xf878f8, 0xf85898, 0xf87858, 0xfca044,
|
||||
0xf8b800, 0xb8f818, 0x58d854, 0x58f898,
|
||||
0x00e8d8, 0x787878, 0x000000, 0x000000,
|
||||
0xfcfcfc, 0xa4e4fc, 0xb8b8b8, 0xd8d8f8,
|
||||
0xf8b8f8, 0xf8a4c0, 0xf0d0b0, 0xfce0a8,
|
||||
0xf8d878, 0xd8f878, 0xb8f8b8, 0xb8f8d8,
|
||||
0x00fcfc, 0xf8d8f8, 0x000000, 0x000000,
|
||||
};
|
||||
|
||||
reset();
|
||||
}
|
||||
|
||||
void PPU::reset() {
|
||||
create(PPU::Main, 21477272);
|
||||
|
||||
status.mdr = 0x00;
|
||||
status.ly = 0;
|
||||
status.lx = 0;
|
||||
status.bus_data = 0x00;
|
||||
status.address_latch = 0;
|
||||
|
||||
status.vaddr = 0x0000;
|
||||
status.taddr = 0x0000;
|
||||
status.xaddr = 0x00;
|
||||
|
||||
//$2000
|
||||
status.nmi_enable = false;
|
||||
status.master_select = 0;
|
||||
status.sprite_size = 0;
|
||||
status.bg_addr = 0x0000;
|
||||
status.sprite_addr = 0x0000;
|
||||
status.vram_increment = 1;
|
||||
|
||||
//$2001
|
||||
status.intensify_blue = false;
|
||||
status.intensify_green = false;
|
||||
status.intensify_red = false;
|
||||
status.sprite_enable = false;
|
||||
status.bg_enable = false;
|
||||
status.sprite_edge_enable = false;
|
||||
status.bg_edge_enable = false;
|
||||
status.grayscale = false;
|
||||
|
||||
//$2002
|
||||
status.nmi = false;
|
||||
status.sprite_zero_hit = false;
|
||||
status.sprite_overflow = false;
|
||||
|
||||
//$2003
|
||||
status.oam_addr = 0x00;
|
||||
|
||||
memset(buffer, 0, sizeof buffer);
|
||||
|
||||
memset(nram, 0, sizeof nram);
|
||||
memset(pram, 0, sizeof pram);
|
||||
memset(sram, 0, sizeof sram);
|
||||
}
|
||||
|
||||
uint8 PPU::read(uint16 addr) {
|
||||
uint8 result = 0x00;
|
||||
|
||||
switch(addr & 7) {
|
||||
case 2: //PPUSTATUS
|
||||
result |= status.nmi << 7;
|
||||
result |= status.sprite_zero_hit << 6;
|
||||
result |= status.sprite_overflow << 5;
|
||||
result |= status.mdr & 0x1f;
|
||||
status.nmi = 0;
|
||||
status.address_latch = 0;
|
||||
break;
|
||||
case 4: //OAMDATA
|
||||
result = sram[status.oam_addr++];
|
||||
break;
|
||||
case 7: //PPUDATA
|
||||
if(addr >= 0x3f00) {
|
||||
result = bus_read(status.vaddr);
|
||||
} else {
|
||||
result = status.bus_data;
|
||||
status.bus_data = bus_read(status.vaddr);
|
||||
}
|
||||
status.vaddr += status.vram_increment;
|
||||
break;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void PPU::write(uint16 addr, uint8 data) {
|
||||
status.mdr = data;
|
||||
|
||||
switch(addr & 7) {
|
||||
case 0: //PPUCTRL
|
||||
status.nmi_enable = data & 0x80;
|
||||
status.master_select = data & 0x40;
|
||||
status.sprite_size = data & 0x20;
|
||||
status.bg_addr = (data & 0x10) ? 0x1000 : 0x0000;
|
||||
status.sprite_addr = (data & 0x08) ? 0x1000 : 0x0000;
|
||||
status.vram_increment = (data & 0x04) ? 32 : 1;
|
||||
status.taddr = (status.taddr & 0x73ff) | ((data & 0x03) << 10);
|
||||
return;
|
||||
case 1: //PPUMASK
|
||||
status.intensify_blue = data & 0x80;
|
||||
status.intensify_green = data & 0x40;
|
||||
status.intensify_red = data & 0x20;
|
||||
status.sprite_enable = data & 0x10;
|
||||
status.bg_enable = data & 0x08;
|
||||
status.sprite_edge_enable = data & 0x04;
|
||||
status.bg_edge_enable = data & 0x02;
|
||||
status.grayscale = data & 0x01;
|
||||
return;
|
||||
case 2: //PPUSTATUS
|
||||
return;
|
||||
case 3: //OAMADDR
|
||||
status.oam_addr = data;
|
||||
return;
|
||||
case 4: //OAMDATA
|
||||
sram[status.oam_addr++] = data;
|
||||
return;
|
||||
case 5: //PPUSCROLL
|
||||
if(status.address_latch == 0) {
|
||||
status.xaddr = data & 0x07;
|
||||
status.taddr = (status.taddr & 0x7fe0) | (data >> 3);
|
||||
} else {
|
||||
status.taddr = (status.taddr & 0x0c1f) | ((data & 0x07) << 12) | ((data >> 3) << 5);
|
||||
}
|
||||
status.address_latch ^= 1;
|
||||
return;
|
||||
case 6: //PPUADDR
|
||||
if(status.address_latch == 0) {
|
||||
status.taddr = (status.taddr & 0x00ff) | ((data & 0x3f) << 8);
|
||||
} else {
|
||||
status.taddr = (status.taddr & 0x7f00) | data;
|
||||
status.vaddr = status.taddr;
|
||||
}
|
||||
status.address_latch ^= 1;
|
||||
return;
|
||||
case 7: //PPUDATA
|
||||
bus_write(status.vaddr, data);
|
||||
status.vaddr += status.vram_increment;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
uint8 PPU::bus_read(uint16 addr) {
|
||||
if(addr <= 0x1fff) return cartridge.chr_read(addr);
|
||||
if(addr <= 0x3eff) return nram[addr & 0x07ff];
|
||||
if(addr <= 0x3fff) return pram[addr & 0x001f];
|
||||
}
|
||||
|
||||
void PPU::bus_write(uint16 addr, uint8 data) {
|
||||
if(addr <= 0x1fff) {
|
||||
return cartridge.chr_write(addr, data);
|
||||
}
|
||||
|
||||
if(addr <= 0x3eff) {
|
||||
nram[addr & 0x07ff] = data;
|
||||
return;
|
||||
}
|
||||
|
||||
if(addr <= 0x3fff) {
|
||||
addr &= 0x1f;
|
||||
if(addr == 0x10) addr = 0x00;
|
||||
if(addr == 0x14) addr = 0x04;
|
||||
if(addr == 0x18) addr = 0x08;
|
||||
if(addr == 0x1c) addr = 0x0c;
|
||||
pram[addr] = data;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
bool PPU::raster_enable() const {
|
||||
return status.bg_enable || status.sprite_enable;
|
||||
}
|
||||
|
||||
unsigned PPU::nametable_addr() const {
|
||||
return 0x2000 + (status.vaddr & 0x0c00);
|
||||
}
|
||||
|
||||
unsigned PPU::scrollx() const {
|
||||
return ((status.vaddr & 0x1f) << 3) | status.xaddr;
|
||||
}
|
||||
|
||||
unsigned PPU::scrolly() const {
|
||||
return (((status.vaddr >> 5) & 0x1f) << 3) | ((status.vaddr >> 12) & 7);
|
||||
}
|
||||
|
||||
void PPU::render_scanline() {
|
||||
uint32 *line = buffer + status.ly * 256;
|
||||
|
||||
uint8 oam[8][5];
|
||||
unsigned oamc = 0;
|
||||
|
||||
unsigned sprite_height = status.sprite_size ? 16 : 8;
|
||||
for(unsigned n = 0; n < 64; n++) {
|
||||
unsigned y = sram[(n * 4) + 0] + 1;
|
||||
if(status.ly < y || status.ly >= y + sprite_height) continue;
|
||||
oam[oamc][0] = y;
|
||||
oam[oamc][1] = sram[(n * 4) + 1];
|
||||
oam[oamc][2] = sram[(n * 4) + 2];
|
||||
oam[oamc][3] = sram[(n * 4) + 3];
|
||||
oam[oamc][4] = n;
|
||||
if(++oamc >= 8) break;
|
||||
}
|
||||
|
||||
for(unsigned x = 0; x < 256; x++) {
|
||||
unsigned offsetx = x + scrollx();
|
||||
unsigned offsety = status.ly + scrolly();
|
||||
|
||||
bool screenx = offsetx & 256;
|
||||
bool screeny = offsety & 256;
|
||||
|
||||
offsetx &= 255;
|
||||
offsety &= 255;
|
||||
|
||||
unsigned base = nametable_addr() + (screenx * 0x0400) + (screeny * 0x0800);
|
||||
|
||||
uint8 tile = bus_read(base + (offsety / 8) * 32 + (offsetx / 8));
|
||||
uint8 attr = bus_read(base + 0x03c0 + (offsety / 32) * 8 + (offsetx / 32));
|
||||
|
||||
if((offsety / 16) & 1) attr >>= 4;
|
||||
if((offsetx / 16) & 1) attr >>= 2;
|
||||
|
||||
unsigned tilex = offsetx & 7;
|
||||
unsigned tiley = offsety & 7;
|
||||
|
||||
uint8 tdlo = bus_read(status.bg_addr + tile * 16 + 0 + tiley);
|
||||
uint8 tdhi = bus_read(status.bg_addr + tile * 16 + 8 + tiley);
|
||||
|
||||
unsigned mask = 0x80 >> tilex;
|
||||
unsigned palette = 0;
|
||||
if(status.bg_enable) {
|
||||
if(status.bg_edge_enable == true || x >= 8) {
|
||||
palette |= (tdlo & mask) ? 1 : 0;
|
||||
palette |= (tdhi & mask) ? 2 : 0;
|
||||
if(palette) palette |= (attr & 3) << 2;
|
||||
}
|
||||
}
|
||||
|
||||
for(unsigned n = 0; n < oamc; n++) {
|
||||
if(x < oam[n][3] || x >= oam[n][3] + 8) continue;
|
||||
if(status.sprite_enable == false) continue;
|
||||
if(status.sprite_edge_enable == false && x < 8) continue;
|
||||
|
||||
unsigned spritex = x - oam[n][3];
|
||||
unsigned spritey = status.ly - oam[n][0];
|
||||
|
||||
unsigned addr = (sprite_height == 8)
|
||||
? status.sprite_addr + oam[n][1] * 16
|
||||
: ((oam[n][1] & ~1) * 16) + ((oam[n][1] & 1) * 0x1000);
|
||||
|
||||
if(oam[n][2] & 0x80) spritey ^= (sprite_height - 1);
|
||||
if(oam[n][2] & 0x40) spritex ^= 7;
|
||||
if(spritey & 8) spritey += 8;
|
||||
|
||||
tdlo = bus_read(addr + 0 + spritey);
|
||||
tdhi = bus_read(addr + 8 + spritey);
|
||||
|
||||
mask = 0x80 >> spritex;
|
||||
unsigned sprite_palette = 0;
|
||||
sprite_palette |= (tdlo & mask) ? 1 : 0;
|
||||
sprite_palette |= (tdhi & mask) ? 2 : 0;
|
||||
if(sprite_palette == 0) continue;
|
||||
sprite_palette |= (oam[n][2] & 3) << 2;
|
||||
|
||||
bool priority = oam[n][2] & 0x20;
|
||||
if(priority == 0 || palette == 0) {
|
||||
if(oam[n][4] == 0) status.sprite_zero_hit = 1;
|
||||
palette = 16 + sprite_palette;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
*line++ = paletteRGB[pram[palette]];
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,70 @@
|
|||
struct PPU : Processor {
|
||||
static void Main();
|
||||
void main();
|
||||
void add_clocks(unsigned clocks);
|
||||
|
||||
void power();
|
||||
void reset();
|
||||
|
||||
uint8 read(uint16 addr);
|
||||
void write(uint16 addr, uint8 data);
|
||||
|
||||
uint8 bus_read(uint16 addr);
|
||||
void bus_write(uint16 addr, uint8 data);
|
||||
|
||||
void render_scanline();
|
||||
|
||||
bool raster_enable() const;
|
||||
unsigned nametable_addr() const;
|
||||
unsigned scrollx() const;
|
||||
unsigned scrolly() const;
|
||||
|
||||
struct Status {
|
||||
uint8 mdr;
|
||||
|
||||
unsigned ly;
|
||||
unsigned lx;
|
||||
|
||||
uint8 bus_data;
|
||||
|
||||
bool address_latch;
|
||||
|
||||
uint15 vaddr;
|
||||
uint15 taddr;
|
||||
uint8 xaddr;
|
||||
|
||||
//$2000
|
||||
bool nmi_enable;
|
||||
bool master_select;
|
||||
bool sprite_size;
|
||||
unsigned bg_addr;
|
||||
unsigned sprite_addr;
|
||||
unsigned vram_increment;
|
||||
|
||||
//$2001
|
||||
bool intensify_blue;
|
||||
bool intensify_green;
|
||||
bool intensify_red;
|
||||
bool sprite_enable;
|
||||
bool bg_enable;
|
||||
bool sprite_edge_enable;
|
||||
bool bg_edge_enable;
|
||||
bool grayscale;
|
||||
|
||||
//$2002
|
||||
bool nmi;
|
||||
bool sprite_zero_hit;
|
||||
bool sprite_overflow;
|
||||
|
||||
//$2003
|
||||
uint8 oam_addr;
|
||||
} status;
|
||||
|
||||
uint32 buffer[256 * 240];
|
||||
uint32 paletteRGB[64];
|
||||
uint8 nram[2048];
|
||||
uint8 pram[32];
|
||||
uint8 sram[256];
|
||||
};
|
||||
|
||||
extern PPU ppu;
|
|
@ -0,0 +1,26 @@
|
|||
#include <nes/nes.hpp>
|
||||
|
||||
namespace NES {
|
||||
|
||||
Scheduler scheduler;
|
||||
|
||||
void Scheduler::enter() {
|
||||
host_thread = co_active();
|
||||
co_switch(thread);
|
||||
}
|
||||
|
||||
void Scheduler::exit() {
|
||||
thread = co_active();
|
||||
co_switch(host_thread);
|
||||
}
|
||||
|
||||
void Scheduler::power() {
|
||||
reset();
|
||||
}
|
||||
|
||||
void Scheduler::reset() {
|
||||
host_thread = co_active();
|
||||
thread = cpu.thread;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
struct Scheduler {
|
||||
cothread_t host_thread; //program thread (used to exit emulation)
|
||||
cothread_t thread; //active emulation thread (used to enter emulation)
|
||||
|
||||
void enter();
|
||||
void exit();
|
||||
|
||||
void power();
|
||||
void reset();
|
||||
};
|
||||
|
||||
extern Scheduler scheduler;
|
|
@ -0,0 +1,30 @@
|
|||
#include <nes/nes.hpp>
|
||||
|
||||
namespace NES {
|
||||
|
||||
System system;
|
||||
|
||||
void System::run() {
|
||||
scheduler.enter();
|
||||
}
|
||||
|
||||
void System::power() {
|
||||
cpu.power();
|
||||
ppu.power();
|
||||
scheduler.power();
|
||||
}
|
||||
|
||||
void System::reset() {
|
||||
cpu.reset();
|
||||
ppu.reset();
|
||||
scheduler.reset();
|
||||
}
|
||||
|
||||
void System::init(Interface *interface) {
|
||||
this->interface = interface;
|
||||
}
|
||||
|
||||
void System::term() {
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
struct System {
|
||||
Interface *interface;
|
||||
|
||||
void run();
|
||||
void power();
|
||||
void reset();
|
||||
|
||||
void init(Interface *interface);
|
||||
void term();
|
||||
};
|
||||
|
||||
extern System system;
|
|
@ -406,7 +406,7 @@ public:
|
|||
presentation.BackBufferHeight = 0;
|
||||
|
||||
if(lpd3d->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, settings.handle,
|
||||
D3DCREATE_SOFTWARE_VERTEXPROCESSING, &presentation, &device) != D3D_OK) {
|
||||
D3DCREATE_FPU_PRESERVE | D3DCREATE_SOFTWARE_VERTEXPROCESSING, &presentation, &device) != D3D_OK) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
|
@ -39,7 +39,6 @@ void SMP::op_step() {
|
|||
#define op_writeaddr(addr, data) op_write(addr, data)
|
||||
#define op_readstack() op_read(0x0100 | ++regs.sp)
|
||||
#define op_writestack(data) op_write(0x0100 | regs.sp--, data)
|
||||
static unsigned rd, wr, dp, sp, ya, bit;
|
||||
|
||||
#if defined(CYCLE_ACCURATE)
|
||||
|
||||
|
|
|
@ -110,6 +110,13 @@ void SMP::serialize(serializer &s) {
|
|||
s.integer(regs.p.z);
|
||||
s.integer(regs.p.c);
|
||||
|
||||
s.integer(rd);
|
||||
s.integer(wr);
|
||||
s.integer(dp);
|
||||
s.integer(sp);
|
||||
s.integer(ya);
|
||||
s.integer(bit);
|
||||
|
||||
s.integer(status.iplrom_enable);
|
||||
|
||||
s.integer(status.dsp_addr);
|
||||
|
|
|
@ -55,6 +55,8 @@ public:
|
|||
Flags p;
|
||||
} regs;
|
||||
|
||||
unsigned rd, wr, dp, sp, ya, bit;
|
||||
|
||||
struct Status {
|
||||
//$00f1
|
||||
bool iplrom_enable;
|
||||
|
|
|
@ -1,7 +1,10 @@
|
|||
#ifndef SNES_HPP
|
||||
#define SNES_HPP
|
||||
|
||||
namespace SNES {
|
||||
namespace Info {
|
||||
static const char Name[] = "bsnes";
|
||||
static const char Version[] = "082.03";
|
||||
static const char Version[] = "082.04";
|
||||
static const unsigned SerializerVersion = 21;
|
||||
}
|
||||
}
|
||||
|
@ -10,6 +13,7 @@ namespace SNES {
|
|||
bsnes - SNES emulator
|
||||
author: byuu
|
||||
license: GPLv2
|
||||
project started: 2004-10-14
|
||||
*/
|
||||
|
||||
#include <libco/libco.h>
|
||||
|
@ -154,3 +158,5 @@ namespace nall {
|
|||
}
|
||||
|
||||
#undef debugvirtual
|
||||
|
||||
#endif
|
||||
|
|
|
@ -0,0 +1,110 @@
|
|||
include $(snes)/Makefile
|
||||
include $(gameboy)/Makefile
|
||||
|
||||
ui_objects := ui-main ui-general ui-settings ui-tools ui-input ui-utility ui-path ui-cartridge ui-debugger
|
||||
ui_objects += ruby phoenix
|
||||
ui_objects += $(if $(call streq,$(platform),win),resource)
|
||||
|
||||
# platform
|
||||
ifeq ($(platform),x)
|
||||
ifeq ($(phoenix),gtk)
|
||||
phoenix_compile = $(call compile,-DPHOENIX_GTK `pkg-config --cflags gtk+-2.0`)
|
||||
link += `pkg-config --libs gtk+-2.0`
|
||||
else
|
||||
phoenix_compile = $(call compile,-DPHOENIX_QT `pkg-config --cflags QtCore QtGui`)
|
||||
link += `pkg-config --libs QtCore QtGui`
|
||||
endif
|
||||
|
||||
ruby := video.glx video.xv video.sdl
|
||||
ruby += audio.alsa audio.openal audio.oss audio.pulseaudio audio.pulseaudiosimple audio.ao
|
||||
ruby += input.sdl input.x
|
||||
|
||||
link += $(if $(findstring audio.openal,$(ruby)),-lopenal)
|
||||
else ifeq ($(platform),osx)
|
||||
phoenix_compile = $(call compile,-DPHOENIX_QT)
|
||||
link +=
|
||||
|
||||
ruby :=
|
||||
ruby += audio.openal
|
||||
ruby += input.carbon
|
||||
|
||||
link += $(if $(findstring audio.openal,$(ruby)),-framework OpenAL)
|
||||
else ifeq ($(platform),win)
|
||||
phoenix_compile = $(call compile,-DPHOENIX_WINDOWS)
|
||||
link +=
|
||||
|
||||
ruby := video.direct3d video.wgl video.directdraw video.gdi
|
||||
ruby += audio.directsound audio.xaudio2
|
||||
ruby += input.rawinput input.directinput
|
||||
|
||||
link += $(if $(findstring audio.openal,$(ruby)),-lopenal32)
|
||||
endif
|
||||
|
||||
# ruby
|
||||
rubyflags := $(if $(finstring .sdl,$(ruby)),`sdl-config --cflags`)
|
||||
|
||||
link += $(if $(findstring .sdl,$(ruby)),`sdl-config --libs`)
|
||||
link += $(if $(findstring video.direct3d,$(ruby)),-ld3d9)
|
||||
link += $(if $(findstring video.directdraw,$(ruby)),-lddraw)
|
||||
link += $(if $(findstring video.glx,$(ruby)),-lGL)
|
||||
link += $(if $(findstring video.wgl,$(ruby)),-lopengl32)
|
||||
link += $(if $(findstring video.xv,$(ruby)),-lXv)
|
||||
link += $(if $(findstring audio.alsa,$(ruby)),-lasound)
|
||||
link += $(if $(findstring audio.ao,$(ruby)),-lao)
|
||||
link += $(if $(findstring audio.directsound,$(ruby)),-ldsound)
|
||||
link += $(if $(findstring audio.pulseaudio,$(ruby)),-lpulse)
|
||||
link += $(if $(findstring audio.pulseaudiosimple,$(ruby)),-lpulse-simple)
|
||||
link += $(if $(findstring audio.xaudio2,$(ruby)),-lole32)
|
||||
link += $(if $(findstring input.directinput,$(ruby)),-ldinput8 -ldxguid)
|
||||
link += $(if $(findstring input.rawinput,$(ruby)),-ldinput8 -ldxguid)
|
||||
|
||||
rubydef := $(foreach c,$(subst .,_,$(call strupper,$(ruby))),-D$c)
|
||||
|
||||
# rules
|
||||
objects := $(ui_objects) $(objects)
|
||||
objects := $(patsubst %,obj/%.o,$(objects))
|
||||
|
||||
obj/ui-main.o: $(ui)/main.cpp $(call rwildcard,$(ui)/*.hpp) $(call rwildcard,$(ui)/*)
|
||||
obj/ui-general.o: $(ui)/general/general.cpp $(call rwildcard,$(ui)/*.hpp) $(call rwildcard,$(ui)/general/*)
|
||||
obj/ui-tools.o: $(ui)/tools/tools.cpp $(call rwildcard,$(ui)/*.hpp) $(call rwildcard,$(ui)/tools/*)
|
||||
obj/ui-settings.o: $(ui)/settings/settings.cpp $(call rwildcard,$(ui)/*.hpp) $(call rwildcard,$(ui)/settings/*)
|
||||
obj/ui-input.o: $(ui)/input/input.cpp $(call rwildcard,$(ui)/*.hpp) $(call rwildcard,$(ui)/input/*)
|
||||
obj/ui-utility.o: $(ui)/utility/utility.cpp $(call rwildcard,$(ui)/*.hpp) $(call rwildcard,$(ui)/utility/*)
|
||||
obj/ui-path.o: $(ui)/path/path.cpp $(call rwildcard,$(ui)/*.hpp) $(call rwildcard,$(ui)/path/*)
|
||||
obj/ui-cartridge.o: $(ui)/cartridge/cartridge.cpp $(call rwildcard,$(ui)/*.hpp) $(call rwildcard,$(ui)/cartridge/*)
|
||||
obj/ui-debugger.o: $(ui)/debugger/debugger.cpp $(call rwildcard,$(ui)/*.hpp) $(call rwildcard,$(ui)/debugger/*)
|
||||
|
||||
obj/ruby.o: ruby/ruby.cpp $(call rwildcard,ruby/*)
|
||||
$(call compile,$(rubydef) $(rubyflags))
|
||||
|
||||
obj/phoenix.o: phoenix/phoenix.cpp $(call rwildcard,phoenix/*)
|
||||
$(phoenix_compile)
|
||||
|
||||
obj/resource.o: $(ui)/resource.rc
|
||||
windres $(ui)/resource.rc obj/resource.o
|
||||
|
||||
# targets
|
||||
build: $(objects)
|
||||
ifeq ($(platform),osx)
|
||||
test -d ../bsnes.app || mkdir -p ../bsnes.app/Contents/MacOS
|
||||
$(strip $(cpp) -o ../bsnes.app/Contents/MacOS/bsnes $(objects) $(link))
|
||||
else
|
||||
$(strip $(cpp) -o out/bsnes $(objects) $(link))
|
||||
endif
|
||||
|
||||
install:
|
||||
ifeq ($(platform),x)
|
||||
install -D -m 755 out/bsnes $(DESTDIR)$(prefix)/bin/bsnes
|
||||
endif
|
||||
install -D -m 644 data/bsnes.png $(DESTDIR)$(prefix)/share/pixmaps/bsnes.png
|
||||
install -D -m 644 data/bsnes.desktop $(DESTDIR)$(prefix)/share/applications/bsnes.desktop
|
||||
mkdir -p ~/.config/bsnes
|
||||
cp data/cheats.xml ~/.config/bsnes/cheats.xml
|
||||
chmod 777 ~/.config/bsnes ~/.config/bsnes/cheats.xml
|
||||
|
||||
uninstall:
|
||||
ifeq ($(platform),x)
|
||||
rm $(DESTDIR)$(prefix)/bin/bsnes
|
||||
endif
|
||||
rm $(DESTDIR)$(prefix)/share/pixmaps/bsnes.png
|
||||
rm $(DESTDIR)$(prefix)/share/applications/bsnes.desktop
|
|
@ -0,0 +1,65 @@
|
|||
#include <snes/snes.hpp>
|
||||
|
||||
#include <nall/base64.hpp>
|
||||
#include <nall/bmp.hpp>
|
||||
#include <nall/compositor.hpp>
|
||||
#include <nall/config.hpp>
|
||||
#include <nall/crc32.hpp>
|
||||
#include <nall/directory.hpp>
|
||||
#include <nall/dsp.hpp>
|
||||
#include <nall/filemap.hpp>
|
||||
#include <nall/input.hpp>
|
||||
#include <nall/resource.hpp>
|
||||
#include <nall/bps/patch.hpp>
|
||||
#include <nall/snes/cartridge.hpp>
|
||||
#include <nall/gameboy/cartridge.hpp>
|
||||
using namespace nall;
|
||||
|
||||
#include <ruby/ruby.hpp>
|
||||
using namespace ruby;
|
||||
|
||||
#include <phoenix/phoenix.hpp>
|
||||
using namespace phoenix;
|
||||
|
||||
struct TopLevelWindow : Window {
|
||||
string name;
|
||||
string position;
|
||||
};
|
||||
|
||||
#include "interface.hpp"
|
||||
#include "config.hpp"
|
||||
#include "general/general.hpp"
|
||||
#include "settings/settings.hpp"
|
||||
#include "tools/tools.hpp"
|
||||
#include "input/input.hpp"
|
||||
#include "utility/utility.hpp"
|
||||
#include "path/path.hpp"
|
||||
#include "cartridge/cartridge.hpp"
|
||||
|
||||
#if defined(DEBUGGER)
|
||||
#include "debugger/debugger.hpp"
|
||||
#endif
|
||||
|
||||
struct Application {
|
||||
string proportionalFont;
|
||||
string proportionalFontBold;
|
||||
string monospaceFont;
|
||||
string titleFont;
|
||||
bool compositorActive;
|
||||
|
||||
bool pause;
|
||||
bool quit;
|
||||
void main(int argc, char **argv);
|
||||
|
||||
void addWindow(TopLevelWindow *window, const string &name, const string &position);
|
||||
Application();
|
||||
|
||||
private:
|
||||
array<TopLevelWindow*> windows;
|
||||
configuration geometryConfig;
|
||||
void loadGeometry();
|
||||
void saveGeometry();
|
||||
};
|
||||
|
||||
extern nall::DSP dspaudio;
|
||||
extern Application application;
|
|
@ -0,0 +1,6 @@
|
|||
#include "../base.hpp"
|
||||
#include "main-window.cpp"
|
||||
#include "file-browser.cpp"
|
||||
#include "slot-loader.cpp"
|
||||
#include "nss-dip-window.cpp"
|
||||
#include "about-window.cpp"
|
|
@ -0,0 +1,5 @@
|
|||
#include "main-window.hpp"
|
||||
#include "file-browser.hpp"
|
||||
#include "slot-loader.hpp"
|
||||
#include "nss-dip-window.hpp"
|
||||
#include "about-window.hpp"
|
|
@ -0,0 +1,476 @@
|
|||
MainWindow mainWindow;
|
||||
|
||||
void MainWindow::create() {
|
||||
setTitle({ SNES::Info::Name, " v", SNES::Info::Version });
|
||||
setResizable(false);
|
||||
setGeometry({ 0, 0, 595, 448 });
|
||||
application.addWindow(this, "MainWindow", "128,128");
|
||||
setMenuFont(application.proportionalFont);
|
||||
setStatusFont(application.proportionalFontBold);
|
||||
setBackgroundColor({ 0, 0, 0 });
|
||||
|
||||
system.setText("System");
|
||||
|
||||
systemLoadCartridge.setText("Load Cartridge ...");
|
||||
system.append(systemLoadCartridge);
|
||||
|
||||
systemLoadCartridgeSpecial.setText("Load Special");
|
||||
system.append(systemLoadCartridgeSpecial);
|
||||
|
||||
systemLoadCartridgeBsxSlotted.setText("Load BS-X Slotted Cartridge ...");
|
||||
systemLoadCartridgeSpecial.append(systemLoadCartridgeBsxSlotted);
|
||||
|
||||
systemLoadCartridgeBsx.setText("Load BS-X Cartridge ...");
|
||||
systemLoadCartridgeSpecial.append(systemLoadCartridgeBsx);
|
||||
|
||||
systemLoadCartridgeSufamiTurbo.setText("Load Sufami Turbo Cartridge ...");
|
||||
systemLoadCartridgeSpecial.append(systemLoadCartridgeSufamiTurbo);
|
||||
|
||||
systemLoadCartridgeSuperGameBoy.setText("Load Super Game Boy Cartridge ...");
|
||||
systemLoadCartridgeSpecial.append(systemLoadCartridgeSuperGameBoy);
|
||||
|
||||
system.append(systemSeparator1);
|
||||
|
||||
systemPower.setText("Power Cycle");
|
||||
system.append(systemPower);
|
||||
|
||||
systemReset.setText("Reset");
|
||||
system.append(systemReset);
|
||||
|
||||
system.append(systemSeparator2);
|
||||
|
||||
systemPort1.setText("Controller Port 1");
|
||||
system.append(systemPort1);
|
||||
|
||||
systemPort1None.setText("None");
|
||||
systemPort1.append(systemPort1None);
|
||||
|
||||
systemPort1Gamepad.setText("Gamepad");
|
||||
systemPort1.append(systemPort1Gamepad);
|
||||
|
||||
systemPort1Multitap.setText("Multitap");
|
||||
systemPort1.append(systemPort1Multitap);
|
||||
|
||||
systemPort1Mouse.setText("Mouse");
|
||||
systemPort1.append(systemPort1Mouse);
|
||||
|
||||
RadioItem::group(
|
||||
systemPort1None, systemPort1Gamepad, systemPort1Multitap, systemPort1Mouse
|
||||
);
|
||||
|
||||
systemPort2.setText("Controller Port 2");
|
||||
system.append(systemPort2);
|
||||
|
||||
systemPort2None.setText("None");
|
||||
systemPort2.append(systemPort2None);
|
||||
|
||||
systemPort2Gamepad.setText("Gamepad");
|
||||
systemPort2.append(systemPort2Gamepad);
|
||||
|
||||
systemPort2Multitap.setText("Multitap");
|
||||
systemPort2.append(systemPort2Multitap);
|
||||
|
||||
systemPort2Mouse.setText("Mouse");
|
||||
systemPort2.append(systemPort2Mouse);
|
||||
|
||||
systemPort2SuperScope.setText("Super Scope");
|
||||
systemPort2.append(systemPort2SuperScope);
|
||||
|
||||
systemPort2Justifier.setText("Justifier");
|
||||
systemPort2.append(systemPort2Justifier);
|
||||
|
||||
systemPort2Justifiers.setText("Justifiers");
|
||||
systemPort2.append(systemPort2Justifiers);
|
||||
|
||||
systemPort2Serial.setText("Serial Cable");
|
||||
systemPort2.append(systemPort2Serial);
|
||||
|
||||
RadioItem::group(
|
||||
systemPort2None, systemPort2Gamepad, systemPort2Multitap, systemPort2Mouse,
|
||||
systemPort2SuperScope, systemPort2Justifier, systemPort2Justifiers,
|
||||
systemPort2Serial
|
||||
);
|
||||
|
||||
append(system);
|
||||
|
||||
settings.setText("Settings");
|
||||
|
||||
settingsVideoMode.setText("Video Mode");
|
||||
settings.append(settingsVideoMode);
|
||||
|
||||
settingsVideoMode1x.setText("Scale 1x");
|
||||
settingsVideoMode.append(settingsVideoMode1x);
|
||||
|
||||
settingsVideoMode2x.setText("Scale 2x");
|
||||
settingsVideoMode.append(settingsVideoMode2x);
|
||||
|
||||
settingsVideoMode3x.setText("Scale 3x");
|
||||
settingsVideoMode.append(settingsVideoMode3x);
|
||||
|
||||
settingsVideoMode4x.setText("Scale 4x");
|
||||
settingsVideoMode.append(settingsVideoMode4x);
|
||||
|
||||
settingsVideoMode5x.setText("Scale 5x");
|
||||
settingsVideoMode.append(settingsVideoMode5x);
|
||||
|
||||
RadioItem::group(
|
||||
settingsVideoMode1x, settingsVideoMode2x, settingsVideoMode3x, settingsVideoMode4x, settingsVideoMode5x
|
||||
);
|
||||
|
||||
settingsVideoMode.append(settingsVideoModeSeparator1);
|
||||
|
||||
settingsVideoModeAspectRatioCorrection.setText("Correct Aspect Ratio");
|
||||
settingsVideoMode.append(settingsVideoModeAspectRatioCorrection);
|
||||
|
||||
settingsVideoModeSmoothVideo.setText("Smooth Video");
|
||||
settingsVideoMode.append(settingsVideoModeSmoothVideo);
|
||||
|
||||
settingsVideoMode.append(settingsVideoModeSeparator2);
|
||||
|
||||
settingsVideoModeNTSC.setText("NTSC");
|
||||
settingsVideoMode.append(settingsVideoModeNTSC);
|
||||
|
||||
settingsVideoModePAL.setText("PAL");
|
||||
settingsVideoMode.append(settingsVideoModePAL);
|
||||
|
||||
setupFiltersAndShaders();
|
||||
|
||||
RadioItem::group(
|
||||
settingsVideoModeNTSC, settingsVideoModePAL
|
||||
);
|
||||
|
||||
settings.append(settingsSeparator1);
|
||||
|
||||
settingsSynchronizeVideo.setText("Synchronize Video");
|
||||
settings.append(settingsSynchronizeVideo);
|
||||
|
||||
settingsSynchronizeAudio.setText("Synchronize Audio");
|
||||
settings.append(settingsSynchronizeAudio);
|
||||
|
||||
settingsMuteAudio.setText("Mute Audio");
|
||||
settings.append(settingsMuteAudio);
|
||||
|
||||
settings.append(settingsSeparator2);
|
||||
|
||||
settingsConfiguration.setText("Configuration Settings ...");
|
||||
settings.append(settingsConfiguration);
|
||||
|
||||
append(settings);
|
||||
|
||||
tools.setText("Tools");
|
||||
|
||||
toolsStateSave.setText("Save State");
|
||||
tools.append(toolsStateSave);
|
||||
|
||||
toolsStateSave1.setText("Slot 1");
|
||||
toolsStateSave.append(toolsStateSave1);
|
||||
|
||||
toolsStateSave2.setText("Slot 2");
|
||||
toolsStateSave.append(toolsStateSave2);
|
||||
|
||||
toolsStateSave3.setText("Slot 3");
|
||||
toolsStateSave.append(toolsStateSave3);
|
||||
|
||||
toolsStateSave4.setText("Slot 4");
|
||||
toolsStateSave.append(toolsStateSave4);
|
||||
|
||||
toolsStateSave5.setText("Slot 5");
|
||||
toolsStateSave.append(toolsStateSave5);
|
||||
|
||||
toolsStateLoad.setText("Load State");
|
||||
tools.append(toolsStateLoad);
|
||||
|
||||
toolsStateLoad1.setText("Slot 1");
|
||||
toolsStateLoad.append(toolsStateLoad1);
|
||||
|
||||
toolsStateLoad2.setText("Slot 2");
|
||||
toolsStateLoad.append(toolsStateLoad2);
|
||||
|
||||
toolsStateLoad3.setText("Slot 3");
|
||||
toolsStateLoad.append(toolsStateLoad3);
|
||||
|
||||
toolsStateLoad4.setText("Slot 4");
|
||||
toolsStateLoad.append(toolsStateLoad4);
|
||||
|
||||
toolsStateLoad5.setText("Slot 5");
|
||||
toolsStateLoad.append(toolsStateLoad5);
|
||||
|
||||
tools.append(toolsSeparator1);
|
||||
|
||||
toolsCaptureScreenshot.setText("Capture Screenshot");
|
||||
tools.append(toolsCaptureScreenshot);
|
||||
|
||||
toolsCheatEditor.setText("Cheat Editor ...");
|
||||
tools.append(toolsCheatEditor);
|
||||
|
||||
toolsStateManager.setText("State Manager ...");
|
||||
tools.append(toolsStateManager);
|
||||
|
||||
#if defined(DEBUGGER)
|
||||
tools.append(toolsSeparator2);
|
||||
|
||||
toolsDebugger.setText("Debugger ...");
|
||||
tools.append(toolsDebugger);
|
||||
#endif
|
||||
|
||||
append(tools);
|
||||
|
||||
help.setText("Help");
|
||||
|
||||
helpAbout.setText("About ...");
|
||||
help.append(helpAbout);
|
||||
|
||||
append(help);
|
||||
|
||||
if(config.controller.port1 == 0) systemPort1None.setChecked();
|
||||
if(config.controller.port1 == 1) systemPort1Gamepad.setChecked();
|
||||
if(config.controller.port1 == 2) systemPort1Multitap.setChecked();
|
||||
if(config.controller.port1 == 3) systemPort1Mouse.setChecked();
|
||||
if(config.controller.port2 == 0) systemPort2None.setChecked();
|
||||
if(config.controller.port2 == 1) systemPort2Gamepad.setChecked();
|
||||
if(config.controller.port2 == 2) systemPort2Multitap.setChecked();
|
||||
if(config.controller.port2 == 3) systemPort2Mouse.setChecked();
|
||||
if(config.controller.port2 == 4) systemPort2SuperScope.setChecked();
|
||||
if(config.controller.port2 == 5) systemPort2Justifier.setChecked();
|
||||
if(config.controller.port2 == 6) systemPort2Justifiers.setChecked();
|
||||
if(config.controller.port2 == 7) systemPort2Serial.setChecked();
|
||||
|
||||
if(config.video.scale == 1) settingsVideoMode1x.setChecked();
|
||||
if(config.video.scale == 2) settingsVideoMode2x.setChecked();
|
||||
if(config.video.scale == 3) settingsVideoMode3x.setChecked();
|
||||
if(config.video.scale == 4) settingsVideoMode4x.setChecked();
|
||||
if(config.video.scale == 5) settingsVideoMode5x.setChecked();
|
||||
settingsVideoModeAspectRatioCorrection.setChecked(config.video.aspectRatioCorrection);
|
||||
settingsVideoModeSmoothVideo.setChecked(config.video.smooth);
|
||||
if(config.video.region == 0) settingsVideoModeNTSC.setChecked();
|
||||
if(config.video.region == 1) settingsVideoModePAL.setChecked();
|
||||
settingsSynchronizeVideo.setChecked(config.video.synchronize);
|
||||
settingsSynchronizeAudio.setChecked(config.audio.synchronize);
|
||||
settingsMuteAudio.setChecked(config.audio.mute);
|
||||
|
||||
layout.append(viewport, { 0, 0, 595, 448 });
|
||||
append(layout);
|
||||
|
||||
utility.setStatus("");
|
||||
setMenuVisible(true);
|
||||
setStatusVisible(true);
|
||||
|
||||
systemLoadCartridge.onTick = [] {
|
||||
fileBrowser.fileOpen(FileBrowser::Mode::Cartridge, [](string filename) {
|
||||
cartridge.loadNormal(filename);
|
||||
});
|
||||
};
|
||||
|
||||
systemLoadCartridgeBsxSlotted.onTick = [] { singleSlotLoader.loadCartridgeBsxSlotted(); };
|
||||
systemLoadCartridgeBsx.onTick = [] { singleSlotLoader.loadCartridgeBsx(); };
|
||||
systemLoadCartridgeSufamiTurbo.onTick = [] { doubleSlotLoader.loadCartridgeSufamiTurbo(); };
|
||||
systemLoadCartridgeSuperGameBoy.onTick = [] { singleSlotLoader.loadCartridgeSuperGameBoy(); };
|
||||
|
||||
systemPower.onTick = [] {
|
||||
SNES::system.power();
|
||||
utility.showMessage("System was power cycled");
|
||||
};
|
||||
|
||||
systemReset.onTick = [] {
|
||||
SNES::system.reset();
|
||||
utility.showMessage("System was reset");
|
||||
};
|
||||
|
||||
systemPort1None.onTick = [] { config.controller.port1 = 0; utility.setControllers(); };
|
||||
systemPort1Gamepad.onTick = [] { config.controller.port1 = 1; utility.setControllers(); };
|
||||
systemPort1Multitap.onTick = [] { config.controller.port1 = 2; utility.setControllers(); };
|
||||
systemPort1Mouse.onTick = [] { config.controller.port1 = 3; utility.setControllers(); };
|
||||
|
||||
systemPort2None.onTick = [] { config.controller.port2 = 0; utility.setControllers(); };
|
||||
systemPort2Gamepad.onTick = [] { config.controller.port2 = 1; utility.setControllers(); };
|
||||
systemPort2Multitap.onTick = [] { config.controller.port2 = 2; utility.setControllers(); };
|
||||
systemPort2Mouse.onTick = [] { config.controller.port2 = 3; utility.setControllers(); };
|
||||
systemPort2SuperScope.onTick = [] { config.controller.port2 = 4; utility.setControllers(); };
|
||||
systemPort2Justifier.onTick = [] { config.controller.port2 = 5; utility.setControllers(); };
|
||||
systemPort2Justifiers.onTick = [] { config.controller.port2 = 6; utility.setControllers(); };
|
||||
systemPort2Serial.onTick = [] { config.controller.port2 = 7; utility.setControllers(); };
|
||||
|
||||
settingsVideoMode1x.onTick = [] { utility.setScale(1); };
|
||||
settingsVideoMode2x.onTick = [] { utility.setScale(2); };
|
||||
settingsVideoMode3x.onTick = [] { utility.setScale(3); };
|
||||
settingsVideoMode4x.onTick = [] { utility.setScale(4); };
|
||||
settingsVideoMode5x.onTick = [] { utility.setScale(5); };
|
||||
|
||||
settingsVideoModeAspectRatioCorrection.onTick = [] {
|
||||
config.video.aspectRatioCorrection = mainWindow.settingsVideoModeAspectRatioCorrection.checked();
|
||||
utility.setScale();
|
||||
};
|
||||
|
||||
settingsVideoModeSmoothVideo.onTick = [] {
|
||||
config.video.smooth = mainWindow.settingsVideoModeSmoothVideo.checked();
|
||||
video.set(Video::Filter, (unsigned)config.video.smooth);
|
||||
};
|
||||
|
||||
settingsVideoModeNTSC.onTick = [] { config.video.region = 0; utility.setScale(); };
|
||||
settingsVideoModePAL.onTick = [] { config.video.region = 1; utility.setScale(); };
|
||||
|
||||
settingsVideoFilterNone.onTick = [] {
|
||||
config.video.filter = "";
|
||||
utility.setFilter();
|
||||
};
|
||||
|
||||
settingsVideoShaderNone.onTick = [] {
|
||||
config.video.shader = "";
|
||||
utility.setShader();
|
||||
};
|
||||
|
||||
settingsSynchronizeVideo.onTick = [] {
|
||||
config.video.synchronize = mainWindow.settingsSynchronizeVideo.checked();
|
||||
video.set(Video::Synchronize, config.video.synchronize);
|
||||
};
|
||||
|
||||
settingsSynchronizeAudio.onTick = [] {
|
||||
config.audio.synchronize = mainWindow.settingsSynchronizeAudio.checked();
|
||||
audio.set(Audio::Synchronize, config.audio.synchronize);
|
||||
};
|
||||
|
||||
settingsMuteAudio.onTick = [] { config.audio.mute = mainWindow.settingsMuteAudio.checked(); };
|
||||
|
||||
settingsConfiguration.onTick = [] {
|
||||
settingsWindow.setVisible();
|
||||
settingsWindow.panel.setFocused();
|
||||
};
|
||||
|
||||
toolsStateSave1.onTick = [] { utility.saveState(1); };
|
||||
toolsStateSave2.onTick = [] { utility.saveState(2); };
|
||||
toolsStateSave3.onTick = [] { utility.saveState(3); };
|
||||
toolsStateSave4.onTick = [] { utility.saveState(4); };
|
||||
toolsStateSave5.onTick = [] { utility.saveState(5); };
|
||||
|
||||
toolsStateLoad1.onTick = [] { utility.loadState(1); };
|
||||
toolsStateLoad2.onTick = [] { utility.loadState(2); };
|
||||
toolsStateLoad3.onTick = [] { utility.loadState(3); };
|
||||
toolsStateLoad4.onTick = [] { utility.loadState(4); };
|
||||
toolsStateLoad5.onTick = [] { utility.loadState(5); };
|
||||
|
||||
toolsCaptureScreenshot.onTick = [] { interface.captureScreenshot = true; };
|
||||
toolsCheatEditor.onTick = [] { cheatEditor.setVisible(); };
|
||||
toolsStateManager.onTick = [] { stateManager.setVisible(); };
|
||||
|
||||
#if defined(DEBUGGER)
|
||||
toolsDebugger.onTick = [] { debugger.setVisible(); };
|
||||
#endif
|
||||
|
||||
helpAbout.onTick = [] {
|
||||
aboutWindow.show();
|
||||
};
|
||||
|
||||
onClose = [] {
|
||||
application.quit = true;
|
||||
};
|
||||
|
||||
synchronize();
|
||||
}
|
||||
|
||||
void MainWindow::synchronize() {
|
||||
bool loaded = SNES::cartridge.loaded();
|
||||
systemPower.setEnabled(loaded);
|
||||
systemReset.setEnabled(loaded);
|
||||
toolsStateSave.setEnabled(loaded);
|
||||
toolsStateLoad.setEnabled(loaded);
|
||||
toolsCaptureScreenshot.setEnabled(loaded);
|
||||
}
|
||||
|
||||
void MainWindow::setupFiltersAndShaders() {
|
||||
string folderPath;
|
||||
lstring files;
|
||||
reference_array<RadioItem&> group;
|
||||
signed active;
|
||||
|
||||
settingsVideoFilter.setText("Video Filter");
|
||||
|
||||
settingsVideoFilterNone.setText("None");
|
||||
settingsVideoFilter.append(settingsVideoFilterNone);
|
||||
|
||||
settingsVideoFilter.append(settingsVideoFilterSeparator);
|
||||
|
||||
group.append(settingsVideoFilterNone);
|
||||
active = -1;
|
||||
|
||||
folderPath = { path.base, "filters/" };
|
||||
files = directory::files(folderPath, "*.filter");
|
||||
if(files.size() == 0) {
|
||||
#if defined(PLATFORM_X) || defined(PLATFORM_OSX)
|
||||
folderPath = { path.user, ".config/bsnes/filters/" };
|
||||
#else
|
||||
folderPath = { path.user, "bsnes/filters/" };
|
||||
#endif
|
||||
files = directory::files(folderPath, "*.filter");
|
||||
}
|
||||
foreach(filename, files) {
|
||||
settingsVideoFilterName.append({ folderPath, filename });
|
||||
}
|
||||
|
||||
if(settingsVideoFilterName.size() == 0) {
|
||||
config.video.filter = ""; //as the list (and thus the 'None' option) is invisible,
|
||||
utility.setFilter(); //erase any previously saved filter name
|
||||
} else {
|
||||
settingsVideoFilterItem = new RadioItem[settingsVideoFilterName.size()];
|
||||
foreach(filename, settingsVideoFilterName, n) {
|
||||
settingsVideoFilterItem[n].onTick = [n]() {
|
||||
config.video.filter = mainWindow.settingsVideoFilterName[n];
|
||||
utility.setFilter();
|
||||
};
|
||||
settingsVideoFilterItem[n].setText(nall::basename(notdir(filename)));
|
||||
settingsVideoFilter.append(settingsVideoFilterItem[n]);
|
||||
group.append(settingsVideoFilterItem[n]);
|
||||
if(filename == config.video.filter) active = n;
|
||||
}
|
||||
|
||||
RadioItem::group(group);
|
||||
group.reset();
|
||||
active < 0 ? settingsVideoFilterNone.setChecked() : settingsVideoFilterItem[active].setChecked();
|
||||
settings.append(settingsVideoFilter);
|
||||
}
|
||||
|
||||
settingsVideoShader.setText("Video Shader");
|
||||
|
||||
settingsVideoShaderNone.setText("None");
|
||||
settingsVideoShader.append(settingsVideoShaderNone);
|
||||
|
||||
settingsVideoShader.append(settingsVideoShaderSeparator);
|
||||
|
||||
group.append(settingsVideoShaderNone);
|
||||
active = -1;
|
||||
|
||||
folderPath = { path.base, "shaders/" };
|
||||
files = directory::files(folderPath, { "*.", config.video.driver, ".shader" });
|
||||
if(files.size() == 0) {
|
||||
#if defined(PLATFORM_X) || defined(PLATFORM_OSX)
|
||||
folderPath = { path.user, ".config/bsnes/shaders/" };
|
||||
#else
|
||||
folderPath = { path.user, "bsnes/shaders/" };
|
||||
#endif
|
||||
files = directory::files(folderPath, { "*.", config.video.driver, ".shader" });
|
||||
}
|
||||
foreach(filename, files) {
|
||||
settingsVideoShaderName.append({ folderPath, filename });
|
||||
}
|
||||
|
||||
if(settingsVideoShaderName.size() == 0) {
|
||||
config.video.shader = "";
|
||||
utility.setShader();
|
||||
} else {
|
||||
settingsVideoShaderItem = new RadioItem[settingsVideoShaderName.size()];
|
||||
foreach(filename, settingsVideoShaderName, n) {
|
||||
settingsVideoShaderItem[n].onTick = [n]() {
|
||||
config.video.shader = mainWindow.settingsVideoShaderName[n];
|
||||
utility.setShader();
|
||||
};
|
||||
settingsVideoShaderItem[n].setText(nall::basename(nall::basename(notdir(filename))));
|
||||
settingsVideoShader.append(settingsVideoShaderItem[n]);
|
||||
group.append(settingsVideoShaderItem[n]);
|
||||
if(filename == config.video.shader) active = n;
|
||||
}
|
||||
|
||||
RadioItem::group(group);
|
||||
group.reset();
|
||||
active < 0 ? settingsVideoShaderNone.setChecked() : settingsVideoShaderItem[active].setChecked();
|
||||
settings.append(settingsVideoShader);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,94 @@
|
|||
struct MainWindow : TopLevelWindow {
|
||||
Menu system;
|
||||
Item systemLoadCartridge;
|
||||
Menu systemLoadCartridgeSpecial;
|
||||
Item systemLoadCartridgeBsxSlotted;
|
||||
Item systemLoadCartridgeBsx;
|
||||
Item systemLoadCartridgeSufamiTurbo;
|
||||
Item systemLoadCartridgeSuperGameBoy;
|
||||
Separator systemSeparator1;
|
||||
Item systemPower;
|
||||
Item systemReset;
|
||||
Separator systemSeparator2;
|
||||
Menu systemPort1;
|
||||
RadioItem systemPort1None;
|
||||
RadioItem systemPort1Gamepad;
|
||||
RadioItem systemPort1Multitap;
|
||||
RadioItem systemPort1Mouse;
|
||||
Menu systemPort2;
|
||||
RadioItem systemPort2None;
|
||||
RadioItem systemPort2Gamepad;
|
||||
RadioItem systemPort2Multitap;
|
||||
RadioItem systemPort2Mouse;
|
||||
RadioItem systemPort2SuperScope;
|
||||
RadioItem systemPort2Justifier;
|
||||
RadioItem systemPort2Justifiers;
|
||||
RadioItem systemPort2Serial;
|
||||
|
||||
Menu settings;
|
||||
Menu settingsVideoMode;
|
||||
RadioItem settingsVideoMode1x;
|
||||
RadioItem settingsVideoMode2x;
|
||||
RadioItem settingsVideoMode3x;
|
||||
RadioItem settingsVideoMode4x;
|
||||
RadioItem settingsVideoMode5x;
|
||||
Separator settingsVideoModeSeparator1;
|
||||
CheckItem settingsVideoModeAspectRatioCorrection;
|
||||
CheckItem settingsVideoModeSmoothVideo;
|
||||
Separator settingsVideoModeSeparator2;
|
||||
RadioItem settingsVideoModeNTSC;
|
||||
RadioItem settingsVideoModePAL;
|
||||
|
||||
Menu settingsVideoFilter;
|
||||
RadioItem settingsVideoFilterNone;
|
||||
Separator settingsVideoFilterSeparator;
|
||||
RadioItem *settingsVideoFilterItem;
|
||||
lstring settingsVideoFilterName;
|
||||
|
||||
Menu settingsVideoShader;
|
||||
RadioItem settingsVideoShaderNone;
|
||||
Separator settingsVideoShaderSeparator;
|
||||
RadioItem *settingsVideoShaderItem;
|
||||
lstring settingsVideoShaderName;
|
||||
|
||||
Separator settingsSeparator1;
|
||||
CheckItem settingsSynchronizeVideo;
|
||||
CheckItem settingsSynchronizeAudio;
|
||||
CheckItem settingsMuteAudio;
|
||||
Separator settingsSeparator2;
|
||||
Item settingsConfiguration;
|
||||
|
||||
Menu tools;
|
||||
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 toolsSeparator1;
|
||||
Item toolsCaptureScreenshot;
|
||||
Item toolsCheatEditor;
|
||||
Item toolsStateManager;
|
||||
#if defined(DEBUGGER)
|
||||
Separator toolsSeparator2;
|
||||
Item toolsDebugger;
|
||||
#endif
|
||||
|
||||
Menu help;
|
||||
Item helpAbout;
|
||||
|
||||
FixedLayout layout;
|
||||
Viewport viewport;
|
||||
|
||||
void create();
|
||||
void synchronize();
|
||||
void setupFiltersAndShaders();
|
||||
};
|
||||
|
||||
extern MainWindow mainWindow;
|
|
@ -0,0 +1,197 @@
|
|||
#include "base.hpp"
|
||||
#include "interface.cpp"
|
||||
#include "config.cpp"
|
||||
nall::DSP dspaudio;
|
||||
Application application;
|
||||
|
||||
void Application::main(int argc, char **argv) {
|
||||
#if defined(PLATFORM_WIN)
|
||||
utf8_args(argc, argv);
|
||||
#endif
|
||||
|
||||
compositorActive = compositor::enabled();
|
||||
|
||||
config.create();
|
||||
inputMapper.create();
|
||||
|
||||
path.base = dir(realpath(argv[0]));
|
||||
path.user = userpath();
|
||||
path.load();
|
||||
path.save();
|
||||
|
||||
config.load();
|
||||
config.save();
|
||||
|
||||
inputMapper.bind();
|
||||
|
||||
#if defined(PLATFORM_WIN)
|
||||
proportionalFont = "Tahoma, 8";
|
||||
proportionalFontBold = "Tahoma, 8, Bold";
|
||||
monospaceFont = "Lucida Console, 8";
|
||||
titleFont = "Segoe Print, 16, Bold";
|
||||
#else
|
||||
proportionalFont = "Sans, 8";
|
||||
proportionalFontBold = "Sans, 8, Bold";
|
||||
monospaceFont = "Liberation Mono, 8";
|
||||
titleFont = "Sans, 16, Bold Italic";
|
||||
#endif
|
||||
|
||||
SNES::system.init(&interface);
|
||||
|
||||
if(config.video.driver == "") config.video.driver = video.default_driver();
|
||||
if(config.audio.driver == "") config.audio.driver = audio.default_driver();
|
||||
if(config.input.driver == "") config.input.driver = input.default_driver();
|
||||
|
||||
palette.update();
|
||||
|
||||
mainWindow.create();
|
||||
fileBrowser.create();
|
||||
singleSlotLoader.create();
|
||||
doubleSlotLoader.create();
|
||||
nssDipWindow.create();
|
||||
aboutWindow.create();
|
||||
settingsWindow.create();
|
||||
cheatEditor.create();
|
||||
cheatDatabase.create();
|
||||
stateManager.create();
|
||||
#if defined(DEBUGGER)
|
||||
debugger.create();
|
||||
#endif
|
||||
|
||||
loadGeometry();
|
||||
saveGeometry();
|
||||
|
||||
utility.setScale(config.video.scale);
|
||||
mainWindow.setVisible();
|
||||
OS::processEvents();
|
||||
|
||||
video.driver(config.video.driver);
|
||||
video.set(Video::Handle, mainWindow.viewport.handle());
|
||||
video.set(Video::Synchronize, config.video.synchronize);
|
||||
video.set(Video::Filter, (unsigned)config.video.smooth);
|
||||
if(video.init() == false) {
|
||||
MessageWindow::critical(mainWindow, "Failed to initialize video.");
|
||||
video.driver("None");
|
||||
video.init();
|
||||
}
|
||||
|
||||
audio.driver(config.audio.driver);
|
||||
audio.set(Audio::Handle, mainWindow.viewport.handle());
|
||||
audio.set(Audio::Synchronize, config.audio.synchronize);
|
||||
audio.set(Audio::Latency, config.audio.latency);
|
||||
audio.set(Audio::Frequency, config.audio.outputFrequency);
|
||||
if(audio.init() == false) {
|
||||
MessageWindow::critical(mainWindow, "Failed to initialize audio.");
|
||||
audio.driver("None");
|
||||
audio.init();
|
||||
}
|
||||
|
||||
dspaudio.setPrecision(16); //16-bit signed audio
|
||||
dspaudio.setVolume((double)config.audio.volume / 100.0);
|
||||
dspaudio.setBalance((double)((signed)config.audio.balance - 100) / 100.0);
|
||||
dspaudio.setFrequency(config.audio.inputFrequency);
|
||||
dspaudio.setResampler(DSP::Resampler::Hermite);
|
||||
dspaudio.setResamplerFrequency(config.audio.outputFrequency);
|
||||
|
||||
input.driver(config.input.driver);
|
||||
input.set(Input::Handle, mainWindow.viewport.handle());
|
||||
if(input.init() == false) {
|
||||
MessageWindow::critical(mainWindow, "Failed to initialize input.");
|
||||
input.driver("None");
|
||||
input.init();
|
||||
}
|
||||
|
||||
utility.setControllers();
|
||||
utility.setFilter();
|
||||
utility.setShader();
|
||||
if(config.settings.startFullScreen) utility.setFullScreen();
|
||||
|
||||
if(argc == 2) cartridge.loadNormal(argv[1]);
|
||||
|
||||
while(quit == false) {
|
||||
OS::processEvents();
|
||||
inputMapper.poll();
|
||||
utility.updateStatus();
|
||||
|
||||
if(SNES::cartridge.loaded()) {
|
||||
if(application.pause == true || (config.settings.focusPolicy == 0 && mainWindow.focused() == false)) {
|
||||
audio.clear();
|
||||
usleep(20 * 1000);
|
||||
continue;
|
||||
}
|
||||
#if defined(DEBUGGER)
|
||||
debugger.run();
|
||||
#else
|
||||
SNES::system.run();
|
||||
#endif
|
||||
} else {
|
||||
usleep(20 * 1000);
|
||||
}
|
||||
}
|
||||
|
||||
cartridge.unload();
|
||||
utility.setFullScreen(false);
|
||||
saveGeometry();
|
||||
foreach(window, windows) window->setVisible(false);
|
||||
OS::processEvents();
|
||||
SNES::system.term();
|
||||
|
||||
path.save();
|
||||
config.save();
|
||||
|
||||
video.term();
|
||||
audio.term();
|
||||
input.term();
|
||||
|
||||
if(compositorActive) compositor::enable(true);
|
||||
}
|
||||
|
||||
void Application::addWindow(TopLevelWindow *window, const string &name, const string &position) {
|
||||
windows.append(window);
|
||||
window->name = name;
|
||||
window->position = position;
|
||||
window->setWidgetFont(proportionalFont);
|
||||
geometryConfig.attach(window->position, window->name);
|
||||
}
|
||||
|
||||
Application::Application() {
|
||||
pause = false;
|
||||
quit = false;
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
application.main(argc, argv);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void Application::loadGeometry() {
|
||||
geometryConfig.load(path.home("geometry.cfg"));
|
||||
foreach(window, windows) {
|
||||
lstring position;
|
||||
position.split(",", window->position);
|
||||
Geometry configGeometry = {
|
||||
(signed)integer(position[0]), (signed)integer(position[1]),
|
||||
(unsigned)decimal(position[2]), (unsigned)decimal(position[3])
|
||||
};
|
||||
Geometry windowGeometry = window->geometry();
|
||||
|
||||
//Windows places minimized windows offscreen at 32000,32000
|
||||
//this is a fix for older releases that did not compensate for this
|
||||
if(configGeometry.x >= 30000) configGeometry.x = 128;
|
||||
if(configGeometry.y >= 30000) configGeometry.y = 128;
|
||||
|
||||
window->setGeometry({
|
||||
configGeometry.x, configGeometry.y,
|
||||
windowGeometry.width, windowGeometry.height
|
||||
//configGeometry.width, configGeometry.height
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void Application::saveGeometry() {
|
||||
foreach(window, windows) {
|
||||
Geometry geom = window->geometry();
|
||||
window->position = { geom.x, ",", geom.y, ",", geom.width, ",", geom.height };
|
||||
}
|
||||
geometryConfig.save(path.home("geometry.cfg"));
|
||||
}
|
|
@ -0,0 +1,2 @@
|
|||
1 24 "../data/bsnes.Manifest"
|
||||
2 ICON DISCARDABLE "../data/bsnes.ico"
|
|
@ -0,0 +1,246 @@
|
|||
#include "../base.hpp"
|
||||
Utility utility;
|
||||
|
||||
void Utility::setTitle(const string &text) {
|
||||
if(*text) {
|
||||
mainWindow.setTitle({ text, " - ", SNES::Info::Name, " v", SNES::Info::Version });
|
||||
} else {
|
||||
mainWindow.setTitle({ SNES::Info::Name, " v", SNES::Info::Version });
|
||||
}
|
||||
}
|
||||
|
||||
void Utility::updateStatus() {
|
||||
time_t currentTime = time(0);
|
||||
string text;
|
||||
if((currentTime - statusTime) <= 3) {
|
||||
text = statusMessage;
|
||||
} else if(SNES::cartridge.loaded() == false) {
|
||||
text = "No cartridge loaded";
|
||||
} else if(application.pause) {
|
||||
text = "Paused";
|
||||
} else if(config.settings.focusPolicy == 0 && mainWindow.focused() == false) {
|
||||
text = "Auto-paused";
|
||||
} else {
|
||||
text = statusText;
|
||||
}
|
||||
if(text != statusCurrentText) {
|
||||
mainWindow.setStatusText(statusCurrentText = text);
|
||||
}
|
||||
}
|
||||
|
||||
void Utility::setStatus(const string &text) {
|
||||
static char profile[] = { '[', SNES::Info::Profile[0], ']', ' ', 0 };
|
||||
statusText = { profile, text };
|
||||
}
|
||||
|
||||
void Utility::showMessage(const string &text) {
|
||||
statusMessage = text;
|
||||
statusTime = time(0);
|
||||
}
|
||||
|
||||
void Utility::setControllers() {
|
||||
switch(config.controller.port1) {
|
||||
case 0: SNES::input.connect(SNES::Controller::Port1, SNES::Input::Device::None); break;
|
||||
case 1: SNES::input.connect(SNES::Controller::Port1, SNES::Input::Device::Joypad); break;
|
||||
case 2: SNES::input.connect(SNES::Controller::Port1, SNES::Input::Device::Multitap); break;
|
||||
case 3: SNES::input.connect(SNES::Controller::Port1, SNES::Input::Device::Mouse); break;
|
||||
}
|
||||
|
||||
switch(config.controller.port2) {
|
||||
case 0: SNES::input.connect(SNES::Controller::Port2, SNES::Input::Device::None); break;
|
||||
case 1: SNES::input.connect(SNES::Controller::Port2, SNES::Input::Device::Joypad); break;
|
||||
case 2: SNES::input.connect(SNES::Controller::Port2, SNES::Input::Device::Multitap); break;
|
||||
case 3: SNES::input.connect(SNES::Controller::Port2, SNES::Input::Device::Mouse); break;
|
||||
case 4: SNES::input.connect(SNES::Controller::Port2, SNES::Input::Device::SuperScope); break;
|
||||
case 5: SNES::input.connect(SNES::Controller::Port2, SNES::Input::Device::Justifier); break;
|
||||
case 6: SNES::input.connect(SNES::Controller::Port2, SNES::Input::Device::Justifiers); break;
|
||||
case 7: SNES::input.connect(SNES::Controller::Port2, SNES::Input::Device::Serial); break;
|
||||
}
|
||||
}
|
||||
|
||||
void Utility::setScale(unsigned scale) {
|
||||
if(scale == 0) scale = config.video.scale;
|
||||
config.video.scale = scale;
|
||||
unsigned width, height;
|
||||
if(config.video.region == 0) {
|
||||
width = 256 * scale;
|
||||
height = 224 * scale;
|
||||
if(config.video.aspectRatioCorrection) width *= 54.0 / 47.0;
|
||||
} else {
|
||||
width = 256 * scale;
|
||||
height = 239 * scale;
|
||||
if(config.video.aspectRatioCorrection) width *= 32.0 / 23.0;
|
||||
}
|
||||
|
||||
viewportX = 0;
|
||||
viewportY = 0;
|
||||
viewportWidth = width;
|
||||
viewportHeight = height;
|
||||
|
||||
mainWindow.viewport.setGeometry({ 0, 0, width, height });
|
||||
Geometry geom = mainWindow.geometry();
|
||||
mainWindow.setGeometry({ geom.x, geom.y, width, height });
|
||||
}
|
||||
|
||||
void Utility::setFullScreen(bool fullScreen) {
|
||||
this->fullScreen = fullScreen;
|
||||
|
||||
mainWindow.setMenuVisible(!fullScreen);
|
||||
mainWindow.setStatusVisible(!fullScreen);
|
||||
mainWindow.setFullScreen(fullScreen);
|
||||
if(fullScreen == false) {
|
||||
input.unacquire();
|
||||
setScale();
|
||||
} else {
|
||||
input.acquire();
|
||||
Geometry desktop = OS::desktopGeometry();
|
||||
unsigned width, height;
|
||||
switch(config.video.fullscreenScale) { default:
|
||||
case 0: { //center (even multiple of base height)
|
||||
unsigned baseHeight = config.video.region == 0 ? 224 : 239;
|
||||
unsigned heightScale = desktop.height / baseHeight;
|
||||
height = baseHeight * heightScale;
|
||||
width = 256 * heightScale;
|
||||
if(config.video.region == 0 && config.video.aspectRatioCorrection) width *= 54.0 / 47.0;
|
||||
if(config.video.region == 1 && config.video.aspectRatioCorrection) width *= 32.0 / 23.0;
|
||||
width = min(width, desktop.width);
|
||||
break;
|
||||
}
|
||||
|
||||
case 1: { //scale (100% screen height, aspect-corrected width)
|
||||
unsigned baseHeight = config.video.region == 0 ? 224 : 239;
|
||||
height = desktop.height;
|
||||
width = 256.0 / baseHeight * height;
|
||||
if(config.video.region == 0 && config.video.aspectRatioCorrection) width *= 54.0 / 47.0;
|
||||
if(config.video.region == 1 && config.video.aspectRatioCorrection) width *= 32.0 / 23.0;
|
||||
width = min(width, desktop.width);
|
||||
break;
|
||||
}
|
||||
|
||||
case 2: { //stretch (100% screen width and 100% screen height)
|
||||
width = desktop.width;
|
||||
height = desktop.height;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
viewportX = (desktop.width - width) / 2;
|
||||
viewportY = (desktop.height - height) / 2;
|
||||
viewportWidth = width;
|
||||
viewportHeight = height;
|
||||
|
||||
mainWindow.viewport.setGeometry({ viewportX, viewportY, viewportWidth, viewportHeight });
|
||||
}
|
||||
|
||||
if(application.compositorActive) {
|
||||
if(advancedSettings.compositorPolicyFullScreen.checked()) {
|
||||
compositor::enable(fullScreen == false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Utility::setFilter() {
|
||||
if(filter.opened()) filter.close();
|
||||
if(config.video.filter == "") return;
|
||||
if(filter.open_absolute(config.video.filter)) {
|
||||
filter.dl_size = filter.sym("filter_size");
|
||||
filter.dl_render = filter.sym("filter_render");
|
||||
if(!filter.dl_size || !filter.dl_render) filter.close();
|
||||
}
|
||||
}
|
||||
|
||||
void Utility::setShader() {
|
||||
string data;
|
||||
data.readfile(config.video.shader);
|
||||
video.set(Video::Shader, (const char*)data);
|
||||
}
|
||||
|
||||
void Utility::cartridgeLoaded() {
|
||||
SNES::system.power();
|
||||
cheatEditor.load();
|
||||
stateManager.load();
|
||||
mainWindow.synchronize();
|
||||
|
||||
string name = baseName(activeSlot());
|
||||
utility.setTitle(notdir(name));
|
||||
utility.showMessage({
|
||||
"Loaded ", notdir(name),
|
||||
cartridge.patch.applied ? ", and applied BPS patch" : ""
|
||||
});
|
||||
|
||||
//NSS
|
||||
if(SNES::cartridge.has_nss_dip()) {
|
||||
nssDipWindow.select();
|
||||
application.pause = true;
|
||||
}
|
||||
}
|
||||
|
||||
void Utility::cartridgeUnloaded() {
|
||||
SNES::cartridge.unload();
|
||||
cheatEditor.save();
|
||||
stateManager.save();
|
||||
mainWindow.synchronize();
|
||||
}
|
||||
|
||||
SNES::Cartridge::Slot Utility::activeSlot() {
|
||||
SNES::Cartridge::Slot slot = SNES::Cartridge::Slot::Base;
|
||||
if(SNES::cartridge.mode() == SNES::Cartridge::Mode::Bsx) slot = SNES::Cartridge::Slot::Bsx;
|
||||
if(SNES::cartridge.mode() == SNES::Cartridge::Mode::SufamiTurbo) slot = SNES::Cartridge::Slot::SufamiTurbo;
|
||||
if(SNES::cartridge.mode() == SNES::Cartridge::Mode::SuperGameBoy) slot = SNES::Cartridge::Slot::GameBoy;
|
||||
return slot;
|
||||
}
|
||||
|
||||
string Utility::baseName(SNES::Cartridge::Slot slot) {
|
||||
switch(slot) {
|
||||
default:
|
||||
return cartridge.baseName;
|
||||
case SNES::Cartridge::Slot::Bsx:
|
||||
if(cartridge.bsxName == "") return cartridge.baseName;
|
||||
return cartridge.bsxName;
|
||||
case SNES::Cartridge::Slot::SufamiTurbo:
|
||||
if(cartridge.sufamiTurboAName == "" && cartridge.sufamiTurboBName == "") return cartridge.baseName;
|
||||
if(cartridge.sufamiTurboBName == "") return cartridge.sufamiTurboAName;
|
||||
if(cartridge.sufamiTurboAName == "") return cartridge.sufamiTurboBName;
|
||||
return { cartridge.sufamiTurboAName, "+", cartridge.sufamiTurboBName };
|
||||
case SNES::Cartridge::Slot::GameBoy:
|
||||
if(cartridge.gameBoyName == "") return cartridge.baseName;
|
||||
return cartridge.gameBoyName;
|
||||
}
|
||||
}
|
||||
|
||||
void Utility::saveState(unsigned slot) {
|
||||
string filename = path.load(activeSlot(), { "-", slot, ".bst" });
|
||||
SNES::system.runtosave();
|
||||
serializer s = SNES::system.serialize();
|
||||
file fp;
|
||||
if(fp.open(filename, file::mode::write)) {
|
||||
fp.write(s.data(), s.size());
|
||||
fp.close();
|
||||
showMessage({ "Saved state ", slot });
|
||||
} else {
|
||||
showMessage({ "Failed to save state ", slot });
|
||||
}
|
||||
}
|
||||
|
||||
void Utility::loadState(unsigned slot) {
|
||||
string filename = path.load(activeSlot(), { "-", slot, ".bst" });
|
||||
file fp;
|
||||
if(fp.open(filename, file::mode::read)) {
|
||||
unsigned size = fp.size();
|
||||
uint8_t *data = new uint8_t[size];
|
||||
fp.read(data, size);
|
||||
fp.close();
|
||||
serializer s(data, size);
|
||||
delete[] data;
|
||||
if(SNES::system.unserialize(s) == true) {
|
||||
showMessage({ "Loaded state ", slot });
|
||||
} else {
|
||||
showMessage({ "Failed to load state ", slot });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Utility::Utility() {
|
||||
fullScreen = false;
|
||||
statusTime = 0;
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
struct Utility : property<Utility> {
|
||||
void setTitle(const string &text);
|
||||
void updateStatus();
|
||||
void setStatus(const string &text);
|
||||
void showMessage(const string &text);
|
||||
|
||||
void setControllers();
|
||||
|
||||
void setScale(unsigned scale = 0);
|
||||
void setFullScreen(bool fullScreen = true);
|
||||
|
||||
void setFilter();
|
||||
void setShader();
|
||||
|
||||
void cartridgeLoaded();
|
||||
void cartridgeUnloaded();
|
||||
|
||||
SNES::Cartridge::Slot activeSlot();
|
||||
string baseName(SNES::Cartridge::Slot slot);
|
||||
|
||||
void saveState(unsigned slot);
|
||||
void loadState(unsigned slot);
|
||||
|
||||
Utility();
|
||||
|
||||
bool fullScreen;
|
||||
unsigned viewportX, viewportY;
|
||||
unsigned viewportWidth, viewportHeight;
|
||||
|
||||
private:
|
||||
string statusCurrentText;
|
||||
string statusText;
|
||||
string statusMessage;
|
||||
time_t statusTime;
|
||||
};
|
||||
|
||||
extern Utility utility;
|
|
@ -1,8 +1,10 @@
|
|||
include $(nes)/Makefile
|
||||
include $(snes)/Makefile
|
||||
include $(gameboy)/Makefile
|
||||
name := batch
|
||||
|
||||
ui_objects := ui-main ui-general ui-settings ui-tools ui-input ui-utility ui-path ui-cartridge ui-debugger
|
||||
ui_objects += ruby phoenix
|
||||
ui_objects := ui-main ui-interface ui-utility ui-general
|
||||
ui_objects += phoenix ruby
|
||||
ui_objects += $(if $(call streq,$(platform),win),resource)
|
||||
|
||||
# platform
|
||||
|
@ -65,14 +67,9 @@ objects := $(ui_objects) $(objects)
|
|||
objects := $(patsubst %,obj/%.o,$(objects))
|
||||
|
||||
obj/ui-main.o: $(ui)/main.cpp $(call rwildcard,$(ui)/*.hpp) $(call rwildcard,$(ui)/*)
|
||||
obj/ui-general.o: $(ui)/general/general.cpp $(call rwildcard,$(ui)/*.hpp) $(call rwildcard,$(ui)/general/*)
|
||||
obj/ui-tools.o: $(ui)/tools/tools.cpp $(call rwildcard,$(ui)/*.hpp) $(call rwildcard,$(ui)/tools/*)
|
||||
obj/ui-settings.o: $(ui)/settings/settings.cpp $(call rwildcard,$(ui)/*.hpp) $(call rwildcard,$(ui)/settings/*)
|
||||
obj/ui-input.o: $(ui)/input/input.cpp $(call rwildcard,$(ui)/*.hpp) $(call rwildcard,$(ui)/input/*)
|
||||
obj/ui-interface.o: $(ui)/interface/interface.cpp $(call rwildcard,$(ui)/*.hpp) $(call rwildcard,$(ui)/interface/*)
|
||||
obj/ui-utility.o: $(ui)/utility/utility.cpp $(call rwildcard,$(ui)/*.hpp) $(call rwildcard,$(ui)/utility/*)
|
||||
obj/ui-path.o: $(ui)/path/path.cpp $(call rwildcard,$(ui)/*.hpp) $(call rwildcard,$(ui)/path/*)
|
||||
obj/ui-cartridge.o: $(ui)/cartridge/cartridge.cpp $(call rwildcard,$(ui)/*.hpp) $(call rwildcard,$(ui)/cartridge/*)
|
||||
obj/ui-debugger.o: $(ui)/debugger/debugger.cpp $(call rwildcard,$(ui)/*.hpp) $(call rwildcard,$(ui)/debugger/*)
|
||||
obj/ui-general.o: $(ui)/general/general.cpp $(call rwildcard,$(ui)/*.hpp) $(call rwildcard,$(ui)/general/*)
|
||||
|
||||
obj/ruby.o: ruby/ruby.cpp $(call rwildcard,ruby/*)
|
||||
$(call compile,$(rubydef) $(rubyflags))
|
||||
|
@ -86,25 +83,27 @@ obj/resource.o: $(ui)/resource.rc
|
|||
# targets
|
||||
build: $(objects)
|
||||
ifeq ($(platform),osx)
|
||||
test -d ../bsnes.app || mkdir -p ../bsnes.app/Contents/MacOS
|
||||
$(strip $(cpp) -o ../bsnes.app/Contents/MacOS/bsnes $(objects) $(link))
|
||||
test -d ../$(name).app || mkdir -p ../$(name).app/Contents/MacOS
|
||||
$(strip $(cpp) -o ../$(name).app/Contents/MacOS/$(name) $(objects) $(link))
|
||||
else
|
||||
$(strip $(cpp) -o out/bsnes $(objects) $(link))
|
||||
$(strip $(cpp) -o out/$(name) $(objects) $(link))
|
||||
endif
|
||||
|
||||
install:
|
||||
ifeq ($(platform),x)
|
||||
install -D -m 755 out/bsnes $(DESTDIR)$(prefix)/bin/bsnes
|
||||
install -D -m 755 out/$(name) $(DESTDIR)$(prefix)/bin/$(name)
|
||||
mkdir -p ~/.config/$(name)
|
||||
endif
|
||||
install -D -m 644 data/bsnes.png $(DESTDIR)$(prefix)/share/pixmaps/bsnes.png
|
||||
install -D -m 644 data/bsnes.desktop $(DESTDIR)$(prefix)/share/applications/bsnes.desktop
|
||||
mkdir -p ~/.config/bsnes
|
||||
cp data/cheats.xml ~/.config/bsnes/cheats.xml
|
||||
chmod 777 ~/.config/bsnes ~/.config/bsnes/cheats.xml
|
||||
|
||||
uninstall:
|
||||
ifeq ($(platform),x)
|
||||
rm $(DESTDIR)$(prefix)/bin/bsnes
|
||||
rm $(DESTDIR)$(prefix)/bin/$(name)
|
||||
endif
|
||||
rm $(DESTDIR)$(prefix)/share/pixmaps/bsnes.png
|
||||
rm $(DESTDIR)$(prefix)/share/applications/bsnes.desktop
|
||||
|
||||
# install -D -m 644 data/$(name).png $(DESTDIR)$(prefix)/share/pixmaps/$(name).png
|
||||
# install -D -m 644 data/$(name).desktop $(DESTDIR)$(prefix)/share/applications/$(name).desktop
|
||||
# cp data/cheats.xml ~/.config/$(name)/cheats.xml
|
||||
# chmod 777 ~/.config/$(name) ~/.config/$(name)/cheats.xml
|
||||
|
||||
# rm $(DESTDIR)$(prefix)/share/pixmaps/$(name).png
|
||||
# rm $(DESTDIR)$(prefix)/share/applications/$(name).desktop
|
||||
|
|
|
@ -1,65 +1,39 @@
|
|||
#include <nes/nes.hpp>
|
||||
#include <snes/snes.hpp>
|
||||
#include <gameboy/gameboy.hpp>
|
||||
|
||||
#include <nall/base64.hpp>
|
||||
#include <nall/bmp.hpp>
|
||||
#include <nall/compositor.hpp>
|
||||
#include <nall/config.hpp>
|
||||
#include <nall/crc32.hpp>
|
||||
#include <nall/directory.hpp>
|
||||
#include <nall/dsp.hpp>
|
||||
#include <nall/file.hpp>
|
||||
#include <nall/filemap.hpp>
|
||||
#include <nall/input.hpp>
|
||||
#include <nall/resource.hpp>
|
||||
#include <nall/bps/patch.hpp>
|
||||
#include <nall/snes/cartridge.hpp>
|
||||
#include <nall/gameboy/cartridge.hpp>
|
||||
using namespace nall;
|
||||
|
||||
#include <ruby/ruby.hpp>
|
||||
using namespace ruby;
|
||||
|
||||
#include <phoenix/phoenix.hpp>
|
||||
using namespace phoenix;
|
||||
|
||||
struct TopLevelWindow : Window {
|
||||
string name;
|
||||
string position;
|
||||
};
|
||||
#include <ruby/ruby.hpp>
|
||||
using namespace ruby;
|
||||
|
||||
#include "interface.hpp"
|
||||
#include "config.hpp"
|
||||
#include "general/general.hpp"
|
||||
#include "settings/settings.hpp"
|
||||
#include "tools/tools.hpp"
|
||||
#include "input/input.hpp"
|
||||
#include "interface/interface.hpp"
|
||||
#include "utility/utility.hpp"
|
||||
#include "path/path.hpp"
|
||||
#include "cartridge/cartridge.hpp"
|
||||
|
||||
#if defined(DEBUGGER)
|
||||
#include "debugger/debugger.hpp"
|
||||
#endif
|
||||
#include "general/general.hpp"
|
||||
|
||||
struct Application {
|
||||
string proportionalFont;
|
||||
string proportionalFontBold;
|
||||
string monospaceFont;
|
||||
string titleFont;
|
||||
bool compositorActive;
|
||||
string title;
|
||||
string normalFont;
|
||||
string boldFont;
|
||||
|
||||
bool pause;
|
||||
bool quit;
|
||||
void main(int argc, char **argv);
|
||||
Timer timer;
|
||||
|
||||
void addWindow(TopLevelWindow *window, const string &name, const string &position);
|
||||
Application();
|
||||
|
||||
private:
|
||||
array<TopLevelWindow*> windows;
|
||||
configuration geometryConfig;
|
||||
void loadGeometry();
|
||||
void saveGeometry();
|
||||
void run();
|
||||
Application(int argc, char **argv);
|
||||
~Application();
|
||||
};
|
||||
|
||||
extern Application *application;
|
||||
extern nall::DSP dspaudio;
|
||||
extern Application application;
|
||||
|
|
|
@ -1,6 +1,2 @@
|
|||
#include "../base.hpp"
|
||||
#include "main-window.cpp"
|
||||
#include "file-browser.cpp"
|
||||
#include "slot-loader.cpp"
|
||||
#include "nss-dip-window.cpp"
|
||||
#include "about-window.cpp"
|
||||
|
|
|
@ -1,5 +1 @@
|
|||
#include "main-window.hpp"
|
||||
#include "file-browser.hpp"
|
||||
#include "slot-loader.hpp"
|
||||
#include "nss-dip-window.hpp"
|
||||
#include "about-window.hpp"
|
||||
|
|
|
@ -1,476 +1,128 @@
|
|||
MainWindow mainWindow;
|
||||
MainWindow *mainWindow = 0;
|
||||
|
||||
void MainWindow::create() {
|
||||
setTitle({ SNES::Info::Name, " v", SNES::Info::Version });
|
||||
setResizable(false);
|
||||
setGeometry({ 0, 0, 595, 448 });
|
||||
application.addWindow(this, "MainWindow", "128,128");
|
||||
setMenuFont(application.proportionalFont);
|
||||
setStatusFont(application.proportionalFontBold);
|
||||
MainWindow::MainWindow() {
|
||||
setTitle(application->title);
|
||||
setGeometry({ 256, 256, 512, 480 });
|
||||
setBackgroundColor({ 0, 0, 0 });
|
||||
|
||||
system.setText("System");
|
||||
|
||||
systemLoadCartridge.setText("Load Cartridge ...");
|
||||
system.append(systemLoadCartridge);
|
||||
|
||||
systemLoadCartridgeSpecial.setText("Load Special");
|
||||
system.append(systemLoadCartridgeSpecial);
|
||||
|
||||
systemLoadCartridgeBsxSlotted.setText("Load BS-X Slotted Cartridge ...");
|
||||
systemLoadCartridgeSpecial.append(systemLoadCartridgeBsxSlotted);
|
||||
|
||||
systemLoadCartridgeBsx.setText("Load BS-X Cartridge ...");
|
||||
systemLoadCartridgeSpecial.append(systemLoadCartridgeBsx);
|
||||
|
||||
systemLoadCartridgeSufamiTurbo.setText("Load Sufami Turbo Cartridge ...");
|
||||
systemLoadCartridgeSpecial.append(systemLoadCartridgeSufamiTurbo);
|
||||
|
||||
systemLoadCartridgeSuperGameBoy.setText("Load Super Game Boy Cartridge ...");
|
||||
systemLoadCartridgeSpecial.append(systemLoadCartridgeSuperGameBoy);
|
||||
|
||||
system.append(systemSeparator1);
|
||||
|
||||
systemPower.setText("Power Cycle");
|
||||
system.append(systemPower);
|
||||
|
||||
systemReset.setText("Reset");
|
||||
system.append(systemReset);
|
||||
|
||||
system.append(systemSeparator2);
|
||||
|
||||
systemPort1.setText("Controller Port 1");
|
||||
system.append(systemPort1);
|
||||
|
||||
systemPort1None.setText("None");
|
||||
systemPort1.append(systemPort1None);
|
||||
|
||||
systemPort1Gamepad.setText("Gamepad");
|
||||
systemPort1.append(systemPort1Gamepad);
|
||||
|
||||
systemPort1Multitap.setText("Multitap");
|
||||
systemPort1.append(systemPort1Multitap);
|
||||
|
||||
systemPort1Mouse.setText("Mouse");
|
||||
systemPort1.append(systemPort1Mouse);
|
||||
|
||||
RadioItem::group(
|
||||
systemPort1None, systemPort1Gamepad, systemPort1Multitap, systemPort1Mouse
|
||||
);
|
||||
|
||||
systemPort2.setText("Controller Port 2");
|
||||
system.append(systemPort2);
|
||||
|
||||
systemPort2None.setText("None");
|
||||
systemPort2.append(systemPort2None);
|
||||
|
||||
systemPort2Gamepad.setText("Gamepad");
|
||||
systemPort2.append(systemPort2Gamepad);
|
||||
|
||||
systemPort2Multitap.setText("Multitap");
|
||||
systemPort2.append(systemPort2Multitap);
|
||||
|
||||
systemPort2Mouse.setText("Mouse");
|
||||
systemPort2.append(systemPort2Mouse);
|
||||
|
||||
systemPort2SuperScope.setText("Super Scope");
|
||||
systemPort2.append(systemPort2SuperScope);
|
||||
|
||||
systemPort2Justifier.setText("Justifier");
|
||||
systemPort2.append(systemPort2Justifier);
|
||||
|
||||
systemPort2Justifiers.setText("Justifiers");
|
||||
systemPort2.append(systemPort2Justifiers);
|
||||
|
||||
systemPort2Serial.setText("Serial Cable");
|
||||
systemPort2.append(systemPort2Serial);
|
||||
|
||||
RadioItem::group(
|
||||
systemPort2None, systemPort2Gamepad, systemPort2Multitap, systemPort2Mouse,
|
||||
systemPort2SuperScope, systemPort2Justifier, systemPort2Justifiers,
|
||||
systemPort2Serial
|
||||
);
|
||||
|
||||
append(system);
|
||||
|
||||
settings.setText("Settings");
|
||||
|
||||
settingsVideoMode.setText("Video Mode");
|
||||
settings.append(settingsVideoMode);
|
||||
|
||||
settingsVideoMode1x.setText("Scale 1x");
|
||||
settingsVideoMode.append(settingsVideoMode1x);
|
||||
|
||||
settingsVideoMode2x.setText("Scale 2x");
|
||||
settingsVideoMode.append(settingsVideoMode2x);
|
||||
|
||||
settingsVideoMode3x.setText("Scale 3x");
|
||||
settingsVideoMode.append(settingsVideoMode3x);
|
||||
|
||||
settingsVideoMode4x.setText("Scale 4x");
|
||||
settingsVideoMode.append(settingsVideoMode4x);
|
||||
|
||||
settingsVideoMode5x.setText("Scale 5x");
|
||||
settingsVideoMode.append(settingsVideoMode5x);
|
||||
|
||||
RadioItem::group(
|
||||
settingsVideoMode1x, settingsVideoMode2x, settingsVideoMode3x, settingsVideoMode4x, settingsVideoMode5x
|
||||
);
|
||||
|
||||
settingsVideoMode.append(settingsVideoModeSeparator1);
|
||||
|
||||
settingsVideoModeAspectRatioCorrection.setText("Correct Aspect Ratio");
|
||||
settingsVideoMode.append(settingsVideoModeAspectRatioCorrection);
|
||||
|
||||
settingsVideoModeSmoothVideo.setText("Smooth Video");
|
||||
settingsVideoMode.append(settingsVideoModeSmoothVideo);
|
||||
|
||||
settingsVideoMode.append(settingsVideoModeSeparator2);
|
||||
|
||||
settingsVideoModeNTSC.setText("NTSC");
|
||||
settingsVideoMode.append(settingsVideoModeNTSC);
|
||||
|
||||
settingsVideoModePAL.setText("PAL");
|
||||
settingsVideoMode.append(settingsVideoModePAL);
|
||||
|
||||
setupFiltersAndShaders();
|
||||
|
||||
RadioItem::group(
|
||||
settingsVideoModeNTSC, settingsVideoModePAL
|
||||
);
|
||||
|
||||
settings.append(settingsSeparator1);
|
||||
|
||||
settingsSynchronizeVideo.setText("Synchronize Video");
|
||||
settings.append(settingsSynchronizeVideo);
|
||||
|
||||
settingsSynchronizeAudio.setText("Synchronize Audio");
|
||||
settings.append(settingsSynchronizeAudio);
|
||||
|
||||
settingsMuteAudio.setText("Mute Audio");
|
||||
settings.append(settingsMuteAudio);
|
||||
|
||||
settings.append(settingsSeparator2);
|
||||
|
||||
settingsConfiguration.setText("Configuration Settings ...");
|
||||
settings.append(settingsConfiguration);
|
||||
|
||||
append(settings);
|
||||
|
||||
tools.setText("Tools");
|
||||
|
||||
toolsStateSave.setText("Save State");
|
||||
tools.append(toolsStateSave);
|
||||
|
||||
toolsStateSave1.setText("Slot 1");
|
||||
toolsStateSave.append(toolsStateSave1);
|
||||
|
||||
toolsStateSave2.setText("Slot 2");
|
||||
toolsStateSave.append(toolsStateSave2);
|
||||
|
||||
toolsStateSave3.setText("Slot 3");
|
||||
toolsStateSave.append(toolsStateSave3);
|
||||
|
||||
toolsStateSave4.setText("Slot 4");
|
||||
toolsStateSave.append(toolsStateSave4);
|
||||
|
||||
toolsStateSave5.setText("Slot 5");
|
||||
toolsStateSave.append(toolsStateSave5);
|
||||
|
||||
toolsStateLoad.setText("Load State");
|
||||
tools.append(toolsStateLoad);
|
||||
|
||||
toolsStateLoad1.setText("Slot 1");
|
||||
toolsStateLoad.append(toolsStateLoad1);
|
||||
|
||||
toolsStateLoad2.setText("Slot 2");
|
||||
toolsStateLoad.append(toolsStateLoad2);
|
||||
|
||||
toolsStateLoad3.setText("Slot 3");
|
||||
toolsStateLoad.append(toolsStateLoad3);
|
||||
|
||||
toolsStateLoad4.setText("Slot 4");
|
||||
toolsStateLoad.append(toolsStateLoad4);
|
||||
|
||||
toolsStateLoad5.setText("Slot 5");
|
||||
toolsStateLoad.append(toolsStateLoad5);
|
||||
|
||||
tools.append(toolsSeparator1);
|
||||
|
||||
toolsCaptureScreenshot.setText("Capture Screenshot");
|
||||
tools.append(toolsCaptureScreenshot);
|
||||
|
||||
toolsCheatEditor.setText("Cheat Editor ...");
|
||||
tools.append(toolsCheatEditor);
|
||||
|
||||
toolsStateManager.setText("State Manager ...");
|
||||
tools.append(toolsStateManager);
|
||||
|
||||
#if defined(DEBUGGER)
|
||||
tools.append(toolsSeparator2);
|
||||
|
||||
toolsDebugger.setText("Debugger ...");
|
||||
tools.append(toolsDebugger);
|
||||
#endif
|
||||
|
||||
append(tools);
|
||||
|
||||
help.setText("Help");
|
||||
|
||||
helpAbout.setText("About ...");
|
||||
help.append(helpAbout);
|
||||
|
||||
append(help);
|
||||
|
||||
if(config.controller.port1 == 0) systemPort1None.setChecked();
|
||||
if(config.controller.port1 == 1) systemPort1Gamepad.setChecked();
|
||||
if(config.controller.port1 == 2) systemPort1Multitap.setChecked();
|
||||
if(config.controller.port1 == 3) systemPort1Mouse.setChecked();
|
||||
if(config.controller.port2 == 0) systemPort2None.setChecked();
|
||||
if(config.controller.port2 == 1) systemPort2Gamepad.setChecked();
|
||||
if(config.controller.port2 == 2) systemPort2Multitap.setChecked();
|
||||
if(config.controller.port2 == 3) systemPort2Mouse.setChecked();
|
||||
if(config.controller.port2 == 4) systemPort2SuperScope.setChecked();
|
||||
if(config.controller.port2 == 5) systemPort2Justifier.setChecked();
|
||||
if(config.controller.port2 == 6) systemPort2Justifiers.setChecked();
|
||||
if(config.controller.port2 == 7) systemPort2Serial.setChecked();
|
||||
|
||||
if(config.video.scale == 1) settingsVideoMode1x.setChecked();
|
||||
if(config.video.scale == 2) settingsVideoMode2x.setChecked();
|
||||
if(config.video.scale == 3) settingsVideoMode3x.setChecked();
|
||||
if(config.video.scale == 4) settingsVideoMode4x.setChecked();
|
||||
if(config.video.scale == 5) settingsVideoMode5x.setChecked();
|
||||
settingsVideoModeAspectRatioCorrection.setChecked(config.video.aspectRatioCorrection);
|
||||
settingsVideoModeSmoothVideo.setChecked(config.video.smooth);
|
||||
if(config.video.region == 0) settingsVideoModeNTSC.setChecked();
|
||||
if(config.video.region == 1) settingsVideoModePAL.setChecked();
|
||||
settingsSynchronizeVideo.setChecked(config.video.synchronize);
|
||||
settingsSynchronizeAudio.setChecked(config.audio.synchronize);
|
||||
settingsMuteAudio.setChecked(config.audio.mute);
|
||||
|
||||
layout.append(viewport, { 0, 0, 595, 448 });
|
||||
cartridgeMenu.setText("Cartridge");
|
||||
cartridgeLoadSNES.setText("Load SNES Cartridge ...");
|
||||
cartridgeLoadNES.setText("Load NES Cartridge ...");
|
||||
cartridgeLoadGameBoy.setText("Load Game Boy Cartridge ...");
|
||||
|
||||
nesMenu.setText("NES");
|
||||
nesPower.setText("Power Cycle");
|
||||
nesReset.setText("Reset");
|
||||
nesCartridgeUnload.setText("Unload Cartridge");
|
||||
|
||||
snesMenu.setText("SNES");
|
||||
snesPower.setText("Power Cycle");
|
||||
snesReset.setText("Reset");
|
||||
snesCartridgeUnload.setText("Unload Cartridge");
|
||||
|
||||
gameBoyMenu.setText("Game Boy");
|
||||
gameBoyPower.setText("Power Cycle");
|
||||
gameBoyCartridgeUnload.setText("Unload Cartridge");
|
||||
|
||||
settingsMenu.setText("Settings");
|
||||
settingsSynchronizeVideo.setText("Synchronize Video");
|
||||
settingsSynchronizeVideo.setChecked();
|
||||
settingsSynchronizeAudio.setText("Synchronize Audio");
|
||||
settingsSynchronizeAudio.setChecked();
|
||||
settingsMuteAudio.setText("Mute Audio");
|
||||
|
||||
helpMenu.setText("Help");
|
||||
helpAbout.setText("About ...");
|
||||
|
||||
append(cartridgeMenu);
|
||||
cartridgeMenu.append(cartridgeLoadNES);
|
||||
cartridgeMenu.append(cartridgeLoadSNES);
|
||||
cartridgeMenu.append(cartridgeLoadGameBoy);
|
||||
|
||||
append(nesMenu);
|
||||
nesMenu.append(nesPower);
|
||||
nesMenu.append(nesReset);
|
||||
nesMenu.append(nesSeparator);
|
||||
nesMenu.append(nesCartridgeUnload);
|
||||
|
||||
append(snesMenu);
|
||||
snesMenu.append(snesPower);
|
||||
snesMenu.append(snesReset);
|
||||
snesMenu.append(snesSeparator);
|
||||
snesMenu.append(snesCartridgeUnload);
|
||||
|
||||
append(gameBoyMenu);
|
||||
gameBoyMenu.append(gameBoyPower);
|
||||
gameBoyMenu.append(gameBoySeparator);
|
||||
gameBoyMenu.append(gameBoyCartridgeUnload);
|
||||
|
||||
append(settingsMenu);
|
||||
settingsMenu.append(settingsSynchronizeVideo);
|
||||
settingsMenu.append(settingsSynchronizeAudio);
|
||||
settingsMenu.append(settingsMuteAudio);
|
||||
|
||||
append(helpMenu);
|
||||
helpMenu.append(helpAbout);
|
||||
|
||||
setMenuFont(application->normalFont);
|
||||
setMenuVisible();
|
||||
|
||||
setStatusText("No cartridge loaded");
|
||||
setStatusFont(application->boldFont);
|
||||
setStatusVisible();
|
||||
|
||||
layout.append(viewport, { 0, 0, 512, 480 });
|
||||
append(layout);
|
||||
|
||||
utility.setStatus("");
|
||||
setMenuVisible(true);
|
||||
setStatusVisible(true);
|
||||
onClose = &OS::quit;
|
||||
onSize = { &Utility::resizeMainWindow, utility };
|
||||
|
||||
systemLoadCartridge.onTick = [] {
|
||||
fileBrowser.fileOpen(FileBrowser::Mode::Cartridge, [](string filename) {
|
||||
cartridge.loadNormal(filename);
|
||||
cartridgeLoadSNES.onTick = [&] {
|
||||
string filename = OS::fileLoad(*this, "/media/sdb1/root/snes_roms/", "SNES images (*.sfc)");
|
||||
if(filename == "") return;
|
||||
interface->loadCartridgeSNES(filename);
|
||||
};
|
||||
|
||||
cartridgeLoadNES.onTick = [&] {
|
||||
string filename = OS::fileLoad(*this, "/media/sdb1/root/nes_images/", "NES images (*.nes)");
|
||||
if(filename == "") return;
|
||||
interface->loadCartridgeNES(filename);
|
||||
};
|
||||
|
||||
cartridgeLoadGameBoy.onTick = [&] {
|
||||
string filename = OS::fileLoad(*this, "/media/sdb1/root/gameboy_images/", "Game Boy images (*.gb, *.gbc)");
|
||||
if(filename == "") return;
|
||||
interface->loadCartridgeGameBoy(filename);
|
||||
};
|
||||
|
||||
nesPower.onTick = { &Interface::power, interface };
|
||||
nesReset.onTick = { &Interface::reset, interface };
|
||||
nesCartridgeUnload.onTick = { &Interface::unloadCartridgeNES, interface };
|
||||
|
||||
snesPower.onTick = { &Interface::power, interface };
|
||||
snesReset.onTick = { &Interface::reset, interface };
|
||||
snesCartridgeUnload.onTick = { &Interface::unloadCartridgeSNES, interface };
|
||||
|
||||
gameBoyPower.onTick = { &Interface::power, interface };
|
||||
gameBoyCartridgeUnload.onTick = { &Interface::unloadCartridgeGameBoy, interface };
|
||||
|
||||
settingsSynchronizeVideo.onTick = [&] {
|
||||
video.set(Video::Synchronize, settingsSynchronizeVideo.checked());
|
||||
};
|
||||
|
||||
settingsSynchronizeAudio.onTick = [&] {
|
||||
audio.set(Audio::Synchronize, settingsSynchronizeAudio.checked());
|
||||
};
|
||||
|
||||
settingsMuteAudio.onTick = [&] {
|
||||
dspaudio.setVolume(settingsMuteAudio.checked() ? 0.0 : 1.0);
|
||||
};
|
||||
|
||||
helpAbout.onTick = [&] {
|
||||
MessageWindow::information(*this, {
|
||||
application->title, "\n\n",
|
||||
"Author: byuu\n",
|
||||
"Website: http://byuu.org/"
|
||||
});
|
||||
};
|
||||
|
||||
systemLoadCartridgeBsxSlotted.onTick = [] { singleSlotLoader.loadCartridgeBsxSlotted(); };
|
||||
systemLoadCartridgeBsx.onTick = [] { singleSlotLoader.loadCartridgeBsx(); };
|
||||
systemLoadCartridgeSufamiTurbo.onTick = [] { doubleSlotLoader.loadCartridgeSufamiTurbo(); };
|
||||
systemLoadCartridgeSuperGameBoy.onTick = [] { singleSlotLoader.loadCartridgeSuperGameBoy(); };
|
||||
|
||||
systemPower.onTick = [] {
|
||||
SNES::system.power();
|
||||
utility.showMessage("System was power cycled");
|
||||
};
|
||||
|
||||
systemReset.onTick = [] {
|
||||
SNES::system.reset();
|
||||
utility.showMessage("System was reset");
|
||||
};
|
||||
|
||||
systemPort1None.onTick = [] { config.controller.port1 = 0; utility.setControllers(); };
|
||||
systemPort1Gamepad.onTick = [] { config.controller.port1 = 1; utility.setControllers(); };
|
||||
systemPort1Multitap.onTick = [] { config.controller.port1 = 2; utility.setControllers(); };
|
||||
systemPort1Mouse.onTick = [] { config.controller.port1 = 3; utility.setControllers(); };
|
||||
|
||||
systemPort2None.onTick = [] { config.controller.port2 = 0; utility.setControllers(); };
|
||||
systemPort2Gamepad.onTick = [] { config.controller.port2 = 1; utility.setControllers(); };
|
||||
systemPort2Multitap.onTick = [] { config.controller.port2 = 2; utility.setControllers(); };
|
||||
systemPort2Mouse.onTick = [] { config.controller.port2 = 3; utility.setControllers(); };
|
||||
systemPort2SuperScope.onTick = [] { config.controller.port2 = 4; utility.setControllers(); };
|
||||
systemPort2Justifier.onTick = [] { config.controller.port2 = 5; utility.setControllers(); };
|
||||
systemPort2Justifiers.onTick = [] { config.controller.port2 = 6; utility.setControllers(); };
|
||||
systemPort2Serial.onTick = [] { config.controller.port2 = 7; utility.setControllers(); };
|
||||
|
||||
settingsVideoMode1x.onTick = [] { utility.setScale(1); };
|
||||
settingsVideoMode2x.onTick = [] { utility.setScale(2); };
|
||||
settingsVideoMode3x.onTick = [] { utility.setScale(3); };
|
||||
settingsVideoMode4x.onTick = [] { utility.setScale(4); };
|
||||
settingsVideoMode5x.onTick = [] { utility.setScale(5); };
|
||||
|
||||
settingsVideoModeAspectRatioCorrection.onTick = [] {
|
||||
config.video.aspectRatioCorrection = mainWindow.settingsVideoModeAspectRatioCorrection.checked();
|
||||
utility.setScale();
|
||||
};
|
||||
|
||||
settingsVideoModeSmoothVideo.onTick = [] {
|
||||
config.video.smooth = mainWindow.settingsVideoModeSmoothVideo.checked();
|
||||
video.set(Video::Filter, (unsigned)config.video.smooth);
|
||||
};
|
||||
|
||||
settingsVideoModeNTSC.onTick = [] { config.video.region = 0; utility.setScale(); };
|
||||
settingsVideoModePAL.onTick = [] { config.video.region = 1; utility.setScale(); };
|
||||
|
||||
settingsVideoFilterNone.onTick = [] {
|
||||
config.video.filter = "";
|
||||
utility.setFilter();
|
||||
};
|
||||
|
||||
settingsVideoShaderNone.onTick = [] {
|
||||
config.video.shader = "";
|
||||
utility.setShader();
|
||||
};
|
||||
|
||||
settingsSynchronizeVideo.onTick = [] {
|
||||
config.video.synchronize = mainWindow.settingsSynchronizeVideo.checked();
|
||||
video.set(Video::Synchronize, config.video.synchronize);
|
||||
};
|
||||
|
||||
settingsSynchronizeAudio.onTick = [] {
|
||||
config.audio.synchronize = mainWindow.settingsSynchronizeAudio.checked();
|
||||
audio.set(Audio::Synchronize, config.audio.synchronize);
|
||||
};
|
||||
|
||||
settingsMuteAudio.onTick = [] { config.audio.mute = mainWindow.settingsMuteAudio.checked(); };
|
||||
|
||||
settingsConfiguration.onTick = [] {
|
||||
settingsWindow.setVisible();
|
||||
settingsWindow.panel.setFocused();
|
||||
};
|
||||
|
||||
toolsStateSave1.onTick = [] { utility.saveState(1); };
|
||||
toolsStateSave2.onTick = [] { utility.saveState(2); };
|
||||
toolsStateSave3.onTick = [] { utility.saveState(3); };
|
||||
toolsStateSave4.onTick = [] { utility.saveState(4); };
|
||||
toolsStateSave5.onTick = [] { utility.saveState(5); };
|
||||
|
||||
toolsStateLoad1.onTick = [] { utility.loadState(1); };
|
||||
toolsStateLoad2.onTick = [] { utility.loadState(2); };
|
||||
toolsStateLoad3.onTick = [] { utility.loadState(3); };
|
||||
toolsStateLoad4.onTick = [] { utility.loadState(4); };
|
||||
toolsStateLoad5.onTick = [] { utility.loadState(5); };
|
||||
|
||||
toolsCaptureScreenshot.onTick = [] { interface.captureScreenshot = true; };
|
||||
toolsCheatEditor.onTick = [] { cheatEditor.setVisible(); };
|
||||
toolsStateManager.onTick = [] { stateManager.setVisible(); };
|
||||
|
||||
#if defined(DEBUGGER)
|
||||
toolsDebugger.onTick = [] { debugger.setVisible(); };
|
||||
#endif
|
||||
|
||||
helpAbout.onTick = [] {
|
||||
aboutWindow.show();
|
||||
};
|
||||
|
||||
onClose = [] {
|
||||
application.quit = true;
|
||||
};
|
||||
|
||||
synchronize();
|
||||
}
|
||||
|
||||
void MainWindow::synchronize() {
|
||||
bool loaded = SNES::cartridge.loaded();
|
||||
systemPower.setEnabled(loaded);
|
||||
systemReset.setEnabled(loaded);
|
||||
toolsStateSave.setEnabled(loaded);
|
||||
toolsStateLoad.setEnabled(loaded);
|
||||
toolsCaptureScreenshot.setEnabled(loaded);
|
||||
}
|
||||
|
||||
void MainWindow::setupFiltersAndShaders() {
|
||||
string folderPath;
|
||||
lstring files;
|
||||
reference_array<RadioItem&> group;
|
||||
signed active;
|
||||
|
||||
settingsVideoFilter.setText("Video Filter");
|
||||
|
||||
settingsVideoFilterNone.setText("None");
|
||||
settingsVideoFilter.append(settingsVideoFilterNone);
|
||||
|
||||
settingsVideoFilter.append(settingsVideoFilterSeparator);
|
||||
|
||||
group.append(settingsVideoFilterNone);
|
||||
active = -1;
|
||||
|
||||
folderPath = { path.base, "filters/" };
|
||||
files = directory::files(folderPath, "*.filter");
|
||||
if(files.size() == 0) {
|
||||
#if defined(PLATFORM_X) || defined(PLATFORM_OSX)
|
||||
folderPath = { path.user, ".config/bsnes/filters/" };
|
||||
#else
|
||||
folderPath = { path.user, "bsnes/filters/" };
|
||||
#endif
|
||||
files = directory::files(folderPath, "*.filter");
|
||||
}
|
||||
foreach(filename, files) {
|
||||
settingsVideoFilterName.append({ folderPath, filename });
|
||||
}
|
||||
|
||||
if(settingsVideoFilterName.size() == 0) {
|
||||
config.video.filter = ""; //as the list (and thus the 'None' option) is invisible,
|
||||
utility.setFilter(); //erase any previously saved filter name
|
||||
} else {
|
||||
settingsVideoFilterItem = new RadioItem[settingsVideoFilterName.size()];
|
||||
foreach(filename, settingsVideoFilterName, n) {
|
||||
settingsVideoFilterItem[n].onTick = [n]() {
|
||||
config.video.filter = mainWindow.settingsVideoFilterName[n];
|
||||
utility.setFilter();
|
||||
};
|
||||
settingsVideoFilterItem[n].setText(nall::basename(notdir(filename)));
|
||||
settingsVideoFilter.append(settingsVideoFilterItem[n]);
|
||||
group.append(settingsVideoFilterItem[n]);
|
||||
if(filename == config.video.filter) active = n;
|
||||
}
|
||||
|
||||
RadioItem::group(group);
|
||||
group.reset();
|
||||
active < 0 ? settingsVideoFilterNone.setChecked() : settingsVideoFilterItem[active].setChecked();
|
||||
settings.append(settingsVideoFilter);
|
||||
}
|
||||
|
||||
settingsVideoShader.setText("Video Shader");
|
||||
|
||||
settingsVideoShaderNone.setText("None");
|
||||
settingsVideoShader.append(settingsVideoShaderNone);
|
||||
|
||||
settingsVideoShader.append(settingsVideoShaderSeparator);
|
||||
|
||||
group.append(settingsVideoShaderNone);
|
||||
active = -1;
|
||||
|
||||
folderPath = { path.base, "shaders/" };
|
||||
files = directory::files(folderPath, { "*.", config.video.driver, ".shader" });
|
||||
if(files.size() == 0) {
|
||||
#if defined(PLATFORM_X) || defined(PLATFORM_OSX)
|
||||
folderPath = { path.user, ".config/bsnes/shaders/" };
|
||||
#else
|
||||
folderPath = { path.user, "bsnes/shaders/" };
|
||||
#endif
|
||||
files = directory::files(folderPath, { "*.", config.video.driver, ".shader" });
|
||||
}
|
||||
foreach(filename, files) {
|
||||
settingsVideoShaderName.append({ folderPath, filename });
|
||||
}
|
||||
|
||||
if(settingsVideoShaderName.size() == 0) {
|
||||
config.video.shader = "";
|
||||
utility.setShader();
|
||||
} else {
|
||||
settingsVideoShaderItem = new RadioItem[settingsVideoShaderName.size()];
|
||||
foreach(filename, settingsVideoShaderName, n) {
|
||||
settingsVideoShaderItem[n].onTick = [n]() {
|
||||
config.video.shader = mainWindow.settingsVideoShaderName[n];
|
||||
utility.setShader();
|
||||
};
|
||||
settingsVideoShaderItem[n].setText(nall::basename(nall::basename(notdir(filename))));
|
||||
settingsVideoShader.append(settingsVideoShaderItem[n]);
|
||||
group.append(settingsVideoShaderItem[n]);
|
||||
if(filename == config.video.shader) active = n;
|
||||
}
|
||||
|
||||
RadioItem::group(group);
|
||||
group.reset();
|
||||
active < 0 ? settingsVideoShaderNone.setChecked() : settingsVideoShaderItem[active].setChecked();
|
||||
settings.append(settingsVideoShader);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,94 +1,38 @@
|
|||
struct MainWindow : TopLevelWindow {
|
||||
Menu system;
|
||||
Item systemLoadCartridge;
|
||||
Menu systemLoadCartridgeSpecial;
|
||||
Item systemLoadCartridgeBsxSlotted;
|
||||
Item systemLoadCartridgeBsx;
|
||||
Item systemLoadCartridgeSufamiTurbo;
|
||||
Item systemLoadCartridgeSuperGameBoy;
|
||||
Separator systemSeparator1;
|
||||
Item systemPower;
|
||||
Item systemReset;
|
||||
Separator systemSeparator2;
|
||||
Menu systemPort1;
|
||||
RadioItem systemPort1None;
|
||||
RadioItem systemPort1Gamepad;
|
||||
RadioItem systemPort1Multitap;
|
||||
RadioItem systemPort1Mouse;
|
||||
Menu systemPort2;
|
||||
RadioItem systemPort2None;
|
||||
RadioItem systemPort2Gamepad;
|
||||
RadioItem systemPort2Multitap;
|
||||
RadioItem systemPort2Mouse;
|
||||
RadioItem systemPort2SuperScope;
|
||||
RadioItem systemPort2Justifier;
|
||||
RadioItem systemPort2Justifiers;
|
||||
RadioItem systemPort2Serial;
|
||||
|
||||
Menu settings;
|
||||
Menu settingsVideoMode;
|
||||
RadioItem settingsVideoMode1x;
|
||||
RadioItem settingsVideoMode2x;
|
||||
RadioItem settingsVideoMode3x;
|
||||
RadioItem settingsVideoMode4x;
|
||||
RadioItem settingsVideoMode5x;
|
||||
Separator settingsVideoModeSeparator1;
|
||||
CheckItem settingsVideoModeAspectRatioCorrection;
|
||||
CheckItem settingsVideoModeSmoothVideo;
|
||||
Separator settingsVideoModeSeparator2;
|
||||
RadioItem settingsVideoModeNTSC;
|
||||
RadioItem settingsVideoModePAL;
|
||||
|
||||
Menu settingsVideoFilter;
|
||||
RadioItem settingsVideoFilterNone;
|
||||
Separator settingsVideoFilterSeparator;
|
||||
RadioItem *settingsVideoFilterItem;
|
||||
lstring settingsVideoFilterName;
|
||||
|
||||
Menu settingsVideoShader;
|
||||
RadioItem settingsVideoShaderNone;
|
||||
Separator settingsVideoShaderSeparator;
|
||||
RadioItem *settingsVideoShaderItem;
|
||||
lstring settingsVideoShaderName;
|
||||
|
||||
Separator settingsSeparator1;
|
||||
CheckItem settingsSynchronizeVideo;
|
||||
CheckItem settingsSynchronizeAudio;
|
||||
CheckItem settingsMuteAudio;
|
||||
Separator settingsSeparator2;
|
||||
Item settingsConfiguration;
|
||||
|
||||
Menu tools;
|
||||
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 toolsSeparator1;
|
||||
Item toolsCaptureScreenshot;
|
||||
Item toolsCheatEditor;
|
||||
Item toolsStateManager;
|
||||
#if defined(DEBUGGER)
|
||||
Separator toolsSeparator2;
|
||||
Item toolsDebugger;
|
||||
#endif
|
||||
|
||||
Menu help;
|
||||
Item helpAbout;
|
||||
|
||||
struct MainWindow : Window {
|
||||
FixedLayout layout;
|
||||
Viewport viewport;
|
||||
|
||||
void create();
|
||||
void synchronize();
|
||||
void setupFiltersAndShaders();
|
||||
Menu cartridgeMenu;
|
||||
Item cartridgeLoadSNES;
|
||||
Item cartridgeLoadNES;
|
||||
Item cartridgeLoadGameBoy;
|
||||
|
||||
Menu nesMenu;
|
||||
Item nesPower;
|
||||
Item nesReset;
|
||||
Separator nesSeparator;
|
||||
Item nesCartridgeUnload;
|
||||
|
||||
Menu snesMenu;
|
||||
Item snesPower;
|
||||
Item snesReset;
|
||||
Separator snesSeparator;
|
||||
Item snesCartridgeUnload;
|
||||
|
||||
Menu gameBoyMenu;
|
||||
Item gameBoyPower;
|
||||
Separator gameBoySeparator;
|
||||
Item gameBoyCartridgeUnload;
|
||||
|
||||
Menu settingsMenu;
|
||||
CheckItem settingsSynchronizeVideo;
|
||||
CheckItem settingsSynchronizeAudio;
|
||||
CheckItem settingsMuteAudio;
|
||||
|
||||
Menu helpMenu;
|
||||
Item helpAbout;
|
||||
|
||||
MainWindow();
|
||||
};
|
||||
|
||||
extern MainWindow mainWindow;
|
||||
extern MainWindow *mainWindow;
|
||||
|
|
|
@ -0,0 +1,64 @@
|
|||
bool InterfaceGameBoy::loadCartridge(const string &filename) {
|
||||
uint8_t *data;
|
||||
unsigned size;
|
||||
if(file::read(filename, data, size) == false) return false;
|
||||
|
||||
interface->baseName = nall::basename(filename);
|
||||
GameBoyCartridge info(data, size);
|
||||
GameBoy::cartridge.load(info.xml, data, size);
|
||||
GameBoy::system.power();
|
||||
|
||||
delete[] data;
|
||||
return true;
|
||||
}
|
||||
|
||||
void InterfaceGameBoy::unloadCartridge() {
|
||||
GameBoy::cartridge.unload();
|
||||
interface->baseName = "";
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
void InterfaceGameBoy::video_refresh(const uint8_t *data) {
|
||||
interface->video_refresh();
|
||||
|
||||
uint32_t *output;
|
||||
unsigned outpitch;
|
||||
if(video.lock(output, outpitch, 160, 144)) {
|
||||
for(unsigned y = 0; y < 144; y++) {
|
||||
const uint8_t *sp = data + y * 160;
|
||||
uint32_t *dp = output + y * (outpitch >> 2);
|
||||
for(unsigned x = 0; x < 160; x++) {
|
||||
uint32_t color = *sp++;
|
||||
*dp++ = (color << 16) | (color << 8) | (color << 0);
|
||||
}
|
||||
}
|
||||
|
||||
video.unlock();
|
||||
video.refresh();
|
||||
}
|
||||
}
|
||||
|
||||
void InterfaceGameBoy::audio_sample(int16_t csample, int16_t lsample, int16_t rsample) {
|
||||
dspaudio.sample(lsample, rsample);
|
||||
while(dspaudio.pending()) {
|
||||
signed lsample, rsample;
|
||||
dspaudio.read(lsample, rsample);
|
||||
audio.sample(lsample, rsample);
|
||||
}
|
||||
}
|
||||
|
||||
bool InterfaceGameBoy::input_poll(unsigned id) {
|
||||
switch((GameBoy::Input)id) {
|
||||
case GameBoy::Input::Up: return interface->inputState[keyboard(0)[Keyboard::Up]];
|
||||
case GameBoy::Input::Down: return interface->inputState[keyboard(0)[Keyboard::Down]];
|
||||
case GameBoy::Input::Left: return interface->inputState[keyboard(0)[Keyboard::Left]];
|
||||
case GameBoy::Input::Right: return interface->inputState[keyboard(0)[Keyboard::Right]];
|
||||
case GameBoy::Input::B: return interface->inputState[keyboard(0)[Keyboard::Z]];
|
||||
case GameBoy::Input::A: return interface->inputState[keyboard(0)[Keyboard::X]];
|
||||
case GameBoy::Input::Select: return interface->inputState[keyboard(0)[Keyboard::Apostrophe]];
|
||||
case GameBoy::Input::Start: return interface->inputState[keyboard(0)[Keyboard::Return]];
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
struct InterfaceGameBoy : GameBoy::Interface {
|
||||
bool loadCartridge(const string &filename);
|
||||
void unloadCartridge();
|
||||
|
||||
void video_refresh(const uint8_t *data);
|
||||
void audio_sample(int16_t csample, int16_t lsample, int16_t rsample);
|
||||
bool input_poll(unsigned id);
|
||||
};
|
|
@ -0,0 +1,111 @@
|
|||
#include "../base.hpp"
|
||||
#include "nes.cpp"
|
||||
#include "snes.cpp"
|
||||
#include "gameboy.cpp"
|
||||
Interface *interface = 0;
|
||||
|
||||
bool Interface::loaded() {
|
||||
switch(mode()) {
|
||||
case Mode::NES: return NES::cartridge.loaded();
|
||||
case Mode::SNES: return SNES::cartridge.loaded();
|
||||
case Mode::GameBoy: return GameBoy::cartridge.loaded();
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool Interface::loadCartridgeNES(const string &filename) {
|
||||
if(nes.loadCartridge(filename) == false) return false;
|
||||
utility->setMode(mode = Mode::NES);
|
||||
return true;
|
||||
}
|
||||
|
||||
void Interface::unloadCartridgeNES() {
|
||||
nes.unloadCartridge();
|
||||
utility->setMode(mode = Mode::None);
|
||||
}
|
||||
|
||||
bool Interface::loadCartridgeSNES(const string &filename) {
|
||||
if(snes.loadCartridge(filename) == false) return false;
|
||||
utility->setMode(mode = Mode::SNES);
|
||||
return true;
|
||||
}
|
||||
|
||||
void Interface::unloadCartridgeSNES() {
|
||||
snes.unloadCartridge();
|
||||
utility->setMode(mode = Mode::None);
|
||||
}
|
||||
|
||||
bool Interface::loadCartridgeGameBoy(const string &filename) {
|
||||
if(gameBoy.loadCartridge(filename) == false) return false;
|
||||
utility->setMode(mode = Mode::GameBoy);
|
||||
return true;
|
||||
}
|
||||
|
||||
void Interface::unloadCartridgeGameBoy() {
|
||||
gameBoy.unloadCartridge();
|
||||
utility->setMode(mode = Mode::None);
|
||||
}
|
||||
|
||||
void Interface::power() {
|
||||
switch(mode()) {
|
||||
case Mode::NES: return NES::system.power();
|
||||
case Mode::SNES: return SNES::system.power();
|
||||
case Mode::GameBoy: return GameBoy::system.power();
|
||||
}
|
||||
}
|
||||
|
||||
void Interface::reset() {
|
||||
switch(mode()) {
|
||||
case Mode::NES: return NES::system.reset();
|
||||
case Mode::SNES: return SNES::system.reset();
|
||||
case Mode::GameBoy: return GameBoy::system.power(); //Game Boy lacks reset button
|
||||
}
|
||||
}
|
||||
|
||||
void Interface::run() {
|
||||
switch(mode()) {
|
||||
case Mode::NES:
|
||||
return NES::system.run();
|
||||
|
||||
case Mode::SNES:
|
||||
return SNES::system.run();
|
||||
|
||||
case Mode::GameBoy:
|
||||
do {
|
||||
GameBoy::system.run();
|
||||
} while(GameBoy::scheduler.exit_reason() != GameBoy::Scheduler::ExitReason::FrameEvent);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Interface::Interface() {
|
||||
mode = Mode::None;
|
||||
NES::system.init(&nes);
|
||||
SNES::system.init(&snes);
|
||||
GameBoy::system.init(&gameBoy);
|
||||
}
|
||||
|
||||
//internal
|
||||
|
||||
void Interface::input_poll() {
|
||||
bool fullScreen = inputState[keyboard(0)[Keyboard::F11]];
|
||||
|
||||
input.poll(inputState);
|
||||
|
||||
if(!fullScreen && inputState[keyboard(0)[Keyboard::F11]]) {
|
||||
utility->toggleFullScreen();
|
||||
}
|
||||
}
|
||||
|
||||
void Interface::video_refresh() {
|
||||
static unsigned frameCounter = 0;
|
||||
static time_t previous, current;
|
||||
frameCounter++;
|
||||
|
||||
time(¤t);
|
||||
if(current != previous) {
|
||||
previous = current;
|
||||
mainWindow->setStatusText({ "FPS: ", frameCounter });
|
||||
frameCounter = 0;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
#include "nes.hpp"
|
||||
#include "snes.hpp"
|
||||
#include "gameboy.hpp"
|
||||
|
||||
struct Interface : property<Interface> {
|
||||
enum class Mode : unsigned { None, SNES, NES, GameBoy };
|
||||
readonly<Mode> mode;
|
||||
|
||||
bool loaded();
|
||||
|
||||
bool loadCartridgeNES(const string &filename);
|
||||
bool loadCartridgeSNES(const string &filename);
|
||||
bool loadCartridgeGameBoy(const string &filename);
|
||||
|
||||
void unloadCartridgeNES();
|
||||
void unloadCartridgeSNES();
|
||||
void unloadCartridgeGameBoy();
|
||||
|
||||
void power();
|
||||
void reset();
|
||||
void run();
|
||||
|
||||
Interface();
|
||||
|
||||
int16_t inputState[Scancode::Limit];
|
||||
void input_poll();
|
||||
void video_refresh();
|
||||
|
||||
string baseName; // = "/path/to/cartridge" (no extension)
|
||||
|
||||
private:
|
||||
InterfaceNES nes;
|
||||
InterfaceSNES snes;
|
||||
InterfaceGameBoy gameBoy;
|
||||
};
|
||||
|
||||
extern Interface *interface;
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue