Update to v087r04 release.

byuu says:

GBA stuff re-added. Only thing missing that was there before is the ARM
branch opcode.
Since we're going to be staring at it for a very long time, I added
a more interesting test video pattern.

Went from 6fps to 912fps. Amazing what being able to divide can do for
a frame rate.
This commit is contained in:
Tim Allen 2012-03-19 22:19:53 +11:00
parent 95c62f92ac
commit 6701403745
52 changed files with 1123 additions and 61 deletions

View File

@ -3,6 +3,7 @@ include nall/Makefile
nes := nes
snes := snes
gb := gb
gba := gba
profile := accuracy
target := ui
@ -92,6 +93,6 @@ sync:
rm -r phoenix/test
archive-all:
tar -cjf bsnes.tar.bz2 base data gb 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 gb 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.03";
const char Version[] = "087.04";
#include <nall/platform.hpp>
#include <nall/algorithm.hpp>

View File

@ -65,7 +65,7 @@ void Video::generate(Format format) {
}
Video::Video() {
palette = new unsigned[1 << 15];
palette = new unsigned[1 << 15]();
}
Video::~Video() {

14
bsnes/gba/Makefile Executable file
View File

@ -0,0 +1,14 @@
gba_objects := gba-memory gba-interface gba-scheduler gba-system
gba_objects += gba-video gba-cartridge
gba_objects += gba-cpu gba-ppu gba-apu
objects += $(gba_objects)
obj/gba-memory.o: $(gba)/memory/memory.cpp $(call rwildcard,$(gba)/memory)
obj/gba-interface.o: $(gba)/interface/interface.cpp $(call rwildcard,$(gba)/interface)
obj/gba-scheduler.o: $(gba)/scheduler/scheduler.cpp $(call rwildcard,$(gba)/scheduler)
obj/gba-system.o: $(gba)/system/system.cpp $(call rwildcard,$(gba)/system)
obj/gba-video.o: $(gba)/video/video.cpp $(call rwildcard,$(gba)/video)
obj/gba-cartridge.o: $(gba)/cartridge/cartridge.cpp $(call rwildcard,$(gba)/cartridge)
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)

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

