mirror of https://github.com/bsnes-emu/bsnes.git
Update to v087r03 release.
byuu says: I wanted to keep this a secret, but unlike other recent additions, this will easily take several weeks, maybe months, to show anything. Assuming I can even pull it off. Nothing technically overwhelming here, I'm more worried about the near-impossibility of debugging the CPU.
This commit is contained in:
parent
06e83c6154
commit
8db134843f
|
@ -3,6 +3,8 @@ include nall/Makefile
|
||||||
nes := nes
|
nes := nes
|
||||||
snes := snes
|
snes := snes
|
||||||
gameboy := gameboy
|
gameboy := gameboy
|
||||||
|
gba := gba
|
||||||
|
|
||||||
profile := accuracy
|
profile := accuracy
|
||||||
target := ui
|
target := ui
|
||||||
|
|
||||||
|
@ -11,28 +13,34 @@ target := ui
|
||||||
# compiler
|
# compiler
|
||||||
c := $(compiler) -std=gnu99
|
c := $(compiler) -std=gnu99
|
||||||
cpp := $(subst cc,++,$(compiler)) -std=gnu++0x
|
cpp := $(subst cc,++,$(compiler)) -std=gnu++0x
|
||||||
flags := -I. -march=native -O3 -fomit-frame-pointer
|
flags := -I. -O3 -fomit-frame-pointer
|
||||||
link :=
|
link :=
|
||||||
objects := libco
|
objects := libco
|
||||||
|
|
||||||
# profile-guided optimization mode
|
# profile-guided optimization mode
|
||||||
# pgo := instrument
|
# pgo := instrument
|
||||||
# pgo := optimize
|
# pgo := optimize
|
||||||
|
# pgo := analyze
|
||||||
|
|
||||||
ifeq ($(pgo),instrument)
|
ifeq ($(pgo),instrument)
|
||||||
flags += -fprofile-generate
|
flags += -fprofile-generate
|
||||||
link += -lgcov
|
link += -lgcov
|
||||||
else ifeq ($(pgo),optimize)
|
else ifeq ($(pgo),optimize)
|
||||||
flags += -fprofile-use
|
flags += -fprofile-use
|
||||||
|
else ifeq ($(pgo),analyze)
|
||||||
|
flags := $(subst -fomit-frame-pointer,,$(flags))
|
||||||
|
flags += -pg
|
||||||
|
link += -pg -lgcov
|
||||||
endif
|
endif
|
||||||
|
|
||||||
# platform
|
# platform
|
||||||
ifeq ($(platform),x)
|
ifeq ($(platform),x)
|
||||||
link += -s -ldl -lX11 -lXext
|
flags += -march=native
|
||||||
|
link += -ldl -lX11 -lXext
|
||||||
else ifeq ($(platform),osx)
|
else ifeq ($(platform),osx)
|
||||||
else ifeq ($(platform),win)
|
else ifeq ($(platform),win)
|
||||||
link += $(if $(findstring console,$(options)),-mconsole,-mwindows)
|
link += $(if $(findstring console,$(options)),-mconsole,-mwindows)
|
||||||
link += -mthreads -s -luuid -lkernel32 -luser32 -lgdi32 -lcomctl32 -lcomdlg32 -lshell32 -lole32
|
link += -mthreads -luuid -lkernel32 -luser32 -lgdi32 -lcomctl32 -lcomdlg32 -lshell32 -lole32
|
||||||
link += -Wl,-enable-auto-import -Wl,-enable-runtime-pseudo-reloc
|
link += -Wl,-enable-auto-import -Wl,-enable-runtime-pseudo-reloc
|
||||||
else
|
else
|
||||||
unknown_platform: help;
|
unknown_platform: help;
|
||||||
|
@ -91,6 +99,6 @@ sync:
|
||||||
rm -r phoenix/test
|
rm -r phoenix/test
|
||||||
|
|
||||||
archive-all:
|
archive-all:
|
||||||
tar -cjf bsnes.tar.bz2 base data gameboy libco nall nes obj out phoenix ruby snes target-debugger target-libsnes target-ui Makefile cc.bat purge.bat
|
tar -cjf bsnes.tar.bz2 base data gameboy gba libco nall nes obj out phoenix ruby snes target-debugger target-libsnes target-ui Makefile cc.bat purge.bat
|
||||||
|
|
||||||
help:;
|
help:;
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
#ifndef BASE_HPP
|
#ifndef BASE_HPP
|
||||||
#define BASE_HPP
|
#define BASE_HPP
|
||||||
|
|
||||||
const char Version[] = "087.02";
|
const char Version[] = "087.03";
|
||||||
|
|
||||||
#include <nall/platform.hpp>
|
#include <nall/platform.hpp>
|
||||||
#include <nall/algorithm.hpp>
|
#include <nall/algorithm.hpp>
|
||||||
|
|
|
@ -0,0 +1,15 @@
|
||||||
|
gba_objects := gba-interface gba-system gba-scheduler
|
||||||
|
gba_objects += gba-cartridge gba-memory
|
||||||
|
gba_objects += gba-cpu gba-ppu gba-apu
|
||||||
|
gba_objects += gba-video
|
||||||
|
objects += $(gba_objects)
|
||||||
|
|
||||||
|
obj/gba-interface.o: $(gba)/interface/interface.cpp $(call rwildcard,$(gba)/interface)
|
||||||
|
obj/gba-system.o: $(gba)/system/system.cpp $(call rwildcard,$(gba)/system)
|
||||||
|
obj/gba-scheduler.o: $(gba)/scheduler/scheduler.cpp $(call rwildcard,$(gba)/scheduler)
|
||||||
|
obj/gba-cartridge.o: $(gba)/cartridge/cartridge.cpp $(call rwildcard,$(gba)/cartridge)
|
||||||
|
obj/gba-memory.o: $(gba)/memory/memory.cpp $(call rwildcard,$(gba)/memory)
|
||||||
|
obj/gba-cpu.o: $(gba)/cpu/cpu.cpp $(call rwildcard,$(gba)/cpu)
|
||||||
|
obj/gba-ppu.o: $(gba)/ppu/ppu.cpp $(call rwildcard,$(gba)/ppu)
|
||||||
|
obj/gba-apu.o: $(gba)/apu/apu.cpp $(call rwildcard,$(gba)/apu)
|
||||||
|
obj/gba-video.o: $(gba)/video/video.cpp $(call rwildcard,$(gba)/video)
|
|
@ -0,0 +1,30 @@
|
||||||
|
#include <gba/gba.hpp>
|
||||||
|
|
||||||
|
#define APU_CPP
|
||||||
|
namespace GBA {
|
||||||
|
|
||||||
|
APU apu;
|
||||||
|
|
||||||
|
void APU::Enter() { apu.enter(); }
|
||||||
|
|
||||||
|
void APU::enter() {
|
||||||
|
while(true) {
|
||||||
|
//scheduler.synchronize_all();
|
||||||
|
|
||||||
|
interface->audioSample(0, 0);
|
||||||
|
step(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void APU::step(unsigned clocks) {
|
||||||
|
clock += clocks << 9; //16777216hz / 32768hz = 512hz (1 << 9)
|
||||||
|
if(clock >= 0 && scheduler.sync != Scheduler::SynchronizeMode::All) {
|
||||||
|
co_switch(cpu.thread);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void APU::power() {
|
||||||
|
create(APU::Enter, 16777216);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
struct APU : Processor {
|
||||||
|
static void Enter();
|
||||||
|
void enter();
|
||||||
|
void step(unsigned clocks);
|
||||||
|
|
||||||
|
void power();
|
||||||
|
};
|
||||||
|
|
||||||
|
extern APU apu;
|
|
@ -0,0 +1,35 @@
|
||||||
|
#include <gba/gba.hpp>
|
||||||
|
|
||||||
|
#define CARTRIDGE_CPP
|
||||||
|
namespace GBA {
|
||||||
|
|
||||||
|
Cartridge cartridge;
|
||||||
|
|
||||||
|
void Cartridge::load(const string &markup, const uint8_t *data, unsigned size) {
|
||||||
|
romdata = new uint8_t[romsize = size];
|
||||||
|
memcpy(romdata, data, size);
|
||||||
|
sha256 = nall::sha256(data, size);
|
||||||
|
|
||||||
|
loaded = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Cartridge::unload() {
|
||||||
|
if(loaded == false) return;
|
||||||
|
|
||||||
|
delete[] romdata;
|
||||||
|
romdata = nullptr;
|
||||||
|
romsize = 0u;
|
||||||
|
|
||||||
|
loaded = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Cartridge::power() {
|
||||||
|
}
|
||||||
|
|
||||||
|
Cartridge::Cartridge() {
|
||||||
|
loaded = false;
|
||||||
|
romdata = nullptr;
|
||||||
|
romsize = 0u;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,16 @@
|
||||||
|
struct Cartridge : property<Cartridge> {
|
||||||
|
readonly<bool> loaded;
|
||||||
|
readonly<string> sha256;
|
||||||
|
|
||||||
|
uint8_t *romdata;
|
||||||
|
unsigned romsize;
|
||||||
|
|
||||||
|
void load(const string &markup, const uint8_t *data, unsigned size);
|
||||||
|
void unload();
|
||||||
|
|
||||||
|
void power();
|
||||||
|
|
||||||
|
Cartridge();
|
||||||
|
};
|
||||||
|
|
||||||
|
extern Cartridge cartridge;
|
|
@ -0,0 +1,3 @@
|
||||||
|
void ARM7TDMI::power() {
|
||||||
|
for(auto &gpr : r) gpr = 0;
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
struct ARM7TDMI {
|
||||||
|
#include "registers.hpp"
|
||||||
|
GPR r[31];
|
||||||
|
PSR cpsr;
|
||||||
|
PSR spsr[5];
|
||||||
|
|
||||||
|
void power();
|
||||||
|
};
|
|
@ -0,0 +1,62 @@
|
||||||
|
struct GPR {
|
||||||
|
uint32 data;
|
||||||
|
|
||||||
|
inline operator uint32() const { return data; }
|
||||||
|
inline GPR& operator=(uint32 n) { data = n; return *this; }
|
||||||
|
};
|
||||||
|
|
||||||
|
struct PSR {
|
||||||
|
struct Mode {
|
||||||
|
enum : unsigned {
|
||||||
|
User = 0x10,
|
||||||
|
FIQ = 0x11,
|
||||||
|
IRQ = 0x12,
|
||||||
|
SWI = 0x13,
|
||||||
|
Abort = 0x17,
|
||||||
|
Undefined = 0x1b,
|
||||||
|
System = 0x1f,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
bool n, z, c, v;
|
||||||
|
bool i, f, t;
|
||||||
|
unsigned mode;
|
||||||
|
|
||||||
|
inline operator uint32() const {
|
||||||
|
return (n << 31) | (z << 30) | (c << 29) | (v << 28)
|
||||||
|
| (i << 7) | (f << 6) | (t << 5) | (mode << 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline PSR& operator=(uint32 d) {
|
||||||
|
n = d & (1 << 31);
|
||||||
|
z = d & (1 << 30);
|
||||||
|
c = d & (1 << 29);
|
||||||
|
v = d & (1 << 28);
|
||||||
|
i = d & (1 << 7);
|
||||||
|
f = d & (1 << 6);
|
||||||
|
t = d & (1 << 5);
|
||||||
|
mode = d & 31;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct GPRs {
|
||||||
|
struct ID {
|
||||||
|
enum : unsigned {
|
||||||
|
R0, R1, R2, R3, R4, R5, R6, R7, R8, R9, R10, R11, R12, SP, LR, PC,
|
||||||
|
R8_FIQ, R9_FIQ, R10_FIQ, R11_FIQ, R12_FIQ, R13_FIQ, R14_FIQ,
|
||||||
|
R13_SWI, R14_SWI,
|
||||||
|
R13_Abort, R14_Abort,
|
||||||
|
R13_IRQ, R14_IRQ,
|
||||||
|
R13_Undefined, R14_Undefined,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
struct SPSRs {
|
||||||
|
struct ID {
|
||||||
|
enum : unsigned {
|
||||||
|
FIQ, SWI, Abort, IRQ, Undefined,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
|
@ -0,0 +1,32 @@
|
||||||
|
#include <gba/gba.hpp>
|
||||||
|
|
||||||
|
#define CPU_CPP
|
||||||
|
namespace GBA {
|
||||||
|
|
||||||
|
#include "core/core.cpp"
|
||||||
|
CPU cpu;
|
||||||
|
|
||||||
|
void CPU::Enter() { cpu.enter(); }
|
||||||
|
|
||||||
|
void CPU::enter() {
|
||||||
|
while(true) {
|
||||||
|
//scheduler.synchronize_cpu();
|
||||||
|
|
||||||
|
step(4);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void CPU::step(unsigned clocks) {
|
||||||
|
ppu.clock -= clocks;
|
||||||
|
if(ppu.clock < 0) co_switch(ppu.thread);
|
||||||
|
|
||||||
|
//apu.clock -= clocks;
|
||||||
|
//if(apu.clock < 0) co_switch(apu.thread);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CPU::power() {
|
||||||
|
create(CPU::Enter, 16777216);
|
||||||
|
ARM7TDMI::power();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
#include "core/core.hpp"
|
||||||
|
|
||||||
|
struct CPU : Processor, ARM7TDMI {
|
||||||
|
static void Enter();
|
||||||
|
void enter();
|
||||||
|
void step(unsigned clocks);
|
||||||
|
|
||||||
|
void power();
|
||||||
|
};
|
||||||
|
|
||||||
|
extern CPU cpu;
|
|
@ -0,0 +1,61 @@
|
||||||
|
#ifndef GBA_HPP
|
||||||
|
#define GBA_HPP
|
||||||
|
|
||||||
|
#include <base/base.hpp>
|
||||||
|
|
||||||
|
namespace GBA {
|
||||||
|
namespace Info {
|
||||||
|
static const char Name[] = "bgba";
|
||||||
|
static const unsigned SerializerVersion = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
bgba - Game Boy Advance emulator
|
||||||
|
author: byuu
|
||||||
|
license: GPLv3
|
||||||
|
project started: 2012-03-17
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <libco/libco.h>
|
||||||
|
|
||||||
|
namespace GBA {
|
||||||
|
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(nullptr) {
|
||||||
|
}
|
||||||
|
|
||||||
|
inline ~Processor() {
|
||||||
|
if(thread) co_delete(thread);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
#include <gba/interface/interface.hpp>
|
||||||
|
#include <gba/system/system.hpp>
|
||||||
|
#include <gba/scheduler/scheduler.hpp>
|
||||||
|
#include <gba/cartridge/cartridge.hpp>
|
||||||
|
#include <gba/memory/memory.hpp>
|
||||||
|
#include <gba/cpu/cpu.hpp>
|
||||||
|
#include <gba/ppu/ppu.hpp>
|
||||||
|
#include <gba/apu/apu.hpp>
|
||||||
|
#include <gba/video/video.hpp>
|
||||||
|
|
||||||
|
#include <gba/scheduler/scheduler-inline.hpp>
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
|
@ -0,0 +1,21 @@
|
||||||
|
#include <gba/gba.hpp>
|
||||||
|
|
||||||
|
namespace GBA {
|
||||||
|
|
||||||
|
Interface *interface = nullptr;
|
||||||
|
|
||||||
|
void Interface::videoRefresh(const uint16_t *data) {
|
||||||
|
}
|
||||||
|
|
||||||
|
void Interface::audioSample(int16_t lsample, int16_t rsample) {
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Interface::inputPoll(unsigned id) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Interface::message(const string &text) {
|
||||||
|
print(text, "\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
struct Interface {
|
||||||
|
virtual void videoRefresh(const uint16_t *data);
|
||||||
|
virtual void audioSample(int16_t lsample, int16_t rsample);
|
||||||
|
virtual bool inputPoll(unsigned id);
|
||||||
|
|
||||||
|
virtual void message(const string &text);
|
||||||
|
};
|
||||||
|
|
||||||
|
extern Interface *interface;
|
|
@ -0,0 +1,36 @@
|
||||||
|
#include <gba/gba.hpp>
|
||||||
|
|
||||||
|
#define MEMORY_CPP
|
||||||
|
namespace GBA {
|
||||||
|
|
||||||
|
Bus bus;
|
||||||
|
|
||||||
|
uint8 Bus::read(unsigned addr) {
|
||||||
|
if((addr & 0x0fffc000) == 0x00000000) {
|
||||||
|
//00000000-00003fff BIOS
|
||||||
|
return system.bios.data[addr & 0x3fff];
|
||||||
|
}
|
||||||
|
|
||||||
|
if((addr & 0x0e000000) == 0x08000000) {
|
||||||
|
//08000000-09ffffff ROM (wait state 0)
|
||||||
|
return cartridge.romdata[addr & 0x01ffffff];
|
||||||
|
}
|
||||||
|
|
||||||
|
if((addr & 0x0e000000) == 0x0a000000) {
|
||||||
|
//0a000000-0bffffff ROM (wait state 1)
|
||||||
|
return cartridge.romdata[addr & 0x01ffffff];
|
||||||
|
}
|
||||||
|
|
||||||
|
if((addr & 0x0e000000) == 0x0c000000) {
|
||||||
|
//0c000000-0dffffff ROM (wait state 2)
|
||||||
|
return cartridge.romdata[addr & 0x01ffffff];
|
||||||
|
}
|
||||||
|
|
||||||
|
//unmapped
|
||||||
|
return 0u;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Bus::write(unsigned addr, uint8 data) {
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
struct Bus {
|
||||||
|
uint8 read(unsigned addr);
|
||||||
|
void write(unsigned addr, uint8 data);
|
||||||
|
};
|
||||||
|
|
||||||
|
extern Bus bus;
|
|
@ -0,0 +1,38 @@
|
||||||
|
#include <gba/gba.hpp>
|
||||||
|
|
||||||
|
#define PPU_CPP
|
||||||
|
namespace GBA {
|
||||||
|
|
||||||
|
PPU ppu;
|
||||||
|
|
||||||
|
void PPU::Enter() { ppu.enter(); }
|
||||||
|
|
||||||
|
void PPU::enter() {
|
||||||
|
while(true) {
|
||||||
|
//scheduler.synchronize_all();
|
||||||
|
|
||||||
|
step(16777216);
|
||||||
|
static uint16_t buffer[240 * 160];
|
||||||
|
for(unsigned y = 0; y < 160; y++) {
|
||||||
|
uint16_t *dp = buffer + y * 240;
|
||||||
|
for(unsigned x = 0; x < 240; x++) {
|
||||||
|
*dp++ = x + y;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
interface->videoRefresh(buffer);
|
||||||
|
scheduler.exit(Scheduler::ExitReason::FrameEvent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void PPU::step(unsigned clocks) {
|
||||||
|
clock += clocks;
|
||||||
|
if(clock >= 0 && scheduler.sync != Scheduler::SynchronizeMode::All) {
|
||||||
|
co_switch(cpu.thread);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void PPU::power() {
|
||||||
|
create(PPU::Enter, 16777216);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
struct PPU : Processor {
|
||||||
|
static void Enter();
|
||||||
|
void enter();
|
||||||
|
void step(unsigned clocks);
|
||||||
|
|
||||||
|
void power();
|
||||||
|
};
|
||||||
|
|
||||||
|
extern PPU ppu;
|
|
@ -0,0 +1,17 @@
|
||||||
|
void Scheduler::swapto(Processor &p) {
|
||||||
|
active_thread = p.thread;
|
||||||
|
co_switch(active_thread);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Scheduler::synchronize_cpu() {
|
||||||
|
if(sync == SynchronizeMode::CPU) {
|
||||||
|
sync = SynchronizeMode::All;
|
||||||
|
exit(ExitReason::SynchronizeEvent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Scheduler::synchronize_all() {
|
||||||
|
if(sync == SynchronizeMode::All) {
|
||||||
|
exit(ExitReason::SynchronizeEvent);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
#include <gba/gba.hpp>
|
||||||
|
|
||||||
|
#define SCHEDULER_CPP
|
||||||
|
namespace GBA {
|
||||||
|
|
||||||
|
Scheduler scheduler;
|
||||||
|
|
||||||
|
void Scheduler::enter() {
|
||||||
|
host_thread = co_active();
|
||||||
|
co_switch(active_thread);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Scheduler::exit(ExitReason reason) {
|
||||||
|
exit_reason = reason;
|
||||||
|
active_thread = co_active();
|
||||||
|
co_switch(host_thread);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Scheduler::power() {
|
||||||
|
host_thread = co_active();
|
||||||
|
active_thread = cpu.thread;
|
||||||
|
}
|
||||||
|
|
||||||
|
Scheduler::Scheduler() {
|
||||||
|
sync = SynchronizeMode::None;
|
||||||
|
exit_reason = ExitReason::UnknownEvent;
|
||||||
|
host_thread = nullptr;
|
||||||
|
active_thread = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,19 @@
|
||||||
|
struct Scheduler : property<Scheduler> {
|
||||||
|
enum class SynchronizeMode : unsigned { None, CPU, All } sync;
|
||||||
|
enum class ExitReason : unsigned { UnknownEvent, FrameEvent, SynchronizeEvent };
|
||||||
|
readonly<ExitReason> exit_reason;
|
||||||
|
|
||||||
|
cothread_t host_thread;
|
||||||
|
cothread_t active_thread;
|
||||||
|
|
||||||
|
void enter();
|
||||||
|
void exit(ExitReason);
|
||||||
|
alwaysinline void swapto(Processor&);
|
||||||
|
alwaysinline void synchronize_cpu();
|
||||||
|
alwaysinline void synchronize_all();
|
||||||
|
|
||||||
|
void power();
|
||||||
|
Scheduler();
|
||||||
|
};
|
||||||
|
|
||||||
|
extern Scheduler scheduler;
|
|
@ -0,0 +1,53 @@
|
||||||
|
#include <gba/gba.hpp>
|
||||||
|
|
||||||
|
#define SYSTEM_CPP
|
||||||
|
namespace GBA {
|
||||||
|
|
||||||
|
System system;
|
||||||
|
|
||||||
|
void System::BIOS::load(const uint8_t *newdata, unsigned newsize) {
|
||||||
|
if(data) delete[] data;
|
||||||
|
data = new uint8[size = newsize];
|
||||||
|
memcpy(data, newdata, newsize);
|
||||||
|
|
||||||
|
string sha256 = nall::sha256(data, size);
|
||||||
|
if(size != 16384 || sha256 != "fd2547724b505f487e6dcb29ec2ecff3af35a841a77ab2e85fd87350abd36570") {
|
||||||
|
interface->message("Warning: Game Boy Advance BIOS SHA256 sum is incorrect.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
System::BIOS::BIOS() {
|
||||||
|
data = nullptr;
|
||||||
|
size = 0u;
|
||||||
|
}
|
||||||
|
|
||||||
|
System::BIOS::~BIOS() {
|
||||||
|
delete[] data;
|
||||||
|
}
|
||||||
|
|
||||||
|
void System::run() {
|
||||||
|
scheduler.enter();
|
||||||
|
}
|
||||||
|
|
||||||
|
void System::init() {
|
||||||
|
}
|
||||||
|
|
||||||
|
void System::term() {
|
||||||
|
}
|
||||||
|
|
||||||
|
void System::load() {
|
||||||
|
}
|
||||||
|
|
||||||
|
void System::unload() {
|
||||||
|
cartridge.unload();
|
||||||
|
}
|
||||||
|
|
||||||
|
void System::power() {
|
||||||
|
cartridge.power();
|
||||||
|
cpu.power();
|
||||||
|
ppu.power();
|
||||||
|
apu.power();
|
||||||
|
scheduler.power();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
enum class Input : unsigned {
|
||||||
|
A, B, Select, Start, Right, Left, Up, Down, R, L,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct System : property<System> {
|
||||||
|
struct BIOS {
|
||||||
|
uint8_t *data;
|
||||||
|
unsigned size;
|
||||||
|
void load(const uint8_t *data, unsigned size);
|
||||||
|
BIOS();
|
||||||
|
~BIOS();
|
||||||
|
} bios;
|
||||||
|
|
||||||
|
void run();
|
||||||
|
|
||||||
|
void init();
|
||||||
|
void term();
|
||||||
|
void load();
|
||||||
|
void unload();
|
||||||
|
void power();
|
||||||
|
};
|
||||||
|
|
||||||
|
extern System system;
|
|
@ -0,0 +1,53 @@
|
||||||
|
#include <gba/gba.hpp>
|
||||||
|
|
||||||
|
#define VIDEO_CPP
|
||||||
|
namespace GBA {
|
||||||
|
|
||||||
|
Video video;
|
||||||
|
|
||||||
|
unsigned Video::color(unsigned color) const {
|
||||||
|
uint5_t b = color >> 10;
|
||||||
|
uint5_t g = color >> 5;
|
||||||
|
uint5_t r = color >> 0;
|
||||||
|
|
||||||
|
uint10_t R = (r << 5) | (r << 0);
|
||||||
|
uint10_t G = (g << 5) | (g << 0);
|
||||||
|
uint10_t B = (b << 5) | (b << 0);
|
||||||
|
|
||||||
|
return (R << 20) | (G << 10) | (B << 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Video::generate(Format format) {
|
||||||
|
for(unsigned n = 0; n < (1 << 15); n++) palette[n] = color(n);
|
||||||
|
|
||||||
|
if(format == Format::RGB24) {
|
||||||
|
for(unsigned n = 0; n < (1 << 15); n++) {
|
||||||
|
unsigned color = palette[n];
|
||||||
|
palette[n] = ((color >> 6) & 0xff0000) + ((color >> 4) & 0x00ff00) + ((color >> 2) & 0x0000ff);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(format == Format::RGB16) {
|
||||||
|
for(unsigned n = 0; n < (1 << 15); n++) {
|
||||||
|
unsigned color = palette[n];
|
||||||
|
palette[n] = ((color >> 14) & 0xf800) + ((color >> 9) & 0x07e0) + ((color >> 5) & 0x001f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(format == Format::RGB15) {
|
||||||
|
for(unsigned n = 0; n < (1 << 15); n++) {
|
||||||
|
unsigned color = palette[n];
|
||||||
|
palette[n] = ((color >> 15) & 0x7c00) + ((color >> 10) & 0x03e0) + ((color >> 5) & 0x001f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Video::Video() {
|
||||||
|
palette = new unsigned[1 << 15]();
|
||||||
|
}
|
||||||
|
|
||||||
|
Video::~Video() {
|
||||||
|
delete[] palette;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
struct Video {
|
||||||
|
enum class Format : unsigned { RGB30, RGB24, RGB16, RGB15 };
|
||||||
|
unsigned *palette;
|
||||||
|
|
||||||
|
unsigned color(unsigned color) const;
|
||||||
|
void generate(Format format);
|
||||||
|
Video();
|
||||||
|
~Video();
|
||||||
|
};
|
||||||
|
|
||||||
|
extern Video video;
|
|
@ -1,6 +1,7 @@
|
||||||
include $(nes)/Makefile
|
include $(nes)/Makefile
|
||||||
include $(gameboy)/Makefile
|
|
||||||
include $(snes)/Makefile
|
include $(snes)/Makefile
|
||||||
|
include $(gameboy)/Makefile
|
||||||
|
include $(gba)/Makefile
|
||||||
name := bsnes
|
name := bsnes
|
||||||
|
|
||||||
ui_objects := ui-main ui-config ui-interface ui-input ui-utility
|
ui_objects := ui-main ui-config ui-interface ui-input ui-utility
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
#include <nes/nes.hpp>
|
#include <nes/nes.hpp>
|
||||||
#include <gameboy/gameboy.hpp>
|
|
||||||
#include <snes/snes.hpp>
|
#include <snes/snes.hpp>
|
||||||
|
#include <gameboy/gameboy.hpp>
|
||||||
|
#include <gba/gba.hpp>
|
||||||
|
|
||||||
#include <nall/compositor.hpp>
|
#include <nall/compositor.hpp>
|
||||||
#include <nall/config.hpp>
|
#include <nall/config.hpp>
|
||||||
|
|
|
@ -2,49 +2,50 @@
|
||||||
Config *config = nullptr;
|
Config *config = nullptr;
|
||||||
|
|
||||||
Config::Config() {
|
Config::Config() {
|
||||||
attach(video.driver = "", "Video::Driver");
|
append(video.driver = "", "Video::Driver");
|
||||||
attach(video.filter = "None", "Video::Filter");
|
append(video.filter = "None", "Video::Filter");
|
||||||
attach(video.shader = "Blur", "Video::Shader");
|
append(video.shader = "Blur", "Video::Shader");
|
||||||
attach(video.synchronize = true, "Video::Synchronize");
|
append(video.synchronize = true, "Video::Synchronize");
|
||||||
attach(video.correctAspectRatio = true, "Video::CorrectAspectRatio");
|
append(video.correctAspectRatio = true, "Video::CorrectAspectRatio");
|
||||||
|
|
||||||
attach(video.maskOverscan = false, "Video::MaskOverscan");
|
append(video.maskOverscan = false, "Video::MaskOverscan");
|
||||||
attach(video.maskOverscanHorizontal = 8, "Video::MaskOverscanHorizontal");
|
append(video.maskOverscanHorizontal = 8, "Video::MaskOverscanHorizontal");
|
||||||
attach(video.maskOverscanVertical = 8, "Video::MaskOverscanVertical");
|
append(video.maskOverscanVertical = 8, "Video::MaskOverscanVertical");
|
||||||
|
|
||||||
attach(video.brightness = 100, "Video::Brightness");
|
append(video.brightness = 100, "Video::Brightness");
|
||||||
attach(video.contrast = 100, "Video::Contrast");
|
append(video.contrast = 100, "Video::Contrast");
|
||||||
attach(video.gamma = 50, "Video::Gamma");
|
append(video.gamma = 50, "Video::Gamma");
|
||||||
|
|
||||||
attach(video.fullScreenMode = 0, "Video::FullScreenMode");
|
append(video.fullScreenMode = 0, "Video::FullScreenMode");
|
||||||
|
|
||||||
attach(video.startFullScreen = false, "Video::StartFullScreen");
|
append(video.startFullScreen = false, "Video::StartFullScreen");
|
||||||
attach(video.compositionMode = 0, "Video::CompositionMode");
|
append(video.compositionMode = 0, "Video::CompositionMode");
|
||||||
|
|
||||||
attach(audio.driver = "", "Audio::Driver");
|
append(audio.driver = "", "Audio::Driver");
|
||||||
attach(audio.synchronize = true, "Audio::Synchronize");
|
append(audio.synchronize = true, "Audio::Synchronize");
|
||||||
attach(audio.mute = false, "Audio::Mute");
|
append(audio.mute = false, "Audio::Mute");
|
||||||
attach(audio.volume = 100, "Audio::Volume");
|
append(audio.volume = 100, "Audio::Volume");
|
||||||
attach(audio.latency = 60, "Audio::Latency");
|
append(audio.latency = 60, "Audio::Latency");
|
||||||
attach(audio.resampler = "sinc", "Audio::Resampler");
|
append(audio.resampler = "sinc", "Audio::Resampler");
|
||||||
|
|
||||||
attach(audio.frequency = 48000, "Audio::Frequency::Native");
|
append(audio.frequency = 48000, "Audio::Frequency::Native");
|
||||||
attach(audio.frequencyNES = 1789772, "Audio::Frequency::NES");
|
append(audio.frequencyNES = 1789772, "Audio::Frequency::NES");
|
||||||
attach(audio.frequencySNES = 32000, "Audio::Frequency::SNES");
|
append(audio.frequencySNES = 32000, "Audio::Frequency::SNES");
|
||||||
attach(audio.frequencyGameBoy = 4194304, "Audio::Frequency::GameBoy");
|
append(audio.frequencyGameBoy = 4194304, "Audio::Frequency::GameBoy");
|
||||||
|
append(audio.frequencyGBA = 32768, "Audio::Frequency::GBA");
|
||||||
|
|
||||||
attach(input.driver = "", "Input::Driver");
|
append(input.driver = "", "Input::Driver");
|
||||||
attach(input.focusPolicy = 1, "Input::FocusPolicy");
|
append(input.focusPolicy = 1, "Input::FocusPolicy");
|
||||||
|
|
||||||
attach(path.bios.satellaview = "", "Path::BIOS::Satellaview");
|
append(path.bios.satellaview = "", "Path::BIOS::Satellaview");
|
||||||
attach(path.bios.sufamiTurbo = "", "Path::BIOS::SufamiTurbo");
|
append(path.bios.sufamiTurbo = "", "Path::BIOS::SufamiTurbo");
|
||||||
attach(path.bios.superGameBoy = "", "Path::BIOS::SuperGameBoy");
|
append(path.bios.superGameBoy = "", "Path::BIOS::SuperGameBoy");
|
||||||
|
|
||||||
attach(nes.controllerPort1Device = 1, "NES::Controller::Port1");
|
append(nes.controllerPort1Device = 1, "NES::Controller::Port1");
|
||||||
attach(nes.controllerPort2Device = 0, "NES::Controller::Port2");
|
append(nes.controllerPort2Device = 0, "NES::Controller::Port2");
|
||||||
|
|
||||||
attach(snes.controllerPort1Device = 1, "SNES::Controller::Port1");
|
append(snes.controllerPort1Device = 1, "SNES::Controller::Port1");
|
||||||
attach(snes.controllerPort2Device = 0, "SNES::Controller::Port2");
|
append(snes.controllerPort2Device = 0, "SNES::Controller::Port2");
|
||||||
|
|
||||||
load(application->path("settings.cfg"));
|
load(application->path("settings.cfg"));
|
||||||
save(application->path("settings.cfg"));
|
save(application->path("settings.cfg"));
|
||||||
|
|
|
@ -34,6 +34,7 @@ struct Config : public configuration {
|
||||||
unsigned frequencyNES;
|
unsigned frequencyNES;
|
||||||
unsigned frequencySNES;
|
unsigned frequencySNES;
|
||||||
unsigned frequencyGameBoy;
|
unsigned frequencyGameBoy;
|
||||||
|
unsigned frequencyGBA;
|
||||||
} audio;
|
} audio;
|
||||||
|
|
||||||
struct Input {
|
struct Input {
|
||||||
|
|
|
@ -42,13 +42,14 @@ FileBrowser::FileBrowser() {
|
||||||
fileList.onChange = { &FileBrowser::synchronize, this };
|
fileList.onChange = { &FileBrowser::synchronize, this };
|
||||||
fileList.onActivate = openButton.onActivate = { &FileBrowser::fileListActivate, this };
|
fileList.onActivate = openButton.onActivate = { &FileBrowser::fileListActivate, this };
|
||||||
|
|
||||||
filterModes.append({ "Default", "", { "*" } });
|
filterModes.append({ "Default", "", { "*" } });
|
||||||
filterModes.append({ "NES", "", { "*.fc", "*.nes" } });
|
filterModes.append({ "NES", "", { "*.fc", "*.nes" } });
|
||||||
filterModes.append({ "SNES", "", { "*.sfc" } });
|
filterModes.append({ "SNES", "", { "*.sfc" } });
|
||||||
filterModes.append({ "GameBoy", "", { "*.gb", "*.gbc" } });
|
filterModes.append({ "GameBoy", "", { "*.gb", "*.gbc" } });
|
||||||
filterModes.append({ "GameBoyColor", "", { "*.gbc" } });
|
filterModes.append({ "GameBoyColor", "", { "*.gbc" } });
|
||||||
filterModes.append({ "Satellaview", "", { "*.bs" } });
|
filterModes.append({ "GameBoyAdvance", "", { "*.gba" } });
|
||||||
filterModes.append({ "SufamiTurbo", "", { "*.st" } });
|
filterModes.append({ "Satellaview", "", { "*.bs" } });
|
||||||
|
filterModes.append({ "SufamiTurbo", "", { "*.st" } });
|
||||||
mode = &filterModes[Mode::Default];
|
mode = &filterModes[Mode::Default];
|
||||||
|
|
||||||
for(auto &mode : filterModes) config.attach(mode.path, mode.name);
|
for(auto &mode : filterModes) config.attach(mode.path, mode.name);
|
||||||
|
|
|
@ -9,7 +9,16 @@ struct FileBrowser : Window {
|
||||||
Label filterLabel;
|
Label filterLabel;
|
||||||
Button openButton;
|
Button openButton;
|
||||||
|
|
||||||
struct Mode { enum : unsigned { Default, NES, SNES, GameBoy, GameBoyColor, Satellaview, SufamiTurbo }; };
|
struct Mode { enum : unsigned {
|
||||||
|
Default,
|
||||||
|
NES,
|
||||||
|
SNES,
|
||||||
|
GameBoy,
|
||||||
|
GameBoyColor,
|
||||||
|
GameBoyAdvance,
|
||||||
|
Satellaview,
|
||||||
|
SufamiTurbo
|
||||||
|
}; };
|
||||||
void open(const string &title, unsigned mode, function<void (string)> callback);
|
void open(const string &title, unsigned mode, function<void (string)> callback);
|
||||||
|
|
||||||
FileBrowser();
|
FileBrowser();
|
||||||
|
|
|
@ -11,6 +11,7 @@ MainWindow::MainWindow() {
|
||||||
cartridgeLoadNES.setText("Load &NES Cartridge ...");
|
cartridgeLoadNES.setText("Load &NES Cartridge ...");
|
||||||
cartridgeLoadGameBoy.setText("Load &Game Boy Cartridge ...");
|
cartridgeLoadGameBoy.setText("Load &Game Boy Cartridge ...");
|
||||||
cartridgeLoadGameBoyColor.setText("Load Game Boy &Color Cartridge ...");
|
cartridgeLoadGameBoyColor.setText("Load Game Boy &Color Cartridge ...");
|
||||||
|
cartridgeLoadGameBoyAdvance.setText("Load Game Boy &Advance Cartridge ...");
|
||||||
cartridgeLoadSatellaviewSlotted.setText("Load Satellaview-Slotted Cartridge ...");
|
cartridgeLoadSatellaviewSlotted.setText("Load Satellaview-Slotted Cartridge ...");
|
||||||
cartridgeLoadSatellaview.setText("Load Satellaview Cartridge ...");
|
cartridgeLoadSatellaview.setText("Load Satellaview Cartridge ...");
|
||||||
cartridgeLoadSufamiTurbo.setText("Load Sufami Turbo Cartridge ...");
|
cartridgeLoadSufamiTurbo.setText("Load Sufami Turbo Cartridge ...");
|
||||||
|
@ -103,6 +104,7 @@ MainWindow::MainWindow() {
|
||||||
cartridgeMenu.append(cartridgeLoadSNES);
|
cartridgeMenu.append(cartridgeLoadSNES);
|
||||||
cartridgeMenu.append(cartridgeLoadGameBoy);
|
cartridgeMenu.append(cartridgeLoadGameBoy);
|
||||||
cartridgeMenu.append(cartridgeLoadGameBoyColor);
|
cartridgeMenu.append(cartridgeLoadGameBoyColor);
|
||||||
|
cartridgeMenu.append(cartridgeLoadGameBoyAdvance);
|
||||||
cartridgeMenu.append(cartridgeSeparator);
|
cartridgeMenu.append(cartridgeSeparator);
|
||||||
cartridgeMenu.append(cartridgeLoadSatellaviewSlotted);
|
cartridgeMenu.append(cartridgeLoadSatellaviewSlotted);
|
||||||
cartridgeMenu.append(cartridgeLoadSatellaview);
|
cartridgeMenu.append(cartridgeLoadSatellaview);
|
||||||
|
@ -215,6 +217,12 @@ MainWindow::MainWindow() {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
cartridgeLoadGameBoyAdvance.onActivate = [&] {
|
||||||
|
fileBrowser->open("Load Cartridge - Game Boy Advance", FileBrowser::Mode::GameBoyAdvance, [](string filename) {
|
||||||
|
interface->gba.loadCartridge(filename);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
cartridgeLoadSatellaviewSlotted.onActivate = [&] { slotLoader->loadSatellaviewSlotted(); };
|
cartridgeLoadSatellaviewSlotted.onActivate = [&] { slotLoader->loadSatellaviewSlotted(); };
|
||||||
cartridgeLoadSatellaview.onActivate = [&] { slotLoader->loadSatellaview(); };
|
cartridgeLoadSatellaview.onActivate = [&] { slotLoader->loadSatellaview(); };
|
||||||
cartridgeLoadSufamiTurbo.onActivate = [&] { slotLoader->loadSufamiTurbo(); };
|
cartridgeLoadSufamiTurbo.onActivate = [&] { slotLoader->loadSufamiTurbo(); };
|
||||||
|
|
|
@ -7,6 +7,7 @@ struct MainWindow : Window {
|
||||||
Item cartridgeLoadNES;
|
Item cartridgeLoadNES;
|
||||||
Item cartridgeLoadGameBoy;
|
Item cartridgeLoadGameBoy;
|
||||||
Item cartridgeLoadGameBoyColor;
|
Item cartridgeLoadGameBoyColor;
|
||||||
|
Item cartridgeLoadGameBoyAdvance;
|
||||||
Separator cartridgeSeparator;
|
Separator cartridgeSeparator;
|
||||||
Item cartridgeLoadSatellaviewSlotted;
|
Item cartridgeLoadSatellaviewSlotted;
|
||||||
Item cartridgeLoadSatellaview;
|
Item cartridgeLoadSatellaview;
|
||||||
|
|
|
@ -0,0 +1,62 @@
|
||||||
|
int16_t GbaController::poll(unsigned n) {
|
||||||
|
switch((GBA::Input)n) {
|
||||||
|
case GBA::Input::Up: return up.poll() & !down.poll();
|
||||||
|
case GBA::Input::Down: return down.poll() & !up.poll();
|
||||||
|
case GBA::Input::Left: return left.poll() & !right.poll();
|
||||||
|
case GBA::Input::Right: return right.poll() & !left.poll();
|
||||||
|
case GBA::Input::B: return b.poll() | bTurbo.poll();
|
||||||
|
case GBA::Input::A: return a.poll() | aTurbo.poll();
|
||||||
|
case GBA::Input::L: return l.poll() | lTurbo.poll();
|
||||||
|
case GBA::Input::R: return r.poll() | rTurbo.poll();
|
||||||
|
case GBA::Input::Select: return select.poll();
|
||||||
|
case GBA::Input::Start: return start.poll();
|
||||||
|
}
|
||||||
|
return 0; //never reached
|
||||||
|
}
|
||||||
|
|
||||||
|
GbaController::GbaController() {
|
||||||
|
name = "Controller";
|
||||||
|
|
||||||
|
up.name = "Up";
|
||||||
|
down.name = "Down";
|
||||||
|
left.name = "Left";
|
||||||
|
right.name = "Right";
|
||||||
|
b.name = "B";
|
||||||
|
a.name = "A";
|
||||||
|
l.name = "L";
|
||||||
|
r.name = "R";
|
||||||
|
select.name = "Select";
|
||||||
|
start.name = "Start";
|
||||||
|
bTurbo.name = "Turbo B";
|
||||||
|
aTurbo.name = "Turbo A";
|
||||||
|
lTurbo.name = "Turbo L";
|
||||||
|
rTurbo.name = "Turbo R";
|
||||||
|
|
||||||
|
up.mapping = "KB0::Up";
|
||||||
|
down.mapping = "KB0::Down";
|
||||||
|
left.mapping = "KB0::Left";
|
||||||
|
right.mapping = "KB0::Right";
|
||||||
|
b.mapping = "KB0::Z";
|
||||||
|
a.mapping = "KB0::X";
|
||||||
|
l.mapping = "KB0::A";
|
||||||
|
r.mapping = "KB0::S";
|
||||||
|
select.mapping = "KB0::Apostrophe";
|
||||||
|
start.mapping = "KB0::Return";
|
||||||
|
|
||||||
|
append(up, down, left, right, b, a, l, r, select, start);
|
||||||
|
append(bTurbo, aTurbo, lTurbo, rTurbo);
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
|
||||||
|
GbaDevice::GbaDevice() {
|
||||||
|
name = "Device";
|
||||||
|
append(controller);
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
|
||||||
|
GbaInput::GbaInput() {
|
||||||
|
name = "Game Boy Advance";
|
||||||
|
append(device);
|
||||||
|
}
|
|
@ -0,0 +1,20 @@
|
||||||
|
struct GbaController : TertiaryInput {
|
||||||
|
DigitalInput up, down, left, right;
|
||||||
|
DigitalInput b, a, l, r, select, start;
|
||||||
|
TurboInput bTurbo, aTurbo, lTurbo, rTurbo;
|
||||||
|
|
||||||
|
int16_t poll(unsigned n);
|
||||||
|
GbaController();
|
||||||
|
};
|
||||||
|
|
||||||
|
struct GbaDevice : SecondaryInput {
|
||||||
|
GbaController controller;
|
||||||
|
|
||||||
|
GbaDevice();
|
||||||
|
};
|
||||||
|
|
||||||
|
struct GbaInput : PrimaryInput {
|
||||||
|
GbaDevice device;
|
||||||
|
|
||||||
|
GbaInput();
|
||||||
|
};
|
|
@ -2,6 +2,7 @@
|
||||||
#include "nes.cpp"
|
#include "nes.cpp"
|
||||||
#include "snes.cpp"
|
#include "snes.cpp"
|
||||||
#include "gameboy.cpp"
|
#include "gameboy.cpp"
|
||||||
|
#include "gba.cpp"
|
||||||
#include "user-interface.cpp"
|
#include "user-interface.cpp"
|
||||||
InputManager *inputManager = nullptr;
|
InputManager *inputManager = nullptr;
|
||||||
|
|
||||||
|
@ -200,6 +201,7 @@ InputManager::InputManager() {
|
||||||
inputList.append(nes);
|
inputList.append(nes);
|
||||||
inputList.append(snes);
|
inputList.append(snes);
|
||||||
inputList.append(gameBoy);
|
inputList.append(gameBoy);
|
||||||
|
inputList.append(gba);
|
||||||
inputList.append(userInterface);
|
inputList.append(userInterface);
|
||||||
|
|
||||||
for(unsigned n = 0; n < inputList.size(); n++) inputList[n].attach();
|
for(unsigned n = 0; n < inputList.size(); n++) inputList[n].attach();
|
||||||
|
|
|
@ -52,6 +52,7 @@ struct PrimaryInput : array<SecondaryInput&> {
|
||||||
#include "nes.hpp"
|
#include "nes.hpp"
|
||||||
#include "snes.hpp"
|
#include "snes.hpp"
|
||||||
#include "gameboy.hpp"
|
#include "gameboy.hpp"
|
||||||
|
#include "gba.hpp"
|
||||||
#include "user-interface.hpp"
|
#include "user-interface.hpp"
|
||||||
|
|
||||||
struct InputManager {
|
struct InputManager {
|
||||||
|
@ -63,6 +64,7 @@ struct InputManager {
|
||||||
NesInput nes;
|
NesInput nes;
|
||||||
SnesInput snes;
|
SnesInput snes;
|
||||||
GameBoyInput gameBoy;
|
GameBoyInput gameBoy;
|
||||||
|
GbaInput gba;
|
||||||
UserInterfaceInput userInterface;
|
UserInterfaceInput userInterface;
|
||||||
|
|
||||||
void scan();
|
void scan();
|
||||||
|
|
|
@ -0,0 +1,110 @@
|
||||||
|
void InterfaceGBA::initialize() {
|
||||||
|
GBA::interface = this;
|
||||||
|
GBA::system.init();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool InterfaceGBA::cartridgeLoaded() {
|
||||||
|
return GBA::cartridge.loaded();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool InterfaceGBA::loadCartridge(const string &filename) {
|
||||||
|
interface->unloadCartridge();
|
||||||
|
|
||||||
|
uint8_t *biosdata;
|
||||||
|
unsigned biossize;
|
||||||
|
|
||||||
|
uint8_t *cartdata;
|
||||||
|
unsigned cartsize;
|
||||||
|
|
||||||
|
if(filename.endswith("/")) {
|
||||||
|
if(!file::exists({filename, "bios.rom"})) {
|
||||||
|
message("Error: Game Boy Advance BIOS (bios.bin) not found.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if(file::read({filename, "bios.rom"}, biosdata, biossize) == false) return false;
|
||||||
|
if(file::read({filename, "program.rom"}, cartdata, cartsize) == false) return false;
|
||||||
|
interface->base = {true, filename};
|
||||||
|
} else {
|
||||||
|
if(!file::exists({dir(filename), "gbabios.rom"})) {
|
||||||
|
message("Error: Game Boy Advance BIOS (gbabios.bin) not found.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if(file::read({dir(filename), "gbabios.rom"}, biosdata, biossize) == false) return false;
|
||||||
|
if(file::read(filename, cartdata, cartsize) == false) return false;
|
||||||
|
interface->base = {false, filename};
|
||||||
|
}
|
||||||
|
|
||||||
|
interface->game = interface->base;
|
||||||
|
interface->cartridgeTitle = interface->base.title();
|
||||||
|
interface->applyPatch(interface->base, cartdata, cartsize);
|
||||||
|
|
||||||
|
string markup;
|
||||||
|
markup.readfile(interface->base.filename("manifest.xml", ".xml"));
|
||||||
|
|
||||||
|
GBA::system.bios.load(biosdata, biossize);
|
||||||
|
GBA::cartridge.load(markup, cartdata, cartsize);
|
||||||
|
GBA::system.power();
|
||||||
|
delete[] biosdata;
|
||||||
|
delete[] cartdata;
|
||||||
|
|
||||||
|
GBA::video.generate(GBA::Video::Format::RGB30);
|
||||||
|
interface->loadCartridge(::Interface::Mode::GBA);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void InterfaceGBA::unloadCartridge() {
|
||||||
|
GBA::system.unload();
|
||||||
|
}
|
||||||
|
|
||||||
|
void InterfaceGBA::power() {
|
||||||
|
GBA::system.power();
|
||||||
|
}
|
||||||
|
|
||||||
|
void InterfaceGBA::reset() {
|
||||||
|
GBA::system.power(); //GBA lacks reset button
|
||||||
|
}
|
||||||
|
|
||||||
|
void InterfaceGBA::run() {
|
||||||
|
GBA::system.run();
|
||||||
|
}
|
||||||
|
|
||||||
|
serializer InterfaceGBA::serialize() {
|
||||||
|
return serializer();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool InterfaceGBA::unserialize(serializer &s) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void InterfaceGBA::setCheats(const lstring &list) {
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
|
||||||
|
void InterfaceGBA::videoRefresh(const uint16_t *data) {
|
||||||
|
static uint32_t output[240 * 160];
|
||||||
|
|
||||||
|
for(unsigned y = 0; y < 160; y++) {
|
||||||
|
const uint16_t *sp = data + y * 240;
|
||||||
|
uint32_t *dp = output + y * 240;
|
||||||
|
for(unsigned x = 0; x < 240; x++) {
|
||||||
|
uint16_t color = *sp++;
|
||||||
|
*dp++ = GBA::video.palette[color];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface->videoRefresh(output, 240 * sizeof(uint32_t), 240, 160);
|
||||||
|
}
|
||||||
|
|
||||||
|
void InterfaceGBA::audioSample(int16_t lsample, int16_t rsample) {
|
||||||
|
signed samples[] = { lsample, rsample };
|
||||||
|
dspaudio.sample(samples);
|
||||||
|
while(dspaudio.pending()) {
|
||||||
|
dspaudio.read(samples);
|
||||||
|
audio.sample(samples[0], samples[1]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool InterfaceGBA::inputPoll(unsigned id) {
|
||||||
|
return inputManager->gba.device.controller.poll(id);
|
||||||
|
}
|
|
@ -0,0 +1,20 @@
|
||||||
|
struct InterfaceGBA : InterfaceCore, GBA::Interface {
|
||||||
|
void initialize();
|
||||||
|
|
||||||
|
bool cartridgeLoaded();
|
||||||
|
bool loadCartridge(const string &filename);
|
||||||
|
void unloadCartridge();
|
||||||
|
|
||||||
|
void power();
|
||||||
|
void reset();
|
||||||
|
void run();
|
||||||
|
|
||||||
|
serializer serialize();
|
||||||
|
bool unserialize(serializer&);
|
||||||
|
|
||||||
|
void setCheats(const lstring &list = lstring{});
|
||||||
|
|
||||||
|
void videoRefresh(const uint16_t *data);
|
||||||
|
void audioSample(int16_t lsample, int16_t rsample);
|
||||||
|
bool inputPoll(unsigned id);
|
||||||
|
};
|
|
@ -3,6 +3,7 @@
|
||||||
#include "nes/nes.cpp"
|
#include "nes/nes.cpp"
|
||||||
#include "snes/snes.cpp"
|
#include "snes/snes.cpp"
|
||||||
#include "gameboy/gameboy.cpp"
|
#include "gameboy/gameboy.cpp"
|
||||||
|
#include "gba/gba.cpp"
|
||||||
Interface *interface = nullptr;
|
Interface *interface = nullptr;
|
||||||
|
|
||||||
Filter filter;
|
Filter filter;
|
||||||
|
@ -74,6 +75,7 @@ void Interface::updateDSP() {
|
||||||
case Mode::NES: return dspaudio.setFrequency(config->audio.frequencyNES);
|
case Mode::NES: return dspaudio.setFrequency(config->audio.frequencyNES);
|
||||||
case Mode::SNES: return dspaudio.setFrequency(config->audio.frequencySNES);
|
case Mode::SNES: return dspaudio.setFrequency(config->audio.frequencySNES);
|
||||||
case Mode::GameBoy: return dspaudio.setFrequency(config->audio.frequencyGameBoy);
|
case Mode::GameBoy: return dspaudio.setFrequency(config->audio.frequencyGameBoy);
|
||||||
|
case Mode::GBA: return dspaudio.setFrequency(config->audio.frequencyGBA);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -82,6 +84,7 @@ bool Interface::cartridgeLoaded() {
|
||||||
case Mode::NES: return nes.cartridgeLoaded();
|
case Mode::NES: return nes.cartridgeLoaded();
|
||||||
case Mode::SNES: return snes.cartridgeLoaded();
|
case Mode::SNES: return snes.cartridgeLoaded();
|
||||||
case Mode::GameBoy: return gameBoy.cartridgeLoaded();
|
case Mode::GameBoy: return gameBoy.cartridgeLoaded();
|
||||||
|
case Mode::GBA: return gba.cartridgeLoaded();
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -92,6 +95,7 @@ void Interface::loadCartridge(Mode mode) {
|
||||||
case Mode::NES: core = &nes; break;
|
case Mode::NES: core = &nes; break;
|
||||||
case Mode::SNES: core = &snes; break;
|
case Mode::SNES: core = &snes; break;
|
||||||
case Mode::GameBoy: core = &gameBoy; break;
|
case Mode::GameBoy: core = &gameBoy; break;
|
||||||
|
case Mode::GBA: core = &gba; break;
|
||||||
default: core = nullptr; break;
|
default: core = nullptr; break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -124,6 +128,7 @@ void Interface::unloadCartridge() {
|
||||||
case Mode::NES: nes.unloadCartridge(); break;
|
case Mode::NES: nes.unloadCartridge(); break;
|
||||||
case Mode::SNES: snes.unloadCartridge(); break;
|
case Mode::SNES: snes.unloadCartridge(); break;
|
||||||
case Mode::GameBoy: gameBoy.unloadCartridge(); break;
|
case Mode::GameBoy: gameBoy.unloadCartridge(); break;
|
||||||
|
case Mode::GBA: gba.unloadCartridge(); break;
|
||||||
}
|
}
|
||||||
|
|
||||||
cartridgeTitle = "";
|
cartridgeTitle = "";
|
||||||
|
@ -184,6 +189,7 @@ void Interface::setCheatCodes(const lstring &list) {
|
||||||
case Mode::NES: return nes.setCheats(list);
|
case Mode::NES: return nes.setCheats(list);
|
||||||
case Mode::SNES: return snes.setCheats(list);
|
case Mode::SNES: return snes.setCheats(list);
|
||||||
case Mode::GameBoy: return gameBoy.setCheats(list);
|
case Mode::GameBoy: return gameBoy.setCheats(list);
|
||||||
|
case Mode::GBA: return gba.setCheats(list);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -192,6 +198,7 @@ string Interface::sha256() {
|
||||||
case Mode::NES: return NES::cartridge.sha256();
|
case Mode::NES: return NES::cartridge.sha256();
|
||||||
case Mode::SNES: return SNES::cartridge.sha256();
|
case Mode::SNES: return SNES::cartridge.sha256();
|
||||||
case Mode::GameBoy: return GameBoy::cartridge.sha256();
|
case Mode::GameBoy: return GameBoy::cartridge.sha256();
|
||||||
|
case Mode::GBA: return GBA::cartridge.sha256();
|
||||||
}
|
}
|
||||||
return "{None}";
|
return "{None}";
|
||||||
}
|
}
|
||||||
|
@ -201,6 +208,7 @@ Interface::Interface() : core(nullptr) {
|
||||||
nes.initialize();
|
nes.initialize();
|
||||||
snes.initialize();
|
snes.initialize();
|
||||||
gameBoy.initialize();
|
gameBoy.initialize();
|
||||||
|
gba.initialize();
|
||||||
}
|
}
|
||||||
|
|
||||||
//internal
|
//internal
|
||||||
|
|
|
@ -19,6 +19,7 @@ struct CartridgePath {
|
||||||
#include "nes/nes.hpp"
|
#include "nes/nes.hpp"
|
||||||
#include "snes/snes.hpp"
|
#include "snes/snes.hpp"
|
||||||
#include "gameboy/gameboy.hpp"
|
#include "gameboy/gameboy.hpp"
|
||||||
|
#include "gba/gba.hpp"
|
||||||
|
|
||||||
struct Filter : public library {
|
struct Filter : public library {
|
||||||
function<void (unsigned&, unsigned&)> dl_size;
|
function<void (unsigned&, unsigned&)> dl_size;
|
||||||
|
@ -36,7 +37,7 @@ struct Filter : public library {
|
||||||
extern Filter filter;
|
extern Filter filter;
|
||||||
|
|
||||||
struct Interface : property<Interface> {
|
struct Interface : property<Interface> {
|
||||||
enum class Mode : unsigned { None, SNES, NES, GameBoy };
|
enum class Mode : unsigned { None, SNES, NES, GameBoy, GBA };
|
||||||
readonly<Mode> mode;
|
readonly<Mode> mode;
|
||||||
|
|
||||||
void bindControllers();
|
void bindControllers();
|
||||||
|
@ -74,6 +75,7 @@ struct Interface : property<Interface> {
|
||||||
InterfaceNES nes;
|
InterfaceNES nes;
|
||||||
InterfaceSNES snes;
|
InterfaceSNES snes;
|
||||||
InterfaceGameBoy gameBoy;
|
InterfaceGameBoy gameBoy;
|
||||||
|
InterfaceGBA gba;
|
||||||
};
|
};
|
||||||
|
|
||||||
extern Interface *interface;
|
extern Interface *interface;
|
||||||
|
|
|
@ -125,6 +125,7 @@ Application::Application(int argc, char **argv) {
|
||||||
|
|
||||||
interface->unloadCartridge();
|
interface->unloadCartridge();
|
||||||
windowManager->saveGeometry();
|
windowManager->saveGeometry();
|
||||||
|
windowManager->hideAll();
|
||||||
if(compositionEnable) compositor::enable(true);
|
if(compositionEnable) compositor::enable(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -68,6 +68,11 @@ AudioSettings::AudioSettings() {
|
||||||
gameBoy.base = 4194304;
|
gameBoy.base = 4194304;
|
||||||
gameBoy.step = 131;
|
gameBoy.step = 131;
|
||||||
|
|
||||||
|
gba.name.setText("GBA:");
|
||||||
|
gba.slider.setLength(2001);
|
||||||
|
gba.base = 32768;
|
||||||
|
gba.step = 1;
|
||||||
|
|
||||||
append(title, { ~0, 0 }, 5);
|
append(title, { ~0, 0 }, 5);
|
||||||
append(outputLabel, { ~0, 0 }, 0);
|
append(outputLabel, { ~0, 0 }, 0);
|
||||||
append(outputLayout, { ~0, 0 }, 5);
|
append(outputLayout, { ~0, 0 }, 5);
|
||||||
|
@ -82,6 +87,7 @@ AudioSettings::AudioSettings() {
|
||||||
append(nes, { ~0, 0 }, 0);
|
append(nes, { ~0, 0 }, 0);
|
||||||
append(snes, { ~0, 0 }, 0);
|
append(snes, { ~0, 0 }, 0);
|
||||||
append(gameBoy, { ~0, 0 }, 0);
|
append(gameBoy, { ~0, 0 }, 0);
|
||||||
|
append(gba, { ~0, 0 }, 0);
|
||||||
|
|
||||||
frequencySelection.setSelection(
|
frequencySelection.setSelection(
|
||||||
config->audio.frequency == 32000 ? 0 :
|
config->audio.frequency == 32000 ? 0 :
|
||||||
|
@ -109,9 +115,10 @@ AudioSettings::AudioSettings() {
|
||||||
nes.setPosition(config->audio.frequencyNES);
|
nes.setPosition(config->audio.frequencyNES);
|
||||||
snes.setPosition(config->audio.frequencySNES);
|
snes.setPosition(config->audio.frequencySNES);
|
||||||
gameBoy.setPosition(config->audio.frequencyGameBoy);
|
gameBoy.setPosition(config->audio.frequencyGameBoy);
|
||||||
|
gba.setPosition(config->audio.frequencyGBA);
|
||||||
|
|
||||||
frequencySelection.onChange = latencySelection.onChange = resamplerSelection.onChange =
|
frequencySelection.onChange = latencySelection.onChange = resamplerSelection.onChange = volume.slider.onChange =
|
||||||
volume.slider.onChange = nes.slider.onChange = snes.slider.onChange = gameBoy.slider.onChange =
|
nes.slider.onChange = snes.slider.onChange = gameBoy.slider.onChange = gba.slider.onChange =
|
||||||
{ &AudioSettings::synchronize, this };
|
{ &AudioSettings::synchronize, this };
|
||||||
|
|
||||||
synchronize();
|
synchronize();
|
||||||
|
@ -141,10 +148,12 @@ void AudioSettings::synchronize() {
|
||||||
config->audio.frequencyNES = nes.position();
|
config->audio.frequencyNES = nes.position();
|
||||||
config->audio.frequencySNES = snes.position();
|
config->audio.frequencySNES = snes.position();
|
||||||
config->audio.frequencyGameBoy = gameBoy.position();
|
config->audio.frequencyGameBoy = gameBoy.position();
|
||||||
|
config->audio.frequencyGBA = gba.position();
|
||||||
|
|
||||||
nes.value.setText({ nes.position(), "hz" });
|
nes.value.setText({ nes.position(), "hz" });
|
||||||
snes.value.setText({ snes.position(), "hz" });
|
snes.value.setText({ snes.position(), "hz" });
|
||||||
gameBoy.value.setText({ gameBoy.position(), "hz" });
|
gameBoy.value.setText({ gameBoy.position(), "hz" });
|
||||||
|
gba.value.setText({ gba.position(), "hz" });
|
||||||
volume.value.setText({ volume.position(), "%" });
|
volume.value.setText({ volume.position(), "%" });
|
||||||
|
|
||||||
interface->updateDSP();
|
interface->updateDSP();
|
||||||
|
|
|
@ -26,6 +26,7 @@ struct AudioSettings : SettingsLayout {
|
||||||
AudioSlider nes;
|
AudioSlider nes;
|
||||||
AudioSlider snes;
|
AudioSlider snes;
|
||||||
AudioSlider gameBoy;
|
AudioSlider gameBoy;
|
||||||
|
AudioSlider gba;
|
||||||
|
|
||||||
void synchronize();
|
void synchronize();
|
||||||
AudioSettings();
|
AudioSettings();
|
||||||
|
|
|
@ -37,6 +37,11 @@ void Utility::setMode(Interface::Mode mode) {
|
||||||
dspaudio.setChannels(2);
|
dspaudio.setChannels(2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
else if(mode == Interface::Mode::GBA) {
|
||||||
|
mainWindow->setTitle(interface->cartridgeTitle);
|
||||||
|
dspaudio.setChannels(2);
|
||||||
|
}
|
||||||
|
|
||||||
interface->updateDSP();
|
interface->updateDSP();
|
||||||
mainWindow->synchronize();
|
mainWindow->synchronize();
|
||||||
resizeMainWindow();
|
resizeMainWindow();
|
||||||
|
@ -51,6 +56,7 @@ void Utility::resizeMainWindow(bool shrink) {
|
||||||
case Interface::Mode::NES: width = 256, height = 240; break;
|
case Interface::Mode::NES: width = 256, height = 240; break;
|
||||||
case Interface::Mode::SNES: width = 256, height = 240; break;
|
case Interface::Mode::SNES: width = 256, height = 240; break;
|
||||||
case Interface::Mode::GameBoy: width = 160, height = 144; break;
|
case Interface::Mode::GameBoy: width = 160, height = 144; break;
|
||||||
|
case Interface::Mode::GBA: width = 240, height = 160; break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(config->video.correctAspectRatio) {
|
if(config->video.correctAspectRatio) {
|
||||||
|
|
|
@ -44,3 +44,9 @@ void WindowManager::saveGeometry() {
|
||||||
}
|
}
|
||||||
config.save(application->path("geometry.cfg"));
|
config.save(application->path("geometry.cfg"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void WindowManager::hideAll() {
|
||||||
|
for(auto &window : windowList) {
|
||||||
|
window.window->setVisible(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -13,6 +13,7 @@ struct WindowManager {
|
||||||
|
|
||||||
void loadGeometry();
|
void loadGeometry();
|
||||||
void saveGeometry();
|
void saveGeometry();
|
||||||
|
void hideAll();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
configuration config;
|
configuration config;
|
||||||
|
|
Loading…
Reference in New Issue