Compare commits

...

7 Commits

Author SHA1 Message Date
c242fb7574 enums and RTC work 2026-03-18 01:09:03 -07:00
80e23312de more MBC3 work 2026-03-17 01:46:28 -07:00
037ba0c76d MBC3 work 2026-03-16 23:44:38 -07:00
a8ed60259a resizable window 2026-03-16 22:38:31 -07:00
89e7891249 MBC2 working 2026-03-16 02:50:02 -07:00
3bd1ef34d8 formatting 2026-03-13 18:07:43 -07:00
712ee55e7b start of mbc3 2026-01-30 16:18:01 -08:00
15 changed files with 5074 additions and 5047 deletions

View File

@@ -19,7 +19,11 @@ add_executable(GameBoy++ src/main.cpp
src/testing.hpp src/testing.hpp
src/joypad.cpp src/joypad.cpp
) )
target_compile_options(GameBoy++ PRIVATE -O2)
target_link_libraries(GameBoy++ ${SDL2_LIBRARIES}) target_link_libraries(GameBoy++ ${SDL2_LIBRARIES})
target_compile_options(GameBoy++ PRIVATE
$<$<CONFIG:Release>:-O2>
)
if(CMAKE_EXPORT_COMPILE_COMMANDS) if(CMAKE_EXPORT_COMPILE_COMMANDS)
add_custom_target(copy_compile_commands ALL add_custom_target(copy_compile_commands ALL

View File

@@ -2,66 +2,58 @@
#include <iostream> #include <iostream>
#include <iterator> #include <iterator>
bool AddressSpace::getBootromState() const { bool AddressSpace::getBootromState() const { return bootromLoaded; }
return bootromLoaded;
void AddressSpace::unmapBootrom() { bootromLoaded = false; }
void AddressSpace::mapBootrom() { bootromLoaded = true; }
void AddressSpace::loadBootrom(const std::string &filename) {
std::ifstream file;
file.open(filename, std::ios::binary);
if (!file.is_open()) {
std::cerr << "Bootrom was not found!\nQuitting!\n" << std::endl;
exit(1);
}
const uintmax_t size = std::filesystem::file_size(filename);
if (size != 256) {
std::cerr << "Bootrom was an unexpected size!\nQuitting!\n" << std::endl;
exit(1);
}
file.read(reinterpret_cast<char *>(bootrom), BOOTROM_SIZE);
} }
void AddressSpace::unmapBootrom() { void AddressSpace::loadGame(const std::string &filename) {
bootromLoaded = false; std::ifstream rom(filename, std::ios::binary);
} rom.unsetf(std::ios::skipws);
void AddressSpace::mapBootrom() { if (!rom.is_open()) {
bootromLoaded = true; std::cerr << "Game was not found!\nQuitting!\n" << std::endl;
} exit(1);
}
void AddressSpace::loadBootrom(const std::string& filename) { rom.seekg(0, std::ios::end);
std::ifstream file; const std::streampos rom_size = rom.tellg();
file.open(filename, std::ios::binary); rom.seekg(0, std::ios::beg);
if (!file.is_open()) { game.reserve(rom_size);
std::cerr << "Bootrom was not found!\nQuitting!\n" << std::endl; game.insert(game.begin(), std::istream_iterator<Byte>(rom),
exit(1); std::istream_iterator<Byte>());
}
const uintmax_t size = std::filesystem::file_size(filename); memoryLayout.romBank0 = game.data();
if (size != 256) { memoryLayout.romBankSwitch = game.data() + ROM_BANK_SIZE;
std::cerr << "Bootrom was an unexpected size!\nQuitting!\n" << std::endl;
exit(1);
}
file.read(reinterpret_cast<char*>(bootrom), BOOTROM_SIZE);
}
void AddressSpace::loadGame(const std::string& filename) {
std::ifstream rom(filename, std::ios::binary);
rom.unsetf(std::ios::skipws);
if (!rom.is_open()) {
std::cerr << "Game was not found!\nQuitting!\n" << std::endl;
exit(1);
}
rom.seekg(0, std::ios::end);
const std::streampos rom_size = rom.tellg();
rom.seekg(0, std::ios::beg);
game.reserve(rom_size);
game.insert(game.begin(),
std::istream_iterator<Byte>(rom),
std::istream_iterator<Byte>());
memoryLayout.romBank0 = game.data();
memoryLayout.romBankSwitch = game.data() + ROM_BANK_SIZE;
} }
void AddressSpace::dmaTransfer() { void AddressSpace::dmaTransfer() {
dmaTransferRequested = false; dmaTransferRequested = false;
const Word addr = memoryLayout.DMA << 8; const Word addr = memoryLayout.DMA << 8;
for (int i = 0; i < 0xA0; i++) { for (int i = 0; i < 0xA0; i++) {
const Byte data = (*this)[addr + i];; const Byte data = (*this)[addr + i];
(*this)[0xFE00 + i] = data; ;
} (*this)[0xFE00 + i] = data;
}
} }
void AddressSpace::setTesting(const bool state) { void AddressSpace::setTesting(const bool state) { testing = state; }
testing = state;
}

View File

@@ -1,383 +1,410 @@
#ifndef ADDRESSSPACE_HPP #ifndef ADDRESSSPACE_HPP
#define ADDRESSSPACE_HPP #define ADDRESSSPACE_HPP
#include <fstream>
#include <filesystem>
#include <cstdint> #include <cstdint>
#include <cstring> #include <cstring>
#include <filesystem>
#include <fstream>
#include <string> #include <string>
#include <vector> #include <vector>
#include "defines.hpp" #include "defines.hpp"
class AddressSpace { class AddressSpace {
bool bootromLoaded = true; bool bootromLoaded = true;
Byte bootrom[BOOTROM_SIZE] = {0}; Byte bootrom[BOOTROM_SIZE] = {0};
std::vector<Byte> game; std::vector<Byte> game;
bool testing; bool testing;
Byte testRam[0xFFFF]; Byte testRam[0xFFFF];
Byte* cartridgeRam = nullptr; Byte *cartridgeRam = nullptr;
public: public:
AddressSpace() { AddressSpace() {
// Initialize the memory to zero // Initialize the memory to zero
memoryLayout = {}; memoryLayout = {};
} }
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 oam[0xA0]; //Mapped to 0xFE00 Byte oam[0xA0]; // Mapped to 0xFE00
Byte notUsable[0x60]; //Mapped to 0xFEA0 Byte notUsable[0x60]; // Mapped to 0xFEA0
//General purpose hardware registers // General purpose hardware registers
Byte JOYP = 0xCF; Byte JOYP = 0xCF;
Byte SB; Byte SB;
Byte SC = 0x7E; Byte SC = 0x7E;
Byte DIV; Byte DIV;
//Timer registers // Timer registers
Byte TIMA; Byte TIMA;
Byte TMA; Byte TMA;
Byte TAC = 0xF8; Byte TAC = 0xF8;
//interrupt flag and enable // interrupt flag and enable
Byte IF = 0xE1;; Byte IF = 0xE1;
//Sound registers // Sound registers
Byte NR10; Byte NR10;
Byte NR11; Byte NR11;
Byte NR12; Byte NR12;
Byte NR13; Byte NR13;
Byte NR14; Byte NR14;
Byte NR20; //not used Byte NR20; // not used
Byte NR21; Byte NR21;
Byte NR22; Byte NR22;
Byte NR23; Byte NR23;
Byte NR24; Byte NR24;
Byte NR30; Byte NR30;
Byte NR31; Byte NR31;
Byte NR32; Byte NR32;
Byte NR33; Byte NR33;
Byte NR34; Byte NR34;
Byte NR40; //unused Byte NR40; // unused
Byte NR41; Byte NR41;
Byte NR42; Byte NR42;
Byte NR43; Byte NR43;
Byte NR44; Byte NR44;
Byte NR50; Byte NR50;
Byte NR51; Byte NR51;
Byte NR52; Byte NR52;
Byte waveRam[0x10]; Byte waveRam[0x10];
//PPU registers // PPU registers
Byte LCDC; Byte LCDC;
Byte STAT; Byte STAT;
Byte SCY; Byte SCY;
Byte SCX; Byte SCX;
Byte LY; Byte LY;
Byte LYC; Byte LYC;
Byte DMA; Byte DMA;
Byte BGP; Byte BGP;
Byte OBP0; Byte OBP0;
Byte OBP1; Byte OBP1;
Byte WY; Byte WY;
Byte WX; Byte WX;
Byte specialRam[0x7F]; //Mapped to 0xFF80 Byte specialRam[0x7F]; // Mapped to 0xFF80
Byte IE; // Mapped to 0xFFFF Byte IE; // Mapped to 0xFFFF
} memoryLayout{}; } memoryLayout{};
void unmapBootrom(); void unmapBootrom();
void mapBootrom(); void mapBootrom();
bool getBootromState() const; bool getBootromState() const;
void loadBootrom(const std::string& filename); void loadBootrom(const std::string &filename);
void loadGame(const std::string& filename); void loadGame(const std::string &filename);
void determineMBCInfo(); void determineMBCInfo();
static bool testMBCWrite(Word address); Byte *MBCWrite(Word address);
Byte* MBCRead(Word address); // prevents seg faults when programs with no MBC try to write to ROM
//prevents seg faults when programs with no MBC try to write to ROM Byte dummyVal = 0;
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;
bool dmaTransferRequested = false; // called after every op
void dmaTransfer(); void MBCUpdate();
//Selected ROM Bank = (Secondary Bank << 5) + ROM Bank void loadRomBank();
Byte selectedRomBank = 0; void createRamBank();
Byte romBankRegister = 0x00; void loadRamBank();
//2 bit register acts as secondary rom bank register or ram bank number MBCType_enum MBC = {};
Byte twoBitBankRegister = 0x0; uint32_t romSize = 0;
Byte selectedExternalRamBank = 0; uint32_t romBanks = 0;
Byte romRamSelect = 0x00; uint32_t externalRamSize = 0;
Byte ramEnable = 0x00; uint32_t externalRamBanks = 0;
//MBC3
Byte latchClockData = 0x00;
Byte ramBankRTCRegister = 0x00;
void setTesting(bool state); bool dmaTransferRequested = false;
void dmaTransfer();
//read // MBC registers
Byte operator[](const Word address) const { // Selected ROM Bank in MBC1 = (Secondary Bank << 5) + ROM Bank
if (testing) Byte selectedRomBank = 0;
return testRam[address]; Byte romBankRegister = 0x00;
if (address < 0x0100 && bootromLoaded) // 2 bit register acts as secondary rom bank register or ram bank number in
return bootrom[address]; // MBC1
if (address < 0x4000) Byte twoBitBankRegister = 0x0;
return memoryLayout.romBank0[address]; Byte selectedExternalRamBank = 0;
if (address < 0x8000) Byte romRamSelect = 0x00;
return memoryLayout.romBankSwitch[address - 0x4000]; // 0xA if enabled in MBC1/3, else off
if (address < 0xA000) // in MBC2 also the rom bank number
return memoryLayout.vram[address - 0x8000]; // in MBC3 also timer enable
if (address < 0xC000) { // reading ram while disabled is undefined behaviour
if (externalRamSize == 0) Byte ramEnable = 0x00;
return 0xFF; // MBC3
return memoryLayout.externalRam[address - 0xA000]; Byte latchClockData = 0x00;
} Byte ramBankRTCRegister = 0x00;
if (address < 0xD000) // RTC registers
return memoryLayout.memoryBank1[address - 0xC000]; enabledRTCRegister_enum enabledRTCRegister = NONE;
if (address < 0xE000) Byte RTCSeconds = 0x0;
return memoryLayout.memoryBank2[address - 0xD000]; Byte RTCMinutes = 0x0;
if (address < 0xFE00) Byte RTCHours = 0x0;
return memoryLayout.memoryBank1[address - 0xE000]; Byte RTCDayLower = 0x0;
if (address < 0xFEA0) Byte RTCDayHigher = 0x0;
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;
case 0xFF04:
return memoryLayout.DIV;
case 0xFF05:
return memoryLayout.TIMA;
case 0xFF06:
return memoryLayout.TMA;
case 0xFF07:
return memoryLayout.TAC | 0xF8;;
case 0xFF0F:
return memoryLayout.IF | 0xE0;
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:
//for debugging only
//return 0x90;
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;
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;
}
//write void setTesting(bool state);
Byte& operator[](const Word address) {
dummyVal = 0xFF; // read
if (testing) Byte operator[](const Word address) const {
return testRam[address]; if (testing)
if (address < 0x0100 && bootromLoaded) return testRam[address];
return bootrom[address]; if (address < 0x0100 && bootromLoaded)
if (address < 0x8000) return bootrom[address];
return (*MBCRead(address)); else if (address < 0x4000)
if (address < 0xA000) return memoryLayout.romBank0[address];
return memoryLayout.vram[address - 0x8000]; else if (address < 0x8000)
if (address < 0xC000) { return memoryLayout.romBankSwitch[address - 0x4000];
if (externalRamSize == 0) else if (address < 0xA000)
return dummyVal; return memoryLayout.vram[address - 0x8000];
return memoryLayout.externalRam[address - 0xA000]; else if (address < 0xC000) {
} if (enabledRTCRegister != NONE) {
if (address < 0xD000) switch (enabledRTCRegister) {
return memoryLayout.memoryBank1[address - 0xC000]; case RTCS:
if (address < 0xE000) return RTCSeconds;
return memoryLayout.memoryBank2[address - 0xD000]; case RTCM:
if (address < 0xFE00) return RTCMinutes;
return memoryLayout.memoryBank1[address - 0xE000]; case RTCH:
if (address < 0xFEA0) return RTCHours;
return memoryLayout.oam[address - 0xFE00]; case RTCDL:
if (address < 0xFF00) return RTCDayLower;
return memoryLayout.notUsable[address - 0xFEA0]; case RTCDH:
if (address < 0xFF80) return RTCDayHigher;
switch (address) { }
case 0xFF00: }
return memoryLayout.JOYP; if (externalRamSize == 0)
case 0xFF01: return 0xFF;
return memoryLayout.SB; // MBC2 echos 15 times and only stores lower 4 bits
case 0xFF02: else if (MBC == MBC2 || MBC == MBC2Battery)
return memoryLayout.SC; return memoryLayout.externalRam[(address - 0xA000) & 0x01FF] | 0xF0;
case 0xFF04: else
memoryLayout.DIV = 0; return memoryLayout.externalRam[address - 0xA000];
return dummyVal; } else if (address < 0xD000)
// Timer registers return memoryLayout.memoryBank1[address - 0xC000];
case 0xFF05: else if (address < 0xE000)
return memoryLayout.TIMA; return memoryLayout.memoryBank2[address - 0xD000];
case 0xFF06: else if (address < 0xFE00)
return memoryLayout.TMA; return memoryLayout.memoryBank1[address - 0xE000];
case 0xFF07: else if (address < 0xFEA0)
return memoryLayout.TAC; return memoryLayout.oam[address - 0xFE00];
case 0xFF0F: else if (address < 0xFF00) {
return memoryLayout.IF; if ((memoryLayout.STAT & 0x03) == 2 || (memoryLayout.STAT & 0x03) == 3)
case 0xFF10: return 0xFF;
return memoryLayout.NR10; return 0x00;
case 0xFF11: } else if (address < 0xFF80)
return memoryLayout.NR11; switch (address) {
case 0xFF12: case 0xFF00:
return memoryLayout.NR12; return memoryLayout.JOYP;
case 0xFF13: case 0xFF01:
return memoryLayout.NR13; return memoryLayout.SB;
case 0xFF14: case 0xFF02:
return memoryLayout.NR14; return memoryLayout.SC;
case 0xFF16: case 0xFF04:
return memoryLayout.NR21; return memoryLayout.DIV;
case 0xFF17: case 0xFF05:
return memoryLayout.NR22; return memoryLayout.TIMA;
case 0xFF18: case 0xFF06:
return memoryLayout.NR23; return memoryLayout.TMA;
case 0xFF19: case 0xFF07:
return memoryLayout.NR24; return memoryLayout.TAC | 0xF8;
case 0xFF1A: case 0xFF0F:
return memoryLayout.NR30; return memoryLayout.IF | 0xE0;
case 0xFF1B: case 0xFF10:
return memoryLayout.NR31; return memoryLayout.NR10;
case 0xFF1C: case 0xFF11:
return memoryLayout.NR32; return memoryLayout.NR11;
case 0xFF1D: case 0xFF12:
return memoryLayout.NR33; return memoryLayout.NR12;
case 0xFF1E: case 0xFF13:
return memoryLayout.NR34; return memoryLayout.NR13;
case 0xFF20: case 0xFF14:
return memoryLayout.NR41; return memoryLayout.NR14;
case 0xFF21: case 0xFF16:
return memoryLayout.NR42; return memoryLayout.NR21;
case 0xFF22: case 0xFF17:
return memoryLayout.NR43; return memoryLayout.NR22;
case 0xFF23: case 0xFF18:
return memoryLayout.NR44; return memoryLayout.NR23;
case 0xFF24: case 0xFF19:
return memoryLayout.NR50; return memoryLayout.NR24;
case 0xFF25: case 0xFF1A:
return memoryLayout.NR51; return memoryLayout.NR30;
case 0xFF26: case 0xFF1B:
return memoryLayout.NR52; return memoryLayout.NR31;
case 0xFF40: case 0xFF1C:
return memoryLayout.LCDC; return memoryLayout.NR32;
case 0xFF41: case 0xFF1D:
return memoryLayout.STAT; return memoryLayout.NR33;
case 0xFF42: case 0xFF1E:
return memoryLayout.SCY; return memoryLayout.NR34;
case 0xFF43: case 0xFF20:
return memoryLayout.SCX; return memoryLayout.NR41;
case 0xFF44: case 0xFF21:
dummyVal = memoryLayout.LY; return memoryLayout.NR42;
return dummyVal; case 0xFF22:
case 0xFF45: return memoryLayout.NR43;
return memoryLayout.LYC; case 0xFF23:
case 0xFF46: return memoryLayout.NR44;
dmaTransferRequested = true; case 0xFF24:
return memoryLayout.DMA; return memoryLayout.NR50;
case 0xFF47: case 0xFF25:
return memoryLayout.BGP; return memoryLayout.NR51;
case 0xFF48: case 0xFF26:
return memoryLayout.OBP0; return memoryLayout.NR52;
case 0xFF49: // PPU registers
return memoryLayout.OBP1; case 0xFF40:
case 0xFF4A: return memoryLayout.LCDC;
return memoryLayout.WY; case 0xFF41:
case 0xFF4B: return memoryLayout.STAT;
return memoryLayout.WX; case 0xFF42:
default: return memoryLayout.SCY;
if (address >= 0xFF30 && address <= 0xFF3F) { case 0xFF43:
return memoryLayout.waveRam[address - 0xFF30]; return memoryLayout.SCX;
} case 0xFF44:
return dummyVal; return memoryLayout.LY;
} case 0xFF45:
if (address < 0xFFFF) return memoryLayout.LYC;
return memoryLayout.specialRam[address - 0xFF80]; case 0xFF46:
//0xFFFF return memoryLayout.DMA;
return memoryLayout.IE; 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;
default:
if (address >= 0xFF30 && address <= 0xFF3F) {
return memoryLayout.waveRam[address - 0xFF30];
}
return 0xFF;
}
else if (address < 0xFFFF)
return memoryLayout.specialRam[address - 0xFF80];
// 0xFFFF
return memoryLayout.IE;
}
// write
Byte &operator[](const Word address) {
dummyVal = 0xFF;
if (testing)
return testRam[address];
else if (address < 0x0100 && bootromLoaded)
return bootrom[address];
else if (address < 0x8000)
return (*MBCWrite(address));
else if (address < 0xA000)
return memoryLayout.vram[address - 0x8000];
else if (address < 0xC000) {
if (externalRamSize == 0)
return dummyVal;
return memoryLayout.externalRam[address - 0xA000];
} else if (address < 0xD000)
return memoryLayout.memoryBank1[address - 0xC000];
else if (address < 0xE000)
return memoryLayout.memoryBank2[address - 0xD000];
else if (address < 0xFE00)
return memoryLayout.memoryBank1[address - 0xE000];
else if (address < 0xFEA0)
return memoryLayout.oam[address - 0xFE00];
else if (address < 0xFF00)
return memoryLayout.notUsable[address - 0xFEA0];
else if (address < 0xFF80)
switch (address) {
case 0xFF00:
return memoryLayout.JOYP;
case 0xFF01:
return memoryLayout.SB;
case 0xFF02:
return memoryLayout.SC;
case 0xFF04:
memoryLayout.DIV = 0;
return dummyVal;
// Timer registers
case 0xFF05:
return memoryLayout.TIMA;
case 0xFF06:
return memoryLayout.TMA;
case 0xFF07:
return memoryLayout.TAC;
case 0xFF0F:
return memoryLayout.IF;
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;
case 0xFF40:
return memoryLayout.LCDC;
case 0xFF41:
return memoryLayout.STAT;
case 0xFF42:
return memoryLayout.SCY;
case 0xFF43:
return memoryLayout.SCX;
case 0xFF44:
dummyVal = memoryLayout.LY;
return dummyVal;
case 0xFF45:
return memoryLayout.LYC;
case 0xFF46:
dmaTransferRequested = true;
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;
default:
if (address >= 0xFF30 && address <= 0xFF3F) {
return memoryLayout.waveRam[address - 0xFF30];
}
return dummyVal;
}
else if (address < 0xFFFF)
return memoryLayout.specialRam[address - 0xFF80];
// 0xFFFF
return memoryLayout.IE;
}
}; };
#endif // ADDRESSSPACE_HPP
#endif //ADDRESSSPACE_HPP

View File

@@ -10,10 +10,10 @@
// 5 h - - Half Carry Flag (BCD) // 5 h - - Half Carry Flag (BCD)
// 4 cy C NC Carry Flag // 4 cy C NC Carry Flag
// 3-0 - - - Not used // 3-0 - - - Not used
#define CARRY_FLAG 4 //'C' #define CARRY_FLAG 4 //'C'
#define HALFCARRY_FLAG 5 //'H' #define HALFCARRY_FLAG 5 //'H'
#define SUBTRACT_FLAG 6 //'N' #define SUBTRACT_FLAG 6 //'N'
#define ZERO_FLAG 7 //'Z' #define ZERO_FLAG 7 //'Z'
#define VBLANK_INTERRUPT 0 #define VBLANK_INTERRUPT 0
#define LCD_STAT_INTERRUPT 1 #define LCD_STAT_INTERRUPT 1
@@ -21,9 +21,9 @@
#define SERIAL_INTERRUPT 3 #define SERIAL_INTERRUPT 3
#define JOYPAD_INTERRUPT 4 #define JOYPAD_INTERRUPT 4
#define T_CLOCK_FREQ 4194304 //2^22 #define T_CLOCK_FREQ 4194304 // 2^22
#define DIVIDER_REGISTER_FREQ (4194304/16384) #define DIVIDER_REGISTER_FREQ (4194304 / 16384)
#define BOOTROM_SIZE 0x100 #define BOOTROM_SIZE 0x100
@@ -31,7 +31,7 @@
#define RESOLUTION_Y 144 #define RESOLUTION_Y 144
#define SCREEN_BPP 3 #define SCREEN_BPP 3
//lcdc // lcdc
#define BG_WINDOW_ENABLE 0 #define BG_WINDOW_ENABLE 0
#define OBJ_ENABLE 1 #define OBJ_ENABLE 1
#define OBJ_SIZE 2 #define OBJ_SIZE 2
@@ -41,13 +41,12 @@
#define WINDOW_TILE_MAP_AREA 6 #define WINDOW_TILE_MAP_AREA 6
#define LCD_ENABLE 7 #define LCD_ENABLE 7
//oam // oam
#define PRIORITY 7 #define PRIORITY 7
#define Y_FLIP 6 #define Y_FLIP 6
#define X_FLIP 5 #define X_FLIP 5
#define OBJ_PALETTE 4 #define OBJ_PALETTE 4
#define ROM_BANK_SIZE 0x4000 #define ROM_BANK_SIZE 0x4000
#define RAM_BANK_SIZE 0x2000 #define RAM_BANK_SIZE 0x2000
@@ -59,10 +58,10 @@
#define H_SYNC 9198 #define H_SYNC 9198
#define V_SYNC 59.73 #define V_SYNC 59.73
#define HBLANK_DURATION 204 //PPU_MODE 0 #define HBLANK_DURATION 204 // PPU_MODE 0
#define VBLANK_DURATION 4560 #define VBLANK_DURATION 4560
#define SCANLINE_OAM_FREQ 80 //PPU_MODE 2 #define SCANLINE_OAM_FREQ 80 // PPU_MODE 2
#define SCANLINE_VRAM_FREQ 80 //PPU_MODE 3 #define SCANLINE_VRAM_FREQ 80 // PPU_MODE 3
#define WHITE 0xFFFFFFFF; #define WHITE 0xFFFFFFFF;
#define LIGHT_GRAY 0xFFAAAAAA; #define LIGHT_GRAY 0xFFAAAAAA;
@@ -70,52 +69,65 @@
#define BLACK 0xFF000000 #define BLACK 0xFF000000
struct Input { struct Input {
bool UP = false; bool UP = false;
bool DOWN = false; bool DOWN = false;
bool LEFT = false; bool LEFT = false;
bool RIGHT = false; bool RIGHT = false;
bool B = false; bool B = false;
bool A = false; bool A = false;
bool START = false; bool START = false;
bool SELECT = false; bool SELECT = false;
}; };
enum MBCType { enum MBCType_enum {
romOnly = 0x00, romOnly = 0x00,
MBC1 = 0x01, MBC1 = 0x01,
MBC1Ram = 0x02, MBC1Ram = 0x02,
MBC1RamBattery = 0x03, MBC1RamBattery = 0x03,
MBC2 = 0x05, MBC2 = 0x05,
MBC2Battery = 0x06, MBC2Battery = 0x06,
RomRam = 0x08, //unused RomRam = 0x08, // unused
RomRamBattery = 0x09, //unused RomRamBattery = 0x09, // unused
MMM01 = 0x0B, //multigame roms only MMM01 = 0x0B, // multigame roms only
MMM01Ram = 0x0C, MMM01Ram = 0x0C,
MMM01RamBattery = 0x0D, MMM01RamBattery = 0x0D,
MBC3TimerBattery = 0x0F, MBC3TimerBattery = 0x0F,
MBC3TimerRamBattery = 0x10, MBC3TimerRamBattery = 0x10,
MBC3 = 0x11, MBC3 = 0x11,
MBC3Ram = 0x12, // MBC3 with 64 KiB of SRAM refers to MBC30, used only in Pocket Monsters: Crystal Version. MBC3Ram = 0x12, // MBC3 with 64 KiB of SRAM refers to MBC30, used only in
MBC3RamBattery = 0x13, // Pocket Monsters: Crystal Version.
MBC5 = 0x19, MBC3RamBattery = 0x13,
MBC5Ram = 0x1A, MBC5 = 0x19,
MBC5RamBattery = 0x1B, MBC5Ram = 0x1A,
MBC5Rumble = 0x1C, MBC5RamBattery = 0x1B,
MBC5RumbleRam = 0x1D, MBC5Rumble = 0x1C,
MBC5RumbleRamBattery = 0x1E, MBC5RumbleRam = 0x1D,
MBC6 = 0x20, MBC5RumbleRamBattery = 0x1E,
MBC7SensorRumbleRamBattery = 0x22, MBC6 = 0x20,
PocketCamera = 0xFC, MBC7SensorRumbleRamBattery = 0x22,
BandaiTama5 = 0xFD, PocketCamera = 0xFC,
HuC3 = 0xFE, BandaiTama5 = 0xFD,
HuC1RamBattery = 0xFF HuC3 = 0xFE,
HuC1RamBattery = 0xFF
}; };
enum PPUMode { enum PPUMode_enum {
mode0, // Horizontal Blank (Mode 0): No access to video RAM, occurs during horizontal blanking period. mode0, // Horizontal Blank (Mode 0): No access to video RAM, occurs during
mode1, // Vertical Blank (Mode 1): No access to video RAM, occurs during vertical blanking period. // horizontal blanking period.
mode2, // OAM Search (Mode 2): Access to OAM (Object Attribute Memory) only, sprite evaluation. mode1, // Vertical Blank (Mode 1): No access to video RAM, occurs during
mode3 // Pixel Transfer (Mode 3): Access to both OAM and video RAM, actual pixel transfer to the screen. // vertical blanking period.
mode2, // OAM Search (Mode 2): Access to OAM (Object Attribute Memory) only,
// sprite evaluation.
mode3 // Pixel Transfer (Mode 3): Access to both OAM and video RAM, actual
// pixel transfer to the screen.
}; };
enum enabledRTCRegister_enum {
NONE = 0x00,
RTCS = 0x08,
RTCM = 0x09,
RTCH = 0x0A,
RTCDL = 0x0B,
RTCDH = 0x0C
};
#endif #endif

File diff suppressed because it is too large Load Diff

View File

@@ -1,202 +1,194 @@
#include <iostream>
#include "gameboy.hpp" #include "gameboy.hpp"
#include <iostream>
void GameBoy::addCycles(const uint8_t ticks) { void GameBoy::addCycles(const uint8_t ticks) {
cycles += ticks; cycles += ticks;
if (ppuEnabled) { if (ppuEnabled) {
ppuCycles += ticks; ppuCycles += ticks;
} }
lastOpTicks = ticks; lastOpTicks = ticks;
} }
GameboyTestState GameBoy::runTest(GameboyTestState initial) { GameboyTestState GameBoy::runTest(GameboyTestState initial) {
addressSpace.setTesting(true); addressSpace.setTesting(true);
PC = initial.PC; PC = initial.PC;
SP = initial.SP; SP = initial.SP;
AF.hi = initial.A; AF.hi = initial.A;
AF.lo = initial.F; AF.lo = initial.F;
BC.hi = initial.B; BC.hi = initial.B;
BC.lo = initial.C; BC.lo = initial.C;
DE.hi = initial.D; DE.hi = initial.D;
DE.lo = initial.E; DE.lo = initial.E;
HL.hi = initial.H; HL.hi = initial.H;
HL.lo = initial.L; HL.lo = initial.L;
addressSpace.memoryLayout.IE = 1; addressSpace.memoryLayout.IE = 1;
for (const auto& [addr, val] : initial.RAM) { for (const auto &[addr, val] : initial.RAM) {
addressSpace[addr] = val; addressSpace[addr] = val;
} }
opcodeResolver(); opcodeResolver();
std::vector<std::tuple<Word, Byte>> returnRAM; std::vector<std::tuple<Word, Byte>> returnRAM;
for (const auto& [addr, val] : initial.RAM) { for (const auto &[addr, val] : initial.RAM) {
returnRAM.emplace_back(addr, addressSpace[addr]); returnRAM.emplace_back(addr, addressSpace[addr]);
} }
return { return {PC, SP, AF.hi, AF.lo, BC.hi, BC.lo,
PC, SP, DE.hi, DE.lo, HL.hi, HL.lo, returnRAM};
AF.hi, AF.lo,
BC.hi, BC.lo,
DE.hi, DE.lo,
HL.hi, HL.lo,
returnRAM
};
} }
void GameBoy::start(const std::string &bootrom, const std::string &game) {
addressSpace.loadBootrom(bootrom);
addressSpace.loadGame(game);
addressSpace.determineMBCInfo();
addressSpace.createRamBank();
void GameBoy::start(const std::string& bootrom, const std::string& game) { bool quit = false;
addressSpace.loadBootrom(bootrom); bool setIME = false;
addressSpace.loadGame(game); bool debug = false;
addressSpace.determineMBCInfo(); bool step = false;
addressSpace.createRamBank();
bool quit = false; while (!quit) {
bool setIME = false; // Event loop
bool debug = false; while (SDL_PollEvent(&event)) {
bool step = false; switch (event.type) {
case SDL_QUIT:
quit = true;
break;
case SDL_KEYDOWN:
switch (event.key.keysym.sym) {
case SDLK_a:
joypadInput.LEFT = true;
break;
case SDLK_d:
joypadInput.RIGHT = true;
break;
case SDLK_w:
joypadInput.UP = true;
break;
case SDLK_s:
joypadInput.DOWN = true;
break;
case SDLK_k:
joypadInput.A = true;
break;
case SDLK_l:
joypadInput.B = true;
break;
case SDLK_o:
joypadInput.SELECT = true;
break;
case SDLK_p:
joypadInput.START = true;
break;
case SDLK_h:
debug = !debug;
break;
case SDLK_n:
step = true;
break;
default:
break;
}
break;
case SDL_KEYUP:
switch (event.key.keysym.sym) {
case SDLK_a:
joypadInput.LEFT = false;
break;
case SDLK_d:
joypadInput.RIGHT = false;
break;
case SDLK_w:
joypadInput.UP = false;
break;
case SDLK_s:
joypadInput.DOWN = false;
break;
case SDLK_k:
joypadInput.A = false;
break;
case SDLK_l:
joypadInput.B = false;
break;
case SDLK_o:
joypadInput.SELECT = false;
break;
case SDLK_p:
joypadInput.START = false;
break;
default:
break;
}
break;
default:
break;
}
}
while (!quit) { while (!rendered) {
// Event loop if (debug == true && step == false)
while (SDL_PollEvent(&event)) { break;
switch (event.type) { step = false;
case SDL_QUIT: joypadHandler();
quit = true; if (PC > 0xFF && addressSpace.getBootromState()) {
break; addressSpace.unmapBootrom();
case SDL_KEYDOWN: }
switch (event.key.keysym.sym) { ppuEnabled = addressSpace.memoryLayout.LCDC & 0x80;
case SDLK_a: prevTMA = addressSpace.memoryLayout.TMA;
joypadInput.LEFT = true;
break;
case SDLK_d:
joypadInput.RIGHT = true;
break;
case SDLK_w:
joypadInput.UP = true;
break;
case SDLK_s:
joypadInput.DOWN = true;
break;
case SDLK_k:
joypadInput.A = true;
break;
case SDLK_l:
joypadInput.B = true;
break;
case SDLK_o:
joypadInput.SELECT = true;
break;
case SDLK_p:
joypadInput.START = true;
break;
case SDLK_h:
debug = !debug;
break;
case SDLK_n:
step = true;
break;
default:
break;
}
break;
case SDL_KEYUP:
switch (event.key.keysym.sym) {
case SDLK_a:
joypadInput.LEFT = false;
break;
case SDLK_d:
joypadInput.RIGHT = false;
break;
case SDLK_w:
joypadInput.UP = false;
break;
case SDLK_s:
joypadInput.DOWN = false;
break;
case SDLK_k:
joypadInput.A = false;
break;
case SDLK_l:
joypadInput.B = false;
break;
case SDLK_o:
joypadInput.SELECT = false;
break;
case SDLK_p:
joypadInput.START = false;
break;
default:
break;
}
break;
default:
break;
}
}
while (!rendered) { if (debug) {
if (debug == true && step == false) printf("A: %.2X F: %.2X B: %.2X C: %.2X D: %.2X E: %.2X H: %.2X L: "
break; "%.2X SP: %.4X PC: 00:%.4X (%.2X %.2X %.2X %.2X)\n",
step = false; AF.hi, AF.lo, BC.hi, BC.lo, DE.hi, DE.lo, HL.hi, HL.lo, SP, PC,
joypadHandler(); readOnlyAddressSpace[PC], readOnlyAddressSpace[PC + 1],
if (PC > 0xFF && addressSpace.getBootromState()) { readOnlyAddressSpace[PC + 2], readOnlyAddressSpace[PC + 3]);
addressSpace.unmapBootrom();
}
ppuEnabled = addressSpace.memoryLayout.LCDC & 0x80;
prevTMA = addressSpace.memoryLayout.TMA;
if (debug) { // printf("Cycles: %lu, Opcode: 0x%.2x PPU cycles: %lu, PPMode: %d\n",
printf( // cycles, readOnlyAddressSpace[PC],
"A: %.2X F: %.2X B: %.2X C: %.2X D: %.2X E: %.2X H: %.2X L: %.2X SP: %.4X PC: 00:%.4X (%.2X %.2X %.2X %.2X)\n", // cyclesSinceLastScanline(), currentMode);
AF.hi, AF.lo, BC.hi, BC.lo, DE.hi, DE.lo, HL.hi, HL.lo, SP, PC, readOnlyAddressSpace[PC], // printf("AF:0x%.4x, BC:0x%.4x\n", AF.reg, BC.reg);
readOnlyAddressSpace[PC + 1], readOnlyAddressSpace[PC + 2], readOnlyAddressSpace[PC + 3]); // 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 (!halted) {
// printf("Cycles: %lu, Opcode: 0x%.2x PPU cycles: %lu, PPMode: %d\n", cycles, readOnlyAddressSpace[PC], opcodeResolver();
// cyclesSinceLastScanline(), currentMode); addressSpace.MBCUpdate();
// printf("AF:0x%.4x, BC:0x%.4x\n", AF.reg, BC.reg); } else {
// printf("DE:0x%.4x, HL:0x%.4x\n", DE.reg, HL.reg); addCycles(4);
// printf("IME:%d IF:0x%.2x IE:0x%.2x\n", IME, (*IF), (*IE)); }
// printf("PC:0x%.4x, SP:0x%.4x\n", PC, SP); timingHandler();
// printf("LCDC:%.2x STAT:0x%.2x LY:%d LYC:%d\n", (*LCDC), (*STAT), (*LY), (*LYC)); interruptHandler();
// printf("\n"); if (ppuEnabled) {
} ppuUpdate();
} else {
if (!halted) { ppuCycles = 2;
opcodeResolver(); lastScanline = 0;
addressSpace.MBCUpdate(); lastRefresh = 0;
} addressSpace.memoryLayout.LY = 0x00;
else { addressSpace.memoryLayout.STAT &= 0xfc;
addCycles(4); }
} if (setIME) {
timingHandler(); IME = 1;
interruptHandler(); setIME = false;
if (ppuEnabled) { }
ppuUpdate(); if (IME_togge) {
} setIME = true;
else { IME_togge = false;
ppuCycles = 2; }
lastScanline = 0; if (addressSpace.dmaTransferRequested) {
lastRefresh = 0; cyclesUntilDMATransfer -= lastOpTicks;
addressSpace.memoryLayout.LY = 0x00; if (cyclesUntilDMATransfer <= 0) {
addressSpace.memoryLayout.STAT &= 0xfc; cyclesUntilDMATransfer = 160;
} addressSpace.dmaTransfer();
if (setIME) { }
IME = 1; }
setIME = false; }
} rendered = false;
if (IME_togge) { }
setIME = true;
IME_togge = false;
}
if (addressSpace.dmaTransferRequested) {
cyclesUntilDMATransfer -= lastOpTicks;
if (cyclesUntilDMATransfer <= 0) {
cyclesUntilDMATransfer = 160;
addressSpace.dmaTransfer();
}
}
}
rendered = false;
}
} }

View File

@@ -1,186 +1,172 @@
#ifndef GBPP_SRC_GAMEBOY_HPP_ #ifndef GBPP_SRC_GAMEBOY_HPP_
#define GBPP_SRC_GAMEBOY_HPP_ #define GBPP_SRC_GAMEBOY_HPP_
#include <filesystem>
#include <cstdint>
#include <string>
#include <SDL.h>
#include "defines.hpp"
#include "addressSpace.hpp" #include "addressSpace.hpp"
#include "defines.hpp"
#include "testing.hpp" #include "testing.hpp"
#include <SDL.h>
#include <cstdint>
#include <filesystem>
#include <string>
union RegisterPair { union RegisterPair {
Word reg; //register.reg == (hi << 8) + lo. (hi is more significant than lo) Word reg; // register.reg == (hi << 8) + lo. (hi is more significant than lo)
struct { struct {
Byte lo; Byte lo;
Byte hi; Byte hi;
}; };
}; };
class GameBoy { class GameBoy {
//T-cycles not M-cycles (4 T-cycles = 1 M-cycle) // T-cycles not M-cycles (4 T-cycles = 1 M-cycle)
uint64_t cycles = 0; uint64_t cycles = 0;
//Start at 2 T-cycles https://github.com/Gekkio/mooneye-test-suite/blob/main/acceptance/ppu/lcdon_timing-GS.s // Start at 2 T-cycles
uint64_t ppuCycles = 2; // https://github.com/Gekkio/mooneye-test-suite/blob/main/acceptance/ppu/lcdon_timing-GS.s
bool ppuEnabled = false; uint64_t ppuCycles = 2;
uint16_t lastOpTicks = 0; bool ppuEnabled = false;
uint64_t lastRefresh = 0; uint16_t lastOpTicks = 0;
uint64_t lastScanline = 0; uint64_t lastRefresh = 0;
uint64_t cyclesToStayInHblank = -1; uint64_t lastScanline = 0;
uint64_t lastDivUpdate = 0; uint64_t cyclesToStayInHblank = -1;
bool rendered = false; uint64_t lastDivUpdate = 0;
bool rendered = false;
uint8_t IME = 0; //enables interupts uint8_t IME = 0; // enables interupts
// EI is actually "disable interrupts for one instruction, then enable them" // EI is actually "disable interrupts for one instruction, then enable them"
// This keeps track of that // This keeps track of that
bool IME_togge = false; bool IME_togge = false;
//Accumulator and flags // Accumulator and flags
RegisterPair AF = {0}; RegisterPair AF = {0};
//General purpose CPU registers // General purpose CPU registers
RegisterPair BC = {0}; RegisterPair BC = {0};
RegisterPair DE = {0}; RegisterPair DE = {0};
RegisterPair HL = {0}; RegisterPair HL = {0};
Word SP = 0xFFFE; //stack pointer Word SP = 0xFFFE; // stack pointer
Word PC = 0x0000; //program counter Word PC = 0x0000; // program counter
AddressSpace addressSpace; AddressSpace addressSpace;
const AddressSpace& readOnlyAddressSpace = addressSpace; const AddressSpace &readOnlyAddressSpace = addressSpace;
PPUMode currentMode = PPUMode::mode0; PPUMode_enum currentMode = PPUMode_enum::mode0;
Byte windowLineCounter = 0; Byte windowLineCounter = 0;
int16_t cyclesUntilDMATransfer = 160; int16_t cyclesUntilDMATransfer = 160;
Byte prevTMA = 0; Byte prevTMA = 0;
uint64_t lastTIMAUpdate = 0; uint64_t lastTIMAUpdate = 0;
bool halted = false; bool halted = false;
bool haltBug = true; bool haltBug = true;
bool stopped = false; bool stopped = false;
//3 colour channels // 3 colour channels
uint32_t* framebuffer = new uint32_t[RESOLUTION_X * RESOLUTION_Y * SCREEN_BPP]; uint32_t *framebuffer =
SDL_Window* screen = nullptr; new uint32_t[RESOLUTION_X * RESOLUTION_Y * SCREEN_BPP];
SDL_Renderer* renderer = nullptr; SDL_Window *screen = nullptr;
SDL_Texture* texture = nullptr; SDL_Renderer *renderer = nullptr;
SDL_Event event = {0}; SDL_Texture *texture = nullptr;
uint32_t frameStart = 0; SDL_Event event = {0};
uint32_t frameTime = 0; uint32_t frameStart = 0;
const int frameDelay = 1000 / V_SYNC; uint32_t frameTime = 0;
const int frameDelay = 1000 / V_SYNC;
Input joypadInput; Input joypadInput;
void joypadHandler(); void joypadHandler();
void opcodeResolver(); void opcodeResolver();
bool statInteruptLine = false; bool statInteruptLine = false;
bool LCDCBitEnabled(Byte bit) const; bool LCDCBitEnabled(Byte bit) const;
void incLY(); void incLY();
void ppuUpdate(); void ppuUpdate();
void drawLine(); void drawLine();
static bool oamBitEnabled(Byte oamAttributeByte, Byte bit); static bool oamBitEnabled(Byte oamAttributeByte, Byte bit);
static unsigned int getColourFromPalette(Byte palette); static unsigned int getColourFromPalette(Byte palette);
void SDL2present(); void SDL2present();
void checkPPUMode(); void checkPPUMode();
void setPPUMode(PPUMode mode); void setPPUMode(PPUMode_enum mode);
uint64_t cyclesSinceLastScanline() const; uint64_t cyclesSinceLastScanline() const;
uint64_t cyclesSinceLastRefresh() const; uint64_t cyclesSinceLastRefresh() const;
void timingHandler(); void timingHandler();
void interruptHandler(); void interruptHandler();
bool testInterruptEnabled(Byte interrupt) const; bool testInterruptEnabled(Byte interrupt) const;
void setInterrupt(Byte interrupt); void setInterrupt(Byte interrupt);
void resetInterrupt(Byte interrupt); void resetInterrupt(Byte interrupt);
void VBlankHandle(); void VBlankHandle();
void LCDStatHandle(); void LCDStatHandle();
void timerHandle(); void timerHandle();
void serialHandle(); void serialHandle();
void joypadHandle(); void joypadHandle();
void setFlag(Byte bit); void setFlag(Byte bit);
void resetFlag(Byte bit); void resetFlag(Byte bit);
bool getFlag(Byte bit) const; bool getFlag(Byte bit) const;
Word getWordPC(); Word getWordPC();
Byte getBytePC(); Byte getBytePC();
Word getWordSP(); Word getWordSP();
Byte getByteSP(); Byte getByteSP();
void addCycles(Byte ticks); void addCycles(Byte ticks);
//OPCODE FUNCTIONS // OPCODE FUNCTIONS
template <typename T> template <typename T> void ld(T &dest, T src);
void ld(T& dest, T src); void ldW(Word destAddr, Word src);
void ldW(Word destAddr, Word src); template <typename T> void orBitwise(T &dest, T src);
template <typename T> template <typename T> void andBitwise(T &dest, T src);
void orBitwise(T& dest, T src); template <typename T> void xorBitwise(T &dest, T src);
template <typename T> void bit(Byte testBit, Byte reg);
void andBitwise(T& dest, T src); void extendedOpcodeResolver();
template <typename T> static void set(uint8_t testBit, uint8_t &reg);
void xorBitwise(T& dest, T src); static void res(uint8_t testBit, uint8_t &reg);
void bit(Byte testBit, Byte reg); template <typename T> void jp(T address);
void extendedOpcodeResolver(); template <typename T> bool jrNZ(T offset);
static void set(uint8_t testBit, uint8_t& reg); template <class T> bool jrNC(T offset);
static void res(uint8_t testBit, uint8_t& reg); template <class T> bool jrC(T offset);
template <typename T> template <typename T> void inc(T &reg);
void jp(T address); template <typename T> void call(T address);
template <typename T> void halt();
bool jrNZ(T offset); void daa();
template <class T> void stop();
bool jrNC(T offset); void cp(Byte value);
template <class T> template <typename T> void dec(T &reg);
bool jrC(T offset); template <typename T> bool jrZ(T offset);
template <typename T> void sub(Byte value);
void inc(T& reg); void sbc(Byte value);
template <typename T> template <typename T> void jr(T OFFSET);
void call(T address); void push(Word reg);
void halt(); void rl(Byte &reg);
void daa(); void sla(Byte &reg);
void stop(); void sra(uint8_t &reg);
void cp(Byte value); void srl(uint8_t &reg);
template <typename T> void rrc(Byte &reg);
void dec(T& reg); void rrca();
template <typename T> void rra();
bool jrZ(T offset); void rr(Byte &reg);
void sub(Byte value); void rlc(Byte &reg);
void sbc(Byte value); void rlca();
template <typename T> void rla();
void jr(T OFFSET); template <typename T> void pop(T &reg);
void push(Word reg); template <typename T> void rst(T address);
void rl(Byte& reg); void ret();
void sla(Byte& reg); template <typename T> void add(T &reg, T value);
void sra(uint8_t& reg); void adc(Byte value);
void srl(uint8_t& reg); void cpl();
void rrc(Byte& reg); void scf();
void rrca(); void ccf();
void rra(); void swap(Byte &value);
void rr(Byte& reg);
void rlc(Byte& reg);
void rlca();
void rla();
template <typename T>
void pop(T& reg);
template <typename T>
void rst(T address);
void ret();
template <typename T>
void add(T& reg, T value);
void adc(Byte value);
void cpl();
void scf();
void ccf();
void swap(Byte& value);
public: public:
void start(const std::string& bootrom, const std::string& game); void start(const std::string &bootrom, const std::string &game);
void SDL2setup(); void SDL2setup();
void SDL2destroy() const; void SDL2destroy() const;
GameboyTestState runTest(GameboyTestState initial); GameboyTestState runTest(GameboyTestState initial);
}; };
#endif //GBPP_SRC_GAMEBOY_HPP_ #endif // GBPP_SRC_GAMEBOY_HPP_

View File

@@ -2,88 +2,94 @@
#include "gameboy.hpp" #include "gameboy.hpp"
bool GameBoy::testInterruptEnabled(const Byte interrupt) const { bool GameBoy::testInterruptEnabled(const Byte interrupt) const {
return readOnlyAddressSpace.memoryLayout.IE & static_cast<Byte>(1 << interrupt); return readOnlyAddressSpace.memoryLayout.IE &
static_cast<Byte>(1 << interrupt);
} }
void GameBoy::setInterrupt(const Byte interrupt) { void GameBoy::setInterrupt(const Byte interrupt) {
addressSpace.memoryLayout.IF |= 1 << interrupt; addressSpace.memoryLayout.IF |= 1 << interrupt;
addressSpace.memoryLayout.IF |= 0xE0; addressSpace.memoryLayout.IF |= 0xE0;
} }
void GameBoy::resetInterrupt(const Byte interrupt) { void GameBoy::resetInterrupt(const Byte interrupt) {
addressSpace.memoryLayout.IF &= ~(1 << interrupt); addressSpace.memoryLayout.IF &= ~(1 << interrupt);
addressSpace.memoryLayout.IF |= 0xE0; addressSpace.memoryLayout.IF |= 0xE0;
} }
void GameBoy::interruptHandler() { void GameBoy::interruptHandler() {
if (readOnlyAddressSpace.memoryLayout.IF & static_cast<Byte>(1 << VBLANK_INTERRUPT) && testInterruptEnabled( if (readOnlyAddressSpace.memoryLayout.IF &
VBLANK_INTERRUPT)) { static_cast<Byte>(1 << VBLANK_INTERRUPT) &&
if (IME) testInterruptEnabled(VBLANK_INTERRUPT)) {
VBlankHandle(); if (IME)
halted = false; VBlankHandle();
} halted = false;
if (readOnlyAddressSpace.memoryLayout.IF & static_cast<Byte>(1 << LCD_STAT_INTERRUPT) && testInterruptEnabled( }
LCD_STAT_INTERRUPT)) { if (readOnlyAddressSpace.memoryLayout.IF &
if (IME) static_cast<Byte>(1 << LCD_STAT_INTERRUPT) &&
LCDStatHandle(); testInterruptEnabled(LCD_STAT_INTERRUPT)) {
halted = false; if (IME)
} LCDStatHandle();
if (readOnlyAddressSpace.memoryLayout.IF & static_cast<Byte>(1 << TIMER_INTERRUPT) && testInterruptEnabled( halted = false;
TIMER_INTERRUPT)) { }
if (IME) if (readOnlyAddressSpace.memoryLayout.IF &
timerHandle(); static_cast<Byte>(1 << TIMER_INTERRUPT) &&
halted = false; testInterruptEnabled(TIMER_INTERRUPT)) {
} if (IME)
if (readOnlyAddressSpace.memoryLayout.IF & static_cast<Byte>(1 << SERIAL_INTERRUPT) && testInterruptEnabled( timerHandle();
SERIAL_INTERRUPT)) { halted = false;
if (IME) }
serialHandle(); if (readOnlyAddressSpace.memoryLayout.IF &
halted = false; static_cast<Byte>(1 << SERIAL_INTERRUPT) &&
} testInterruptEnabled(SERIAL_INTERRUPT)) {
if (readOnlyAddressSpace.memoryLayout.IF & static_cast<Byte>(1 << JOYPAD_INTERRUPT) && testInterruptEnabled( if (IME)
JOYPAD_INTERRUPT)) { serialHandle();
if (IME) halted = false;
joypadHandle(); }
halted = false; if (readOnlyAddressSpace.memoryLayout.IF &
} static_cast<Byte>(1 << JOYPAD_INTERRUPT) &&
testInterruptEnabled(JOYPAD_INTERRUPT)) {
if (IME)
joypadHandle();
halted = false;
}
} }
void GameBoy::VBlankHandle() { void GameBoy::VBlankHandle() {
IME = 0; IME = 0;
push(PC); push(PC);
addCycles(20); addCycles(20);
PC = 0x40; PC = 0x40;
resetInterrupt(VBLANK_INTERRUPT); resetInterrupt(VBLANK_INTERRUPT);
} }
void GameBoy::LCDStatHandle() { void GameBoy::LCDStatHandle() {
IME = 0; IME = 0;
push(PC); push(PC);
addCycles(20); addCycles(20);
PC = 0x48; PC = 0x48;
resetInterrupt(LCD_STAT_INTERRUPT); resetInterrupt(LCD_STAT_INTERRUPT);
} }
void GameBoy::timerHandle() { void GameBoy::timerHandle() {
IME = 0; IME = 0;
push(PC); push(PC);
addCycles(20); addCycles(20);
PC = 0x50; PC = 0x50;
resetInterrupt(TIMER_INTERRUPT); resetInterrupt(TIMER_INTERRUPT);
} }
void GameBoy::serialHandle() { void GameBoy::serialHandle() {
IME = 0; IME = 0;
push(PC); push(PC);
addCycles(20); addCycles(20);
PC = 0x58; PC = 0x58;
resetInterrupt(SERIAL_INTERRUPT); resetInterrupt(SERIAL_INTERRUPT);
} }
void GameBoy::joypadHandle() { void GameBoy::joypadHandle() {
IME = 0; IME = 0;
push(PC); push(PC);
addCycles(20); addCycles(20);
PC = 0x60; PC = 0x60;
resetInterrupt(JOYPAD_INTERRUPT); resetInterrupt(JOYPAD_INTERRUPT);
} }

View File

@@ -1,44 +1,44 @@
#include "gameboy.hpp"
#include "defines.hpp" #include "defines.hpp"
#include "gameboy.hpp"
void GameBoy::joypadHandler() { void GameBoy::joypadHandler() {
const Byte joyP = addressSpace.memoryLayout.JOYP; const Byte joyP = addressSpace.memoryLayout.JOYP;
const bool buttons = (joyP & 0x20) == 0; const bool buttons = (joyP & 0x20) == 0;
const bool dpad = (joyP & 0x10) == 0; const bool dpad = (joyP & 0x10) == 0;
addressSpace.memoryLayout.JOYP |= 0xCF; addressSpace.memoryLayout.JOYP |= 0xCF;
if (buttons && !dpad) { if (buttons && !dpad) {
if (joypadInput.A) if (joypadInput.A)
addressSpace.memoryLayout.JOYP &= ~0x1; addressSpace.memoryLayout.JOYP &= ~0x1;
if (joypadInput.B) if (joypadInput.B)
addressSpace.memoryLayout.JOYP &= ~0x2; addressSpace.memoryLayout.JOYP &= ~0x2;
if (joypadInput.SELECT) if (joypadInput.SELECT)
addressSpace.memoryLayout.JOYP &= ~0x4; addressSpace.memoryLayout.JOYP &= ~0x4;
if (joypadInput.START) if (joypadInput.START)
addressSpace.memoryLayout.JOYP &= ~0x8; addressSpace.memoryLayout.JOYP &= ~0x8;
} }
if (!buttons && dpad) { if (!buttons && dpad) {
if (joypadInput.RIGHT) if (joypadInput.RIGHT)
addressSpace.memoryLayout.JOYP &= ~0x1; addressSpace.memoryLayout.JOYP &= ~0x1;
if (joypadInput.LEFT) if (joypadInput.LEFT)
addressSpace.memoryLayout.JOYP &= ~0x2; addressSpace.memoryLayout.JOYP &= ~0x2;
if (joypadInput.UP) if (joypadInput.UP)
addressSpace.memoryLayout.JOYP &= ~0x4; addressSpace.memoryLayout.JOYP &= ~0x4;
if (joypadInput.DOWN) if (joypadInput.DOWN)
addressSpace.memoryLayout.JOYP &= ~0x8; addressSpace.memoryLayout.JOYP &= ~0x8;
} }
if (buttons && dpad) { if (buttons && dpad) {
addressSpace.memoryLayout.JOYP |= 0xCF; addressSpace.memoryLayout.JOYP |= 0xCF;
if (joypadInput.RIGHT && joypadInput.A) if (joypadInput.RIGHT && joypadInput.A)
addressSpace.memoryLayout.JOYP &= ~0x1; addressSpace.memoryLayout.JOYP &= ~0x1;
if (joypadInput.LEFT && joypadInput.B) if (joypadInput.LEFT && joypadInput.B)
addressSpace.memoryLayout.JOYP &= ~0x2; addressSpace.memoryLayout.JOYP &= ~0x2;
if (joypadInput.UP && joypadInput.SELECT) if (joypadInput.UP && joypadInput.SELECT)
addressSpace.memoryLayout.JOYP &= ~0x4; addressSpace.memoryLayout.JOYP &= ~0x4;
if (joypadInput.DOWN && joypadInput.START) if (joypadInput.DOWN && joypadInput.START)
addressSpace.memoryLayout.JOYP &= ~0x8; addressSpace.memoryLayout.JOYP &= ~0x8;
} }
} }

View File

@@ -1,94 +1,91 @@
#include <string>
#include <filesystem>
#include <vector>
#include "3rdParty/json.hpp" #include "3rdParty/json.hpp"
#include "gameboy.hpp" #include "gameboy.hpp"
#include <filesystem>
#include <string>
#include <vector>
namespace fs = std::filesystem; namespace fs = std::filesystem;
using json = nlohmann::json; using json = nlohmann::json;
void runJSONTests(GameBoy* gb); void runJSONTests(GameBoy *gb);
int main(int argc, char** argv) { int main(int argc, char **argv) {
if (argc != 3) { if (argc != 3) {
std::cerr << "Usage: " << argv[0] << " <bios> <game>\n" << std::endl; std::cerr << "Usage: " << argv[0] << " <bios> <game>\n" << std::endl;
return 1; return 1;
} }
auto* gb = new GameBoy(); auto *gb = new GameBoy();
gb->SDL2setup(); gb->SDL2setup();
//runJSONTests(gb); // runJSONTests(gb);
gb->start(argv[1], argv[2]); gb->start(argv[1], argv[2]);
gb->SDL2destroy(); gb->SDL2destroy();
delete gb; delete gb;
return 0; return 0;
} }
void runJSONTests(GameBoy* gb) { void runJSONTests(GameBoy *gb) {
std::string path = "../tests/sm83/v1"; std::string path = "../tests/sm83/v1";
std::vector<std::string> testFiles; std::vector<std::string> testFiles;
int failed = 0; int failed = 0;
for (const auto& entry : fs::directory_iterator(path)) for (const auto &entry : fs::directory_iterator(path))
testFiles.emplace_back(entry.path()); testFiles.emplace_back(entry.path());
for (const auto &testFile : testFiles) {
std::ifstream file(testFile);
std::cout << "Running test: " << testFile << std::endl;
const json tests = json::parse(file);
for (const auto& testFile : testFiles) { for (auto &test : tests) {
std::ifstream file(testFile); // create state
std::cout << "Running test: " << testFile << std::endl; std::vector<std::tuple<Word, Byte>> initialRAM;
const json tests = json::parse(file); for (int i = 0; i < test["initial"]["ram"].size(); i++)
initialRAM.emplace_back(test["initial"]["ram"][i][0],
test["initial"]["ram"][i][1]);
for (auto& test : tests) { GameboyTestState initialState = {test["initial"]["pc"],
//create state test["initial"]["sp"],
std::vector<std::tuple<Word, Byte>> initialRAM; test["initial"]["a"],
for (int i = 0; i < test["initial"]["ram"].size(); i++) test["initial"]["f"],
initialRAM.emplace_back(test["initial"]["ram"][i][0], test["initial"]["ram"][i][1]); test["initial"]["b"],
test["initial"]["c"],
test["initial"]["d"],
test["initial"]["e"],
test["initial"]["h"],
test["initial"]["l"],
initialRAM};
GameboyTestState initialState = { // run
test["initial"]["pc"], GameboyTestState result = gb->runTest(initialState);
test["initial"]["sp"],
test["initial"]["a"],
test["initial"]["f"],
test["initial"]["b"],
test["initial"]["c"],
test["initial"]["d"],
test["initial"]["e"],
test["initial"]["h"],
test["initial"]["l"],
initialRAM
};
//run // compare new state to expected
GameboyTestState result = gb->runTest(initialState); std::vector<std::tuple<Word, Byte>> finalRAM;
for (int i = 0; i < test["final"]["ram"].size(); i++)
finalRAM.emplace_back(test["final"]["ram"][i][0],
test["final"]["ram"][i][1]);
//compare new state to expected GameboyTestState finalState = {test["final"]["pc"],
std::vector<std::tuple<Word, Byte>> finalRAM; test["final"]["sp"],
for (int i = 0; i < test["final"]["ram"].size(); i++) test["final"]["a"],
finalRAM.emplace_back(test["final"]["ram"][i][0], test["final"]["ram"][i][1]); test["final"]["f"],
test["final"]["b"],
test["final"]["c"],
test["final"]["d"],
test["final"]["e"],
test["final"]["h"],
test["final"]["l"],
finalRAM};
GameboyTestState finalState = { if (finalState != result) {
test["final"]["pc"], std::cout << "Test " << testFile << " failed!" << std::endl;
test["final"]["sp"], failed += 1;
test["final"]["a"], break;
test["final"]["f"], }
test["final"]["b"], }
test["final"]["c"], }
test["final"]["d"], if (!failed)
test["final"]["e"], std::cout << "Success!" << std::endl;
test["final"]["h"], else
test["final"]["l"], std::cout << failed << "/" << testFiles.size() << " failed!" << std::endl;
finalRAM
};
if (finalState != result) {
std::cout << "Test " << testFile << " failed!" << std::endl;
failed += 1;
break;
}
}
}
if (!failed)
std::cout << "Success!" << std::endl;
else
std::cout << failed << "/" << testFiles.size() << " failed!" << std::endl;
} }

