first tests now pass

This commit is contained in:
2024-04-02 17:38:43 -07:00
parent 0f95c0da86
commit 5c573789c9
12 changed files with 557 additions and 230 deletions

View File

@@ -42,6 +42,6 @@ void AddressSpace::loadGame(const std::string& filename) {
std::istream_iterator<Byte>(rom),
std::istream_iterator<Byte>());
memcpy(memoryLayout.romBank0, game.data(), ROM_BANK_SIZE);
memcpy(memoryLayout.romBankSwitch, game.data() + ROM_BANK_SIZE, ROM_BANK_SIZE);
memoryLayout.romBank0 = game.data();
memoryLayout.romBankSwitch = game.data() + ROM_BANK_SIZE;
}

View File

@@ -12,34 +12,76 @@
class AddressSpace {
bool bootromLoaded = true;
Byte bootrom[BOOTROM_SIZE] = {0};
public:
std::vector<Byte> game;
public:
AddressSpace() {
// Initialize the memory to zero
memoryLayout = {};
std::memset(memoryLayout.memory, 0, sizeof(memoryLayout.memory));
}
// Nested union for the memory layout
union MemoryLayout {
Byte memory[0x10000];
Byte* cartridgeRam = nullptr;
struct {
Byte romBank0[ROM_BANK_SIZE]; // Mapped to 0x0000
Byte romBankSwitch[ROM_BANK_SIZE]; // Mapped to 0x4000
Byte vram[0x2000]; // Mapped to 0x8000
Byte externalRam[0x2000]; // Mapped to 0xA000
Byte memoryBank1[0x1000]; // Mapped to 0xC000
Byte memoryBank2[0x1000]; // Mapped to 0xD000
Byte echoRam[0x1E00]; // Mapped to 0xE000 (Echo RAM, mirrors 0xC000 to 0xDFFF)
Byte spriteAttributeTable[0xA0]; // Mapped to 0xFE00
Byte notUsable[0x60]; // Mapped to 0xFEA0
Byte io[0x80]; // Mapped to 0xFF00, 0xFF0F is interrupt flag
Byte specialRam[0x7F]; // Mapped to 0xFF80
Byte interuptEnableReg; // Mapped to 0xFFFF
};
struct {
Byte* romBank0; //[ROM_BANK_SIZE] Mapped to 0x0000
Byte* romBankSwitch; //[ROM_BANK_SIZE] Mapped to 0x4000
Byte vram[0x2000]; //Mapped to 0x8000
Byte* externalRam; //[0x2000]; Mapped to 0xA000
Byte memoryBank1[0x1000]; //Mapped to 0xC000
Byte memoryBank2[0x1000]; //Mapped to 0xD000
Byte oam[0xA0]; //Mapped to 0xFE00
Byte notUsable[0x60]; //Mapped to 0xFEA0
//General purpose hardware registers
Byte JOYP;
Byte SB;
Byte SC;
Byte DIV;
//Timer registers
Byte TIMA;
Byte TMA;
Byte TAC;
//interrupt flag and enable
Byte IF;
//Sound registers
Byte NR10;
Byte NR11;
Byte NR12;
Byte NR13;
Byte NR14;
Byte NR20; //not used
Byte NR21;
Byte NR22;
Byte NR23;
Byte NR24;
Byte NR30;
Byte NR31;
Byte NR32;
Byte NR33;
Byte NR34;
Byte NR40; //unused
Byte NR41;
Byte NR42;
Byte NR43;
Byte NR44;
Byte NR50;
Byte NR51;
Byte NR52;
Byte waveRam[0x10];
//PPU registers
Byte LCDC;
Byte STAT;
Byte SCY;
Byte SCX;
Byte LY;
Byte LYC;
Byte DMA;
Byte BGP;
Byte OBP0;
Byte OBP1;
Byte WY;
Byte WX;
Byte specialRam[0x7F]; //Mapped to 0xFF80
Byte IE; // Mapped to 0xFFFF
} memoryLayout{};
void unmapBootrom();
@@ -49,35 +91,281 @@ public:
void loadGame(const std::string& filename);
void determineMBCInfo();
static bool testMBCWrite(Word address);
Byte* MBCRead(Word address);
//prevents seg faults when programs with no MBC try to write to ROM
Byte dummyVal = 0;
void MBCUpdate();
void loadRomBank();
void createRamBank();
void loadRamBank();
MBCType MBC = {};
uint32_t romSize = 0;
uint32_t romBanks = 0;
uint32_t externalRamSize = 0;
uint32_t externalRamBanks = 0;
Byte romBankRegister = 0x01;
Byte ramBankRegister = 0x00;
//Selected ROM Bank = (Secondary Bank << 5) + ROM Bank
Byte selectedRomBank = 0;
Byte romBankRegister = 0x00;
//2 bit register acts as secondary rom bank register or ram bank number
Byte twoBitBankRegister = 0x0;
Byte selectedExternalRamBank = 0;
Byte romRamSelect = 0x00;
Byte ramEnable = 0x00;
//MBC3
Byte latchClockData = 0x00;
Byte ramBankRTCRegister = 0x00;
//overload [] for echo ram and bootrom support
Byte operator[](const uint32_t address) const {
if (address >= 0xE000 && address < 0xFE00)
return memoryLayout.echoRam[address - 0x2000];
//read
Byte operator[](const Word address) const {
if (address < 0x0100 && bootromLoaded)
return bootrom[address];
if (address < 0x4000)
return memoryLayout.romBank0[address];
if (address < 0x8000)
return memoryLayout.romBankSwitch[address - 0x4000];
if (address < 0xA000)
return memoryLayout.vram[address - 0x8000];
if (address < 0xC000) {
if (externalRamSize == 0)
return 0xFF;
return memoryLayout.externalRam[address - 0xA000];
}
if (address < 0xD000)
return memoryLayout.memoryBank1[address - 0xC000];
if (address < 0xE000)
return memoryLayout.memoryBank2[address - 0xD000];
if (address < 0xFE00)
return memoryLayout.memoryBank1[address - 0xE000];
if (address < 0xFEA0)
return memoryLayout.oam[address - 0xFE00];
if (address < 0xFF00) {
if ((memoryLayout.STAT & 0x03) == 2 || (memoryLayout.STAT & 0x03) == 3)
return 0xFF;
return 0x00;
}
if (address < 0xFF80)
switch (address) {
case 0xFF00:
return memoryLayout.JOYP;
case 0xFF01:
return memoryLayout.SB;
case 0xFF02:
return memoryLayout.SC;
// Timer registers
case 0xFF04:
return memoryLayout.DIV;
case 0xFF05:
return memoryLayout.TIMA;
case 0xFF06:
return memoryLayout.TMA;
case 0xFF07:
return memoryLayout.TAC;
// Interrupt flag
case 0xFF0F:
return memoryLayout.IF;
// Sound registers
case 0xFF10:
return memoryLayout.NR10;
case 0xFF11:
return memoryLayout.NR11;
case 0xFF12:
return memoryLayout.NR12;
case 0xFF13:
return memoryLayout.NR13;
case 0xFF14:
return memoryLayout.NR14;
case 0xFF16:
return memoryLayout.NR21;
case 0xFF17:
return memoryLayout.NR22;
case 0xFF18:
return memoryLayout.NR23;
case 0xFF19:
return memoryLayout.NR24;
case 0xFF1A:
return memoryLayout.NR30;
case 0xFF1B:
return memoryLayout.NR31;
case 0xFF1C:
return memoryLayout.NR32;
case 0xFF1D:
return memoryLayout.NR33;
case 0xFF1E:
return memoryLayout.NR34;
case 0xFF20:
return memoryLayout.NR41;
case 0xFF21:
return memoryLayout.NR42;
case 0xFF22:
return memoryLayout.NR43;
case 0xFF23:
return memoryLayout.NR44;
case 0xFF24:
return memoryLayout.NR50;
case 0xFF25:
return memoryLayout.NR51;
case 0xFF26:
return memoryLayout.NR52;
// PPU registers
case 0xFF40:
return memoryLayout.LCDC;
case 0xFF41:
return memoryLayout.STAT;
case 0xFF42:
return memoryLayout.SCY;
case 0xFF43:
return memoryLayout.SCX;
case 0xFF44:
return memoryLayout.LY;
case 0xFF45:
return memoryLayout.LYC;
case 0xFF46:
return memoryLayout.DMA;
case 0xFF47:
return memoryLayout.BGP;
case 0xFF48:
return memoryLayout.OBP0;
case 0xFF49:
return memoryLayout.OBP1;
case 0xFF4A:
return memoryLayout.WY;
case 0xFF4B:
return memoryLayout.WX;
return memoryLayout.memory[address];
default:
if (address >= 0xFF30 && address <= 0xFF3F) {
return memoryLayout.waveRam[address - 0xFF30];
}
return 0xFF;
}
if (address < 0xFFFF)
return memoryLayout.specialRam[address - 0xFF80];
//0xFFFF
return memoryLayout.IE;
}
Byte& operator[](const uint32_t address) {
if (address >= 0xE000 && address < 0xFE00)
return memoryLayout.echoRam[address - 0x2000];
//write
Byte& operator[](const Word address) {
if (address < 0x0100 && bootromLoaded)
return bootrom[address];
if (address < 0x8000)
return (*MBCRead(address));
if (address < 0xA000)
return memoryLayout.vram[address - 0x8000];
if (address < 0xC000)
return memoryLayout.externalRam[address - 0xA000];
if (address < 0xD000)
return memoryLayout.memoryBank1[address - 0xC000];
if (address < 0xE000)
return memoryLayout.memoryBank2[address - 0xD000];
if (address < 0xFE00)
return memoryLayout.memoryBank1[address - 0xE000];
if (address < 0xFEA0)
return memoryLayout.oam[address - 0xFE00];
if (address < 0xFF00)
return memoryLayout.notUsable[address - 0xFEA0];
if (address < 0xFF80)
switch (address) {
case 0xFF00:
return memoryLayout.JOYP;
case 0xFF01:
return memoryLayout.SB;
case 0xFF02:
return memoryLayout.SC;
case 0xFF04:
return memoryLayout.DIV;
// Timer registers
case 0xFF05:
return memoryLayout.TIMA;
// The address 0xFF15 is mentioned as unused, possibly a mistake? Original has TMA = 0xFF06 typically.
case 0xFF06:
return memoryLayout.TMA;
case 0xFF07:
return memoryLayout.TAC;
// Interrupt flag
case 0xFF0F:
return memoryLayout.IF;
// Sound registers
case 0xFF10:
return memoryLayout.NR10;
case 0xFF11:
return memoryLayout.NR11;
case 0xFF12:
return memoryLayout.NR12;
case 0xFF13:
return memoryLayout.NR13;
case 0xFF14:
return memoryLayout.NR14;
case 0xFF16:
return memoryLayout.NR21;
case 0xFF17:
return memoryLayout.NR22;
case 0xFF18:
return memoryLayout.NR23;
case 0xFF19:
return memoryLayout.NR24;
case 0xFF1A:
return memoryLayout.NR30;
case 0xFF1B:
return memoryLayout.NR31;
case 0xFF1C:
return memoryLayout.NR32;
case 0xFF1D:
return memoryLayout.NR33;
case 0xFF1E:
return memoryLayout.NR34;
case 0xFF20:
return memoryLayout.NR41;
case 0xFF21:
return memoryLayout.NR42;
case 0xFF22:
return memoryLayout.NR43;
case 0xFF23:
return memoryLayout.NR44;
case 0xFF24:
return memoryLayout.NR50;
case 0xFF25:
return memoryLayout.NR51;
case 0xFF26:
return memoryLayout.NR52;
// PPU registers
case 0xFF40:
return memoryLayout.LCDC;
case 0xFF41:
return memoryLayout.STAT;
case 0xFF42:
return memoryLayout.SCY;
case 0xFF43:
return memoryLayout.SCX;
case 0xFF44:
return memoryLayout.LY;
case 0xFF45:
return memoryLayout.LYC;
case 0xFF46:
return memoryLayout.DMA;
case 0xFF47:
return memoryLayout.BGP;
case 0xFF48:
return memoryLayout.OBP0;
case 0xFF49:
return memoryLayout.OBP1;
case 0xFF4A:
return memoryLayout.WY;
case 0xFF4B:
return memoryLayout.WX;
return memoryLayout.memory[address];
default:
if (address >= 0xFF30 && address <= 0xFF3F) {
return memoryLayout.waveRam[address - 0xFF30];
}
return dummyVal;
}
if (address < 0xFFFF)
return memoryLayout.specialRam[address - 0xFF80];
//0xFFFF
return memoryLayout.IE;
}
};

View File

@@ -32,6 +32,7 @@
#define SCREEN_BPP 3
#define ROM_BANK_SIZE 0x4000
#define RAM_BANK_SIZE 0x2000
#define SCANLINES_PER_FRAME 154
#define SCANLINE_DURATION 456

View File

@@ -3,7 +3,7 @@
void GameBoy::extendedOpcodeResolver() {
PC += 1;
switch (addressSpace[PC]) {
switch (readOnlyAddressSpace[PC]) {
case 0x00:
rlc(BC.hi);
PC += 1;
@@ -425,7 +425,7 @@ void GameBoy::extendedOpcodeResolver() {
break;
case 0x46:
bit(0, addressSpace[HL.reg]);
bit(0, readOnlyAddressSpace[HL.reg]);
PC += 1;
addCycles(12);
break;
@@ -473,7 +473,7 @@ void GameBoy::extendedOpcodeResolver() {
break;
case 0x4E:
bit(1, addressSpace[HL.reg]);
bit(1, readOnlyAddressSpace[HL.reg]);
PC += 1;
addCycles(12);
break;
@@ -521,7 +521,7 @@ void GameBoy::extendedOpcodeResolver() {
break;
case 0x56:
bit(2, addressSpace[HL.reg]);
bit(2, readOnlyAddressSpace[HL.reg]);
PC += 1;
addCycles(12);
break;
@@ -569,7 +569,7 @@ void GameBoy::extendedOpcodeResolver() {
break;
case 0x5E:
bit(3, addressSpace[HL.reg]);
bit(3, readOnlyAddressSpace[HL.reg]);
PC += 1;
addCycles(12);
break;
@@ -617,7 +617,7 @@ void GameBoy::extendedOpcodeResolver() {
break;
case 0x66:
bit(4, addressSpace[HL.reg]);
bit(4, readOnlyAddressSpace[HL.reg]);
PC += 1;
addCycles(12);
break;
@@ -665,7 +665,7 @@ void GameBoy::extendedOpcodeResolver() {
break;
case 0x6E:
bit(5, addressSpace[HL.reg]);
bit(5, readOnlyAddressSpace[HL.reg]);
PC += 1;
addCycles(12);
break;
@@ -713,7 +713,7 @@ void GameBoy::extendedOpcodeResolver() {
break;
case 0x76:
bit(6, addressSpace[HL.reg]);
bit(6, readOnlyAddressSpace[HL.reg]);
PC += 1;
addCycles(12);
break;
@@ -761,7 +761,7 @@ void GameBoy::extendedOpcodeResolver() {
break;
case 0x7E:
bit(7, addressSpace[HL.reg]);
bit(7, readOnlyAddressSpace[HL.reg]);
PC += 1;
addCycles(12);
break;

View File

@@ -13,10 +13,11 @@ void GameBoy::start(std::string bootrom, std::string game) {
addressSpace.loadBootrom(bootrom);
addressSpace.loadGame(game);
addressSpace.determineMBCInfo();
addressSpace.createRamBank();
//init some registers that won't otherwise by set
(*JOYP) = 0xCF;
(*SC) = 0x7E;
addressSpace.memoryLayout.JOYP = 0xCF;
addressSpace.memoryLayout.SC = 0x7E;
bool quit = false;
@@ -28,29 +29,34 @@ void GameBoy::start(std::string bootrom, std::string game) {
if (event.type == SDL_QUIT) {
quit = true;
}
if (event.type == SDL_KEYUP) {
display = true;
}
}
while (!rendered) {
if (PC > 0xFF && addressSpace.getBootromState()) {
addressSpace.unmapBootrom();
}
ppuEnabled = (*LCDC) & 0x80;
ppuEnabled = addressSpace.memoryLayout.LCDC & 0x80;
if (PC >= 0xe0)
display = true;
if (display) {
printf("Cycles: %lu, Opcode: 0x%.2x PPU cycles: %lu, PPMode: %d\n", cycles, addressSpace[PC],
cyclesSinceLastScanline(), currentMode);
printf("PC:0x%.2x, SP:0x%.2x\n", PC, SP);
printf("AF:0x%.4x, BC:0x%.4x\n", AF.reg, BC.reg);
printf("DE:0x%.4x, HL:0x%.4x\n", DE.reg, HL.reg);
printf("IME:%d IF:0x%.2x IE:0x%.2x\n", IME, (*IF), (*IE));
printf("LCDC:%.2x STAT:0x%.2x LY:%d LYC:%d\n", (*LCDC), (*STAT), (*LY), (*LYC));
printf("Cart type: 0x%.2x\n", addressSpace.game[0x147]);
printf("\n");
}
// if (PC == 0x100)
// display = true;
// if (display) {
// printf("Cycles: %lu, Opcode: 0x%.2x PPU cycles: %lu, PPMode: %d\n", cycles, readOnlyAddressSpace[PC],
// cyclesSinceLastScanline(), currentMode);
// printf("AF:0x%.4x, BC:0x%.4x\n", AF.reg, BC.reg);
// printf("DE:0x%.4x, HL:0x%.4x\n", DE.reg, HL.reg);
// printf("IME:%d IF:0x%.2x IE:0x%.2x\n", IME, (*IF), (*IE));
// printf("PC:0x%.4x, SP:0x%.4x\n", PC, SP);
// printf("LCDC:%.2x STAT:0x%.2x LY:%d LYC:%d\n", (*LCDC), (*STAT), (*LY), (*LYC));
// printf("\n");
// }
// if (PC >= 0xf000)
// exit(1);
opcodeResolver();
addressSpace.MBCUpdate();
interruptHandler();
timingHandler();
if (ppuEnabled) {
@@ -60,8 +66,8 @@ void GameBoy::start(std::string bootrom, std::string game) {
ppuCycles = 2;
lastScanline = 0;
lastRefresh = 0;
(*LY) = 0x00;
(*STAT) &= 0xfc;
addressSpace.memoryLayout.LY = 0x00;
addressSpace.memoryLayout.STAT &= 0xfc;
}
}
rendered = false;

View File

@@ -46,61 +46,7 @@ class GameBoy {
Word PC = 0x0000; //program counter
AddressSpace addressSpace;
//General purpose hardware registers
Byte* const JOYP = &addressSpace[0xFF00];
Byte* const SB = &addressSpace[0xFF01];
Byte* const SC = &addressSpace[0xFF02];
Byte* const DIV = &addressSpace[0xFF04];
//Timer registers
Byte* const TIMA = &addressSpace[0xFF05];
Byte* const TMA = &addressSpace[0xFF15]; //unused
Byte* const TAC = &addressSpace[0xFF16];
//interrupt flag and enable
Byte* const IF = &addressSpace[0xFF0F];
Byte* const IE = &addressSpace[0xFFFF];
//Sound registers
Byte* const NR10 = &addressSpace[0xFF10];
Byte* const NR11 = &addressSpace[0xFF11];
Byte* const NR12 = &addressSpace[0xFF12];
Byte* const NR13 = &addressSpace[0xFF13];
Byte* const NR14 = &addressSpace[0xFF14];
Byte* const NR20 = &addressSpace[0xFF15]; //unused
Byte* const NR21 = &addressSpace[0xFF16];
Byte* const NR22 = &addressSpace[0xFF17];
Byte* const NR23 = &addressSpace[0xFF18];
Byte* const NR24 = &addressSpace[0xFF19];
Byte* const NR30 = &addressSpace[0xFF1A];
Byte* const NR31 = &addressSpace[0xFF1B];
Byte* const NR32 = &addressSpace[0xFF1C];
Byte* const NR33 = &addressSpace[0xFF1D];
Byte* const NR34 = &addressSpace[0xFF1E];
Byte* const NR40 = &addressSpace[0xFF1F]; //unused
Byte* const NR41 = &addressSpace[0xFF20];
Byte* const NR42 = &addressSpace[0xFF21];
Byte* const NR43 = &addressSpace[0xFF22];
Byte* const NR44 = &addressSpace[0xFF23];
Byte* const NR50 = &addressSpace[0xFF24];
Byte* const NR51 = &addressSpace[0xFF25];
Byte* const NR52 = &addressSpace[0xFF26];
Byte* const waveRam = &addressSpace[0xFF30]; //WaveRam[0x10]
//PPU registers
Byte* const LCDC = &addressSpace[0xFF40];
Byte* const STAT = &addressSpace[0xFF41];
Byte* const SCY = &addressSpace[0xFF42];
Byte* const SCX = &addressSpace[0xFF43];
Byte* const LY = &addressSpace[0xFF44];
Byte* const LYC = &addressSpace[0xFF45];
Byte* const DMA = &addressSpace[0xFF46];
Byte* const BGP = &addressSpace[0xFF47];
Byte* const OBP0 = &addressSpace[0xFF48];
Byte* const OBP1 = &addressSpace[0xFF49];
Byte* const WY = &addressSpace[0xFF4A];
Byte* const WX = &addressSpace[0xFF4B];
const AddressSpace& readOnlyAddressSpace = addressSpace;
PPUMode currentMode = PPUMode::mode0;
@@ -114,8 +60,6 @@ class GameBoy {
uint32_t frameTime = 0;
const int frameDelay = 1000 / V_SYNC;
bool testMBCWrite(const Byte& address);
void opcodeResolver();
void incLY();
void ppuUpdate();
@@ -131,7 +75,7 @@ class GameBoy {
void interruptHandler();
bool testInterruptEnabled(Byte interrupt) const;
void resetInterrupt(Byte interrupt) const;
void resetInterrupt(Byte interrupt);
void VBlankHandle();
void LCDStatHandle();
@@ -162,8 +106,8 @@ class GameBoy {
void xorBitwise(T& dest, T src);
void bit(Byte testBit, Byte reg);
void extendedOpcodeResolver();
static void set(const uint8_t testBit, uint8_t& reg);
static void res(const uint8_t testBit, uint8_t& reg);
static void set(uint8_t testBit, uint8_t& reg);
static void res(uint8_t testBit, uint8_t& reg);
template <typename T>
void jp(T address);
template <typename T>

View File

@@ -2,27 +2,32 @@
#include "gameboy.hpp"
bool GameBoy::testInterruptEnabled(const Byte interrupt) const {
return (*IE) & static_cast<Byte>(1 << interrupt);
return readOnlyAddressSpace.memoryLayout.IE & static_cast<Byte>(1 << interrupt);
}
void GameBoy::resetInterrupt(const Byte interrupt) const {
*IF &= ~(1 << interrupt);
*IF |= 0xE0;
void GameBoy::resetInterrupt(const Byte interrupt) {
addressSpace.memoryLayout.IF &= ~(1 << interrupt);
addressSpace.memoryLayout.IF |= 0xE0;
}
void GameBoy::interruptHandler() {
if (!IME)
return;
if (*IF & static_cast<Byte>(1 << VBLANK_INTERRUPT) && testInterruptEnabled(VBLANK_INTERRUPT))
if (readOnlyAddressSpace.memoryLayout.IF & static_cast<Byte>(1 << VBLANK_INTERRUPT) && testInterruptEnabled(
VBLANK_INTERRUPT))
VBlankHandle();
if (*IF & static_cast<Byte>(1 << LCD_STAT_INTERRUPT) && testInterruptEnabled(LCD_STAT_INTERRUPT))
if (readOnlyAddressSpace.memoryLayout.IF & static_cast<Byte>(1 << LCD_STAT_INTERRUPT) && testInterruptEnabled(
LCD_STAT_INTERRUPT))
LCDStatHandle();
if (*IF & static_cast<Byte>(1 << TIMER_INTERRUPT) && testInterruptEnabled(TIMER_INTERRUPT))
if (readOnlyAddressSpace.memoryLayout.IF & static_cast<Byte>(1 << TIMER_INTERRUPT) && testInterruptEnabled(
TIMER_INTERRUPT))
timerHandle();
if (*IF & static_cast<Byte>(1 << SERIAL_INTERRUPT) && testInterruptEnabled(SERIAL_INTERRUPT))
if (readOnlyAddressSpace.memoryLayout.IF & static_cast<Byte>(1 << SERIAL_INTERRUPT) && testInterruptEnabled(
SERIAL_INTERRUPT))
serialHandle();
if (*IF & static_cast<Byte>(1 << JOYPAD_INTERRUPT) && testInterruptEnabled(JOYPAD_INTERRUPT))
if (readOnlyAddressSpace.memoryLayout.IF & static_cast<Byte>(1 << JOYPAD_INTERRUPT) && testInterruptEnabled(
JOYPAD_INTERRUPT))
joypadHandle();
}

View File

@@ -4,7 +4,7 @@
int main(int argc, char** argv) {
auto* gb = new GameBoy();
gb->SDL2setup();
gb->start("../dmg_boot.bin", "../roms/cpu_instrs.gb");
gb->start("../dmg_boot.bin", "../roms/03-op_sp,hl.gb");
gb->SDL2destroy();
delete gb;

View File

@@ -1,10 +1,9 @@
#include "addressSpace.hpp"
#include "gameboy.hpp"
void AddressSpace::determineMBCInfo() {
MBC = static_cast<MBCType>(memoryLayout.romBank0[0x147]);
romSize = 32768 * (1 << memoryLayout.romBank0[0x147]);
romBanks = (1 << (memoryLayout.romBank0[0x147] + 1));
romBanks = 1 << (memoryLayout.romBank0[0x147] + 1);
switch (memoryLayout.romBank0[0x0149]) {
case 0x02:
@@ -29,16 +28,90 @@ void AddressSpace::determineMBCInfo() {
break;
}
if (MBC == MBC2 || MBC2Battery) {
if (MBC == MBC2 || MBC == MBC2Battery) {
//only the lower 4 bits are usable
externalRamSize = 512;
}
}
bool GameBoy::testMBCWrite(const Byte& address) {
const Byte* ptr = &address;
if (ptr >= &addressSpace[0x0] && ptr <= &addressSpace[0x7FFF])
bool AddressSpace::testMBCWrite(const Word address) {
if (address <= 0x7FFF)
return true;
return false;
}
Byte* AddressSpace::MBCRead(const Word address) {
if (MBC == MBC1) {
if (address <= 0x1FFF)
return &ramEnable;
if (address <= 0x3FFF)
return &romBankRegister;
if (address <= 0x5FFF)
return &twoBitBankRegister;
if (address <= 0x7FFF)
return &romRamSelect;
}
if (MBC == MBC1Ram || MBC == MBC1RamBattery) {
if (address <= 0x1FFF)
return &ramEnable;
//bits 0-4
if (address <= 0x3FFF)
return &romBankRegister;
if (address <= 0x5FFF) {
return &twoBitBankRegister;
}
if (address <= 0x7FFF)
return &romRamSelect;
}
return &dummyVal;
}
void AddressSpace::MBCUpdate() {
if (MBC == MBC1) {
//TODO: multicart roms need to be able to switch the first rom bank as well
//see: https://gbdev.io/pandocs/MBC1.html
//Selected ROM Bank = (Secondary Bank << 5) + ROM Bank
romBankRegister &= 0x1F;
twoBitBankRegister &= 0x3;
//512 KiB can only have 8KiB of ram
if (romSize > 524288) {
if (romBankRegister == 0)
selectedRomBank = (twoBitBankRegister << 5) + 1;
selectedRomBank = (twoBitBankRegister << 5) + romBankRegister;
selectedExternalRamBank = 0;
}
else {
if (romBankRegister == 0)
selectedRomBank = 1;
else
selectedRomBank = romBankRegister;
selectedExternalRamBank = twoBitBankRegister;
}
loadRomBank();
loadRamBank();
}
if (MBC == MBC1Ram || MBC == MBC1RamBattery) {}
}
void AddressSpace::loadRomBank() {
Byte* old = memoryLayout.romBankSwitch;
memoryLayout.romBankSwitch = game.data() + (ROM_BANK_SIZE * selectedRomBank);
if (old != memoryLayout.romBankSwitch)
printf("\n");
}
void AddressSpace::createRamBank() {
if (externalRamSize)
cartridgeRam = new Byte[externalRamSize];
}
void AddressSpace::loadRamBank() {
Byte* old = memoryLayout.externalRam;
if (cartridgeRam != nullptr)
memoryLayout.externalRam = cartridgeRam + (RAM_BANK_SIZE * selectedExternalRamBank);
if (old != memoryLayout.externalRam)
printf("\n");
}

View File

@@ -16,28 +16,28 @@ Word GameBoy::getWordPC() {
RegisterPair word = {0};
//remember little endianness
word.lo = addressSpace[PC + 1];
word.hi = addressSpace[PC + 2];
word.lo = readOnlyAddressSpace[PC + 1];
word.hi = readOnlyAddressSpace[PC + 2];
return word.reg;
}
Byte GameBoy::getBytePC() {
return addressSpace[PC + 1];
return readOnlyAddressSpace[PC + 1];
}
Word GameBoy::getWordSP() {
RegisterPair word = {0};
//remember little endianness
word.lo = addressSpace[SP++];
word.hi = addressSpace[SP++];
word.lo = readOnlyAddressSpace[SP++];
word.hi = readOnlyAddressSpace[SP++];
return word.reg;
}
Byte GameBoy::getByteSP() {
return addressSpace[SP++];
return readOnlyAddressSpace[SP++];
}
void GameBoy::ret() {
@@ -47,8 +47,8 @@ void GameBoy::ret() {
template <typename T>
void GameBoy::ld(T& dest, T src) {
if constexpr (std::is_same_v<T, Byte>) {
if (&dest == DIV) {
*DIV = 0x00;
if (&dest == &addressSpace.memoryLayout.DIV) {
addressSpace.memoryLayout.DIV = 0x00;
lastDivUpdate = cycles;
}
else {
@@ -90,7 +90,7 @@ void GameBoy::add(T& reg, T value) {
else
resetFlag(HALFCARRY_FLAG);
//halfcarry test https://robdor.com/2016/08/10/gameboy-emulator-half-carry-flag/
if ((((value & 0xFFFF) + (reg & 0xFFFF)) & 0x10000) == 0x10000)
if ((static_cast<unsigned>(value) + static_cast<unsigned>(reg)) & 0x10000)
setFlag(CARRY_FLAG);
else
resetFlag(CARRY_FLAG);
@@ -98,10 +98,12 @@ void GameBoy::add(T& reg, T value) {
reg += value;
if (reg == 0)
setFlag(ZERO_FLAG);
else
resetFlag(ZERO_FLAG);
if (sizeof(reg) == sizeof(Byte)) {
if (reg == 0)
setFlag(ZERO_FLAG);
else
resetFlag(ZERO_FLAG);
}
resetFlag(SUBTRACT_FLAG);
}
@@ -225,6 +227,8 @@ void GameBoy::orBitwise(T& dest, T src) {
if (dest == 0)
setFlag(ZERO_FLAG);
else
resetFlag(ZERO_FLAG);
resetFlag(SUBTRACT_FLAG);
resetFlag(HALFCARRY_FLAG);
@@ -237,6 +241,8 @@ void GameBoy::andBitwise(T& dest, T src) {
if (dest == 0)
setFlag(ZERO_FLAG);
else
resetFlag(ZERO_FLAG);
resetFlag(SUBTRACT_FLAG);
setFlag(HALFCARRY_FLAG);
@@ -249,6 +255,8 @@ void GameBoy::xorBitwise(T& dest, T src) {
if (dest == 0)
setFlag(ZERO_FLAG);
else
resetFlag(ZERO_FLAG);
resetFlag(SUBTRACT_FLAG);
resetFlag(CARRY_FLAG);
@@ -473,7 +481,7 @@ void GameBoy::rr(Byte& reg) {
reg >>= 1;
if (getFlag(CARRY_FLAG))
AF.hi |= 0x80;
reg |= 0x80;
if (lsb)
setFlag(CARRY_FLAG);
@@ -628,6 +636,7 @@ void GameBoy::srl(Byte& reg) {
template <typename T>
void GameBoy::pop(T& reg) {
reg = getWordSP();
AF.reg &= 0xFFF0;
}
template <typename T>
@@ -636,9 +645,9 @@ void GameBoy::push(T reg) {
RegisterPair temp = {0};
temp.lo = reg & 0xFF;
temp.hi = reg >> 8;
SP--;
SP -= 1;
addressSpace[SP] = temp.hi;
SP--;
SP -= 1;
addressSpace[SP] = temp.lo;
}
@@ -678,9 +687,9 @@ void GameBoy::ccf() {
void GameBoy::stop() {}
void GameBoy::opcodeResolver() {
if (addressSpace[PC] != 0xCB) {
if (readOnlyAddressSpace[PC] != 0xCB) {
bool jumped;
switch (addressSpace[PC]) {
switch (readOnlyAddressSpace[PC]) {
case 0x00:
//NOP
PC += 1;
@@ -742,7 +751,7 @@ void GameBoy::opcodeResolver() {
break;
case 0x0A:
ld(AF.hi, addressSpace[BC.reg]);
ld(AF.hi, readOnlyAddressSpace[BC.reg]);
PC += 1;
addCycles(8);
break;
@@ -790,13 +799,13 @@ void GameBoy::opcodeResolver() {
break;
case 0x12:
ld(addressSpace[BC.reg], AF.hi);
ld(addressSpace[DE.reg], AF.hi);
PC += 1;
addCycles(8);
break;
case 0x13:
DE.reg++; //no flags change no just inc it manually
DE.reg += 1; //no flags change no just inc it manually
PC += 1;
addCycles(8);
break;
@@ -837,7 +846,7 @@ void GameBoy::opcodeResolver() {
break;
case 0x1A:
ld(AF.hi, addressSpace[DE.reg]);
ld(AF.hi, readOnlyAddressSpace[DE.reg]);
PC += 1;
addCycles(8);
break;
@@ -891,7 +900,7 @@ void GameBoy::opcodeResolver() {
case 0x22:
ld(addressSpace[HL.reg], AF.hi);
HL.reg++;
HL.reg += 1;
PC += 1;
addCycles(8);
break;
@@ -944,7 +953,7 @@ void GameBoy::opcodeResolver() {
break;
case 0x2A:
ld(AF.hi, addressSpace[HL.reg]);
ld(AF.hi, readOnlyAddressSpace[HL.reg]);
HL.reg += 1;
PC += 1;
addCycles(8);
@@ -963,7 +972,7 @@ void GameBoy::opcodeResolver() {
break;
case 0x2D:
dec(HL.hi);
dec(HL.lo);
PC += 1;
addCycles(4);
break;
@@ -999,7 +1008,7 @@ void GameBoy::opcodeResolver() {
case 0x32:
ld(addressSpace[HL.reg], AF.hi);
HL.reg--;
HL.reg -= 1;
PC += 1;
addCycles(8);
break;
@@ -1052,14 +1061,14 @@ void GameBoy::opcodeResolver() {
break;
case 0x3A:
ld(AF.hi, addressSpace[HL.reg]);
ld(AF.hi, readOnlyAddressSpace[HL.reg]);
HL.reg -= 1;
PC += 1;
addCycles(8);
break;
case 0x3B:
SP--;
SP -= 1;
PC += 1;
addCycles(8);
break;
@@ -1125,7 +1134,7 @@ void GameBoy::opcodeResolver() {
break;
case 0x46:
ld(BC.hi, addressSpace[HL.reg]);
ld(BC.hi, readOnlyAddressSpace[HL.reg]);
PC += 1;
addCycles(8);
break;
@@ -1173,7 +1182,7 @@ void GameBoy::opcodeResolver() {
break;
case 0x4E:
ld(BC.lo, addressSpace[HL.reg]);
ld(BC.lo, readOnlyAddressSpace[HL.reg]);
PC += 1;
addCycles(8);
break;
@@ -1221,7 +1230,7 @@ void GameBoy::opcodeResolver() {
break;
case 0x56:
ld(DE.hi, addressSpace[HL.reg]);
ld(DE.hi, readOnlyAddressSpace[HL.reg]);
PC += 1;
addCycles(8);
break;
@@ -1269,7 +1278,7 @@ void GameBoy::opcodeResolver() {
break;
case 0x5E:
ld(DE.lo, addressSpace[HL.reg]);
ld(DE.lo, readOnlyAddressSpace[HL.reg]);
PC += 1;
addCycles(8);
break;
@@ -1317,7 +1326,7 @@ void GameBoy::opcodeResolver() {
break;
case 0x66:
ld(HL.hi, addressSpace[HL.reg]);
ld(HL.hi, readOnlyAddressSpace[HL.reg]);
PC += 1;
addCycles(8);
break;
@@ -1365,7 +1374,7 @@ void GameBoy::opcodeResolver() {
break;
case 0x6E:
ld(HL.lo, addressSpace[HL.reg]);
ld(HL.lo, readOnlyAddressSpace[HL.reg]);
PC += 1;
addCycles(8);
break;
@@ -1461,7 +1470,7 @@ void GameBoy::opcodeResolver() {
break;
case 0x7E:
ld(AF.hi, addressSpace[HL.reg]);
ld(AF.hi, readOnlyAddressSpace[HL.reg]);
PC += 1;
addCycles(8);
break;
@@ -1509,7 +1518,7 @@ void GameBoy::opcodeResolver() {
break;
case 0x86:
add(AF.hi, addressSpace[HL.reg]);
add(AF.hi, readOnlyAddressSpace[HL.reg]);
PC += 1;
addCycles(8);
break;
@@ -1557,7 +1566,7 @@ void GameBoy::opcodeResolver() {
break;
case 0x8E:
adc(AF.hi, addressSpace[HL.reg]);
adc(AF.hi, readOnlyAddressSpace[HL.reg]);
PC += 1;
addCycles(8);
break;
@@ -1605,7 +1614,7 @@ void GameBoy::opcodeResolver() {
break;
case 0x96:
sub(addressSpace[HL.reg]);
sub(readOnlyAddressSpace[HL.reg]);
PC += 1;
addCycles(8);
break;
@@ -1653,7 +1662,7 @@ void GameBoy::opcodeResolver() {
break;
case 0x9E:
sbc(addressSpace[HL.reg]);
sbc(readOnlyAddressSpace[HL.reg]);
PC += 1;
addCycles(8);
break;
@@ -1701,7 +1710,7 @@ void GameBoy::opcodeResolver() {
break;
case 0xA6:
andBitwise(AF.hi, addressSpace[HL.reg]);
andBitwise(AF.hi, readOnlyAddressSpace[HL.reg]);
PC += 1;
addCycles(8);
break;
@@ -1749,7 +1758,7 @@ void GameBoy::opcodeResolver() {
break;
case 0xAE:
xorBitwise(AF.hi, addressSpace[HL.reg]);
xorBitwise(AF.hi, readOnlyAddressSpace[HL.reg]);
PC += 1;
addCycles(8);
break;
@@ -1797,7 +1806,7 @@ void GameBoy::opcodeResolver() {
break;
case 0xB6:
orBitwise(AF.hi, addressSpace[HL.reg]);
orBitwise(AF.hi, readOnlyAddressSpace[HL.reg]);
PC += 1;
addCycles(8);
break;
@@ -1845,7 +1854,7 @@ void GameBoy::opcodeResolver() {
break;
case 0xBE:
cp(addressSpace[HL.reg]);
cp(readOnlyAddressSpace[HL.reg]);
PC += 1;
addCycles(8);
break;
@@ -2091,7 +2100,7 @@ void GameBoy::opcodeResolver() {
break;
case 0xE2:
ld(addressSpace[BC.lo + 0xFF00], AF.hi);
ld(addressSpace[0xFF00 + BC.lo], AF.hi);
PC += 1;
addCycles(8);
break;
@@ -2115,20 +2124,19 @@ void GameBoy::opcodeResolver() {
case 0xE8:
{
const auto immediate = static_cast<int8_t>(getBytePC());
const Word result = SP + static_cast<int16_t>(immediate);
const int16_t immediate = static_cast<int8_t>(getBytePC());
if (((SP ^ immediate ^ result) & 0x10) != 0)
if ((SP & 0xF) + (immediate & 0xF) > 0xF)
setFlag(HALFCARRY_FLAG);
else
resetFlag(HALFCARRY_FLAG);
if (((SP ^ immediate ^ result) & 0x100) != 0)
if ((SP & 0xFF) + (immediate & 0xFF) > 0xFF)
setFlag(CARRY_FLAG);
else
resetFlag(CARRY_FLAG);
SP = result;
SP += immediate;
resetFlag(ZERO_FLAG);
resetFlag(SUBTRACT_FLAG);
@@ -2161,7 +2169,7 @@ void GameBoy::opcodeResolver() {
break;
case 0xF0:
ld(AF.hi, addressSpace[0xFF00 + getBytePC()]);
ld(AF.hi, readOnlyAddressSpace[0xFF00 + getBytePC()]);
PC += 2;
addCycles(12);
break;
@@ -2173,7 +2181,7 @@ void GameBoy::opcodeResolver() {
break;
case 0xF2:
ld(AF.hi, addressSpace[0xFF00 + BC.lo]);
ld(AF.hi, readOnlyAddressSpace[0xFF00 + BC.lo]);
PC += 1;
addCycles(12);
break;
@@ -2203,20 +2211,20 @@ void GameBoy::opcodeResolver() {
case 0xF8:
{
const auto n = static_cast<int8_t>(getBytePC());
const Word result = SP + n;
const int16_t immediate = static_cast<int8_t>(getBytePC());
HL.reg = SP + immediate;
//halfcarry test https://robdor.com/2016/08/10/gameboy-emulator-half-carry-flag/
if ((((result & 0xF) + (HL.reg & 0xF)) & 0x10) == 0x10)
if ((SP & 0xF) + (immediate & 0xF) > 0xF)
setFlag(HALFCARRY_FLAG);
else
resetFlag(HALFCARRY_FLAG);
//halfcarry test https://robdor.com/2016/08/10/gameboy-emulator-half-carry-flag/
if ((((result & 0xFF) + (HL.reg & 0xFF)) & 0x100) == 0x100)
if ((SP & 0xFF) + (immediate & 0xFF) > 0xFF)
setFlag(CARRY_FLAG);
else
resetFlag(CARRY_FLAG);
HL.reg = result; // Load the result into HL
resetFlag(ZERO_FLAG);
resetFlag(SUBTRACT_FLAG);
@@ -2233,7 +2241,7 @@ void GameBoy::opcodeResolver() {
break;
case 0xFA:
ld(AF.hi, addressSpace[getWordPC()]);
ld(AF.hi, readOnlyAddressSpace[getWordPC()]);
PC += 3;
addCycles(16);
break;

View File

@@ -8,25 +8,28 @@ void GameBoy::ppuUpdate() {
//test for HBlank
checkPPUMode();
if ((*LY) == (*LYC) || (*STAT) & (1 << 6)) {
if (readOnlyAddressSpace.memoryLayout.LY == readOnlyAddressSpace.memoryLayout.LYC || readOnlyAddressSpace.
memoryLayout.STAT & (1 << 6)) {
// Request STAT interrupt if LY matches LYC
// bug on DMG models triggers a STAT interrupt anytime the STAT register is written
// Road Rage and Zerd no Denetsu rely on this
(*STAT) |= (1 << 2);
addressSpace.memoryLayout.STAT |= (1 << 2);
}
else {
(*STAT) &= ~(1 << 2);
addressSpace.memoryLayout.STAT &= ~(1 << 2);
}
// Check for STAT interrupts and request if needed (e.g., when entering specific modes)
bool hBlankInterruptEnabled = (*STAT) & (1 << 3);
bool vBlankInterruptEnabled = (*STAT) & (1 << 4); /* Determine if VBlank interrupt is enabled */
bool oamInterruptEnabled = (*STAT) & (1 << 5); /* Determine if OAM Search interrupt is enabled */
bool hBlankInterruptEnabled = readOnlyAddressSpace.memoryLayout.STAT & (1 << 3);
bool vBlankInterruptEnabled = readOnlyAddressSpace.memoryLayout.STAT & (1 << 4);
/* Determine if VBlank interrupt is enabled */
bool oamInterruptEnabled = readOnlyAddressSpace.memoryLayout.STAT & (1 << 5);
/* Determine if OAM Search interrupt is enabled */
if (currentMode == PPUMode::mode0 && hBlankInterruptEnabled ||
currentMode == PPUMode::mode1 && vBlankInterruptEnabled ||
currentMode == PPUMode::mode2 && oamInterruptEnabled) {
*IF |= 0x2;
addressSpace.memoryLayout.IF |= 0x2;
}
}
@@ -58,16 +61,16 @@ void GameBoy::checkPPUMode() {
}
void GameBoy::incLY() {
(*LY)++;
addressSpace.memoryLayout.LY += 1;
setPPUMode(PPUMode::mode2);
if ((*LY) > SCANLINES_PER_FRAME - 1) {
(*LY) = 0;
if (addressSpace.memoryLayout.LY > SCANLINES_PER_FRAME - 1) {
addressSpace.memoryLayout.LY = 0;
}
else if ((*LY) == 144) {
else if (addressSpace.memoryLayout.LY == 144) {
// VBlank Period
SDL2present();
setPPUMode(PPUMode::mode1);
*IF |= 0x1;
addressSpace.memoryLayout.IF |= 0x1;
}
}
@@ -84,31 +87,31 @@ uint64_t GameBoy::cyclesSinceLastRefresh() const {
void GameBoy::setPPUMode(const PPUMode mode) {
switch (mode) {
case PPUMode::mode0:
(*STAT) &= ~0x03;
addressSpace.memoryLayout.STAT &= ~0x03;
currentMode = PPUMode::mode0;
break;
case PPUMode::mode1:
(*STAT) &= ~0x03;
(*STAT) |= 0x01;
addressSpace.memoryLayout.STAT &= ~0x03;
addressSpace.memoryLayout.STAT |= 0x01;
currentMode = PPUMode::mode1;
break;
case PPUMode::mode2:
(*STAT) &= ~0x03;
(*STAT) |= 0x02;
addressSpace.memoryLayout.STAT &= ~0x03;
addressSpace.memoryLayout.STAT |= 0x02;
currentMode = PPUMode::mode2;
break;
case PPUMode::mode3:
(*STAT) &= ~0x03;
(*STAT) |= 0x03;
addressSpace.memoryLayout.STAT &= ~0x03;
addressSpace.memoryLayout.STAT |= 0x03;
currentMode = PPUMode::mode3;
break;
}
//7th bit is unused but always set
(*STAT) |= 0x80;
addressSpace.memoryLayout.STAT |= 0x80;
}
void GameBoy::drawLine() {
const uint8_t line = (*LY);
const uint8_t line = readOnlyAddressSpace.memoryLayout.LY;
// Calculate the starting index of the current scanline in the framebuffer
const uint32_t lineStartIndex = line * RESOLUTION_X;
@@ -116,25 +119,27 @@ void GameBoy::drawLine() {
// Pointer to the current line's pixel data in the framebuffer
uint32_t* currentLinePixels = framebuffer + lineStartIndex;
if (!(*LCDC & 0x1)) {
if (!(readOnlyAddressSpace.memoryLayout.LCDC & 0x1)) {
std::fill_n(currentLinePixels, RESOLUTION_X, 0xFFFFFFFF); // Fill line with white if BG display is off.
return;
}
const uint16_t backgroundMapAddr = (*LCDC & 0x08) ? 0x9C00 : 0x9800;
const uint16_t tileDataTableAddr = (*LCDC & 0x10) ? 0x8000 : 0x8800;
const bool signedIndex = !(*LCDC & 0x10);
const uint16_t backgroundMapAddr = (readOnlyAddressSpace.memoryLayout.LCDC & 0x08) ? 0x9C00 : 0x9800;
const uint16_t tileDataTableAddr = (readOnlyAddressSpace.memoryLayout.LCDC & 0x10) ? 0x8000 : 0x8800;
const bool signedIndex = !(readOnlyAddressSpace.memoryLayout.LCDC & 0x10);
for (int pixel = 0; pixel < RESOLUTION_X; pixel++) {
const uint8_t xPos = (pixel + (*SCX)) % 256; // 256 pixels in total BG width
const uint8_t yPos = (line + (*SCY)) % 256; // 256 pixels in total BG height
const uint8_t xPos = (pixel + readOnlyAddressSpace.memoryLayout.SCX) % 256; // 256 pixels in total BG width
const uint8_t yPos = (line + readOnlyAddressSpace.memoryLayout.SCY) % 256; // 256 pixels in total BG height
const uint16_t tileRow = (yPos / 8) * 32;
const uint16_t tileCol = xPos / 8;
const uint16_t tileIndex = tileRow + tileCol;
const uint16_t tileAddr = backgroundMapAddr + tileIndex;
const int8_t tileID = signedIndex ? static_cast<int8_t>(addressSpace[tileAddr]) : addressSpace[tileAddr];
const int8_t tileID = signedIndex
? static_cast<int8_t>(readOnlyAddressSpace[tileAddr])
: addressSpace[tileAddr];
uint16_t tileDataAddr;
if (signedIndex) {
@@ -145,14 +150,14 @@ void GameBoy::drawLine() {
}
const uint8_t lineOffset = yPos % 8;
const uint8_t tileRowData1 = addressSpace[tileDataAddr + (lineOffset * 2)];
const uint8_t tileRowData2 = addressSpace[tileDataAddr + (lineOffset * 2) + 1];
const uint8_t tileRowData1 = readOnlyAddressSpace[tileDataAddr + (lineOffset * 2)];
const uint8_t tileRowData2 = readOnlyAddressSpace[tileDataAddr + (lineOffset * 2) + 1];
const uint8_t colourBit = 7 - (xPos % 8);
const uint8_t colourNum = ((tileRowData2 >> colourBit) & 0x1) << 1 | ((tileRowData1 >> colourBit) & 0x1);
// Apply the BGP register for palette mapping
uint8_t palette = (*BGP >> (colourNum * 2)) & 0x3;
uint8_t palette = (readOnlyAddressSpace.memoryLayout.BGP >> (colourNum * 2)) & 0x3;
if (xPos == 0)
palette = 0x3;
switch (palette) {
@@ -198,7 +203,6 @@ void GameBoy::SDL2present() {
frameTime = SDL_GetTicks() - frameStart;
std::cout << SDL_GetTicks() << " " << frameTime << std::endl;
if (frameDelay > frameTime) {
SDL_Delay(frameDelay - frameTime);

View File

@@ -4,9 +4,7 @@
void GameBoy::timingHandler() {
if (cycles - lastDivUpdate >= DIVIDER_REGISTER_FREQ) {
const uint8_t increments = (cycles - lastDivUpdate) / DIVIDER_REGISTER_FREQ;
(*DIV) += increments;
addressSpace.memoryLayout.DIV += increments;
lastDivUpdate += increments * DIVIDER_REGISTER_FREQ;
}
}