diff --git a/bsnes/Makefile b/bsnes/Makefile index 224eeac9..7ad84c06 100755 --- a/bsnes/Makefile +++ b/bsnes/Makefile @@ -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:; diff --git a/bsnes/gameboy/gameboy.hpp b/bsnes/gameboy/gameboy.hpp index 3a169f03..b0de9e02 100755 --- a/bsnes/gameboy/gameboy.hpp +++ b/bsnes/gameboy/gameboy.hpp @@ -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 #include @@ -94,3 +100,5 @@ namespace GameBoy { #include #include }; + +#endif diff --git a/bsnes/nall/detect.hpp b/bsnes/nall/detect.hpp index b4991aaf..85122fbd 100755 --- a/bsnes/nall/detect.hpp +++ b/bsnes/nall/detect.hpp @@ -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 diff --git a/bsnes/nes/Makefile b/bsnes/nes/Makefile new file mode 100755 index 00000000..64732a51 --- /dev/null +++ b/bsnes/nes/Makefile @@ -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/) diff --git a/bsnes/nes/cartridge/cartridge.cpp b/bsnes/nes/cartridge/cartridge.cpp new file mode 100755 index 00000000..d752293e --- /dev/null +++ b/bsnes/nes/cartridge/cartridge.cpp @@ -0,0 +1,60 @@ +#include + +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; +} + +} diff --git a/bsnes/nes/cartridge/cartridge.hpp b/bsnes/nes/cartridge/cartridge.hpp new file mode 100755 index 00000000..2015403e --- /dev/null +++ b/bsnes/nes/cartridge/cartridge.hpp @@ -0,0 +1,24 @@ +struct Cartridge : property { + void load(const string &xml, const uint8_t *data, unsigned size); + void unload(); + + readonly 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; diff --git a/bsnes/nes/cpu/core/core.cpp b/bsnes/nes/cpu/core/core.cpp new file mode 100755 index 00000000..8fbd2acd --- /dev/null +++ b/bsnes/nes/cpu/core/core.cpp @@ -0,0 +1,3 @@ +#include "opcodes.cpp" +#include "exec.cpp" +#include "disassembler.cpp" diff --git a/bsnes/nes/cpu/core/core.hpp b/bsnes/nes/cpu/core/core.hpp new file mode 100755 index 00000000..d9599876 --- /dev/null +++ b/bsnes/nes/cpu/core/core.hpp @@ -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 opi_read_absolute(); +template void opi_read_absolute_x(); +template void opi_read_absolute_y(); +template void opi_read_immediate(); +template void opi_read_indirect_zero_page_x(); +template void opi_read_indirect_zero_page_y(); +template void opi_read_zero_page(); +template void opi_read_zero_page_x(); +template void opi_read_zero_page_y(); +template void opi_rmw_absolute(); +template void opi_rmw_absolute_x(); +template void opi_rmw_zero_page(); +template void opi_rmw_zero_page_x(); +void opi_set_flag(bool &flag); +template 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(); diff --git a/bsnes/nes/cpu/core/disassembler.cpp b/bsnes/nes/cpu/core/disassembler.cpp new file mode 100755 index 00000000..6c9093c8 --- /dev/null +++ b/bsnes/nes/cpu/core/disassembler.cpp @@ -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; +} diff --git a/bsnes/nes/cpu/core/exec.cpp b/bsnes/nes/cpu/core/exec.cpp new file mode 100755 index 00000000..5ea21117 --- /dev/null +++ b/bsnes/nes/cpu/core/exec.cpp @@ -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(); +} diff --git a/bsnes/nes/cpu/core/opcodes.cpp b/bsnes/nes/cpu/core/opcodes.cpp new file mode 100755 index 00000000..6498c70d --- /dev/null +++ b/bsnes/nes/cpu/core/opcodes.cpp @@ -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::opi_read_absolute() { + abs.l = op_readpci(); + abs.h = op_readpci(); +L rd = op_read(abs.w); + call(op); +} + +template +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::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::opi_read_immediate() { +L rd = op_readpci(); + call(op); +} + +template +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::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::opi_read_zero_page() { + zp = op_readpci(); +L rd = op_read(zp); + call(op); +} + +template +void CPU::opi_read_zero_page_x() { + zp = op_readpci(); + op_readpc(); +L rd = op_readdp(zp + regs.x); + call(op); +} + +template +void CPU::opi_read_zero_page_y() { + zp = op_readpci(); + op_readpc(); +L rd = op_readdp(zp + regs.y); + call(op); +} + +template +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::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::opi_rmw_zero_page() { + zp = op_readpci(); + rd = op_read(zp); + call(op); + op_readpc(); +L op_write(zp, rd); +} + +template +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::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 diff --git a/bsnes/nes/cpu/cpu.cpp b/bsnes/nes/cpu/cpu.cpp new file mode 100755 index 00000000..ba3cf649 --- /dev/null +++ b/bsnes/nes/cpu/cpu.cpp @@ -0,0 +1,130 @@ +#include + +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); + } +} + +} diff --git a/bsnes/nes/cpu/cpu.hpp b/bsnes/nes/cpu/cpu.hpp new file mode 100755 index 00000000..80f63041 --- /dev/null +++ b/bsnes/nes/cpu/cpu.hpp @@ -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; diff --git a/bsnes/nes/cpu/memory/memory.cpp b/bsnes/nes/cpu/memory/memory.cpp new file mode 100755 index 00000000..6d0bde3b --- /dev/null +++ b/bsnes/nes/cpu/memory/memory.cpp @@ -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(); +} diff --git a/bsnes/nes/cpu/memory/memory.hpp b/bsnes/nes/cpu/memory/memory.hpp new file mode 100755 index 00000000..16c4c40c --- /dev/null +++ b/bsnes/nes/cpu/memory/memory.hpp @@ -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); diff --git a/bsnes/nes/interface/interface.hpp b/bsnes/nes/interface/interface.hpp new file mode 100755 index 00000000..f6c92d9c --- /dev/null +++ b/bsnes/nes/interface/interface.hpp @@ -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"); } +}; diff --git a/bsnes/nes/memory/memory.cpp b/bsnes/nes/memory/memory.cpp new file mode 100755 index 00000000..bfef3292 --- /dev/null +++ b/bsnes/nes/memory/memory.cpp @@ -0,0 +1,28 @@ +#include + +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); +} + +} diff --git a/bsnes/nes/memory/memory.hpp b/bsnes/nes/memory/memory.hpp new file mode 100755 index 00000000..6ee5a463 --- /dev/null +++ b/bsnes/nes/memory/memory.hpp @@ -0,0 +1,6 @@ +struct Bus { + uint8 read(uint16 addr); + void write(uint16 addr, uint8 data); +}; + +extern Bus bus; diff --git a/bsnes/nes/nes.hpp b/bsnes/nes/nes.hpp new file mode 100755 index 00000000..d136ae53 --- /dev/null +++ b/bsnes/nes/nes.hpp @@ -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 + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +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 + #include + #include + #include + #include + #include + #include +} + +#endif diff --git a/bsnes/nes/ppu/ppu.cpp b/bsnes/nes/ppu/ppu.cpp new file mode 100755 index 00000000..f8e5df19 --- /dev/null +++ b/bsnes/nes/ppu/ppu.cpp @@ -0,0 +1,348 @@ +#include + +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]]; + } +} + +} diff --git a/bsnes/nes/ppu/ppu.hpp b/bsnes/nes/ppu/ppu.hpp new file mode 100755 index 00000000..d2ae10a0 --- /dev/null +++ b/bsnes/nes/ppu/ppu.hpp @@ -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; diff --git a/bsnes/nes/scheduler/scheduler.cpp b/bsnes/nes/scheduler/scheduler.cpp new file mode 100755 index 00000000..f7e106d2 --- /dev/null +++ b/bsnes/nes/scheduler/scheduler.cpp @@ -0,0 +1,26 @@ +#include + +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; +} + +} diff --git a/bsnes/nes/scheduler/scheduler.hpp b/bsnes/nes/scheduler/scheduler.hpp new file mode 100755 index 00000000..558d3a4d --- /dev/null +++ b/bsnes/nes/scheduler/scheduler.hpp @@ -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; diff --git a/bsnes/nes/system/system.cpp b/bsnes/nes/system/system.cpp new file mode 100755 index 00000000..83395c9f --- /dev/null +++ b/bsnes/nes/system/system.cpp @@ -0,0 +1,30 @@ +#include + +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() { +} + +} diff --git a/bsnes/nes/system/system.hpp b/bsnes/nes/system/system.hpp new file mode 100755 index 00000000..4324abb0 --- /dev/null +++ b/bsnes/nes/system/system.hpp @@ -0,0 +1,12 @@ +struct System { + Interface *interface; + + void run(); + void power(); + void reset(); + + void init(Interface *interface); + void term(); +}; + +extern System system; diff --git a/bsnes/ruby/video/direct3d.cpp b/bsnes/ruby/video/direct3d.cpp index a9404d8e..353b0e17 100755 --- a/bsnes/ruby/video/direct3d.cpp +++ b/bsnes/ruby/video/direct3d.cpp @@ -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; } diff --git a/bsnes/snes/alt/smp/core.cpp b/bsnes/snes/alt/smp/core.cpp index 6e0a29b2..21bf5bf2 100755 --- a/bsnes/snes/alt/smp/core.cpp +++ b/bsnes/snes/alt/smp/core.cpp @@ -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) diff --git a/bsnes/snes/alt/smp/smp.cpp b/bsnes/snes/alt/smp/smp.cpp index 08a67ef0..0b834074 100755 --- a/bsnes/snes/alt/smp/smp.cpp +++ b/bsnes/snes/alt/smp/smp.cpp @@ -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); diff --git a/bsnes/snes/alt/smp/smp.hpp b/bsnes/snes/alt/smp/smp.hpp index 613d663e..44c16010 100755 --- a/bsnes/snes/alt/smp/smp.hpp +++ b/bsnes/snes/alt/smp/smp.hpp @@ -55,6 +55,8 @@ public: Flags p; } regs; + unsigned rd, wr, dp, sp, ya, bit; + struct Status { //$00f1 bool iplrom_enable; diff --git a/bsnes/snes/snes.hpp b/bsnes/snes/snes.hpp index b4b743b8..e3aa96cc 100755 --- a/bsnes/snes/snes.hpp +++ b/bsnes/snes/snes.hpp @@ -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 @@ -154,3 +158,5 @@ namespace nall { } #undef debugvirtual + +#endif diff --git a/bsnes/ui-snes/Makefile b/bsnes/ui-snes/Makefile new file mode 100755 index 00000000..dc521b62 --- /dev/null +++ b/bsnes/ui-snes/Makefile @@ -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 diff --git a/bsnes/ui-snes/base.hpp b/bsnes/ui-snes/base.hpp new file mode 100755 index 00000000..34dcabdb --- /dev/null +++ b/bsnes/ui-snes/base.hpp @@ -0,0 +1,65 @@ +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +using namespace nall; + +#include +using namespace ruby; + +#include +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 windows; + configuration geometryConfig; + void loadGeometry(); + void saveGeometry(); +}; + +extern nall::DSP dspaudio; +extern Application application; diff --git a/bsnes/ui/cartridge/cartridge.cpp b/bsnes/ui-snes/cartridge/cartridge.cpp similarity index 100% rename from bsnes/ui/cartridge/cartridge.cpp rename to bsnes/ui-snes/cartridge/cartridge.cpp diff --git a/bsnes/ui/cartridge/cartridge.hpp b/bsnes/ui-snes/cartridge/cartridge.hpp similarity index 100% rename from bsnes/ui/cartridge/cartridge.hpp rename to bsnes/ui-snes/cartridge/cartridge.hpp diff --git a/bsnes/ui/config.cpp b/bsnes/ui-snes/config.cpp similarity index 100% rename from bsnes/ui/config.cpp rename to bsnes/ui-snes/config.cpp diff --git a/bsnes/ui/config.hpp b/bsnes/ui-snes/config.hpp similarity index 100% rename from bsnes/ui/config.hpp rename to bsnes/ui-snes/config.hpp diff --git a/bsnes/ui/debugger/console.cpp b/bsnes/ui-snes/debugger/console.cpp similarity index 100% rename from bsnes/ui/debugger/console.cpp rename to bsnes/ui-snes/debugger/console.cpp diff --git a/bsnes/ui/debugger/console.hpp b/bsnes/ui-snes/debugger/console.hpp similarity index 100% rename from bsnes/ui/debugger/console.hpp rename to bsnes/ui-snes/debugger/console.hpp diff --git a/bsnes/ui/debugger/cpu/debugger.cpp b/bsnes/ui-snes/debugger/cpu/debugger.cpp similarity index 100% rename from bsnes/ui/debugger/cpu/debugger.cpp rename to bsnes/ui-snes/debugger/cpu/debugger.cpp diff --git a/bsnes/ui/debugger/cpu/debugger.hpp b/bsnes/ui-snes/debugger/cpu/debugger.hpp similarity index 100% rename from bsnes/ui/debugger/cpu/debugger.hpp rename to bsnes/ui-snes/debugger/cpu/debugger.hpp diff --git a/bsnes/ui/debugger/debugger.cpp b/bsnes/ui-snes/debugger/debugger.cpp similarity index 100% rename from bsnes/ui/debugger/debugger.cpp rename to bsnes/ui-snes/debugger/debugger.cpp diff --git a/bsnes/ui/debugger/debugger.hpp b/bsnes/ui-snes/debugger/debugger.hpp similarity index 100% rename from bsnes/ui/debugger/debugger.hpp rename to bsnes/ui-snes/debugger/debugger.hpp diff --git a/bsnes/ui/debugger/smp/debugger.cpp b/bsnes/ui-snes/debugger/smp/debugger.cpp similarity index 100% rename from bsnes/ui/debugger/smp/debugger.cpp rename to bsnes/ui-snes/debugger/smp/debugger.cpp diff --git a/bsnes/ui/debugger/smp/debugger.hpp b/bsnes/ui-snes/debugger/smp/debugger.hpp similarity index 100% rename from bsnes/ui/debugger/smp/debugger.hpp rename to bsnes/ui-snes/debugger/smp/debugger.hpp diff --git a/bsnes/ui/debugger/tools/breakpoint-editor.cpp b/bsnes/ui-snes/debugger/tools/breakpoint-editor.cpp similarity index 100% rename from bsnes/ui/debugger/tools/breakpoint-editor.cpp rename to bsnes/ui-snes/debugger/tools/breakpoint-editor.cpp diff --git a/bsnes/ui/debugger/tools/breakpoint-editor.hpp b/bsnes/ui-snes/debugger/tools/breakpoint-editor.hpp similarity index 100% rename from bsnes/ui/debugger/tools/breakpoint-editor.hpp rename to bsnes/ui-snes/debugger/tools/breakpoint-editor.hpp diff --git a/bsnes/ui/debugger/tools/memory-editor.cpp b/bsnes/ui-snes/debugger/tools/memory-editor.cpp similarity index 100% rename from bsnes/ui/debugger/tools/memory-editor.cpp rename to bsnes/ui-snes/debugger/tools/memory-editor.cpp diff --git a/bsnes/ui/debugger/tools/memory-editor.hpp b/bsnes/ui-snes/debugger/tools/memory-editor.hpp similarity index 100% rename from bsnes/ui/debugger/tools/memory-editor.hpp rename to bsnes/ui-snes/debugger/tools/memory-editor.hpp diff --git a/bsnes/ui/general/about-window.cpp b/bsnes/ui-snes/general/about-window.cpp similarity index 100% rename from bsnes/ui/general/about-window.cpp rename to bsnes/ui-snes/general/about-window.cpp diff --git a/bsnes/ui/general/about-window.hpp b/bsnes/ui-snes/general/about-window.hpp similarity index 100% rename from bsnes/ui/general/about-window.hpp rename to bsnes/ui-snes/general/about-window.hpp diff --git a/bsnes/ui/general/file-browser.cpp b/bsnes/ui-snes/general/file-browser.cpp similarity index 100% rename from bsnes/ui/general/file-browser.cpp rename to bsnes/ui-snes/general/file-browser.cpp diff --git a/bsnes/ui/general/file-browser.hpp b/bsnes/ui-snes/general/file-browser.hpp similarity index 100% rename from bsnes/ui/general/file-browser.hpp rename to bsnes/ui-snes/general/file-browser.hpp diff --git a/bsnes/ui-snes/general/general.cpp b/bsnes/ui-snes/general/general.cpp new file mode 100755 index 00000000..be70ba66 --- /dev/null +++ b/bsnes/ui-snes/general/general.cpp @@ -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" diff --git a/bsnes/ui-snes/general/general.hpp b/bsnes/ui-snes/general/general.hpp new file mode 100755 index 00000000..b0f94545 --- /dev/null +++ b/bsnes/ui-snes/general/general.hpp @@ -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" diff --git a/bsnes/ui-snes/general/main-window.cpp b/bsnes/ui-snes/general/main-window.cpp new file mode 100755 index 00000000..e0e8850d --- /dev/null +++ b/bsnes/ui-snes/general/main-window.cpp @@ -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 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); + } +} diff --git a/bsnes/ui-snes/general/main-window.hpp b/bsnes/ui-snes/general/main-window.hpp new file mode 100755 index 00000000..45cdfd2b --- /dev/null +++ b/bsnes/ui-snes/general/main-window.hpp @@ -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; diff --git a/bsnes/ui/general/nss-dip-window.cpp b/bsnes/ui-snes/general/nss-dip-window.cpp similarity index 100% rename from bsnes/ui/general/nss-dip-window.cpp rename to bsnes/ui-snes/general/nss-dip-window.cpp diff --git a/bsnes/ui/general/nss-dip-window.hpp b/bsnes/ui-snes/general/nss-dip-window.hpp similarity index 100% rename from bsnes/ui/general/nss-dip-window.hpp rename to bsnes/ui-snes/general/nss-dip-window.hpp diff --git a/bsnes/ui/general/slot-loader.cpp b/bsnes/ui-snes/general/slot-loader.cpp similarity index 100% rename from bsnes/ui/general/slot-loader.cpp rename to bsnes/ui-snes/general/slot-loader.cpp diff --git a/bsnes/ui/general/slot-loader.hpp b/bsnes/ui-snes/general/slot-loader.hpp similarity index 100% rename from bsnes/ui/general/slot-loader.hpp rename to bsnes/ui-snes/general/slot-loader.hpp diff --git a/bsnes/ui/input/hotkeys.cpp b/bsnes/ui-snes/input/hotkeys.cpp similarity index 100% rename from bsnes/ui/input/hotkeys.cpp rename to bsnes/ui-snes/input/hotkeys.cpp diff --git a/bsnes/ui/input/hotkeys.hpp b/bsnes/ui-snes/input/hotkeys.hpp similarity index 100% rename from bsnes/ui/input/hotkeys.hpp rename to bsnes/ui-snes/input/hotkeys.hpp diff --git a/bsnes/ui/input/input.cpp b/bsnes/ui-snes/input/input.cpp similarity index 100% rename from bsnes/ui/input/input.cpp rename to bsnes/ui-snes/input/input.cpp diff --git a/bsnes/ui/input/input.hpp b/bsnes/ui-snes/input/input.hpp similarity index 100% rename from bsnes/ui/input/input.hpp rename to bsnes/ui-snes/input/input.hpp diff --git a/bsnes/ui/interface.cpp b/bsnes/ui-snes/interface.cpp similarity index 100% rename from bsnes/ui/interface.cpp rename to bsnes/ui-snes/interface.cpp diff --git a/bsnes/ui/interface.hpp b/bsnes/ui-snes/interface.hpp similarity index 100% rename from bsnes/ui/interface.hpp rename to bsnes/ui-snes/interface.hpp diff --git a/bsnes/ui-snes/main.cpp b/bsnes/ui-snes/main.cpp new file mode 100755 index 00000000..69f8b024 --- /dev/null +++ b/bsnes/ui-snes/main.cpp @@ -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")); +} diff --git a/bsnes/ui/path/path.cpp b/bsnes/ui-snes/path/path.cpp similarity index 100% rename from bsnes/ui/path/path.cpp rename to bsnes/ui-snes/path/path.cpp diff --git a/bsnes/ui/path/path.hpp b/bsnes/ui-snes/path/path.hpp similarity index 100% rename from bsnes/ui/path/path.hpp rename to bsnes/ui-snes/path/path.hpp diff --git a/bsnes/ui-snes/resource.rc b/bsnes/ui-snes/resource.rc new file mode 100755 index 00000000..7fc5b0e3 --- /dev/null +++ b/bsnes/ui-snes/resource.rc @@ -0,0 +1,2 @@ +1 24 "../data/bsnes.Manifest" +2 ICON DISCARDABLE "../data/bsnes.ico" diff --git a/bsnes/ui/settings/advanced.cpp b/bsnes/ui-snes/settings/advanced.cpp similarity index 100% rename from bsnes/ui/settings/advanced.cpp rename to bsnes/ui-snes/settings/advanced.cpp diff --git a/bsnes/ui/settings/advanced.hpp b/bsnes/ui-snes/settings/advanced.hpp similarity index 100% rename from bsnes/ui/settings/advanced.hpp rename to bsnes/ui-snes/settings/advanced.hpp diff --git a/bsnes/ui/settings/audio.cpp b/bsnes/ui-snes/settings/audio.cpp similarity index 100% rename from bsnes/ui/settings/audio.cpp rename to bsnes/ui-snes/settings/audio.cpp diff --git a/bsnes/ui/settings/audio.hpp b/bsnes/ui-snes/settings/audio.hpp similarity index 100% rename from bsnes/ui/settings/audio.hpp rename to bsnes/ui-snes/settings/audio.hpp diff --git a/bsnes/ui/settings/input.cpp b/bsnes/ui-snes/settings/input.cpp similarity index 100% rename from bsnes/ui/settings/input.cpp rename to bsnes/ui-snes/settings/input.cpp diff --git a/bsnes/ui/settings/input.hpp b/bsnes/ui-snes/settings/input.hpp similarity index 100% rename from bsnes/ui/settings/input.hpp rename to bsnes/ui-snes/settings/input.hpp diff --git a/bsnes/ui/settings/settings.cpp b/bsnes/ui-snes/settings/settings.cpp similarity index 100% rename from bsnes/ui/settings/settings.cpp rename to bsnes/ui-snes/settings/settings.cpp diff --git a/bsnes/ui/settings/settings.hpp b/bsnes/ui-snes/settings/settings.hpp similarity index 100% rename from bsnes/ui/settings/settings.hpp rename to bsnes/ui-snes/settings/settings.hpp diff --git a/bsnes/ui/settings/video.cpp b/bsnes/ui-snes/settings/video.cpp similarity index 100% rename from bsnes/ui/settings/video.cpp rename to bsnes/ui-snes/settings/video.cpp diff --git a/bsnes/ui/settings/video.hpp b/bsnes/ui-snes/settings/video.hpp similarity index 100% rename from bsnes/ui/settings/video.hpp rename to bsnes/ui-snes/settings/video.hpp diff --git a/bsnes/ui/tools/cheat-database.cpp b/bsnes/ui-snes/tools/cheat-database.cpp similarity index 100% rename from bsnes/ui/tools/cheat-database.cpp rename to bsnes/ui-snes/tools/cheat-database.cpp diff --git a/bsnes/ui/tools/cheat-database.hpp b/bsnes/ui-snes/tools/cheat-database.hpp similarity index 100% rename from bsnes/ui/tools/cheat-database.hpp rename to bsnes/ui-snes/tools/cheat-database.hpp diff --git a/bsnes/ui/tools/cheat-editor.cpp b/bsnes/ui-snes/tools/cheat-editor.cpp similarity index 100% rename from bsnes/ui/tools/cheat-editor.cpp rename to bsnes/ui-snes/tools/cheat-editor.cpp diff --git a/bsnes/ui/tools/cheat-editor.hpp b/bsnes/ui-snes/tools/cheat-editor.hpp similarity index 100% rename from bsnes/ui/tools/cheat-editor.hpp rename to bsnes/ui-snes/tools/cheat-editor.hpp diff --git a/bsnes/ui/tools/state-manager.cpp b/bsnes/ui-snes/tools/state-manager.cpp similarity index 100% rename from bsnes/ui/tools/state-manager.cpp rename to bsnes/ui-snes/tools/state-manager.cpp diff --git a/bsnes/ui/tools/state-manager.hpp b/bsnes/ui-snes/tools/state-manager.hpp similarity index 100% rename from bsnes/ui/tools/state-manager.hpp rename to bsnes/ui-snes/tools/state-manager.hpp diff --git a/bsnes/ui/tools/tools.cpp b/bsnes/ui-snes/tools/tools.cpp similarity index 100% rename from bsnes/ui/tools/tools.cpp rename to bsnes/ui-snes/tools/tools.cpp diff --git a/bsnes/ui/tools/tools.hpp b/bsnes/ui-snes/tools/tools.hpp similarity index 100% rename from bsnes/ui/tools/tools.hpp rename to bsnes/ui-snes/tools/tools.hpp diff --git a/bsnes/ui-snes/utility/utility.cpp b/bsnes/ui-snes/utility/utility.cpp new file mode 100755 index 00000000..fdfb8783 --- /dev/null +++ b/bsnes/ui-snes/utility/utility.cpp @@ -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; +} diff --git a/bsnes/ui-snes/utility/utility.hpp b/bsnes/ui-snes/utility/utility.hpp new file mode 100755 index 00000000..9a3f9dad --- /dev/null +++ b/bsnes/ui-snes/utility/utility.hpp @@ -0,0 +1,37 @@ +struct Utility : property { + 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; diff --git a/bsnes/ui/Makefile b/bsnes/ui/Makefile index dc521b62..c7824725 100755 --- a/bsnes/ui/Makefile +++ b/bsnes/ui/Makefile @@ -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 diff --git a/bsnes/ui/base.hpp b/bsnes/ui/base.hpp index 34dcabdb..b85b4e32 100755 --- a/bsnes/ui/base.hpp +++ b/bsnes/ui/base.hpp @@ -1,65 +1,39 @@ +#include #include +#include -#include -#include -#include #include -#include #include #include +#include #include #include -#include #include #include #include using namespace nall; -#include -using namespace ruby; - #include using namespace phoenix; -struct TopLevelWindow : Window { - string name; - string position; -}; +#include +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 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; diff --git a/bsnes/ui/general/general.cpp b/bsnes/ui/general/general.cpp index be70ba66..1d067ea8 100755 --- a/bsnes/ui/general/general.cpp +++ b/bsnes/ui/general/general.cpp @@ -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" diff --git a/bsnes/ui/general/general.hpp b/bsnes/ui/general/general.hpp index b0f94545..9f5a58c7 100755 --- a/bsnes/ui/general/general.hpp +++ b/bsnes/ui/general/general.hpp @@ -1,5 +1 @@ #include "main-window.hpp" -#include "file-browser.hpp" -#include "slot-loader.hpp" -#include "nss-dip-window.hpp" -#include "about-window.hpp" diff --git a/bsnes/ui/general/main-window.cpp b/bsnes/ui/general/main-window.cpp index e0e8850d..4ae57df8 100755 --- a/bsnes/ui/general/main-window.cpp +++ b/bsnes/ui/general/main-window.cpp @@ -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 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); - } } diff --git a/bsnes/ui/general/main-window.hpp b/bsnes/ui/general/main-window.hpp index 45cdfd2b..a22cb9d5 100755 --- a/bsnes/ui/general/main-window.hpp +++ b/bsnes/ui/general/main-window.hpp @@ -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; diff --git a/bsnes/ui/interface/gameboy.cpp b/bsnes/ui/interface/gameboy.cpp new file mode 100755 index 00000000..0bc869fd --- /dev/null +++ b/bsnes/ui/interface/gameboy.cpp @@ -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; +} diff --git a/bsnes/ui/interface/gameboy.hpp b/bsnes/ui/interface/gameboy.hpp new file mode 100755 index 00000000..b9c1c97a --- /dev/null +++ b/bsnes/ui/interface/gameboy.hpp @@ -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); +}; diff --git a/bsnes/ui/interface/interface.cpp b/bsnes/ui/interface/interface.cpp new file mode 100755 index 00000000..71111177 --- /dev/null +++ b/bsnes/ui/interface/interface.cpp @@ -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; + } +} diff --git a/bsnes/ui/interface/interface.hpp b/bsnes/ui/interface/interface.hpp new file mode 100755 index 00000000..617f6e3b --- /dev/null +++ b/bsnes/ui/interface/interface.hpp @@ -0,0 +1,37 @@ +#include "nes.hpp" +#include "snes.hpp" +#include "gameboy.hpp" + +struct Interface : property { + enum class Mode : unsigned { None, SNES, NES, GameBoy }; + readonly 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; diff --git a/bsnes/ui/interface/nes.cpp b/bsnes/ui/interface/nes.cpp new file mode 100755 index 00000000..2bef1fd8 --- /dev/null +++ b/bsnes/ui/interface/nes.cpp @@ -0,0 +1,57 @@ +bool InterfaceNES::loadCartridge(const string &filename) { + filemap fp; + if(fp.open(filename, filemap::mode::read) == false) return false; + + interface->baseName = nall::basename(filename); + NES::cartridge.load("", fp.data(), fp.size()); + NES::system.power(); + + fp.close(); + return true; +} + +void InterfaceNES::unloadCartridge() { + NES::cartridge.unload(); + interface->baseName = ""; +} + +// + +void InterfaceNES::video_refresh(const uint32_t *data) { + interface->video_refresh(); + + uint32_t *output; + unsigned outpitch; + if(video.lock(output, outpitch, 256, 240)) { + for(unsigned y = 0; y < 240; y++) { + const uint32_t *sp = data + y * 256; + uint32_t *dp = output + y * (outpitch >> 2); + for(unsigned x = 0; x < 256; x++) { + *dp++ = *sp++; + } + } + + video.unlock(); + video.refresh(); + } +} + +void InterfaceNES::audio_sample(int16_t lsample, int16_t rsample) { +} + +int16_t InterfaceNES::input_poll(bool port, unsigned device, unsigned id) { + if(port == 0 && device == 0) { + switch(id) { + case 0: return interface->inputState[keyboard(0)[Keyboard::X]]; + case 1: return interface->inputState[keyboard(0)[Keyboard::Z]]; + case 2: return interface->inputState[keyboard(0)[Keyboard::Apostrophe]]; + case 3: return interface->inputState[keyboard(0)[Keyboard::Return]]; + case 4: return interface->inputState[keyboard(0)[Keyboard::Up]]; + case 5: return interface->inputState[keyboard(0)[Keyboard::Down]]; + case 6: return interface->inputState[keyboard(0)[Keyboard::Left]]; + case 7: return interface->inputState[keyboard(0)[Keyboard::Right]]; + } + } + + return 0; +} diff --git a/bsnes/ui/interface/nes.hpp b/bsnes/ui/interface/nes.hpp new file mode 100755 index 00000000..4af344a4 --- /dev/null +++ b/bsnes/ui/interface/nes.hpp @@ -0,0 +1,8 @@ +struct InterfaceNES : NES::Interface { + bool loadCartridge(const string &filename); + void unloadCartridge(); + + void video_refresh(const uint32_t *data); + void audio_sample(int16_t lsample, int16_t rsample); + int16_t input_poll(bool port, unsigned device, unsigned id); +}; diff --git a/bsnes/ui/interface/snes.cpp b/bsnes/ui/interface/snes.cpp new file mode 100755 index 00000000..daf12bd7 --- /dev/null +++ b/bsnes/ui/interface/snes.cpp @@ -0,0 +1,91 @@ +bool InterfaceSNES::loadCartridge(const string &filename) { + uint8_t *data; + unsigned size; + if(file::read(filename, data, size) == false) return false; + + interface->baseName = nall::basename(filename); + string xml = SNESCartridge(data, size).xmlMemoryMap; + SNES::cartridge.rom.copy(data, size); + SNES::cartridge.load(SNES::Cartridge::Mode::Normal, { xml }); + SNES::system.power(); + + delete[] data; + return true; +} + +void InterfaceSNES::unloadCartridge() { + SNES::cartridge.unload(); + interface->baseName = ""; +} + +// + +void InterfaceSNES::video_refresh(const uint16_t *data, bool hires, bool interlace, bool overscan) { + interface->video_refresh(); + + unsigned width = hires ? 512 : 256; + unsigned height = 0 ? 224 : 239; + if(interlace) height <<= 1; + unsigned inpitch = interlace ? 1024 : 2048; + + if(0) { //NTSC + if(overscan == false) data += 9 * 1024; // 0 + 224 + 0 + if(overscan == true ) data += 16 * 1024; //-7 + 224 + -7 + } + + if(1) { //PAL + if(overscan == false) data += 1 * 1024; // 8 + 224 + 7 + if(overscan == true ) data += 9 * 1024; // 0 + 239 + 0 + } + + uint32_t *output; + unsigned outpitch; + if(video.lock(output, outpitch, width, height)) { + for(unsigned y = 0; y < height; y++) { + const uint16_t *sp = data + y * (inpitch >> 1); + uint32_t *dp = output + y * (outpitch >> 2); + for(unsigned x = 0; x < width; x++) { + uint32_t color = *sp++; + color = ((color & 0x7c00) << 9) | ((color & 0x03e0) << 6) | ((color & 0x001f) << 3); + *dp++ = color | ((color >> 3) & 0x070707); + } + } + + video.unlock(); + video.refresh(); + } +} + +void InterfaceSNES::audio_sample(int16_t lsample, int16_t rsample) { + dspaudio.sample(lsample, rsample); + while(dspaudio.pending()) { + signed lsample, rsample; + dspaudio.read(lsample, rsample); + audio.sample(lsample, rsample); + } +} + +int16_t InterfaceSNES::input_poll(bool port, SNES::Input::Device device, unsigned index, unsigned id) { + if(port == 0 && device == SNES::Input::Device::Joypad) { + switch((SNES::Input::JoypadID)id) { + case SNES::Input::JoypadID::Up: return interface->inputState[keyboard(0)[Keyboard::Up]]; + case SNES::Input::JoypadID::Down: return interface->inputState[keyboard(0)[Keyboard::Down]]; + case SNES::Input::JoypadID::Left: return interface->inputState[keyboard(0)[Keyboard::Left]]; + case SNES::Input::JoypadID::Right: return interface->inputState[keyboard(0)[Keyboard::Right]]; + case SNES::Input::JoypadID::B: return interface->inputState[keyboard(0)[Keyboard::Z]]; + case SNES::Input::JoypadID::A: return interface->inputState[keyboard(0)[Keyboard::X]]; + case SNES::Input::JoypadID::Y: return interface->inputState[keyboard(0)[Keyboard::A]]; + case SNES::Input::JoypadID::X: return interface->inputState[keyboard(0)[Keyboard::S]]; + case SNES::Input::JoypadID::L: return interface->inputState[keyboard(0)[Keyboard::D]]; + case SNES::Input::JoypadID::R: return interface->inputState[keyboard(0)[Keyboard::C]]; + case SNES::Input::JoypadID::Select: return interface->inputState[keyboard(0)[Keyboard::Apostrophe]]; + case SNES::Input::JoypadID::Start: return interface->inputState[keyboard(0)[Keyboard::Return]]; + } + } + + return 0; +} + +string InterfaceSNES::path(SNES::Cartridge::Slot slot, const string &hint) { + return "/home/byuu/Desktop/test"; +} diff --git a/bsnes/ui/interface/snes.hpp b/bsnes/ui/interface/snes.hpp new file mode 100755 index 00000000..5eb26356 --- /dev/null +++ b/bsnes/ui/interface/snes.hpp @@ -0,0 +1,10 @@ +struct InterfaceSNES : SNES::Interface { + bool loadCartridge(const string &filename); + void unloadCartridge(); + + void video_refresh(const uint16_t *data, bool hires, bool interlace, bool overscan); + void audio_sample(int16_t lsample, int16_t rsample); + int16_t input_poll(bool port, SNES::Input::Device device, unsigned index, unsigned id); + + string path(SNES::Cartridge::Slot slot, const string &hint); +}; diff --git a/bsnes/ui/main.cpp b/bsnes/ui/main.cpp index 69f8b024..133c34e1 100755 --- a/bsnes/ui/main.cpp +++ b/bsnes/ui/main.cpp @@ -1,197 +1,78 @@ #include "base.hpp" -#include "interface.cpp" -#include "config.cpp" + +Application *application = 0; nall::DSP dspaudio; -Application application; -void Application::main(int argc, char **argv) { - #if defined(PLATFORM_WIN) - utf8_args(argc, argv); - #endif +Application::Application(int argc, char **argv) { + application = this; + interface = new Interface; + utility = new Utility; - 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(); + title = "batch v000"; #if defined(PLATFORM_WIN) - proportionalFont = "Tahoma, 8"; - proportionalFontBold = "Tahoma, 8, Bold"; - monospaceFont = "Lucida Console, 8"; - titleFont = "Segoe Print, 16, Bold"; + string videoDriver = "Direct3D", audioDriver = "XAudio2", inputDriver = "RawInput"; + normalFont = "Tahoma, 8"; + boldFont = "Tahoma, 8, Bold"; #else - proportionalFont = "Sans, 8"; - proportionalFontBold = "Sans, 8, Bold"; - monospaceFont = "Liberation Mono, 8"; - titleFont = "Sans, 16, Bold Italic"; + string videoDriver = "OpenGL", audioDriver = "PulseAudio", inputDriver = "SDL"; + normalFont = "Sans, 8"; + boldFont = "Sans, 8, Bold"; #endif - SNES::system.init(&interface); + mainWindow = new MainWindow; + utility->setMode(Interface::Mode::None); + mainWindow->setVisible(); - 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(); + video.driver(videoDriver); + video.set(Video::Handle, mainWindow->viewport.handle()); + video.set(Video::Synchronize, true); + video.set(Video::Filter, 0u); + video.init(); - palette.update(); + audio.driver(audioDriver); + audio.set(Audio::Handle, mainWindow->viewport.handle()); + audio.set(Audio::Synchronize, true); + audio.set(Audio::Latency, 60u); + audio.set(Audio::Frequency, 48000u); + audio.init(); - 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 + dspaudio.setPrecision(16); + dspaudio.setVolume(1.0); + dspaudio.setBalance(0.0); + dspaudio.setResampler(DSP::Resampler::Average); + dspaudio.setResamplerFrequency(48000.0); - loadGeometry(); - saveGeometry(); + input.driver(inputDriver); + input.set(Input::Handle, mainWindow->viewport.handle()); + input.init(); - utility.setScale(config.video.scale); - mainWindow.setVisible(); - OS::processEvents(); + timer.onTimeout = { &Application::run, this }; + timer.setInterval(0); + timer.setEnabled(); - 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); + OS::main(); } -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() { + delete mainWindow; + delete utility; + delete interface; } -Application::Application() { - pause = false; - quit = false; +void Application::run() { + interface->input_poll(); + + if(interface->loaded() == false) { + usleep(20 * 1000); + return; + } + + interface->run(); } int main(int argc, char **argv) { - application.main(argc, argv); + new Application(argc, argv); + delete application; 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")); -} diff --git a/bsnes/ui/utility/utility.cpp b/bsnes/ui/utility/utility.cpp index fdfb8783..bd8a0cc0 100755 --- a/bsnes/ui/utility/utility.cpp +++ b/bsnes/ui/utility/utility.cpp @@ -1,246 +1,83 @@ #include "../base.hpp" -Utility utility; +Utility *utility = 0; -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::setMode(Interface::Mode mode) { + video.clear(); + audio.clear(); -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); - } -} + mainWindow->cartridgeMenu.setVisible(false); + mainWindow->nesMenu.setVisible(false); + mainWindow->snesMenu.setVisible(false); + mainWindow->gameBoyMenu.setVisible(false); -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; + if(mode == Interface::Mode::None) { + mainWindow->setTitle(application->title); + mainWindow->cartridgeMenu.setVisible(true); + mainWindow->setStatusText("No cartridge loaded"); } - 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; + else if(mode == Interface::Mode::NES) { + mainWindow->setTitle({ notdir(interface->baseName), " - ", NES::Info::Name, " v", NES::Info::Version }); + mainWindow->nesMenu.setVisible(true); + dspaudio.setFrequency(315.0 / 88.8 * 6000000.0 / 12.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 }); + else if(mode == Interface::Mode::SNES) { + mainWindow->setTitle({ notdir(interface->baseName), " - ", SNES::Info::Name, " v", SNES::Info::Version }); + mainWindow->snesMenu.setVisible(true); + dspaudio.setFrequency(32040.0); } - if(application.compositorActive) { - if(advancedSettings.compositorPolicyFullScreen.checked()) { - compositor::enable(fullScreen == false); - } + else if(mode == Interface::Mode::GameBoy) { + mainWindow->setTitle({ notdir(interface->baseName), " - ", GameBoy::Info::Name, " v", GameBoy::Info::Version }); + mainWindow->gameBoyMenu.setVisible(true); + dspaudio.setFrequency(4194304.0); } + + resizeMainWindow(); } -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::resizeMainWindow() { + Geometry geometry = mainWindow->geometry(); + unsigned width = geometry.width, height = geometry.height; + + switch(interface->mode()) { + case Interface::Mode::NES: width = 256, height = 240; break; + case Interface::Mode::SNES: width = 256, height = 239; break; + case Interface::Mode::GameBoy: width = 160, height = 144; break; } -} -void Utility::setShader() { - string data; - data.readfile(config.video.shader); - video.set(Video::Shader, (const char*)data); -} + unsigned maxW = geometry.width / width; + unsigned maxH = geometry.height / height; + unsigned maxM = max(1u, min(maxW, maxH)); -void Utility::cartridgeLoaded() { - SNES::system.power(); - cheatEditor.load(); - stateManager.load(); - mainWindow.synchronize(); + width = width * maxM; + height = height * maxM; - string name = baseName(activeSlot()); - utility.setTitle(notdir(name)); - utility.showMessage({ - "Loaded ", notdir(name), - cartridge.patch.applied ? ", and applied BPS patch" : "" + mainWindow->viewport.setGeometry({ + (geometry.width - width) / 2, (geometry.height - height) / 2, + width, height }); - - //NSS - if(SNES::cartridge.has_nss_dip()) { - nssDipWindow.select(); - application.pause = true; - } } -void Utility::cartridgeUnloaded() { - SNES::cartridge.unload(); - cheatEditor.save(); - stateManager.save(); - mainWindow.synchronize(); -} +void Utility::toggleFullScreen() { + static bool fullScreen = false; + static Geometry geometry; -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 }); + if(fullScreen == false) { + geometry = mainWindow->geometry(); + mainWindow->setMenuVisible(false); + mainWindow->setStatusVisible(false); + mainWindow->setFullScreen(true); + input.acquire(); } else { - showMessage({ "Failed to save state ", slot }); + input.unacquire(); + mainWindow->setMenuVisible(true); + mainWindow->setStatusVisible(true); + mainWindow->setFullScreen(false); + mainWindow->setGeometry(geometry); } -} -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; + fullScreen ^= 1; + resizeMainWindow(); } diff --git a/bsnes/ui/utility/utility.hpp b/bsnes/ui/utility/utility.hpp index 9a3f9dad..2fc3a290 100755 --- a/bsnes/ui/utility/utility.hpp +++ b/bsnes/ui/utility/utility.hpp @@ -1,37 +1,7 @@ -struct Utility : property { - 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; +struct Utility { + void setMode(Interface::Mode mode); + void resizeMainWindow(); + void toggleFullScreen(); }; -extern Utility utility; +extern Utility *utility;