View File

@@ -1,135 +1,182 @@
#include "addressSpace.hpp" #include "addressSpace.hpp"
#include "defines.hpp"
void AddressSpace::determineMBCInfo() { void AddressSpace::determineMBCInfo() {
MBC = static_cast<MBCType>(memoryLayout.romBank0[0x147]); MBC = static_cast<MBCType_enum>(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);
const Byte ramSize = memoryLayout.romBank0[0x0149]; const Byte ramSize = memoryLayout.romBank0[0x0149];
switch (ramSize) { switch (ramSize) {
case 0x02: case 0x02:
externalRamSize = 8196; externalRamSize = 8196;
externalRamBanks = 1; externalRamBanks = 1;
break; break;
case 0x03: case 0x03:
externalRamSize = 32768; externalRamSize = 32768;
externalRamBanks = 4; externalRamBanks = 4;
break; break;
case 0x04: case 0x04:
externalRamSize = 131072; externalRamSize = 131072;
externalRamBanks = 16; externalRamBanks = 16;
break; break;
case 0x05: case 0x05:
externalRamSize = 65536; externalRamSize = 65536;
externalRamBanks = 8; externalRamBanks = 8;
break; break;
default: default:
externalRamSize = 0; externalRamSize = 0;
externalRamBanks = 0; externalRamBanks = 0;
break; break;
} }
if (MBC == MBC2 || MBC == 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 AddressSpace::testMBCWrite(const Word address) { Byte *AddressSpace::MBCWrite(const Word address) {
if (address <= 0x7FFF) if (MBC == MBC1) {
return true; if (address <= 0x1FFF)
return false; return &ramEnable;
} if (address <= 0x3FFF)
return &romBankRegister;
Byte* AddressSpace::MBCRead(const Word address) { if (address <= 0x5FFF)
if (MBC == MBC1) { return &twoBitBankRegister;
if (address <= 0x1FFF) if (address <= 0x7FFF)
return &ramEnable; return &romRamSelect;
if (address <= 0x3FFF) } else if (MBC == MBC1Ram || MBC == MBC1RamBattery) {
return &romBankRegister; if (address <= 0x1FFF)
if (address <= 0x5FFF) return &ramEnable;
return &twoBitBankRegister; // bits 0-4
if (address <= 0x7FFF) if (address <= 0x3FFF)
return &romRamSelect; return &romBankRegister;
} if (address <= 0x5FFF) {
if (MBC == MBC1Ram || MBC == MBC1RamBattery) { return &twoBitBankRegister;
if (address <= 0x1FFF) }
return &ramEnable; if (address <= 0x7FFF)
//bits 0-4 return &romRamSelect;
if (address <= 0x3FFF) } else if (MBC == MBC2 || MBC == MBC2Battery) {
return &romBankRegister; if (address <= 0x3FFF) {
if (address <= 0x5FFF) { if (address & 0x0100)
return &twoBitBankRegister; return &romBankRegister;
} else
if (address <= 0x7FFF) return &ramEnable;
return &romRamSelect; }
} } else if (MBC == MBC3 || MBC == MBC3TimerBattery) {
return &dummyVal; if (address <= 0x1FFF)
return &ramEnable;
} else if (MBC == MBC3Ram || MBC == MBC3RamBattery ||
MBC == MBC3TimerRamBattery) {
if (address <= 0x1FFF)
return &ramEnable;
if (address <= 0x3FFF)
return &romBankRegister;
if (address <= 0x5FFF)
return &ramBankRTCRegister;
}
return &dummyVal;
} }
void AddressSpace::MBCUpdate() { void AddressSpace::MBCUpdate() {
//TODO: multicart roms need to be able to switch the first rom bank as well // TODO: multicart roms need to be able to switch the first rom bank as well
//see: https://gbdev.io/pandocs/MBC1.html // see: https://gbdev.io/pandocs/MBC1.html
if (MBC == MBC1) { if (MBC == MBC1) {
//Selected ROM Bank = (Secondary Bank << 5) + ROM Bank // Selected ROM Bank = (Secondary Bank << 5) + ROM Bank
romBankRegister &= 0x1F; romBankRegister &= 0b11111;
twoBitBankRegister &= 0x3; twoBitBankRegister &= 0b11;
//512 KiB can only have 8KiB of ram // 512 KiB can only have 8KiB of ram
if (romSize >= 524288) { if (romSize >= 524288) {
if (romBankRegister == 0) if (romBankRegister == 0)
selectedRomBank = (twoBitBankRegister << 5) + 1; selectedRomBank = (twoBitBankRegister << 5) + 1;
selectedRomBank = (twoBitBankRegister << 5) + romBankRegister; selectedRomBank = (twoBitBankRegister << 5) + romBankRegister;
} } else {
else { if (romBankRegister == 0)
if (romBankRegister == 0) selectedRomBank = 1;
selectedRomBank = 1; else
else selectedRomBank = romBankRegister;
selectedRomBank = romBankRegister; }
} if (romBankRegister == 0x20 || romBankRegister == 0x40 ||
selectedExternalRamBank = 0; romBankRegister == 0x60)
romBankRegister += 1;
selectedExternalRamBank = 0;
loadRomBank(); loadRomBank();
loadRamBank(); loadRamBank();
} } else if (MBC == MBC1Ram || MBC == MBC1RamBattery) {
if (MBC == MBC1Ram || MBC == MBC1RamBattery) { // Selected ROM Bank = (Secondary Bank << 5) + ROM Bank
//Selected ROM Bank = (Secondary Bank << 5) + ROM Bank romBankRegister &= 0b11111;
romBankRegister &= 0x1F; twoBitBankRegister &= 0b11;
twoBitBankRegister &= 0x3;
//512 KiB can only have 8KiB of ram // 512 KiB can only have 8KiB of ram
if (romSize >= 524288) { if (romSize >= 524288) {
if (romBankRegister == 0) if (romBankRegister == 0)
selectedRomBank = (twoBitBankRegister << 5) + 1; selectedRomBank = (twoBitBankRegister << 5) + 1;
selectedRomBank = (twoBitBankRegister << 5) + romBankRegister; selectedRomBank = (twoBitBankRegister << 5) + romBankRegister;
selectedExternalRamBank = 0; selectedExternalRamBank = 0;
} } else {
else { if (romBankRegister == 0)
if (romBankRegister == 0) selectedRomBank = 1;
selectedRomBank = 1; else
else selectedRomBank = romBankRegister;
selectedRomBank = romBankRegister; selectedExternalRamBank = twoBitBankRegister;
selectedExternalRamBank = twoBitBankRegister; }
} if (romBankRegister == 0x20 || romBankRegister == 0x40 ||
loadRomBank(); romBankRegister == 0x60)
loadRamBank(); romBankRegister += 1;
}
loadRomBank();
loadRamBank();
} else if (MBC == MBC2 || MBC == MBC2Battery) {
selectedRomBank = romBankRegister & 0x0F;
selectedExternalRamBank = 0;
loadRomBank();
loadRamBank();
} else if (MBC == MBC3 || MBC == MBC3TimerBattery) {
selectedRomBank = romBankRegister & 0x7F;
selectedExternalRamBank = 0;
if (MBC == MBC3TimerBattery) {
if(ramBankRTCRegister >= 0x8 && ramBankRTCRegister <= 0xC)
enabledRTCRegister = static_cast<enabledRTCRegister_enum>(ramBankRTCRegister);
else
enabledRTCRegister = NONE;
}
loadRomBank();
loadRamBank();
} else if (MBC == MBC3Ram || MBC == MBC3RamBattery ||
MBC == MBC3TimerRamBattery) {
selectedRomBank = romBankRegister & 0x7F;
selectedExternalRamBank = ramBankRTCRegister & 0b11;
if (MBC == MBC3TimerRamBattery) {
if(ramBankRTCRegister >= 0x8 && ramBankRTCRegister <= 0xC)
enabledRTCRegister = static_cast<enabledRTCRegister_enum>(ramBankRTCRegister);
else
enabledRTCRegister = NONE;
}
loadRomBank();
loadRamBank();
}
} }
void AddressSpace::loadRomBank() { void AddressSpace::loadRomBank() {
memoryLayout.romBankSwitch = game.data() + (ROM_BANK_SIZE * selectedRomBank); memoryLayout.romBankSwitch = game.data() + (ROM_BANK_SIZE * selectedRomBank);
} }
void AddressSpace::createRamBank() { void AddressSpace::createRamBank() {
if (externalRamSize) { if (externalRamSize) {
cartridgeRam = new Byte[externalRamSize]; cartridgeRam = new Byte[externalRamSize];
memoryLayout.externalRam = cartridgeRam; memoryLayout.externalRam = cartridgeRam;
} }
} }
void AddressSpace::loadRamBank() { void AddressSpace::loadRamBank() {
if (cartridgeRam != nullptr) if (cartridgeRam != nullptr) {
memoryLayout.externalRam = cartridgeRam + (RAM_BANK_SIZE * selectedExternalRamBank); memoryLayout.externalRam =
cartridgeRam + (RAM_BANK_SIZE * selectedExternalRamBank);
}
} }

File diff suppressed because it is too large Load Diff

View File

@@ -1,390 +1,409 @@
#include "gameboy.hpp"
#include "defines.hpp" #include "defines.hpp"
#include "gameboy.hpp"
#include <algorithm> #include <algorithm>
#include <cstdint> #include <cstdint>
#include <iostream> #include <iostream>
#include <utility> #include <utility>
bool GameBoy::LCDCBitEnabled(const Byte bit) const { bool GameBoy::LCDCBitEnabled(const Byte bit) const {
return readOnlyAddressSpace.memoryLayout.LCDC & static_cast<Byte>(1 << bit); return readOnlyAddressSpace.memoryLayout.LCDC & static_cast<Byte>(1 << bit);
} }
bool GameBoy::oamBitEnabled(const Byte oamAttributeByte, const Byte bit) { bool GameBoy::oamBitEnabled(const Byte oamAttributeByte, const Byte bit) {
return oamAttributeByte & static_cast<Byte>(1 << bit); return oamAttributeByte & static_cast<Byte>(1 << bit);
} }
unsigned int GameBoy::getColourFromPalette(const Byte palette) { unsigned int GameBoy::getColourFromPalette(const Byte palette) {
switch (palette & 0x3) { switch (palette & 0x3) {
case 0: case 0:
return WHITE; return WHITE;
case 1: case 1:
return LIGHT_GRAY; return LIGHT_GRAY;
case 2: case 2:
return DARK_GRAY; return DARK_GRAY;
case 3: case 3:
return BLACK; return BLACK;
default: default:
std::unreachable(); std::unreachable();
} }
} }
void GameBoy::ppuUpdate() { void GameBoy::ppuUpdate() {
//test for HBlank // test for HBlank
checkPPUMode(); checkPPUMode();
// TODO: // TODO:
// 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
// Road Rage and Zerd no Denetsu rely on this // written Road Rage and Zerd no Denetsu rely on this
// 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
const bool hBlankInterruptEnabled = readOnlyAddressSpace.memoryLayout.STAT & (1 << 3); // specific modes)
const bool drawingInterruptEnabled = readOnlyAddressSpace.memoryLayout.STAT & (1 << 4); const bool hBlankInterruptEnabled =
const bool oamInterruptEnabled = readOnlyAddressSpace.memoryLayout.STAT & (1 << 5); readOnlyAddressSpace.memoryLayout.STAT & (1 << 3);
const bool previousInterruptLine = statInteruptLine; const bool drawingInterruptEnabled =
readOnlyAddressSpace.memoryLayout.STAT & (1 << 4);
const bool oamInterruptEnabled =
readOnlyAddressSpace.memoryLayout.STAT & (1 << 5);
const bool previousInterruptLine = statInteruptLine;
if (currentMode == PPUMode::mode0 && hBlankInterruptEnabled || if (currentMode == PPUMode_enum::mode0 && hBlankInterruptEnabled ||
currentMode == PPUMode::mode3 && drawingInterruptEnabled || currentMode == PPUMode_enum::mode3 && drawingInterruptEnabled ||
currentMode == PPUMode::mode2 && oamInterruptEnabled) { currentMode == PPUMode_enum::mode2 && oamInterruptEnabled) {
statInteruptLine = true; statInteruptLine = true;
} } else {
else { statInteruptLine = false;
statInteruptLine = false; }
}
const bool lyLycInterruptEnabled = readOnlyAddressSpace.memoryLayout.STAT & (1 << 6); const bool lyLycInterruptEnabled =
readOnlyAddressSpace.memoryLayout.STAT & (1 << 6);
if (readOnlyAddressSpace.memoryLayout.LY == readOnlyAddressSpace.memoryLayout.LYC) { if (readOnlyAddressSpace.memoryLayout.LY ==
addressSpace.memoryLayout.STAT |= (1 << 2); readOnlyAddressSpace.memoryLayout.LYC) {
if (lyLycInterruptEnabled) addressSpace.memoryLayout.STAT |= (1 << 2);
statInteruptLine = true; if (lyLycInterruptEnabled)
} statInteruptLine = true;
else { } else {
addressSpace.memoryLayout.STAT &= ~(1 << 2); addressSpace.memoryLayout.STAT &= ~(1 << 2);
} }
if (statInteruptLine && !previousInterruptLine) if (statInteruptLine && !previousInterruptLine)
addressSpace.memoryLayout.IF |= 1 << LCD_STAT_INTERRUPT; addressSpace.memoryLayout.IF |= 1 << LCD_STAT_INTERRUPT;
} }
void GameBoy::checkPPUMode() { void GameBoy::checkPPUMode() {
// Check the PPU mode (HBlank, VBlank, OAM Search, or Pixel Transfer) // Check the PPU mode (HBlank, VBlank, OAM Search, or Pixel Transfer)
const uint64_t cyclesSinceScanline = cyclesSinceLastScanline(); const uint64_t cyclesSinceScanline = cyclesSinceLastScanline();
switch (currentMode) { switch (currentMode) {
//hblank AND vblank // hblank AND vblank
case 0: case 0:
case 1: case 1:
if (cyclesSinceScanline > SCANLINE_DURATION) { if (cyclesSinceScanline > SCANLINE_DURATION) {
lastScanline = ppuCycles - (cyclesSinceScanline - SCANLINE_DURATION); lastScanline = ppuCycles - (cyclesSinceScanline - SCANLINE_DURATION);
incLY(); incLY();
} }
break; break;
case 2: case 2:
if (cyclesSinceScanline > MODE2_DURATION) { if (cyclesSinceScanline > MODE2_DURATION) {
setPPUMode(PPUMode::mode3); setPPUMode(PPUMode_enum::mode3);
} }
break; break;
case 3: case 3:
if (cyclesSinceScanline > MODE2_DURATION + MODE3_MIN_DURATION) { if (cyclesSinceScanline > MODE2_DURATION + MODE3_MIN_DURATION) {
drawLine(); drawLine();
setPPUMode(PPUMode::mode0); setPPUMode(PPUMode_enum::mode0);
} }
break; break;
} }
} }
void GameBoy::incLY() { void GameBoy::incLY() {
addressSpace.memoryLayout.LY += 1; addressSpace.memoryLayout.LY += 1;
setPPUMode(PPUMode::mode2); setPPUMode(PPUMode_enum::mode2);
if (addressSpace.memoryLayout.LY > SCANLINES_PER_FRAME - 1) { if (addressSpace.memoryLayout.LY > SCANLINES_PER_FRAME - 1) {
addressSpace.memoryLayout.LY = 0; addressSpace.memoryLayout.LY = 0;
windowLineCounter = 0; windowLineCounter = 0;
} } else if (addressSpace.memoryLayout.LY == 144) {
else if (addressSpace.memoryLayout.LY == 144) { // VBlank Period
// VBlank Period SDL2present();
SDL2present(); setPPUMode(PPUMode_enum::mode1);
setPPUMode(PPUMode::mode1); addressSpace.memoryLayout.IF |= 0x1;
addressSpace.memoryLayout.IF |= 0x1; }
}
} }
uint64_t GameBoy::cyclesSinceLastScanline() const { uint64_t GameBoy::cyclesSinceLastScanline() const {
const uint64_t difference = ppuCycles - lastScanline; const uint64_t difference = ppuCycles - lastScanline;
return difference; return difference;
} }
uint64_t GameBoy::cyclesSinceLastRefresh() const { uint64_t GameBoy::cyclesSinceLastRefresh() const {
const uint64_t difference = ppuCycles - lastRefresh; const uint64_t difference = ppuCycles - lastRefresh;
return difference; return difference;
} }
void GameBoy::setPPUMode(const PPUMode mode) { void GameBoy::setPPUMode(const PPUMode_enum mode) {
switch (mode) { switch (mode) {
case mode0: case mode0:
addressSpace.memoryLayout.STAT &= ~0x03; addressSpace.memoryLayout.STAT &= ~0x03;
currentMode = mode0; currentMode = mode0;
break; break;
case mode1: case mode1:
addressSpace.memoryLayout.STAT &= ~0x03; addressSpace.memoryLayout.STAT &= ~0x03;
addressSpace.memoryLayout.STAT |= 0x01; addressSpace.memoryLayout.STAT |= 0x01;
currentMode = mode1; currentMode = mode1;
break; break;
case mode2: case mode2:
addressSpace.memoryLayout.STAT &= ~0x03; addressSpace.memoryLayout.STAT &= ~0x03;
addressSpace.memoryLayout.STAT |= 0x02; addressSpace.memoryLayout.STAT |= 0x02;
currentMode = mode2; currentMode = mode2;
break; break;
case mode3: case mode3:
addressSpace.memoryLayout.STAT &= ~0x03; addressSpace.memoryLayout.STAT &= ~0x03;
addressSpace.memoryLayout.STAT |= 0x03; addressSpace.memoryLayout.STAT |= 0x03;
currentMode = mode3; currentMode = mode3;
break; break;
} }
//7th bit is unused but always set // 7th bit is unused but always set
addressSpace.memoryLayout.STAT |= 0x80; addressSpace.memoryLayout.STAT |= 0x80;
} }
void GameBoy::drawLine() { void GameBoy::drawLine() {
const uint8_t line = readOnlyAddressSpace.memoryLayout.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;
// 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;
std::fill_n(currentLinePixels, RESOLUTION_X, 0xFFFFFFFF); std::fill_n(currentLinePixels, RESOLUTION_X, 0xFFFFFFFF);
const uint16_t backgroundMapAddr =
LCDCBitEnabled(BG_TILE_MAP_AREA) ? 0x9C00 : 0x9800;
const uint16_t windowMapAddr =
LCDCBitEnabled(WINDOW_TILE_MAP_AREA) ? 0x9C00 : 0x9800;
const uint16_t tileDataTableAddr =
LCDCBitEnabled(BG_WINDOW_TILE_DATA_AREA) ? 0x8000 : 0x8800;
const bool signedIndex = !LCDCBitEnabled(BG_WINDOW_TILE_DATA_AREA);
const uint16_t backgroundMapAddr = LCDCBitEnabled(BG_TILE_MAP_AREA) ? 0x9C00 : 0x9800; // BG
const uint16_t windowMapAddr = LCDCBitEnabled(WINDOW_TILE_MAP_AREA) ? 0x9C00 : 0x9800; if (LCDCBitEnabled(BG_WINDOW_ENABLE)) {
const uint16_t tileDataTableAddr = LCDCBitEnabled(BG_WINDOW_TILE_DATA_AREA) ? 0x8000 : 0x8800; for (int pixel = 0; pixel < RESOLUTION_X; pixel++) {
const bool signedIndex = !LCDCBitEnabled(BG_WINDOW_TILE_DATA_AREA); const uint16_t xIndex =
(pixel + readOnlyAddressSpace.memoryLayout.SCX) % 256;
// 256 pixels in total BG width
const uint16_t yIndex =
(line + readOnlyAddressSpace.memoryLayout.SCY) % 256;
// 256 pixels in total BG height
//BG const uint16_t tileUpper = (yIndex / 8) << 5;
if (LCDCBitEnabled(BG_WINDOW_ENABLE)) { const uint16_t tileLower = xIndex / 8 & 0x1F;
for (int pixel = 0; pixel < RESOLUTION_X; pixel++) { const uint16_t tileIndex = tileUpper + tileLower;
const uint16_t xIndex = (pixel + readOnlyAddressSpace.memoryLayout.SCX) % 256;
// 256 pixels in total BG width
const uint16_t yIndex = (line + readOnlyAddressSpace.memoryLayout.SCY) % 256;
// 256 pixels in total BG height
const uint16_t tileUpper = (yIndex / 8) << 5; const uint16_t tileAddr = backgroundMapAddr + tileIndex;
const uint16_t tileLower = xIndex / 8 & 0x1F; const int16_t tileID =
const uint16_t tileIndex = tileUpper + tileLower; signedIndex ? static_cast<int16_t>(readOnlyAddressSpace[tileAddr])
: readOnlyAddressSpace[tileAddr];
const uint16_t tileAddr = backgroundMapAddr + tileIndex; uint16_t tileDataAddr;
const int16_t tileID = signedIndex if (signedIndex)
? static_cast<int16_t>(readOnlyAddressSpace[tileAddr]) tileDataAddr = tileDataTableAddr +
: readOnlyAddressSpace[tileAddr]; (((tileID + 128) % 256) << 4); // For signed, wrap around
else
tileDataAddr = tileDataTableAddr + (tileID * 16);
uint16_t tileDataAddr; const uint8_t lineOffset = yIndex % 8;
if (signedIndex) const uint8_t tileRowData1 =
tileDataAddr = tileDataTableAddr + (((tileID + 128) % 256) << 4); // For signed, wrap around readOnlyAddressSpace[tileDataAddr + (lineOffset * 2)];
else const uint8_t tileRowData2 =
tileDataAddr = tileDataTableAddr + (tileID * 16); readOnlyAddressSpace[tileDataAddr + (lineOffset * 2) + 1];
// get pixel data (2 bits)
const uint8_t colourBit = 7 - (xIndex % 8);
const uint8_t colourNum = ((tileRowData2 >> colourBit) & 0x1) << 1 |
((tileRowData1 >> colourBit) & 0x1);
const uint8_t lineOffset = yIndex % 8; // Apply the BGP register for palette mapping
const uint8_t tileRowData1 = readOnlyAddressSpace[tileDataAddr + (lineOffset * 2)]; const uint8_t palette =
const uint8_t tileRowData2 = readOnlyAddressSpace[tileDataAddr + (lineOffset * 2) + 1]; (readOnlyAddressSpace.memoryLayout.BGP >> (colourNum * 2)) & 0x3;
//get pixel data (2 bits) currentLinePixels[pixel] = getColourFromPalette(palette);
const uint8_t colourBit = 7 - (xIndex % 8); }
const uint8_t colourNum = ((tileRowData2 >> colourBit) & 0x1) << 1 | ((tileRowData1 >> colourBit) & 0x1);
// Apply the BGP register for palette mapping // For the window to be displayed on a scanline, the following conditions
const uint8_t palette = (readOnlyAddressSpace.memoryLayout.BGP >> (colourNum * 2)) & 0x3; // must be met: WY condition was triggered: i.e. at some point in this frame
// the value of WY was equal to LY (checked at the start of Mode 2 only) WX
// condition was triggered: i.e. the current X coordinate being rendered + 7
// was equal to WX Window enable bit in LCDC is set
// Window
const uint8_t windowY = readOnlyAddressSpace.memoryLayout.WY;
const int16_t windowX =
static_cast<int16_t>(readOnlyAddressSpace.memoryLayout.WX - 7);
if (LCDCBitEnabled(WINDOW_ENABLE) && windowX >= 0 &&
windowX < RESOLUTION_X && line >= windowY) {
for (int pixel = windowX; pixel < RESOLUTION_X; pixel++) {
const uint16_t yIndex = windowLineCounter;
const uint16_t windowTileUpper = (yIndex / 8) << 5;
currentLinePixels[pixel] = getColourFromPalette(palette); const uint16_t xIndex = pixel - windowX;
} const uint16_t windowTileLower = (xIndex / 8) & 0x1F;
// For the window to be displayed on a scanline, the following conditions must be met: const uint16_t tileIndex = windowTileUpper + windowTileLower;
// WY condition was triggered: i.e. at some point in this frame the value of WY was equal to LY (checked at the start of Mode 2 only) const uint16_t tileAddr = windowMapAddr + tileIndex;
// WX condition was triggered: i.e. the current X coordinate being rendered + 7 was equal to WX
// Window enable bit in LCDC is set
//Window
const uint8_t windowY = readOnlyAddressSpace.memoryLayout.WY;
const int16_t windowX = static_cast<int16_t>(readOnlyAddressSpace.memoryLayout.WX - 7);
if (LCDCBitEnabled(WINDOW_ENABLE) && windowX >= 0 && windowX < RESOLUTION_X && line >= windowY) {
for (int pixel = windowX; pixel < RESOLUTION_X; pixel++) {
const uint16_t yIndex = windowLineCounter;
const uint16_t windowTileUpper = (yIndex / 8) << 5;
const uint16_t xIndex = pixel - windowX; const int16_t tileID =
const uint16_t windowTileLower = (xIndex / 8) & 0x1F; signedIndex ? static_cast<int16_t>(readOnlyAddressSpace[tileAddr])
: readOnlyAddressSpace[tileAddr];
const uint16_t tileIndex = windowTileUpper + windowTileLower; uint16_t tileDataAddr;
const uint16_t tileAddr = windowMapAddr + tileIndex; if (signedIndex)
tileDataAddr = tileDataTableAddr + (((tileID + 128) % 256) *
16); // For signed, wrap around
else
tileDataAddr = tileDataTableAddr + (tileID * 16);
const int16_t tileID = signedIndex const uint8_t lineOffset = yIndex % 8;
? static_cast<int16_t>(readOnlyAddressSpace[tileAddr]) const uint8_t tileRowData1 =
: readOnlyAddressSpace[tileAddr]; readOnlyAddressSpace[tileDataAddr + (lineOffset * 2)];
const uint8_t tileRowData2 =
readOnlyAddressSpace[tileDataAddr + (lineOffset * 2) + 1];
uint16_t tileDataAddr; // get pixel data (2 bits)
if (signedIndex) const uint8_t colourBit = 7 - (xIndex % 8);
tileDataAddr = tileDataTableAddr + (((tileID + 128) % 256) * 16); // For signed, wrap around const uint8_t colourNum = ((tileRowData2 >> colourBit) & 0x1) << 1 |
else ((tileRowData1 >> colourBit) & 0x1);
tileDataAddr = tileDataTableAddr + (tileID * 16);
// Apply the BGP register for palette mapping
const uint8_t palette =
(readOnlyAddressSpace.memoryLayout.BGP >> (colourNum * 2)) & 0x3;
const uint8_t lineOffset = yIndex % 8; currentLinePixels[pixel] = getColourFromPalette(palette);
const uint8_t tileRowData1 = readOnlyAddressSpace[tileDataAddr + (lineOffset * 2)]; }
const uint8_t tileRowData2 = readOnlyAddressSpace[tileDataAddr + (lineOffset * 2) + 1]; windowLineCounter += 1;
}
}
// oam/sprites
if (LCDCBitEnabled(OBJ_ENABLE)) {
uint32_t oamPixels[RESOLUTION_X];
std::fill_n(oamPixels, RESOLUTION_X, 0);
//get pixel data (2 bits) constexpr Word oamAddrStart = 0xFE00;
const uint8_t colourBit = 7 - (xIndex % 8); const int spriteHeight = LCDCBitEnabled(OBJ_SIZE) ? 16 : 8;
const uint8_t colourNum = ((tileRowData2 >> colourBit) & 0x1) << 1 | ((tileRowData1 >> colourBit) &
0x1);
// Apply the BGP register for palette mapping int objects[10] = {0};
const uint8_t palette = (readOnlyAddressSpace.memoryLayout.BGP >> (colourNum * 2)) & 0x3; int found = 0;
currentLinePixels[pixel] = getColourFromPalette(palette); // oam hold 40 objects
} // find first 10
windowLineCounter += 1; for (int i = 0; i < 40 && found < 10; i++) {
} const int offset = i * 4;
} const int yPos = readOnlyAddressSpace[oamAddrStart + offset] - 16;
// oam/sprites
if (LCDCBitEnabled(OBJ_ENABLE)) {
uint32_t oamPixels[RESOLUTION_X];
std::fill_n(oamPixels, RESOLUTION_X, 0);
constexpr if (line >= yPos && line <= yPos + spriteHeight - 1) {
Word oamAddrStart = 0xFE00; objects[found] = oamAddrStart + offset;
const int spriteHeight = LCDCBitEnabled(OBJ_SIZE) ? 16 : 8; found += 1;
}
}
int objects[10] = {0}; // sort by xPos (lower has higher priority when rendering) and then earlier
int found = 0; // objects
for (int i = 0; i < found; i++) {
for (int j = 0; j < found - i - 1; j++) {
const int xPos1 = readOnlyAddressSpace[objects[j] + 1];
const int xPos2 = readOnlyAddressSpace[objects[j + 1] + 1];
//oam hold 40 objects if (xPos1 > xPos2) {
//find first 10 const int tmp = objects[j];
for (int i = 0; i < 40 && found < 10; i++) { objects[j] = objects[j + 1];
const int offset = i * 4; objects[j + 1] = tmp;
const int yPos = readOnlyAddressSpace[oamAddrStart + offset] - 16; }
}
}
if (line >= yPos && line <= yPos + spriteHeight - 1) { for (int objectIndex = found - 1; objectIndex >= 0; objectIndex--) {
objects[found] = oamAddrStart + offset; const int yPos = readOnlyAddressSpace[objects[objectIndex]] - 16;
found += 1; const int xPos = readOnlyAddressSpace[objects[objectIndex] + 1] - 8;
} const int tileIndex = readOnlyAddressSpace[objects[objectIndex] + 2];
} const Byte attributes = readOnlyAddressSpace[objects[objectIndex] + 3];
//sort by xPos (lower has higher priority when rendering) and then earlier objects const bool priority = oamBitEnabled(attributes, PRIORITY);
for (int i = 0; i < found; i++) { const bool yFlip = oamBitEnabled(attributes, Y_FLIP);
for (int j = 0; j < found - i - 1; j++) { const bool xFlip = oamBitEnabled(attributes, X_FLIP);
const int xPos1 = readOnlyAddressSpace[objects[j] + 1]; // get obp0 or obj1
const int xPos2 = readOnlyAddressSpace[objects[j + 1] + 1]; const Byte objPalette = oamBitEnabled(attributes, OBJ_PALETTE)
? addressSpace.memoryLayout.OBP1
: addressSpace.memoryLayout.OBP0;
if (xPos1 > xPos2) { for (int pixel = xPos; pixel < RESOLUTION_X && pixel < xPos + 8;
const int tmp = objects[j]; pixel++) {
objects[j] = objects[j + 1]; constexpr Word objectTileAddr = 0x8000;
objects[j + 1] = tmp; if (pixel < 0)
} continue;
}
}
const uint32_t colour = currentLinePixels[pixel];
const Byte BGP = readOnlyAddressSpace.memoryLayout.BGP;
if (priority && (colour == getColourFromPalette((BGP >> 2) & 0x3) ||
colour == getColourFromPalette((BGP >> 4) & 0x3) ||
colour == getColourFromPalette((BGP >> 6) & 0x3))) {
oamPixels[pixel] = colour;
continue;
}
for (int objectIndex = found - 1; objectIndex >= 0; objectIndex--) { Byte objectX = pixel - xPos;
const int yPos = readOnlyAddressSpace[objects[objectIndex]] - 16; Byte objectY = line - yPos;
const int xPos = readOnlyAddressSpace[objects[objectIndex] + 1] - 8; if (xFlip)
const int tileIndex = readOnlyAddressSpace[objects[objectIndex] + 2]; objectX = 7 - objectX;
const Byte attributes = readOnlyAddressSpace[objects[objectIndex] + 3]; if (yFlip)
objectY = (spriteHeight - 1) - objectY;
const bool priority = oamBitEnabled(attributes, PRIORITY); const Word objTileDataAddr =
const bool yFlip = oamBitEnabled(attributes, Y_FLIP); spriteHeight == 8 ? objectTileAddr + (tileIndex * 16)
const bool xFlip = oamBitEnabled(attributes, X_FLIP); : objectTileAddr + ((tileIndex & 0xFE) * 16);
//get obp0 or obj1
const Byte objPalette = oamBitEnabled(attributes, OBJ_PALETTE)
? addressSpace.memoryLayout.OBP1
: addressSpace.memoryLayout.OBP0;
for (int pixel = xPos; pixel < RESOLUTION_X && pixel < xPos + 8; pixel++) { const Byte tileRow = objectY * 2;
constexpr const Byte tileRowData1 =
Word objectTileAddr = 0x8000; readOnlyAddressSpace[objTileDataAddr + tileRow];
if (pixel < 0) const Byte tileRowData2 =
continue; readOnlyAddressSpace[objTileDataAddr + tileRow + 1];
const uint32_t colour = currentLinePixels[pixel]; const int bit = 7 - objectX;
const Byte BGP = readOnlyAddressSpace.memoryLayout.BGP; const int colorIndex =
if (priority && (colour == getColourFromPalette((BGP >> 2) & 0x3) || ((tileRowData2 >> bit) & 1) << 1 | ((tileRowData1 >> bit) & 1);
colour == getColourFromPalette((BGP >> 4) & 0x3) ||
colour == getColourFromPalette((BGP >> 6) & 0x3)
)) {
oamPixels[pixel] = colour;
continue;
}
Byte objectX = pixel - xPos; // 0 is always transparent
Byte objectY = line - yPos; if (colorIndex != 0) {
if (xFlip) const uint8_t paletteColor = (objPalette >> (colorIndex * 2)) & 0x3;
objectX = 7 - objectX; const uint32_t finalColor = getColourFromPalette(paletteColor);
if (yFlip) oamPixels[pixel] = finalColor;
objectY = (spriteHeight - 1) - objectY; } else if (oamPixels[pixel] == 0) {
oamPixels[pixel] = currentLinePixels[pixel];
}
}
}
const Word objTileDataAddr = spriteHeight == 8 // help ensure correct interaction with "BG OVER OBJ" flag
? objectTileAddr + (tileIndex * 16) for (int i = 0; i < RESOLUTION_X; i++) {
: objectTileAddr + ((tileIndex & 0xFE) * 16); if (oamPixels[i] != currentLinePixels[i] && oamPixels[i] != 0)
currentLinePixels[i] = oamPixels[i];
const Byte tileRow = objectY * 2; }
const Byte tileRowData1 = readOnlyAddressSpace[objTileDataAddr + tileRow]; }
const Byte tileRowData2 = readOnlyAddressSpace[objTileDataAddr + tileRow + 1];
const int bit = 7 - objectX;
const int colorIndex = ((tileRowData2 >> bit) & 1) << 1 | ((tileRowData1 >> bit) & 1);
// 0 is always transparent
if (colorIndex != 0) {
const uint8_t paletteColor = (objPalette >> (colorIndex * 2)) & 0x3;
const uint32_t finalColor = getColourFromPalette(paletteColor);
oamPixels[pixel] = finalColor;
}
else if (oamPixels[pixel] == 0) {
oamPixels[pixel] = currentLinePixels[pixel];
}
}
}
//help ensure correct interaction with "BG OVER OBJ" flag
for (int i = 0; i < RESOLUTION_X; i++) {
if (oamPixels[i] != currentLinePixels[i] && oamPixels[i] != 0)
currentLinePixels[i] = oamPixels[i];
}
}
} }
void GameBoy::SDL2setup() { void GameBoy::SDL2setup() {
SDL_Init(SDL_INIT_EVERYTHING); SDL_Init(SDL_INIT_EVERYTHING);
screen = SDL_CreateWindow("GameBoy++", screen = SDL_CreateWindow("GameBoy++", SDL_WINDOWPOS_UNDEFINED,
SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, RESOLUTION_X, RESOLUTION_Y,
RESOLUTION_X, RESOLUTION_Y, SDL_WINDOW_RESIZABLE);
0);
// Create an SDL renderer to draw on the window // Create an SDL renderer to draw on the window
renderer = SDL_CreateRenderer(screen, -1, SDL_RENDERER_PRESENTVSYNC | SDL_RENDERER_ACCELERATED); renderer = SDL_CreateRenderer(
screen, -1, SDL_RENDERER_PRESENTVSYNC | SDL_RENDERER_ACCELERATED);
// Create an SDL texture to hold the framebuffer data // Create an SDL texture to hold the framebuffer data
texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_ARGB8888, texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_ARGB8888,
SDL_TEXTUREACCESS_STREAMING, RESOLUTION_X, RESOLUTION_Y); SDL_TEXTUREACCESS_STREAMING, RESOLUTION_X,
RESOLUTION_Y);
} }
void GameBoy::SDL2destroy() const { void GameBoy::SDL2destroy() const {
SDL_DestroyTexture(texture); SDL_DestroyTexture(texture);
SDL_DestroyRenderer(renderer); SDL_DestroyRenderer(renderer);
SDL_DestroyWindow(screen); SDL_DestroyWindow(screen);
SDL_Quit(); SDL_Quit();
} }
void GameBoy::SDL2present() { void GameBoy::SDL2present() {
SDL_UpdateTexture(texture, nullptr, framebuffer, RESOLUTION_X * sizeof(uint32_t)); SDL_UpdateTexture(texture, nullptr, framebuffer,
SDL_RenderClear(renderer); RESOLUTION_X * sizeof(uint32_t));
SDL_RenderCopy(renderer, texture, nullptr, nullptr); SDL_RenderClear(renderer);
SDL_RenderCopy(renderer, texture, nullptr, nullptr);
SDL_RenderSetLogicalSize(renderer, RESOLUTION_X, RESOLUTION_Y);
frameTime = SDL_GetTicks() - frameStart;
frameTime = SDL_GetTicks() - frameStart; if (frameDelay > frameTime) {
SDL_Delay(frameDelay - frameTime);
}
if (frameDelay > frameTime) { SDL_RenderPresent(renderer);
SDL_Delay(frameDelay - frameTime); frameStart = SDL_GetTicks();
} rendered = true;
SDL_RenderPresent(renderer);
frameStart = SDL_GetTicks();
rendered = true;
} }

