formatting

This commit is contained in:
2026-03-13 18:07:43 -07:00
parent 712ee55e7b
commit 3bd1ef34d8
13 changed files with 4873 additions and 4932 deletions

View File

@@ -2,19 +2,13 @@
#include <iostream> #include <iostream>
#include <iterator> #include <iterator>
bool AddressSpace::getBootromState() const { bool AddressSpace::getBootromState() const { return bootromLoaded; }
return bootromLoaded;
}
void AddressSpace::unmapBootrom() { void AddressSpace::unmapBootrom() { bootromLoaded = false; }
bootromLoaded = false;
}
void AddressSpace::mapBootrom() { void AddressSpace::mapBootrom() { bootromLoaded = true; }
bootromLoaded = true;
}
void AddressSpace::loadBootrom(const std::string& filename) { void AddressSpace::loadBootrom(const std::string &filename) {
std::ifstream file; std::ifstream file;
file.open(filename, std::ios::binary); file.open(filename, std::ios::binary);
@@ -28,10 +22,10 @@ void AddressSpace::loadBootrom(const std::string& filename) {
std::cerr << "Bootrom was an unexpected size!\nQuitting!\n" << std::endl; std::cerr << "Bootrom was an unexpected size!\nQuitting!\n" << std::endl;
exit(1); exit(1);
} }
file.read(reinterpret_cast<char*>(bootrom), BOOTROM_SIZE); file.read(reinterpret_cast<char *>(bootrom), BOOTROM_SIZE);
} }
void AddressSpace::loadGame(const std::string& filename) { void AddressSpace::loadGame(const std::string &filename) {
std::ifstream rom(filename, std::ios::binary); std::ifstream rom(filename, std::ios::binary);
rom.unsetf(std::ios::skipws); rom.unsetf(std::ios::skipws);
@@ -45,8 +39,7 @@ void AddressSpace::loadGame(const std::string& filename) {
rom.seekg(0, std::ios::beg); rom.seekg(0, std::ios::beg);
game.reserve(rom_size); game.reserve(rom_size);
game.insert(game.begin(), game.insert(game.begin(), std::istream_iterator<Byte>(rom),
std::istream_iterator<Byte>(rom),
std::istream_iterator<Byte>()); std::istream_iterator<Byte>());
memoryLayout.romBank0 = game.data(); memoryLayout.romBank0 = game.data();
@@ -57,11 +50,10 @@ 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,9 +1,9 @@
#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>
@@ -15,7 +15,7 @@ class AddressSpace {
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() {
@@ -24,32 +24,33 @@ public:
} }
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;
@@ -59,7 +60,7 @@ public:
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;
@@ -68,7 +69,7 @@ public:
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;
@@ -81,20 +82,20 @@ public:
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();
bool MBCWrite(Word address); bool MBCWrite(Word address);
Byte* MBCRead(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 MBCUpdate();
void loadRomBank(); void loadRomBank();
@@ -109,21 +110,21 @@ public:
bool dmaTransferRequested = false; bool dmaTransferRequested = false;
void dmaTransfer(); void dmaTransfer();
//Selected ROM Bank = (Secondary Bank << 5) + ROM Bank // Selected ROM Bank = (Secondary Bank << 5) + ROM Bank
Byte selectedRomBank = 0; Byte selectedRomBank = 0;
Byte romBankRegister = 0x00; Byte romBankRegister = 0x00;
//2 bit register acts as secondary rom bank register or ram bank number // 2 bit register acts as secondary rom bank register or ram bank number
Byte twoBitBankRegister = 0x0; Byte twoBitBankRegister = 0x0;
Byte selectedExternalRamBank = 0; Byte selectedExternalRamBank = 0;
Byte romRamSelect = 0x00; Byte romRamSelect = 0x00;
Byte ramEnable = 0x00; Byte ramEnable = 0x00;
//MBC3 // MBC3
Byte latchClockData = 0x00; Byte latchClockData = 0x00;
Byte ramBankRTCRegister = 0x00; Byte ramBankRTCRegister = 0x00;
void setTesting(bool state); void setTesting(bool state);
//read // read
Byte operator[](const Word address) const { Byte operator[](const Word address) const {
if (testing) if (testing)
return testRam[address]; return testRam[address];
@@ -168,7 +169,8 @@ public:
case 0xFF06: case 0xFF06:
return memoryLayout.TMA; return memoryLayout.TMA;
case 0xFF07: case 0xFF07:
return memoryLayout.TAC | 0xF8;; return memoryLayout.TAC | 0xF8;
;
case 0xFF0F: case 0xFF0F:
return memoryLayout.IF | 0xE0; return memoryLayout.IF | 0xE0;
case 0xFF10: case 0xFF10:
@@ -223,8 +225,8 @@ public:
case 0xFF43: case 0xFF43:
return memoryLayout.SCX; return memoryLayout.SCX;
case 0xFF44: case 0xFF44:
//for debugging only // for debugging only
//return 0x90; // return 0x90;
return memoryLayout.LY; return memoryLayout.LY;
case 0xFF45: case 0xFF45:
return memoryLayout.LYC; return memoryLayout.LYC;
@@ -248,12 +250,12 @@ public:
} }
if (address < 0xFFFF) if (address < 0xFFFF)
return memoryLayout.specialRam[address - 0xFF80]; return memoryLayout.specialRam[address - 0xFF80];
//0xFFFF // 0xFFFF
return memoryLayout.IE; return memoryLayout.IE;
} }
//write // write
Byte& operator[](const Word address) { Byte &operator[](const Word address) {
dummyVal = 0xFF; dummyVal = 0xFF;
if (testing) if (testing)
return testRam[address]; return testRam[address];
@@ -374,10 +376,9 @@ public:
} }
if (address < 0xFFFF) if (address < 0xFFFF)
return memoryLayout.specialRam[address - 0xFF80]; return memoryLayout.specialRam[address - 0xFF80];
//0xFFFF // 0xFFFF
return memoryLayout.IE; return memoryLayout.IE;
} }
}; };
#endif // ADDRESSSPACE_HPP
#endif //ADDRESSSPACE_HPP

View File

@@ -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;
@@ -87,15 +86,16 @@ enum MBCType {
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
// Pocket Monsters: Crystal Version.
MBC3RamBattery = 0x13, MBC3RamBattery = 0x13,
MBC5 = 0x19, MBC5 = 0x19,
MBC5Ram = 0x1A, MBC5Ram = 0x1A,
@@ -112,10 +112,14 @@ enum MBCType {
}; };
enum PPUMode { enum PPUMode {
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.
}; };
#endif #endif

View File

@@ -1541,7 +1541,8 @@ void GameBoy::extendedOpcodeResolver() {
break; break;
default: default:
printf("Unsupported extended opcode found: PC:0x%.2x, Opcode:0xcb%.2x\n", PC, addressSpace[PC]); printf("Unsupported extended opcode found: PC:0x%.2x, Opcode:0xcb%.2x\n",
PC, addressSpace[PC]);
exit(1); exit(1);
} }
} }

View File

