mirror of https://github.com/bsnes-emu/bsnes.git
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:
parent
95c62f92ac
commit
6701403745
|
@ -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:;
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -65,7 +65,7 @@ void Video::generate(Format format) {
|
|||
}
|
||||
|
||||
Video::Video() {
|
||||
palette = new unsigned[1 << 15];
|
||||
palette = new unsigned[1 << 15]();
|
||||
}
|
||||
|
||||
Video::~Video() {
|
||||
|
|
|
@ -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)
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
struct APU : Processor {
|
||||
static void Enter();
|
||||
void enter();
|
||||
void step(unsigned clocks);
|
||||
|
||||
void power();
|
||||
};
|
||||
|
||||
extern APU apu;
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
|
@ -0,0 +1,8 @@
|
|||
#include "registers.cpp"
|
||||
|
||||
void ARM::power() {
|
||||
processor.power();
|
||||
pipeline.reload = true;
|
||||
exception = false;
|
||||
r(15).modify = [&] { pipeline.reload = true; };
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
struct ARM {
|
||||
#include "registers.hpp"
|
||||
void power();
|
||||
};
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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; }
|
|
@ -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];
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
|
@ -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
|
|
@ -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,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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
|
@ -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];
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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"));
|
||||
|
|
|
@ -34,6 +34,7 @@ struct Config : public configuration {
|
|||
unsigned frequencyNES;
|
||||
unsigned frequencySNES;
|
||||
unsigned frequencyGB;
|
||||
unsigned frequencyGBA;
|
||||
} audio;
|
||||
|
||||
struct Input {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -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();
|
||||
};
|
|
@ -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();
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -56,5 +56,5 @@ NesPort2Input::NesPort2Input() {
|
|||
|
||||
NesInput::NesInput() {
|
||||
name = "NES";
|
||||
append(port1, port1);
|
||||
append(port1, port2);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -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 "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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -125,6 +125,7 @@ Application::Application(int argc, char **argv) {
|
|||
|
||||
interface->unloadCartridge();
|
||||
windowManager->saveGeometry();
|
||||
windowManager->hideAll();
|
||||
if(compositionEnable) compositor::enable(true);
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -26,6 +26,7 @@ struct AudioSettings : SettingsLayout {
|
|||
AudioSlider nes;
|
||||
AudioSlider snes;
|
||||
AudioSlider gb;
|
||||
AudioSlider gba;
|
||||
|
||||
void synchronize();
|
||||
AudioSettings();
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,6 +13,7 @@ struct WindowManager {
|
|||
|
||||
void loadGeometry();
|
||||
void saveGeometry();
|
||||
void hideAll();
|
||||
|
||||
private:
|
||||
configuration config;
|
||||
|
|
Loading…
Reference in New Issue