View File

@@ -1,43 +1,39 @@
#ifndef TESTING_HPP #ifndef TESTING_HPP
#define TESTING_HPP #define TESTING_HPP
#include "defines.hpp"
#include <cstdint> #include <cstdint>
#include <vector>
#include <tuple>
#include <iostream> #include <iostream>
#include <stdexcept> #include <stdexcept>
#include <string> #include <string>
#include "defines.hpp" #include <tuple>
#include <vector>
struct GameboyTestState { struct GameboyTestState {
Word PC; Word PC;
Word SP; Word SP;
Byte A; Byte A;
Byte F; Byte F;
Byte B; Byte B;
Byte C; Byte C;
Byte D; Byte D;
Byte E; Byte E;
Byte H; Byte H;
Byte L; Byte L;
//Byte IME; // Byte IME;
//Byte IE; // Byte IE;
std::vector<std::tuple<Word, Byte>> RAM; std::vector<std::tuple<Word, Byte>> RAM;
}; };
inline bool operator==(const GameboyTestState& lhs, const GameboyTestState& rhs) { inline bool operator==(const GameboyTestState &lhs,
for (int i = 0; i < lhs.RAM.size(); i++) { const GameboyTestState &rhs) {
if (std::get<1>(lhs.RAM[i]) != std::get<1>(rhs.RAM[i])) for (int i = 0; i < lhs.RAM.size(); i++) {
return false; if (std::get<1>(lhs.RAM[i]) != std::get<1>(rhs.RAM[i]))
} return false;
return (lhs.A == rhs.A && }
lhs.F == rhs.F && return (lhs.A == rhs.A && lhs.F == rhs.F && lhs.B == rhs.B &&
lhs.B == rhs.B && lhs.C == rhs.C && lhs.D == rhs.D && lhs.E == rhs.E &&
lhs.C == rhs.C && lhs.H == rhs.H && lhs.L == rhs.L);
lhs.D == rhs.D &&
lhs.E == rhs.E &&
lhs.H == rhs.H &&
lhs.L == rhs.L);
} }
#endif //TESTING_HPP #endif // TESTING_HPP