@ -0,0 +1,25 @@
#include <gba/gba.hpp>
namespace GBA {
APU apu;
void APU::Enter() { apu.enter(); }
void APU::enter() {
while(true) {
interface->audioSample(0, 0);
step(512);
}
}
void APU::step(unsigned clocks) {
clock += clocks;
if(clock >= 0) 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,30 @@
#include <gba/gba.hpp>
namespace GBA {
Cartridge cartridge;
bool Cartridge::load(const string &markup, const uint8_t *data, unsigned size) {
if(cartridge.rom.data) delete[] cartridge.rom.data;
cartridge.rom.data = new uint8[cartridge.rom.size = size];
memcpy(cartridge.rom.data, data, size);
sha256 = nall::sha256(cartridge.rom.data, cartridge.rom.size);
return loaded = true;
}
void Cartridge::unload() {
if(loaded) return;
loaded = false;
delete[] cartridge.rom.data;
cartridge.rom.data = nullptr;
cartridge.rom.size = 0u;
}
Cartridge::Cartridge() {
loaded = false;
}
}

View File

@ -0,0 +1,14 @@
struct Cartridge : property<Cartridge> {
StaticMemory rom;
StaticMemory ram;
readonly<bool> loaded;
readonly<string> sha256;
bool load(const string &markup, const uint8_t *data, unsigned size);
void unload();
Cartridge();
};
extern Cartridge cartridge;

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

@ -0,0 +1,8 @@
#include "registers.cpp"
void ARM::power() {
processor.power();
pipeline.reload = true;
exception = false;
r(15).modify = [&] { pipeline.reload = true; };
}

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

@ -0,0 +1,4 @@
struct ARM {
#include "registers.hpp"
void power();
};

View File

@ -0,0 +1,58 @@
void ARM::Processor::power() {
r0 = r1 = r2 = r3 = r4 = r5 = r6 = r7 = 0;
usr.r8 = usr.r9 = usr.r10 = usr.r11 = usr.r12 = usr.sp = usr.lr = 0;
fiq.r8 = fiq.r9 = fiq.r10 = fiq.r11 = fiq.r12 = fiq.sp = fiq.lr = 0;
irq.sp = irq.lr = 0;
svc.sp = svc.lr = 0;
abt.sp = abt.lr = 0;
und.sp = und.lr = 0;
pc = 0;
cpsr = 0;
spsr = nullptr;
fiq.spsr = 0;
irq.spsr = 0;
svc.spsr = 0;
abt.spsr = 0;
und.spsr = 0;
r[0] = &r0;
r[1] = &r1;
r[2] = &r2;
r[3] = &r3;
r[4] = &r4;
r[5] = &r5;
r[6] = &r6;
r[7] = &r7;
r[15] = &pc;
setMode(Mode::SYS);
}
void ARM::Processor::setMode(Mode mode) {
cpsr.m = (unsigned)mode;
if(mode == Mode::FIQ) {
r[ 8] = &fiq.r8;
r[ 9] = &fiq.r9;
r[10] = &fiq.r10;
r[11] = &fiq.r11;
r[12] = &fiq.r12;
} else {
r[ 8] = &usr.r8;
r[ 9] = &usr.r9;
r[10] = &usr.r10;
r[11] = &usr.r11;
r[12] = &usr.r12;
}
switch(mode) {
case Mode::FIQ: r[13] = &fiq.sp; r[14] = &fiq.lr; spsr = &fiq.spsr; break;
case Mode::IRQ: r[13] = &irq.sp; r[14] = &irq.lr; spsr = &irq.spsr; break;
case Mode::SVC: r[13] = &svc.sp; r[14] = &svc.lr; spsr = &svc.spsr; break;
case Mode::ABT: r[13] = &abt.sp; r[14] = &abt.lr; spsr = &abt.spsr; break;
case Mode::UND: r[13] = &und.sp; r[14] = &und.lr; spsr = &und.spsr; break;
default: r[13] = &usr.sp; r[14] = &usr.lr; spsr = nullptr; break;
}
}

117
bsnes/gba/cpu/core/registers.hpp Executable file
View File

@ -0,0 +1,117 @@
struct GPR {
uint32 data;
function<void ()> modify;
inline operator uint32() const { return data; }
inline GPR& operator=(uint32 n) { data = n; if(modify) modify(); return *this; }
inline GPR& operator &=(uint32 n) { return operator=(data & n); }
inline GPR& operator |=(uint32 n) { return operator=(data | n); }
inline GPR& operator ^=(uint32 n) { return operator=(data ^ n); }
inline GPR& operator +=(uint32 n) { return operator=(data + n); }
inline GPR& operator -=(uint32 n) { return operator=(data - n); }
inline GPR& operator *=(uint32 n) { return operator=(data * n); }
inline GPR& operator /=(uint32 n) { return operator=(data / n); }
inline GPR& operator %=(uint32 n) { return operator=(data % n); }
inline GPR& operator<<=(uint32 n) { return operator=(data << n); }
inline GPR& operator>>=(uint32 n) { return operator=(data >> n); }
};
struct PSR {
bool n; //negative
bool z; //zero
bool c; //carry
bool v; //overflow
bool i; //irq
bool f; //fiq
bool t; //thumb
unsigned m; //mode
inline operator uint32() const {
return (n << 31) + (z << 30) + (c << 29) + (v << 28)
+ (i << 7) + (f << 6) + (t << 5) + (m << 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);
m = d & 31;
return *this;
}
};
struct Pipeline {
bool reload;
struct Instruction {
uint32 opcode;
uint32 address;
};
Instruction execute;
Instruction decode;
Instruction fetch;
};
struct Processor {
enum class Mode : unsigned {
USR = 0x10, //user
FIQ = 0x11, //fast interrupt request
IRQ = 0x12, //interrupt request
SVC = 0x13, //supervisor (software interrupt)
ABT = 0x17, //abort
UND = 0x1b, //undefined
SYS = 0x1f, //system
};
GPR r0, r1, r2, r3, r4, r5, r6, r7;
struct USR {
GPR r8, r9, r10, r11, r12, sp, lr;
} usr;
struct FIQ {
GPR r8, r9, r10, r11, r12, sp, lr;
PSR spsr;
} fiq;
struct IRQ {
GPR sp, lr;
PSR spsr;
} irq;
struct SVC {
GPR sp, lr;
PSR spsr;
} svc;
struct ABT {
GPR sp, lr;
PSR spsr;
} abt;
struct UND {
GPR sp, lr;
PSR spsr;
} und;
GPR pc;
PSR cpsr;
GPR *r[16];
PSR *spsr;
void power();
void setMode(Mode);
};
Processor processor;
Pipeline pipeline;
bool exception;
alwaysinline GPR& r(unsigned n) { return *processor.r[n]; }
alwaysinline PSR& cpsr() { return processor.cpsr; }
alwaysinline PSR& spsr() { return *processor.spsr; }

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

@ -0,0 +1,37 @@
#include <gba/gba.hpp>
namespace GBA {
#include "core/core.cpp"
CPU cpu;
void CPU::Enter() { cpu.enter(); }
void CPU::enter() {
while(true) {
step(2);
}
}
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);
ARM::power();
for(unsigned n = 0; n < iram.size; n++) iram.data[n] = 0;
for(unsigned n = 0; n < eram.size; n++) eram.data[n] = 0;
}
CPU::CPU() {
iram.data = new uint8[iram.size = 32 * 1024];
eram.data = new uint8[eram.size = 256 * 1024];
}
}

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

