Update to release v000r08.

byuu says:

Changelog:
- rewrote sprite rendering, grabs first ten sprites, draws them in
  revere order of a: X-index, b: OAM appearance order
- simplified tile decoding to use less variables
- added MBC3 emulation (for now, RTC is always enabled)
- STOP can be broken via joypad IF, this may not be correct though (it
  may trigger even without P10-13 being modified)
- cleaned up all MBC emulation to use masks instead of ranges
- MBC2 uses 512-byte table now, ignores high 4-bits. Easier this way I guess
- tools menu temporarily has a console tracer enable option
- some other stuff

No real visible improvements :(
This commit is contained in:
Tim Allen 2011-01-04 21:42:27 +11:00
parent afdb3c4d20
commit a7ffbd784b
16 changed files with 292 additions and 90 deletions

View File

@ -6,6 +6,7 @@ namespace GameBoy {
#include "mbc0/mbc0.cpp"
#include "mbc1/mbc1.cpp"
#include "mbc2/mbc2.cpp"
#include "mbc3/mbc3.cpp"
#include "mbc5/mbc5.cpp"
Cartridge cartridge;
@ -34,6 +35,13 @@ void Cartridge::load(uint8_t *data, unsigned size) {
case 0x03: info.mapper = Mapper::MBC1; info.ram = true; info.battery = true; break;
case 0x05: info.mapper = Mapper::MBC2; info.ram = true; break;
case 0x06: info.mapper = Mapper::MBC2; info.ram = true; info.battery = true; break;
case 0x08: info.mapper = Mapper::MBC0; info.ram = true; break;
case 0x09: info.mapper = Mapper::MBC0; info.ram = true; info.battery = true; break;
case 0x0f: info.mapper = Mapper::MBC3; info.rtc = true; info.battery = true; break;
case 0x10: info.mapper = Mapper::MBC3; info.rtc = true; info.ram = true; info.battery = true; break;
case 0x11: info.mapper = Mapper::MBC3; break;
case 0x12: info.mapper = Mapper::MBC3; info.ram = true; break;
case 0x13: info.mapper = Mapper::MBC3; info.ram = true; info.battery = true; break;
case 0x19: info.mapper = Mapper::MBC5; break;
case 0x1a: info.mapper = Mapper::MBC5; info.ram = true; break;
case 0x1b: info.mapper = Mapper::MBC5; info.ram = true; info.battery = true; break;
@ -64,7 +72,7 @@ void Cartridge::load(uint8_t *data, unsigned size) {
case 0x03: info.ramsize = 32 * 1024; break;
}
if(info.mapper == Mapper::MBC2) info.ramsize = 256; //512 x 4-bit
if(info.mapper == Mapper::MBC2) info.ramsize = 512; //512 x 4-bit
ramdata = new uint8_t[ramsize = info.ramsize]();
@ -105,6 +113,7 @@ void Cartridge::power() {
mbc0.power();
mbc1.power();
mbc2.power();
mbc3.power();
mbc5.power();
MMIO *mapper = 0;
@ -112,6 +121,7 @@ void Cartridge::power() {
case Mapper::MBC0: mapper = &mbc0; break;
case Mapper::MBC1: mapper = &mbc1; break;
case Mapper::MBC2: mapper = &mbc2; break;
case Mapper::MBC3: mapper = &mbc3; break;
case Mapper::MBC5: mapper = &mbc5; break;
}

View File

@ -2,12 +2,14 @@ struct Cartridge : property<Cartridge> {
#include "mbc0/mbc0.hpp"
#include "mbc1/mbc1.hpp"
#include "mbc2/mbc2.hpp"
#include "mbc3/mbc3.hpp"
#include "mbc5/mbc5.hpp"
enum Mapper : unsigned {
MBC0,
MBC1,
MBC2,
MBC3,
MBC5,
Unknown,
};

View File

@ -1,11 +1,22 @@
#ifdef CARTRIDGE_CPP
uint8 Cartridge::MBC0::mmio_read(uint16 addr) {
if(addr >= 0x0000 && addr <= 0x7fff) return cartridge.rom_read(addr);
if((addr & 0x8000) == 0x0000) { //0000-7fff
return cartridge.rom_read(addr);
}
if((addr & 0xe000) == 0xa000) { //a000-bfff
return cartridge.ram_read(addr & 0x1fff);
}
return 0x00;
}
void Cartridge::MBC0::mmio_write(uint16 addr, uint8 data) {
if((addr & 0xe000) == 0xa000) { //a000-bfff
cartridge.ram_write(addr & 0x1fff, data);
return;
}
}
void Cartridge::MBC0::power() {

View File

@ -1,48 +1,62 @@
#ifdef CARTRIDGE_CPP
uint8 Cartridge::MBC1::mmio_read(uint16 addr) {
if(addr >= 0x0000 && addr <= 0x3fff) {
if((addr & 0xc000) == 0x0000) { //0000-3fff
return cartridge.rom_read(addr);
}
if(addr >= 0x4000 && addr <= 0x7fff) {
return cartridge.rom_read(rom_bank | (addr & 0x3fff));
if((addr & 0xc000) == 0x4000) { //4000-7fff
if(mode_select == 0) {
return cartridge.rom_read((ram_select << 19) | (rom_select << 14) | (addr & 0x3fff));
} else {
return cartridge.rom_read((rom_select << 14) | (addr & 0x3fff));
}
}
if(addr >= 0xa000 && addr <= 0xbfff) {
if(ram_enable) return cartridge.ram_read(ram_bank | (addr & 0x1fff));
if((addr & 0xe000) == 0xa000) { //a000-bfff
if(ram_enable) {
if(mode_select == 0) {
return cartridge.ram_read(addr & 0x1fff);
} else {
return cartridge.ram_read((ram_select << 13) | (addr & 0x1fff));
}
}
return 0x00;
}
return 0x00;
}
void Cartridge::MBC1::mmio_write(uint16 addr, uint8 data) {
if(addr >= 0x0000 && addr <= 0x1fff) {
if((addr & 0xe000) == 0x0000) { //0000-1fff
ram_enable = (data & 0x0f) == 0x0a;
return;
}
if(addr >= 0x2000 && addr <= 0x3fff) {
rom_select = data & 0x1f;
if(rom_select == 0) rom_select = 1;
if((addr & 0xe000) == 0x2000) { //2000-3fff
rom_select = (data & 0x1f) + ((data & 0x1f) == 0);
return;
}
if(addr >= 0x4000 && addr <= 0x5fff) {
if((addr & 0xe000) == 0x4000) { //4000-5fff
ram_select = data & 0x03;
return;
}
if(addr >= 0x6000 && addr <= 0x7fff) {
if((addr & 0xe000) == 0x6000) { //6000-7fff
mode_select = data & 0x01;
return;
}
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);
if((addr & 0xe000) == 0xa000) { //a000-bfff
if(ram_enable) {
if(mode_select == 0) {
cartridge.ram_write(addr & 0x1fff, data);
} else {
cartridge.ram_write((ram_select << 13) | (addr & 0x1fff), data);
}
}
return;
}
}
@ -51,9 +65,6 @@ void Cartridge::MBC1::power() {
rom_select = 0x01;
ram_select = 0x00;
mode_select = 0;
rom_bank = 0x4000;
ram_bank = 0x0000;
}
#endif

View File

@ -4,9 +4,6 @@ struct MBC1 : MMIO {
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();

View File

@ -1,19 +1,16 @@
#ifdef CARTRIDGE_CPP
uint8 Cartridge::MBC2::mmio_read(uint16 addr) {
if(addr >= 0x0000 && addr <= 0x3fff) {
if((addr & 0xc000) == 0x0000) { //0000-3fff
return cartridge.rom_read(addr);
}
if(addr >= 0x4000 && addr <= 0x7fff) {
if((addr & 0xc000) == 0x4000) { //4000-7fff
return cartridge.rom_read((rom_select << 14) | (addr & 0x3fff));
}
if(addr >= 0xa000 && addr <= 0xa1ff) {
if(ram_enable) {
uint8 data = cartridge.ram_read(addr & 0x1ff);
return (addr & 1) == 0 ? ((data >> 4) & 0x0f) : ((data >> 0) & 0x0f);
}
if((addr & 0xfe00) == 0xa000) { //a000-a1ff
if(ram_enable) return cartridge.ram_read(addr & 0x1ff);
return 0x00;
}
@ -21,29 +18,19 @@ uint8 Cartridge::MBC2::mmio_read(uint16 addr) {
}
void Cartridge::MBC2::mmio_write(uint16 addr, uint8 data) {
if(addr >= 0x0000 && addr <= 0x1fff) {
if((addr & 0x100) == 0) {
ram_enable = (data & 0x0f) == 0x0a;
}
if((addr & 0xe100) == 0x0000) { //0000-1fff [d8=0]
ram_enable = (data & 0x0f) == 0x0a;
return;
}
if(addr >= 0x2000 && addr <= 0x3fff) {
if(addr & 0x100) {
rom_select = data & 0x0f;
if(rom_select == 0) rom_select = 1;
}
if((addr & 0xe100) == 0x2100) { //2000-3fff [d8=1]
rom_select = (data & 0x0f) + ((data & 0x0f) == 0);
return;
}
if(addr >= 0xa000 && addr <= 0xa1ff) {
if(ram_enable) {
addr &= 511;
if((addr & 1) == 0) {
cartridge.ram_write(addr, (cartridge.ram_read(addr) & 0x0f) | (data << 4));
} else {
cartridge.ram_write(addr, (cartridge.ram_read(addr) & 0xf0) | (data << 0));
}
return;
}
if((addr & 0xfe00) == 0xa000) { //a000-a1ff
if(ram_enable) cartridge.ram_write(addr & 0x1ff, data & 0x0f);
return;
}
}

120
gameboy/cartridge/mbc3/mbc3.cpp Executable file
View File

@ -0,0 +1,120 @@
#ifdef CARTRIDGE_CPP
void Cartridge::MBC3::second() {
if(rtc_halt == false) {
if(++rtc_second >= 60) {
rtc_second = 0;
if(++rtc_minute >= 60) {
rtc_minute = 0;
if(++rtc_hour >= 24) {
rtc_hour = 0;
if(++rtc_day >= 512) {
rtc_day = 0;
rtc_day_carry = true;
}
}
}
}
}
}
uint8 Cartridge::MBC3::mmio_read(uint16 addr) {
if((addr & 0xc000) == 0x0000) { //0000-3fff
return cartridge.rom_read(addr);
}
if((addr & 0xc000) == 0x4000) { //4000-7fff
return cartridge.rom_read((rom_select << 13) | (addr & 0x3fff));
}
if((addr & 0xe000) == 0xa000) { //a000-bfff
if(ram_enable) {
if(ram_select >= 0x00 && ram_select <= 0x03) {
return cartridge.ram_read((ram_select << 13) | (addr & 0x1fff));
}
if(ram_select == 0x08) return rtc_latch_second;
if(ram_select == 0x09) return rtc_latch_minute;
if(ram_select == 0x0a) return rtc_latch_hour;
if(ram_select == 0x0b) return rtc_latch_day;
if(ram_select == 0x0c) return (rtc_latch_day_carry << 7) | (rtc_latch_day >> 8);
}
return 0x00;
}
return 0x00;
}
void Cartridge::MBC3::mmio_write(uint16 addr, uint8 data) {
if((addr & 0xe000) == 0x0000) { //0000-1fff
ram_enable = (data & 0x0f) == 0x0a;
return;
}
if((addr & 0xe000) == 0x2000) { //2000-3fff
rom_select = (data & 0x7f) + ((data & 0x7f) == 0);
return;
}
if((addr & 0xe000) == 0x4000) { //4000-5fff
ram_select = data;
return;
}
if((addr & 0xe000) == 0x6000) { //6000-7fff
if(rtc_latch == 0 && data == 1) {
rtc_latch_second = rtc_second;
rtc_latch_minute = rtc_minute;
rtc_latch_hour = rtc_hour;
rtc_latch_day = rtc_day;
rtc_latch_day_carry = rtc_day_carry;
}
rtc_latch = data;
return;
}
if((addr & 0xe000) == 0xa000) { //a000-bfff
if(ram_enable) {
if(ram_select >= 0x00 && ram_select <= 0x03) {
cartridge.ram_write((ram_select << 13) | (addr & 0x1fff), data);
} else if(ram_select == 0x08) {
if(data >= 60) data = 0;
rtc_second = data;
} else if(ram_select == 0x09) {
if(data >= 60) data = 0;
rtc_minute = data;
} else if(ram_select == 0x0a) {
if(data >= 24) data = 0;
rtc_hour = data;
} else if(ram_select == 0x0b) {
rtc_day = (rtc_day & 0x0100) | data;
} else if(ram_select == 0x0c) {
rtc_day = ((data & 1) << 8) | (rtc_day & 0xff);
rtc_halt = data & 0x40;
rtc_day_carry = data & 0x80;
}
}
return;
}
}
void Cartridge::MBC3::power() {
ram_enable = false;
rom_select = 0x01;
ram_select = 0x00;
rtc_latch = 0;
rtc_halt = true;
rtc_second = 0;
rtc_minute = 0;
rtc_hour = 0;
rtc_day = 0;
rtc_day_carry = false;
rtc_latch_second = 0;
rtc_latch_minute = 0;
rtc_latch_hour = 0;
rtc_latch_day = 0;
rtc_latch_day_carry = false;
}
#endif

24
gameboy/cartridge/mbc3/mbc3.hpp Executable file
View File

@ -0,0 +1,24 @@
struct MBC3 : MMIO {
bool ram_enable; //0000-1fff
uint8 rom_select; //2000-3fff
uint8 ram_select; //4000-5fff
bool rtc_latch; //6000-7fff
bool rtc_halt;
unsigned rtc_second;
unsigned rtc_minute;
unsigned rtc_hour;
unsigned rtc_day;
bool rtc_day_carry;
unsigned rtc_latch_second;
unsigned rtc_latch_minute;
unsigned rtc_latch_hour;
unsigned rtc_latch_day;
unsigned rtc_latch_day_carry;
void second();
uint8 mmio_read(uint16 addr);
void mmio_write(uint16 addr, uint8 data);
void power();
} mbc3;

View File

@ -1,18 +1,16 @@
#ifdef CARTRIDGE_CPP
uint8 Cartridge::MBC5::mmio_read(uint16 addr) {
if(addr >= 0x0000 && addr <= 0x3fff) {
if((addr & 0xc000) == 0x0000) { //0000-3fff
return cartridge.rom_read(addr);
}
if(addr >= 0x4000 && addr <= 0x7fff) {
if((addr & 0xc000) == 0x4000) { //4000-7fff
return cartridge.rom_read((rom_select << 14) | (addr & 0x3fff));
}
if(addr >= 0xa000 && addr <= 0xbfff) {
if(ram_enable) {
return cartridge.ram_read((ram_select << 13) | (addr & 0x1fff));
}
if((addr & 0xe000) == 0xa000) { //a000-bfff
if(ram_enable) return cartridge.ram_read((ram_select << 13) | (addr & 0x1fff));
return 0x00;
}
@ -20,26 +18,29 @@ uint8 Cartridge::MBC5::mmio_read(uint16 addr) {
}
void Cartridge::MBC5::mmio_write(uint16 addr, uint8 data) {
if(addr >= 0x0000 && addr <= 0x1fff) {
if((addr & 0xe000) == 0x0000) { //0000-1fff
ram_enable = (data & 0x0f) == 0x0a;
return;
}
if(addr >= 0x2000 && addr <= 0x2fff) {
if((addr & 0xf000) == 0x2000) { //2000-2fff
rom_select = (rom_select & 0x0100) | data;
return;
}
if(addr >= 0x3000 && addr <= 0x3fff) {
if((addr & 0xf000) == 0x3000) { //3000-3fff
rom_select = ((data & 1) << 8) | (rom_select & 0x00ff);
return;
}
if(addr >= 0x4000 && addr <= 0x5fff) {
if((addr & 0xe000) == 0x4000) { //4000-5fff
ram_select = data & 0x0f;
return;
}
if(addr >= 0xa000 && addr <= 0xbfff) {
if(ram_enable) {
cartridge.ram_write((ram_select << 13) | (addr & 0x1fff), data);
}
if((addr & 0xe000) == 0xa000) { //a000-bfff
if(ram_enable) cartridge.ram_write((ram_select << 13) | (addr & 0x1fff), data);
return;
}
}

View File

@ -14,7 +14,7 @@ void CPU::Main() {
void CPU::main() {
while(true) {
//print(disassemble(r[PC]), "\n");
if(trace) print(disassemble(r[PC]), "\n");
interrupt_test();
uint8 opcode = op_read(r[PC]++);
(this->*opcode_table[opcode])();
@ -27,7 +27,7 @@ void CPU::interrupt_raise(CPU::Interrupt id) {
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;
case Interrupt::Joypad: status.interrupt_request_joypad = 1; status.stop = false; break;
}
status.halt = false;
}
@ -89,6 +89,7 @@ void CPU::power() {
r[DE] = 0x0000;
r[HL] = 0x0000;
status.clock = 0;
status.halt = false;
status.stop = false;
@ -124,7 +125,7 @@ void CPU::power() {
status.interrupt_enable_vblank = 0;
}
CPU::CPU() {
CPU::CPU() : trace(false) {
initialize_opcode_table();
}

View File

@ -3,6 +3,8 @@ struct CPU : Processor, MMIO {
#include "mmio/mmio.hpp"
#include "timing/timing.hpp"
bool trace;
enum class Interrupt : unsigned {
Vblank,
Stat,
@ -12,6 +14,7 @@ struct CPU : Processor, MMIO {
};
struct Status {
unsigned clock;
bool halt;
bool stop;

View File

@ -13,6 +13,12 @@
#include "opcode.cpp"
void CPU::add_clocks(unsigned clocks) {
status.clock += clocks;
if(status.clock >= 4 * 1024 * 1024) {
status.clock -= 4 * 1024 * 1024;
cartridge.mbc3.second();
}
status.timer0 += clocks;
if(status.timer0 >= 16) timer_stage0();

View File

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

View File

@ -82,13 +82,11 @@ uint16 LCD::read_tile(bool select, unsigned x, unsigned y) {
void LCD::render_bg() {
unsigned iy = (status.ly + status.scy) & 255;
unsigned ix = status.scx, tx = ix & 7;
uint8 mask = 0x80 >> tx;
unsigned data = read_tile(status.bg_tilemap_select, ix, iy), palette;
unsigned data = read_tile(status.bg_tilemap_select, ix, iy);
for(unsigned ox = 0; ox < 160; ox++) {
palette = ((data & (mask << 0)) ? 1 : 0);
palette |= ((data & (mask << 8)) ? 2 : 0);
mask = (mask >> 1) | (mask << 7);
uint8 palette = ((data & (0x0080 >> tx)) ? 1 : 0)
| ((data & (0x8000 >> tx)) ? 2 : 0);
line[ox] = status.bgp[palette];
ix = (ix + 1) & 255;
@ -102,13 +100,11 @@ void LCD::render_window() {
if(status.ly - status.wy >= 144U) return;
unsigned iy = status.ly - status.wy;
unsigned ix = (status.wx - 7) & 255, tx = ix & 7;
uint8 mask = 0x80 >> tx;
unsigned data = read_tile(status.window_tilemap_select, ix, iy), palette;
unsigned data = read_tile(status.window_tilemap_select, ix, iy);
for(unsigned ox = 0; ox < 160; ox++) {
palette = ((data & (mask << 0)) ? 1 : 0);
palette |= ((data & (mask << 8)) ? 2 : 0);
mask = (mask >> 1) | (mask << 7);
uint8 palette = ((data & (0x0080 >> tx)) ? 1 : 0)
| ((data & (0x8000 >> tx)) ? 2 : 0);
if(ox - (status.wx - 7) < 160U) line[ox] = status.bgp[palette];
ix = (ix + 1) & 255;
@ -121,25 +117,53 @@ void LCD::render_window() {
void LCD::render_obj() {
unsigned obj_size = (status.obj_size == 0 ? 8 : 16);
unsigned sprite[10], sprites = 0;
//find first ten sprites on this scanline
for(unsigned s = 0; s < 40; s++) {
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 - sy;
if(sy >= obj_size) continue;
sprite[sprites++] = s;
if(sprites == 10) break;
}
//sort by X-coordinate, when equal, lower address comes first
for(unsigned x = 0; x < sprites; x++) {
for(unsigned y = x + 1; y < sprites; y++) {
signed sx = oam[(sprite[x] << 2) + 1] - 8;
signed sy = oam[(sprite[y] << 2) + 1] - 8;
if(sy < sx) {
sprite[x] ^= sprite[y];
sprite[y] ^= sprite[x];
sprite[x] ^= sprite[y];
}
}
}
//render backwards, so that first sprite has highest priority
for(signed s = sprites - 1; s >= 0; s--) {
unsigned n = sprite[s] << 2;
unsigned sy = oam[n + 0] - 16;
unsigned sx = oam[n + 1] - 8;
unsigned tile = oam[n + 2];
unsigned attribute = oam[n + 3];
sy = status.ly - sy;
if(sy >= obj_size) continue;
if(attribute & 0x40) sy ^= (obj_size - 1);
unsigned tdaddr = tile * 16 + sy * 2;
unsigned tdaddr = (tile << 4) + (sy << 1);
uint8 d0 = vram[tdaddr + 0];
uint8 d1 = vram[tdaddr + 1];
unsigned xflip = attribute & 0x20 ? 7 : 0;
for(unsigned tx = 0; tx < 8; tx++) {
uint8 palette = ((d0 & 0x80) >> 7) + ((d1 & 0x80) >> 6);
d0 <<= 1, d1 <<= 1;
uint8 palette = ((d0 & (0x80 >> tx)) ? 1 : 0)
| ((d1 & (0x80 >> tx)) ? 2 : 0);
if(palette == 0) continue;
palette = status.obp[(bool)(attribute & 0x10)][palette];

View File

@ -15,7 +15,7 @@ void MainWindow::create() {
settingsVideoSync.setChecked(true);
tools.create(*this, "Tools");
//tools.setEnabled(false);
toolsTraceCPU.create(tools, "Trace CPU");
help.create(*this, "Help");
helpAbout.create(help, "About ...");
@ -43,6 +43,10 @@ void MainWindow::create() {
video.set(Video::Synchronize, mainWindow.settingsVideoSync.checked());
};
toolsTraceCPU.onTick = []() {
GameBoy::cpu.trace = mainWindow.toolsTraceCPU.checked();
};
helpAbout.onTick = []() {
MessageWindow::information(mainWindow, {
"bgameboy\n\n",

View File

@ -8,6 +8,7 @@ struct MainWindow : Window {
MenuCheckItem settingsVideoSync;
Menu tools;
MenuCheckItem toolsTraceCPU;
Menu help;
MenuItem helpAbout;