first tests now pass
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
85
src/mbc.cpp
85
src/mbc.cpp
@@ -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");
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
68
src/ppu.cpp
68
src/ppu.cpp
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user