@ -0,0 +1,16 @@
#include "core/core.hpp"
struct CPU : Processor, ARM {
StaticMemory iram;
StaticMemory eram;
static void Enter();
void enter();
void step(unsigned clocks);
void power();
CPU();
};
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-19
*/
#include <libco/libco.h>
namespace GBA {
enum : unsigned { Byte = 8, Half = 16, Word = 32 };
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/memory/memory.hpp>
#include <gba/interface/interface.hpp>
#include <gba/scheduler/scheduler.hpp>
#include <gba/system/system.hpp>
#include <gba/cartridge/cartridge.hpp>
#include <gba/cpu/cpu.hpp>
#include <gba/ppu/ppu.hpp>
#include <gba/apu/apu.hpp>
#include <gba/video/video.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;

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

@ -0,0 +1,104 @@
#include <gba/gba.hpp>
namespace GBA {
Bus bus;
uint32 StaticMemory::read(uint32 addr, uint32 size) {
unsigned bits = addr & 3;
addr &= ~3;
uint32 word = 0;
switch(size) {
case Word: word |= data[addr + 3] << 24;
word |= data[addr + 2] << 16;
case Half: word |= data[addr + 1] << 8;
case Byte: word |= data[addr + 0] << 0;
}
if(bits) {
unsigned rotate = bits << 3;
word = (word >> rotate) | (word << (32 - rotate));
}
switch(size) {
case Word: return word;
case Half: return word & 0xffff;
case Byte: return word & 0xff;
}
}
void StaticMemory::write(uint32 addr, uint32 size, uint32 word) {
switch(size) {
case Word:
addr &= ~3;
data[addr + 3] = word >> 24;
data[addr + 2] = word >> 16;
data[addr + 1] = word >> 8;
data[addr + 0] = word >> 0;
break;
case Half:
addr &= ~1;
data[addr + 1] = word >> 8;
data[addr + 0] = word >> 0;
break;
case Byte:
data[addr + 0] = word >> 0;
break;
}
}
StaticMemory::StaticMemory() {
data = nullptr;
size = 0u;
}
StaticMemory::~StaticMemory() {
if(data) delete[] data;
}
//
uint32 Bus::read(uint32 addr, uint32 size) {
switch(addr & 0x0f000000) {
case 0x00000000: return system.bios.read(addr & 0x3fff, size);
case 0x01000000: return system.bios.read(addr & 0x3fff, size);
case 0x02000000: return cpu.eram.read(addr & 0x3ffff, size);
case 0x03000000: return cpu.iram.read(addr & 0x7fff, size);
case 0x04000000: return 0u; //MMIO [0x400]
case 0x05000000: return ppu.pram.read(addr & 0x3ff, size);
case 0x06000000: return ppu.vram.read(addr & 0x10000 ? (0x10000 + (addr & 0x7fff)) : (addr & 0xffff), size);
case 0x07000000: return ppu.oam.read(addr & 0x3ff, size);
case 0x08000000: return cartridge.rom.read(addr & 0x1ffffff, size);
case 0x09000000: return cartridge.rom.read(addr & 0x1ffffff, size);
case 0x0a000000: return cartridge.rom.read(addr & 0x1ffffff, size);
case 0x0b000000: return cartridge.rom.read(addr & 0x1ffffff, size);
case 0x0c000000: return cartridge.rom.read(addr & 0x1ffffff, size);
case 0x0d000000: return cartridge.rom.read(addr & 0x1ffffff, size);
case 0x0e000000: return cartridge.ram.read(addr & 0xffff, size);
case 0x0f000000: return cartridge.ram.read(addr & 0xffff, size);
}
}
void Bus::write(uint32 addr, uint32 size, uint32 word) {
switch(addr & 0x0f000000) {
case 0x00000000: return;
case 0x01000000: return;
case 0x02000000: return cpu.eram.write(addr & 0x3ffff, size, word);
case 0x03000000: return cpu.iram.write(addr & 0x7fff, size, word);
case 0x04000000: return; //MMIO [0x400]
case 0x05000000: return ppu.pram.write(addr & 0x3ff, size, word);
case 0x06000000: return ppu.vram.write(addr & 0x10000 ? (0x10000 + (addr & 0x7fff)) : (addr & 0xffff), size, word);
case 0x07000000: return ppu.oam.write(addr & 0x3ff, size, word);
case 0x08000000: return;
case 0x09000000: return;
case 0x0a000000: return;
case 0x0b000000: return;
case 0x0c000000: return;
case 0x0d000000: return;
case 0x0e000000: return cartridge.ram.write(addr & 0xffff, size, word);
case 0x0f000000: return cartridge.ram.write(addr & 0xffff, size, word);
}
}
}

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

@ -0,0 +1,21 @@
struct Memory {
virtual uint32 read(uint32 addr, uint32 size) = 0;
virtual void write(uint32 addr, uint32 size, uint32 word) = 0;
};
struct StaticMemory {
uint8_t *data;
unsigned size;
uint32 read(uint32 addr, uint32 size);
void write(uint32 addr, uint32 size, uint32 word);
StaticMemory();
~StaticMemory();
};
struct Bus : Memory {
uint32 read(uint32 addr, uint32 size);
void write(uint32 addr, uint32 size, uint32 word);
};
extern Bus bus;

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

@ -0,0 +1,55 @@
#include <gba/gba.hpp>
namespace GBA {
PPU ppu;
void PPU::Enter() { ppu.enter(); }
void PPU::enter() {
while(true) {
frame();
step(279620);
}
}
void PPU::step(unsigned clocks) {
clock += clocks;
if(clock >= 0) co_switch(cpu.thread);
}
void PPU::power() {
create(PPU::Enter, 16777216);
for(unsigned n = 0; n < vram.size; n++) vram.data[n] = 0;
for(unsigned n = 0; n < oam.size; n++) oam.data[n] = 0;
for(unsigned n = 0; n < pram.size; n++) pram.data[n] = 0;
}
void PPU::frame() {
static uint16_t output[240 * 160];
static bool once = true;
if(once) {
once = false;
for(signed y = 0; y < 160; y++) {
uint16_t *dp = output + y * 240;
for(signed x = 0; x < 240; x++) {
uint16_t color = sin((x - 60) * 6.283 / 240) * 16 + cos((y - 80) * 6.283 / 160) * 16;
if(color >= 16) color = 31 - color;
*dp++ = color;
}
}
}
interface->videoRefresh(output);
scheduler.exit(Scheduler::ExitReason::FrameEvent);
}
PPU::PPU() {
vram.data = new uint8[vram.size = 96 * 1024];
oam.data = new uint8[oam.size = 1024];
pram.data = new uint8[pram.size = 1024];
}
}

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

@ -0,0 +1,16 @@
struct PPU : Processor {
StaticMemory vram;
StaticMemory oam;
StaticMemory pram;
static void Enter();
void enter();
void step(unsigned clocks);
void power();
void frame();
PPU();
};
extern PPU ppu;

View File

@ -0,0 +1,30 @@
#include <gba/gba.hpp>
namespace GBA {
Scheduler scheduler;
void Scheduler::enter() {
host = co_active();
co_switch(active);
}
void Scheduler::exit(ExitReason reason) {
exit_reason = reason;
active = co_active();
co_switch(host);
}
void Scheduler::power() {
host = co_active();
active = cpu.thread;
}
Scheduler::Scheduler() {
sync = SynchronizeMode::None;
exit_reason = ExitReason::UnknownEvent;
host = nullptr;
active = nullptr;
}
}

View File

@ -0,0 +1,16 @@
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;
cothread_t active;
void enter();
void exit(ExitReason);
void power();
Scheduler();
};
extern Scheduler scheduler;

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