@@ -1,5 +1,5 @@
#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;
@@ -24,28 +24,21 @@ GameboyTestState GameBoy::runTest(GameboyTestState initial) {
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) {
void GameBoy::start(const std::string& bootrom, const std::string& game) {
addressSpace.loadBootrom(bootrom); addressSpace.loadBootrom(bootrom);
addressSpace.loadGame(game); addressSpace.loadGame(game);
addressSpace.determineMBCInfo(); addressSpace.determineMBCInfo();
@@ -146,35 +139,34 @@ void GameBoy::start(const std::string& bootrom, const std::string& game) {
prevTMA = addressSpace.memoryLayout.TMA; prevTMA = addressSpace.memoryLayout.TMA;
if (debug) { if (debug) {
printf( printf("A: %.2X F: %.2X B: %.2X C: %.2X D: %.2X E: %.2X H: %.2X L: "
"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", "%.2X SP: %.4X PC: 00:%.4X (%.2X %.2X %.2X %.2X)\n",
AF.hi, AF.lo, BC.hi, BC.lo, DE.hi, DE.lo, HL.hi, HL.lo, SP, PC, readOnlyAddressSpace[PC], AF.hi, AF.lo, BC.hi, BC.lo, DE.hi, DE.lo, HL.hi, HL.lo, SP, PC,
readOnlyAddressSpace[PC + 1], readOnlyAddressSpace[PC + 2], readOnlyAddressSpace[PC + 3]); readOnlyAddressSpace[PC], readOnlyAddressSpace[PC + 1],
readOnlyAddressSpace[PC + 2], readOnlyAddressSpace[PC + 3]);
// printf("Cycles: %lu, Opcode: 0x%.2x PPU cycles: %lu, PPMode: %d\n",
// printf("Cycles: %lu, Opcode: 0x%.2x PPU cycles: %lu, PPMode: %d\n", cycles, readOnlyAddressSpace[PC], // cycles, readOnlyAddressSpace[PC],
// cyclesSinceLastScanline(), currentMode); // cyclesSinceLastScanline(), currentMode);
// 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("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),
// printf("\n"); // (*LY), (*LYC)); printf("\n");
} }
if (!halted) { if (!halted) {
opcodeResolver(); opcodeResolver();
addressSpace.MBCUpdate(); addressSpace.MBCUpdate();
} } else {
else {
addCycles(4); addCycles(4);
} }
timingHandler(); timingHandler();
interruptHandler(); interruptHandler();
if (ppuEnabled) { if (ppuEnabled) {
ppuUpdate(); ppuUpdate();
} } else {
else {
ppuCycles = 2; ppuCycles = 2;
lastScanline = 0; lastScanline = 0;
lastRefresh = 0; lastRefresh = 0;

View File

@@ -1,16 +1,16 @@
#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;
@@ -19,9 +19,10 @@ union RegisterPair {
}; };
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
// https://github.com/Gekkio/mooneye-test-suite/blob/main/acceptance/ppu/lcdon_timing-GS.s
uint64_t ppuCycles = 2; uint64_t ppuCycles = 2;
bool ppuEnabled = false; bool ppuEnabled = false;
uint16_t lastOpTicks = 0; uint16_t lastOpTicks = 0;
@@ -31,23 +32,23 @@ class GameBoy {
uint64_t lastDivUpdate = 0; uint64_t lastDivUpdate = 0;
bool rendered = false; 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 currentMode = PPUMode::mode0;
Byte windowLineCounter = 0; Byte windowLineCounter = 0;
@@ -59,11 +60,12 @@ class GameBoy {
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_Texture *texture = nullptr;
SDL_Event event = {0}; SDL_Event event = {0};
uint32_t frameStart = 0; uint32_t frameStart = 0;
uint32_t frameTime = 0; uint32_t frameTime = 0;
@@ -112,75 +114,59 @@ class GameBoy {
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> template <typename T> void orBitwise(T &dest, T src);
void orBitwise(T& dest, T src); template <typename T> void andBitwise(T &dest, T src);
template <typename T> template <typename T> void xorBitwise(T &dest, T src);
void andBitwise(T& dest, T src);
template <typename T>
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(uint8_t testBit, uint8_t& reg); static void set(uint8_t testBit, uint8_t &reg);
static void res(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> bool jrNZ(T offset);
template <typename T> template <class T> bool jrNC(T offset);
bool jrNZ(T offset); template <class T> bool jrC(T offset);
template <class T> template <typename T> void inc(T &reg);
bool jrNC(T offset); template <typename T> void call(T address);
template <class T>
bool jrC(T offset);
template <typename T>
void inc(T& reg);
template <typename T>
void call(T address);
void halt(); void halt();
void daa(); void daa();
void stop(); void stop();
void cp(Byte value); void cp(Byte value);
template <typename T> template <typename T> void dec(T &reg);
void dec(T& reg); template <typename T> bool jrZ(T offset);
template <typename T>
bool jrZ(T offset);
void sub(Byte value); void sub(Byte value);
void sbc(Byte value); void sbc(Byte value);
template <typename T> template <typename T> void jr(T OFFSET);
void jr(T OFFSET);
void push(Word reg); void push(Word reg);
void rl(Byte& reg); void rl(Byte &reg);
void sla(Byte& reg); void sla(Byte &reg);
void sra(uint8_t& reg); void sra(uint8_t &reg);
void srl(uint8_t& reg); void srl(uint8_t &reg);
void rrc(Byte& reg); void rrc(Byte &reg);
void rrca(); void rrca();
void rra(); void rra();
void rr(Byte& reg); void rr(Byte &reg);
void rlc(Byte& reg); void rlc(Byte &reg);
void rlca(); void rlca();
void rla(); void rla();
template <typename T> template <typename T> void pop(T &reg);
void pop(T& reg); template <typename T> void rst(T address);
template <typename T>
void rst(T address);
void ret(); void ret();
template <typename T> template <typename T> void add(T &reg, T value);
void add(T& reg, T value);
void adc(Byte value); void adc(Byte value);
void cpl(); void cpl();
void scf(); void scf();
void ccf(); void ccf();
void swap(Byte& value); 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,7 +2,8 @@
#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) {
@@ -16,32 +17,37 @@ void GameBoy::resetInterrupt(const Byte interrupt) {
} }
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) &&
testInterruptEnabled(VBLANK_INTERRUPT)) {
if (IME) if (IME)
VBlankHandle(); VBlankHandle();
halted = false; halted = false;
} }
if (readOnlyAddressSpace.memoryLayout.IF & static_cast<Byte>(1 << LCD_STAT_INTERRUPT) && testInterruptEnabled( if (readOnlyAddressSpace.memoryLayout.IF &
LCD_STAT_INTERRUPT)) { static_cast<Byte>(1 << LCD_STAT_INTERRUPT) &&
testInterruptEnabled(LCD_STAT_INTERRUPT)) {
if (IME) if (IME)
LCDStatHandle(); LCDStatHandle();
halted = false; halted = false;
} }
if (readOnlyAddressSpace.memoryLayout.IF & static_cast<Byte>(1 << TIMER_INTERRUPT) && testInterruptEnabled( if (readOnlyAddressSpace.memoryLayout.IF &
TIMER_INTERRUPT)) { static_cast<Byte>(1 << TIMER_INTERRUPT) &&
testInterruptEnabled(TIMER_INTERRUPT)) {
if (IME) if (IME)
timerHandle(); timerHandle();
halted = false; halted = false;
} }
if (readOnlyAddressSpace.memoryLayout.IF & static_cast<Byte>(1 << SERIAL_INTERRUPT) && testInterruptEnabled( if (readOnlyAddressSpace.memoryLayout.IF &
SERIAL_INTERRUPT)) { static_cast<Byte>(1 << SERIAL_INTERRUPT) &&
testInterruptEnabled(SERIAL_INTERRUPT)) {
if (IME) if (IME)
serialHandle(); serialHandle();
halted = false; halted = false;
} }
if (readOnlyAddressSpace.memoryLayout.IF & static_cast<Byte>(1 << JOYPAD_INTERRUPT) && testInterruptEnabled( if (readOnlyAddressSpace.memoryLayout.IF &
JOYPAD_INTERRUPT)) { static_cast<Byte>(1 << JOYPAD_INTERRUPT) &&
testInterruptEnabled(JOYPAD_INTERRUPT)) {
if (IME) if (IME)
joypadHandle(); joypadHandle();
halted = false; halted = false;

View File

@@ -1,5 +1,5 @@
#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;

View File

@@ -1,23 +1,23 @@
#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;
@@ -25,27 +25,26 @@ int main(int argc, char** argv) {
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) {
for (const auto& testFile : testFiles) {
std::ifstream file(testFile); std::ifstream file(testFile);
std::cout << "Running test: " << testFile << std::endl; std::cout << "Running test: " << testFile << std::endl;
const json tests = json::parse(file); const json tests = json::parse(file);
for (auto& test : tests) { for (auto &test : tests) {
//create state // create state
std::vector<std::tuple<Word, Byte>> initialRAM; std::vector<std::tuple<Word, Byte>> initialRAM;
for (int i = 0; i < test["initial"]["ram"].size(); i++) for (int i = 0; i < test["initial"]["ram"].size(); i++)
initialRAM.emplace_back(test["initial"]["ram"][i][0], test["initial"]["ram"][i][1]); initialRAM.emplace_back(test["initial"]["ram"][i][0],
test["initial"]["ram"][i][1]);
GameboyTestState initialState = { GameboyTestState initialState = {test["initial"]["pc"],
test["initial"]["pc"],
test["initial"]["sp"], test["initial"]["sp"],
test["initial"]["a"], test["initial"]["a"],
test["initial"]["f"], test["initial"]["f"],
@@ -55,19 +54,18 @@ void runJSONTests(GameBoy* gb) {
test["initial"]["e"], test["initial"]["e"],
test["initial"]["h"], test["initial"]["h"],
test["initial"]["l"], test["initial"]["l"],
initialRAM initialRAM};
};
//run // run
GameboyTestState result = gb->runTest(initialState); GameboyTestState result = gb->runTest(initialState);
//compare new state to expected // compare new state to expected
std::vector<std::tuple<Word, Byte>> finalRAM; std::vector<std::tuple<Word, Byte>> finalRAM;
for (int i = 0; i < test["final"]["ram"].size(); i++) for (int i = 0; i < test["final"]["ram"].size(); i++)
finalRAM.emplace_back(test["final"]["ram"][i][0], test["final"]["ram"][i][1]); finalRAM.emplace_back(test["final"]["ram"][i][0],
test["final"]["ram"][i][1]);
GameboyTestState finalState = { GameboyTestState finalState = {test["final"]["pc"],
test["final"]["pc"],
test["final"]["sp"], test["final"]["sp"],
test["final"]["a"], test["final"]["a"],
test["final"]["f"], test["final"]["f"],
@@ -77,8 +75,7 @@ void runJSONTests(GameBoy* gb) {
test["final"]["e"], test["final"]["e"],
test["final"]["h"], test["final"]["h"],
test["final"]["l"], test["final"]["l"],
finalRAM finalRAM};
};
if (finalState != result) { if (finalState != result) {
std::cout << "Test " << testFile << " failed!" << std::endl; std::cout << "Test " << testFile << " failed!" << std::endl;

View File

@@ -1,63 +1,50 @@
#include "gameboy.hpp" #include "gameboy.hpp"
void GameBoy::setFlag(const Byte bit) { void GameBoy::setFlag(const Byte bit) { AF.lo |= (1 << bit); }
AF.lo |= (1 << bit);
}
void GameBoy::resetFlag(const Byte bit) { void GameBoy::resetFlag(const Byte bit) { AF.lo &= ~(1 << bit); }
AF.lo &= ~(1 << bit);
}
bool GameBoy::getFlag(const Byte bit) const { bool GameBoy::getFlag(const Byte bit) const { return (AF.lo >> bit) & 1; }
return (AF.lo >> bit) & 1;
}
Word GameBoy::getWordPC() { Word GameBoy::getWordPC() {
RegisterPair word = {0}; RegisterPair word = {0};
//remember little endianness // remember little endianness
word.lo = readOnlyAddressSpace[PC + 1]; word.lo = readOnlyAddressSpace[PC + 1];
word.hi = readOnlyAddressSpace[PC + 2]; word.hi = readOnlyAddressSpace[PC + 2];
return word.reg; return word.reg;
} }
Byte GameBoy::getBytePC() { Byte GameBoy::getBytePC() { return readOnlyAddressSpace[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 = readOnlyAddressSpace[SP++]; word.lo = readOnlyAddressSpace[SP++];
word.hi = readOnlyAddressSpace[SP++]; word.hi = readOnlyAddressSpace[SP++];
return word.reg; return word.reg;
} }
Byte GameBoy::getByteSP() { Byte GameBoy::getByteSP() { return readOnlyAddressSpace[SP++]; }
return readOnlyAddressSpace[SP++];
}
void GameBoy::ret() { void GameBoy::ret() {
PC = readOnlyAddressSpace[SP++]; PC = readOnlyAddressSpace[SP++];
PC |= readOnlyAddressSpace[SP++] << 8; PC |= readOnlyAddressSpace[SP++] << 8;
} }
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 == &addressSpace.memoryLayout.DIV) { if (&dest == &addressSpace.memoryLayout.DIV) {
addressSpace.memoryLayout.DIV = 0x00; addressSpace.memoryLayout.DIV = 0x00;
lastDivUpdate = cycles; lastDivUpdate = cycles;
} } else {
else {
dest = src; dest = src;
} }
} } else {
else { // 16-bit register pair write
//16-bit register pair write
dest = src; dest = src;
} }
} }
@@ -67,8 +54,7 @@ void GameBoy::ldW(const Word destAddr, const Word src) {
addressSpace[destAddr + 1] = static_cast<Byte>((src & 0xFF00) >> 8); addressSpace[destAddr + 1] = static_cast<Byte>((src & 0xFF00) >> 8);
} }
template <typename T> template <typename T> void GameBoy::add(T &reg, T value) {
void GameBoy::add(T& reg, T value) {
if (sizeof(reg) == sizeof(Byte)) { if (sizeof(reg) == sizeof(Byte)) {
if ((((value & 0xF) + (reg & 0xF)) & 0x10) == 0x10) if ((((value & 0xF) + (reg & 0xF)) & 0x10) == 0x10)
setFlag(HALFCARRY_FLAG); setFlag(HALFCARRY_FLAG);
@@ -115,7 +101,6 @@ void GameBoy::adc(const Byte value) {
else else
resetFlag(CARRY_FLAG); resetFlag(CARRY_FLAG);
AF.hi += value + carry; AF.hi += value + carry;
if (AF.hi == 0) if (AF.hi == 0)
@@ -149,7 +134,8 @@ void GameBoy::sbc(const Byte value) {
const Byte carry = getFlag(CARRY_FLAG) ? 1 : 0; const Byte carry = getFlag(CARRY_FLAG) ? 1 : 0;
const Byte result = AF.hi - value - carry; const Byte result = AF.hi - value - carry;
if ((static_cast<unsigned>(AF.hi) - static_cast<unsigned>(value) - carry) > 0xFF) if ((static_cast<unsigned>(AF.hi) - static_cast<unsigned>(value) - carry) >
0xFF)
setFlag(CARRY_FLAG); setFlag(CARRY_FLAG);
else else
resetFlag(CARRY_FLAG); resetFlag(CARRY_FLAG);
@@ -169,7 +155,7 @@ void GameBoy::sbc(const Byte value) {
setFlag(SUBTRACT_FLAG); setFlag(SUBTRACT_FLAG);
} }
//https://gbdev.gg8.se/wiki/articles/DAA // https://gbdev.gg8.se/wiki/articles/DAA
void GameBoy::daa() { void GameBoy::daa() {
if (getFlag(SUBTRACT_FLAG)) { if (getFlag(SUBTRACT_FLAG)) {
if (getFlag(CARRY_FLAG)) { if (getFlag(CARRY_FLAG)) {
@@ -178,8 +164,7 @@ void GameBoy::daa() {
if (getFlag(HALFCARRY_FLAG)) { if (getFlag(HALFCARRY_FLAG)) {
AF.hi -= 0x06; AF.hi -= 0x06;
} }
} } else {
else {
if (getFlag(CARRY_FLAG) || (AF.hi & 0xFF) > 0x99) { if (getFlag(CARRY_FLAG) || (AF.hi & 0xFF) > 0x99) {
AF.hi += 0x60; AF.hi += 0x60;
setFlag(CARRY_FLAG); setFlag(CARRY_FLAG);
@@ -196,8 +181,7 @@ void GameBoy::daa() {
resetFlag(HALFCARRY_FLAG); resetFlag(HALFCARRY_FLAG);
} }
template <typename T> template <typename T> void GameBoy::orBitwise(T &dest, T src) {
void GameBoy::orBitwise(T& dest, T src) {
dest |= src; dest |= src;
if (dest == 0) if (dest == 0)
@@ -210,8 +194,7 @@ void GameBoy::orBitwise(T& dest, T src) {
resetFlag(CARRY_FLAG); resetFlag(CARRY_FLAG);
} }
template <typename T> template <typename T> void GameBoy::andBitwise(T &dest, T src) {
void GameBoy::andBitwise(T& dest, T src) {
dest &= src; dest &= src;
if (dest == 0) if (dest == 0)
@@ -224,8 +207,7 @@ void GameBoy::andBitwise(T& dest, T src) {
resetFlag(CARRY_FLAG); resetFlag(CARRY_FLAG);
} }
template <typename T> template <typename T> void GameBoy::xorBitwise(T &dest, T src) {
void GameBoy::xorBitwise(T& dest, T src) {
dest ^= src; dest ^= src;
if (dest == 0) if (dest == 0)
@@ -248,70 +230,63 @@ void GameBoy::bit(Byte testBit, Byte reg) {
setFlag(HALFCARRY_FLAG); setFlag(HALFCARRY_FLAG);
} }
void GameBoy::set(const Byte testBit, Byte& reg) { void GameBoy::set(const Byte testBit, Byte &reg) { reg |= (1 << testBit); }
reg |= (1 << testBit);
void GameBoy::res(const Byte testBit, Byte &reg) { reg &= ~(1 << testBit); }
template <typename T> void GameBoy::jr(T offset) {
PC += static_cast<int8_t>(offset) + 2; // PC moves 2 from original instruction
} }
void GameBoy::res(const Byte testBit, Byte& reg) { template <typename T> bool GameBoy::jrNZ(T offset) {
reg &= ~(1 << testBit);
}
template <typename T>
void GameBoy::jr(T offset) {
PC += static_cast<int8_t>(offset) + 2; //PC moves 2 from original instruction
}
template <typename T>
bool GameBoy::jrNZ(T offset) {
bool jumped = false; bool jumped = false;
if (!getFlag(ZERO_FLAG)) //if not set if (!getFlag(ZERO_FLAG)) // if not set
{ {
PC += static_cast<int8_t>(offset) + 2; //PC moves 2 from the original instruction PC += static_cast<int8_t>(offset) +
2; // PC moves 2 from the original instruction
jumped = true; jumped = true;
} }
return jumped; return jumped;
} }
template <typename T> template <typename T> bool GameBoy::jrZ(T offset) {
bool GameBoy::jrZ(T offset) {
bool jumped = false; bool jumped = false;
if (getFlag(ZERO_FLAG)) //if not set if (getFlag(ZERO_FLAG)) // if not set
{ {
PC += static_cast<int8_t>(offset) + 2; //PC moves 2 from the original instruction PC += static_cast<int8_t>(offset) +
2; // PC moves 2 from the original instruction
jumped = true; jumped = true;
} }
return jumped; return jumped;
} }
template <typename T> template <typename T> bool GameBoy::jrNC(T offset) {
bool GameBoy::jrNC(T offset) {
bool jumped = false; bool jumped = false;
if (!getFlag(CARRY_FLAG)) //if not set if (!getFlag(CARRY_FLAG)) // if not set
{ {
PC += static_cast<int8_t>(offset) + 2; //PC moves 2 from the original instruction PC += static_cast<int8_t>(offset) +
2; // PC moves 2 from the original instruction
jumped = true; jumped = true;
} }
return jumped; return jumped;
} }
template <typename T> template <typename T> bool GameBoy::jrC(T offset) {
bool GameBoy::jrC(T offset) {
bool jumped = false; bool jumped = false;
if (getFlag(CARRY_FLAG)) //if not set if (getFlag(CARRY_FLAG)) // if not set
{ {
PC += static_cast<int8_t>(offset) + 2; //PC moves 2 from the original instruction PC += static_cast<int8_t>(offset) +
2; // PC moves 2 from the original instruction
jumped = true; jumped = true;
} }
return jumped; return jumped;
} }
template <typename T> template <typename T> void GameBoy::inc(T &reg) {
void GameBoy::inc(T& reg) {
reg += 1; reg += 1;
if (sizeof(reg) == sizeof(Byte)) { if (sizeof(reg) == sizeof(Byte)) {
@@ -322,7 +297,8 @@ void GameBoy::inc(T& reg) {
resetFlag(SUBTRACT_FLAG); resetFlag(SUBTRACT_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 ((reg & 0x0F) == 0) if ((reg & 0x0F) == 0)
setFlag(HALFCARRY_FLAG); setFlag(HALFCARRY_FLAG);
else else
@@ -330,13 +306,12 @@ void GameBoy::inc(T& reg) {
} }
} }
template <typename T> template <typename T> void GameBoy::call(T address) {
void GameBoy::call(T address) {
push(PC + 3); push(PC + 3);
PC = address; PC = address;
} }
void GameBoy::cp(const Byte value) //compare void GameBoy::cp(const Byte value) // compare
{ {
resetFlag(ZERO_FLAG); resetFlag(ZERO_FLAG);
resetFlag(CARRY_FLAG); resetFlag(CARRY_FLAG);
@@ -355,8 +330,7 @@ void GameBoy::cp(const Byte value) //compare
setFlag(SUBTRACT_FLAG); setFlag(SUBTRACT_FLAG);
} }
template <typename T> template <typename T> void GameBoy::dec(T &reg) {
void GameBoy::dec(T& reg) {
reg -= 1; reg -= 1;
if (sizeof(reg) == sizeof(Byte)) { if (sizeof(reg) == sizeof(Byte)) {
@@ -367,7 +341,8 @@ void GameBoy::dec(T& reg) {
setFlag(SUBTRACT_FLAG); setFlag(SUBTRACT_FLAG);
//halfcarry test https://www.reddit.com/r/EmuDev/comments/4clh23/trouble_with_halfcarrycarry_flag/ // halfcarry test
// https://www.reddit.com/r/EmuDev/comments/4clh23/trouble_with_halfcarrycarry_flag/
if (0 > (((reg + 1) & 0xf) - (reg & 0xf))) if (0 > (((reg + 1) & 0xf) - (reg & 0xf)))
setFlag(HALFCARRY_FLAG); setFlag(HALFCARRY_FLAG);
else else
@@ -375,7 +350,7 @@ void GameBoy::dec(T& reg) {
} }
} }
void GameBoy::swap(Byte& value) { void GameBoy::swap(Byte &value) {
const Byte lowerNibble = value & 0x0F; const Byte lowerNibble = value & 0x0F;
const Byte upperNibble = (value >> 4) & 0x0F; const Byte upperNibble = (value >> 4) & 0x0F;
@@ -397,7 +372,7 @@ void GameBoy::halt() {
haltBug = true; haltBug = true;
} }
void GameBoy::rrc(Byte& reg) { void GameBoy::rrc(Byte &reg) {
const Byte lsb = reg & 0x01; const Byte lsb = reg & 0x01;
reg >>= 1; reg >>= 1;
@@ -452,7 +427,7 @@ void GameBoy::rra() {
resetFlag(HALFCARRY_FLAG); resetFlag(HALFCARRY_FLAG);
} }
void GameBoy::rr(Byte& reg) { void GameBoy::rr(Byte &reg) {
const Byte lsb = reg & 0x01; const Byte lsb = reg & 0x01;
reg >>= 1; reg >>= 1;
@@ -474,7 +449,7 @@ void GameBoy::rr(Byte& reg) {
resetFlag(HALFCARRY_FLAG); resetFlag(HALFCARRY_FLAG);
} }
void GameBoy::rlc(Byte& reg) { void GameBoy::rlc(Byte &reg) {
const Byte msb = (reg & 0x80) >> 7; const Byte msb = (reg & 0x80) >> 7;
reg <<= 1; reg <<= 1;
@@ -527,7 +502,7 @@ void GameBoy::rla() {
resetFlag(HALFCARRY_FLAG); resetFlag(HALFCARRY_FLAG);
} }
void GameBoy::rl(Byte& reg) { void GameBoy::rl(Byte &reg) {
const Byte msb = (reg & 0x80) >> 7; const Byte msb = (reg & 0x80) >> 7;
reg <<= 1; reg <<= 1;
@@ -549,7 +524,7 @@ void GameBoy::rl(Byte& reg) {
resetFlag(HALFCARRY_FLAG); resetFlag(HALFCARRY_FLAG);
} }
void GameBoy::sla(Byte& reg) { void GameBoy::sla(Byte &reg) {
const Byte msb = (reg & 0x80) >> 7; const Byte msb = (reg & 0x80) >> 7;
reg <<= 1; reg <<= 1;
@@ -568,7 +543,7 @@ void GameBoy::sla(Byte& reg) {
resetFlag(HALFCARRY_FLAG); resetFlag(HALFCARRY_FLAG);
} }
void GameBoy::sra(Byte& reg) { void GameBoy::sra(Byte &reg) {
const Byte msb = (reg & 0x80) >> 7; const Byte msb = (reg & 0x80) >> 7;
const Byte lsb = reg & 0x1; const Byte lsb = reg & 0x1;
@@ -591,7 +566,7 @@ void GameBoy::sra(Byte& reg) {
resetFlag(HALFCARRY_FLAG); resetFlag(HALFCARRY_FLAG);
} }
void GameBoy::srl(Byte& reg) { void GameBoy::srl(Byte &reg) {
const Byte lsb = reg & 0x1; const Byte lsb = reg & 0x1;
reg >>= 1; reg >>= 1;
@@ -610,25 +585,20 @@ void GameBoy::srl(Byte& reg) {
resetFlag(HALFCARRY_FLAG); resetFlag(HALFCARRY_FLAG);
} }
template <typename T> template <typename T> void GameBoy::pop(T &reg) {
void GameBoy::pop(T& reg) {
reg = getWordSP(); reg = getWordSP();
AF.reg &= 0xFFF0; AF.reg &= 0xFFF0;
} }
void GameBoy::push(const Word reg) { void GameBoy::push(const Word reg) {
//little endian // little endian
addressSpace[--SP] = reg >> 8; addressSpace[--SP] = reg >> 8;
addressSpace[--SP] = reg & 0xFF; addressSpace[--SP] = reg & 0xFF;
} }
template <typename T> template <typename T> void GameBoy::jp(T address) { PC = address; }
void GameBoy::jp(T address) {
PC = address;
}
template <typename T> template <typename T> void GameBoy::rst(T address) {
void GameBoy::rst(T address) {
PC += 1; PC += 1;
push(PC); push(PC);
PC = address; PC = address;
@@ -656,16 +626,14 @@ void GameBoy::ccf() {
resetFlag(HALFCARRY_FLAG); resetFlag(HALFCARRY_FLAG);
} }
void GameBoy::stop() { void GameBoy::stop() { stopped = true; }
stopped = true;
}
void GameBoy::opcodeResolver() { void GameBoy::opcodeResolver() {
if (readOnlyAddressSpace[PC] != 0xCB) { if (readOnlyAddressSpace[PC] != 0xCB) {
bool jumped; bool jumped;
switch (readOnlyAddressSpace[PC]) { switch (readOnlyAddressSpace[PC]) {
case 0x00: case 0x00:
//NOP // NOP
PC += 1; PC += 1;
addCycles(4); addCycles(4);
break; break;
@@ -779,7 +747,7 @@ void GameBoy::opcodeResolver() {
break; break;
case 0x13: case 0x13:
DE.reg += 1; //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;
@@ -859,8 +827,7 @@ void GameBoy::opcodeResolver() {
jumped = jrNZ(getBytePC()); jumped = jrNZ(getBytePC());
if (jumped) { if (jumped) {
addCycles(12); addCycles(12);
} } else {
else {
PC += 2; PC += 2;
addCycles(8); addCycles(8);
} }
@@ -913,8 +880,7 @@ void GameBoy::opcodeResolver() {
jumped = jrZ(getBytePC()); jumped = jrZ(getBytePC());
if (jumped) { if (jumped) {
addCycles(12); addCycles(12);
} } else {
else {
PC += 2; PC += 2;
addCycles(8); addCycles(8);
} }
@@ -967,8 +933,7 @@ void GameBoy::opcodeResolver() {
jumped = jrNC(getBytePC()); jumped = jrNC(getBytePC());
if (jumped) { if (jumped) {
addCycles(12); addCycles(12);
} } else {
else {
PC += 2; PC += 2;
addCycles(8); addCycles(8);
} }
@@ -1021,8 +986,7 @@ void GameBoy::opcodeResolver() {
jumped = jrC(getBytePC()); jumped = jrC(getBytePC());
if (jumped) { if (jumped) {
addCycles(12); addCycles(12);
} } else {
else {
PC += 2; PC += 2;
addCycles(8); addCycles(8);
} }
@@ -1839,12 +1803,11 @@ void GameBoy::opcodeResolver() {
addCycles(4); addCycles(4);
break; break;
case 0xC0: //RET NZ case 0xC0: // RET NZ
if (!getFlag(ZERO_FLAG)) { if (!getFlag(ZERO_FLAG)) {
ret(); ret();
addCycles(20); addCycles(20);
} } else {
else {
addCycles(8); addCycles(8);
PC += 1; PC += 1;
} }
@@ -1860,8 +1823,7 @@ void GameBoy::opcodeResolver() {
if (!getFlag(ZERO_FLAG)) { if (!getFlag(ZERO_FLAG)) {
jp(getWordPC()); jp(getWordPC());
addCycles(16); addCycles(16);
} } else {
else {
addCycles(12); addCycles(12);
PC += 3; PC += 3;
} }
@@ -1876,8 +1838,7 @@ void GameBoy::opcodeResolver() {
if (!getFlag(ZERO_FLAG)) { if (!getFlag(ZERO_FLAG)) {
call(getWordPC()); call(getWordPC());
addCycles(24); addCycles(24);
} } else {
else {
addCycles(12); addCycles(12);
PC += 3; PC += 3;
} }
@@ -1904,8 +1865,7 @@ void GameBoy::opcodeResolver() {
if (getFlag(ZERO_FLAG)) { if (getFlag(ZERO_FLAG)) {
ret(); ret();
addCycles(20); addCycles(20);
} } else {
else {
addCycles(8); addCycles(8);
PC += 1; PC += 1;
} }
@@ -1920,8 +1880,7 @@ void GameBoy::opcodeResolver() {
if (getFlag(ZERO_FLAG)) { if (getFlag(ZERO_FLAG)) {
jp(getWordPC()); jp(getWordPC());
addCycles(16); addCycles(16);
} } else {
else {
addCycles(12); addCycles(12);
PC += 3; PC += 3;
} }
@@ -1931,8 +1890,7 @@ void GameBoy::opcodeResolver() {
if (getFlag(ZERO_FLAG)) { if (getFlag(ZERO_FLAG)) {
call(getWordPC()); call(getWordPC());
addCycles(24); addCycles(24);
} } else {
else {
addCycles(12); addCycles(12);
PC += 3; PC += 3;
} }
@@ -1954,12 +1912,11 @@ void GameBoy::opcodeResolver() {
addCycles(16); addCycles(16);
break; break;
case 0xD0: //RET NC case 0xD0: // RET NC
if (!getFlag(CARRY_FLAG)) { if (!getFlag(CARRY_FLAG)) {
ret(); ret();
addCycles(20); addCycles(20);
} } else {
else {
addCycles(8); addCycles(8);
PC += 1; PC += 1;
} }
@@ -1975,8 +1932,7 @@ void GameBoy::opcodeResolver() {
if (!getFlag(CARRY_FLAG)) { if (!getFlag(CARRY_FLAG)) {
jp(getWordPC()); jp(getWordPC());
addCycles(24); addCycles(24);
} } else {
else {
addCycles(12); addCycles(12);
PC += 3; PC += 3;
} }
@@ -1986,8 +1942,7 @@ void GameBoy::opcodeResolver() {
if (!getFlag(CARRY_FLAG)) { if (!getFlag(CARRY_FLAG)) {
call(getWordPC()); call(getWordPC());
addCycles(24); addCycles(24);
} } else {
else {
addCycles(12); addCycles(12);
PC += 3; PC += 3;
} }
@@ -2014,14 +1969,13 @@ void GameBoy::opcodeResolver() {
if (getFlag(CARRY_FLAG)) { if (getFlag(CARRY_FLAG)) {
ret(); ret();
addCycles(20); addCycles(20);
} } else {
else {
addCycles(8); addCycles(8);
PC += 1; PC += 1;
} }
break; break;
//reti // reti
case 0xD9: case 0xD9:
IME = 1; IME = 1;
ret(); ret();
@@ -2032,8 +1986,7 @@ void GameBoy::opcodeResolver() {
if (getFlag(CARRY_FLAG)) { if (getFlag(CARRY_FLAG)) {
jp(getWordPC()); jp(getWordPC());
addCycles(16); addCycles(16);
} } else {
else {
addCycles(12); addCycles(12);
PC += 3; PC += 3;
} }
@@ -2043,8 +1996,7 @@ void GameBoy::opcodeResolver() {
if (getFlag(CARRY_FLAG)) { if (getFlag(CARRY_FLAG)) {
call(getWordPC()); call(getWordPC());
addCycles(24); addCycles(24);
} } else {
else {
addCycles(12); addCycles(12);
PC += 3; PC += 3;
} }
@@ -2096,8 +2048,7 @@ void GameBoy::opcodeResolver() {
addCycles(16); addCycles(16);
break; break;
case 0xE8: case 0xE8: {
{
const int16_t immediate = static_cast<int8_t>(getBytePC()); const int16_t immediate = static_cast<int8_t>(getBytePC());
if ((SP & 0xF) + (immediate & 0xF) > 0xF) if ((SP & 0xF) + (immediate & 0xF) > 0xF)
@@ -2117,8 +2068,7 @@ void GameBoy::opcodeResolver() {
PC += 2; PC += 2;
addCycles(16); addCycles(16);
} } break;
break;
case 0xE9: case 0xE9:
jp(HL.reg); jp(HL.reg);
@@ -2183,8 +2133,7 @@ void GameBoy::opcodeResolver() {
addCycles(16); addCycles(16);
break; break;
case 0xF8: case 0xF8: {
{
const int16_t immediate = static_cast<int8_t>(getBytePC()); const int16_t immediate = static_cast<int8_t>(getBytePC());
HL.reg = SP + immediate; HL.reg = SP + immediate;
@@ -2193,20 +2142,17 @@ void GameBoy::opcodeResolver() {
else else
resetFlag(HALFCARRY_FLAG); resetFlag(HALFCARRY_FLAG);
if ((SP & 0xFF) + (immediate & 0xFF) > 0xFF) if ((SP & 0xFF) + (immediate & 0xFF) > 0xFF)
setFlag(CARRY_FLAG); setFlag(CARRY_FLAG);
else else
resetFlag(CARRY_FLAG); resetFlag(CARRY_FLAG);
resetFlag(ZERO_FLAG); resetFlag(ZERO_FLAG);
resetFlag(SUBTRACT_FLAG); resetFlag(SUBTRACT_FLAG);
PC += 2; PC += 2;
addCycles(12); addCycles(12);
} } break;
break;
case 0xF9: case 0xF9:
ld(SP, HL.reg); ld(SP, HL.reg);
@@ -2220,7 +2166,7 @@ void GameBoy::opcodeResolver() {
addCycles(16); addCycles(16);
break; break;
//EI (0xFB) then DI (0xF3) never allows interrupts to happen // EI (0xFB) then DI (0xF3) never allows interrupts to happen
case 0xFB: case 0xFB:
IME = 0; IME = 0;
IME_togge = true; IME_togge = true;
@@ -2240,10 +2186,10 @@ void GameBoy::opcodeResolver() {
break; break;
default: default:
printf("Unsupported opcode found: PC:0x%.2x, Opcode:0x%.2x\n", PC, addressSpace[PC]); printf("Unsupported opcode found: PC:0x%.2x, Opcode:0x%.2x\n", PC,
addressSpace[PC]);
exit(1); exit(1);
} }
} } else
else
extendedOpcodeResolver(); extendedOpcodeResolver();
} }

View File

@@ -1,5 +1,5 @@
#include "gameboy.hpp"
#include "defines.hpp" #include "defines.hpp"
#include "gameboy.hpp"
#include <algorithm> #include <algorithm>
#include <cstdint> #include <cstdint>
#include <iostream> #include <iostream>
@@ -29,36 +29,40 @@ unsigned int GameBoy::getColourFromPalette(const Byte palette) {
} }
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 drawingInterruptEnabled =
readOnlyAddressSpace.memoryLayout.STAT & (1 << 4);
const bool oamInterruptEnabled =
readOnlyAddressSpace.memoryLayout.STAT & (1 << 5);
const bool previousInterruptLine = statInteruptLine; const bool previousInterruptLine = statInteruptLine;
if (currentMode == PPUMode::mode0 && hBlankInterruptEnabled || if (currentMode == PPUMode::mode0 && hBlankInterruptEnabled ||
currentMode == PPUMode::mode3 && drawingInterruptEnabled || currentMode == PPUMode::mode3 && drawingInterruptEnabled ||
currentMode == PPUMode::mode2 && oamInterruptEnabled) { currentMode == PPUMode::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 ==
readOnlyAddressSpace.memoryLayout.LYC) {
addressSpace.memoryLayout.STAT |= (1 << 2); addressSpace.memoryLayout.STAT |= (1 << 2);
if (lyLycInterruptEnabled) if (lyLycInterruptEnabled)
statInteruptLine = true; statInteruptLine = true;
} } else {
else {
addressSpace.memoryLayout.STAT &= ~(1 << 2); addressSpace.memoryLayout.STAT &= ~(1 << 2);
} }
if (statInteruptLine && !previousInterruptLine) if (statInteruptLine && !previousInterruptLine)
@@ -70,7 +74,7 @@ void GameBoy::checkPPUMode() {
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) {
@@ -98,8 +102,7 @@ void GameBoy::incLY() {
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::mode1); setPPUMode(PPUMode::mode1);
@@ -139,7 +142,7 @@ void GameBoy::setPPUMode(const PPUMode mode) {
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;
} }
@@ -150,21 +153,25 @@ void GameBoy::drawLine() {
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 =
const uint16_t backgroundMapAddr = LCDCBitEnabled(BG_TILE_MAP_AREA) ? 0x9C00 : 0x9800; LCDCBitEnabled(BG_TILE_MAP_AREA) ? 0x9C00 : 0x9800;
const uint16_t windowMapAddr = LCDCBitEnabled(WINDOW_TILE_MAP_AREA) ? 0x9C00 : 0x9800; const uint16_t windowMapAddr =
const uint16_t tileDataTableAddr = LCDCBitEnabled(BG_WINDOW_TILE_DATA_AREA) ? 0x8000 : 0x8800; 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 bool signedIndex = !LCDCBitEnabled(BG_WINDOW_TILE_DATA_AREA);
//BG // BG
if (LCDCBitEnabled(BG_WINDOW_ENABLE)) { if (LCDCBitEnabled(BG_WINDOW_ENABLE)) {
for (int pixel = 0; pixel < RESOLUTION_X; pixel++) { for (int pixel = 0; pixel < RESOLUTION_X; pixel++) {
const uint16_t xIndex = (pixel + readOnlyAddressSpace.memoryLayout.SCX) % 256; const uint16_t xIndex =
(pixel + readOnlyAddressSpace.memoryLayout.SCX) % 256;
// 256 pixels in total BG width // 256 pixels in total BG width
const uint16_t yIndex = (line + readOnlyAddressSpace.memoryLayout.SCY) % 256; const uint16_t yIndex =
(line + readOnlyAddressSpace.memoryLayout.SCY) % 256;
// 256 pixels in total BG height // 256 pixels in total BG height
const uint16_t tileUpper = (yIndex / 8) << 5; const uint16_t tileUpper = (yIndex / 8) << 5;
@@ -172,39 +179,46 @@ void GameBoy::drawLine() {
const uint16_t tileIndex = tileUpper + tileLower; const uint16_t tileIndex = tileUpper + tileLower;
const uint16_t tileAddr = backgroundMapAddr + tileIndex; const uint16_t tileAddr = backgroundMapAddr + tileIndex;
const int16_t tileID = signedIndex const int16_t tileID =
? static_cast<int16_t>(readOnlyAddressSpace[tileAddr]) signedIndex ? static_cast<int16_t>(readOnlyAddressSpace[tileAddr])
: readOnlyAddressSpace[tileAddr]; : readOnlyAddressSpace[tileAddr];
uint16_t tileDataAddr; uint16_t tileDataAddr;
if (signedIndex) if (signedIndex)
tileDataAddr = tileDataTableAddr + (((tileID + 128) % 256) << 4); // For signed, wrap around tileDataAddr = tileDataTableAddr +
(((tileID + 128) % 256) << 4); // For signed, wrap around
else else
tileDataAddr = tileDataTableAddr + (tileID * 16); tileDataAddr = tileDataTableAddr + (tileID * 16);
const uint8_t lineOffset = yIndex % 8; const uint8_t lineOffset = yIndex % 8;
const uint8_t tileRowData1 = readOnlyAddressSpace[tileDataAddr + (lineOffset * 2)]; const uint8_t tileRowData1 =
const uint8_t tileRowData2 = readOnlyAddressSpace[tileDataAddr + (lineOffset * 2) + 1]; readOnlyAddressSpace[tileDataAddr + (lineOffset * 2)];
const uint8_t tileRowData2 =
readOnlyAddressSpace[tileDataAddr + (lineOffset * 2) + 1];
//get pixel data (2 bits) // get pixel data (2 bits)
const uint8_t colourBit = 7 - (xIndex % 8); const uint8_t colourBit = 7 - (xIndex % 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
const uint8_t palette = (readOnlyAddressSpace.memoryLayout.BGP >> (colourNum * 2)) & 0x3; const uint8_t palette =
(readOnlyAddressSpace.memoryLayout.BGP >> (colourNum * 2)) & 0x3;
currentLinePixels[pixel] = getColourFromPalette(palette); currentLinePixels[pixel] = getColourFromPalette(palette);
} }
// For the window to be displayed on a scanline, the following conditions must be met: // For the window to be displayed on a scanline, the following conditions
// 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) // must be met: WY condition was triggered: i.e. at some point in this frame
// WX condition was triggered: i.e. the current X coordinate being rendered + 7 was equal to WX // the value of WY was equal to LY (checked at the start of Mode 2 only) WX
// Window enable bit in LCDC is set // condition was triggered: i.e. the current X coordinate being rendered + 7
//Window // was equal to WX Window enable bit in LCDC is set
// Window
const uint8_t windowY = readOnlyAddressSpace.memoryLayout.WY; const uint8_t windowY = readOnlyAddressSpace.memoryLayout.WY;
const int16_t windowX = static_cast<int16_t>(readOnlyAddressSpace.memoryLayout.WX - 7); const int16_t windowX =
if (LCDCBitEnabled(WINDOW_ENABLE) && windowX >= 0 && windowX < RESOLUTION_X && line >= windowY) { 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++) { for (int pixel = windowX; pixel < RESOLUTION_X; pixel++) {
const uint16_t yIndex = windowLineCounter; const uint16_t yIndex = windowLineCounter;
const uint16_t windowTileUpper = (yIndex / 8) << 5; const uint16_t windowTileUpper = (yIndex / 8) << 5;
@@ -215,28 +229,31 @@ void GameBoy::drawLine() {
const uint16_t tileIndex = windowTileUpper + windowTileLower; const uint16_t tileIndex = windowTileUpper + windowTileLower;
const uint16_t tileAddr = windowMapAddr + tileIndex; const uint16_t tileAddr = windowMapAddr + tileIndex;
const int16_t tileID = signedIndex const int16_t tileID =
? static_cast<int16_t>(readOnlyAddressSpace[tileAddr]) signedIndex ? static_cast<int16_t>(readOnlyAddressSpace[tileAddr])
: readOnlyAddressSpace[tileAddr]; : readOnlyAddressSpace[tileAddr];
uint16_t tileDataAddr; uint16_t tileDataAddr;
if (signedIndex) if (signedIndex)
tileDataAddr = tileDataTableAddr + (((tileID + 128) % 256) * 16); // For signed, wrap around tileDataAddr = tileDataTableAddr + (((tileID + 128) % 256) *
16); // For signed, wrap around
else else
tileDataAddr = tileDataTableAddr + (tileID * 16); tileDataAddr = tileDataTableAddr + (tileID * 16);
const uint8_t lineOffset = yIndex % 8; const uint8_t lineOffset = yIndex % 8;
const uint8_t tileRowData1 = readOnlyAddressSpace[tileDataAddr + (lineOffset * 2)]; const uint8_t tileRowData1 =
const uint8_t tileRowData2 = readOnlyAddressSpace[tileDataAddr + (lineOffset * 2) + 1]; readOnlyAddressSpace[tileDataAddr + (lineOffset * 2)];
const uint8_t tileRowData2 =
readOnlyAddressSpace[tileDataAddr + (lineOffset * 2) + 1];
//get pixel data (2 bits) // get pixel data (2 bits)
const uint8_t colourBit = 7 - (xIndex % 8); const uint8_t colourBit = 7 - (xIndex % 8);
const uint8_t colourNum = ((tileRowData2 >> colourBit) & 0x1) << 1 | ((tileRowData1 >> colourBit) & const uint8_t colourNum = ((tileRowData2 >> colourBit) & 0x1) << 1 |
0x1); ((tileRowData1 >> colourBit) & 0x1);
// Apply the BGP register for palette mapping // Apply the BGP register for palette mapping
const uint8_t palette = (readOnlyAddressSpace.memoryLayout.BGP >> (colourNum * 2)) & 0x3; const uint8_t palette =
(readOnlyAddressSpace.memoryLayout.BGP >> (colourNum * 2)) & 0x3;
currentLinePixels[pixel] = getColourFromPalette(palette); currentLinePixels[pixel] = getColourFromPalette(palette);
} }
@@ -248,15 +265,14 @@ void GameBoy::drawLine() {
uint32_t oamPixels[RESOLUTION_X]; uint32_t oamPixels[RESOLUTION_X];
std::fill_n(oamPixels, RESOLUTION_X, 0); std::fill_n(oamPixels, RESOLUTION_X, 0);
constexpr constexpr Word oamAddrStart = 0xFE00;
Word oamAddrStart = 0xFE00;
const int spriteHeight = LCDCBitEnabled(OBJ_SIZE) ? 16 : 8; const int spriteHeight = LCDCBitEnabled(OBJ_SIZE) ? 16 : 8;
int objects[10] = {0}; int objects[10] = {0};
int found = 0; int found = 0;
//oam hold 40 objects // oam hold 40 objects
//find first 10 // find first 10
for (int i = 0; i < 40 && found < 10; i++) { for (int i = 0; i < 40 && found < 10; i++) {
const int offset = i * 4; const int offset = i * 4;
const int yPos = readOnlyAddressSpace[oamAddrStart + offset] - 16; const int yPos = readOnlyAddressSpace[oamAddrStart + offset] - 16;
@@ -267,7 +283,8 @@ void GameBoy::drawLine() {
} }
} }
//sort by xPos (lower has higher priority when rendering) and then earlier objects // sort by xPos (lower has higher priority when rendering) and then earlier
// objects
for (int i = 0; i < found; i++) { for (int i = 0; i < found; i++) {
for (int j = 0; j < found - i - 1; j++) { for (int j = 0; j < found - i - 1; j++) {
const int xPos1 = readOnlyAddressSpace[objects[j] + 1]; const int xPos1 = readOnlyAddressSpace[objects[j] + 1];
@@ -281,7 +298,6 @@ void GameBoy::drawLine() {
} }
} }
for (int objectIndex = found - 1; objectIndex >= 0; objectIndex--) { for (int objectIndex = found - 1; objectIndex >= 0; objectIndex--) {
const int yPos = readOnlyAddressSpace[objects[objectIndex]] - 16; const int yPos = readOnlyAddressSpace[objects[objectIndex]] - 16;
const int xPos = readOnlyAddressSpace[objects[objectIndex] + 1] - 8; const int xPos = readOnlyAddressSpace[objects[objectIndex] + 1] - 8;
@@ -291,14 +307,14 @@ void GameBoy::drawLine() {
const bool priority = oamBitEnabled(attributes, PRIORITY); const bool priority = oamBitEnabled(attributes, PRIORITY);
const bool yFlip = oamBitEnabled(attributes, Y_FLIP); const bool yFlip = oamBitEnabled(attributes, Y_FLIP);
const bool xFlip = oamBitEnabled(attributes, X_FLIP); const bool xFlip = oamBitEnabled(attributes, X_FLIP);
//get obp0 or obj1 // get obp0 or obj1
const Byte objPalette = oamBitEnabled(attributes, OBJ_PALETTE) const Byte objPalette = oamBitEnabled(attributes, OBJ_PALETTE)
? addressSpace.memoryLayout.OBP1 ? addressSpace.memoryLayout.OBP1
: addressSpace.memoryLayout.OBP0; : addressSpace.memoryLayout.OBP0;
for (int pixel = xPos; pixel < RESOLUTION_X && pixel < xPos + 8; pixel++) { for (int pixel = xPos; pixel < RESOLUTION_X && pixel < xPos + 8;
constexpr pixel++) {
Word objectTileAddr = 0x8000; constexpr Word objectTileAddr = 0x8000;
if (pixel < 0) if (pixel < 0)
continue; continue;
@@ -306,8 +322,7 @@ void GameBoy::drawLine() {
const Byte BGP = readOnlyAddressSpace.memoryLayout.BGP; const Byte BGP = readOnlyAddressSpace.memoryLayout.BGP;
if (priority && (colour == getColourFromPalette((BGP >> 2) & 0x3) || if (priority && (colour == getColourFromPalette((BGP >> 2) & 0x3) ||
colour == getColourFromPalette((BGP >> 4) & 0x3) || colour == getColourFromPalette((BGP >> 4) & 0x3) ||
colour == getColourFromPalette((BGP >> 6) & 0x3) colour == getColourFromPalette((BGP >> 6) & 0x3))) {
)) {
oamPixels[pixel] = colour; oamPixels[pixel] = colour;
continue; continue;
} }
@@ -319,30 +334,32 @@ void GameBoy::drawLine() {
if (yFlip) if (yFlip)
objectY = (spriteHeight - 1) - objectY; objectY = (spriteHeight - 1) - objectY;
const Word objTileDataAddr = spriteHeight == 8 const Word objTileDataAddr =
? objectTileAddr + (tileIndex * 16) spriteHeight == 8 ? objectTileAddr + (tileIndex * 16)
: objectTileAddr + ((tileIndex & 0xFE) * 16); : objectTileAddr + ((tileIndex & 0xFE) * 16);
const Byte tileRow = objectY * 2; const Byte tileRow = objectY * 2;
const Byte tileRowData1 = readOnlyAddressSpace[objTileDataAddr + tileRow]; const Byte tileRowData1 =
const Byte tileRowData2 = readOnlyAddressSpace[objTileDataAddr + tileRow + 1]; readOnlyAddressSpace[objTileDataAddr + tileRow];
const Byte tileRowData2 =
readOnlyAddressSpace[objTileDataAddr + tileRow + 1];
const int bit = 7 - objectX; const int bit = 7 - objectX;
const int colorIndex = ((tileRowData2 >> bit) & 1) << 1 | ((tileRowData1 >> bit) & 1); const int colorIndex =
((tileRowData2 >> bit) & 1) << 1 | ((tileRowData1 >> bit) & 1);
// 0 is always transparent // 0 is always transparent
if (colorIndex != 0) { if (colorIndex != 0) {
const uint8_t paletteColor = (objPalette >> (colorIndex * 2)) & 0x3; const uint8_t paletteColor = (objPalette >> (colorIndex * 2)) & 0x3;
const uint32_t finalColor = getColourFromPalette(paletteColor); const uint32_t finalColor = getColourFromPalette(paletteColor);
oamPixels[pixel] = finalColor; oamPixels[pixel] = finalColor;
} } else if (oamPixels[pixel] == 0) {
else if (oamPixels[pixel] == 0) {
oamPixels[pixel] = currentLinePixels[pixel]; oamPixels[pixel] = currentLinePixels[pixel];
} }
} }
} }
//help ensure correct interaction with "BG OVER OBJ" flag // help ensure correct interaction with "BG OVER OBJ" flag
for (int i = 0; i < RESOLUTION_X; i++) { for (int i = 0; i < RESOLUTION_X; i++) {
if (oamPixels[i] != currentLinePixels[i] && oamPixels[i] != 0) if (oamPixels[i] != currentLinePixels[i] && oamPixels[i] != 0)
currentLinePixels[i] = oamPixels[i]; currentLinePixels[i] = oamPixels[i];
@@ -352,17 +369,18 @@ void GameBoy::drawLine() {
void GameBoy::SDL2setup() { void GameBoy::SDL2setup() {
SDL_Init(SDL_INIT_EVERYTHING); SDL_Init(SDL_INIT_EVERYTHING);
screen = SDL_CreateWindow("GameBoy++", screen =
SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, SDL_CreateWindow("GameBoy++", SDL_WINDOWPOS_UNDEFINED,
RESOLUTION_X, RESOLUTION_Y, SDL_WINDOWPOS_UNDEFINED, RESOLUTION_X, RESOLUTION_Y, 0);
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 {
@@ -373,11 +391,11 @@ void GameBoy::SDL2destroy() const {
} }
void GameBoy::SDL2present() { void GameBoy::SDL2present() {
SDL_UpdateTexture(texture, nullptr, framebuffer, RESOLUTION_X * sizeof(uint32_t)); SDL_UpdateTexture(texture, nullptr, framebuffer,
RESOLUTION_X * sizeof(uint32_t));
SDL_RenderClear(renderer); SDL_RenderClear(renderer);
SDL_RenderCopy(renderer, texture, nullptr, nullptr); SDL_RenderCopy(renderer, texture, nullptr, nullptr);
frameTime = SDL_GetTicks() - frameStart; frameTime = SDL_GetTicks() - frameStart;
if (frameDelay > frameTime) { if (frameDelay > frameTime) {

View File

@@ -1,13 +1,13 @@
#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;
@@ -20,24 +20,20 @@ struct GameboyTestState {
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,
const GameboyTestState &rhs) {
for (int i = 0; i < lhs.RAM.size(); i++) { for (int i = 0; i < lhs.RAM.size(); i++) {
if (std::get<1>(lhs.RAM[i]) != std::get<1>(rhs.RAM[i])) if (std::get<1>(lhs.RAM[i]) != std::get<1>(rhs.RAM[i]))
return false; return false;
} }
return (lhs.A == rhs.A && return (lhs.A == rhs.A && lhs.F == rhs.F && lhs.B == rhs.B &&
lhs.F == rhs.F && lhs.C == rhs.C && lhs.D == rhs.D && lhs.E == rhs.E &&
lhs.B == rhs.B && lhs.H == rhs.H && lhs.L == rhs.L);
lhs.C == rhs.C &&
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,9 +1,10 @@
#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;
@@ -11,7 +12,7 @@ void GameBoy::timingHandler() {
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) {
@@ -28,14 +29,15 @@ void GameBoy::timingHandler() {
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
// prevTMA regardless)
const int increments = (cycles - lastTIMAUpdate) / TIMAFrequency; const int increments = (cycles - lastTIMAUpdate) / TIMAFrequency;
if (cycles - lastTIMAUpdate >= TIMAFrequency) { if (cycles - lastTIMAUpdate >= TIMAFrequency) {
if (static_cast<int>(addressSpace.memoryLayout.TIMA) + increments > 255) { if (static_cast<int>(addressSpace.memoryLayout.TIMA) + increments > 255) {
addressSpace.memoryLayout.TIMA = prevTMA + ((addressSpace.memoryLayout.TIMA + increments) % 256); addressSpace.memoryLayout.TIMA =
prevTMA + ((addressSpace.memoryLayout.TIMA + increments) % 256);
setInterrupt(TIMER_INTERRUPT); setInterrupt(TIMER_INTERRUPT);
} } else
else
addressSpace.memoryLayout.TIMA += increments; addressSpace.memoryLayout.TIMA += increments;
lastTIMAUpdate += increments * TIMAFrequency; lastTIMAUpdate += increments * TIMAFrequency;
} }