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:
Tim Allen 2012-03-18 12:04:22 +11:00
parent 06e83c6154
commit 8db134843f
48 changed files with 940 additions and 51 deletions

View File

@ -3,6 +3,8 @@ include nall/Makefile
nes := nes
snes := snes
gameboy := gameboy
gba := gba
profile := accuracy
target := ui
@ -11,28 +13,34 @@ target := ui
# compiler
c := $(compiler) -std=gnu99
cpp := $(subst cc,++,$(compiler)) -std=gnu++0x
flags := -I. -march=native -O3 -fomit-frame-pointer
flags := -I. -O3 -fomit-frame-pointer
link :=
objects := libco
# profile-guided optimization mode
# pgo := instrument
# pgo := optimize
# pgo := analyze
ifeq ($(pgo),instrument)
flags += -fprofile-generate
link += -lgcov
else ifeq ($(pgo),optimize)
flags += -fprofile-use
else ifeq ($(pgo),analyze)
flags := $(subst -fomit-frame-pointer,,$(flags))
flags += -pg
link += -pg -lgcov
endif
# platform
ifeq ($(platform),x)
link += -s -ldl -lX11 -lXext
flags += -march=native
link += -ldl -lX11 -lXext
else ifeq ($(platform),osx)
else ifeq ($(platform),win)
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
else
unknown_platform: help;
@ -91,6 +99,6 @@ sync:
rm -r phoenix/test
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:;

View File

@ -1,7 +1,7 @@
#ifndef BASE_HPP
#define BASE_HPP
const char Version[] = "087.02";
const char Version[] = "087.03";
#include <nall/platform.hpp>
#include <nall/algorithm.hpp>

15
bsnes/gba/Makefile Executable file
View File

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

30
bsnes/gba/apu/apu.cpp Executable file
View File

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

9
bsnes/gba/apu/apu.hpp Executable file
View File

@ -0,0 +1,9 @@
struct APU : Processor {
static void Enter();
void enter();
void step(unsigned clocks);
void power();
};
extern APU apu;

View File

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

View File

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

3
bsnes/gba/cpu/core/core.cpp Executable file
View File

@ -0,0 +1,3 @@
void ARM7TDMI::power() {
for(auto &gpr : r) gpr = 0;
}

8
bsnes/gba/cpu/core/core.hpp Executable file
View File

@ -0,0 +1,8 @@
struct ARM7TDMI {
#include "registers.hpp"
GPR r[31];
PSR cpsr;
PSR spsr[5];
void power();
};

View File

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

32
bsnes/gba/cpu/cpu.cpp Executable file
View File

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

11
bsnes/gba/cpu/cpu.hpp Executable file
View File

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

61
bsnes/gba/gba.hpp Executable file
View File

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

View File

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

View File

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

36
bsnes/gba/memory/memory.cpp Executable file
View File

@ -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) {
}
}

6
bsnes/gba/memory/memory.hpp Executable file
View File

@ -0,0 +1,6 @@
struct Bus {
uint8 read(unsigned addr);
void write(unsigned addr, uint8 data);
};
extern Bus bus;

38
bsnes/gba/ppu/ppu.cpp Executable file
View File

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

9
bsnes/gba/ppu/ppu.hpp Executable file
View File

@ -0,0 +1,9 @@
struct PPU : Processor {
static void Enter();
void enter();
void step(unsigned clocks);
void power();
};
extern PPU ppu;

View File

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

View File

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

View File

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

53
bsnes/gba/system/system.cpp Executable file
View File

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

23
bsnes/gba/system/system.hpp Executable file
View File

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

53
bsnes/gba/video/video.cpp Executable file
View File

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

11
bsnes/gba/video/video.hpp Executable file
View File

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

View File

@ -1,6 +1,7 @@
include $(nes)/Makefile
include $(gameboy)/Makefile
include $(snes)/Makefile
include $(gameboy)/Makefile
include $(gba)/Makefile
name := bsnes
ui_objects := ui-main ui-config ui-interface ui-input ui-utility

View File

@ -1,6 +1,7 @@
#include <nes/nes.hpp>
#include <gameboy/gameboy.hpp>
#include <snes/snes.hpp>
#include <gameboy/gameboy.hpp>
#include <gba/gba.hpp>
#include <nall/compositor.hpp>
#include <nall/config.hpp>

View File

@ -2,49 +2,50 @@
Config *config = nullptr;
Config::Config() {
attach(video.driver = "", "Video::Driver");
attach(video.filter = "None", "Video::Filter");
attach(video.shader = "Blur", "Video::Shader");
attach(video.synchronize = true, "Video::Synchronize");
attach(video.correctAspectRatio = true, "Video::CorrectAspectRatio");
append(video.driver = "", "Video::Driver");
append(video.filter = "None", "Video::Filter");
append(video.shader = "Blur", "Video::Shader");
append(video.synchronize = true, "Video::Synchronize");
append(video.correctAspectRatio = true, "Video::CorrectAspectRatio");
attach(video.maskOverscan = false, "Video::MaskOverscan");
attach(video.maskOverscanHorizontal = 8, "Video::MaskOverscanHorizontal");
attach(video.maskOverscanVertical = 8, "Video::MaskOverscanVertical");
append(video.maskOverscan = false, "Video::MaskOverscan");
append(video.maskOverscanHorizontal = 8, "Video::MaskOverscanHorizontal");
append(video.maskOverscanVertical = 8, "Video::MaskOverscanVertical");
attach(video.brightness = 100, "Video::Brightness");
attach(video.contrast = 100, "Video::Contrast");
attach(video.gamma = 50, "Video::Gamma");
append(video.brightness = 100, "Video::Brightness");
append(video.contrast = 100, "Video::Contrast");
append(video.gamma = 50, "Video::Gamma");
attach(video.fullScreenMode = 0, "Video::FullScreenMode");
append(video.fullScreenMode = 0, "Video::FullScreenMode");
attach(video.startFullScreen = false, "Video::StartFullScreen");
attach(video.compositionMode = 0, "Video::CompositionMode");
append(video.startFullScreen = false, "Video::StartFullScreen");
append(video.compositionMode = 0, "Video::CompositionMode");
attach(audio.driver = "", "Audio::Driver");
attach(audio.synchronize = true, "Audio::Synchronize");
attach(audio.mute = false, "Audio::Mute");
attach(audio.volume = 100, "Audio::Volume");
attach(audio.latency = 60, "Audio::Latency");
attach(audio.resampler = "sinc", "Audio::Resampler");
append(audio.driver = "", "Audio::Driver");
append(audio.synchronize = true, "Audio::Synchronize");
append(audio.mute = false, "Audio::Mute");
append(audio.volume = 100, "Audio::Volume");
append(audio.latency = 60, "Audio::Latency");
append(audio.resampler = "sinc", "Audio::Resampler");
attach(audio.frequency = 48000, "Audio::Frequency::Native");
attach(audio.frequencyNES = 1789772, "Audio::Frequency::NES");
attach(audio.frequencySNES = 32000, "Audio::Frequency::SNES");
attach(audio.frequencyGameBoy = 4194304, "Audio::Frequency::GameBoy");
append(audio.frequency = 48000, "Audio::Frequency::Native");
append(audio.frequencyNES = 1789772, "Audio::Frequency::NES");
append(audio.frequencySNES = 32000, "Audio::Frequency::SNES");
append(audio.frequencyGameBoy = 4194304, "Audio::Frequency::GameBoy");
append(audio.frequencyGBA = 32768, "Audio::Frequency::GBA");
attach(input.driver = "", "Input::Driver");
attach(input.focusPolicy = 1, "Input::FocusPolicy");
append(input.driver = "", "Input::Driver");
append(input.focusPolicy = 1, "Input::FocusPolicy");
attach(path.bios.satellaview = "", "Path::BIOS::Satellaview");
attach(path.bios.sufamiTurbo = "", "Path::BIOS::SufamiTurbo");
attach(path.bios.superGameBoy = "", "Path::BIOS::SuperGameBoy");
append(path.bios.satellaview = "", "Path::BIOS::Satellaview");
append(path.bios.sufamiTurbo = "", "Path::BIOS::SufamiTurbo");
append(path.bios.superGameBoy = "", "Path::BIOS::SuperGameBoy");
attach(nes.controllerPort1Device = 1, "NES::Controller::Port1");
attach(nes.controllerPort2Device = 0, "NES::Controller::Port2");
append(nes.controllerPort1Device = 1, "NES::Controller::Port1");
append(nes.controllerPort2Device = 0, "NES::Controller::Port2");
attach(snes.controllerPort1Device = 1, "SNES::Controller::Port1");
attach(snes.controllerPort2Device = 0, "SNES::Controller::Port2");
append(snes.controllerPort1Device = 1, "SNES::Controller::Port1");
append(snes.controllerPort2Device = 0, "SNES::Controller::Port2");
load(application->path("settings.cfg"));
save(application->path("settings.cfg"));

View File

@ -34,6 +34,7 @@ struct Config : public configuration {
unsigned frequencyNES;
unsigned frequencySNES;
unsigned frequencyGameBoy;
unsigned frequencyGBA;
} audio;
struct Input {

View File

@ -42,13 +42,14 @@ FileBrowser::FileBrowser() {
fileList.onChange = { &FileBrowser::synchronize, this };
fileList.onActivate = openButton.onActivate = { &FileBrowser::fileListActivate, this };
filterModes.append({ "Default", "", { "*" } });
filterModes.append({ "NES", "", { "*.fc", "*.nes" } });
filterModes.append({ "SNES", "", { "*.sfc" } });
filterModes.append({ "GameBoy", "", { "*.gb", "*.gbc" } });
filterModes.append({ "GameBoyColor", "", { "*.gbc" } });
filterModes.append({ "Satellaview", "", { "*.bs" } });
filterModes.append({ "SufamiTurbo", "", { "*.st" } });
filterModes.append({ "Default", "", { "*" } });
filterModes.append({ "NES", "", { "*.fc", "*.nes" } });
filterModes.append({ "SNES", "", { "*.sfc" } });
filterModes.append({ "GameBoy", "", { "*.gb", "*.gbc" } });
filterModes.append({ "GameBoyColor", "", { "*.gbc" } });
filterModes.append({ "GameBoyAdvance", "", { "*.gba" } });
filterModes.append({ "Satellaview", "", { "*.bs" } });
filterModes.append({ "SufamiTurbo", "", { "*.st" } });
mode = &filterModes[Mode::Default];
for(auto &mode : filterModes) config.attach(mode.path, mode.name);

View File

@ -9,7 +9,16 @@ struct FileBrowser : Window {
Label filterLabel;
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);
FileBrowser();

View File

@ -11,6 +11,7 @@ MainWindow::MainWindow() {
cartridgeLoadNES.setText("Load &NES Cartridge ...");
cartridgeLoadGameBoy.setText("Load &Game Boy Cartridge ...");
cartridgeLoadGameBoyColor.setText("Load Game Boy &Color Cartridge ...");
cartridgeLoadGameBoyAdvance.setText("Load Game Boy &Advance Cartridge ...");
cartridgeLoadSatellaviewSlotted.setText("Load Satellaview-Slotted Cartridge ...");
cartridgeLoadSatellaview.setText("Load Satellaview Cartridge ...");
cartridgeLoadSufamiTurbo.setText("Load Sufami Turbo Cartridge ...");
@ -103,6 +104,7 @@ MainWindow::MainWindow() {
cartridgeMenu.append(cartridgeLoadSNES);
cartridgeMenu.append(cartridgeLoadGameBoy);
cartridgeMenu.append(cartridgeLoadGameBoyColor);
cartridgeMenu.append(cartridgeLoadGameBoyAdvance);
cartridgeMenu.append(cartridgeSeparator);
cartridgeMenu.append(cartridgeLoadSatellaviewSlotted);
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(); };
cartridgeLoadSatellaview.onActivate = [&] { slotLoader->loadSatellaview(); };
cartridgeLoadSufamiTurbo.onActivate = [&] { slotLoader->loadSufamiTurbo(); };

View File

@ -7,6 +7,7 @@ struct MainWindow : Window {
Item cartridgeLoadNES;
Item cartridgeLoadGameBoy;
Item cartridgeLoadGameBoyColor;
Item cartridgeLoadGameBoyAdvance;
Separator cartridgeSeparator;
Item cartridgeLoadSatellaviewSlotted;
Item cartridgeLoadSatellaview;

62
bsnes/target-ui/input/gba.cpp Executable file
View File

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

20
bsnes/target-ui/input/gba.hpp Executable file
View File

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

View File

@ -2,6 +2,7 @@
#include "nes.cpp"
#include "snes.cpp"
#include "gameboy.cpp"
#include "gba.cpp"
#include "user-interface.cpp"
InputManager *inputManager = nullptr;
@ -200,6 +201,7 @@ InputManager::InputManager() {
inputList.append(nes);
inputList.append(snes);
inputList.append(gameBoy);
inputList.append(gba);
inputList.append(userInterface);
for(unsigned n = 0; n < inputList.size(); n++) inputList[n].attach();

View File

@ -52,6 +52,7 @@ struct PrimaryInput : array<SecondaryInput&> {
#include "nes.hpp"
#include "snes.hpp"
#include "gameboy.hpp"
#include "gba.hpp"
#include "user-interface.hpp"
struct InputManager {
@ -63,6 +64,7 @@ struct InputManager {
NesInput nes;
SnesInput snes;
GameBoyInput gameBoy;
GbaInput gba;
UserInterfaceInput userInterface;
void scan();

View File

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

View File

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

View File

@ -3,6 +3,7 @@
#include "nes/nes.cpp"
#include "snes/snes.cpp"
#include "gameboy/gameboy.cpp"
#include "gba/gba.cpp"
Interface *interface = nullptr;
Filter filter;
@ -74,6 +75,7 @@ void Interface::updateDSP() {
case Mode::NES: return dspaudio.setFrequency(config->audio.frequencyNES);
case Mode::SNES: return dspaudio.setFrequency(config->audio.frequencySNES);
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::SNES: return snes.cartridgeLoaded();
case Mode::GameBoy: return gameBoy.cartridgeLoaded();
case Mode::GBA: return gba.cartridgeLoaded();
}
return false;
}
@ -92,6 +95,7 @@ void Interface::loadCartridge(Mode mode) {
case Mode::NES: core = &nes; break;
case Mode::SNES: core = &snes; break;
case Mode::GameBoy: core = &gameBoy; break;
case Mode::GBA: core = &gba; break;
default: core = nullptr; break;
}
@ -124,6 +128,7 @@ void Interface::unloadCartridge() {
case Mode::NES: nes.unloadCartridge(); break;
case Mode::SNES: snes.unloadCartridge(); break;
case Mode::GameBoy: gameBoy.unloadCartridge(); break;
case Mode::GBA: gba.unloadCartridge(); break;
}
cartridgeTitle = "";
@ -184,6 +189,7 @@ void Interface::setCheatCodes(const lstring &list) {
case Mode::NES: return nes.setCheats(list);
case Mode::SNES: return snes.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::SNES: return SNES::cartridge.sha256();
case Mode::GameBoy: return GameBoy::cartridge.sha256();
case Mode::GBA: return GBA::cartridge.sha256();
}
return "{None}";
}
@ -201,6 +208,7 @@ Interface::Interface() : core(nullptr) {
nes.initialize();
snes.initialize();
gameBoy.initialize();
gba.initialize();
}
//internal

View File

@ -19,6 +19,7 @@ struct CartridgePath {
#include "nes/nes.hpp"
#include "snes/snes.hpp"
#include "gameboy/gameboy.hpp"
#include "gba/gba.hpp"
struct Filter : public library {
function<void (unsigned&, unsigned&)> dl_size;
@ -36,7 +37,7 @@ struct Filter : public library {
extern Filter filter;
struct Interface : property<Interface> {
enum class Mode : unsigned { None, SNES, NES, GameBoy };
enum class Mode : unsigned { None, SNES, NES, GameBoy, GBA };
readonly<Mode> mode;
void bindControllers();
@ -74,6 +75,7 @@ struct Interface : property<Interface> {
InterfaceNES nes;
InterfaceSNES snes;
InterfaceGameBoy gameBoy;
InterfaceGBA gba;
};
extern Interface *interface;

View File

@ -125,6 +125,7 @@ Application::Application(int argc, char **argv) {
interface->unloadCartridge();
windowManager->saveGeometry();
windowManager->hideAll();
if(compositionEnable) compositor::enable(true);
}

View File

@ -68,6 +68,11 @@ AudioSettings::AudioSettings() {
gameBoy.base = 4194304;
gameBoy.step = 131;
gba.name.setText("GBA:");
gba.slider.setLength(2001);
gba.base = 32768;
gba.step = 1;
append(title, { ~0, 0 }, 5);
append(outputLabel, { ~0, 0 }, 0);
append(outputLayout, { ~0, 0 }, 5);
@ -82,6 +87,7 @@ AudioSettings::AudioSettings() {
append(nes, { ~0, 0 }, 0);
append(snes, { ~0, 0 }, 0);
append(gameBoy, { ~0, 0 }, 0);
append(gba, { ~0, 0 }, 0);
frequencySelection.setSelection(
config->audio.frequency == 32000 ? 0 :
@ -109,9 +115,10 @@ AudioSettings::AudioSettings() {
nes.setPosition(config->audio.frequencyNES);
snes.setPosition(config->audio.frequencySNES);
gameBoy.setPosition(config->audio.frequencyGameBoy);
gba.setPosition(config->audio.frequencyGBA);
frequencySelection.onChange = latencySelection.onChange = resamplerSelection.onChange =
volume.slider.onChange = nes.slider.onChange = snes.slider.onChange = gameBoy.slider.onChange =
frequencySelection.onChange = latencySelection.onChange = resamplerSelection.onChange = volume.slider.onChange =
nes.slider.onChange = snes.slider.onChange = gameBoy.slider.onChange = gba.slider.onChange =
{ &AudioSettings::synchronize, this };
synchronize();
@ -141,10 +148,12 @@ void AudioSettings::synchronize() {
config->audio.frequencyNES = nes.position();
config->audio.frequencySNES = snes.position();
config->audio.frequencyGameBoy = gameBoy.position();
config->audio.frequencyGBA = gba.position();
nes.value.setText({ nes.position(), "hz" });
snes.value.setText({ snes.position(), "hz" });
gameBoy.value.setText({ gameBoy.position(), "hz" });
gba.value.setText({ gba.position(), "hz" });
volume.value.setText({ volume.position(), "%" });
interface->updateDSP();

View File

@ -26,6 +26,7 @@ struct AudioSettings : SettingsLayout {
AudioSlider nes;
AudioSlider snes;
AudioSlider gameBoy;
AudioSlider gba;
void synchronize();
AudioSettings();

View File

@ -37,6 +37,11 @@ void Utility::setMode(Interface::Mode mode) {
dspaudio.setChannels(2);
}
else if(mode == Interface::Mode::GBA) {
mainWindow->setTitle(interface->cartridgeTitle);
dspaudio.setChannels(2);
}
interface->updateDSP();
mainWindow->synchronize();
resizeMainWindow();
@ -51,6 +56,7 @@ void Utility::resizeMainWindow(bool shrink) {
case Interface::Mode::NES: 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::GBA: width = 240, height = 160; break;
}
if(config->video.correctAspectRatio) {

View File

@ -44,3 +44,9 @@ void WindowManager::saveGeometry() {
}
config.save(application->path("geometry.cfg"));
}
void WindowManager::hideAll() {
for(auto &window : windowList) {
window.window->setVisible(false);
}
}

View File

@ -13,6 +13,7 @@ struct WindowManager {
void loadGeometry();
void saveGeometry();
void hideAll();
private:
configuration config;