mirror of https://github.com/bsnes-emu/bsnes.git
Update to release v000r06.
byuu says: Added MBC1 emulation, although battery RAM doesn't save or load to disk yet. Made up a fake MBC0 which is really just saying 'no MBC', for consistent handling of all MBCs. Added bumpers to stop ROM/RAM out of bounds accesses. Added STAT interrupts for LY coincidence, Vblank and Hblank (not for OAM access yet, I don't know the timings.) Fixed timer interrupt [Jonas Quinn] Made all interrupts call a CPU function instead of just setting a flag for better control (to allow below addition.) Added HALT and STOP emulation, the latter permanently locks the Game Boy for now. The former breaks on interrupts. Rewrote all the rendering code to suck 50% less, though it's still absolutely miserable and scanline-based. Added pixel-level horizontal scrolling to BGs. Fixed OBJ rendering error that was making them render upside down (I was flipping to compensate before.) Added OBJ 8x16 mode. Added OBJ priority support. Added window (but it's broken to all hell on Mega Man II.)
This commit is contained in:
parent
42a9f9cfa4
commit
ebd6a52811
|
@ -3,7 +3,8 @@
|
||||||
#define CARTRIDGE_CPP
|
#define CARTRIDGE_CPP
|
||||||
namespace GameBoy {
|
namespace GameBoy {
|
||||||
|
|
||||||
#include "mmio/mmio.cpp"
|
#include "mbc0/mbc0.cpp"
|
||||||
|
#include "mbc1/mbc1.cpp"
|
||||||
Cartridge cartridge;
|
Cartridge cartridge;
|
||||||
|
|
||||||
void Cartridge::load(uint8_t *data, unsigned size) {
|
void Cartridge::load(uint8_t *data, unsigned size) {
|
||||||
|
@ -18,7 +19,18 @@ void Cartridge::load(uint8_t *data, unsigned size) {
|
||||||
|
|
||||||
info.cgbflag = romdata[0x0143];
|
info.cgbflag = romdata[0x0143];
|
||||||
info.sgbflag = romdata[0x0146];
|
info.sgbflag = romdata[0x0146];
|
||||||
info.type = romdata[0x0147];
|
|
||||||
|
info.mapper = Mapper::Unknown;
|
||||||
|
info.ram = false;
|
||||||
|
info.battery = false;
|
||||||
|
info.rtc = false;
|
||||||
|
|
||||||
|
switch(romdata[0x0147]) { default:
|
||||||
|
case 0x00: info.mapper = Mapper::MBC0; break;
|
||||||
|
case 0x01: info.mapper = Mapper::MBC1; break;
|
||||||
|
case 0x02: info.mapper = Mapper::MBC1; info.ram = true; break;
|
||||||
|
case 0x03: info.mapper = Mapper::MBC1; info.ram = true; info.battery = true; break;
|
||||||
|
}
|
||||||
|
|
||||||
switch(romdata[0x0148]) { default:
|
switch(romdata[0x0148]) { default:
|
||||||
case 0x00: info.romsize = 2 * 16 * 1024; break;
|
case 0x00: info.romsize = 2 * 16 * 1024; break;
|
||||||
|
@ -42,6 +54,8 @@ void Cartridge::load(uint8_t *data, unsigned size) {
|
||||||
case 0x03: info.ramsize = 32 * 1024; break;
|
case 0x03: info.ramsize = 32 * 1024; break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ramdata = new uint8_t[ramsize = info.ramsize]();
|
||||||
|
|
||||||
loaded = true;
|
loaded = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -53,13 +67,48 @@ void Cartridge::unload() {
|
||||||
loaded = false;
|
loaded = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
uint8 Cartridge::rom_read(unsigned addr) {
|
||||||
|
if(addr >= romsize) addr %= romsize;
|
||||||
|
return romdata[addr];
|
||||||
|
}
|
||||||
|
|
||||||
|
void Cartridge::rom_write(unsigned addr, uint8 data) {
|
||||||
|
if(addr >= romsize) addr %= romsize;
|
||||||
|
romdata[addr] = data;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8 Cartridge::ram_read(unsigned addr) {
|
||||||
|
if(ramsize == 0) return 0x00;
|
||||||
|
if(addr >= ramsize) addr %= ramsize;
|
||||||
|
return ramdata[addr];
|
||||||
|
}
|
||||||
|
|
||||||
|
void Cartridge::ram_write(unsigned addr, uint8 data) {
|
||||||
|
if(ramsize == 0) return;
|
||||||
|
if(addr >= ramsize) addr %= ramsize;
|
||||||
|
ramdata[addr] = data;
|
||||||
|
}
|
||||||
|
|
||||||
void Cartridge::power() {
|
void Cartridge::power() {
|
||||||
for(unsigned n = 0x0000; n <= 0x7fff; n++) bus.mmio[n] = this;
|
mbc0.power();
|
||||||
|
mbc1.power();
|
||||||
|
|
||||||
|
MMIO *mapper = 0;
|
||||||
|
switch(info.mapper) {
|
||||||
|
case Mapper::MBC0: mapper = &mbc0; break;
|
||||||
|
case Mapper::MBC1: mapper = &mbc1; break;
|
||||||
|
}
|
||||||
|
if(mapper) {
|
||||||
|
for(unsigned n = 0x0000; n <= 0x7fff; n++) bus.mmio[n] = mapper;
|
||||||
|
for(unsigned n = 0xa000; n <= 0xbfff; n++) bus.mmio[n] = mapper;
|
||||||
|
}
|
||||||
|
|
||||||
reset();
|
reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Cartridge::reset() {
|
void Cartridge::reset() {
|
||||||
|
mbc1.reset();
|
||||||
|
mbc1.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
Cartridge::Cartridge() {
|
Cartridge::Cartridge() {
|
||||||
|
|
|
@ -1,11 +1,23 @@
|
||||||
struct Cartridge : MMIO, property<Cartridge> {
|
struct Cartridge : property<Cartridge> {
|
||||||
#include "mmio/mmio.hpp"
|
#include "mbc0/mbc0.hpp"
|
||||||
|
#include "mbc1/mbc1.hpp"
|
||||||
|
|
||||||
|
enum Mapper : unsigned {
|
||||||
|
MBC0,
|
||||||
|
MBC1,
|
||||||
|
Unknown,
|
||||||
|
};
|
||||||
|
|
||||||
struct Information {
|
struct Information {
|
||||||
string name;
|
string name;
|
||||||
uint8 cgbflag;
|
uint8 cgbflag;
|
||||||
uint8 sgbflag;
|
uint8 sgbflag;
|
||||||
uint8 type;
|
|
||||||
|
Mapper mapper;
|
||||||
|
bool ram;
|
||||||
|
bool battery;
|
||||||
|
bool rtc;
|
||||||
|
|
||||||
unsigned romsize;
|
unsigned romsize;
|
||||||
unsigned ramsize;
|
unsigned ramsize;
|
||||||
} info;
|
} info;
|
||||||
|
@ -21,6 +33,11 @@ struct Cartridge : MMIO, property<Cartridge> {
|
||||||
void load(uint8_t *data, unsigned size);
|
void load(uint8_t *data, unsigned size);
|
||||||
void unload();
|
void unload();
|
||||||
|
|
||||||
|
uint8 rom_read(unsigned addr);
|
||||||
|
void rom_write(unsigned addr, uint8 data);
|
||||||
|
uint8 ram_read(unsigned addr);
|
||||||
|
void ram_write(unsigned addr, uint8 data);
|
||||||
|
|
||||||
void power();
|
void power();
|
||||||
void reset();
|
void reset();
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,17 @@
|
||||||
|
#ifdef CARTRIDGE_CPP
|
||||||
|
|
||||||
|
uint8 Cartridge::MBC0::mmio_read(uint16 addr) {
|
||||||
|
if(addr >= 0x0000 && addr <= 0x7fff) return cartridge.rom_read(addr);
|
||||||
|
return 0x00;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Cartridge::MBC0::mmio_write(uint16 addr, uint8 data) {
|
||||||
|
}
|
||||||
|
|
||||||
|
void Cartridge::MBC0::power() {
|
||||||
|
}
|
||||||
|
|
||||||
|
void Cartridge::MBC0::reset() {
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
|
@ -0,0 +1,6 @@
|
||||||
|
struct MBC0 : MMIO {
|
||||||
|
uint8 mmio_read(uint16 addr);
|
||||||
|
void mmio_write(uint16 addr, uint8 data);
|
||||||
|
void power();
|
||||||
|
void reset();
|
||||||
|
} mbc0;
|
|
@ -0,0 +1,62 @@
|
||||||
|
#ifdef CARTRIDGE_CPP
|
||||||
|
|
||||||
|
uint8 Cartridge::MBC1::mmio_read(uint16 addr) {
|
||||||
|
if(addr >= 0x0000 && addr <= 0x3fff) {
|
||||||
|
return cartridge.rom_read(addr);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(addr >= 0x4000 && addr <= 0x7fff) {
|
||||||
|
return cartridge.rom_read(rom_bank | (addr & 0x3fff));
|
||||||
|
}
|
||||||
|
|
||||||
|
if(addr >= 0xa000 && addr <= 0xbfff) {
|
||||||
|
if(ram_enable) return cartridge.ram_read(ram_bank | (addr & 0x1fff));
|
||||||
|
return 0x00;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Cartridge::MBC1::mmio_write(uint16 addr, uint8 data) {
|
||||||
|
if(addr >= 0x0000 && addr <= 0x1fff) {
|
||||||
|
ram_enable = (data & 0x0f) == 0x0a;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(addr >= 0x2000 && addr <= 0x3fff) {
|
||||||
|
rom_select = data & 0x1f;
|
||||||
|
if(rom_select == 0) rom_select = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(addr >= 0x4000 && addr <= 0x5fff) {
|
||||||
|
ram_select = data & 0x03;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(addr >= 0x6000 && addr <= 0x7fff) {
|
||||||
|
mode_select = data & 0x01;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(mode_select == 0) {
|
||||||
|
rom_bank = (ram_select << 19) | (rom_select << 14);
|
||||||
|
ram_bank = 0x00;
|
||||||
|
} else {
|
||||||
|
rom_bank = (rom_select << 14);
|
||||||
|
ram_bank = (ram_select << 13);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(addr >= 0xa000 && addr <= 0xbfff) {
|
||||||
|
if(ram_enable) cartridge.ram_write(ram_bank | (addr & 0x1fff), data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Cartridge::MBC1::power() {
|
||||||
|
}
|
||||||
|
|
||||||
|
void Cartridge::MBC1::reset() {
|
||||||
|
ram_enable = false;
|
||||||
|
rom_select = 0x01;
|
||||||
|
ram_select = 0x00;
|
||||||
|
mode_select = 0;
|
||||||
|
|
||||||
|
rom_bank = 0x4000;
|
||||||
|
ram_bank = 0x0000;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
|
@ -0,0 +1,14 @@
|
||||||
|
struct MBC1 : MMIO {
|
||||||
|
bool ram_enable; //0000-1fff
|
||||||
|
uint8 rom_select; //2000-3fff
|
||||||
|
uint8 ram_select; //4000-5fff
|
||||||
|
bool mode_select; //6000-7fff
|
||||||
|
|
||||||
|
unsigned rom_bank;
|
||||||
|
unsigned ram_bank;
|
||||||
|
|
||||||
|
uint8 mmio_read(uint16 addr);
|
||||||
|
void mmio_write(uint16 addr, uint8 data);
|
||||||
|
void power();
|
||||||
|
void reset();
|
||||||
|
} mbc1;
|
|
@ -1,10 +0,0 @@
|
||||||
#ifdef CARTRIDGE_CPP
|
|
||||||
|
|
||||||
uint8 Cartridge::mmio_read(uint16 addr) {
|
|
||||||
if(addr >= 0x0000 && addr <= 0x7fff) return romdata[addr];
|
|
||||||
}
|
|
||||||
|
|
||||||
void Cartridge::mmio_write(uint16 addr, uint8 data) {
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
|
|
@ -1,2 +0,0 @@
|
||||||
uint8 mmio_read(uint16 addr);
|
|
||||||
void mmio_write(uint16 addr, uint8 data);
|
|
|
@ -566,11 +566,13 @@ void CPU::op_nop() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void CPU::op_halt() {
|
void CPU::op_halt() {
|
||||||
//TODO
|
status.halt = true;
|
||||||
|
while(status.halt == true) op_io();
|
||||||
}
|
}
|
||||||
|
|
||||||
void CPU::op_stop() {
|
void CPU::op_stop() {
|
||||||
//TODO
|
status.stop = true;
|
||||||
|
while(status.stop == true) op_io();
|
||||||
}
|
}
|
||||||
|
|
||||||
void CPU::op_di() {
|
void CPU::op_di() {
|
||||||
|
|
|
@ -21,6 +21,17 @@ void CPU::main() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void CPU::interrupt_raise(CPU::Interrupt id) {
|
||||||
|
switch(id) {
|
||||||
|
case Interrupt::Vblank: status.interrupt_request_vblank = 1; break;
|
||||||
|
case Interrupt::Stat : status.interrupt_request_stat = 1; break;
|
||||||
|
case Interrupt::Timer : status.interrupt_request_timer = 1; break;
|
||||||
|
case Interrupt::Serial: status.interrupt_request_serial = 1; break;
|
||||||
|
case Interrupt::Joypad: status.interrupt_request_joypad = 1; break;
|
||||||
|
}
|
||||||
|
status.halt = false;
|
||||||
|
}
|
||||||
|
|
||||||
void CPU::interrupt_test() {
|
void CPU::interrupt_test() {
|
||||||
if(status.ime) {
|
if(status.ime) {
|
||||||
if(status.interrupt_request_vblank && status.interrupt_enable_vblank) {
|
if(status.interrupt_request_vblank && status.interrupt_enable_vblank) {
|
||||||
|
@ -80,6 +91,9 @@ void CPU::reset() {
|
||||||
r[DE] = 0x0000;
|
r[DE] = 0x0000;
|
||||||
r[HL] = 0x0000;
|
r[HL] = 0x0000;
|
||||||
|
|
||||||
|
status.halt = false;
|
||||||
|
status.stop = false;
|
||||||
|
|
||||||
status.ime = 0;
|
status.ime = 0;
|
||||||
status.timer0 = 0;
|
status.timer0 = 0;
|
||||||
status.timer1 = 0;
|
status.timer1 = 0;
|
||||||
|
|
|
@ -3,7 +3,18 @@ struct CPU : Processor, MMIO {
|
||||||
#include "mmio/mmio.hpp"
|
#include "mmio/mmio.hpp"
|
||||||
#include "timing/timing.hpp"
|
#include "timing/timing.hpp"
|
||||||
|
|
||||||
|
enum class Interrupt : unsigned {
|
||||||
|
Vblank,
|
||||||
|
Stat,
|
||||||
|
Timer,
|
||||||
|
Serial,
|
||||||
|
Joypad,
|
||||||
|
};
|
||||||
|
|
||||||
struct Status {
|
struct Status {
|
||||||
|
bool halt;
|
||||||
|
bool stop;
|
||||||
|
|
||||||
bool ime;
|
bool ime;
|
||||||
unsigned timer0;
|
unsigned timer0;
|
||||||
unsigned timer1;
|
unsigned timer1;
|
||||||
|
@ -48,6 +59,7 @@ struct CPU : Processor, MMIO {
|
||||||
|
|
||||||
static void Main();
|
static void Main();
|
||||||
void main();
|
void main();
|
||||||
|
void interrupt_raise(Interrupt id);
|
||||||
void interrupt_test();
|
void interrupt_test();
|
||||||
void interrupt_exec(uint16 pc);
|
void interrupt_exec(uint16 pc);
|
||||||
void power();
|
void power();
|
||||||
|
|
|
@ -16,7 +16,7 @@ void CPU::mmio_joyp_poll() {
|
||||||
status.joyp = 0x0f;
|
status.joyp = 0x0f;
|
||||||
if(status.p15 == 0) status.joyp &= button ^ 0x0f;
|
if(status.p15 == 0) status.joyp &= button ^ 0x0f;
|
||||||
if(status.p14 == 0) status.joyp &= dpad ^ 0x0f;
|
if(status.p14 == 0) status.joyp &= dpad ^ 0x0f;
|
||||||
if(status.joyp != 0x0f) status.interrupt_request_joypad = 1;
|
if(status.joyp != 0x0f) interrupt_raise(Interrupt::Joypad);
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8 CPU::mmio_read(uint16 addr) {
|
uint8 CPU::mmio_read(uint16 addr) {
|
||||||
|
|
|
@ -21,10 +21,10 @@ void CPU::add_clocks(unsigned clocks) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void CPU::timer_stage0() { //262144hz
|
void CPU::timer_stage0() { //262144hz
|
||||||
if(status.timer_clock == 1) {
|
if(status.timer_enable && status.timer_clock == 1) {
|
||||||
if(++status.tima == 0) {
|
if(++status.tima == 0) {
|
||||||
status.tima = status.tma;
|
status.tima = status.tma;
|
||||||
status.interrupt_request_timer = 1;
|
interrupt_raise(Interrupt::Timer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -33,10 +33,10 @@ void CPU::timer_stage0() { //262144hz
|
||||||
}
|
}
|
||||||
|
|
||||||
void CPU::timer_stage1() { // 65536hz
|
void CPU::timer_stage1() { // 65536hz
|
||||||
if(status.timer_clock == 2) {
|
if(status.timer_enable && status.timer_clock == 2) {
|
||||||
if(++status.tima == 0) {
|
if(++status.tima == 0) {
|
||||||
status.tima = status.tma;
|
status.tima = status.tma;
|
||||||
status.interrupt_request_timer = 1;
|
interrupt_raise(Interrupt::Timer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -45,10 +45,10 @@ void CPU::timer_stage1() { // 65536hz
|
||||||
}
|
}
|
||||||
|
|
||||||
void CPU::timer_stage2() { // 16384hz
|
void CPU::timer_stage2() { // 16384hz
|
||||||
if(status.timer_clock == 3) {
|
if(status.timer_enable && status.timer_clock == 3) {
|
||||||
if(++status.tima == 0) {
|
if(++status.tima == 0) {
|
||||||
status.tima = status.tma;
|
status.tima = status.tma;
|
||||||
status.interrupt_request_timer = 1;
|
interrupt_raise(Interrupt::Timer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -59,10 +59,10 @@ void CPU::timer_stage2() { // 16384hz
|
||||||
}
|
}
|
||||||
|
|
||||||
void CPU::timer_stage3() { // 4096hz
|
void CPU::timer_stage3() { // 4096hz
|
||||||
if(status.timer_clock == 0) {
|
if(status.timer_enable && status.timer_clock == 0) {
|
||||||
if(++status.tima == 0) {
|
if(++status.tima == 0) {
|
||||||
status.tima = status.tma;
|
status.tima = status.tma;
|
||||||
status.interrupt_request_timer = 1;
|
interrupt_raise(Interrupt::Timer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
namespace GameBoy {
|
namespace GameBoy {
|
||||||
namespace Info {
|
namespace Info {
|
||||||
static const char Name[] = "bgameboy";
|
static const char Name[] = "bgameboy";
|
||||||
static const char Version[] = "000.05";
|
static const char Version[] = "000.06";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -13,6 +13,10 @@ void LCD::Main() {
|
||||||
void LCD::main() {
|
void LCD::main() {
|
||||||
while(true) {
|
while(true) {
|
||||||
add_clocks(4);
|
add_clocks(4);
|
||||||
|
|
||||||
|
if(status.lx == 320) {
|
||||||
|
if(status.interrupt_hblank) cpu.interrupt_raise(CPU::Interrupt::Stat);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -28,8 +32,14 @@ void LCD::scanline() {
|
||||||
status.lx -= 456;
|
status.lx -= 456;
|
||||||
status.ly++;
|
status.ly++;
|
||||||
|
|
||||||
if(status.ly == 144) cpu.status.interrupt_request_vblank = 1;
|
if(status.interrupt_lyc == true) {
|
||||||
//print("Vblank - ", cpu.status.ime, " - ", cpu.status.interrupt_enable_vblank, "\n"); }
|
if(status.ly == status.lyc) cpu.interrupt_raise(CPU::Interrupt::Stat);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(status.ly == 144) {
|
||||||
|
cpu.interrupt_raise(CPU::Interrupt::Vblank);
|
||||||
|
if(status.interrupt_vblank) cpu.interrupt_raise(CPU::Interrupt::Stat);
|
||||||
|
}
|
||||||
if(status.ly == 154) frame();
|
if(status.ly == 154) frame();
|
||||||
|
|
||||||
if(status.ly < 144) render();
|
if(status.ly < 144) render();
|
||||||
|
@ -45,54 +55,130 @@ void LCD::frame() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void LCD::render() {
|
void LCD::render() {
|
||||||
uint8_t *output = screen + status.ly * 160;
|
for(unsigned n = 0; n < 160; n++) {
|
||||||
uint8 y = status.ly + status.scy;
|
line[n].source = Line::Source::None;
|
||||||
uint16 tmaddr = (status.bg_tilemap_select == 0 ? 0x1800 : 0x1c00);
|
line[n].output = 0;
|
||||||
tmaddr += (y >> 3) * 32;
|
}
|
||||||
tmaddr += (status.scx >> 3);
|
|
||||||
|
|
||||||
for(unsigned t = 0; t < 20; t++) {
|
if(status.display_enable == true) {
|
||||||
|
if(status.bg_enable == true) render_bg();
|
||||||
|
if(status.obj_enable == true) render_obj();
|
||||||
|
if(status.window_display_enable == true) render_window();
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t *output = screen + status.ly * 160;
|
||||||
|
for(unsigned n = 0; n < 160; n++) {
|
||||||
|
output[n] = (3 - line[n].output) * 0x55;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void LCD::render_bg() {
|
||||||
|
unsigned iy = (status.ly + status.scy) & 255;
|
||||||
|
unsigned ix = status.scx;
|
||||||
|
|
||||||
|
unsigned tmaddr = (status.bg_tilemap_select == 0 ? 0x1800 : 0x1c00);
|
||||||
|
|
||||||
|
unsigned tx = (ix - 7) & 7;
|
||||||
|
uint8 d0 = 0, d1 = 0;
|
||||||
|
for(signed ox = -7; ox < 160; ox++) {
|
||||||
|
if(tx == 0) {
|
||||||
|
unsigned tile = (((iy >> 3) * 32) + (ix >> 3)) & 0x3fff;
|
||||||
unsigned tdaddr;
|
unsigned tdaddr;
|
||||||
if(status.bg_tiledata_select == 0) {
|
if(status.bg_tiledata_select == 0) {
|
||||||
tdaddr = 0x1000 + (int8)vram[tmaddr + t] * 16;
|
tdaddr = 0x1000 + (int8)vram[tmaddr + tile] * 16;
|
||||||
} else {
|
} else {
|
||||||
tdaddr = 0x0000 + vram[tmaddr + t] * 16;
|
tdaddr = 0x0000 + vram[tmaddr + tile] * 16;
|
||||||
}
|
}
|
||||||
tdaddr += (status.ly & 7) * 2;
|
tdaddr += (iy & 7) * 2;
|
||||||
|
|
||||||
uint8 d0 = vram[tdaddr + 0];
|
d0 = vram[tdaddr + 0];
|
||||||
uint8 d1 = vram[tdaddr + 1];
|
d1 = vram[tdaddr + 1];
|
||||||
|
}
|
||||||
|
|
||||||
for(unsigned x = 0; x < 8; x++) {
|
|
||||||
uint8 palette = ((d0 & 0x80) >> 7) + ((d1 & 0x80) >> 6);
|
uint8 palette = ((d0 & 0x80) >> 7) + ((d1 & 0x80) >> 6);
|
||||||
d0 <<= 1, d1 <<= 1;
|
d0 <<= 1, d1 <<= 1;
|
||||||
*output++ = (3 - status.bgp[palette]) * 0x55;
|
|
||||||
}
|
if(ox >= 0) {
|
||||||
|
line[ox].source = Line::Source::BG;
|
||||||
|
line[ox].output = status.bgp[palette];
|
||||||
}
|
}
|
||||||
|
|
||||||
output = screen + status.ly * 160;
|
ix = (ix + 1) & 255;
|
||||||
|
tx = (tx + 1) & 7;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void LCD::render_window() {
|
||||||
|
if(status.wy > status.ly) return;
|
||||||
|
unsigned iy = (status.ly + status.wy) & 255;
|
||||||
|
unsigned ix = (status.wx - 7) & 255;
|
||||||
|
|
||||||
|
unsigned tmaddr = (status.window_tilemap_select == 0 ? 0x1800 : 0x1c00);
|
||||||
|
|
||||||
|
unsigned tx = (ix - 7) & 7;
|
||||||
|
uint8 d0 = 0, d1 = 0;
|
||||||
|
for(signed ox = -7; ox < 160; ox++) {
|
||||||
|
if(tx == 0) {
|
||||||
|
unsigned tile = (((iy >> 3) * 32) + (ix >> 3)) & 0x3fff;
|
||||||
|
unsigned tdaddr;
|
||||||
|
if(status.bg_tiledata_select == 0) {
|
||||||
|
tdaddr = 0x1000 + (int8)vram[tmaddr + tile] * 16;
|
||||||
|
} else {
|
||||||
|
tdaddr = 0x0000 + vram[tmaddr + tile] * 16;
|
||||||
|
}
|
||||||
|
tdaddr += (iy & 7) * 2;
|
||||||
|
|
||||||
|
d0 = vram[tdaddr + 0];
|
||||||
|
d1 = vram[tdaddr + 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8 palette = ((d0 & 0x80) >> 7) + ((d1 & 0x80) >> 6);
|
||||||
|
d0 <<= 1, d1 <<= 1;
|
||||||
|
|
||||||
|
if(ox >= 7) {
|
||||||
|
line[ox].source = Line::Source::Window;
|
||||||
|
line[ox].output = status.bgp[palette];
|
||||||
|
}
|
||||||
|
|
||||||
|
ix = (ix + 1) & 255;
|
||||||
|
tx = (tx + 1) & 7;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void LCD::render_obj() {
|
||||||
|
unsigned obj_size = (status.obj_size == 0 ? 8 : 16);
|
||||||
|
|
||||||
for(unsigned s = 0; s < 40; s++) {
|
for(unsigned s = 0; s < 40; s++) {
|
||||||
unsigned sy = oam[(s << 2) + 0] - 9;
|
unsigned sy = oam[(s << 2) + 0] - 16;
|
||||||
unsigned sx = oam[(s << 2) + 1] - 8;
|
unsigned sx = oam[(s << 2) + 1] - 8;
|
||||||
unsigned tile = oam[(s << 2) + 2];
|
unsigned tile = oam[(s << 2) + 2];
|
||||||
unsigned attribute = oam[(s << 2) + 3];
|
unsigned attribute = oam[(s << 2) + 3];
|
||||||
|
|
||||||
sy -= status.ly;
|
sy = status.ly - sy;
|
||||||
if(sy >= 8) continue;
|
if(sy >= obj_size) continue;
|
||||||
if(attribute & 0x40||1) sy ^= 7;
|
if(attribute & 0x40) sy ^= (obj_size - 1);
|
||||||
|
|
||||||
unsigned addr = tile * 16 + sy * 2;
|
unsigned tdaddr = tile * 16 + sy * 2;
|
||||||
|
|
||||||
uint8 d0 = vram[addr + 0];
|
uint8 d0 = vram[tdaddr + 0];
|
||||||
uint8 d1 = vram[addr + 1];
|
uint8 d1 = vram[tdaddr + 1];
|
||||||
unsigned xflip = attribute & 0x20 ? -7 : 0;
|
unsigned xflip = attribute & 0x20 ? 7 : 0;
|
||||||
|
|
||||||
for(unsigned x = 0; x < 8; x++) {
|
for(unsigned tx = 0; tx < 8; tx++) {
|
||||||
uint8 palette = ((d0 & 0x80) >> 7) + ((d1 & 0x80) >> 6);
|
uint8 palette = ((d0 & 0x80) >> 7) + ((d1 & 0x80) >> 6);
|
||||||
d0 <<= 1, d1 <<= 1;
|
d0 <<= 1, d1 <<= 1;
|
||||||
if(palette == 0) continue;
|
if(palette == 0) continue;
|
||||||
|
|
||||||
palette = status.obp[(bool)(attribute & 0x10)][palette];
|
palette = status.obp[(bool)(attribute & 0x10)][palette];
|
||||||
output[sx + (x ^ xflip)] = (3 - palette) * 0x55;
|
unsigned ox = sx + (tx ^ xflip);
|
||||||
|
|
||||||
|
if(ox <= 159) {
|
||||||
|
if((attribute & 0x80) == 1) {
|
||||||
|
if(line[ox].source == Line::Source::BG && line[ox].output > 0) continue;
|
||||||
|
}
|
||||||
|
line[ox].source = Line::Source::OBJ;
|
||||||
|
line[ox].output = palette;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -121,21 +207,16 @@ void LCD::reset() {
|
||||||
status.bg_tilemap_select = 0;
|
status.bg_tilemap_select = 0;
|
||||||
status.obj_size = 0;
|
status.obj_size = 0;
|
||||||
status.obj_enable = 0;
|
status.obj_enable = 0;
|
||||||
status.bg_display = 0;
|
status.bg_enable = 0;
|
||||||
|
|
||||||
status.interrupt_lyc = 0;
|
status.interrupt_lyc = 0;
|
||||||
status.interrupt_oam = 0;
|
status.interrupt_oam = 0;
|
||||||
status.interrupt_vblank = 0;
|
status.interrupt_vblank = 0;
|
||||||
status.interrupt_hblank = 0;
|
status.interrupt_hblank = 0;
|
||||||
status.coincidence = 0;
|
|
||||||
status.mode = 0;
|
|
||||||
|
|
||||||
status.scy = 0;
|
status.scy = 0;
|
||||||
|
|
||||||
status.scx = 0;
|
status.scx = 0;
|
||||||
|
|
||||||
status.ly = 0;
|
status.ly = 0;
|
||||||
|
|
||||||
status.lyc = 0;
|
status.lyc = 0;
|
||||||
|
|
||||||
for(unsigned n = 0; n < 4; n++) {
|
for(unsigned n = 0; n < 4; n++) {
|
||||||
|
@ -145,7 +226,6 @@ void LCD::reset() {
|
||||||
}
|
}
|
||||||
|
|
||||||
status.wy = 0;
|
status.wy = 0;
|
||||||
|
|
||||||
status.wx = 0;
|
status.wx = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -12,15 +12,13 @@ struct LCD : Processor, MMIO {
|
||||||
bool bg_tilemap_select;
|
bool bg_tilemap_select;
|
||||||
bool obj_size;
|
bool obj_size;
|
||||||
bool obj_enable;
|
bool obj_enable;
|
||||||
bool bg_display;
|
bool bg_enable;
|
||||||
|
|
||||||
//$ff41 STAT
|
//$ff41 STAT
|
||||||
bool interrupt_lyc;
|
bool interrupt_lyc;
|
||||||
bool interrupt_oam;
|
bool interrupt_oam;
|
||||||
bool interrupt_vblank;
|
bool interrupt_vblank;
|
||||||
bool interrupt_hblank;
|
bool interrupt_hblank;
|
||||||
bool coincidence;
|
|
||||||
unsigned mode;
|
|
||||||
|
|
||||||
//$ff42 SCY
|
//$ff42 SCY
|
||||||
uint8 scy;
|
uint8 scy;
|
||||||
|
@ -52,12 +50,20 @@ struct LCD : Processor, MMIO {
|
||||||
uint8 vram[8192];
|
uint8 vram[8192];
|
||||||
uint8 oam[160];
|
uint8 oam[160];
|
||||||
|
|
||||||
|
struct Line {
|
||||||
|
enum class Source : unsigned { None, BG, OBJ, Window } source;
|
||||||
|
uint8 output;
|
||||||
|
} line[160];
|
||||||
|
|
||||||
static void Main();
|
static void Main();
|
||||||
void main();
|
void main();
|
||||||
void add_clocks(unsigned clocks);
|
void add_clocks(unsigned clocks);
|
||||||
void scanline();
|
void scanline();
|
||||||
void frame();
|
void frame();
|
||||||
void render();
|
void render();
|
||||||
|
void render_bg();
|
||||||
|
void render_window();
|
||||||
|
void render_obj();
|
||||||
|
|
||||||
void power();
|
void power();
|
||||||
void reset();
|
void reset();
|
||||||
|
|
|
@ -12,16 +12,21 @@ uint8 LCD::mmio_read(uint16 addr) {
|
||||||
| (status.bg_tilemap_select << 3)
|
| (status.bg_tilemap_select << 3)
|
||||||
| (status.obj_size << 2)
|
| (status.obj_size << 2)
|
||||||
| (status.obj_enable << 1)
|
| (status.obj_enable << 1)
|
||||||
| (status.bg_display << 0);
|
| (status.bg_enable << 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(addr == 0xff41) { //STAT
|
if(addr == 0xff41) { //STAT
|
||||||
|
unsigned mode;
|
||||||
|
if(status.ly >= 144) mode = 1; //Vblank
|
||||||
|
else if(status.lx >= 320) mode = 0; //Hblank
|
||||||
|
else mode = 3; //LCD transfer
|
||||||
|
|
||||||
return (status.interrupt_lyc << 6)
|
return (status.interrupt_lyc << 6)
|
||||||
| (status.interrupt_oam << 5)
|
| (status.interrupt_oam << 5)
|
||||||
| (status.interrupt_vblank << 4)
|
| (status.interrupt_vblank << 4)
|
||||||
| (status.interrupt_hblank << 3)
|
| (status.interrupt_hblank << 3)
|
||||||
| (status.coincidence << 2)
|
| ((status.ly == status.lyc) << 2)
|
||||||
| (status.mode << 0);
|
| (mode << 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(addr == 0xff42) { //SCY
|
if(addr == 0xff42) { //SCY
|
||||||
|
@ -84,7 +89,7 @@ void LCD::mmio_write(uint16 addr, uint8 data) {
|
||||||
status.bg_tilemap_select = data & 0x08;
|
status.bg_tilemap_select = data & 0x08;
|
||||||
status.obj_size = data & 0x04;
|
status.obj_size = data & 0x04;
|
||||||
status.obj_enable = data & 0x02;
|
status.obj_enable = data & 0x02;
|
||||||
status.bg_display = data & 0x01;
|
status.bg_enable = data & 0x01;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -33,7 +33,7 @@ void MainWindow::create() {
|
||||||
};
|
};
|
||||||
|
|
||||||
systemLoadCartridge.onTick = []() {
|
systemLoadCartridge.onTick = []() {
|
||||||
string filename = OS::fileOpen(mainWindow, "Game Boy cartridges\t*.gb", "/media/sdb1/root/gameboy_images/");
|
string filename = OS::fileOpen(mainWindow, "Game Boy cartridges\t*.gb,*.gbc", "/media/sdb1/root/gameboy_images/");
|
||||||
if(filename != "") utility.loadCartridge(filename);
|
if(filename != "") utility.loadCartridge(filename);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue