diff --git a/CMakeLists.txt b/CMakeLists.txt index ab855ef..f39b1d0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -12,5 +12,8 @@ add_executable(GBpp src/main.cpp src/ppu.cpp src/timing.cpp src/extendedOpcodeResolver.cpp + src/mbc.cpp + src/addressSpace.cpp + src/addressSpace.hpp ) target_link_libraries(GBpp ${SDL2_LIBRARIES}) \ No newline at end of file diff --git a/src/addressSpace.cpp b/src/addressSpace.cpp new file mode 100644 index 0000000..06ba7ef --- /dev/null +++ b/src/addressSpace.cpp @@ -0,0 +1,34 @@ +#include "addressSpace.hpp" +#include + +bool AddressSpace::getBootromState() const { + return bootromLoaded; +} + +void AddressSpace::unmapBootrom() { + bootromLoaded = false; +} + +void AddressSpace::mapBootrom() { + bootromLoaded = true; +} + +void AddressSpace::loadBootrom(const std::string& filename) { + std::ifstream file; + if (const uintmax_t size = std::filesystem::file_size(filename); size != 256) { + std::cerr << "Bootrom was an unexpected size!\nQuitting!\n" << std::endl; + exit(1); + } + file.open(filename, std::ios::binary); + file.read(reinterpret_cast(bootrom), BOOTROM_SIZE); +} + +void AddressSpace::loadGame(const std::string& filename) { + game.open(filename, std::ios::binary); + + if (!game.is_open()) { + std::cerr << "Game was not found!\nQuitting!\n" << std::endl; + exit(1); + } + game.read(reinterpret_cast(memoryLayout.romBank1), ROM_BANK_SIZE * 2); +} diff --git a/src/addressSpace.hpp b/src/addressSpace.hpp new file mode 100644 index 0000000..617def7 --- /dev/null +++ b/src/addressSpace.hpp @@ -0,0 +1,77 @@ +#ifndef ADDRESSSPACE_HPP +#define ADDRESSSPACE_HPP +#include +#include +#include +#include +#include +#include "defines.hpp" + +class AddressSpace { + bool bootromLoaded = true; + Byte bootrom[BOOTROM_SIZE] = {0}; + std::ifstream game; + +public: + AddressSpace() { + // Initialize the memory to zero + memoryLayout = {}; + std::memset(memoryLayout.memory, 0, sizeof(memoryLayout.memory)); + } + + // Nested union for the memory layout + union MemoryLayout { + Byte memory[0x10000]; + + struct { + Byte romBank1[ROM_BANK_SIZE]; // Mapped to 0x0000 + Byte romBankSwitch[ROM_BANK_SIZE]; // Mapped to 0x4000 + Byte vram[0x2000]; // Mapped to 0x8000 + Byte externalRam[0x2000]; // Mapped to 0xA000 + Byte memoryBank1[0x1000]; // Mapped to 0xC000 + Byte memoryBank2[0x1000]; // Mapped to 0xD000 + Byte echoRam[0x1E00]; // Mapped to 0xE000 (Echo RAM, mirrors 0xC000 to 0xDFFF) + Byte spriteAttributeTable[0xA0]; // Mapped to 0xFE00 + Byte notUsable[0x60]; // Mapped to 0xFEA0 + Byte io[0x80]; // Mapped to 0xFF00, 0xFF0F is interrupt flag + Byte specialRam[0x7F]; // Mapped to 0xFF80 + Byte interuptEnableReg; // Mapped to 0xFFFF + }; + } memoryLayout{}; + + void unmapBootrom(); + void mapBootrom(); + bool getBootromState() const; + void loadBootrom(const std::string& filename); + void loadGame(const std::string& filename); + + void determineMBCInfo(); + MBCType MBC = {}; + uint32_t RomSize = 0; + uint32_t RomPages = 0; + uint32_t ExternalRamSize = 0; + uint32_t ExternalRamPages = 0; + + + //overload [] for echo ram and bootrom support + Byte operator[](const uint32_t address) const { + if (address >= 0xE000 && address < 0xFE00) + return memoryLayout.echoRam[address - 0x2000]; + if (address < 0x0100 && bootromLoaded) + return bootrom[address]; + + return memoryLayout.memory[address]; + } + + Byte& operator[](const uint32_t address) { + if (address >= 0xE000 && address < 0xFE00) + return memoryLayout.echoRam[address - 0x2000]; + if (address < 0x0100 && bootromLoaded) + return bootrom[address]; + + return memoryLayout.memory[address]; + } +}; + + +#endif //ADDRESSSPACE_HPP diff --git a/src/defines.hpp b/src/defines.hpp index 85b0934..73e41f9 100644 --- a/src/defines.hpp +++ b/src/defines.hpp @@ -46,5 +46,50 @@ #define SCANLINE_OAM_FREQ 80 //PPU_MODE 2 #define SCANLINE_VRAM_FREQ 80 //PPU_MODE 3 +//two bits per colour +enum Colour { + black = 0b11, + darkGray = 0b10, + lightGray = 0b01, + white = 0b00 +}; + +enum MBCType { + romOnly = 0x00, + MBC1 = 0x01, + MBC1Ram = 0x02, + MBC1RamBattery = 0x03, + MBC2 = 0x05, + MBC2Battery = 0x06, + RomRam = 0x08, //unused + RomRamBattery = 0x09, //unused + MMM01 = 0x0B, //multigame roms only + MMM01Ram = 0x0C, + MMM01RamBattery = 0x0D, + MBC3TimerBattery = 0x0F, + MBC3TimerRamBattery = 0x10, + MBC3 = 0x11, + MBC3Ram = 0x12, // MBC3 with 64 KiB of SRAM refers to MBC30, used only in Pocket Monsters: Crystal Version. + MBC3RamBattery = 0x13, + MBC5 = 0x19, + MBC5Ram = 0x1A, + MBC5RamBattery = 0x1B, + MBC5Rumble = 0x1C, + MBC5RumbleRam = 0x1D, + MBC5RumbleRamBattery = 0x1E, + MBC6 = 0x20, + MBC7SensorRumbleRamBattery = 0x22, + PocketCamera = 0xFC, + BandaiTama5 = 0xFD, + HuC3 = 0xFE, + HuC1RamBattery = 0xFF +}; + +enum PPUMode { + mode0, // Horizontal Blank (Mode 0): No access to video RAM, occurs during horizontal blanking period. + mode1, // Vertical Blank (Mode 1): No access to video RAM, occurs during 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 diff --git a/src/gameboy.cpp b/src/gameboy.cpp index a835277..c20e384 100644 --- a/src/gameboy.cpp +++ b/src/gameboy.cpp @@ -1,38 +1,6 @@ #include #include "gameboy.hpp" -bool AddressSpace::getBootromState() const { - return bootromLoaded; -} - -void AddressSpace::unmapBootrom() { - bootromLoaded = false; -} - -void AddressSpace::mapBootrom() { - bootromLoaded = true; -} - -void AddressSpace::loadBootrom(const std::string& filename) { - std::ifstream file; - if (const uintmax_t size = std::filesystem::file_size(filename); size != 256) { - std::cerr << "Bootrom was an unexpected size!\nQuitting!\n" << std::endl; - exit(1); - } - file.open(filename, std::ios::binary); - file.read(reinterpret_cast(bootrom), BOOTROM_SIZE); -} - -void AddressSpace::loadGame(const std::string& filename) { - game.open(filename, std::ios::binary); - - if (!game.is_open()) { - std::cerr << "Game was not found!\nQuitting!\n" << std::endl; - exit(1); - } - game.read(reinterpret_cast(memoryLayout.romBank1), ROM_BANK_SIZE * 2); -} - void GameBoy::addCycles(const uint8_t ticks) { cycles += ticks; if (ppuEnabled) { @@ -54,43 +22,46 @@ void GameBoy::start(std::string bootrom, std::string game) { bool display = false; while (!quit) { - // Event loop: Check and handle SDL events + // Event loop while (SDL_PollEvent(&event)) { if (event.type == SDL_QUIT) { - quit = true; // Set the quit flag when the close button is hit + quit = true; } } - if (PC > 0xFF && addressSpace.getBootromState()) { - addressSpace.unmapBootrom(); - } - ppuEnabled = (*LCDC) & 0x80; + while (!rendered) { + if (PC > 0xFF && addressSpace.getBootromState()) { + addressSpace.unmapBootrom(); + } + ppuEnabled = (*LCDC) & 0x80; - // if (PC == 0x100) - // display = true; - // if (display) { - // printf("Cycles: %lu, Opcode: 0x%.2x PPU cycles: %lu, PPMode: %d\n", cycles, addressSpace[PC], - // cyclesSinceLastScanline(), currentMode); - // printf("PC:0x%.2x, SP:0x%.2x\n", PC, SP); - // printf("AF:0x%.4x, BC:0x%.4x\n", AF.reg, BC.reg); - // printf("DE:0x%.4x, HL:0x%.4x\n", DE.reg, HL.reg); - // printf("IME:%d IF:0x%.2x IE:0x%.2x\n", IME, (*IF), (*IE)); - // printf("LCDC:%.2x STAT:0x%.2x LY:%d LYC:%d\n", (*LCDC), (*STAT), (*LY), (*LYC)); - // printf("\n"); - // } + // if (PC == 0x100) + // display = true; + // if (display) { + // printf("Cycles: %lu, Opcode: 0x%.2x PPU cycles: %lu, PPMode: %d\n", cycles, addressSpace[PC], + // cyclesSinceLastScanline(), currentMode); + // printf("PC:0x%.2x, SP:0x%.2x\n", PC, SP); + // printf("AF:0x%.4x, BC:0x%.4x\n", AF.reg, BC.reg); + // printf("DE:0x%.4x, HL:0x%.4x\n", DE.reg, HL.reg); + // printf("IME:%d IF:0x%.2x IE:0x%.2x\n", IME, (*IF), (*IE)); + // printf("LCDC:%.2x STAT:0x%.2x LY:%d LYC:%d\n", (*LCDC), (*STAT), (*LY), (*LYC)); + // printf("\n"); + // } - opcodeResolver(); - interruptHandler(); - timingHandler(); - if (ppuEnabled) { - ppuUpdate(); - } - else { - ppuCycles = 2; - lastScanline = 0; - lastRefresh = 0; - (*LY) = 0x00; - (*STAT) &= 0xfc; + opcodeResolver(); + interruptHandler(); + timingHandler(); + if (ppuEnabled) { + ppuUpdate(); + } + else { + ppuCycles = 2; + lastScanline = 0; + lastRefresh = 0; + (*LY) = 0x00; + (*STAT) &= 0xfc; + } } + rendered = false; } } diff --git a/src/gameboy.hpp b/src/gameboy.hpp index e00d9d0..7c97611 100644 --- a/src/gameboy.hpp +++ b/src/gameboy.hpp @@ -9,21 +9,7 @@ #include #include #include "defines.hpp" - -//two bits per colour -enum Colour { - black = 0b11, - darkGray = 0b10, - lightGray = 0b01, - white = 0b00 -}; - -enum PPUMode { - mode0, // Horizontal Blank (Mode 0): No access to video RAM, occurs during horizontal blanking period. - mode1, // Vertical Blank (Mode 1): No access to video RAM, occurs during 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. -}; +#include "addressSpace.hpp" union RegisterPair { Word reg; //register.reg == (hi << 8) + lo. (hi is more significant than lo) @@ -34,64 +20,6 @@ union RegisterPair { }; }; -class AddressSpace { - bool bootromLoaded = true; - Byte bootrom[BOOTROM_SIZE] = {0}; - std::ifstream game; - -public: - AddressSpace() { - // Initialize the memory to zero - memoryLayout = {}; - std::memset(memoryLayout.memory, 0, sizeof(memoryLayout.memory)); - } - - // Nested union for the memory layout - union MemoryLayout { - Byte memory[0x10000]; - - struct { - Byte romBank1[ROM_BANK_SIZE]; // Mapped to 0x0000 - Byte romBankSwitch[ROM_BANK_SIZE]; // Mapped to 0x4000 - Byte vram[0x2000]; // Mapped to 0x8000 - Byte externalRam[0x2000]; // Mapped to 0xA000 - Byte memoryBank1[0x1000]; // Mapped to 0xC000 - Byte memoryBank2[0x1000]; // Mapped to 0xD000 - Byte echoRam[0x1E00]; // Mapped to 0xE000 (Echo RAM, mirrors 0xC000 to 0xDFFF) - Byte spriteAttributeTable[0xA0]; // Mapped to 0xFE00 - Byte notUsable[0x60]; // Mapped to 0xFEA0 - Byte io[0x80]; // Mapped to 0xFF00, 0xFF0F is interrupt flag - Byte specialRam[0x7F]; // Mapped to 0xFF80 - Byte interuptEnableReg; // Mapped to 0xFFFF - }; - } memoryLayout{}; - - void unmapBootrom(); - void mapBootrom(); - bool getBootromState() const; - void loadBootrom(const std::string& filename); - void loadGame(const std::string& filename); - - //overload [] for echo ram and bootrom support - Byte operator[](const uint32_t address) const { - if (address >= 0xE000 && address < 0xFE00) - return memoryLayout.echoRam[address - 0x2000]; - if (address < 0x0100 && bootromLoaded) - return bootrom[address]; - - return memoryLayout.memory[address]; - } - - Byte& operator[](const uint32_t address) { - if (address >= 0xE000 && address < 0xFE00) - return memoryLayout.echoRam[address - 0x2000]; - if (address < 0x0100 && bootromLoaded) - return bootrom[address]; - - return memoryLayout.memory[address]; - } -}; - class GameBoy { //T-cycles not M-cycles (4 T-cycles = 1 M-cycle) uint64_t cycles = 0; @@ -103,6 +31,7 @@ class GameBoy { uint64_t lastScanline = 0; uint64_t cyclesToStayInHblank = -1; uint64_t lastDivUpdate = 0; + bool rendered = false; uint8_t IME = 0; //enables interupts @@ -120,7 +49,6 @@ class GameBoy { //General purpose hardware registers Byte* const JOYP = &addressSpace[0xFF00]; - Byte* const SB = &addressSpace[0xFF01]; Byte* const SC = &addressSpace[0xFF02]; Byte* const DIV = &addressSpace[0xFF04]; @@ -223,6 +151,7 @@ class GameBoy { //OPCODE FUNCTIONS template void ld(T& dest, T src); + void ldW(Byte& dest, Word src); template void orBitwise(T& dest, T src); template @@ -249,8 +178,6 @@ class GameBoy { void daa(); void stop(); template - void ldW(T dest, T src); - template void cp(T value); template void dec(T& reg); diff --git a/src/mbc.cpp b/src/mbc.cpp new file mode 100644 index 0000000..7529ae3 --- /dev/null +++ b/src/mbc.cpp @@ -0,0 +1 @@ +#include "gameboy.hpp" diff --git a/src/opcodeResolver.cpp b/src/opcodeResolver.cpp index 7f131c8..3e48396 100644 --- a/src/opcodeResolver.cpp +++ b/src/opcodeResolver.cpp @@ -46,19 +46,22 @@ void GameBoy::ret() { template void GameBoy::ld(T& dest, T src) { - if constexpr (std::is_same_v) { - if (&dest == DIV) + if constexpr (std::is_same_v) { + if (&dest == DIV) { *DIV = 0x00; - lastDivUpdate = cycles; + lastDivUpdate = cycles; + } + else { + dest = src; + } } else { + //16-bit register pair write dest = src; } } - -template -void GameBoy::ldW(T dest, T src) { +void GameBoy::ldW(Byte& dest, const Word src) { if (sizeof(src) == sizeof(Word)) { addressSpace[dest] = static_cast(src & 0xFF00) >> 8; addressSpace[dest + 1] = static_cast(src & 0xFF); @@ -169,28 +172,22 @@ void GameBoy::sbc(T value) { T carry = getFlag(CARRY_FLAG) ? 1 : 0; T result = AF.hi - value - carry; - if (AF.hi < value + carry) { + if (AF.hi < value + carry) setFlag(CARRY_FLAG); - } - else { + else resetFlag(CARRY_FLAG); - } - if (result == 0) { + if (result == 0) setFlag(ZERO_FLAG); - } - else { + else resetFlag(ZERO_FLAG); - } setFlag(SUBTRACT_FLAG); - if ((AF.hi & 0xF) < (value & 0xF) + carry) { + if ((AF.hi & 0xF) < (value & 0xF) + carry) setFlag(HALFCARRY_FLAG); - } - else { + else resetFlag(HALFCARRY_FLAG); - } AF.hi = result; } @@ -398,7 +395,6 @@ void GameBoy::dec(T& reg) { } void GameBoy::swap(Byte& value) { - // Extract the lower and upper nibbles of the register const Byte lowerNibble = value & 0x0F; const Byte upperNibble = (value >> 4) & 0x0F; @@ -734,7 +730,7 @@ void GameBoy::opcodeResolver() { break; case 0x08: - ldW(getWordPC(), SP); + ldW(addressSpace[getWordPC()], SP); PC += 3; addCycles(20); break; @@ -2237,7 +2233,7 @@ void GameBoy::opcodeResolver() { break; case 0xFA: - ldW(AF.hi, addressSpace[getWordPC()]); + ld(AF.hi, addressSpace[getWordPC()]); PC += 3; addCycles(16); break; diff --git a/src/ppu.cpp b/src/ppu.cpp index eafbcf8..c5d8297 100644 --- a/src/ppu.cpp +++ b/src/ppu.cpp @@ -174,7 +174,7 @@ void GameBoy::SDL2setup() { screen = SDL_CreateWindow("GBpp", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, RESOLUTION_X, RESOLUTION_Y, - SDL_WINDOW_OPENGL); + 0); // Create an SDL renderer to draw on the window renderer = SDL_CreateRenderer(screen, -1, SDL_RENDERER_PRESENTVSYNC | SDL_RENDERER_ACCELERATED); @@ -203,7 +203,8 @@ void GameBoy::SDL2present() { if (frameDelay > frameTime) { SDL_Delay(frameDelay - frameTime); } - frameStart = SDL_GetTicks(); SDL_RenderPresent(renderer); + frameStart = SDL_GetTicks(); + rendered = true; }