View File

@@ -1,43 +1,45 @@
#include "gameboy.hpp" #include "gameboy.hpp"
//handles most of the behavoir as described here: https://gbdev.io/pandocs/Timer_and_Divider_Registers.html#ff04--div-divider-register // handles most of the behavoir as described here:
// https://gbdev.io/pandocs/Timer_and_Divider_Registers.html#ff04--div-divider-register
void GameBoy::timingHandler() { void GameBoy::timingHandler() {
//can't do this as we use cycles for PPU timing but this is what should happen // can't do this as we use cycles for PPU timing but this is what should
//addressSpace.memoryLayout.DIV = ((cycles / 4) >> 6) & 0xFF; // happen addressSpace.memoryLayout.DIV = ((cycles / 4) >> 6) & 0xFF;
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; addressSpace.memoryLayout.DIV += increments;
lastDivUpdate += increments * DIVIDER_REGISTER_FREQ; lastDivUpdate += increments * DIVIDER_REGISTER_FREQ;
} }
//if enabled // if enabled
uint64_t TIMAFrequency = 0; uint64_t TIMAFrequency = 0;
if (addressSpace.memoryLayout.TAC & 0x04) { if (addressSpace.memoryLayout.TAC & 0x04) {
switch (addressSpace.memoryLayout.TAC & 0x03) { switch (addressSpace.memoryLayout.TAC & 0x03) {
case 0: case 0:
TIMAFrequency = 1024; TIMAFrequency = 1024;
break; break;
case 1: case 1:
TIMAFrequency = 16; TIMAFrequency = 16;
break; break;
case 2: case 2:
TIMAFrequency = 64; TIMAFrequency = 64;
break; break;
case 3: case 3:
TIMAFrequency = 256; TIMAFrequency = 256;
break; break;
} }
//if TIMA overflowed and prevTMA != current TMA, use prevTMA (ie use prevTMA regardless) // if TIMA overflowed and prevTMA != current TMA, use prevTMA (ie use
const int increments = (cycles - lastTIMAUpdate) / TIMAFrequency; // prevTMA regardless)
if (cycles - lastTIMAUpdate >= TIMAFrequency) { const int increments = (cycles - lastTIMAUpdate) / TIMAFrequency;
if (static_cast<int>(addressSpace.memoryLayout.TIMA) + increments > 255) { if (cycles - lastTIMAUpdate >= TIMAFrequency) {
addressSpace.memoryLayout.TIMA = prevTMA + ((addressSpace.memoryLayout.TIMA + increments) % 256); if (static_cast<int>(addressSpace.memoryLayout.TIMA) + increments > 255) {
setInterrupt(TIMER_INTERRUPT); addressSpace.memoryLayout.TIMA =
} prevTMA + ((addressSpace.memoryLayout.TIMA + increments) % 256);
else setInterrupt(TIMER_INTERRUPT);
addressSpace.memoryLayout.TIMA += increments; } else
lastTIMAUpdate += increments * TIMAFrequency; addressSpace.memoryLayout.TIMA += increments;
} lastTIMAUpdate += increments * TIMAFrequency;
} }
}
} }