diff --git a/CMakeLists.txt b/CMakeLists.txt index bf81239..e6c873c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -16,5 +16,6 @@ add_executable(GBpp src/main.cpp src/addressSpace.cpp src/addressSpace.hpp src/testing.hpp + src/joypad.cpp ) target_link_libraries(GBpp ${SDL2_LIBRARIES}) \ No newline at end of file diff --git a/src/addressSpace.hpp b/src/addressSpace.hpp index 1a2d2e4..179dad7 100644 --- a/src/addressSpace.hpp +++ b/src/addressSpace.hpp @@ -40,9 +40,9 @@ public: //Timer registers Byte TIMA; Byte TMA; - Byte TAC; + Byte TAC = 0xF8; //interrupt flag and enable - Byte IF; + Byte IF = 0xE1;; //Sound registers Byte NR10; Byte NR11; @@ -158,7 +158,6 @@ public: return memoryLayout.SB; case 0xFF02: return memoryLayout.SC; - // Timer registers case 0xFF04: return memoryLayout.DIV; case 0xFF05: @@ -166,11 +165,9 @@ public: case 0xFF06: return memoryLayout.TMA; case 0xFF07: - return memoryLayout.TAC; - // Interrupt flag + return memoryLayout.TAC | 0xF8;; case 0xFF0F: - return memoryLayout.IF; - // Sound registers + return memoryLayout.IF | 0xE0; case 0xFF10: return memoryLayout.NR10; case 0xFF11: @@ -223,6 +220,8 @@ public: case 0xFF43: return memoryLayout.SCX; case 0xFF44: + //for debugging only + //return 0x90; return memoryLayout.LY; case 0xFF45: return memoryLayout.LYC; @@ -238,7 +237,6 @@ public: return memoryLayout.WY; case 0xFF4B: return memoryLayout.WX; - default: if (address >= 0xFF30 && address <= 0xFF3F) { return memoryLayout.waveRam[address - 0xFF30]; @@ -253,6 +251,7 @@ public: //write Byte& operator[](const Word address) { + dummyVal = 0xFF; if (testing) return testRam[address]; if (address < 0x0100 && bootromLoaded) @@ -282,19 +281,17 @@ public: case 0xFF02: return memoryLayout.SC; case 0xFF04: - return memoryLayout.DIV; + memoryLayout.DIV = 0; + return dummyVal; // Timer registers case 0xFF05: return memoryLayout.TIMA; - // The address 0xFF15 is mentioned as unused, possibly a mistake? Original has TMA = 0xFF06 typically. case 0xFF06: return memoryLayout.TMA; case 0xFF07: return memoryLayout.TAC; - // Interrupt flag case 0xFF0F: return memoryLayout.IF; - // Sound registers case 0xFF10: return memoryLayout.NR10; case 0xFF11: @@ -337,7 +334,6 @@ public: return memoryLayout.NR51; case 0xFF26: return memoryLayout.NR52; - // PPU registers case 0xFF40: return memoryLayout.LCDC; case 0xFF41: @@ -347,7 +343,8 @@ public: case 0xFF43: return memoryLayout.SCX; case 0xFF44: - return memoryLayout.LY; + dummyVal = memoryLayout.LY; + return dummyVal; case 0xFF45: return memoryLayout.LYC; case 0xFF46: @@ -362,7 +359,6 @@ public: return memoryLayout.WY; case 0xFF4B: return memoryLayout.WX; - default: if (address >= 0xFF30 && address <= 0xFF3F) { return memoryLayout.waveRam[address - 0xFF30]; diff --git a/src/defines.hpp b/src/defines.hpp index 91587bc..cc68923 100644 --- a/src/defines.hpp +++ b/src/defines.hpp @@ -31,6 +31,15 @@ #define RESOLUTION_Y 144 #define SCREEN_BPP 3 +#define BG_WINDOW_ENABLE 0 +#define OBJ_ENABLE 1 +#define OBJ_SIZE 2 +#define BG_TILE_MAP_AREA 3 +#define BG_WINDOW_TILE_DATA_AREA 4 +#define WINDOW_ENABLE 5 +#define WINDOW_TILE_MAP_AREA 6 +#define LCD_ENABLE 7 + #define ROM_BANK_SIZE 0x4000 #define RAM_BANK_SIZE 0x2000 @@ -55,6 +64,17 @@ enum Colour { white = 0b00 }; +struct Input { + bool UP = false; + bool DOWN = false; + bool LEFT = false; + bool RIGHT = false; + bool B = false; + bool A = false; + bool START = false; + bool SELECT = false; +}; + enum MBCType { romOnly = 0x00, MBC1 = 0x01, diff --git a/src/gameboy.cpp b/src/gameboy.cpp index 05d75cc..0a0ccd3 100644 --- a/src/gameboy.cpp +++ b/src/gameboy.cpp @@ -52,7 +52,7 @@ void GameBoy::start(std::string bootrom, std::string game) { addressSpace.createRamBank(); //init some registers that won't otherwise by set - addressSpace.memoryLayout.JOYP = 0xCF; + addressSpace.memoryLayout.JOYP = 0xDF; addressSpace.memoryLayout.SC = 0x7E; bool quit = false; @@ -75,26 +75,38 @@ void GameBoy::start(std::string bootrom, std::string game) { addressSpace.unmapBootrom(); } ppuEnabled = addressSpace.memoryLayout.LCDC & 0x80; + prevTMA = addressSpace.memoryLayout.TMA; - // if (PC == 0x100) - // display = true; - // if (display) { - // printf("Cycles: %lu, Opcode: 0x%.2x PPU cycles: %lu, PPMode: %d\n", cycles, readOnlyAddressSpace[PC], - // cyclesSinceLastScanline(), currentMode); - // printf("AF:0x%.4x, BC:0x%.4x\n", AF.reg, BC.reg); - // printf("DE:0x%.4x, HL:0x%.4x\n", DE.reg, HL.reg); - // printf("IME:%d IF:0x%.2x IE:0x%.2x\n", IME, (*IF), (*IE)); - // printf("PC:0x%.4x, SP:0x%.4x\n", PC, SP); - // printf("LCDC:%.2x STAT:0x%.2x LY:%d LYC:%d\n", (*LCDC), (*STAT), (*LY), (*LYC)); - // printf("\n"); - // } + if (PC == 0x100) + display = true; + if (display) { + // printf("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", + // AF.hi, AF.lo, BC.hi, BC.lo, DE.hi, DE.lo, HL.hi, HL.lo, SP, PC, readOnlyAddressSpace[PC], + // readOnlyAddressSpace[PC + 1], readOnlyAddressSpace[PC + 2], readOnlyAddressSpace[PC + 3]); + + + // printf("Cycles: %lu, Opcode: 0x%.2x PPU cycles: %lu, PPMode: %d\n", cycles, readOnlyAddressSpace[PC], + // cyclesSinceLastScanline(), currentMode); + // printf("AF:0x%.4x, BC:0x%.4x\n", AF.reg, BC.reg); + // printf("DE:0x%.4x, HL:0x%.4x\n", DE.reg, HL.reg); + // printf("IME:%d IF:0x%.2x IE:0x%.2x\n", IME, (*IF), (*IE)); + // printf("PC:0x%.4x, SP:0x%.4x\n", PC, SP); + // printf("LCDC:%.2x STAT:0x%.2x LY:%d LYC:%d\n", (*LCDC), (*STAT), (*LY), (*LYC)); + // printf("\n"); + } // if (PC >= 0xf000) // exit(1); - opcodeResolver(); - addressSpace.MBCUpdate(); - interruptHandler(); + + if (!halted) { + opcodeResolver(); + addressSpace.MBCUpdate(); + } + else { + addCycles(4); + } timingHandler(); + interruptHandler(); if (ppuEnabled) { ppuUpdate(); } diff --git a/src/gameboy.hpp b/src/gameboy.hpp index cbf8db7..c2bc317 100644 --- a/src/gameboy.hpp +++ b/src/gameboy.hpp @@ -50,6 +50,13 @@ class GameBoy { const AddressSpace& readOnlyAddressSpace = addressSpace; PPUMode currentMode = PPUMode::mode0; + Byte windowLineCounter = 0; + + Byte prevTMA = 0; + uint64_t lastTIMAUpdate = 0; + bool halted = false; + bool haltBug = true; + bool stopped = false; //3 colour channels uint32_t* framebuffer = new uint32_t[RESOLUTION_X * RESOLUTION_Y * SCREEN_BPP]; @@ -61,7 +68,12 @@ class GameBoy { uint32_t frameTime = 0; const int frameDelay = 1000 / V_SYNC; + Input joypadInput; + void opcodeResolver(); + + bool statInteruptLine = false; + bool testLCDCBitEnabled(Byte bit) const; void incLY(); void ppuUpdate(); void drawLine(); @@ -76,6 +88,7 @@ class GameBoy { void interruptHandler(); bool testInterruptEnabled(Byte interrupt) const; + void setInterrupt(Byte interrupt); void resetInterrupt(Byte interrupt); void VBlankHandle(); diff --git a/src/interupts.cpp b/src/interupts.cpp index c4fa0ff..38c68af 100644 --- a/src/interupts.cpp +++ b/src/interupts.cpp @@ -5,72 +5,85 @@ bool GameBoy::testInterruptEnabled(const Byte interrupt) const { return readOnlyAddressSpace.memoryLayout.IE & static_cast(1 << interrupt); } +void GameBoy::setInterrupt(const Byte interrupt) { + addressSpace.memoryLayout.IF |= 1 << interrupt; + addressSpace.memoryLayout.IF |= 0xE0; +} + void GameBoy::resetInterrupt(const Byte interrupt) { addressSpace.memoryLayout.IF &= ~(1 << interrupt); addressSpace.memoryLayout.IF |= 0xE0; } void GameBoy::interruptHandler() { - if (!IME) - return; - if (readOnlyAddressSpace.memoryLayout.IF & static_cast(1 << VBLANK_INTERRUPT) && testInterruptEnabled( - VBLANK_INTERRUPT)) - VBlankHandle(); + VBLANK_INTERRUPT)) { + if (IME) + VBlankHandle(); + halted = false; + } if (readOnlyAddressSpace.memoryLayout.IF & static_cast(1 << LCD_STAT_INTERRUPT) && testInterruptEnabled( - LCD_STAT_INTERRUPT)) - LCDStatHandle(); + LCD_STAT_INTERRUPT)) { + if (IME) + LCDStatHandle(); + halted = false; + } if (readOnlyAddressSpace.memoryLayout.IF & static_cast(1 << TIMER_INTERRUPT) && testInterruptEnabled( - TIMER_INTERRUPT)) - timerHandle(); + TIMER_INTERRUPT)) { + if (IME) + timerHandle(); + halted = false; + } if (readOnlyAddressSpace.memoryLayout.IF & static_cast(1 << SERIAL_INTERRUPT) && testInterruptEnabled( - SERIAL_INTERRUPT)) - serialHandle(); + SERIAL_INTERRUPT)) { + if (IME) + serialHandle(); + halted = false; + } if (readOnlyAddressSpace.memoryLayout.IF & static_cast(1 << JOYPAD_INTERRUPT) && testInterruptEnabled( - JOYPAD_INTERRUPT)) - joypadHandle(); + JOYPAD_INTERRUPT)) { + if (IME) + joypadHandle(); + halted = false; + } } void GameBoy::VBlankHandle() { - //printf("VBlank interrupt\n"); IME = 0; push(PC); + addCycles(20); PC = 0x40; resetInterrupt(VBLANK_INTERRUPT); } void GameBoy::LCDStatHandle() { - //printf("LCD stat interrupt\n"); IME = 0; push(PC); - addCycles(16); + addCycles(20); PC = 0x48; resetInterrupt(LCD_STAT_INTERRUPT); } void GameBoy::timerHandle() { - //printf("timer interrupt\n"); IME = 0; push(PC); - addCycles(16); + addCycles(20); PC = 0x50; resetInterrupt(TIMER_INTERRUPT); } void GameBoy::serialHandle() { - //printf("serial interrupt\n"); IME = 0; push(PC); - addCycles(16); + addCycles(20); PC = 0x58; resetInterrupt(SERIAL_INTERRUPT); } void GameBoy::joypadHandle() { - printf("joypad interrupt\n"); IME = 0; push(PC); - addCycles(16); + addCycles(20); PC = 0x60; resetInterrupt(JOYPAD_INTERRUPT); } diff --git a/src/joypad.cpp b/src/joypad.cpp new file mode 100644 index 0000000..e69de29 diff --git a/src/main.cpp b/src/main.cpp index 9109b0e..ef32f82 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -13,7 +13,7 @@ int main(int argc, char** argv) { auto* gb = new GameBoy(); gb->SDL2setup(); //runJSONTests(gb); - gb->start("../dmg_boot.bin", "../roms/07-jr,jp,call,ret,rst.gb"); + gb->start("../dmg_boot.bin", "../roms/dmg-acid2.gb"); gb->SDL2destroy(); delete gb; diff --git a/src/opcodeResolver.cpp b/src/opcodeResolver.cpp index def4f1c..1448797 100644 --- a/src/opcodeResolver.cpp +++ b/src/opcodeResolver.cpp @@ -391,7 +391,11 @@ void GameBoy::swap(Byte& value) { resetFlag(CARRY_FLAG); } -void GameBoy::halt() {} +void GameBoy::halt() { + halted = true; + if (!IME && addressSpace.memoryLayout.IE & addressSpace.memoryLayout.IF) + haltBug = true; +} void GameBoy::rrc(Byte& reg) { const Byte lsb = reg & 0x01; @@ -652,7 +656,9 @@ void GameBoy::ccf() { resetFlag(HALFCARRY_FLAG); } -void GameBoy::stop() {} +void GameBoy::stop() { + stopped = true; +} void GameBoy::opcodeResolver() { if (readOnlyAddressSpace[PC] != 0xCB) { @@ -1955,7 +1961,7 @@ void GameBoy::opcodeResolver() { } else { addCycles(8); - PC += 3; + PC += 1; } break; diff --git a/src/ppu.cpp b/src/ppu.cpp index 79d36ee..338c5f8 100644 --- a/src/ppu.cpp +++ b/src/ppu.cpp @@ -4,33 +4,45 @@ #include #include +bool GameBoy::testLCDCBitEnabled(const Byte bit) const { + return readOnlyAddressSpace.memoryLayout.LCDC & static_cast(1 << bit); +} + void GameBoy::ppuUpdate() { //test for HBlank checkPPUMode(); - if (readOnlyAddressSpace.memoryLayout.LY == readOnlyAddressSpace.memoryLayout.LYC || readOnlyAddressSpace. - memoryLayout.STAT & (1 << 6)) { - // Request STAT interrupt if LY matches LYC - // bug on DMG models triggers a STAT interrupt anytime the STAT register is written - // Road Rage and Zerd no Denetsu rely on this + // TODO: + // bug on DMG models triggers a STAT interrupt anytime the STAT register is written + // Road Rage and Zerd no Denetsu rely on this + + // Check for STAT interrupts and request if needed (e.g., when entering specific modes) + const bool hBlankInterruptEnabled = 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; + + if (currentMode == PPUMode::mode0 && hBlankInterruptEnabled || + currentMode == PPUMode::mode3 && drawingInterruptEnabled || + currentMode == PPUMode::mode2 && oamInterruptEnabled) { + statInteruptLine = true; + } + else { + statInteruptLine = false; + } + + const bool lyLycInterruptEnabled = readOnlyAddressSpace.memoryLayout.STAT & (1 << 6); + + if (readOnlyAddressSpace.memoryLayout.LY == readOnlyAddressSpace.memoryLayout.LYC) { addressSpace.memoryLayout.STAT |= (1 << 2); + if (lyLycInterruptEnabled) + statInteruptLine = true; } else { addressSpace.memoryLayout.STAT &= ~(1 << 2); } - - // Check for STAT interrupts and request if needed (e.g., when entering specific modes) - bool hBlankInterruptEnabled = readOnlyAddressSpace.memoryLayout.STAT & (1 << 3); - bool vBlankInterruptEnabled = readOnlyAddressSpace.memoryLayout.STAT & (1 << 4); - /* Determine if VBlank interrupt is enabled */ - bool oamInterruptEnabled = readOnlyAddressSpace.memoryLayout.STAT & (1 << 5); - /* Determine if OAM Search interrupt is enabled */ - - if (currentMode == PPUMode::mode0 && hBlankInterruptEnabled || - currentMode == PPUMode::mode1 && vBlankInterruptEnabled || - currentMode == PPUMode::mode2 && oamInterruptEnabled) { - addressSpace.memoryLayout.IF |= 0x2; - } + if (statInteruptLine && !previousInterruptLine) + addressSpace.memoryLayout.IF |= 1 << LCD_STAT_INTERRUPT; } void GameBoy::checkPPUMode() { @@ -38,7 +50,7 @@ void GameBoy::checkPPUMode() { const uint64_t cyclesSinceScanline = cyclesSinceLastScanline(); switch (currentMode) { - //hblank and vblank + //hblank AND vblank case 0: case 1: if (cyclesSinceScanline > SCANLINE_DURATION) { @@ -65,6 +77,7 @@ void GameBoy::incLY() { setPPUMode(PPUMode::mode2); if (addressSpace.memoryLayout.LY > SCANLINES_PER_FRAME - 1) { addressSpace.memoryLayout.LY = 0; + windowLineCounter = 0; } else if (addressSpace.memoryLayout.LY == 144) { // VBlank Period @@ -118,58 +131,126 @@ void GameBoy::drawLine() { // Pointer to the current line's pixel data in the framebuffer uint32_t* currentLinePixels = framebuffer + lineStartIndex; + std::fill_n(currentLinePixels, RESOLUTION_X, 0xFFFFFFFF); - if (!(readOnlyAddressSpace.memoryLayout.LCDC & 0x1)) { - std::fill_n(currentLinePixels, RESOLUTION_X, 0xFFFFFFFF); // Fill line with white if BG display is off. - return; - } - const uint16_t backgroundMapAddr = (readOnlyAddressSpace.memoryLayout.LCDC & 0x08) ? 0x9C00 : 0x9800; - const uint16_t tileDataTableAddr = (readOnlyAddressSpace.memoryLayout.LCDC & 0x10) ? 0x8000 : 0x8800; - const bool signedIndex = !(readOnlyAddressSpace.memoryLayout.LCDC & 0x10); + const uint16_t backgroundMapAddr = testLCDCBitEnabled(BG_TILE_MAP_AREA) ? 0x9C00 : 0x9800; + const uint16_t windowMapAddr = testLCDCBitEnabled(WINDOW_TILE_MAP_AREA) ? 0x9C00 : 0x9800; + const uint16_t tileDataTableAddr = testLCDCBitEnabled(BG_WINDOW_TILE_DATA_AREA) ? 0x8000 : 0x8800; + const bool signedIndex = !testLCDCBitEnabled(BG_WINDOW_TILE_DATA_AREA); - for (int pixel = 0; pixel < RESOLUTION_X; pixel++) { - const uint8_t xPos = (pixel + readOnlyAddressSpace.memoryLayout.SCX) % 256; // 256 pixels in total BG width - const uint8_t yPos = (line + readOnlyAddressSpace.memoryLayout.SCY) % 256; // 256 pixels in total BG height + //BG + if (testLCDCBitEnabled(BG_WINDOW_ENABLE)) { + for (int pixel = 0; pixel < RESOLUTION_X; pixel++) { + 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 tileRow = (yPos / 8) * 32; - const uint16_t tileCol = xPos / 8; - const uint16_t tileIndex = tileRow + tileCol; + const uint16_t tileUpper = (yIndex / 8) << 5; + const uint16_t tileLower = xIndex / 8 & 0x1F; + const uint16_t tileIndex = tileUpper + tileLower; - const uint16_t tileAddr = backgroundMapAddr + tileIndex; - const int8_t tileID = signedIndex - ? static_cast(readOnlyAddressSpace[tileAddr]) - : addressSpace[tileAddr]; + const uint16_t tileAddr = backgroundMapAddr + tileIndex; + const int16_t tileID = signedIndex + ? static_cast(readOnlyAddressSpace[tileAddr]) + : readOnlyAddressSpace[tileAddr]; - uint16_t tileDataAddr; - if (signedIndex) { - tileDataAddr = tileDataTableAddr + (((tileID + 128) % 256) * 16); // For signed, wrap around - } - else { - tileDataAddr = tileDataTableAddr + (tileID * 16); + uint16_t tileDataAddr; + if (signedIndex) + tileDataAddr = tileDataTableAddr + (((tileID + 128) % 256) << 4); // For signed, wrap around + else + tileDataAddr = tileDataTableAddr + (tileID * 16); + + + const uint8_t lineOffset = yIndex % 8; + const uint8_t tileRowData1 = readOnlyAddressSpace[tileDataAddr + (lineOffset * 2)]; + const uint8_t tileRowData2 = 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); + + // Apply the BGP register for palette mapping + const uint8_t palette = (readOnlyAddressSpace.memoryLayout.BGP >> (colourNum * 2)) & 0x3; + + switch (palette) { + case 0: + currentLinePixels[pixel] = 0xFFFFFFFF; + break; // White + case 1: + currentLinePixels[pixel] = 0xFFAAAAAA; + break; // Light gray + case 2: + currentLinePixels[pixel] = 0xFF555555; + break; // Dark gray + case 3: + currentLinePixels[pixel] = 0xFF000000; + break; // Black + default: + break; // Default case for safety, should not be reached + } } - const uint8_t lineOffset = yPos % 8; - const uint8_t tileRowData1 = readOnlyAddressSpace[tileDataAddr + (lineOffset * 2)]; - const uint8_t tileRowData2 = readOnlyAddressSpace[tileDataAddr + (lineOffset * 2) + 1]; + // For the window to be displayed on a scanline, the following conditions 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 = readOnlyAddressSpace.memoryLayout.WX - 7; + if (testLCDCBitEnabled(WINDOW_ENABLE) && windowX >= 0 && line >= windowY) { + for (int pixel = windowX; pixel < RESOLUTION_X; pixel++) { + const uint16_t yIndex = line - windowY; + const uint16_t windowTileUpper = (yIndex / 8) << 5; - const uint8_t colourBit = 7 - (xPos % 8); - const uint8_t colourNum = ((tileRowData2 >> colourBit) & 0x1) << 1 | ((tileRowData1 >> colourBit) & 0x1); + const uint16_t xIndex = pixel - windowX; + const uint16_t windowTileLower = (xIndex / 8) & 0x1F; - // Apply the BGP register for palette mapping - uint8_t palette = (readOnlyAddressSpace.memoryLayout.BGP >> (colourNum * 2)) & 0x3; - if (xPos == 0) - palette = 0x3; - switch (palette) { - case 0: currentLinePixels[pixel] = 0xFFFFFFFF; - break; // White - case 1: currentLinePixels[pixel] = 0xAAAAAAFF; - break; // Light gray - case 2: currentLinePixels[pixel] = 0x555555FF; - break; // Dark gray - case 3: currentLinePixels[pixel] = 0x000000FF; - break; // Black - default: break; // Default case for safety, should not be reached + const uint16_t tileIndex = windowTileUpper + windowTileLower; + const uint16_t tileAddr = windowMapAddr + tileIndex; + + const int16_t tileID = signedIndex + ? static_cast(readOnlyAddressSpace[tileAddr]) + : readOnlyAddressSpace[tileAddr]; + + uint16_t tileDataAddr; + if (signedIndex) + tileDataAddr = tileDataTableAddr + (((tileID + 128) % 256) * 16); // For signed, wrap around + else + tileDataAddr = tileDataTableAddr + (tileID * 16); + + + const uint8_t lineOffset = yIndex % 8; + const uint8_t tileRowData1 = readOnlyAddressSpace[tileDataAddr + (lineOffset * 2)]; + const uint8_t tileRowData2 = 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); + + // Apply the BGP register for palette mapping + const uint8_t palette = (readOnlyAddressSpace.memoryLayout.BGP >> (colourNum * 2)) & 0x3; + + switch (palette) { + case 0: + currentLinePixels[pixel] = 0xFFFFFFFF; + break; // White + case 1: + currentLinePixels[pixel] = 0xFFAAAAAA; + break; // Light gray + case 2: + currentLinePixels[pixel] = 0xFF555555; + break; // Dark gray + case 3: + currentLinePixels[pixel] = 0xFF000000; + break; // Black + default: + break; // Default case for safety, should not be reached + } + windowLineCounter += 1; + } } } } diff --git a/src/timing.cpp b/src/timing.cpp index 665c2c4..0837f26 100644 --- a/src/timing.cpp +++ b/src/timing.cpp @@ -2,9 +2,42 @@ //handles most of the behavoir as described here: https://gbdev.io/pandocs/Timer_and_Divider_Registers.html#ff04--div-divider-register void GameBoy::timingHandler() { + //can't do this as we use cycles for PPU timing but this is what should happen + //addressSpace.memoryLayout.DIV = ((cycles / 4) >> 6) & 0xFF; + if (cycles - lastDivUpdate >= DIVIDER_REGISTER_FREQ) { const uint8_t increments = (cycles - lastDivUpdate) / DIVIDER_REGISTER_FREQ; addressSpace.memoryLayout.DIV += increments; lastDivUpdate += increments * DIVIDER_REGISTER_FREQ; } + + //if enabled + uint64_t TIMAFrequency = 0; + if (addressSpace.memoryLayout.TAC & 0x04) { + switch (addressSpace.memoryLayout.TAC & 0x03) { + case 0: + TIMAFrequency = 1024; + break; + case 1: + TIMAFrequency = 16; + break; + case 2: + TIMAFrequency = 64; + break; + case 3: + TIMAFrequency = 256; + break; + } + //if TIMA overflowed and prevTMA != current TMA, use prevTMA (ie use prevTMA regardless) + const int increments = (cycles - lastTIMAUpdate) / TIMAFrequency; + if (cycles - lastTIMAUpdate >= TIMAFrequency) { + if (static_cast(addressSpace.memoryLayout.TIMA) + increments > 255) { + addressSpace.memoryLayout.TIMA = prevTMA + ((addressSpace.memoryLayout.TIMA + increments) % 256); + setInterrupt(TIMER_INTERRUPT); + } + else + addressSpace.memoryLayout.TIMA += increments; + lastTIMAUpdate += increments * TIMAFrequency; + } + } }