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:
Tim Allen 2011-01-02 15:46:54 +11:00
parent 42a9f9cfa4
commit ebd6a52811
18 changed files with 354 additions and 82 deletions

View File

@ -3,7 +3,8 @@
#define CARTRIDGE_CPP
namespace GameBoy {
#include "mmio/mmio.cpp"
#include "mbc0/mbc0.cpp"
#include "mbc1/mbc1.cpp"
Cartridge cartridge;
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.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:
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;
}
ramdata = new uint8_t[ramsize = info.ramsize]();
loaded = true;
}
@ -53,13 +67,48 @@ void Cartridge::unload() {
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() {
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();
}
void Cartridge::reset() {
mbc1.reset();
mbc1.reset();
}
Cartridge::Cartridge() {

View File

@ -1,11 +1,23 @@
struct Cartridge : MMIO, property<Cartridge> {
#include "mmio/mmio.hpp"
struct Cartridge : property<Cartridge> {
#include "mbc0/mbc0.hpp"
#include "mbc1/mbc1.hpp"
enum Mapper : unsigned {
MBC0,
MBC1,
Unknown,
};
struct Information {
string name;
uint8 cgbflag;
uint8 sgbflag;
uint8 type;
Mapper mapper;
bool ram;
bool battery;
bool rtc;
unsigned romsize;
unsigned ramsize;
} info;
@ -21,6 +33,11 @@ struct Cartridge : MMIO, property<Cartridge> {
void load(uint8_t *data, unsigned size);
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 reset();

17
gameboy/cartridge/mbc0/mbc0.cpp Executable file
View File

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

View File

@ -0,0 +1,6 @@
struct MBC0 : MMIO {
uint8 mmio_read(uint16 addr);
void mmio_write(uint16 addr, uint8 data);
void power();
void reset();
} mbc0;

62
gameboy/cartridge/mbc1/mbc1.cpp Executable file
View File

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

14
gameboy/cartridge/mbc1/mbc1.hpp Executable file
View File

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

View File

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

View File

@ -1,2 +0,0 @@
uint8 mmio_read(uint16 addr);
void mmio_write(uint16 addr, uint8 data);

View File

@ -566,11 +566,13 @@ void CPU::op_nop() {
}
void CPU::op_halt() {
//TODO
status.halt = true;
while(status.halt == true) op_io();
}
void CPU::op_stop() {
//TODO
status.stop = true;
while(status.stop == true) op_io();
}
void CPU::op_di() {

View File

@ -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() {
if(status.ime) {
if(status.interrupt_request_vblank && status.interrupt_enable_vblank) {
@ -80,6 +91,9 @@ void CPU::reset() {
r[DE] = 0x0000;
r[HL] = 0x0000;
status.halt = false;
status.stop = false;
status.ime = 0;
status.timer0 = 0;
status.timer1 = 0;

View File

@ -3,7 +3,18 @@ struct CPU : Processor, MMIO {
#include "mmio/mmio.hpp"
#include "timing/timing.hpp"
enum class Interrupt : unsigned {
Vblank,
Stat,
Timer,
Serial,
Joypad,
};
struct Status {
bool halt;
bool stop;
bool ime;
unsigned timer0;
unsigned timer1;
@ -48,6 +59,7 @@ struct CPU : Processor, MMIO {
static void Main();
void main();
void interrupt_raise(Interrupt id);
void interrupt_test();
void interrupt_exec(uint16 pc);
void power();

View File

@ -16,7 +16,7 @@ void CPU::mmio_joyp_poll() {
status.joyp = 0x0f;
if(status.p15 == 0) status.joyp &= button ^ 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) {

View File

@ -21,10 +21,10 @@ void CPU::add_clocks(unsigned clocks) {
}
void CPU::timer_stage0() { //262144hz
if(status.timer_clock == 1) {
if(status.timer_enable && status.timer_clock == 1) {
if(++status.tima == 0) {
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
if(status.timer_clock == 2) {
if(status.timer_enable && status.timer_clock == 2) {
if(++status.tima == 0) {
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
if(status.timer_clock == 3) {
if(status.timer_enable && status.timer_clock == 3) {
if(++status.tima == 0) {
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
if(status.timer_clock == 0) {
if(status.timer_enable && status.timer_clock == 0) {
if(++status.tima == 0) {
status.tima = status.tma;
status.interrupt_request_timer = 1;
interrupt_raise(Interrupt::Timer);
}
}

View File

@ -5,7 +5,7 @@
namespace GameBoy {
namespace Info {
static const char Name[] = "bgameboy";
static const char Version[] = "000.05";
static const char Version[] = "000.06";
}
}

View File

@ -13,6 +13,10 @@ void LCD::Main() {
void LCD::main() {
while(true) {
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.ly++;
if(status.ly == 144) cpu.status.interrupt_request_vblank = 1;
//print("Vblank - ", cpu.status.ime, " - ", cpu.status.interrupt_enable_vblank, "\n"); }
if(status.interrupt_lyc == true) {
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 < 144) render();
@ -45,54 +55,130 @@ void LCD::frame() {
}
void LCD::render() {
uint8_t *output = screen + status.ly * 160;
uint8 y = status.ly + status.scy;
uint16 tmaddr = (status.bg_tilemap_select == 0 ? 0x1800 : 0x1c00);
tmaddr += (y >> 3) * 32;
tmaddr += (status.scx >> 3);
for(unsigned n = 0; n < 160; n++) {
line[n].source = Line::Source::None;
line[n].output = 0;
}
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;
if(status.bg_tiledata_select == 0) {
tdaddr = 0x1000 + (int8)vram[tmaddr + t] * 16;
tdaddr = 0x1000 + (int8)vram[tmaddr + tile] * 16;
} 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];
uint8 d1 = vram[tdaddr + 1];
d0 = vram[tdaddr + 0];
d1 = vram[tdaddr + 1];
}
for(unsigned x = 0; x < 8; x++) {
uint8 palette = ((d0 & 0x80) >> 7) + ((d1 & 0x80) >> 6);
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++) {
unsigned sy = oam[(s << 2) + 0] - 9;
unsigned sy = oam[(s << 2) + 0] - 16;
unsigned sx = oam[(s << 2) + 1] - 8;
unsigned tile = oam[(s << 2) + 2];
unsigned attribute = oam[(s << 2) + 3];
sy -= status.ly;
if(sy >= 8) continue;
if(attribute & 0x40||1) sy ^= 7;
sy = status.ly - sy;
if(sy >= obj_size) continue;
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 d1 = vram[addr + 1];
unsigned xflip = attribute & 0x20 ? -7 : 0;
uint8 d0 = vram[tdaddr + 0];
uint8 d1 = vram[tdaddr + 1];
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);
d0 <<= 1, d1 <<= 1;
if(palette == 0) continue;
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.obj_size = 0;
status.obj_enable = 0;
status.bg_display = 0;
status.bg_enable = 0;
status.interrupt_lyc = 0;
status.interrupt_oam = 0;
status.interrupt_vblank = 0;
status.interrupt_hblank = 0;
status.coincidence = 0;
status.mode = 0;
status.scy = 0;
status.scx = 0;
status.ly = 0;
status.lyc = 0;
for(unsigned n = 0; n < 4; n++) {
@ -145,7 +226,6 @@ void LCD::reset() {
}
status.wy = 0;
status.wx = 0;
}

View File

@ -12,15 +12,13 @@ struct LCD : Processor, MMIO {
bool bg_tilemap_select;
bool obj_size;
bool obj_enable;
bool bg_display;
bool bg_enable;
//$ff41 STAT
bool interrupt_lyc;
bool interrupt_oam;
bool interrupt_vblank;
bool interrupt_hblank;
bool coincidence;
unsigned mode;
//$ff42 SCY
uint8 scy;
@ -52,12 +50,20 @@ struct LCD : Processor, MMIO {
uint8 vram[8192];
uint8 oam[160];
struct Line {
enum class Source : unsigned { None, BG, OBJ, Window } source;
uint8 output;
} line[160];
static void Main();
void main();
void add_clocks(unsigned clocks);
void scanline();
void frame();
void render();
void render_bg();
void render_window();
void render_obj();
void power();
void reset();

View File

@ -12,16 +12,21 @@ uint8 LCD::mmio_read(uint16 addr) {
| (status.bg_tilemap_select << 3)
| (status.obj_size << 2)
| (status.obj_enable << 1)
| (status.bg_display << 0);
| (status.bg_enable << 0);
}
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)
| (status.interrupt_oam << 5)
| (status.interrupt_vblank << 4)
| (status.interrupt_hblank << 3)
| (status.coincidence << 2)
| (status.mode << 0);
| ((status.ly == status.lyc) << 2)
| (mode << 0);
}
if(addr == 0xff42) { //SCY
@ -84,7 +89,7 @@ void LCD::mmio_write(uint16 addr, uint8 data) {
status.bg_tilemap_select = data & 0x08;
status.obj_size = data & 0x04;
status.obj_enable = data & 0x02;
status.bg_display = data & 0x01;
status.bg_enable = data & 0x01;
return;
}

View File

@ -33,7 +33,7 @@ void MainWindow::create() {
};
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);
};