@ -0,0 +1,37 @@
#include <gba/gba.hpp>
namespace GBA {
System system;
void System::BIOS::load(const uint8_t *biosdata, unsigned biossize) {
memcpy(data, biosdata, min(size, biossize));
string sha256 = nall::sha256(data, size);
if(sha256 != "fd2547724b505f487e6dcb29ec2ecff3af35a841a77ab2e85fd87350abd36570") {
interface->message("Warning: Game Boy Advance BIOS SHA256 sum is incorrect.");
}
}
System::BIOS::BIOS() {
data = new uint8[size = 16384]();
}
void System::init() {
}
void System::term() {
}
void System::power() {
cpu.power();
ppu.power();
apu.power();
scheduler.power();
}
void System::run() {
scheduler.enter();
}
}

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

@ -0,0 +1,17 @@
enum class Input : unsigned {
A, B, Select, Start, Right, Left, Up, Down, R, L,
};
struct System {
struct BIOS : StaticMemory {
void load(const uint8_t *data, unsigned size);
BIOS();
} bios;
void init();
void term();
void power();
void run();
};
extern System system;

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

@ -0,0 +1,52 @@
#include <gba/gba.hpp>
namespace GBA {
Video video;
unsigned Video::color(unsigned color) const {
uint5 b = color >> 10;
uint5 g = color >> 5;
uint5 r = color >> 0;
uint10 R = (r << 5) | (r << 0);
uint10 G = (g << 5) | (g << 0);
uint10 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 uint32[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

@ -111,12 +111,12 @@ static string iNES(const uint8_t *data, unsigned size) {
case 34:
output.append(" <board type='NES-BNROM'>\n");
output.append(" <mirror type='", mirror == 0 ? "horizontal" : "vertical", "'/>\n");
output.append(" <mirror mode='", mirror == 0 ? "horizontal" : "vertical", "'/>\n");
break;
case 66:
output.append(" <board type='NES-GNROM'>\n");
output.append(" <mirror type='", mirror == 0 ? "horizontal" : "vertical", "'/>\n");
output.append(" <mirror mode='", mirror == 0 ? "horizontal" : "vertical", "'/>\n");
break;
case 69:
@ -128,7 +128,7 @@ static string iNES(const uint8_t *data, unsigned size) {
case 73:
output.append(" <board type='KONAMI-VRC-3'>\n");
output.append(" <chip type='VRC3'/>\n");
output.append(" <mirror type='", mirror == 0 ? "horizontal" : "vertical", "'/>\n");
output.append(" <mirror mode='", mirror == 0 ? "horizontal" : "vertical", "'/>\n");
prgram = 8192;
break;

View File

@ -1,12 +1,3 @@
//Exceptions:
//00000000 = reset
//00000004 = undefined instruction
//00000008 = software interrupt
//0000000c = prefetch abort
//00000010 = data abort
//00000018 = IRQ (interrupt)
//0000001c = FIQ (fast interrupt)
struct Bridge {
struct Buffer {
bool ready;

View File

@ -1,6 +1,7 @@
include $(nes)/Makefile
include $(snes)/Makefile
include $(gb)/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 <snes/snes.hpp>
#include <gb/gb.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.frequencyGB = 4194304, "Audio::Frequency::GB");
append(audio.frequency = 48000, "Audio::Frequency::Native");
append(audio.frequencyNES = 1789772, "Audio::Frequency::NES");
append(audio.frequencySNES = 32000, "Audio::Frequency::SNES");
append(audio.frequencyGB = 4194304, "Audio::Frequency::GB");
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 frequencyGB;
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,7 @@ 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 ...");
@ -61,6 +62,10 @@ MainWindow::MainWindow() {
gameBoyPower.setText("&Power Cycle");
gameBoyCartridgeUnload.setText("&Unload Cartridge");
gameBoyAdvanceMenu.setText("&Game Boy Advance");
gameBoyAdvancePower.setText("&Power Cycle");
gameBoyAdvanceCartridgeUnload.setText("&Unload Cartridge");
settingsMenu.setText("S&ettings");
settingsVideoFilter.setText("Video &Filter");
settingsVideoFilterNone.setText("None");
@ -103,6 +108,7 @@ MainWindow::MainWindow() {
cartridgeMenu.append(cartridgeLoadSNES);
cartridgeMenu.append(cartridgeLoadGameBoy);
cartridgeMenu.append(cartridgeLoadGameBoyColor);
cartridgeMenu.append(cartridgeLoadGameBoyAdvance);
cartridgeMenu.append(cartridgeSeparator);
cartridgeMenu.append(cartridgeLoadSatellaviewSlotted);
cartridgeMenu.append(cartridgeLoadSatellaview);
@ -138,6 +144,11 @@ MainWindow::MainWindow() {
gameBoyMenu.append(gameBoySeparator);
gameBoyMenu.append(gameBoyCartridgeUnload);
append(gameBoyAdvanceMenu);
gameBoyAdvanceMenu.append(gameBoyAdvancePower);
gameBoyAdvanceMenu.append(gameBoyAdvanceSeparator);
gameBoyAdvanceMenu.append(gameBoyAdvanceCartridgeUnload);
append(settingsMenu);
settingsMenu.append(settingsVideoFilter);
settingsVideoFilter.append(settingsVideoFilterNone);
@ -215,6 +226,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(); };
@ -254,6 +271,9 @@ MainWindow::MainWindow() {
gameBoyPower.onActivate = { &Interface::power, interface };
gameBoyCartridgeUnload.onActivate = { &Interface::unloadCartridge, interface };
gameBoyAdvancePower.onActivate = { &Interface::power, interface };
gameBoyAdvanceCartridgeUnload.onActivate = { &Interface::unloadCartridge, interface };
settingsVideoFilterNone.onActivate = [&] {
config->video.filter = "None";
utility->bindVideoFilter();

View File

@ -7,6 +7,7 @@ struct MainWindow : Window {
Item cartridgeLoadNES;
Item cartridgeLoadGameBoy;
Item cartridgeLoadGameBoyColor;
Item cartridgeLoadGameBoyAdvance;
Separator cartridgeSeparator;
Item cartridgeLoadSatellaviewSlotted;
Item cartridgeLoadSatellaview;
@ -40,6 +41,11 @@ struct MainWindow : Window {
Separator gameBoySeparator;
Item gameBoyCartridgeUnload;
Menu gameBoyAdvanceMenu;
Item gameBoyAdvancePower;
Separator gameBoyAdvanceSeparator;
Item gameBoyAdvanceCartridgeUnload;
Menu settingsMenu;
Menu settingsVideoFilter;
RadioItem settingsVideoFilterNone;

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

@ -0,0 +1,64 @@
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);
}

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

@ -0,0 +1,21 @@
struct GbaController : TertiaryInput {
DigitalInput up, down, left, right;
DigitalInput b, a, l, r;
DigitalInput 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 "gb.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(gb);
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 "gb.hpp"
#include "gba.hpp"
#include "user-interface.hpp"
struct InputManager {
@ -63,6 +64,7 @@ struct InputManager {
NesInput nes;
SnesInput snes;
GbInput gb;
GbaInput gba;
UserInterfaceInput userInterface;
void scan();

View File

@ -56,5 +56,5 @@ NesPort2Input::NesPort2Input() {
NesInput::NesInput() {
name = "NES";
append(port1, port1);
append(port1, port2);
}

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"}) == false) {
message("Error: Game Boy Advance BIOS (bios.rom) 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"}) == false) {
message("Error: Game Boy Advance BIOS (gbabios.rom) 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() {
return GBA::cartridge.unload();
}
void InterfaceGBA::power() {
return GBA::system.power();
}
void InterfaceGBA::reset() {
return GBA::system.power(); //GBA has no reset button
}
void InterfaceGBA::run() {
return 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 "gb/gb.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::GB: return dspaudio.setFrequency(config->audio.frequencyGB);
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::GB: return gb.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::GB: core = &gb; break;
case Mode::GBA: core = &gba; break;
default: core = nullptr; break;
}
@ -110,6 +114,7 @@ bool Interface::loadCartridge(string filename) {
if(filename.endswith(".sfc") || filename.endswith(".sfc/")) result = snes.loadCartridge(filename);
if(filename.endswith(".gb" ) || filename.endswith(".gb/" )) result = gb.loadCartridge(GB::System::Revision::GameBoy, filename);
if(filename.endswith(".gbc") || filename.endswith(".gbc/")) result = gb.loadCartridge(GB::System::Revision::GameBoyColor, filename);
if(filename.endswith(".gba") || filename.endswith(".gba/")) result = gba.loadCartridge(filename);
return result;
}
@ -124,6 +129,7 @@ void Interface::unloadCartridge() {
case Mode::NES: nes.unloadCartridge(); break;
case Mode::SNES: snes.unloadCartridge(); break;
case Mode::GB: gb.unloadCartridge(); break;
case Mode::GBA: gba.unloadCartridge(); break;
}
cartridgeTitle = "";
@ -184,6 +190,7 @@ void Interface::setCheatCodes(const lstring &list) {
case Mode::NES: return nes.setCheats(list);
case Mode::SNES: return snes.setCheats(list);
case Mode::GB: return gb.setCheats(list);
case Mode::GBA: return gba.setCheats(list);
}
}
@ -192,6 +199,7 @@ string Interface::sha256() {
case Mode::NES: return NES::cartridge.sha256();
case Mode::SNES: return SNES::cartridge.sha256();
case Mode::GB: return GB::cartridge.sha256();
case Mode::GBA: return GBA::cartridge.sha256();
}
return "{None}";
}
@ -201,6 +209,7 @@ Interface::Interface() : core(nullptr) {
nes.initialize();
snes.initialize();
gb.initialize();
gba.initialize();
}
//internal

View File

@ -19,6 +19,7 @@ struct CartridgePath {
#include "nes/nes.hpp"
#include "snes/snes.hpp"
#include "gb/gb.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, GB };
enum class Mode : unsigned { None, SNES, NES, GB, GBA };
readonly<Mode> mode;
void bindControllers();
@ -74,6 +75,7 @@ struct Interface : property<Interface> {
InterfaceNES nes;
InterfaceSNES snes;
InterfaceGB gb;
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

@ -1,7 +1,7 @@
AudioSettings *audioSettings = nullptr;
AudioSlider::AudioSlider() {
append(name, { 45, 0 });
append(name, { 50, 0 });
append(value, { 75, 0 });
append(slider, { ~0, 0 });
}
@ -68,6 +68,11 @@ AudioSettings::AudioSettings() {
gb.base = 4194304;
gb.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(gb, { ~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);
gb.setPosition(config->audio.frequencyGB);
gba.setPosition(config->audio.frequencyGBA);
frequencySelection.onChange = latencySelection.onChange = resamplerSelection.onChange =
volume.slider.onChange = nes.slider.onChange = snes.slider.onChange = gb.slider.onChange =
frequencySelection.onChange = latencySelection.onChange = resamplerSelection.onChange = volume.slider.onChange =
nes.slider.onChange = snes.slider.onChange = gb.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.frequencyGB = gb.position();
config->audio.frequencyGBA = gba.position();
nes.value.setText({ nes.position(), "hz" });
snes.value.setText({ snes.position(), "hz" });
gb.value.setText({ gb.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 gb;
AudioSlider gba;
void synchronize();
AudioSettings();

View File

@ -8,6 +8,7 @@ void Utility::setMode(Interface::Mode mode) {
mainWindow->nesMenu.setVisible(false);
mainWindow->snesMenu.setVisible(false);
mainWindow->gameBoyMenu.setVisible(false);
mainWindow->gameBoyAdvanceMenu.setVisible(false);
if(mode == Interface::Mode::None) {
mainWindow->setTitle(application->title);
@ -37,6 +38,12 @@ void Utility::setMode(Interface::Mode mode) {
dspaudio.setChannels(2);
}
else if(mode == Interface::Mode::GBA) {
mainWindow->setTitle(interface->cartridgeTitle);
mainWindow->gameBoyAdvanceMenu.setVisible(true);
dspaudio.setChannels(2);
}
interface->updateDSP();
mainWindow->synchronize();
resizeMainWindow();
@ -51,6 +58,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::GB: width = 160, height = 144; break;
case Interface::Mode::GBA: width = 240, height = 160; break;
}
if(config->video.correctAspectRatio) {

View File

@ -44,3 +44,10 @@ void WindowManager::saveGeometry() {
}
config.save(application->path("geometry.cfg"));
}
//phoenix can destruct windows that are hidden faster
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;