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>(rom),
|
||||||
std::istream_iterator<Byte>());
|
std::istream_iterator<Byte>());
|
||||||
|
|
||||||
memcpy(memoryLayout.romBank0, game.data(), ROM_BANK_SIZE);
|
memoryLayout.romBank0 = game.data();
|
||||||
memcpy(memoryLayout.romBankSwitch, game.data() + ROM_BANK_SIZE, ROM_BANK_SIZE);
|
memoryLayout.romBankSwitch = game.data() + ROM_BANK_SIZE;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,34 +12,76 @@
|
|||||||
class AddressSpace {
|
class AddressSpace {
|
||||||
bool bootromLoaded = true;
|
bool bootromLoaded = true;
|
||||||
Byte bootrom[BOOTROM_SIZE] = {0};
|
Byte bootrom[BOOTROM_SIZE] = {0};
|
||||||
|
|
||||||
public:
|
|
||||||
std::vector<Byte> game;
|
std::vector<Byte> game;
|
||||||
|
|
||||||
|
public:
|
||||||
AddressSpace() {
|
AddressSpace() {
|
||||||
// Initialize the memory to zero
|
// Initialize the memory to zero
|
||||||
memoryLayout = {};
|
memoryLayout = {};
|
||||||
std::memset(memoryLayout.memory, 0, sizeof(memoryLayout.memory));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Nested union for the memory layout
|
Byte* cartridgeRam = nullptr;
|
||||||
union MemoryLayout {
|
|
||||||
Byte memory[0x10000];
|
|
||||||
|
|
||||||
struct {
|
struct {
|
||||||
Byte romBank0[ROM_BANK_SIZE]; // Mapped to 0x0000
|
Byte* romBank0; //[ROM_BANK_SIZE] Mapped to 0x0000
|
||||||
Byte romBankSwitch[ROM_BANK_SIZE]; // Mapped to 0x4000
|
Byte* romBankSwitch; //[ROM_BANK_SIZE] Mapped to 0x4000
|
||||||
Byte vram[0x2000]; // Mapped to 0x8000
|
Byte vram[0x2000]; //Mapped to 0x8000
|
||||||
Byte externalRam[0x2000]; // Mapped to 0xA000
|
Byte* externalRam; //[0x2000]; Mapped to 0xA000
|
||||||
Byte memoryBank1[0x1000]; // Mapped to 0xC000
|
Byte memoryBank1[0x1000]; //Mapped to 0xC000
|
||||||
Byte memoryBank2[0x1000]; // Mapped to 0xD000
|
Byte memoryBank2[0x1000]; //Mapped to 0xD000
|
||||||
Byte echoRam[0x1E00]; // Mapped to 0xE000 (Echo RAM, mirrors 0xC000 to 0xDFFF)
|
Byte oam[0xA0]; //Mapped to 0xFE00
|
||||||
Byte spriteAttributeTable[0xA0]; // Mapped to 0xFE00
|
Byte notUsable[0x60]; //Mapped to 0xFEA0
|
||||||
Byte notUsable[0x60]; // Mapped to 0xFEA0
|
//General purpose hardware registers
|
||||||
Byte io[0x80]; // Mapped to 0xFF00, 0xFF0F is interrupt flag
|
Byte JOYP;
|
||||||
Byte specialRam[0x7F]; // Mapped to 0xFF80
|
Byte SB;
|
||||||
Byte interuptEnableReg; // Mapped to 0xFFFF
|
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{};
|
} memoryLayout{};
|
||||||
|
|
||||||
void unmapBootrom();
|
void unmapBootrom();
|
||||||
@@ -49,35 +91,281 @@ public:
|
|||||||
void loadGame(const std::string& filename);
|
void loadGame(const std::string& filename);
|
||||||
|
|
||||||
void determineMBCInfo();
|
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 = {};
|
MBCType MBC = {};
|
||||||
uint32_t romSize = 0;
|
uint32_t romSize = 0;
|
||||||
uint32_t romBanks = 0;
|
uint32_t romBanks = 0;
|
||||||
uint32_t externalRamSize = 0;
|
uint32_t externalRamSize = 0;
|
||||||
uint32_t externalRamBanks = 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 romRamSelect = 0x00;
|
||||||
|
Byte ramEnable = 0x00;
|
||||||
//MBC3
|
//MBC3
|
||||||
Byte latchClockData = 0x00;
|
Byte latchClockData = 0x00;
|
||||||
Byte ramBankRTCRegister = 0x00;
|
Byte ramBankRTCRegister = 0x00;
|
||||||
|
|
||||||
//overload [] for echo ram and bootrom support
|
//read
|
||||||
Byte operator[](const uint32_t address) const {
|
Byte operator[](const Word address) const {
|
||||||
if (address >= 0xE000 && address < 0xFE00)
|
|
||||||
return memoryLayout.echoRam[address - 0x2000];
|
|
||||||
if (address < 0x0100 && bootromLoaded)
|
if (address < 0x0100 && bootromLoaded)
|
||||||
return bootrom[address];
|
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) {
|
//write
|
||||||
if (address >= 0xE000 && address < 0xFE00)
|
Byte& operator[](const Word address) {
|
||||||
return memoryLayout.echoRam[address - 0x2000];
|
|
||||||
if (address < 0x0100 && bootromLoaded)
|
if (address < 0x0100 && bootromLoaded)
|
||||||
return bootrom[address];
|
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 SCREEN_BPP 3
|
||||||
|
|
||||||
#define ROM_BANK_SIZE 0x4000
|
#define ROM_BANK_SIZE 0x4000
|
||||||
|
#define RAM_BANK_SIZE 0x2000
|
||||||
|
|
||||||
#define SCANLINES_PER_FRAME 154
|
#define SCANLINES_PER_FRAME 154
|
||||||
#define SCANLINE_DURATION 456
|
#define SCANLINE_DURATION 456
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
void GameBoy::extendedOpcodeResolver() {
|
void GameBoy::extendedOpcodeResolver() {
|
||||||
PC += 1;
|
PC += 1;
|
||||||
|
|
||||||
switch (addressSpace[PC]) {
|
switch (readOnlyAddressSpace[PC]) {
|
||||||
case 0x00:
|
case 0x00:
|
||||||
rlc(BC.hi);
|
rlc(BC.hi);
|
||||||
PC += 1;
|
PC += 1;
|
||||||
@@ -425,7 +425,7 @@ void GameBoy::extendedOpcodeResolver() {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case 0x46:
|
case 0x46:
|
||||||
bit(0, addressSpace[HL.reg]);
|
bit(0, readOnlyAddressSpace[HL.reg]);
|
||||||
PC += 1;
|
PC += 1;
|
||||||
addCycles(12);
|
addCycles(12);
|
||||||
break;
|
break;
|
||||||
@@ -473,7 +473,7 @@ void GameBoy::extendedOpcodeResolver() {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case 0x4E:
|
case 0x4E:
|
||||||
bit(1, addressSpace[HL.reg]);
|
bit(1, readOnlyAddressSpace[HL.reg]);
|
||||||
PC += 1;
|
PC += 1;
|
||||||
addCycles(12);
|
addCycles(12);
|
||||||
break;
|
break;
|
||||||
@@ -521,7 +521,7 @@ void GameBoy::extendedOpcodeResolver() {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case 0x56:
|
case 0x56:
|
||||||
bit(2, addressSpace[HL.reg]);
|
bit(2, readOnlyAddressSpace[HL.reg]);
|
||||||
PC += 1;
|
PC += 1;
|
||||||
addCycles(12);
|
addCycles(12);
|
||||||
break;
|
break;
|
||||||
@@ -569,7 +569,7 @@ void GameBoy::extendedOpcodeResolver() {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case 0x5E:
|
case 0x5E:
|
||||||
bit(3, addressSpace[HL.reg]);
|
bit(3, readOnlyAddressSpace[HL.reg]);
|
||||||
PC += 1;
|
PC += 1;
|
||||||
addCycles(12);
|
addCycles(12);
|
||||||
break;
|
break;
|
||||||
@@ -617,7 +617,7 @@ void GameBoy::extendedOpcodeResolver() {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case 0x66:
|
case 0x66:
|
||||||
bit(4, addressSpace[HL.reg]);
|
bit(4, readOnlyAddressSpace[HL.reg]);
|
||||||
PC += 1;
|
PC += 1;
|
||||||
addCycles(12);
|
addCycles(12);
|
||||||
break;
|
break;
|
||||||
@@ -665,7 +665,7 @@ void GameBoy::extendedOpcodeResolver() {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case 0x6E:
|
case 0x6E:
|
||||||
bit(5, addressSpace[HL.reg]);
|
bit(5, readOnlyAddressSpace[HL.reg]);
|
||||||
PC += 1;
|
PC += 1;
|
||||||
addCycles(12);
|
addCycles(12);
|
||||||
break;
|
break;
|
||||||
@@ -713,7 +713,7 @@ void GameBoy::extendedOpcodeResolver() {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case 0x76:
|
case 0x76:
|
||||||
bit(6, addressSpace[HL.reg]);
|
bit(6, readOnlyAddressSpace[HL.reg]);
|
||||||
PC += 1;
|
PC += 1;
|
||||||
addCycles(12);
|
addCycles(12);
|
||||||
break;
|
break;
|
||||||
@@ -761,7 +761,7 @@ void GameBoy::extendedOpcodeResolver() {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case 0x7E:
|
case 0x7E:
|
||||||
bit(7, addressSpace[HL.reg]);
|
bit(7, readOnlyAddressSpace[HL.reg]);
|
||||||
PC += 1;
|
PC += 1;
|
||||||
addCycles(12);
|
addCycles(12);
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -13,10 +13,11 @@ void GameBoy::start(std::string bootrom, std::string game) {
|
|||||||
addressSpace.loadBootrom(bootrom);
|
addressSpace.loadBootrom(bootrom);
|
||||||
addressSpace.loadGame(game);
|
addressSpace.loadGame(game);
|
||||||
addressSpace.determineMBCInfo();
|
addressSpace.determineMBCInfo();
|
||||||
|
addressSpace.createRamBank();
|
||||||
|
|
||||||
//init some registers that won't otherwise by set
|
//init some registers that won't otherwise by set
|
||||||
(*JOYP) = 0xCF;
|
addressSpace.memoryLayout.JOYP = 0xCF;
|
||||||
(*SC) = 0x7E;
|
addressSpace.memoryLayout.SC = 0x7E;
|
||||||
|
|
||||||
bool quit = false;
|
bool quit = false;
|
||||||
|
|
||||||
@@ -28,29 +29,34 @@ void GameBoy::start(std::string bootrom, std::string game) {
|
|||||||
if (event.type == SDL_QUIT) {
|
if (event.type == SDL_QUIT) {
|
||||||
quit = true;
|
quit = true;
|
||||||
}
|
}
|
||||||
|
if (event.type == SDL_KEYUP) {
|
||||||
|
display = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
while (!rendered) {
|
while (!rendered) {
|
||||||
if (PC > 0xFF && addressSpace.getBootromState()) {
|
if (PC > 0xFF && addressSpace.getBootromState()) {
|
||||||
addressSpace.unmapBootrom();
|
addressSpace.unmapBootrom();
|
||||||
}
|
}
|
||||||
ppuEnabled = (*LCDC) & 0x80;
|
ppuEnabled = addressSpace.memoryLayout.LCDC & 0x80;
|
||||||
|
|
||||||
if (PC >= 0xe0)
|
// if (PC == 0x100)
|
||||||
display = true;
|
// display = true;
|
||||||
if (display) {
|
// if (display) {
|
||||||
printf("Cycles: %lu, Opcode: 0x%.2x PPU cycles: %lu, PPMode: %d\n", cycles, addressSpace[PC],
|
// printf("Cycles: %lu, Opcode: 0x%.2x PPU cycles: %lu, PPMode: %d\n", cycles, readOnlyAddressSpace[PC],
|
||||||
cyclesSinceLastScanline(), currentMode);
|
// 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("AF:0x%.4x, BC:0x%.4x\n", AF.reg, BC.reg);
|
// printf("DE:0x%.4x, HL:0x%.4x\n", DE.reg, HL.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("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("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");
|
||||||
printf("\n");
|
// }
|
||||||
}
|
// if (PC >= 0xf000)
|
||||||
|
// exit(1);
|
||||||
|
|
||||||
opcodeResolver();
|
opcodeResolver();
|
||||||
|
addressSpace.MBCUpdate();
|
||||||
interruptHandler();
|
interruptHandler();
|
||||||
timingHandler();
|
timingHandler();
|
||||||
if (ppuEnabled) {
|
if (ppuEnabled) {
|
||||||
@@ -60,8 +66,8 @@ void GameBoy::start(std::string bootrom, std::string game) {
|
|||||||
ppuCycles = 2;
|
ppuCycles = 2;
|
||||||
lastScanline = 0;
|
lastScanline = 0;
|
||||||
lastRefresh = 0;
|
lastRefresh = 0;
|
||||||
(*LY) = 0x00;
|
addressSpace.memoryLayout.LY = 0x00;
|
||||||
(*STAT) &= 0xfc;
|
addressSpace.memoryLayout.STAT &= 0xfc;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
rendered = false;
|
rendered = false;
|
||||||
|
|||||||
@@ -46,61 +46,7 @@ class GameBoy {
|
|||||||
Word PC = 0x0000; //program counter
|
Word PC = 0x0000; //program counter
|
||||||
|
|
||||||
AddressSpace addressSpace;
|
AddressSpace addressSpace;
|
||||||
|
const AddressSpace& readOnlyAddressSpace = 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];
|
|
||||||
|
|
||||||
PPUMode currentMode = PPUMode::mode0;
|
PPUMode currentMode = PPUMode::mode0;
|
||||||
|
|
||||||
@@ -114,8 +60,6 @@ class GameBoy {
|
|||||||
uint32_t frameTime = 0;
|
uint32_t frameTime = 0;
|
||||||
const int frameDelay = 1000 / V_SYNC;
|
const int frameDelay = 1000 / V_SYNC;
|
||||||
|
|
||||||
bool testMBCWrite(const Byte& address);
|
|
||||||
|
|
||||||
void opcodeResolver();
|
void opcodeResolver();
|
||||||
void incLY();
|
void incLY();
|
||||||
void ppuUpdate();
|
void ppuUpdate();
|
||||||
@@ -131,7 +75,7 @@ class GameBoy {
|
|||||||
|
|
||||||
void interruptHandler();
|
void interruptHandler();
|
||||||
bool testInterruptEnabled(Byte interrupt) const;
|
bool testInterruptEnabled(Byte interrupt) const;
|
||||||
void resetInterrupt(Byte interrupt) const;
|
void resetInterrupt(Byte interrupt);
|
||||||
|
|
||||||
void VBlankHandle();
|
void VBlankHandle();
|
||||||
void LCDStatHandle();
|
void LCDStatHandle();
|
||||||
@@ -162,8 +106,8 @@ class GameBoy {
|
|||||||
void xorBitwise(T& dest, T src);
|
void xorBitwise(T& dest, T src);
|
||||||
void bit(Byte testBit, Byte reg);
|
void bit(Byte testBit, Byte reg);
|
||||||
void extendedOpcodeResolver();
|
void extendedOpcodeResolver();
|
||||||
static void set(const uint8_t testBit, uint8_t& reg);
|
static void set(uint8_t testBit, uint8_t& reg);
|
||||||
static void res(const uint8_t testBit, uint8_t& reg);
|
static void res(uint8_t testBit, uint8_t& reg);
|
||||||
template <typename T>
|
template <typename T>
|
||||||
void jp(T address);
|
void jp(T address);
|
||||||
template <typename T>
|
template <typename T>
|
||||||
|
|||||||
@@ -2,27 +2,32 @@
|
|||||||
#include "gameboy.hpp"
|
#include "gameboy.hpp"
|
||||||
|
|
||||||
bool GameBoy::testInterruptEnabled(const Byte interrupt) const {
|
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 {
|
void GameBoy::resetInterrupt(const Byte interrupt) {
|
||||||
*IF &= ~(1 << interrupt);
|
addressSpace.memoryLayout.IF &= ~(1 << interrupt);
|
||||||
*IF |= 0xE0;
|
addressSpace.memoryLayout.IF |= 0xE0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void GameBoy::interruptHandler() {
|
void GameBoy::interruptHandler() {
|
||||||
if (!IME)
|
if (!IME)
|
||||||
return;
|
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();
|
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();
|
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();
|
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();
|
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();
|
joypadHandle();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
int main(int argc, char** argv) {
|
int main(int argc, char** argv) {
|
||||||
auto* gb = new GameBoy();
|
auto* gb = new GameBoy();
|
||||||
gb->SDL2setup();
|
gb->SDL2setup();
|
||||||
gb->start("../dmg_boot.bin", "../roms/cpu_instrs.gb");
|
gb->start("../dmg_boot.bin", "../roms/03-op_sp,hl.gb");
|
||||||
gb->SDL2destroy();
|
gb->SDL2destroy();
|
||||||
delete gb;
|
delete gb;
|
||||||
|
|
||||||
|
|||||||
85
src/mbc.cpp
85
src/mbc.cpp
@@ -1,10 +1,9 @@
|
|||||||
#include "addressSpace.hpp"
|
#include "addressSpace.hpp"
|
||||||
#include "gameboy.hpp"
|
|
||||||
|
|
||||||
void AddressSpace::determineMBCInfo() {
|
void AddressSpace::determineMBCInfo() {
|
||||||
MBC = static_cast<MBCType>(memoryLayout.romBank0[0x147]);
|
MBC = static_cast<MBCType>(memoryLayout.romBank0[0x147]);
|
||||||
romSize = 32768 * (1 << 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]) {
|
switch (memoryLayout.romBank0[0x0149]) {
|
||||||
case 0x02:
|
case 0x02:
|
||||||
@@ -29,16 +28,90 @@ void AddressSpace::determineMBCInfo() {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (MBC == MBC2 || MBC2Battery) {
|
if (MBC == MBC2 || MBC == MBC2Battery) {
|
||||||
//only the lower 4 bits are usable
|
//only the lower 4 bits are usable
|
||||||
externalRamSize = 512;
|
externalRamSize = 512;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool GameBoy::testMBCWrite(const Byte& address) {
|
bool AddressSpace::testMBCWrite(const Word address) {
|
||||||
const Byte* ptr = &address;
|
if (address <= 0x7FFF)
|
||||||
if (ptr >= &addressSpace[0x0] && ptr <= &addressSpace[0x7FFF])
|
|
||||||
return true;
|
return true;
|
||||||
return false;
|
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};
|
RegisterPair word = {0};
|
||||||
|
|
||||||
//remember little endianness
|
//remember little endianness
|
||||||
word.lo = addressSpace[PC + 1];
|
word.lo = readOnlyAddressSpace[PC + 1];
|
||||||
word.hi = addressSpace[PC + 2];
|
word.hi = readOnlyAddressSpace[PC + 2];
|
||||||
|
|
||||||
return word.reg;
|
return word.reg;
|
||||||
}
|
}
|
||||||
|
|
||||||
Byte GameBoy::getBytePC() {
|
Byte GameBoy::getBytePC() {
|
||||||
return addressSpace[PC + 1];
|
return readOnlyAddressSpace[PC + 1];
|
||||||
}
|
}
|
||||||
|
|
||||||
Word GameBoy::getWordSP() {
|
Word GameBoy::getWordSP() {
|
||||||
RegisterPair word = {0};
|
RegisterPair word = {0};
|
||||||
|
|
||||||
//remember little endianness
|
//remember little endianness
|
||||||
word.lo = addressSpace[SP++];
|
word.lo = readOnlyAddressSpace[SP++];
|
||||||
word.hi = addressSpace[SP++];
|
word.hi = readOnlyAddressSpace[SP++];
|
||||||
|
|
||||||
return word.reg;
|
return word.reg;
|
||||||
}
|
}
|
||||||
|
|
||||||
Byte GameBoy::getByteSP() {
|
Byte GameBoy::getByteSP() {
|
||||||
return addressSpace[SP++];
|
return readOnlyAddressSpace[SP++];
|
||||||
}
|
}
|
||||||
|
|
||||||
void GameBoy::ret() {
|
void GameBoy::ret() {
|
||||||
@@ -47,8 +47,8 @@ void GameBoy::ret() {
|
|||||||
template <typename T>
|
template <typename T>
|
||||||
void GameBoy::ld(T& dest, T src) {
|
void GameBoy::ld(T& dest, T src) {
|
||||||
if constexpr (std::is_same_v<T, Byte>) {
|
if constexpr (std::is_same_v<T, Byte>) {
|
||||||
if (&dest == DIV) {
|
if (&dest == &addressSpace.memoryLayout.DIV) {
|
||||||
*DIV = 0x00;
|
addressSpace.memoryLayout.DIV = 0x00;
|
||||||
lastDivUpdate = cycles;
|
lastDivUpdate = cycles;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@@ -90,7 +90,7 @@ void GameBoy::add(T& reg, T value) {
|
|||||||
else
|
else
|
||||||
resetFlag(HALFCARRY_FLAG);
|
resetFlag(HALFCARRY_FLAG);
|
||||||
//halfcarry test https://robdor.com/2016/08/10/gameboy-emulator-half-carry-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);
|
setFlag(CARRY_FLAG);
|
||||||
else
|
else
|
||||||
resetFlag(CARRY_FLAG);
|
resetFlag(CARRY_FLAG);
|
||||||
@@ -98,10 +98,12 @@ void GameBoy::add(T& reg, T value) {
|
|||||||
|
|
||||||
reg += value;
|
reg += value;
|
||||||
|
|
||||||
if (reg == 0)
|
if (sizeof(reg) == sizeof(Byte)) {
|
||||||
setFlag(ZERO_FLAG);
|
if (reg == 0)
|
||||||
else
|
setFlag(ZERO_FLAG);
|
||||||
resetFlag(ZERO_FLAG);
|
else
|
||||||
|
resetFlag(ZERO_FLAG);
|
||||||
|
}
|
||||||
|
|
||||||
resetFlag(SUBTRACT_FLAG);
|
resetFlag(SUBTRACT_FLAG);
|
||||||
}
|
}
|
||||||
@@ -225,6 +227,8 @@ void GameBoy::orBitwise(T& dest, T src) {
|
|||||||
|
|
||||||
if (dest == 0)
|
if (dest == 0)
|
||||||
setFlag(ZERO_FLAG);
|
setFlag(ZERO_FLAG);
|
||||||
|
else
|
||||||
|
resetFlag(ZERO_FLAG);
|
||||||
|
|
||||||
resetFlag(SUBTRACT_FLAG);
|
resetFlag(SUBTRACT_FLAG);
|
||||||
resetFlag(HALFCARRY_FLAG);
|
resetFlag(HALFCARRY_FLAG);
|
||||||
@@ -237,6 +241,8 @@ void GameBoy::andBitwise(T& dest, T src) {
|
|||||||
|
|
||||||
if (dest == 0)
|
if (dest == 0)
|
||||||
setFlag(ZERO_FLAG);
|
setFlag(ZERO_FLAG);
|
||||||
|
else
|
||||||
|
resetFlag(ZERO_FLAG);
|
||||||
|
|
||||||
resetFlag(SUBTRACT_FLAG);
|
resetFlag(SUBTRACT_FLAG);
|
||||||
setFlag(HALFCARRY_FLAG);
|
setFlag(HALFCARRY_FLAG);
|
||||||
@@ -249,6 +255,8 @@ void GameBoy::xorBitwise(T& dest, T src) {
|
|||||||
|
|
||||||
if (dest == 0)
|
if (dest == 0)
|
||||||
setFlag(ZERO_FLAG);
|
setFlag(ZERO_FLAG);
|
||||||
|
else
|
||||||
|
resetFlag(ZERO_FLAG);
|
||||||
|
|
||||||
resetFlag(SUBTRACT_FLAG);
|
resetFlag(SUBTRACT_FLAG);
|
||||||
resetFlag(CARRY_FLAG);
|
resetFlag(CARRY_FLAG);
|
||||||
@@ -473,7 +481,7 @@ void GameBoy::rr(Byte& reg) {
|
|||||||
reg >>= 1;
|
reg >>= 1;
|
||||||
|
|
||||||
if (getFlag(CARRY_FLAG))
|
if (getFlag(CARRY_FLAG))
|
||||||
AF.hi |= 0x80;
|
reg |= 0x80;
|
||||||
|
|
||||||
if (lsb)
|
if (lsb)
|
||||||
setFlag(CARRY_FLAG);
|
setFlag(CARRY_FLAG);
|
||||||
@@ -628,6 +636,7 @@ void GameBoy::srl(Byte& reg) {
|
|||||||
template <typename T>
|
template <typename T>
|
||||||
void GameBoy::pop(T& reg) {
|
void GameBoy::pop(T& reg) {
|
||||||
reg = getWordSP();
|
reg = getWordSP();
|
||||||
|
AF.reg &= 0xFFF0;
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
@@ -636,9 +645,9 @@ void GameBoy::push(T reg) {
|
|||||||
RegisterPair temp = {0};
|
RegisterPair temp = {0};
|
||||||
temp.lo = reg & 0xFF;
|
temp.lo = reg & 0xFF;
|
||||||
temp.hi = reg >> 8;
|
temp.hi = reg >> 8;
|
||||||
SP--;
|
SP -= 1;
|
||||||
addressSpace[SP] = temp.hi;
|
addressSpace[SP] = temp.hi;
|
||||||
SP--;
|
SP -= 1;
|
||||||
addressSpace[SP] = temp.lo;
|
addressSpace[SP] = temp.lo;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -678,9 +687,9 @@ void GameBoy::ccf() {
|
|||||||
void GameBoy::stop() {}
|
void GameBoy::stop() {}
|
||||||
|
|
||||||
void GameBoy::opcodeResolver() {
|
void GameBoy::opcodeResolver() {
|
||||||
if (addressSpace[PC] != 0xCB) {
|
if (readOnlyAddressSpace[PC] != 0xCB) {
|
||||||
bool jumped;
|
bool jumped;
|
||||||
switch (addressSpace[PC]) {
|
switch (readOnlyAddressSpace[PC]) {
|
||||||
case 0x00:
|
case 0x00:
|
||||||
//NOP
|
//NOP
|
||||||
PC += 1;
|
PC += 1;
|
||||||
@@ -742,7 +751,7 @@ void GameBoy::opcodeResolver() {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case 0x0A:
|
case 0x0A:
|
||||||
ld(AF.hi, addressSpace[BC.reg]);
|
ld(AF.hi, readOnlyAddressSpace[BC.reg]);
|
||||||
PC += 1;
|
PC += 1;
|
||||||
addCycles(8);
|
addCycles(8);
|
||||||
break;
|
break;
|
||||||
@@ -790,13 +799,13 @@ void GameBoy::opcodeResolver() {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case 0x12:
|
case 0x12:
|
||||||
ld(addressSpace[BC.reg], AF.hi);
|
ld(addressSpace[DE.reg], AF.hi);
|
||||||
PC += 1;
|
PC += 1;
|
||||||
addCycles(8);
|
addCycles(8);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 0x13:
|
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;
|
PC += 1;
|
||||||
addCycles(8);
|
addCycles(8);
|
||||||
break;
|
break;
|
||||||
@@ -837,7 +846,7 @@ void GameBoy::opcodeResolver() {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case 0x1A:
|
case 0x1A:
|
||||||
ld(AF.hi, addressSpace[DE.reg]);
|
ld(AF.hi, readOnlyAddressSpace[DE.reg]);
|
||||||
PC += 1;
|
PC += 1;
|
||||||
addCycles(8);
|
addCycles(8);
|
||||||
break;
|
break;
|
||||||
@@ -891,7 +900,7 @@ void GameBoy::opcodeResolver() {
|
|||||||
|
|
||||||
case 0x22:
|
case 0x22:
|
||||||
ld(addressSpace[HL.reg], AF.hi);
|
ld(addressSpace[HL.reg], AF.hi);
|
||||||
HL.reg++;
|
HL.reg += 1;
|
||||||
PC += 1;
|
PC += 1;
|
||||||
addCycles(8);
|
addCycles(8);
|
||||||
break;
|
break;
|
||||||
@@ -944,7 +953,7 @@ void GameBoy::opcodeResolver() {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case 0x2A:
|
case 0x2A:
|
||||||
ld(AF.hi, addressSpace[HL.reg]);
|
ld(AF.hi, readOnlyAddressSpace[HL.reg]);
|
||||||
HL.reg += 1;
|
HL.reg += 1;
|
||||||
PC += 1;
|
PC += 1;
|
||||||
addCycles(8);
|
addCycles(8);
|
||||||
@@ -963,7 +972,7 @@ void GameBoy::opcodeResolver() {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case 0x2D:
|
case 0x2D:
|
||||||
dec(HL.hi);
|
dec(HL.lo);
|
||||||
PC += 1;
|
PC += 1;
|
||||||
addCycles(4);
|
addCycles(4);
|
||||||
break;
|
break;
|
||||||
@@ -999,7 +1008,7 @@ void GameBoy::opcodeResolver() {
|
|||||||
|
|
||||||
case 0x32:
|
case 0x32:
|
||||||
ld(addressSpace[HL.reg], AF.hi);
|
ld(addressSpace[HL.reg], AF.hi);
|
||||||
HL.reg--;
|
HL.reg -= 1;
|
||||||
PC += 1;
|
PC += 1;
|
||||||
addCycles(8);
|
addCycles(8);
|
||||||
break;
|
break;
|
||||||
@@ -1052,14 +1061,14 @@ void GameBoy::opcodeResolver() {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case 0x3A:
|
case 0x3A:
|
||||||
ld(AF.hi, addressSpace[HL.reg]);
|
ld(AF.hi, readOnlyAddressSpace[HL.reg]);
|
||||||
HL.reg -= 1;
|
HL.reg -= 1;
|
||||||
PC += 1;
|
PC += 1;
|
||||||
addCycles(8);
|
addCycles(8);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 0x3B:
|
case 0x3B:
|
||||||
SP--;
|
SP -= 1;
|
||||||
PC += 1;
|
PC += 1;
|
||||||
addCycles(8);
|
addCycles(8);
|
||||||
break;
|
break;
|
||||||
@@ -1125,7 +1134,7 @@ void GameBoy::opcodeResolver() {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case 0x46:
|
case 0x46:
|
||||||
ld(BC.hi, addressSpace[HL.reg]);
|
ld(BC.hi, readOnlyAddressSpace[HL.reg]);
|
||||||
PC += 1;
|
PC += 1;
|
||||||
addCycles(8);
|
addCycles(8);
|
||||||
break;
|
break;
|
||||||
@@ -1173,7 +1182,7 @@ void GameBoy::opcodeResolver() {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case 0x4E:
|
case 0x4E:
|
||||||
ld(BC.lo, addressSpace[HL.reg]);
|
ld(BC.lo, readOnlyAddressSpace[HL.reg]);
|
||||||
PC += 1;
|
PC += 1;
|
||||||
addCycles(8);
|
addCycles(8);
|
||||||
break;
|
break;
|
||||||
@@ -1221,7 +1230,7 @@ void GameBoy::opcodeResolver() {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case 0x56:
|
case 0x56:
|
||||||
ld(DE.hi, addressSpace[HL.reg]);
|
ld(DE.hi, readOnlyAddressSpace[HL.reg]);
|
||||||
PC += 1;
|
PC += 1;
|
||||||
addCycles(8);
|
addCycles(8);
|
||||||
break;
|
break;
|
||||||
@@ -1269,7 +1278,7 @@ void GameBoy::opcodeResolver() {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case 0x5E:
|
case 0x5E:
|
||||||
ld(DE.lo, addressSpace[HL.reg]);
|
ld(DE.lo, readOnlyAddressSpace[HL.reg]);
|
||||||
PC += 1;
|
PC += 1;
|
||||||
addCycles(8);
|
addCycles(8);
|
||||||
break;
|
break;
|
||||||
@@ -1317,7 +1326,7 @@ void GameBoy::opcodeResolver() {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case 0x66:
|
case 0x66:
|
||||||
ld(HL.hi, addressSpace[HL.reg]);
|
ld(HL.hi, readOnlyAddressSpace[HL.reg]);
|
||||||
PC += 1;
|
PC += 1;
|
||||||
addCycles(8);
|
addCycles(8);
|
||||||
break;
|
break;
|
||||||
@@ -1365,7 +1374,7 @@ void GameBoy::opcodeResolver() {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case 0x6E:
|
case 0x6E:
|
||||||
ld(HL.lo, addressSpace[HL.reg]);
|
ld(HL.lo, readOnlyAddressSpace[HL.reg]);
|
||||||
PC += 1;
|
PC += 1;
|
||||||
addCycles(8);
|
addCycles(8);
|
||||||
break;
|
break;
|
||||||
@@ -1461,7 +1470,7 @@ void GameBoy::opcodeResolver() {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case 0x7E:
|
case 0x7E:
|
||||||
ld(AF.hi, addressSpace[HL.reg]);
|
ld(AF.hi, readOnlyAddressSpace[HL.reg]);
|
||||||
PC += 1;
|
PC += 1;
|
||||||
addCycles(8);
|
addCycles(8);
|
||||||
break;
|
break;
|
||||||
@@ -1509,7 +1518,7 @@ void GameBoy::opcodeResolver() {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case 0x86:
|
case 0x86:
|
||||||
add(AF.hi, addressSpace[HL.reg]);
|
add(AF.hi, readOnlyAddressSpace[HL.reg]);
|
||||||
PC += 1;
|
PC += 1;
|
||||||
addCycles(8);
|
addCycles(8);
|
||||||
break;
|
break;
|
||||||
@@ -1557,7 +1566,7 @@ void GameBoy::opcodeResolver() {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case 0x8E:
|
case 0x8E:
|
||||||
adc(AF.hi, addressSpace[HL.reg]);
|
adc(AF.hi, readOnlyAddressSpace[HL.reg]);
|
||||||
PC += 1;
|
PC += 1;
|
||||||
addCycles(8);
|
addCycles(8);
|
||||||
break;
|
break;
|
||||||
@@ -1605,7 +1614,7 @@ void GameBoy::opcodeResolver() {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case 0x96:
|
case 0x96:
|
||||||
sub(addressSpace[HL.reg]);
|
sub(readOnlyAddressSpace[HL.reg]);
|
||||||
PC += 1;
|
PC += 1;
|
||||||
addCycles(8);
|
addCycles(8);
|
||||||
break;
|
break;
|
||||||
@@ -1653,7 +1662,7 @@ void GameBoy::opcodeResolver() {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case 0x9E:
|
case 0x9E:
|
||||||
sbc(addressSpace[HL.reg]);
|
sbc(readOnlyAddressSpace[HL.reg]);
|
||||||
PC += 1;
|
PC += 1;
|
||||||
addCycles(8);
|
addCycles(8);
|
||||||
break;
|
break;
|
||||||
@@ -1701,7 +1710,7 @@ void GameBoy::opcodeResolver() {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case 0xA6:
|
case 0xA6:
|
||||||
andBitwise(AF.hi, addressSpace[HL.reg]);
|
andBitwise(AF.hi, readOnlyAddressSpace[HL.reg]);
|
||||||
PC += 1;
|
PC += 1;
|
||||||
addCycles(8);
|
addCycles(8);
|
||||||
break;
|
break;
|
||||||
@@ -1749,7 +1758,7 @@ void GameBoy::opcodeResolver() {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case 0xAE:
|
case 0xAE:
|
||||||
xorBitwise(AF.hi, addressSpace[HL.reg]);
|
xorBitwise(AF.hi, readOnlyAddressSpace[HL.reg]);
|
||||||
PC += 1;
|
PC += 1;
|
||||||
addCycles(8);
|
addCycles(8);
|
||||||
break;
|
break;
|
||||||
@@ -1797,7 +1806,7 @@ void GameBoy::opcodeResolver() {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case 0xB6:
|
case 0xB6:
|
||||||
orBitwise(AF.hi, addressSpace[HL.reg]);
|
orBitwise(AF.hi, readOnlyAddressSpace[HL.reg]);
|
||||||
PC += 1;
|
PC += 1;
|
||||||
addCycles(8);
|
addCycles(8);
|
||||||
break;
|
break;
|
||||||
@@ -1845,7 +1854,7 @@ void GameBoy::opcodeResolver() {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case 0xBE:
|
case 0xBE:
|
||||||
cp(addressSpace[HL.reg]);
|
cp(readOnlyAddressSpace[HL.reg]);
|
||||||
PC += 1;
|
PC += 1;
|
||||||
addCycles(8);
|
addCycles(8);
|
||||||
break;
|
break;
|
||||||
@@ -2091,7 +2100,7 @@ void GameBoy::opcodeResolver() {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case 0xE2:
|
case 0xE2:
|
||||||
ld(addressSpace[BC.lo + 0xFF00], AF.hi);
|
ld(addressSpace[0xFF00 + BC.lo], AF.hi);
|
||||||
PC += 1;
|
PC += 1;
|
||||||
addCycles(8);
|
addCycles(8);
|
||||||
break;
|
break;
|
||||||
@@ -2115,20 +2124,19 @@ void GameBoy::opcodeResolver() {
|
|||||||
|
|
||||||
case 0xE8:
|
case 0xE8:
|
||||||
{
|
{
|
||||||
const auto immediate = static_cast<int8_t>(getBytePC());
|
const int16_t immediate = static_cast<int8_t>(getBytePC());
|
||||||
const Word result = SP + static_cast<int16_t>(immediate);
|
|
||||||
|
|
||||||
if (((SP ^ immediate ^ result) & 0x10) != 0)
|
if ((SP & 0xF) + (immediate & 0xF) > 0xF)
|
||||||
setFlag(HALFCARRY_FLAG);
|
setFlag(HALFCARRY_FLAG);
|
||||||
else
|
else
|
||||||
resetFlag(HALFCARRY_FLAG);
|
resetFlag(HALFCARRY_FLAG);
|
||||||
|
|
||||||
if (((SP ^ immediate ^ result) & 0x100) != 0)
|
if ((SP & 0xFF) + (immediate & 0xFF) > 0xFF)
|
||||||
setFlag(CARRY_FLAG);
|
setFlag(CARRY_FLAG);
|
||||||
else
|
else
|
||||||
resetFlag(CARRY_FLAG);
|
resetFlag(CARRY_FLAG);
|
||||||
|
|
||||||
SP = result;
|
SP += immediate;
|
||||||
|
|
||||||
resetFlag(ZERO_FLAG);
|
resetFlag(ZERO_FLAG);
|
||||||
resetFlag(SUBTRACT_FLAG);
|
resetFlag(SUBTRACT_FLAG);
|
||||||
@@ -2161,7 +2169,7 @@ void GameBoy::opcodeResolver() {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case 0xF0:
|
case 0xF0:
|
||||||
ld(AF.hi, addressSpace[0xFF00 + getBytePC()]);
|
ld(AF.hi, readOnlyAddressSpace[0xFF00 + getBytePC()]);
|
||||||
PC += 2;
|
PC += 2;
|
||||||
addCycles(12);
|
addCycles(12);
|
||||||
break;
|
break;
|
||||||
@@ -2173,7 +2181,7 @@ void GameBoy::opcodeResolver() {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case 0xF2:
|
case 0xF2:
|
||||||
ld(AF.hi, addressSpace[0xFF00 + BC.lo]);
|
ld(AF.hi, readOnlyAddressSpace[0xFF00 + BC.lo]);
|
||||||
PC += 1;
|
PC += 1;
|
||||||
addCycles(12);
|
addCycles(12);
|
||||||
break;
|
break;
|
||||||
@@ -2203,20 +2211,20 @@ void GameBoy::opcodeResolver() {
|
|||||||
|
|
||||||
case 0xF8:
|
case 0xF8:
|
||||||
{
|
{
|
||||||
const auto n = static_cast<int8_t>(getBytePC());
|
const int16_t immediate = static_cast<int8_t>(getBytePC());
|
||||||
const Word result = SP + n;
|
HL.reg = SP + immediate;
|
||||||
|
|
||||||
//halfcarry test https://robdor.com/2016/08/10/gameboy-emulator-half-carry-flag/
|
if ((SP & 0xF) + (immediate & 0xF) > 0xF)
|
||||||
if ((((result & 0xF) + (HL.reg & 0xF)) & 0x10) == 0x10)
|
|
||||||
setFlag(HALFCARRY_FLAG);
|
setFlag(HALFCARRY_FLAG);
|
||||||
else
|
else
|
||||||
resetFlag(HALFCARRY_FLAG);
|
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);
|
setFlag(CARRY_FLAG);
|
||||||
else
|
else
|
||||||
resetFlag(CARRY_FLAG);
|
resetFlag(CARRY_FLAG);
|
||||||
HL.reg = result; // Load the result into HL
|
|
||||||
|
|
||||||
resetFlag(ZERO_FLAG);
|
resetFlag(ZERO_FLAG);
|
||||||
resetFlag(SUBTRACT_FLAG);
|
resetFlag(SUBTRACT_FLAG);
|
||||||
@@ -2233,7 +2241,7 @@ void GameBoy::opcodeResolver() {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case 0xFA:
|
case 0xFA:
|
||||||
ld(AF.hi, addressSpace[getWordPC()]);
|
ld(AF.hi, readOnlyAddressSpace[getWordPC()]);
|
||||||
PC += 3;
|
PC += 3;
|
||||||
addCycles(16);
|
addCycles(16);
|
||||||
break;
|
break;
|
||||||
|
|||||||
68
src/ppu.cpp
68
src/ppu.cpp
@@ -8,25 +8,28 @@ void GameBoy::ppuUpdate() {
|
|||||||
//test for HBlank
|
//test for HBlank
|
||||||
checkPPUMode();
|
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
|
// Request STAT interrupt if LY matches LYC
|
||||||
// bug on DMG models triggers a STAT interrupt anytime the STAT register is written
|
// bug on DMG models triggers a STAT interrupt anytime the STAT register is written
|
||||||
// Road Rage and Zerd no Denetsu rely on this
|
// Road Rage and Zerd no Denetsu rely on this
|
||||||
(*STAT) |= (1 << 2);
|
addressSpace.memoryLayout.STAT |= (1 << 2);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
(*STAT) &= ~(1 << 2);
|
addressSpace.memoryLayout.STAT &= ~(1 << 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for STAT interrupts and request if needed (e.g., when entering specific modes)
|
// Check for STAT interrupts and request if needed (e.g., when entering specific modes)
|
||||||
bool hBlankInterruptEnabled = (*STAT) & (1 << 3);
|
bool hBlankInterruptEnabled = readOnlyAddressSpace.memoryLayout.STAT & (1 << 3);
|
||||||
bool vBlankInterruptEnabled = (*STAT) & (1 << 4); /* Determine if VBlank interrupt is enabled */
|
bool vBlankInterruptEnabled = readOnlyAddressSpace.memoryLayout.STAT & (1 << 4);
|
||||||
bool oamInterruptEnabled = (*STAT) & (1 << 5); /* Determine if OAM Search interrupt is enabled */
|
/* 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 ||
|
if (currentMode == PPUMode::mode0 && hBlankInterruptEnabled ||
|
||||||
currentMode == PPUMode::mode1 && vBlankInterruptEnabled ||
|
currentMode == PPUMode::mode1 && vBlankInterruptEnabled ||
|
||||||
currentMode == PPUMode::mode2 && oamInterruptEnabled) {
|
currentMode == PPUMode::mode2 && oamInterruptEnabled) {
|
||||||
*IF |= 0x2;
|
addressSpace.memoryLayout.IF |= 0x2;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -58,16 +61,16 @@ void GameBoy::checkPPUMode() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void GameBoy::incLY() {
|
void GameBoy::incLY() {
|
||||||
(*LY)++;
|
addressSpace.memoryLayout.LY += 1;
|
||||||
setPPUMode(PPUMode::mode2);
|
setPPUMode(PPUMode::mode2);
|
||||||
if ((*LY) > SCANLINES_PER_FRAME - 1) {
|
if (addressSpace.memoryLayout.LY > SCANLINES_PER_FRAME - 1) {
|
||||||
(*LY) = 0;
|
addressSpace.memoryLayout.LY = 0;
|
||||||
}
|
}
|
||||||
else if ((*LY) == 144) {
|
else if (addressSpace.memoryLayout.LY == 144) {
|
||||||
// VBlank Period
|
// VBlank Period
|
||||||
SDL2present();
|
SDL2present();
|
||||||
setPPUMode(PPUMode::mode1);
|
setPPUMode(PPUMode::mode1);
|
||||||
*IF |= 0x1;
|
addressSpace.memoryLayout.IF |= 0x1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -84,31 +87,31 @@ uint64_t GameBoy::cyclesSinceLastRefresh() const {
|
|||||||
void GameBoy::setPPUMode(const PPUMode mode) {
|
void GameBoy::setPPUMode(const PPUMode mode) {
|
||||||
switch (mode) {
|
switch (mode) {
|
||||||
case PPUMode::mode0:
|
case PPUMode::mode0:
|
||||||
(*STAT) &= ~0x03;
|
addressSpace.memoryLayout.STAT &= ~0x03;
|
||||||
currentMode = PPUMode::mode0;
|
currentMode = PPUMode::mode0;
|
||||||
break;
|
break;
|
||||||
case PPUMode::mode1:
|
case PPUMode::mode1:
|
||||||
(*STAT) &= ~0x03;
|
addressSpace.memoryLayout.STAT &= ~0x03;
|
||||||
(*STAT) |= 0x01;
|
addressSpace.memoryLayout.STAT |= 0x01;
|
||||||
currentMode = PPUMode::mode1;
|
currentMode = PPUMode::mode1;
|
||||||
break;
|
break;
|
||||||
case PPUMode::mode2:
|
case PPUMode::mode2:
|
||||||
(*STAT) &= ~0x03;
|
addressSpace.memoryLayout.STAT &= ~0x03;
|
||||||
(*STAT) |= 0x02;
|
addressSpace.memoryLayout.STAT |= 0x02;
|
||||||
currentMode = PPUMode::mode2;
|
currentMode = PPUMode::mode2;
|
||||||
break;
|
break;
|
||||||
case PPUMode::mode3:
|
case PPUMode::mode3:
|
||||||
(*STAT) &= ~0x03;
|
addressSpace.memoryLayout.STAT &= ~0x03;
|
||||||
(*STAT) |= 0x03;
|
addressSpace.memoryLayout.STAT |= 0x03;
|
||||||
currentMode = PPUMode::mode3;
|
currentMode = PPUMode::mode3;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
//7th bit is unused but always set
|
//7th bit is unused but always set
|
||||||
(*STAT) |= 0x80;
|
addressSpace.memoryLayout.STAT |= 0x80;
|
||||||
}
|
}
|
||||||
|
|
||||||
void GameBoy::drawLine() {
|
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
|
// Calculate the starting index of the current scanline in the framebuffer
|
||||||
const uint32_t lineStartIndex = line * RESOLUTION_X;
|
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
|
// Pointer to the current line's pixel data in the framebuffer
|
||||||
uint32_t* currentLinePixels = framebuffer + lineStartIndex;
|
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.
|
std::fill_n(currentLinePixels, RESOLUTION_X, 0xFFFFFFFF); // Fill line with white if BG display is off.
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const uint16_t backgroundMapAddr = (*LCDC & 0x08) ? 0x9C00 : 0x9800;
|
const uint16_t backgroundMapAddr = (readOnlyAddressSpace.memoryLayout.LCDC & 0x08) ? 0x9C00 : 0x9800;
|
||||||
const uint16_t tileDataTableAddr = (*LCDC & 0x10) ? 0x8000 : 0x8800;
|
const uint16_t tileDataTableAddr = (readOnlyAddressSpace.memoryLayout.LCDC & 0x10) ? 0x8000 : 0x8800;
|
||||||
const bool signedIndex = !(*LCDC & 0x10);
|
const bool signedIndex = !(readOnlyAddressSpace.memoryLayout.LCDC & 0x10);
|
||||||
|
|
||||||
for (int pixel = 0; pixel < RESOLUTION_X; pixel++) {
|
for (int pixel = 0; pixel < RESOLUTION_X; pixel++) {
|
||||||
const uint8_t xPos = (pixel + (*SCX)) % 256; // 256 pixels in total BG width
|
const uint8_t xPos = (pixel + readOnlyAddressSpace.memoryLayout.SCX) % 256; // 256 pixels in total BG width
|
||||||
const uint8_t yPos = (line + (*SCY)) % 256; // 256 pixels in total BG height
|
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 tileRow = (yPos / 8) * 32;
|
||||||
const uint16_t tileCol = xPos / 8;
|
const uint16_t tileCol = xPos / 8;
|
||||||
const uint16_t tileIndex = tileRow + tileCol;
|
const uint16_t tileIndex = tileRow + tileCol;
|
||||||
|
|
||||||
const uint16_t tileAddr = backgroundMapAddr + tileIndex;
|
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;
|
uint16_t tileDataAddr;
|
||||||
if (signedIndex) {
|
if (signedIndex) {
|
||||||
@@ -145,14 +150,14 @@ void GameBoy::drawLine() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const uint8_t lineOffset = yPos % 8;
|
const uint8_t lineOffset = yPos % 8;
|
||||||
const uint8_t tileRowData1 = addressSpace[tileDataAddr + (lineOffset * 2)];
|
const uint8_t tileRowData1 = readOnlyAddressSpace[tileDataAddr + (lineOffset * 2)];
|
||||||
const uint8_t tileRowData2 = addressSpace[tileDataAddr + (lineOffset * 2) + 1];
|
const uint8_t tileRowData2 = readOnlyAddressSpace[tileDataAddr + (lineOffset * 2) + 1];
|
||||||
|
|
||||||
const uint8_t colourBit = 7 - (xPos % 8);
|
const uint8_t colourBit = 7 - (xPos % 8);
|
||||||
const uint8_t colourNum = ((tileRowData2 >> colourBit) & 0x1) << 1 | ((tileRowData1 >> colourBit) & 0x1);
|
const uint8_t colourNum = ((tileRowData2 >> colourBit) & 0x1) << 1 | ((tileRowData1 >> colourBit) & 0x1);
|
||||||
|
|
||||||
// Apply the BGP register for palette mapping
|
// 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)
|
if (xPos == 0)
|
||||||
palette = 0x3;
|
palette = 0x3;
|
||||||
switch (palette) {
|
switch (palette) {
|
||||||
@@ -198,7 +203,6 @@ void GameBoy::SDL2present() {
|
|||||||
|
|
||||||
|
|
||||||
frameTime = SDL_GetTicks() - frameStart;
|
frameTime = SDL_GetTicks() - frameStart;
|
||||||
std::cout << SDL_GetTicks() << " " << frameTime << std::endl;
|
|
||||||
|
|
||||||
if (frameDelay > frameTime) {
|
if (frameDelay > frameTime) {
|
||||||
SDL_Delay(frameDelay - frameTime);
|
SDL_Delay(frameDelay - frameTime);
|
||||||
|
|||||||
@@ -4,9 +4,7 @@
|
|||||||
void GameBoy::timingHandler() {
|
void GameBoy::timingHandler() {
|
||||||
if (cycles - lastDivUpdate >= DIVIDER_REGISTER_FREQ) {
|
if (cycles - lastDivUpdate >= DIVIDER_REGISTER_FREQ) {
|
||||||
const uint8_t increments = (cycles - lastDivUpdate) / DIVIDER_REGISTER_FREQ;
|
const uint8_t increments = (cycles - lastDivUpdate) / DIVIDER_REGISTER_FREQ;
|
||||||
|
addressSpace.memoryLayout.DIV += increments;
|
||||||
(*DIV) += increments;
|
|
||||||
|
|
||||||
lastDivUpdate += increments * DIVIDER_REGISTER_FREQ;
|
lastDivUpdate += increments * DIVIDER_REGISTER_FREQ;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user