Compare commits
7 Commits
ab258d177e
...
MBC3
| Author | SHA1 | Date | |
|---|---|---|---|
| c242fb7574 | |||
| 80e23312de | |||
| 037ba0c76d | |||
| a8ed60259a | |||
| 89e7891249 | |||
| 3bd1ef34d8 | |||
| 712ee55e7b |
@@ -19,7 +19,11 @@ add_executable(GameBoy++ src/main.cpp
|
|||||||
src/testing.hpp
|
src/testing.hpp
|
||||||
src/joypad.cpp
|
src/joypad.cpp
|
||||||
)
|
)
|
||||||
|
target_compile_options(GameBoy++ PRIVATE -O2)
|
||||||
target_link_libraries(GameBoy++ ${SDL2_LIBRARIES})
|
target_link_libraries(GameBoy++ ${SDL2_LIBRARIES})
|
||||||
|
target_compile_options(GameBoy++ PRIVATE
|
||||||
|
$<$<CONFIG:Release>:-O2>
|
||||||
|
)
|
||||||
|
|
||||||
if(CMAKE_EXPORT_COMPILE_COMMANDS)
|
if(CMAKE_EXPORT_COMPILE_COMMANDS)
|
||||||
add_custom_target(copy_compile_commands ALL
|
add_custom_target(copy_compile_commands ALL
|
||||||
|
|||||||
@@ -2,66 +2,58 @@
|
|||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <iterator>
|
#include <iterator>
|
||||||
|
|
||||||
bool AddressSpace::getBootromState() const {
|
bool AddressSpace::getBootromState() const { return bootromLoaded; }
|
||||||
return bootromLoaded;
|
|
||||||
|
void AddressSpace::unmapBootrom() { bootromLoaded = false; }
|
||||||
|
|
||||||
|
void AddressSpace::mapBootrom() { bootromLoaded = true; }
|
||||||
|
|
||||||
|
void AddressSpace::loadBootrom(const std::string &filename) {
|
||||||
|
std::ifstream file;
|
||||||
|
file.open(filename, std::ios::binary);
|
||||||
|
|
||||||
|
if (!file.is_open()) {
|
||||||
|
std::cerr << "Bootrom was not found!\nQuitting!\n" << std::endl;
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
const uintmax_t size = std::filesystem::file_size(filename);
|
||||||
|
if (size != 256) {
|
||||||
|
std::cerr << "Bootrom was an unexpected size!\nQuitting!\n" << std::endl;
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
file.read(reinterpret_cast<char *>(bootrom), BOOTROM_SIZE);
|
||||||
}
|
}
|
||||||
|
|
||||||
void AddressSpace::unmapBootrom() {
|
void AddressSpace::loadGame(const std::string &filename) {
|
||||||
bootromLoaded = false;
|
std::ifstream rom(filename, std::ios::binary);
|
||||||
}
|
rom.unsetf(std::ios::skipws);
|
||||||
|
|
||||||
void AddressSpace::mapBootrom() {
|
if (!rom.is_open()) {
|
||||||
bootromLoaded = true;
|
std::cerr << "Game was not found!\nQuitting!\n" << std::endl;
|
||||||
}
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
void AddressSpace::loadBootrom(const std::string& filename) {
|
rom.seekg(0, std::ios::end);
|
||||||
std::ifstream file;
|
const std::streampos rom_size = rom.tellg();
|
||||||
file.open(filename, std::ios::binary);
|
rom.seekg(0, std::ios::beg);
|
||||||
|
|
||||||
if (!file.is_open()) {
|
game.reserve(rom_size);
|
||||||
std::cerr << "Bootrom was not found!\nQuitting!\n" << std::endl;
|
game.insert(game.begin(), std::istream_iterator<Byte>(rom),
|
||||||
exit(1);
|
std::istream_iterator<Byte>());
|
||||||
}
|
|
||||||
|
|
||||||
const uintmax_t size = std::filesystem::file_size(filename);
|
memoryLayout.romBank0 = game.data();
|
||||||
if (size != 256) {
|
memoryLayout.romBankSwitch = game.data() + ROM_BANK_SIZE;
|
||||||
std::cerr << "Bootrom was an unexpected size!\nQuitting!\n" << std::endl;
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
file.read(reinterpret_cast<char*>(bootrom), BOOTROM_SIZE);
|
|
||||||
}
|
|
||||||
|
|
||||||
void AddressSpace::loadGame(const std::string& filename) {
|
|
||||||
std::ifstream rom(filename, std::ios::binary);
|
|
||||||
rom.unsetf(std::ios::skipws);
|
|
||||||
|
|
||||||
if (!rom.is_open()) {
|
|
||||||
std::cerr << "Game was not found!\nQuitting!\n" << std::endl;
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
rom.seekg(0, std::ios::end);
|
|
||||||
const std::streampos rom_size = rom.tellg();
|
|
||||||
rom.seekg(0, std::ios::beg);
|
|
||||||
|
|
||||||
game.reserve(rom_size);
|
|
||||||
game.insert(game.begin(),
|
|
||||||
std::istream_iterator<Byte>(rom),
|
|
||||||
std::istream_iterator<Byte>());
|
|
||||||
|
|
||||||
memoryLayout.romBank0 = game.data();
|
|
||||||
memoryLayout.romBankSwitch = game.data() + ROM_BANK_SIZE;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void AddressSpace::dmaTransfer() {
|
void AddressSpace::dmaTransfer() {
|
||||||
dmaTransferRequested = false;
|
dmaTransferRequested = false;
|
||||||
const Word addr = memoryLayout.DMA << 8;
|
const Word addr = memoryLayout.DMA << 8;
|
||||||
for (int i = 0; i < 0xA0; i++) {
|
for (int i = 0; i < 0xA0; i++) {
|
||||||
const Byte data = (*this)[addr + i];;
|
const Byte data = (*this)[addr + i];
|
||||||
(*this)[0xFE00 + i] = data;
|
;
|
||||||
}
|
(*this)[0xFE00 + i] = data;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void AddressSpace::setTesting(const bool state) {
|
void AddressSpace::setTesting(const bool state) { testing = state; }
|
||||||
testing = state;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,383 +1,410 @@
|
|||||||
#ifndef ADDRESSSPACE_HPP
|
#ifndef ADDRESSSPACE_HPP
|
||||||
#define ADDRESSSPACE_HPP
|
#define ADDRESSSPACE_HPP
|
||||||
#include <fstream>
|
|
||||||
#include <filesystem>
|
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
|
#include <filesystem>
|
||||||
|
#include <fstream>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "defines.hpp"
|
#include "defines.hpp"
|
||||||
|
|
||||||
class AddressSpace {
|
class AddressSpace {
|
||||||
bool bootromLoaded = true;
|
bool bootromLoaded = true;
|
||||||
Byte bootrom[BOOTROM_SIZE] = {0};
|
Byte bootrom[BOOTROM_SIZE] = {0};
|
||||||
std::vector<Byte> game;
|
std::vector<Byte> game;
|
||||||
bool testing;
|
bool testing;
|
||||||
Byte testRam[0xFFFF];
|
Byte testRam[0xFFFF];
|
||||||
Byte* cartridgeRam = nullptr;
|
Byte *cartridgeRam = nullptr;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
AddressSpace() {
|
AddressSpace() {
|
||||||
// Initialize the memory to zero
|
// Initialize the memory to zero
|
||||||
memoryLayout = {};
|
memoryLayout = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
struct {
|
struct {
|
||||||
Byte* romBank0; //[ROM_BANK_SIZE] Mapped to 0x0000
|
Byte *romBank0; //[ROM_BANK_SIZE] Mapped to 0x0000
|
||||||
Byte* romBankSwitch; //[ROM_BANK_SIZE] Mapped to 0x4000
|
Byte *romBankSwitch; //[ROM_BANK_SIZE] Mapped to 0x4000
|
||||||
Byte vram[0x2000]; //Mapped to 0x8000
|
Byte vram[0x2000]; // Mapped to 0x8000
|
||||||
Byte* externalRam; //[0x2000]; Mapped to 0xA000
|
Byte *externalRam; //[0x2000]; Mapped to 0xA000
|
||||||
Byte memoryBank1[0x1000]; //Mapped to 0xC000
|
Byte memoryBank1[0x1000]; // Mapped to 0xC000
|
||||||
Byte memoryBank2[0x1000]; //Mapped to 0xD000
|
Byte memoryBank2[0x1000]; // Mapped to 0xD000
|
||||||
Byte oam[0xA0]; //Mapped to 0xFE00
|
Byte oam[0xA0]; // Mapped to 0xFE00
|
||||||
Byte notUsable[0x60]; //Mapped to 0xFEA0
|
Byte notUsable[0x60]; // Mapped to 0xFEA0
|
||||||
//General purpose hardware registers
|
// General purpose hardware registers
|
||||||
Byte JOYP = 0xCF;
|
Byte JOYP = 0xCF;
|
||||||
Byte SB;
|
Byte SB;
|
||||||
Byte SC = 0x7E;
|
Byte SC = 0x7E;
|
||||||
Byte DIV;
|
Byte DIV;
|
||||||
//Timer registers
|
// Timer registers
|
||||||
Byte TIMA;
|
Byte TIMA;
|
||||||
Byte TMA;
|
Byte TMA;
|
||||||
Byte TAC = 0xF8;
|
Byte TAC = 0xF8;
|
||||||
//interrupt flag and enable
|
// interrupt flag and enable
|
||||||
Byte IF = 0xE1;;
|
Byte IF = 0xE1;
|
||||||
//Sound registers
|
// Sound registers
|
||||||
Byte NR10;
|
Byte NR10;
|
||||||
Byte NR11;
|
Byte NR11;
|
||||||
Byte NR12;
|
Byte NR12;
|
||||||
Byte NR13;
|
Byte NR13;
|
||||||
Byte NR14;
|
Byte NR14;
|
||||||
Byte NR20; //not used
|
Byte NR20; // not used
|
||||||
Byte NR21;
|
Byte NR21;
|
||||||
Byte NR22;
|
Byte NR22;
|
||||||
Byte NR23;
|
Byte NR23;
|
||||||
Byte NR24;
|
Byte NR24;
|
||||||
Byte NR30;
|
Byte NR30;
|
||||||
Byte NR31;
|
Byte NR31;
|
||||||
Byte NR32;
|
Byte NR32;
|
||||||
Byte NR33;
|
Byte NR33;
|
||||||
Byte NR34;
|
Byte NR34;
|
||||||
Byte NR40; //unused
|
Byte NR40; // unused
|
||||||
Byte NR41;
|
Byte NR41;
|
||||||
Byte NR42;
|
Byte NR42;
|
||||||
Byte NR43;
|
Byte NR43;
|
||||||
Byte NR44;
|
Byte NR44;
|
||||||
Byte NR50;
|
Byte NR50;
|
||||||
Byte NR51;
|
Byte NR51;
|
||||||
Byte NR52;
|
Byte NR52;
|
||||||
Byte waveRam[0x10];
|
Byte waveRam[0x10];
|
||||||
//PPU registers
|
// PPU registers
|
||||||
Byte LCDC;
|
Byte LCDC;
|
||||||
Byte STAT;
|
Byte STAT;
|
||||||
Byte SCY;
|
Byte SCY;
|
||||||
Byte SCX;
|
Byte SCX;
|
||||||
Byte LY;
|
Byte LY;
|
||||||
Byte LYC;
|
Byte LYC;
|
||||||
Byte DMA;
|
Byte DMA;
|
||||||
Byte BGP;
|
Byte BGP;
|
||||||
Byte OBP0;
|
Byte OBP0;
|
||||||
Byte OBP1;
|
Byte OBP1;
|
||||||
Byte WY;
|
Byte WY;
|
||||||
Byte WX;
|
Byte WX;
|
||||||
Byte specialRam[0x7F]; //Mapped to 0xFF80
|
Byte specialRam[0x7F]; // Mapped to 0xFF80
|
||||||
Byte IE; // Mapped to 0xFFFF
|
Byte IE; // Mapped to 0xFFFF
|
||||||
} memoryLayout{};
|
} memoryLayout{};
|
||||||
|
|
||||||
void unmapBootrom();
|
void unmapBootrom();
|
||||||
void mapBootrom();
|
void mapBootrom();
|
||||||
bool getBootromState() const;
|
bool getBootromState() const;
|
||||||
void loadBootrom(const std::string& filename);
|
void loadBootrom(const std::string &filename);
|
||||||
void loadGame(const std::string& filename);
|
void loadGame(const std::string &filename);
|
||||||
|
|
||||||
void determineMBCInfo();
|
void determineMBCInfo();
|
||||||
static bool testMBCWrite(Word address);
|
Byte *MBCWrite(Word address);
|
||||||
Byte* MBCRead(Word address);
|
// prevents seg faults when programs with no MBC try to write to ROM
|
||||||
//prevents seg faults when programs with no MBC try to write to ROM
|
Byte dummyVal = 0;
|
||||||
Byte dummyVal = 0;
|
|
||||||
void MBCUpdate();
|
|
||||||
void loadRomBank();
|
|
||||||
void createRamBank();
|
|
||||||
void loadRamBank();
|
|
||||||
MBCType MBC = {};
|
|
||||||
uint32_t romSize = 0;
|
|
||||||
uint32_t romBanks = 0;
|
|
||||||
uint32_t externalRamSize = 0;
|
|
||||||
uint32_t externalRamBanks = 0;
|
|
||||||
|
|
||||||
bool dmaTransferRequested = false;
|
// called after every op
|
||||||
void dmaTransfer();
|
void MBCUpdate();
|
||||||
|
|
||||||
//Selected ROM Bank = (Secondary Bank << 5) + ROM Bank
|
void loadRomBank();
|
||||||
Byte selectedRomBank = 0;
|
void createRamBank();
|
||||||
Byte romBankRegister = 0x00;
|
void loadRamBank();
|
||||||
//2 bit register acts as secondary rom bank register or ram bank number
|
MBCType_enum MBC = {};
|
||||||
Byte twoBitBankRegister = 0x0;
|
uint32_t romSize = 0;
|
||||||
Byte selectedExternalRamBank = 0;
|
uint32_t romBanks = 0;
|
||||||
Byte romRamSelect = 0x00;
|
uint32_t externalRamSize = 0;
|
||||||
Byte ramEnable = 0x00;
|
uint32_t externalRamBanks = 0;
|
||||||
//MBC3
|
|
||||||
Byte latchClockData = 0x00;
|
|
||||||
Byte ramBankRTCRegister = 0x00;
|
|
||||||
|
|
||||||
void setTesting(bool state);
|
bool dmaTransferRequested = false;
|
||||||
|
void dmaTransfer();
|
||||||
|
|
||||||
//read
|
// MBC registers
|
||||||
Byte operator[](const Word address) const {
|
// Selected ROM Bank in MBC1 = (Secondary Bank << 5) + ROM Bank
|
||||||
if (testing)
|
Byte selectedRomBank = 0;
|
||||||
return testRam[address];
|
Byte romBankRegister = 0x00;
|
||||||
if (address < 0x0100 && bootromLoaded)
|
// 2 bit register acts as secondary rom bank register or ram bank number in
|
||||||
return bootrom[address];
|
// MBC1
|
||||||
if (address < 0x4000)
|
Byte twoBitBankRegister = 0x0;
|
||||||
return memoryLayout.romBank0[address];
|
Byte selectedExternalRamBank = 0;
|
||||||
if (address < 0x8000)
|
Byte romRamSelect = 0x00;
|
||||||
return memoryLayout.romBankSwitch[address - 0x4000];
|
// 0xA if enabled in MBC1/3, else off
|
||||||
if (address < 0xA000)
|
// in MBC2 also the rom bank number
|
||||||
return memoryLayout.vram[address - 0x8000];
|
// in MBC3 also timer enable
|
||||||
if (address < 0xC000) {
|
// reading ram while disabled is undefined behaviour
|
||||||
if (externalRamSize == 0)
|
Byte ramEnable = 0x00;
|
||||||
return 0xFF;
|
// MBC3
|
||||||
return memoryLayout.externalRam[address - 0xA000];
|
Byte latchClockData = 0x00;
|
||||||
}
|
Byte ramBankRTCRegister = 0x00;
|
||||||
if (address < 0xD000)
|
// RTC registers
|
||||||
return memoryLayout.memoryBank1[address - 0xC000];
|
enabledRTCRegister_enum enabledRTCRegister = NONE;
|
||||||
if (address < 0xE000)
|
Byte RTCSeconds = 0x0;
|
||||||
return memoryLayout.memoryBank2[address - 0xD000];
|
Byte RTCMinutes = 0x0;
|
||||||
if (address < 0xFE00)
|
Byte RTCHours = 0x0;
|
||||||
return memoryLayout.memoryBank1[address - 0xE000];
|
Byte RTCDayLower = 0x0;
|
||||||
if (address < 0xFEA0)
|
Byte RTCDayHigher = 0x0;
|
||||||
return memoryLayout.oam[address - 0xFE00];
|
|
||||||
if (address < 0xFF00) {
|
|
||||||
if ((memoryLayout.STAT & 0x03) == 2 || (memoryLayout.STAT & 0x03) == 3)
|
|
||||||
return 0xFF;
|
|
||||||
return 0x00;
|
|
||||||
}
|
|
||||||
if (address < 0xFF80)
|
|
||||||
switch (address) {
|
|
||||||
case 0xFF00:
|
|
||||||
return memoryLayout.JOYP;
|
|
||||||
case 0xFF01:
|
|
||||||
return memoryLayout.SB;
|
|
||||||
case 0xFF02:
|
|
||||||
return memoryLayout.SC;
|
|
||||||
case 0xFF04:
|
|
||||||
return memoryLayout.DIV;
|
|
||||||
case 0xFF05:
|
|
||||||
return memoryLayout.TIMA;
|
|
||||||
case 0xFF06:
|
|
||||||
return memoryLayout.TMA;
|
|
||||||
case 0xFF07:
|
|
||||||
return memoryLayout.TAC | 0xF8;;
|
|
||||||
case 0xFF0F:
|
|
||||||
return memoryLayout.IF | 0xE0;
|
|
||||||
case 0xFF10:
|
|
||||||
return memoryLayout.NR10;
|
|
||||||
case 0xFF11:
|
|
||||||
return memoryLayout.NR11;
|
|
||||||
case 0xFF12:
|
|
||||||
return memoryLayout.NR12;
|
|
||||||
case 0xFF13:
|
|
||||||
return memoryLayout.NR13;
|
|
||||||
case 0xFF14:
|
|
||||||
return memoryLayout.NR14;
|
|
||||||
case 0xFF16:
|
|
||||||
return memoryLayout.NR21;
|
|
||||||
case 0xFF17:
|
|
||||||
return memoryLayout.NR22;
|
|
||||||
case 0xFF18:
|
|
||||||
return memoryLayout.NR23;
|
|
||||||
case 0xFF19:
|
|
||||||
return memoryLayout.NR24;
|
|
||||||
case 0xFF1A:
|
|
||||||
return memoryLayout.NR30;
|
|
||||||
case 0xFF1B:
|
|
||||||
return memoryLayout.NR31;
|
|
||||||
case 0xFF1C:
|
|
||||||
return memoryLayout.NR32;
|
|
||||||
case 0xFF1D:
|
|
||||||
return memoryLayout.NR33;
|
|
||||||
case 0xFF1E:
|
|
||||||
return memoryLayout.NR34;
|
|
||||||
case 0xFF20:
|
|
||||||
return memoryLayout.NR41;
|
|
||||||
case 0xFF21:
|
|
||||||
return memoryLayout.NR42;
|
|
||||||
case 0xFF22:
|
|
||||||
return memoryLayout.NR43;
|
|
||||||
case 0xFF23:
|
|
||||||
return memoryLayout.NR44;
|
|
||||||
case 0xFF24:
|
|
||||||
return memoryLayout.NR50;
|
|
||||||
case 0xFF25:
|
|
||||||
return memoryLayout.NR51;
|
|
||||||
case 0xFF26:
|
|
||||||
return memoryLayout.NR52;
|
|
||||||
// PPU registers
|
|
||||||
case 0xFF40:
|
|
||||||
return memoryLayout.LCDC;
|
|
||||||
case 0xFF41:
|
|
||||||
return memoryLayout.STAT;
|
|
||||||
case 0xFF42:
|
|
||||||
return memoryLayout.SCY;
|
|
||||||
case 0xFF43:
|
|
||||||
return memoryLayout.SCX;
|
|
||||||
case 0xFF44:
|
|
||||||
//for debugging only
|
|
||||||
//return 0x90;
|
|
||||||
return memoryLayout.LY;
|
|
||||||
case 0xFF45:
|
|
||||||
return memoryLayout.LYC;
|
|
||||||
case 0xFF46:
|
|
||||||
return memoryLayout.DMA;
|
|
||||||
case 0xFF47:
|
|
||||||
return memoryLayout.BGP;
|
|
||||||
case 0xFF48:
|
|
||||||
return memoryLayout.OBP0;
|
|
||||||
case 0xFF49:
|
|
||||||
return memoryLayout.OBP1;
|
|
||||||
case 0xFF4A:
|
|
||||||
return memoryLayout.WY;
|
|
||||||
case 0xFF4B:
|
|
||||||
return memoryLayout.WX;
|
|
||||||
default:
|
|
||||||
if (address >= 0xFF30 && address <= 0xFF3F) {
|
|
||||||
return memoryLayout.waveRam[address - 0xFF30];
|
|
||||||
}
|
|
||||||
return 0xFF;
|
|
||||||
}
|
|
||||||
if (address < 0xFFFF)
|
|
||||||
return memoryLayout.specialRam[address - 0xFF80];
|
|
||||||
//0xFFFF
|
|
||||||
return memoryLayout.IE;
|
|
||||||
}
|
|
||||||
|
|
||||||
//write
|
void setTesting(bool state);
|
||||||
Byte& operator[](const Word address) {
|
|
||||||
dummyVal = 0xFF;
|
// read
|
||||||
if (testing)
|
Byte operator[](const Word address) const {
|
||||||
return testRam[address];
|
if (testing)
|
||||||
if (address < 0x0100 && bootromLoaded)
|
return testRam[address];
|
||||||
return bootrom[address];
|
if (address < 0x0100 && bootromLoaded)
|
||||||
if (address < 0x8000)
|
return bootrom[address];
|
||||||
return (*MBCRead(address));
|
else if (address < 0x4000)
|
||||||
if (address < 0xA000)
|
return memoryLayout.romBank0[address];
|
||||||
return memoryLayout.vram[address - 0x8000];
|
else if (address < 0x8000)
|
||||||
if (address < 0xC000) {
|
return memoryLayout.romBankSwitch[address - 0x4000];
|
||||||
if (externalRamSize == 0)
|
else if (address < 0xA000)
|
||||||
return dummyVal;
|
return memoryLayout.vram[address - 0x8000];
|
||||||
return memoryLayout.externalRam[address - 0xA000];
|
else if (address < 0xC000) {
|
||||||
}
|
if (enabledRTCRegister != NONE) {
|
||||||
if (address < 0xD000)
|
switch (enabledRTCRegister) {
|
||||||
return memoryLayout.memoryBank1[address - 0xC000];
|
case RTCS:
|
||||||
if (address < 0xE000)
|
return RTCSeconds;
|
||||||
return memoryLayout.memoryBank2[address - 0xD000];
|
case RTCM:
|
||||||
if (address < 0xFE00)
|
return RTCMinutes;
|
||||||
return memoryLayout.memoryBank1[address - 0xE000];
|
case RTCH:
|
||||||
if (address < 0xFEA0)
|
return RTCHours;
|
||||||
return memoryLayout.oam[address - 0xFE00];
|
case RTCDL:
|
||||||
if (address < 0xFF00)
|
return RTCDayLower;
|
||||||
return memoryLayout.notUsable[address - 0xFEA0];
|
case RTCDH:
|
||||||
if (address < 0xFF80)
|
return RTCDayHigher;
|
||||||
switch (address) {
|
}
|
||||||
case 0xFF00:
|
}
|
||||||
return memoryLayout.JOYP;
|
if (externalRamSize == 0)
|
||||||
case 0xFF01:
|
return 0xFF;
|
||||||
return memoryLayout.SB;
|
// MBC2 echos 15 times and only stores lower 4 bits
|
||||||
case 0xFF02:
|
else if (MBC == MBC2 || MBC == MBC2Battery)
|
||||||
return memoryLayout.SC;
|
return memoryLayout.externalRam[(address - 0xA000) & 0x01FF] | 0xF0;
|
||||||
case 0xFF04:
|
else
|
||||||
memoryLayout.DIV = 0;
|
return memoryLayout.externalRam[address - 0xA000];
|
||||||
return dummyVal;
|
} else if (address < 0xD000)
|
||||||
// Timer registers
|
return memoryLayout.memoryBank1[address - 0xC000];
|
||||||
case 0xFF05:
|
else if (address < 0xE000)
|
||||||
return memoryLayout.TIMA;
|
return memoryLayout.memoryBank2[address - 0xD000];
|
||||||
case 0xFF06:
|
else if (address < 0xFE00)
|
||||||
return memoryLayout.TMA;
|
return memoryLayout.memoryBank1[address - 0xE000];
|
||||||
case 0xFF07:
|
else if (address < 0xFEA0)
|
||||||
return memoryLayout.TAC;
|
return memoryLayout.oam[address - 0xFE00];
|
||||||
case 0xFF0F:
|
else if (address < 0xFF00) {
|
||||||
return memoryLayout.IF;
|
if ((memoryLayout.STAT & 0x03) == 2 || (memoryLayout.STAT & 0x03) == 3)
|
||||||
case 0xFF10:
|
return 0xFF;
|
||||||
return memoryLayout.NR10;
|
return 0x00;
|
||||||
case 0xFF11:
|
} else if (address < 0xFF80)
|
||||||
return memoryLayout.NR11;
|
switch (address) {
|
||||||
case 0xFF12:
|
case 0xFF00:
|
||||||
return memoryLayout.NR12;
|
return memoryLayout.JOYP;
|
||||||
case 0xFF13:
|
case 0xFF01:
|
||||||
return memoryLayout.NR13;
|
return memoryLayout.SB;
|
||||||
case 0xFF14:
|
case 0xFF02:
|
||||||
return memoryLayout.NR14;
|
return memoryLayout.SC;
|
||||||
case 0xFF16:
|
case 0xFF04:
|
||||||
return memoryLayout.NR21;
|
return memoryLayout.DIV;
|
||||||
case 0xFF17:
|
case 0xFF05:
|
||||||
return memoryLayout.NR22;
|
return memoryLayout.TIMA;
|
||||||
case 0xFF18:
|
case 0xFF06:
|
||||||
return memoryLayout.NR23;
|
return memoryLayout.TMA;
|
||||||
case 0xFF19:
|
case 0xFF07:
|
||||||
return memoryLayout.NR24;
|
return memoryLayout.TAC | 0xF8;
|
||||||
case 0xFF1A:
|
case 0xFF0F:
|
||||||
return memoryLayout.NR30;
|
return memoryLayout.IF | 0xE0;
|
||||||
case 0xFF1B:
|
case 0xFF10:
|
||||||
return memoryLayout.NR31;
|
return memoryLayout.NR10;
|
||||||
case 0xFF1C:
|
case 0xFF11:
|
||||||
return memoryLayout.NR32;
|
return memoryLayout.NR11;
|
||||||
case 0xFF1D:
|
case 0xFF12:
|
||||||
return memoryLayout.NR33;
|
return memoryLayout.NR12;
|
||||||
case 0xFF1E:
|
case 0xFF13:
|
||||||
return memoryLayout.NR34;
|
return memoryLayout.NR13;
|
||||||
case 0xFF20:
|
case 0xFF14:
|
||||||
return memoryLayout.NR41;
|
return memoryLayout.NR14;
|
||||||
case 0xFF21:
|
case 0xFF16:
|
||||||
return memoryLayout.NR42;
|
return memoryLayout.NR21;
|
||||||
case 0xFF22:
|
case 0xFF17:
|
||||||
return memoryLayout.NR43;
|
return memoryLayout.NR22;
|
||||||
case 0xFF23:
|
case 0xFF18:
|
||||||
return memoryLayout.NR44;
|
return memoryLayout.NR23;
|
||||||
case 0xFF24:
|
case 0xFF19:
|
||||||
return memoryLayout.NR50;
|
return memoryLayout.NR24;
|
||||||
case 0xFF25:
|
case 0xFF1A:
|
||||||
return memoryLayout.NR51;
|
return memoryLayout.NR30;
|
||||||
case 0xFF26:
|
case 0xFF1B:
|
||||||
return memoryLayout.NR52;
|
return memoryLayout.NR31;
|
||||||
case 0xFF40:
|
case 0xFF1C:
|
||||||
return memoryLayout.LCDC;
|
return memoryLayout.NR32;
|
||||||
case 0xFF41:
|
case 0xFF1D:
|
||||||
return memoryLayout.STAT;
|
return memoryLayout.NR33;
|
||||||
case 0xFF42:
|
case 0xFF1E:
|
||||||
return memoryLayout.SCY;
|
return memoryLayout.NR34;
|
||||||
case 0xFF43:
|
case 0xFF20:
|
||||||
return memoryLayout.SCX;
|
return memoryLayout.NR41;
|
||||||
case 0xFF44:
|
case 0xFF21:
|
||||||
dummyVal = memoryLayout.LY;
|
return memoryLayout.NR42;
|
||||||
return dummyVal;
|
case 0xFF22:
|
||||||
case 0xFF45:
|
return memoryLayout.NR43;
|
||||||
return memoryLayout.LYC;
|
case 0xFF23:
|
||||||
case 0xFF46:
|
return memoryLayout.NR44;
|
||||||
dmaTransferRequested = true;
|
case 0xFF24:
|
||||||
return memoryLayout.DMA;
|
return memoryLayout.NR50;
|
||||||
case 0xFF47:
|
case 0xFF25:
|
||||||
return memoryLayout.BGP;
|
return memoryLayout.NR51;
|
||||||
case 0xFF48:
|
case 0xFF26:
|
||||||
return memoryLayout.OBP0;
|
return memoryLayout.NR52;
|
||||||
case 0xFF49:
|
// PPU registers
|
||||||
return memoryLayout.OBP1;
|
case 0xFF40:
|
||||||
case 0xFF4A:
|
return memoryLayout.LCDC;
|
||||||
return memoryLayout.WY;
|
case 0xFF41:
|
||||||
case 0xFF4B:
|
return memoryLayout.STAT;
|
||||||
return memoryLayout.WX;
|
case 0xFF42:
|
||||||
default:
|
return memoryLayout.SCY;
|
||||||
if (address >= 0xFF30 && address <= 0xFF3F) {
|
case 0xFF43:
|
||||||
return memoryLayout.waveRam[address - 0xFF30];
|
return memoryLayout.SCX;
|
||||||
}
|
case 0xFF44:
|
||||||
return dummyVal;
|
return memoryLayout.LY;
|
||||||
}
|
case 0xFF45:
|
||||||
if (address < 0xFFFF)
|
return memoryLayout.LYC;
|
||||||
return memoryLayout.specialRam[address - 0xFF80];
|
case 0xFF46:
|
||||||
//0xFFFF
|
return memoryLayout.DMA;
|
||||||
return memoryLayout.IE;
|
case 0xFF47:
|
||||||
}
|
return memoryLayout.BGP;
|
||||||
|
case 0xFF48:
|
||||||
|
return memoryLayout.OBP0;
|
||||||
|
case 0xFF49:
|
||||||
|
return memoryLayout.OBP1;
|
||||||
|
case 0xFF4A:
|
||||||
|
return memoryLayout.WY;
|
||||||
|
case 0xFF4B:
|
||||||
|
return memoryLayout.WX;
|
||||||
|
default:
|
||||||
|
if (address >= 0xFF30 && address <= 0xFF3F) {
|
||||||
|
return memoryLayout.waveRam[address - 0xFF30];
|
||||||
|
}
|
||||||
|
return 0xFF;
|
||||||
|
}
|
||||||
|
else if (address < 0xFFFF)
|
||||||
|
return memoryLayout.specialRam[address - 0xFF80];
|
||||||
|
// 0xFFFF
|
||||||
|
return memoryLayout.IE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// write
|
||||||
|
Byte &operator[](const Word address) {
|
||||||
|
dummyVal = 0xFF;
|
||||||
|
if (testing)
|
||||||
|
return testRam[address];
|
||||||
|
else if (address < 0x0100 && bootromLoaded)
|
||||||
|
return bootrom[address];
|
||||||
|
else if (address < 0x8000)
|
||||||
|
return (*MBCWrite(address));
|
||||||
|
else if (address < 0xA000)
|
||||||
|
return memoryLayout.vram[address - 0x8000];
|
||||||
|
else if (address < 0xC000) {
|
||||||
|
if (externalRamSize == 0)
|
||||||
|
return dummyVal;
|
||||||
|
return memoryLayout.externalRam[address - 0xA000];
|
||||||
|
} else if (address < 0xD000)
|
||||||
|
return memoryLayout.memoryBank1[address - 0xC000];
|
||||||
|
else if (address < 0xE000)
|
||||||
|
return memoryLayout.memoryBank2[address - 0xD000];
|
||||||
|
else if (address < 0xFE00)
|
||||||
|
return memoryLayout.memoryBank1[address - 0xE000];
|
||||||
|
else if (address < 0xFEA0)
|
||||||
|
return memoryLayout.oam[address - 0xFE00];
|
||||||
|
else if (address < 0xFF00)
|
||||||
|
return memoryLayout.notUsable[address - 0xFEA0];
|
||||||
|
else if (address < 0xFF80)
|
||||||
|
switch (address) {
|
||||||
|
case 0xFF00:
|
||||||
|
return memoryLayout.JOYP;
|
||||||
|
case 0xFF01:
|
||||||
|
return memoryLayout.SB;
|
||||||
|
case 0xFF02:
|
||||||
|
return memoryLayout.SC;
|
||||||
|
case 0xFF04:
|
||||||
|
memoryLayout.DIV = 0;
|
||||||
|
return dummyVal;
|
||||||
|
// Timer registers
|
||||||
|
case 0xFF05:
|
||||||
|
return memoryLayout.TIMA;
|
||||||
|
case 0xFF06:
|
||||||
|
return memoryLayout.TMA;
|
||||||
|
case 0xFF07:
|
||||||
|
return memoryLayout.TAC;
|
||||||
|
case 0xFF0F:
|
||||||
|
return memoryLayout.IF;
|
||||||
|
case 0xFF10:
|
||||||
|
return memoryLayout.NR10;
|
||||||
|
case 0xFF11:
|
||||||
|
return memoryLayout.NR11;
|
||||||
|
case 0xFF12:
|
||||||
|
return memoryLayout.NR12;
|
||||||
|
case 0xFF13:
|
||||||
|
return memoryLayout.NR13;
|
||||||
|
case 0xFF14:
|
||||||
|
return memoryLayout.NR14;
|
||||||
|
case 0xFF16:
|
||||||
|
return memoryLayout.NR21;
|
||||||
|
case 0xFF17:
|
||||||
|
return memoryLayout.NR22;
|
||||||
|
case 0xFF18:
|
||||||
|
return memoryLayout.NR23;
|
||||||
|
case 0xFF19:
|
||||||
|
return memoryLayout.NR24;
|
||||||
|
case 0xFF1A:
|
||||||
|
return memoryLayout.NR30;
|
||||||
|
case 0xFF1B:
|
||||||
|
return memoryLayout.NR31;
|
||||||
|
case 0xFF1C:
|
||||||
|
return memoryLayout.NR32;
|
||||||
|
case 0xFF1D:
|
||||||
|
return memoryLayout.NR33;
|
||||||
|
case 0xFF1E:
|
||||||
|
return memoryLayout.NR34;
|
||||||
|
case 0xFF20:
|
||||||
|
return memoryLayout.NR41;
|
||||||
|
case 0xFF21:
|
||||||
|
return memoryLayout.NR42;
|
||||||
|
case 0xFF22:
|
||||||
|
return memoryLayout.NR43;
|
||||||
|
case 0xFF23:
|
||||||
|
return memoryLayout.NR44;
|
||||||
|
case 0xFF24:
|
||||||
|
return memoryLayout.NR50;
|
||||||
|
case 0xFF25:
|
||||||
|
return memoryLayout.NR51;
|
||||||
|
case 0xFF26:
|
||||||
|
return memoryLayout.NR52;
|
||||||
|
case 0xFF40:
|
||||||
|
return memoryLayout.LCDC;
|
||||||
|
case 0xFF41:
|
||||||
|
return memoryLayout.STAT;
|
||||||
|
case 0xFF42:
|
||||||
|
return memoryLayout.SCY;
|
||||||
|
case 0xFF43:
|
||||||
|
return memoryLayout.SCX;
|
||||||
|
case 0xFF44:
|
||||||
|
dummyVal = memoryLayout.LY;
|
||||||
|
return dummyVal;
|
||||||
|
case 0xFF45:
|
||||||
|
return memoryLayout.LYC;
|
||||||
|
case 0xFF46:
|
||||||
|
dmaTransferRequested = true;
|
||||||
|
return memoryLayout.DMA;
|
||||||
|
case 0xFF47:
|
||||||
|
return memoryLayout.BGP;
|
||||||
|
case 0xFF48:
|
||||||
|
return memoryLayout.OBP0;
|
||||||
|
case 0xFF49:
|
||||||
|
return memoryLayout.OBP1;
|
||||||
|
case 0xFF4A:
|
||||||
|
return memoryLayout.WY;
|
||||||
|
case 0xFF4B:
|
||||||
|
return memoryLayout.WX;
|
||||||
|
default:
|
||||||
|
if (address >= 0xFF30 && address <= 0xFF3F) {
|
||||||
|
return memoryLayout.waveRam[address - 0xFF30];
|
||||||
|
}
|
||||||
|
return dummyVal;
|
||||||
|
}
|
||||||
|
else if (address < 0xFFFF)
|
||||||
|
return memoryLayout.specialRam[address - 0xFF80];
|
||||||
|
// 0xFFFF
|
||||||
|
return memoryLayout.IE;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#endif // ADDRESSSPACE_HPP
|
||||||
#endif //ADDRESSSPACE_HPP
|
|
||||||
|
|||||||
118
src/defines.hpp
118
src/defines.hpp
@@ -10,10 +10,10 @@
|
|||||||
// 5 h - - Half Carry Flag (BCD)
|
// 5 h - - Half Carry Flag (BCD)
|
||||||
// 4 cy C NC Carry Flag
|
// 4 cy C NC Carry Flag
|
||||||
// 3-0 - - - Not used
|
// 3-0 - - - Not used
|
||||||
#define CARRY_FLAG 4 //'C'
|
#define CARRY_FLAG 4 //'C'
|
||||||
#define HALFCARRY_FLAG 5 //'H'
|
#define HALFCARRY_FLAG 5 //'H'
|
||||||
#define SUBTRACT_FLAG 6 //'N'
|
#define SUBTRACT_FLAG 6 //'N'
|
||||||
#define ZERO_FLAG 7 //'Z'
|
#define ZERO_FLAG 7 //'Z'
|
||||||
|
|
||||||
#define VBLANK_INTERRUPT 0
|
#define VBLANK_INTERRUPT 0
|
||||||
#define LCD_STAT_INTERRUPT 1
|
#define LCD_STAT_INTERRUPT 1
|
||||||
@@ -21,9 +21,9 @@
|
|||||||
#define SERIAL_INTERRUPT 3
|
#define SERIAL_INTERRUPT 3
|
||||||
#define JOYPAD_INTERRUPT 4
|
#define JOYPAD_INTERRUPT 4
|
||||||
|
|
||||||
#define T_CLOCK_FREQ 4194304 //2^22
|
#define T_CLOCK_FREQ 4194304 // 2^22
|
||||||
|
|
||||||
#define DIVIDER_REGISTER_FREQ (4194304/16384)
|
#define DIVIDER_REGISTER_FREQ (4194304 / 16384)
|
||||||
|
|
||||||
#define BOOTROM_SIZE 0x100
|
#define BOOTROM_SIZE 0x100
|
||||||
|
|
||||||
@@ -31,7 +31,7 @@
|
|||||||
#define RESOLUTION_Y 144
|
#define RESOLUTION_Y 144
|
||||||
#define SCREEN_BPP 3
|
#define SCREEN_BPP 3
|
||||||
|
|
||||||
//lcdc
|
// lcdc
|
||||||
#define BG_WINDOW_ENABLE 0
|
#define BG_WINDOW_ENABLE 0
|
||||||
#define OBJ_ENABLE 1
|
#define OBJ_ENABLE 1
|
||||||
#define OBJ_SIZE 2
|
#define OBJ_SIZE 2
|
||||||
@@ -41,13 +41,12 @@
|
|||||||
#define WINDOW_TILE_MAP_AREA 6
|
#define WINDOW_TILE_MAP_AREA 6
|
||||||
#define LCD_ENABLE 7
|
#define LCD_ENABLE 7
|
||||||
|
|
||||||
//oam
|
// oam
|
||||||
#define PRIORITY 7
|
#define PRIORITY 7
|
||||||
#define Y_FLIP 6
|
#define Y_FLIP 6
|
||||||
#define X_FLIP 5
|
#define X_FLIP 5
|
||||||
#define OBJ_PALETTE 4
|
#define OBJ_PALETTE 4
|
||||||
|
|
||||||
|
|
||||||
#define ROM_BANK_SIZE 0x4000
|
#define ROM_BANK_SIZE 0x4000
|
||||||
#define RAM_BANK_SIZE 0x2000
|
#define RAM_BANK_SIZE 0x2000
|
||||||
|
|
||||||
@@ -59,10 +58,10 @@
|
|||||||
|
|
||||||
#define H_SYNC 9198
|
#define H_SYNC 9198
|
||||||
#define V_SYNC 59.73
|
#define V_SYNC 59.73
|
||||||
#define HBLANK_DURATION 204 //PPU_MODE 0
|
#define HBLANK_DURATION 204 // PPU_MODE 0
|
||||||
#define VBLANK_DURATION 4560
|
#define VBLANK_DURATION 4560
|
||||||
#define SCANLINE_OAM_FREQ 80 //PPU_MODE 2
|
#define SCANLINE_OAM_FREQ 80 // PPU_MODE 2
|
||||||
#define SCANLINE_VRAM_FREQ 80 //PPU_MODE 3
|
#define SCANLINE_VRAM_FREQ 80 // PPU_MODE 3
|
||||||
|
|
||||||
#define WHITE 0xFFFFFFFF;
|
#define WHITE 0xFFFFFFFF;
|
||||||
#define LIGHT_GRAY 0xFFAAAAAA;
|
#define LIGHT_GRAY 0xFFAAAAAA;
|
||||||
@@ -70,52 +69,65 @@
|
|||||||
#define BLACK 0xFF000000
|
#define BLACK 0xFF000000
|
||||||
|
|
||||||
struct Input {
|
struct Input {
|
||||||
bool UP = false;
|
bool UP = false;
|
||||||
bool DOWN = false;
|
bool DOWN = false;
|
||||||
bool LEFT = false;
|
bool LEFT = false;
|
||||||
bool RIGHT = false;
|
bool RIGHT = false;
|
||||||
bool B = false;
|
bool B = false;
|
||||||
bool A = false;
|
bool A = false;
|
||||||
bool START = false;
|
bool START = false;
|
||||||
bool SELECT = false;
|
bool SELECT = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
enum MBCType {
|
enum MBCType_enum {
|
||||||
romOnly = 0x00,
|
romOnly = 0x00,
|
||||||
MBC1 = 0x01,
|
MBC1 = 0x01,
|
||||||
MBC1Ram = 0x02,
|
MBC1Ram = 0x02,
|
||||||
MBC1RamBattery = 0x03,
|
MBC1RamBattery = 0x03,
|
||||||
MBC2 = 0x05,
|
MBC2 = 0x05,
|
||||||
MBC2Battery = 0x06,
|
MBC2Battery = 0x06,
|
||||||
RomRam = 0x08, //unused
|
RomRam = 0x08, // unused
|
||||||
RomRamBattery = 0x09, //unused
|
RomRamBattery = 0x09, // unused
|
||||||
MMM01 = 0x0B, //multigame roms only
|
MMM01 = 0x0B, // multigame roms only
|
||||||
MMM01Ram = 0x0C,
|
MMM01Ram = 0x0C,
|
||||||
MMM01RamBattery = 0x0D,
|
MMM01RamBattery = 0x0D,
|
||||||
MBC3TimerBattery = 0x0F,
|
MBC3TimerBattery = 0x0F,
|
||||||
MBC3TimerRamBattery = 0x10,
|
MBC3TimerRamBattery = 0x10,
|
||||||
MBC3 = 0x11,
|
MBC3 = 0x11,
|
||||||
MBC3Ram = 0x12, // MBC3 with 64 KiB of SRAM refers to MBC30, used only in Pocket Monsters: Crystal Version.
|
MBC3Ram = 0x12, // MBC3 with 64 KiB of SRAM refers to MBC30, used only in
|
||||||
MBC3RamBattery = 0x13,
|
// Pocket Monsters: Crystal Version.
|
||||||
MBC5 = 0x19,
|
MBC3RamBattery = 0x13,
|
||||||
MBC5Ram = 0x1A,
|
MBC5 = 0x19,
|
||||||
MBC5RamBattery = 0x1B,
|
MBC5Ram = 0x1A,
|
||||||
MBC5Rumble = 0x1C,
|
MBC5RamBattery = 0x1B,
|
||||||
MBC5RumbleRam = 0x1D,
|
MBC5Rumble = 0x1C,
|
||||||
MBC5RumbleRamBattery = 0x1E,
|
MBC5RumbleRam = 0x1D,
|
||||||
MBC6 = 0x20,
|
MBC5RumbleRamBattery = 0x1E,
|
||||||
MBC7SensorRumbleRamBattery = 0x22,
|
MBC6 = 0x20,
|
||||||
PocketCamera = 0xFC,
|
MBC7SensorRumbleRamBattery = 0x22,
|
||||||
BandaiTama5 = 0xFD,
|
PocketCamera = 0xFC,
|
||||||
HuC3 = 0xFE,
|
BandaiTama5 = 0xFD,
|
||||||
HuC1RamBattery = 0xFF
|
HuC3 = 0xFE,
|
||||||
|
HuC1RamBattery = 0xFF
|
||||||
};
|
};
|
||||||
|
|
||||||
enum PPUMode {
|
enum PPUMode_enum {
|
||||||
mode0, // Horizontal Blank (Mode 0): No access to video RAM, occurs during horizontal blanking period.
|
mode0, // Horizontal Blank (Mode 0): No access to video RAM, occurs during
|
||||||
mode1, // Vertical Blank (Mode 1): No access to video RAM, occurs during vertical blanking period.
|
// horizontal blanking period.
|
||||||
mode2, // OAM Search (Mode 2): Access to OAM (Object Attribute Memory) only, sprite evaluation.
|
mode1, // Vertical Blank (Mode 1): No access to video RAM, occurs during
|
||||||
mode3 // Pixel Transfer (Mode 3): Access to both OAM and video RAM, actual pixel transfer to the screen.
|
// vertical blanking period.
|
||||||
|
mode2, // OAM Search (Mode 2): Access to OAM (Object Attribute Memory) only,
|
||||||
|
// sprite evaluation.
|
||||||
|
mode3 // Pixel Transfer (Mode 3): Access to both OAM and video RAM, actual
|
||||||
|
// pixel transfer to the screen.
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum enabledRTCRegister_enum {
|
||||||
|
NONE = 0x00,
|
||||||
|
RTCS = 0x08,
|
||||||
|
RTCM = 0x09,
|
||||||
|
RTCH = 0x0A,
|
||||||
|
RTCDL = 0x0B,
|
||||||
|
RTCDH = 0x0C
|
||||||
|
};
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
358
src/gameboy.cpp
358
src/gameboy.cpp
@@ -1,202 +1,194 @@
|
|||||||
#include <iostream>
|
|
||||||
#include "gameboy.hpp"
|
#include "gameboy.hpp"
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
void GameBoy::addCycles(const uint8_t ticks) {
|
void GameBoy::addCycles(const uint8_t ticks) {
|
||||||
cycles += ticks;
|
cycles += ticks;
|
||||||
if (ppuEnabled) {
|
if (ppuEnabled) {
|
||||||
ppuCycles += ticks;
|
ppuCycles += ticks;
|
||||||
}
|
}
|
||||||
lastOpTicks = ticks;
|
lastOpTicks = ticks;
|
||||||
}
|
}
|
||||||
|
|
||||||
GameboyTestState GameBoy::runTest(GameboyTestState initial) {
|
GameboyTestState GameBoy::runTest(GameboyTestState initial) {
|
||||||
addressSpace.setTesting(true);
|
addressSpace.setTesting(true);
|
||||||
|
|
||||||
PC = initial.PC;
|
PC = initial.PC;
|
||||||
SP = initial.SP;
|
SP = initial.SP;
|
||||||
AF.hi = initial.A;
|
AF.hi = initial.A;
|
||||||
AF.lo = initial.F;
|
AF.lo = initial.F;
|
||||||
BC.hi = initial.B;
|
BC.hi = initial.B;
|
||||||
BC.lo = initial.C;
|
BC.lo = initial.C;
|
||||||
DE.hi = initial.D;
|
DE.hi = initial.D;
|
||||||
DE.lo = initial.E;
|
DE.lo = initial.E;
|
||||||
HL.hi = initial.H;
|
HL.hi = initial.H;
|
||||||
HL.lo = initial.L;
|
HL.lo = initial.L;
|
||||||
addressSpace.memoryLayout.IE = 1;
|
addressSpace.memoryLayout.IE = 1;
|
||||||
|
|
||||||
for (const auto& [addr, val] : initial.RAM) {
|
for (const auto &[addr, val] : initial.RAM) {
|
||||||
addressSpace[addr] = val;
|
addressSpace[addr] = val;
|
||||||
}
|
}
|
||||||
|
|
||||||
opcodeResolver();
|
opcodeResolver();
|
||||||
|
|
||||||
std::vector<std::tuple<Word, Byte>> returnRAM;
|
std::vector<std::tuple<Word, Byte>> returnRAM;
|
||||||
for (const auto& [addr, val] : initial.RAM) {
|
for (const auto &[addr, val] : initial.RAM) {
|
||||||
returnRAM.emplace_back(addr, addressSpace[addr]);
|
returnRAM.emplace_back(addr, addressSpace[addr]);
|
||||||
}
|
}
|
||||||
return {
|
return {PC, SP, AF.hi, AF.lo, BC.hi, BC.lo,
|
||||||
PC, SP,
|
DE.hi, DE.lo, HL.hi, HL.lo, returnRAM};
|
||||||
AF.hi, AF.lo,
|
|
||||||
BC.hi, BC.lo,
|
|
||||||
DE.hi, DE.lo,
|
|
||||||
HL.hi, HL.lo,
|
|
||||||
returnRAM
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void GameBoy::start(const std::string &bootrom, const std::string &game) {
|
||||||
|
addressSpace.loadBootrom(bootrom);
|
||||||
|
addressSpace.loadGame(game);
|
||||||
|
addressSpace.determineMBCInfo();
|
||||||
|
addressSpace.createRamBank();
|
||||||
|
|
||||||
void GameBoy::start(const std::string& bootrom, const std::string& game) {
|
bool quit = false;
|
||||||
addressSpace.loadBootrom(bootrom);
|
bool setIME = false;
|
||||||
addressSpace.loadGame(game);
|
bool debug = false;
|
||||||
addressSpace.determineMBCInfo();
|
bool step = false;
|
||||||
addressSpace.createRamBank();
|
|
||||||
|
|
||||||
bool quit = false;
|
while (!quit) {
|
||||||
bool setIME = false;
|
// Event loop
|
||||||
bool debug = false;
|
while (SDL_PollEvent(&event)) {
|
||||||
bool step = false;
|
switch (event.type) {
|
||||||
|
case SDL_QUIT:
|
||||||
|
quit = true;
|
||||||
|
break;
|
||||||
|
case SDL_KEYDOWN:
|
||||||
|
switch (event.key.keysym.sym) {
|
||||||
|
case SDLK_a:
|
||||||
|
joypadInput.LEFT = true;
|
||||||
|
break;
|
||||||
|
case SDLK_d:
|
||||||
|
joypadInput.RIGHT = true;
|
||||||
|
break;
|
||||||
|
case SDLK_w:
|
||||||
|
joypadInput.UP = true;
|
||||||
|
break;
|
||||||
|
case SDLK_s:
|
||||||
|
joypadInput.DOWN = true;
|
||||||
|
break;
|
||||||
|
case SDLK_k:
|
||||||
|
joypadInput.A = true;
|
||||||
|
break;
|
||||||
|
case SDLK_l:
|
||||||
|
joypadInput.B = true;
|
||||||
|
break;
|
||||||
|
case SDLK_o:
|
||||||
|
joypadInput.SELECT = true;
|
||||||
|
break;
|
||||||
|
case SDLK_p:
|
||||||
|
joypadInput.START = true;
|
||||||
|
break;
|
||||||
|
case SDLK_h:
|
||||||
|
debug = !debug;
|
||||||
|
break;
|
||||||
|
case SDLK_n:
|
||||||
|
step = true;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case SDL_KEYUP:
|
||||||
|
switch (event.key.keysym.sym) {
|
||||||
|
case SDLK_a:
|
||||||
|
joypadInput.LEFT = false;
|
||||||
|
break;
|
||||||
|
case SDLK_d:
|
||||||
|
joypadInput.RIGHT = false;
|
||||||
|
break;
|
||||||
|
case SDLK_w:
|
||||||
|
joypadInput.UP = false;
|
||||||
|
break;
|
||||||
|
case SDLK_s:
|
||||||
|
joypadInput.DOWN = false;
|
||||||
|
break;
|
||||||
|
case SDLK_k:
|
||||||
|
joypadInput.A = false;
|
||||||
|
break;
|
||||||
|
case SDLK_l:
|
||||||
|
joypadInput.B = false;
|
||||||
|
break;
|
||||||
|
case SDLK_o:
|
||||||
|
joypadInput.SELECT = false;
|
||||||
|
break;
|
||||||
|
case SDLK_p:
|
||||||
|
joypadInput.START = false;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
while (!quit) {
|
while (!rendered) {
|
||||||
// Event loop
|
if (debug == true && step == false)
|
||||||
while (SDL_PollEvent(&event)) {
|
break;
|
||||||
switch (event.type) {
|
step = false;
|
||||||
case SDL_QUIT:
|
joypadHandler();
|
||||||
quit = true;
|
if (PC > 0xFF && addressSpace.getBootromState()) {
|
||||||
break;
|
addressSpace.unmapBootrom();
|
||||||
case SDL_KEYDOWN:
|
}
|
||||||
switch (event.key.keysym.sym) {
|
ppuEnabled = addressSpace.memoryLayout.LCDC & 0x80;
|
||||||
case SDLK_a:
|
prevTMA = addressSpace.memoryLayout.TMA;
|
||||||
joypadInput.LEFT = true;
|
|
||||||
break;
|
|
||||||
case SDLK_d:
|
|
||||||
joypadInput.RIGHT = true;
|
|
||||||
break;
|
|
||||||
case SDLK_w:
|
|
||||||
joypadInput.UP = true;
|
|
||||||
break;
|
|
||||||
case SDLK_s:
|
|
||||||
joypadInput.DOWN = true;
|
|
||||||
break;
|
|
||||||
case SDLK_k:
|
|
||||||
joypadInput.A = true;
|
|
||||||
break;
|
|
||||||
case SDLK_l:
|
|
||||||
joypadInput.B = true;
|
|
||||||
break;
|
|
||||||
case SDLK_o:
|
|
||||||
joypadInput.SELECT = true;
|
|
||||||
break;
|
|
||||||
case SDLK_p:
|
|
||||||
joypadInput.START = true;
|
|
||||||
break;
|
|
||||||
case SDLK_h:
|
|
||||||
debug = !debug;
|
|
||||||
break;
|
|
||||||
case SDLK_n:
|
|
||||||
step = true;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case SDL_KEYUP:
|
|
||||||
switch (event.key.keysym.sym) {
|
|
||||||
case SDLK_a:
|
|
||||||
joypadInput.LEFT = false;
|
|
||||||
break;
|
|
||||||
case SDLK_d:
|
|
||||||
joypadInput.RIGHT = false;
|
|
||||||
break;
|
|
||||||
case SDLK_w:
|
|
||||||
joypadInput.UP = false;
|
|
||||||
break;
|
|
||||||
case SDLK_s:
|
|
||||||
joypadInput.DOWN = false;
|
|
||||||
break;
|
|
||||||
case SDLK_k:
|
|
||||||
joypadInput.A = false;
|
|
||||||
break;
|
|
||||||
case SDLK_l:
|
|
||||||
joypadInput.B = false;
|
|
||||||
break;
|
|
||||||
case SDLK_o:
|
|
||||||
joypadInput.SELECT = false;
|
|
||||||
break;
|
|
||||||
case SDLK_p:
|
|
||||||
joypadInput.START = false;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
while (!rendered) {
|
if (debug) {
|
||||||
if (debug == true && step == false)
|
printf("A: %.2X F: %.2X B: %.2X C: %.2X D: %.2X E: %.2X H: %.2X L: "
|
||||||
break;
|
"%.2X SP: %.4X PC: 00:%.4X (%.2X %.2X %.2X %.2X)\n",
|
||||||
step = false;
|
AF.hi, AF.lo, BC.hi, BC.lo, DE.hi, DE.lo, HL.hi, HL.lo, SP, PC,
|
||||||
joypadHandler();
|
readOnlyAddressSpace[PC], readOnlyAddressSpace[PC + 1],
|
||||||
if (PC > 0xFF && addressSpace.getBootromState()) {
|
readOnlyAddressSpace[PC + 2], readOnlyAddressSpace[PC + 3]);
|
||||||
addressSpace.unmapBootrom();
|
|
||||||
}
|
|
||||||
ppuEnabled = addressSpace.memoryLayout.LCDC & 0x80;
|
|
||||||
prevTMA = addressSpace.memoryLayout.TMA;
|
|
||||||
|
|
||||||
if (debug) {
|
// printf("Cycles: %lu, Opcode: 0x%.2x PPU cycles: %lu, PPMode: %d\n",
|
||||||
printf(
|
// cycles, readOnlyAddressSpace[PC],
|
||||||
"A: %.2X F: %.2X B: %.2X C: %.2X D: %.2X E: %.2X H: %.2X L: %.2X SP: %.4X PC: 00:%.4X (%.2X %.2X %.2X %.2X)\n",
|
// cyclesSinceLastScanline(), currentMode);
|
||||||
AF.hi, AF.lo, BC.hi, BC.lo, DE.hi, DE.lo, HL.hi, HL.lo, SP, PC, readOnlyAddressSpace[PC],
|
// printf("AF:0x%.4x, BC:0x%.4x\n", AF.reg, BC.reg);
|
||||||
readOnlyAddressSpace[PC + 1], readOnlyAddressSpace[PC + 2], readOnlyAddressSpace[PC + 3]);
|
// printf("DE:0x%.4x, HL:0x%.4x\n", DE.reg, HL.reg);
|
||||||
|
// printf("IME:%d IF:0x%.2x IE:0x%.2x\n", IME, (*IF), (*IE));
|
||||||
|
// printf("PC:0x%.4x, SP:0x%.4x\n", PC, SP);
|
||||||
|
// printf("LCDC:%.2x STAT:0x%.2x LY:%d LYC:%d\n", (*LCDC), (*STAT),
|
||||||
|
// (*LY), (*LYC)); printf("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!halted) {
|
||||||
// printf("Cycles: %lu, Opcode: 0x%.2x PPU cycles: %lu, PPMode: %d\n", cycles, readOnlyAddressSpace[PC],
|
opcodeResolver();
|
||||||
// cyclesSinceLastScanline(), currentMode);
|
addressSpace.MBCUpdate();
|
||||||
// printf("AF:0x%.4x, BC:0x%.4x\n", AF.reg, BC.reg);
|
} else {
|
||||||
// printf("DE:0x%.4x, HL:0x%.4x\n", DE.reg, HL.reg);
|
addCycles(4);
|
||||||
// printf("IME:%d IF:0x%.2x IE:0x%.2x\n", IME, (*IF), (*IE));
|
}
|
||||||
// printf("PC:0x%.4x, SP:0x%.4x\n", PC, SP);
|
timingHandler();
|
||||||
// printf("LCDC:%.2x STAT:0x%.2x LY:%d LYC:%d\n", (*LCDC), (*STAT), (*LY), (*LYC));
|
interruptHandler();
|
||||||
// printf("\n");
|
if (ppuEnabled) {
|
||||||
}
|
ppuUpdate();
|
||||||
|
} else {
|
||||||
if (!halted) {
|
ppuCycles = 2;
|
||||||
opcodeResolver();
|
lastScanline = 0;
|
||||||
addressSpace.MBCUpdate();
|
lastRefresh = 0;
|
||||||
}
|
addressSpace.memoryLayout.LY = 0x00;
|
||||||
else {
|
addressSpace.memoryLayout.STAT &= 0xfc;
|
||||||
addCycles(4);
|
}
|
||||||
}
|
if (setIME) {
|
||||||
timingHandler();
|
IME = 1;
|
||||||
interruptHandler();
|
setIME = false;
|
||||||
if (ppuEnabled) {
|
}
|
||||||
ppuUpdate();
|
if (IME_togge) {
|
||||||
}
|
setIME = true;
|
||||||
else {
|
IME_togge = false;
|
||||||
ppuCycles = 2;
|
}
|
||||||
lastScanline = 0;
|
if (addressSpace.dmaTransferRequested) {
|
||||||
lastRefresh = 0;
|
cyclesUntilDMATransfer -= lastOpTicks;
|
||||||
addressSpace.memoryLayout.LY = 0x00;
|
if (cyclesUntilDMATransfer <= 0) {
|
||||||
addressSpace.memoryLayout.STAT &= 0xfc;
|
cyclesUntilDMATransfer = 160;
|
||||||
}
|
addressSpace.dmaTransfer();
|
||||||
if (setIME) {
|
}
|
||||||
IME = 1;
|
}
|
||||||
setIME = false;
|
}
|
||||||
}
|
rendered = false;
|
||||||
if (IME_togge) {
|
}
|
||||||
setIME = true;
|
|
||||||
IME_togge = false;
|
|
||||||
}
|
|
||||||
if (addressSpace.dmaTransferRequested) {
|
|
||||||
cyclesUntilDMATransfer -= lastOpTicks;
|
|
||||||
if (cyclesUntilDMATransfer <= 0) {
|
|
||||||
cyclesUntilDMATransfer = 160;
|
|
||||||
addressSpace.dmaTransfer();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
rendered = false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
290
src/gameboy.hpp
290
src/gameboy.hpp
@@ -1,186 +1,172 @@
|
|||||||
#ifndef GBPP_SRC_GAMEBOY_HPP_
|
#ifndef GBPP_SRC_GAMEBOY_HPP_
|
||||||
#define GBPP_SRC_GAMEBOY_HPP_
|
#define GBPP_SRC_GAMEBOY_HPP_
|
||||||
|
|
||||||
#include <filesystem>
|
|
||||||
#include <cstdint>
|
|
||||||
#include <string>
|
|
||||||
#include <SDL.h>
|
|
||||||
#include "defines.hpp"
|
|
||||||
#include "addressSpace.hpp"
|
#include "addressSpace.hpp"
|
||||||
|
#include "defines.hpp"
|
||||||
#include "testing.hpp"
|
#include "testing.hpp"
|
||||||
|
#include <SDL.h>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <filesystem>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
union RegisterPair {
|
union RegisterPair {
|
||||||
Word reg; //register.reg == (hi << 8) + lo. (hi is more significant than lo)
|
Word reg; // register.reg == (hi << 8) + lo. (hi is more significant than lo)
|
||||||
|
|
||||||
struct {
|
struct {
|
||||||
Byte lo;
|
Byte lo;
|
||||||
Byte hi;
|
Byte hi;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
class GameBoy {
|
class GameBoy {
|
||||||
//T-cycles not M-cycles (4 T-cycles = 1 M-cycle)
|
// T-cycles not M-cycles (4 T-cycles = 1 M-cycle)
|
||||||
uint64_t cycles = 0;
|
uint64_t cycles = 0;
|
||||||
//Start at 2 T-cycles https://github.com/Gekkio/mooneye-test-suite/blob/main/acceptance/ppu/lcdon_timing-GS.s
|
// Start at 2 T-cycles
|
||||||
uint64_t ppuCycles = 2;
|
// https://github.com/Gekkio/mooneye-test-suite/blob/main/acceptance/ppu/lcdon_timing-GS.s
|
||||||
bool ppuEnabled = false;
|
uint64_t ppuCycles = 2;
|
||||||
uint16_t lastOpTicks = 0;
|
bool ppuEnabled = false;
|
||||||
uint64_t lastRefresh = 0;
|
uint16_t lastOpTicks = 0;
|
||||||
uint64_t lastScanline = 0;
|
uint64_t lastRefresh = 0;
|
||||||
uint64_t cyclesToStayInHblank = -1;
|
uint64_t lastScanline = 0;
|
||||||
uint64_t lastDivUpdate = 0;
|
uint64_t cyclesToStayInHblank = -1;
|
||||||
bool rendered = false;
|
uint64_t lastDivUpdate = 0;
|
||||||
|
bool rendered = false;
|
||||||
|
|
||||||
uint8_t IME = 0; //enables interupts
|
uint8_t IME = 0; // enables interupts
|
||||||
// EI is actually "disable interrupts for one instruction, then enable them"
|
// EI is actually "disable interrupts for one instruction, then enable them"
|
||||||
// This keeps track of that
|
// This keeps track of that
|
||||||
bool IME_togge = false;
|
bool IME_togge = false;
|
||||||
|
|
||||||
//Accumulator and flags
|
// Accumulator and flags
|
||||||
RegisterPair AF = {0};
|
RegisterPair AF = {0};
|
||||||
//General purpose CPU registers
|
// General purpose CPU registers
|
||||||
RegisterPair BC = {0};
|
RegisterPair BC = {0};
|
||||||
RegisterPair DE = {0};
|
RegisterPair DE = {0};
|
||||||
RegisterPair HL = {0};
|
RegisterPair HL = {0};
|
||||||
|
|
||||||
Word SP = 0xFFFE; //stack pointer
|
Word SP = 0xFFFE; // stack pointer
|
||||||
Word PC = 0x0000; //program counter
|
Word PC = 0x0000; // program counter
|
||||||
|
|
||||||
AddressSpace addressSpace;
|
AddressSpace addressSpace;
|
||||||
const AddressSpace& readOnlyAddressSpace = addressSpace;
|
const AddressSpace &readOnlyAddressSpace = addressSpace;
|
||||||
|
|
||||||
PPUMode currentMode = PPUMode::mode0;
|
PPUMode_enum currentMode = PPUMode_enum::mode0;
|
||||||
Byte windowLineCounter = 0;
|
Byte windowLineCounter = 0;
|
||||||
int16_t cyclesUntilDMATransfer = 160;
|
int16_t cyclesUntilDMATransfer = 160;
|
||||||
|
|
||||||
Byte prevTMA = 0;
|
Byte prevTMA = 0;
|
||||||
uint64_t lastTIMAUpdate = 0;
|
uint64_t lastTIMAUpdate = 0;
|
||||||
bool halted = false;
|
bool halted = false;
|
||||||
bool haltBug = true;
|
bool haltBug = true;
|
||||||
bool stopped = false;
|
bool stopped = false;
|
||||||
|
|
||||||
//3 colour channels
|
// 3 colour channels
|
||||||
uint32_t* framebuffer = new uint32_t[RESOLUTION_X * RESOLUTION_Y * SCREEN_BPP];
|
uint32_t *framebuffer =
|
||||||
SDL_Window* screen = nullptr;
|
new uint32_t[RESOLUTION_X * RESOLUTION_Y * SCREEN_BPP];
|
||||||
SDL_Renderer* renderer = nullptr;
|
SDL_Window *screen = nullptr;
|
||||||
SDL_Texture* texture = nullptr;
|
SDL_Renderer *renderer = nullptr;
|
||||||
SDL_Event event = {0};
|
SDL_Texture *texture = nullptr;
|
||||||
uint32_t frameStart = 0;
|
SDL_Event event = {0};
|
||||||
uint32_t frameTime = 0;
|
uint32_t frameStart = 0;
|
||||||
const int frameDelay = 1000 / V_SYNC;
|
uint32_t frameTime = 0;
|
||||||
|
const int frameDelay = 1000 / V_SYNC;
|
||||||
|
|
||||||
Input joypadInput;
|
Input joypadInput;
|
||||||
void joypadHandler();
|
void joypadHandler();
|
||||||
|
|
||||||
void opcodeResolver();
|
void opcodeResolver();
|
||||||
|
|
||||||
bool statInteruptLine = false;
|
bool statInteruptLine = false;
|
||||||
bool LCDCBitEnabled(Byte bit) const;
|
bool LCDCBitEnabled(Byte bit) const;
|
||||||
void incLY();
|
void incLY();
|
||||||
void ppuUpdate();
|
void ppuUpdate();
|
||||||
void drawLine();
|
void drawLine();
|
||||||
static bool oamBitEnabled(Byte oamAttributeByte, Byte bit);
|
static bool oamBitEnabled(Byte oamAttributeByte, Byte bit);
|
||||||
static unsigned int getColourFromPalette(Byte palette);
|
static unsigned int getColourFromPalette(Byte palette);
|
||||||
void SDL2present();
|
void SDL2present();
|
||||||
|
|
||||||
void checkPPUMode();
|
void checkPPUMode();
|
||||||
void setPPUMode(PPUMode mode);
|
void setPPUMode(PPUMode_enum mode);
|
||||||
uint64_t cyclesSinceLastScanline() const;
|
uint64_t cyclesSinceLastScanline() const;
|
||||||
uint64_t cyclesSinceLastRefresh() const;
|
uint64_t cyclesSinceLastRefresh() const;
|
||||||
|
|
||||||
void timingHandler();
|
void timingHandler();
|
||||||
|
|
||||||
void interruptHandler();
|
void interruptHandler();
|
||||||
bool testInterruptEnabled(Byte interrupt) const;
|
bool testInterruptEnabled(Byte interrupt) const;
|
||||||
void setInterrupt(Byte interrupt);
|
void setInterrupt(Byte interrupt);
|
||||||
void resetInterrupt(Byte interrupt);
|
void resetInterrupt(Byte interrupt);
|
||||||
|
|
||||||
void VBlankHandle();
|
void VBlankHandle();
|
||||||
void LCDStatHandle();
|
void LCDStatHandle();
|
||||||
void timerHandle();
|
void timerHandle();
|
||||||
void serialHandle();
|
void serialHandle();
|
||||||
void joypadHandle();
|
void joypadHandle();
|
||||||
|
|
||||||
void setFlag(Byte bit);
|
void setFlag(Byte bit);
|
||||||
void resetFlag(Byte bit);
|
void resetFlag(Byte bit);
|
||||||
bool getFlag(Byte bit) const;
|
bool getFlag(Byte bit) const;
|
||||||
|
|
||||||
Word getWordPC();
|
Word getWordPC();
|
||||||
Byte getBytePC();
|
Byte getBytePC();
|
||||||
Word getWordSP();
|
Word getWordSP();
|
||||||
Byte getByteSP();
|
Byte getByteSP();
|
||||||
|
|
||||||
void addCycles(Byte ticks);
|
void addCycles(Byte ticks);
|
||||||
|
|
||||||
//OPCODE FUNCTIONS
|
// OPCODE FUNCTIONS
|
||||||
template <typename T>
|
template <typename T> void ld(T &dest, T src);
|
||||||
void ld(T& dest, T src);
|
void ldW(Word destAddr, Word src);
|
||||||
void ldW(Word destAddr, Word src);
|
template <typename T> void orBitwise(T &dest, T src);
|
||||||
template <typename T>
|
template <typename T> void andBitwise(T &dest, T src);
|
||||||
void orBitwise(T& dest, T src);
|
template <typename T> void xorBitwise(T &dest, T src);
|
||||||
template <typename T>
|
void bit(Byte testBit, Byte reg);
|
||||||
void andBitwise(T& dest, T src);
|
void extendedOpcodeResolver();
|
||||||
template <typename T>
|
static void set(uint8_t testBit, uint8_t ®);
|
||||||
void xorBitwise(T& dest, T src);
|
static void res(uint8_t testBit, uint8_t ®);
|
||||||
void bit(Byte testBit, Byte reg);
|
template <typename T> void jp(T address);
|
||||||
void extendedOpcodeResolver();
|
template <typename T> bool jrNZ(T offset);
|
||||||
static void set(uint8_t testBit, uint8_t& reg);
|
template <class T> bool jrNC(T offset);
|
||||||
static void res(uint8_t testBit, uint8_t& reg);
|
template <class T> bool jrC(T offset);
|
||||||
template <typename T>
|
template <typename T> void inc(T ®);
|
||||||
void jp(T address);
|
template <typename T> void call(T address);
|
||||||
template <typename T>
|
void halt();
|
||||||
bool jrNZ(T offset);
|
void daa();
|
||||||
template <class T>
|
void stop();
|
||||||
bool jrNC(T offset);
|
void cp(Byte value);
|
||||||
template <class T>
|
template <typename T> void dec(T ®);
|
||||||
bool jrC(T offset);
|
template <typename T> bool jrZ(T offset);
|
||||||
template <typename T>
|
void sub(Byte value);
|
||||||
void inc(T& reg);
|
void sbc(Byte value);
|
||||||
template <typename T>
|
template <typename T> void jr(T OFFSET);
|
||||||
void call(T address);
|
void push(Word reg);
|
||||||
void halt();
|
void rl(Byte ®);
|
||||||
void daa();
|
void sla(Byte ®);
|
||||||
void stop();
|
void sra(uint8_t ®);
|
||||||
void cp(Byte value);
|
void srl(uint8_t ®);
|
||||||
template <typename T>
|
void rrc(Byte ®);
|
||||||
void dec(T& reg);
|
void rrca();
|
||||||
template <typename T>
|
void rra();
|
||||||
bool jrZ(T offset);
|
void rr(Byte ®);
|
||||||
void sub(Byte value);
|
void rlc(Byte ®);
|
||||||
void sbc(Byte value);
|
void rlca();
|
||||||
template <typename T>
|
void rla();
|
||||||
void jr(T OFFSET);
|
template <typename T> void pop(T ®);
|
||||||
void push(Word reg);
|
template <typename T> void rst(T address);
|
||||||
void rl(Byte& reg);
|
void ret();
|
||||||
void sla(Byte& reg);
|
template <typename T> void add(T ®, T value);
|
||||||
void sra(uint8_t& reg);
|
void adc(Byte value);
|
||||||
void srl(uint8_t& reg);
|
void cpl();
|
||||||
void rrc(Byte& reg);
|
void scf();
|
||||||
void rrca();
|
void ccf();
|
||||||
void rra();
|
void swap(Byte &value);
|
||||||
void rr(Byte& reg);
|
|
||||||
void rlc(Byte& reg);
|
|
||||||
void rlca();
|
|
||||||
void rla();
|
|
||||||
template <typename T>
|
|
||||||
void pop(T& reg);
|
|
||||||
template <typename T>
|
|
||||||
void rst(T address);
|
|
||||||
void ret();
|
|
||||||
template <typename T>
|
|
||||||
void add(T& reg, T value);
|
|
||||||
void adc(Byte value);
|
|
||||||
void cpl();
|
|
||||||
void scf();
|
|
||||||
void ccf();
|
|
||||||
void swap(Byte& value);
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
void start(const std::string& bootrom, const std::string& game);
|
void start(const std::string &bootrom, const std::string &game);
|
||||||
void SDL2setup();
|
void SDL2setup();
|
||||||
void SDL2destroy() const;
|
void SDL2destroy() const;
|
||||||
|
|
||||||
GameboyTestState runTest(GameboyTestState initial);
|
GameboyTestState runTest(GameboyTestState initial);
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif //GBPP_SRC_GAMEBOY_HPP_
|
#endif // GBPP_SRC_GAMEBOY_HPP_
|
||||||
|
|||||||
@@ -2,88 +2,94 @@
|
|||||||
#include "gameboy.hpp"
|
#include "gameboy.hpp"
|
||||||
|
|
||||||
bool GameBoy::testInterruptEnabled(const Byte interrupt) const {
|
bool GameBoy::testInterruptEnabled(const Byte interrupt) const {
|
||||||
return readOnlyAddressSpace.memoryLayout.IE & static_cast<Byte>(1 << interrupt);
|
return readOnlyAddressSpace.memoryLayout.IE &
|
||||||
|
static_cast<Byte>(1 << interrupt);
|
||||||
}
|
}
|
||||||
|
|
||||||
void GameBoy::setInterrupt(const Byte interrupt) {
|
void GameBoy::setInterrupt(const Byte interrupt) {
|
||||||
addressSpace.memoryLayout.IF |= 1 << interrupt;
|
addressSpace.memoryLayout.IF |= 1 << interrupt;
|
||||||
addressSpace.memoryLayout.IF |= 0xE0;
|
addressSpace.memoryLayout.IF |= 0xE0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void GameBoy::resetInterrupt(const Byte interrupt) {
|
void GameBoy::resetInterrupt(const Byte interrupt) {
|
||||||
addressSpace.memoryLayout.IF &= ~(1 << interrupt);
|
addressSpace.memoryLayout.IF &= ~(1 << interrupt);
|
||||||
addressSpace.memoryLayout.IF |= 0xE0;
|
addressSpace.memoryLayout.IF |= 0xE0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void GameBoy::interruptHandler() {
|
void GameBoy::interruptHandler() {
|
||||||
if (readOnlyAddressSpace.memoryLayout.IF & static_cast<Byte>(1 << VBLANK_INTERRUPT) && testInterruptEnabled(
|
if (readOnlyAddressSpace.memoryLayout.IF &
|
||||||
VBLANK_INTERRUPT)) {
|
static_cast<Byte>(1 << VBLANK_INTERRUPT) &&
|
||||||
if (IME)
|
testInterruptEnabled(VBLANK_INTERRUPT)) {
|
||||||
VBlankHandle();
|
if (IME)
|
||||||
halted = false;
|
VBlankHandle();
|
||||||
}
|
halted = false;
|
||||||
if (readOnlyAddressSpace.memoryLayout.IF & static_cast<Byte>(1 << LCD_STAT_INTERRUPT) && testInterruptEnabled(
|
}
|
||||||
LCD_STAT_INTERRUPT)) {
|
if (readOnlyAddressSpace.memoryLayout.IF &
|
||||||
if (IME)
|
static_cast<Byte>(1 << LCD_STAT_INTERRUPT) &&
|
||||||
LCDStatHandle();
|
testInterruptEnabled(LCD_STAT_INTERRUPT)) {
|
||||||
halted = false;
|
if (IME)
|
||||||
}
|
LCDStatHandle();
|
||||||
if (readOnlyAddressSpace.memoryLayout.IF & static_cast<Byte>(1 << TIMER_INTERRUPT) && testInterruptEnabled(
|
halted = false;
|
||||||
TIMER_INTERRUPT)) {
|
}
|
||||||
if (IME)
|
if (readOnlyAddressSpace.memoryLayout.IF &
|
||||||
timerHandle();
|
static_cast<Byte>(1 << TIMER_INTERRUPT) &&
|
||||||
halted = false;
|
testInterruptEnabled(TIMER_INTERRUPT)) {
|
||||||
}
|
if (IME)
|
||||||
if (readOnlyAddressSpace.memoryLayout.IF & static_cast<Byte>(1 << SERIAL_INTERRUPT) && testInterruptEnabled(
|
timerHandle();
|
||||||
SERIAL_INTERRUPT)) {
|
halted = false;
|
||||||
if (IME)
|
}
|
||||||
serialHandle();
|
if (readOnlyAddressSpace.memoryLayout.IF &
|
||||||
halted = false;
|
static_cast<Byte>(1 << SERIAL_INTERRUPT) &&
|
||||||
}
|
testInterruptEnabled(SERIAL_INTERRUPT)) {
|
||||||
if (readOnlyAddressSpace.memoryLayout.IF & static_cast<Byte>(1 << JOYPAD_INTERRUPT) && testInterruptEnabled(
|
if (IME)
|
||||||
JOYPAD_INTERRUPT)) {
|
serialHandle();
|
||||||
if (IME)
|
halted = false;
|
||||||
joypadHandle();
|
}
|
||||||
halted = false;
|
if (readOnlyAddressSpace.memoryLayout.IF &
|
||||||
}
|
static_cast<Byte>(1 << JOYPAD_INTERRUPT) &&
|
||||||
|
testInterruptEnabled(JOYPAD_INTERRUPT)) {
|
||||||
|
if (IME)
|
||||||
|
joypadHandle();
|
||||||
|
halted = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void GameBoy::VBlankHandle() {
|
void GameBoy::VBlankHandle() {
|
||||||
IME = 0;
|
IME = 0;
|
||||||
push(PC);
|
push(PC);
|
||||||
addCycles(20);
|
addCycles(20);
|
||||||
PC = 0x40;
|
PC = 0x40;
|
||||||
resetInterrupt(VBLANK_INTERRUPT);
|
resetInterrupt(VBLANK_INTERRUPT);
|
||||||
}
|
}
|
||||||
|
|
||||||
void GameBoy::LCDStatHandle() {
|
void GameBoy::LCDStatHandle() {
|
||||||
IME = 0;
|
IME = 0;
|
||||||
push(PC);
|
push(PC);
|
||||||
addCycles(20);
|
addCycles(20);
|
||||||
PC = 0x48;
|
PC = 0x48;
|
||||||
resetInterrupt(LCD_STAT_INTERRUPT);
|
resetInterrupt(LCD_STAT_INTERRUPT);
|
||||||
}
|
}
|
||||||
|
|
||||||
void GameBoy::timerHandle() {
|
void GameBoy::timerHandle() {
|
||||||
IME = 0;
|
IME = 0;
|
||||||
push(PC);
|
push(PC);
|
||||||
addCycles(20);
|
addCycles(20);
|
||||||
PC = 0x50;
|
PC = 0x50;
|
||||||
resetInterrupt(TIMER_INTERRUPT);
|
resetInterrupt(TIMER_INTERRUPT);
|
||||||
}
|
}
|
||||||
|
|
||||||
void GameBoy::serialHandle() {
|
void GameBoy::serialHandle() {
|
||||||
IME = 0;
|
IME = 0;
|
||||||
push(PC);
|
push(PC);
|
||||||
addCycles(20);
|
addCycles(20);
|
||||||
PC = 0x58;
|
PC = 0x58;
|
||||||
resetInterrupt(SERIAL_INTERRUPT);
|
resetInterrupt(SERIAL_INTERRUPT);
|
||||||
}
|
}
|
||||||
|
|
||||||
void GameBoy::joypadHandle() {
|
void GameBoy::joypadHandle() {
|
||||||
IME = 0;
|
IME = 0;
|
||||||
push(PC);
|
push(PC);
|
||||||
addCycles(20);
|
addCycles(20);
|
||||||
PC = 0x60;
|
PC = 0x60;
|
||||||
resetInterrupt(JOYPAD_INTERRUPT);
|
resetInterrupt(JOYPAD_INTERRUPT);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,44 +1,44 @@
|
|||||||
#include "gameboy.hpp"
|
|
||||||
#include "defines.hpp"
|
#include "defines.hpp"
|
||||||
|
#include "gameboy.hpp"
|
||||||
|
|
||||||
void GameBoy::joypadHandler() {
|
void GameBoy::joypadHandler() {
|
||||||
const Byte joyP = addressSpace.memoryLayout.JOYP;
|
const Byte joyP = addressSpace.memoryLayout.JOYP;
|
||||||
const bool buttons = (joyP & 0x20) == 0;
|
const bool buttons = (joyP & 0x20) == 0;
|
||||||
const bool dpad = (joyP & 0x10) == 0;
|
const bool dpad = (joyP & 0x10) == 0;
|
||||||
|
|
||||||
addressSpace.memoryLayout.JOYP |= 0xCF;
|
addressSpace.memoryLayout.JOYP |= 0xCF;
|
||||||
|
|
||||||
if (buttons && !dpad) {
|
if (buttons && !dpad) {
|
||||||
if (joypadInput.A)
|
if (joypadInput.A)
|
||||||
addressSpace.memoryLayout.JOYP &= ~0x1;
|
addressSpace.memoryLayout.JOYP &= ~0x1;
|
||||||
if (joypadInput.B)
|
if (joypadInput.B)
|
||||||
addressSpace.memoryLayout.JOYP &= ~0x2;
|
addressSpace.memoryLayout.JOYP &= ~0x2;
|
||||||
if (joypadInput.SELECT)
|
if (joypadInput.SELECT)
|
||||||
addressSpace.memoryLayout.JOYP &= ~0x4;
|
addressSpace.memoryLayout.JOYP &= ~0x4;
|
||||||
if (joypadInput.START)
|
if (joypadInput.START)
|
||||||
addressSpace.memoryLayout.JOYP &= ~0x8;
|
addressSpace.memoryLayout.JOYP &= ~0x8;
|
||||||
}
|
}
|
||||||
if (!buttons && dpad) {
|
if (!buttons && dpad) {
|
||||||
if (joypadInput.RIGHT)
|
if (joypadInput.RIGHT)
|
||||||
addressSpace.memoryLayout.JOYP &= ~0x1;
|
addressSpace.memoryLayout.JOYP &= ~0x1;
|
||||||
if (joypadInput.LEFT)
|
if (joypadInput.LEFT)
|
||||||
addressSpace.memoryLayout.JOYP &= ~0x2;
|
addressSpace.memoryLayout.JOYP &= ~0x2;
|
||||||
if (joypadInput.UP)
|
if (joypadInput.UP)
|
||||||
addressSpace.memoryLayout.JOYP &= ~0x4;
|
addressSpace.memoryLayout.JOYP &= ~0x4;
|
||||||
if (joypadInput.DOWN)
|
if (joypadInput.DOWN)
|
||||||
addressSpace.memoryLayout.JOYP &= ~0x8;
|
addressSpace.memoryLayout.JOYP &= ~0x8;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (buttons && dpad) {
|
if (buttons && dpad) {
|
||||||
addressSpace.memoryLayout.JOYP |= 0xCF;
|
addressSpace.memoryLayout.JOYP |= 0xCF;
|
||||||
|
|
||||||
if (joypadInput.RIGHT && joypadInput.A)
|
if (joypadInput.RIGHT && joypadInput.A)
|
||||||
addressSpace.memoryLayout.JOYP &= ~0x1;
|
addressSpace.memoryLayout.JOYP &= ~0x1;
|
||||||
if (joypadInput.LEFT && joypadInput.B)
|
if (joypadInput.LEFT && joypadInput.B)
|
||||||
addressSpace.memoryLayout.JOYP &= ~0x2;
|
addressSpace.memoryLayout.JOYP &= ~0x2;
|
||||||
if (joypadInput.UP && joypadInput.SELECT)
|
if (joypadInput.UP && joypadInput.SELECT)
|
||||||
addressSpace.memoryLayout.JOYP &= ~0x4;
|
addressSpace.memoryLayout.JOYP &= ~0x4;
|
||||||
if (joypadInput.DOWN && joypadInput.START)
|
if (joypadInput.DOWN && joypadInput.START)
|
||||||
addressSpace.memoryLayout.JOYP &= ~0x8;
|
addressSpace.memoryLayout.JOYP &= ~0x8;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
147
src/main.cpp
147
src/main.cpp
@@ -1,94 +1,91 @@
|
|||||||
#include <string>
|
|
||||||
#include <filesystem>
|
|
||||||
#include <vector>
|
|
||||||
#include "3rdParty/json.hpp"
|
#include "3rdParty/json.hpp"
|
||||||
#include "gameboy.hpp"
|
#include "gameboy.hpp"
|
||||||
|
#include <filesystem>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
namespace fs = std::filesystem;
|
namespace fs = std::filesystem;
|
||||||
using json = nlohmann::json;
|
using json = nlohmann::json;
|
||||||
|
|
||||||
void runJSONTests(GameBoy* gb);
|
void runJSONTests(GameBoy *gb);
|
||||||
|
|
||||||
int main(int argc, char** argv) {
|
int main(int argc, char **argv) {
|
||||||
if (argc != 3) {
|
if (argc != 3) {
|
||||||
std::cerr << "Usage: " << argv[0] << " <bios> <game>\n" << std::endl;
|
std::cerr << "Usage: " << argv[0] << " <bios> <game>\n" << std::endl;
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto* gb = new GameBoy();
|
auto *gb = new GameBoy();
|
||||||
gb->SDL2setup();
|
gb->SDL2setup();
|
||||||
//runJSONTests(gb);
|
// runJSONTests(gb);
|
||||||
gb->start(argv[1], argv[2]);
|
gb->start(argv[1], argv[2]);
|
||||||
gb->SDL2destroy();
|
gb->SDL2destroy();
|
||||||
delete gb;
|
delete gb;
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void runJSONTests(GameBoy* gb) {
|
void runJSONTests(GameBoy *gb) {
|
||||||
std::string path = "../tests/sm83/v1";
|
std::string path = "../tests/sm83/v1";
|
||||||
std::vector<std::string> testFiles;
|
std::vector<std::string> testFiles;
|
||||||
int failed = 0;
|
int failed = 0;
|
||||||
for (const auto& entry : fs::directory_iterator(path))
|
for (const auto &entry : fs::directory_iterator(path))
|
||||||
testFiles.emplace_back(entry.path());
|
testFiles.emplace_back(entry.path());
|
||||||
|
|
||||||
|
for (const auto &testFile : testFiles) {
|
||||||
|
std::ifstream file(testFile);
|
||||||
|
std::cout << "Running test: " << testFile << std::endl;
|
||||||
|
const json tests = json::parse(file);
|
||||||
|
|
||||||
for (const auto& testFile : testFiles) {
|
for (auto &test : tests) {
|
||||||
std::ifstream file(testFile);
|
// create state
|
||||||
std::cout << "Running test: " << testFile << std::endl;
|
std::vector<std::tuple<Word, Byte>> initialRAM;
|
||||||
const json tests = json::parse(file);
|
for (int i = 0; i < test["initial"]["ram"].size(); i++)
|
||||||
|
initialRAM.emplace_back(test["initial"]["ram"][i][0],
|
||||||
|
test["initial"]["ram"][i][1]);
|
||||||
|
|
||||||
for (auto& test : tests) {
|
GameboyTestState initialState = {test["initial"]["pc"],
|
||||||
//create state
|
test["initial"]["sp"],
|
||||||
std::vector<std::tuple<Word, Byte>> initialRAM;
|
test["initial"]["a"],
|
||||||
for (int i = 0; i < test["initial"]["ram"].size(); i++)
|
test["initial"]["f"],
|
||||||
initialRAM.emplace_back(test["initial"]["ram"][i][0], test["initial"]["ram"][i][1]);
|
test["initial"]["b"],
|
||||||
|
test["initial"]["c"],
|
||||||
|
test["initial"]["d"],
|
||||||
|
test["initial"]["e"],
|
||||||
|
test["initial"]["h"],
|
||||||
|
test["initial"]["l"],
|
||||||
|
initialRAM};
|
||||||
|
|
||||||
GameboyTestState initialState = {
|
// run
|
||||||
test["initial"]["pc"],
|
GameboyTestState result = gb->runTest(initialState);
|
||||||
test["initial"]["sp"],
|
|
||||||
test["initial"]["a"],
|
|
||||||
test["initial"]["f"],
|
|
||||||
test["initial"]["b"],
|
|
||||||
test["initial"]["c"],
|
|
||||||
test["initial"]["d"],
|
|
||||||
test["initial"]["e"],
|
|
||||||
test["initial"]["h"],
|
|
||||||
test["initial"]["l"],
|
|
||||||
initialRAM
|
|
||||||
};
|
|
||||||
|
|
||||||
//run
|
// compare new state to expected
|
||||||
GameboyTestState result = gb->runTest(initialState);
|
std::vector<std::tuple<Word, Byte>> finalRAM;
|
||||||
|
for (int i = 0; i < test["final"]["ram"].size(); i++)
|
||||||
|
finalRAM.emplace_back(test["final"]["ram"][i][0],
|
||||||
|
test["final"]["ram"][i][1]);
|
||||||
|
|
||||||
//compare new state to expected
|
GameboyTestState finalState = {test["final"]["pc"],
|
||||||
std::vector<std::tuple<Word, Byte>> finalRAM;
|
test["final"]["sp"],
|
||||||
for (int i = 0; i < test["final"]["ram"].size(); i++)
|
test["final"]["a"],
|
||||||
finalRAM.emplace_back(test["final"]["ram"][i][0], test["final"]["ram"][i][1]);
|
test["final"]["f"],
|
||||||
|
test["final"]["b"],
|
||||||
|
test["final"]["c"],
|
||||||
|
test["final"]["d"],
|
||||||
|
test["final"]["e"],
|
||||||
|
test["final"]["h"],
|
||||||
|
test["final"]["l"],
|
||||||
|
finalRAM};
|
||||||
|
|
||||||
GameboyTestState finalState = {
|
if (finalState != result) {
|
||||||
test["final"]["pc"],
|
std::cout << "Test " << testFile << " failed!" << std::endl;
|
||||||
test["final"]["sp"],
|
failed += 1;
|
||||||
test["final"]["a"],
|
break;
|
||||||
test["final"]["f"],
|
}
|
||||||
test["final"]["b"],
|
}
|
||||||
test["final"]["c"],
|
}
|
||||||
test["final"]["d"],
|
if (!failed)
|
||||||
test["final"]["e"],
|
std::cout << "Success!" << std::endl;
|
||||||
test["final"]["h"],
|
else
|
||||||
test["final"]["l"],
|
std::cout << failed << "/" << testFiles.size() << " failed!" << std::endl;
|
||||||
finalRAM
|
|
||||||
};
|
|
||||||
|
|
||||||
if (finalState != result) {
|
|
||||||
std::cout << "Test " << testFile << " failed!" << std::endl;
|
|
||||||
failed += 1;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!failed)
|
|
||||||
std::cout << "Success!" << std::endl;
|
|
||||||
else
|
|
||||||
std::cout << failed << "/" << testFiles.size() << " failed!" << std::endl;
|
|
||||||
}
|
}
|
||||||
|
|||||||
271
src/mbc.cpp
271
src/mbc.cpp
@@ -1,135 +1,182 @@
|
|||||||
#include "addressSpace.hpp"
|
#include "addressSpace.hpp"
|
||||||
|
#include "defines.hpp"
|
||||||
|
|
||||||
void AddressSpace::determineMBCInfo() {
|
void AddressSpace::determineMBCInfo() {
|
||||||
MBC = static_cast<MBCType>(memoryLayout.romBank0[0x147]);
|
MBC = static_cast<MBCType_enum>(memoryLayout.romBank0[0x147]);
|
||||||
romSize = 32768 * (1 << memoryLayout.romBank0[0x147]);
|
romSize = 32768 * (1 << memoryLayout.romBank0[0x147]);
|
||||||
romBanks = 1 << (memoryLayout.romBank0[0x147] + 1);
|
romBanks = 1 << (memoryLayout.romBank0[0x147] + 1);
|
||||||
|
|
||||||
const Byte ramSize = memoryLayout.romBank0[0x0149];
|
const Byte ramSize = memoryLayout.romBank0[0x0149];
|
||||||
switch (ramSize) {
|
switch (ramSize) {
|
||||||
case 0x02:
|
case 0x02:
|
||||||
externalRamSize = 8196;
|
externalRamSize = 8196;
|
||||||
externalRamBanks = 1;
|
externalRamBanks = 1;
|
||||||
break;
|
break;
|
||||||
case 0x03:
|
case 0x03:
|
||||||
externalRamSize = 32768;
|
externalRamSize = 32768;
|
||||||
externalRamBanks = 4;
|
externalRamBanks = 4;
|
||||||
break;
|
break;
|
||||||
case 0x04:
|
case 0x04:
|
||||||
externalRamSize = 131072;
|
externalRamSize = 131072;
|
||||||
externalRamBanks = 16;
|
externalRamBanks = 16;
|
||||||
break;
|
break;
|
||||||
case 0x05:
|
case 0x05:
|
||||||
externalRamSize = 65536;
|
externalRamSize = 65536;
|
||||||
externalRamBanks = 8;
|
externalRamBanks = 8;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
externalRamSize = 0;
|
externalRamSize = 0;
|
||||||
externalRamBanks = 0;
|
externalRamBanks = 0;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (MBC == MBC2 || MBC == MBC2Battery) {
|
if (MBC == MBC2 || MBC == MBC2Battery) {
|
||||||
//only the lower 4 bits are usable
|
// only the lower 4 bits are usable
|
||||||
externalRamSize = 512;
|
externalRamSize = 512;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool AddressSpace::testMBCWrite(const Word address) {
|
Byte *AddressSpace::MBCWrite(const Word address) {
|
||||||
if (address <= 0x7FFF)
|
if (MBC == MBC1) {
|
||||||
return true;
|
if (address <= 0x1FFF)
|
||||||
return false;
|
return &ramEnable;
|
||||||
}
|
if (address <= 0x3FFF)
|
||||||
|
return &romBankRegister;
|
||||||
Byte* AddressSpace::MBCRead(const Word address) {
|
if (address <= 0x5FFF)
|
||||||
if (MBC == MBC1) {
|
return &twoBitBankRegister;
|
||||||
if (address <= 0x1FFF)
|
if (address <= 0x7FFF)
|
||||||
return &ramEnable;
|
return &romRamSelect;
|
||||||
if (address <= 0x3FFF)
|
} else if (MBC == MBC1Ram || MBC == MBC1RamBattery) {
|
||||||
return &romBankRegister;
|
if (address <= 0x1FFF)
|
||||||
if (address <= 0x5FFF)
|
return &ramEnable;
|
||||||
return &twoBitBankRegister;
|
// bits 0-4
|
||||||
if (address <= 0x7FFF)
|
if (address <= 0x3FFF)
|
||||||
return &romRamSelect;
|
return &romBankRegister;
|
||||||
}
|
if (address <= 0x5FFF) {
|
||||||
if (MBC == MBC1Ram || MBC == MBC1RamBattery) {
|
return &twoBitBankRegister;
|
||||||
if (address <= 0x1FFF)
|
}
|
||||||
return &ramEnable;
|
if (address <= 0x7FFF)
|
||||||
//bits 0-4
|
return &romRamSelect;
|
||||||
if (address <= 0x3FFF)
|
} else if (MBC == MBC2 || MBC == MBC2Battery) {
|
||||||
return &romBankRegister;
|
if (address <= 0x3FFF) {
|
||||||
if (address <= 0x5FFF) {
|
if (address & 0x0100)
|
||||||
return &twoBitBankRegister;
|
return &romBankRegister;
|
||||||
}
|
else
|
||||||
if (address <= 0x7FFF)
|
return &ramEnable;
|
||||||
return &romRamSelect;
|
}
|
||||||
}
|
} else if (MBC == MBC3 || MBC == MBC3TimerBattery) {
|
||||||
return &dummyVal;
|
if (address <= 0x1FFF)
|
||||||
|
return &ramEnable;
|
||||||
|
} else if (MBC == MBC3Ram || MBC == MBC3RamBattery ||
|
||||||
|
MBC == MBC3TimerRamBattery) {
|
||||||
|
if (address <= 0x1FFF)
|
||||||
|
return &ramEnable;
|
||||||
|
if (address <= 0x3FFF)
|
||||||
|
return &romBankRegister;
|
||||||
|
if (address <= 0x5FFF)
|
||||||
|
return &ramBankRTCRegister;
|
||||||
|
}
|
||||||
|
return &dummyVal;
|
||||||
}
|
}
|
||||||
|
|
||||||
void AddressSpace::MBCUpdate() {
|
void AddressSpace::MBCUpdate() {
|
||||||
//TODO: multicart roms need to be able to switch the first rom bank as well
|
// TODO: multicart roms need to be able to switch the first rom bank as well
|
||||||
//see: https://gbdev.io/pandocs/MBC1.html
|
// see: https://gbdev.io/pandocs/MBC1.html
|
||||||
if (MBC == MBC1) {
|
if (MBC == MBC1) {
|
||||||
//Selected ROM Bank = (Secondary Bank << 5) + ROM Bank
|
// Selected ROM Bank = (Secondary Bank << 5) + ROM Bank
|
||||||
romBankRegister &= 0x1F;
|
romBankRegister &= 0b11111;
|
||||||
twoBitBankRegister &= 0x3;
|
twoBitBankRegister &= 0b11;
|
||||||
|
|
||||||
//512 KiB can only have 8KiB of ram
|
// 512 KiB can only have 8KiB of ram
|
||||||
if (romSize >= 524288) {
|
if (romSize >= 524288) {
|
||||||
if (romBankRegister == 0)
|
if (romBankRegister == 0)
|
||||||
selectedRomBank = (twoBitBankRegister << 5) + 1;
|
selectedRomBank = (twoBitBankRegister << 5) + 1;
|
||||||
selectedRomBank = (twoBitBankRegister << 5) + romBankRegister;
|
selectedRomBank = (twoBitBankRegister << 5) + romBankRegister;
|
||||||
}
|
} else {
|
||||||
else {
|
if (romBankRegister == 0)
|
||||||
if (romBankRegister == 0)
|
selectedRomBank = 1;
|
||||||
selectedRomBank = 1;
|
else
|
||||||
else
|
selectedRomBank = romBankRegister;
|
||||||
selectedRomBank = romBankRegister;
|
}
|
||||||
}
|
if (romBankRegister == 0x20 || romBankRegister == 0x40 ||
|
||||||
selectedExternalRamBank = 0;
|
romBankRegister == 0x60)
|
||||||
|
romBankRegister += 1;
|
||||||
|
selectedExternalRamBank = 0;
|
||||||
|
|
||||||
loadRomBank();
|
loadRomBank();
|
||||||
loadRamBank();
|
loadRamBank();
|
||||||
}
|
} else if (MBC == MBC1Ram || MBC == MBC1RamBattery) {
|
||||||
if (MBC == MBC1Ram || MBC == MBC1RamBattery) {
|
// Selected ROM Bank = (Secondary Bank << 5) + ROM Bank
|
||||||
//Selected ROM Bank = (Secondary Bank << 5) + ROM Bank
|
romBankRegister &= 0b11111;
|
||||||
romBankRegister &= 0x1F;
|
twoBitBankRegister &= 0b11;
|
||||||
twoBitBankRegister &= 0x3;
|
|
||||||
|
|
||||||
//512 KiB can only have 8KiB of ram
|
// 512 KiB can only have 8KiB of ram
|
||||||
if (romSize >= 524288) {
|
if (romSize >= 524288) {
|
||||||
if (romBankRegister == 0)
|
if (romBankRegister == 0)
|
||||||
selectedRomBank = (twoBitBankRegister << 5) + 1;
|
selectedRomBank = (twoBitBankRegister << 5) + 1;
|
||||||
selectedRomBank = (twoBitBankRegister << 5) + romBankRegister;
|
selectedRomBank = (twoBitBankRegister << 5) + romBankRegister;
|
||||||
selectedExternalRamBank = 0;
|
selectedExternalRamBank = 0;
|
||||||
}
|
} else {
|
||||||
else {
|
if (romBankRegister == 0)
|
||||||
if (romBankRegister == 0)
|
selectedRomBank = 1;
|
||||||
selectedRomBank = 1;
|
else
|
||||||
else
|
selectedRomBank = romBankRegister;
|
||||||
selectedRomBank = romBankRegister;
|
selectedExternalRamBank = twoBitBankRegister;
|
||||||
selectedExternalRamBank = twoBitBankRegister;
|
}
|
||||||
}
|
if (romBankRegister == 0x20 || romBankRegister == 0x40 ||
|
||||||
loadRomBank();
|
romBankRegister == 0x60)
|
||||||
loadRamBank();
|
romBankRegister += 1;
|
||||||
}
|
|
||||||
|
loadRomBank();
|
||||||
|
loadRamBank();
|
||||||
|
} else if (MBC == MBC2 || MBC == MBC2Battery) {
|
||||||
|
selectedRomBank = romBankRegister & 0x0F;
|
||||||
|
selectedExternalRamBank = 0;
|
||||||
|
|
||||||
|
loadRomBank();
|
||||||
|
loadRamBank();
|
||||||
|
} else if (MBC == MBC3 || MBC == MBC3TimerBattery) {
|
||||||
|
selectedRomBank = romBankRegister & 0x7F;
|
||||||
|
selectedExternalRamBank = 0;
|
||||||
|
|
||||||
|
if (MBC == MBC3TimerBattery) {
|
||||||
|
if(ramBankRTCRegister >= 0x8 && ramBankRTCRegister <= 0xC)
|
||||||
|
enabledRTCRegister = static_cast<enabledRTCRegister_enum>(ramBankRTCRegister);
|
||||||
|
else
|
||||||
|
enabledRTCRegister = NONE;
|
||||||
|
}
|
||||||
|
loadRomBank();
|
||||||
|
loadRamBank();
|
||||||
|
} else if (MBC == MBC3Ram || MBC == MBC3RamBattery ||
|
||||||
|
MBC == MBC3TimerRamBattery) {
|
||||||
|
selectedRomBank = romBankRegister & 0x7F;
|
||||||
|
selectedExternalRamBank = ramBankRTCRegister & 0b11;
|
||||||
|
|
||||||
|
if (MBC == MBC3TimerRamBattery) {
|
||||||
|
if(ramBankRTCRegister >= 0x8 && ramBankRTCRegister <= 0xC)
|
||||||
|
enabledRTCRegister = static_cast<enabledRTCRegister_enum>(ramBankRTCRegister);
|
||||||
|
else
|
||||||
|
enabledRTCRegister = NONE;
|
||||||
|
}
|
||||||
|
loadRomBank();
|
||||||
|
loadRamBank();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void AddressSpace::loadRomBank() {
|
void AddressSpace::loadRomBank() {
|
||||||
memoryLayout.romBankSwitch = game.data() + (ROM_BANK_SIZE * selectedRomBank);
|
memoryLayout.romBankSwitch = game.data() + (ROM_BANK_SIZE * selectedRomBank);
|
||||||
}
|
}
|
||||||
|
|
||||||
void AddressSpace::createRamBank() {
|
void AddressSpace::createRamBank() {
|
||||||
if (externalRamSize) {
|
if (externalRamSize) {
|
||||||
cartridgeRam = new Byte[externalRamSize];
|
cartridgeRam = new Byte[externalRamSize];
|
||||||
memoryLayout.externalRam = cartridgeRam;
|
memoryLayout.externalRam = cartridgeRam;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void AddressSpace::loadRamBank() {
|
void AddressSpace::loadRamBank() {
|
||||||
if (cartridgeRam != nullptr)
|
if (cartridgeRam != nullptr) {
|
||||||
memoryLayout.externalRam = cartridgeRam + (RAM_BANK_SIZE * selectedExternalRamBank);
|
memoryLayout.externalRam =
|
||||||
|
cartridgeRam + (RAM_BANK_SIZE * selectedExternalRamBank);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
619
src/ppu.cpp
619
src/ppu.cpp
@@ -1,390 +1,409 @@
|
|||||||
#include "gameboy.hpp"
|
|
||||||
#include "defines.hpp"
|
#include "defines.hpp"
|
||||||
|
#include "gameboy.hpp"
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
bool GameBoy::LCDCBitEnabled(const Byte bit) const {
|
bool GameBoy::LCDCBitEnabled(const Byte bit) const {
|
||||||
return readOnlyAddressSpace.memoryLayout.LCDC & static_cast<Byte>(1 << bit);
|
return readOnlyAddressSpace.memoryLayout.LCDC & static_cast<Byte>(1 << bit);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool GameBoy::oamBitEnabled(const Byte oamAttributeByte, const Byte bit) {
|
bool GameBoy::oamBitEnabled(const Byte oamAttributeByte, const Byte bit) {
|
||||||
return oamAttributeByte & static_cast<Byte>(1 << bit);
|
return oamAttributeByte & static_cast<Byte>(1 << bit);
|
||||||
}
|
}
|
||||||
|
|
||||||
unsigned int GameBoy::getColourFromPalette(const Byte palette) {
|
unsigned int GameBoy::getColourFromPalette(const Byte palette) {
|
||||||
switch (palette & 0x3) {
|
switch (palette & 0x3) {
|
||||||
case 0:
|
case 0:
|
||||||
return WHITE;
|
return WHITE;
|
||||||
case 1:
|
case 1:
|
||||||
return LIGHT_GRAY;
|
return LIGHT_GRAY;
|
||||||
case 2:
|
case 2:
|
||||||
return DARK_GRAY;
|
return DARK_GRAY;
|
||||||
case 3:
|
case 3:
|
||||||
return BLACK;
|
return BLACK;
|
||||||
default:
|
default:
|
||||||
std::unreachable();
|
std::unreachable();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void GameBoy::ppuUpdate() {
|
void GameBoy::ppuUpdate() {
|
||||||
//test for HBlank
|
// test for HBlank
|
||||||
checkPPUMode();
|
checkPPUMode();
|
||||||
|
|
||||||
// TODO:
|
// TODO:
|
||||||
// bug on DMG models triggers a STAT interrupt anytime the STAT register is written
|
// bug on DMG models triggers a STAT interrupt anytime the STAT register is
|
||||||
// Road Rage and Zerd no Denetsu rely on this
|
// written Road Rage and Zerd no Denetsu rely on this
|
||||||
|
|
||||||
// Check for STAT interrupts and request if needed (e.g., when entering specific modes)
|
// Check for STAT interrupts and request if needed (e.g., when entering
|
||||||
const bool hBlankInterruptEnabled = readOnlyAddressSpace.memoryLayout.STAT & (1 << 3);
|
// specific modes)
|
||||||
const bool drawingInterruptEnabled = readOnlyAddressSpace.memoryLayout.STAT & (1 << 4);
|
const bool hBlankInterruptEnabled =
|
||||||
const bool oamInterruptEnabled = readOnlyAddressSpace.memoryLayout.STAT & (1 << 5);
|
readOnlyAddressSpace.memoryLayout.STAT & (1 << 3);
|
||||||
const bool previousInterruptLine = statInteruptLine;
|
const bool drawingInterruptEnabled =
|
||||||
|
readOnlyAddressSpace.memoryLayout.STAT & (1 << 4);
|
||||||
|
const bool oamInterruptEnabled =
|
||||||
|
readOnlyAddressSpace.memoryLayout.STAT & (1 << 5);
|
||||||
|
const bool previousInterruptLine = statInteruptLine;
|
||||||
|
|
||||||
if (currentMode == PPUMode::mode0 && hBlankInterruptEnabled ||
|
if (currentMode == PPUMode_enum::mode0 && hBlankInterruptEnabled ||
|
||||||
currentMode == PPUMode::mode3 && drawingInterruptEnabled ||
|
currentMode == PPUMode_enum::mode3 && drawingInterruptEnabled ||
|
||||||
currentMode == PPUMode::mode2 && oamInterruptEnabled) {
|
currentMode == PPUMode_enum::mode2 && oamInterruptEnabled) {
|
||||||
statInteruptLine = true;
|
statInteruptLine = true;
|
||||||
}
|
} else {
|
||||||
else {
|
statInteruptLine = false;
|
||||||
statInteruptLine = false;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
const bool lyLycInterruptEnabled = readOnlyAddressSpace.memoryLayout.STAT & (1 << 6);
|
const bool lyLycInterruptEnabled =
|
||||||
|
readOnlyAddressSpace.memoryLayout.STAT & (1 << 6);
|
||||||
|
|
||||||
if (readOnlyAddressSpace.memoryLayout.LY == readOnlyAddressSpace.memoryLayout.LYC) {
|
if (readOnlyAddressSpace.memoryLayout.LY ==
|
||||||
addressSpace.memoryLayout.STAT |= (1 << 2);
|
readOnlyAddressSpace.memoryLayout.LYC) {
|
||||||
if (lyLycInterruptEnabled)
|
addressSpace.memoryLayout.STAT |= (1 << 2);
|
||||||
statInteruptLine = true;
|
if (lyLycInterruptEnabled)
|
||||||
}
|
statInteruptLine = true;
|
||||||
else {
|
} else {
|
||||||
addressSpace.memoryLayout.STAT &= ~(1 << 2);
|
addressSpace.memoryLayout.STAT &= ~(1 << 2);
|
||||||
}
|
}
|
||||||
if (statInteruptLine && !previousInterruptLine)
|
if (statInteruptLine && !previousInterruptLine)
|
||||||
addressSpace.memoryLayout.IF |= 1 << LCD_STAT_INTERRUPT;
|
addressSpace.memoryLayout.IF |= 1 << LCD_STAT_INTERRUPT;
|
||||||
}
|
}
|
||||||
|
|
||||||
void GameBoy::checkPPUMode() {
|
void GameBoy::checkPPUMode() {
|
||||||
// Check the PPU mode (HBlank, VBlank, OAM Search, or Pixel Transfer)
|
// Check the PPU mode (HBlank, VBlank, OAM Search, or Pixel Transfer)
|
||||||
const uint64_t cyclesSinceScanline = cyclesSinceLastScanline();
|
const uint64_t cyclesSinceScanline = cyclesSinceLastScanline();
|
||||||
|
|
||||||
switch (currentMode) {
|
switch (currentMode) {
|
||||||
//hblank AND vblank
|
// hblank AND vblank
|
||||||
case 0:
|
case 0:
|
||||||
case 1:
|
case 1:
|
||||||
if (cyclesSinceScanline > SCANLINE_DURATION) {
|
if (cyclesSinceScanline > SCANLINE_DURATION) {
|
||||||
lastScanline = ppuCycles - (cyclesSinceScanline - SCANLINE_DURATION);
|
lastScanline = ppuCycles - (cyclesSinceScanline - SCANLINE_DURATION);
|
||||||
incLY();
|
incLY();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 2:
|
case 2:
|
||||||
if (cyclesSinceScanline > MODE2_DURATION) {
|
if (cyclesSinceScanline > MODE2_DURATION) {
|
||||||
setPPUMode(PPUMode::mode3);
|
setPPUMode(PPUMode_enum::mode3);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 3:
|
case 3:
|
||||||
if (cyclesSinceScanline > MODE2_DURATION + MODE3_MIN_DURATION) {
|
if (cyclesSinceScanline > MODE2_DURATION + MODE3_MIN_DURATION) {
|
||||||
drawLine();
|
drawLine();
|
||||||
setPPUMode(PPUMode::mode0);
|
setPPUMode(PPUMode_enum::mode0);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void GameBoy::incLY() {
|
void GameBoy::incLY() {
|
||||||
addressSpace.memoryLayout.LY += 1;
|
addressSpace.memoryLayout.LY += 1;
|
||||||
setPPUMode(PPUMode::mode2);
|
setPPUMode(PPUMode_enum::mode2);
|
||||||
if (addressSpace.memoryLayout.LY > SCANLINES_PER_FRAME - 1) {
|
if (addressSpace.memoryLayout.LY > SCANLINES_PER_FRAME - 1) {
|
||||||
addressSpace.memoryLayout.LY = 0;
|
addressSpace.memoryLayout.LY = 0;
|
||||||
windowLineCounter = 0;
|
windowLineCounter = 0;
|
||||||
}
|
} else if (addressSpace.memoryLayout.LY == 144) {
|
||||||
else if (addressSpace.memoryLayout.LY == 144) {
|
// VBlank Period
|
||||||
// VBlank Period
|
SDL2present();
|
||||||
SDL2present();
|
setPPUMode(PPUMode_enum::mode1);
|
||||||
setPPUMode(PPUMode::mode1);
|
addressSpace.memoryLayout.IF |= 0x1;
|
||||||
addressSpace.memoryLayout.IF |= 0x1;
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
uint64_t GameBoy::cyclesSinceLastScanline() const {
|
uint64_t GameBoy::cyclesSinceLastScanline() const {
|
||||||
const uint64_t difference = ppuCycles - lastScanline;
|
const uint64_t difference = ppuCycles - lastScanline;
|
||||||
return difference;
|
return difference;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint64_t GameBoy::cyclesSinceLastRefresh() const {
|
uint64_t GameBoy::cyclesSinceLastRefresh() const {
|
||||||
const uint64_t difference = ppuCycles - lastRefresh;
|
const uint64_t difference = ppuCycles - lastRefresh;
|
||||||
return difference;
|
return difference;
|
||||||
}
|
}
|
||||||
|
|
||||||
void GameBoy::setPPUMode(const PPUMode mode) {
|
void GameBoy::setPPUMode(const PPUMode_enum mode) {
|
||||||
switch (mode) {
|
switch (mode) {
|
||||||
case mode0:
|
case mode0:
|
||||||
addressSpace.memoryLayout.STAT &= ~0x03;
|
addressSpace.memoryLayout.STAT &= ~0x03;
|
||||||
currentMode = mode0;
|
currentMode = mode0;
|
||||||
break;
|
break;
|
||||||
case mode1:
|
case mode1:
|
||||||
addressSpace.memoryLayout.STAT &= ~0x03;
|
addressSpace.memoryLayout.STAT &= ~0x03;
|
||||||
addressSpace.memoryLayout.STAT |= 0x01;
|
addressSpace.memoryLayout.STAT |= 0x01;
|
||||||
currentMode = mode1;
|
currentMode = mode1;
|
||||||
break;
|
break;
|
||||||
case mode2:
|
case mode2:
|
||||||
addressSpace.memoryLayout.STAT &= ~0x03;
|
addressSpace.memoryLayout.STAT &= ~0x03;
|
||||||
addressSpace.memoryLayout.STAT |= 0x02;
|
addressSpace.memoryLayout.STAT |= 0x02;
|
||||||
currentMode = mode2;
|
currentMode = mode2;
|
||||||
break;
|
break;
|
||||||
case mode3:
|
case mode3:
|
||||||
addressSpace.memoryLayout.STAT &= ~0x03;
|
addressSpace.memoryLayout.STAT &= ~0x03;
|
||||||
addressSpace.memoryLayout.STAT |= 0x03;
|
addressSpace.memoryLayout.STAT |= 0x03;
|
||||||
currentMode = mode3;
|
currentMode = mode3;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
//7th bit is unused but always set
|
// 7th bit is unused but always set
|
||||||
addressSpace.memoryLayout.STAT |= 0x80;
|
addressSpace.memoryLayout.STAT |= 0x80;
|
||||||
}
|
}
|
||||||
|
|
||||||
void GameBoy::drawLine() {
|
void GameBoy::drawLine() {
|
||||||
const uint8_t line = readOnlyAddressSpace.memoryLayout.LY;
|
const uint8_t line = readOnlyAddressSpace.memoryLayout.LY;
|
||||||
|
|
||||||
// Calculate the starting index of the current scanline in the framebuffer
|
// Calculate the starting index of the current scanline in the framebuffer
|
||||||
const uint32_t lineStartIndex = line * RESOLUTION_X;
|
const uint32_t lineStartIndex = line * RESOLUTION_X;
|
||||||
|
|
||||||
// Pointer to the current line's pixel data in the framebuffer
|
// Pointer to the current line's pixel data in the framebuffer
|
||||||
uint32_t* currentLinePixels = framebuffer + lineStartIndex;
|
uint32_t *currentLinePixels = framebuffer + lineStartIndex;
|
||||||
std::fill_n(currentLinePixels, RESOLUTION_X, 0xFFFFFFFF);
|
std::fill_n(currentLinePixels, RESOLUTION_X, 0xFFFFFFFF);
|
||||||
|
|
||||||
|
const uint16_t backgroundMapAddr =
|
||||||
|
LCDCBitEnabled(BG_TILE_MAP_AREA) ? 0x9C00 : 0x9800;
|
||||||
|
const uint16_t windowMapAddr =
|
||||||
|
LCDCBitEnabled(WINDOW_TILE_MAP_AREA) ? 0x9C00 : 0x9800;
|
||||||
|
const uint16_t tileDataTableAddr =
|
||||||
|
LCDCBitEnabled(BG_WINDOW_TILE_DATA_AREA) ? 0x8000 : 0x8800;
|
||||||
|
const bool signedIndex = !LCDCBitEnabled(BG_WINDOW_TILE_DATA_AREA);
|
||||||
|
|
||||||
const uint16_t backgroundMapAddr = LCDCBitEnabled(BG_TILE_MAP_AREA) ? 0x9C00 : 0x9800;
|
// BG
|
||||||
const uint16_t windowMapAddr = LCDCBitEnabled(WINDOW_TILE_MAP_AREA) ? 0x9C00 : 0x9800;
|
if (LCDCBitEnabled(BG_WINDOW_ENABLE)) {
|
||||||
const uint16_t tileDataTableAddr = LCDCBitEnabled(BG_WINDOW_TILE_DATA_AREA) ? 0x8000 : 0x8800;
|
for (int pixel = 0; pixel < RESOLUTION_X; pixel++) {
|
||||||
const bool signedIndex = !LCDCBitEnabled(BG_WINDOW_TILE_DATA_AREA);
|
const uint16_t xIndex =
|
||||||
|
(pixel + readOnlyAddressSpace.memoryLayout.SCX) % 256;
|
||||||
|
// 256 pixels in total BG width
|
||||||
|
const uint16_t yIndex =
|
||||||
|
(line + readOnlyAddressSpace.memoryLayout.SCY) % 256;
|
||||||
|
// 256 pixels in total BG height
|
||||||
|
|
||||||
//BG
|
const uint16_t tileUpper = (yIndex / 8) << 5;
|
||||||
if (LCDCBitEnabled(BG_WINDOW_ENABLE)) {
|
const uint16_t tileLower = xIndex / 8 & 0x1F;
|
||||||
for (int pixel = 0; pixel < RESOLUTION_X; pixel++) {
|
const uint16_t tileIndex = tileUpper + tileLower;
|
||||||
const uint16_t xIndex = (pixel + readOnlyAddressSpace.memoryLayout.SCX) % 256;
|
|
||||||
// 256 pixels in total BG width
|
|
||||||
const uint16_t yIndex = (line + readOnlyAddressSpace.memoryLayout.SCY) % 256;
|
|
||||||
// 256 pixels in total BG height
|
|
||||||
|
|
||||||
const uint16_t tileUpper = (yIndex / 8) << 5;
|
const uint16_t tileAddr = backgroundMapAddr + tileIndex;
|
||||||
const uint16_t tileLower = xIndex / 8 & 0x1F;
|
const int16_t tileID =
|
||||||
const uint16_t tileIndex = tileUpper + tileLower;
|
signedIndex ? static_cast<int16_t>(readOnlyAddressSpace[tileAddr])
|
||||||
|
: readOnlyAddressSpace[tileAddr];
|
||||||
|
|
||||||
const uint16_t tileAddr = backgroundMapAddr + tileIndex;
|
uint16_t tileDataAddr;
|
||||||
const int16_t tileID = signedIndex
|
if (signedIndex)
|
||||||
? static_cast<int16_t>(readOnlyAddressSpace[tileAddr])
|
tileDataAddr = tileDataTableAddr +
|
||||||
: readOnlyAddressSpace[tileAddr];
|
(((tileID + 128) % 256) << 4); // For signed, wrap around
|
||||||
|
else
|
||||||
|
tileDataAddr = tileDataTableAddr + (tileID * 16);
|
||||||
|
|
||||||
uint16_t tileDataAddr;
|
const uint8_t lineOffset = yIndex % 8;
|
||||||
if (signedIndex)
|
const uint8_t tileRowData1 =
|
||||||
tileDataAddr = tileDataTableAddr + (((tileID + 128) % 256) << 4); // For signed, wrap around
|
readOnlyAddressSpace[tileDataAddr + (lineOffset * 2)];
|
||||||
else
|
const uint8_t tileRowData2 =
|
||||||
tileDataAddr = tileDataTableAddr + (tileID * 16);
|
readOnlyAddressSpace[tileDataAddr + (lineOffset * 2) + 1];
|
||||||
|
|
||||||
|
// get pixel data (2 bits)
|
||||||
|
const uint8_t colourBit = 7 - (xIndex % 8);
|
||||||
|
const uint8_t colourNum = ((tileRowData2 >> colourBit) & 0x1) << 1 |
|
||||||
|
((tileRowData1 >> colourBit) & 0x1);
|
||||||
|
|
||||||
const uint8_t lineOffset = yIndex % 8;
|
// Apply the BGP register for palette mapping
|
||||||
const uint8_t tileRowData1 = readOnlyAddressSpace[tileDataAddr + (lineOffset * 2)];
|
const uint8_t palette =
|
||||||
const uint8_t tileRowData2 = readOnlyAddressSpace[tileDataAddr + (lineOffset * 2) + 1];
|
(readOnlyAddressSpace.memoryLayout.BGP >> (colourNum * 2)) & 0x3;
|
||||||
|
|
||||||
//get pixel data (2 bits)
|
currentLinePixels[pixel] = getColourFromPalette(palette);
|
||||||
const uint8_t colourBit = 7 - (xIndex % 8);
|
}
|
||||||
const uint8_t colourNum = ((tileRowData2 >> colourBit) & 0x1) << 1 | ((tileRowData1 >> colourBit) & 0x1);
|
|
||||||
|
|
||||||
// Apply the BGP register for palette mapping
|
// For the window to be displayed on a scanline, the following conditions
|
||||||
const uint8_t palette = (readOnlyAddressSpace.memoryLayout.BGP >> (colourNum * 2)) & 0x3;
|
// must be met: WY condition was triggered: i.e. at some point in this frame
|
||||||
|
// the value of WY was equal to LY (checked at the start of Mode 2 only) WX
|
||||||
|
// condition was triggered: i.e. the current X coordinate being rendered + 7
|
||||||
|
// was equal to WX Window enable bit in LCDC is set
|
||||||
|
// Window
|
||||||
|
const uint8_t windowY = readOnlyAddressSpace.memoryLayout.WY;
|
||||||
|
const int16_t windowX =
|
||||||
|
static_cast<int16_t>(readOnlyAddressSpace.memoryLayout.WX - 7);
|
||||||
|
if (LCDCBitEnabled(WINDOW_ENABLE) && windowX >= 0 &&
|
||||||
|
windowX < RESOLUTION_X && line >= windowY) {
|
||||||
|
for (int pixel = windowX; pixel < RESOLUTION_X; pixel++) {
|
||||||
|
const uint16_t yIndex = windowLineCounter;
|
||||||
|
const uint16_t windowTileUpper = (yIndex / 8) << 5;
|
||||||
|
|
||||||
currentLinePixels[pixel] = getColourFromPalette(palette);
|
const uint16_t xIndex = pixel - windowX;
|
||||||
}
|
const uint16_t windowTileLower = (xIndex / 8) & 0x1F;
|
||||||
|
|
||||||
// For the window to be displayed on a scanline, the following conditions must be met:
|
const uint16_t tileIndex = windowTileUpper + windowTileLower;
|
||||||
// WY condition was triggered: i.e. at some point in this frame the value of WY was equal to LY (checked at the start of Mode 2 only)
|
const uint16_t tileAddr = windowMapAddr + tileIndex;
|
||||||
// WX condition was triggered: i.e. the current X coordinate being rendered + 7 was equal to WX
|
|
||||||
// Window enable bit in LCDC is set
|
|
||||||
//Window
|
|
||||||
const uint8_t windowY = readOnlyAddressSpace.memoryLayout.WY;
|
|
||||||
const int16_t windowX = static_cast<int16_t>(readOnlyAddressSpace.memoryLayout.WX - 7);
|
|
||||||
if (LCDCBitEnabled(WINDOW_ENABLE) && windowX >= 0 && windowX < RESOLUTION_X && line >= windowY) {
|
|
||||||
for (int pixel = windowX; pixel < RESOLUTION_X; pixel++) {
|
|
||||||
const uint16_t yIndex = windowLineCounter;
|
|
||||||
const uint16_t windowTileUpper = (yIndex / 8) << 5;
|
|
||||||
|
|
||||||
const uint16_t xIndex = pixel - windowX;
|
const int16_t tileID =
|
||||||
const uint16_t windowTileLower = (xIndex / 8) & 0x1F;
|
signedIndex ? static_cast<int16_t>(readOnlyAddressSpace[tileAddr])
|
||||||
|
: readOnlyAddressSpace[tileAddr];
|
||||||
|
|
||||||
const uint16_t tileIndex = windowTileUpper + windowTileLower;
|
uint16_t tileDataAddr;
|
||||||
const uint16_t tileAddr = windowMapAddr + tileIndex;
|
if (signedIndex)
|
||||||
|
tileDataAddr = tileDataTableAddr + (((tileID + 128) % 256) *
|
||||||
|
16); // For signed, wrap around
|
||||||
|
else
|
||||||
|
tileDataAddr = tileDataTableAddr + (tileID * 16);
|
||||||
|
|
||||||
const int16_t tileID = signedIndex
|
const uint8_t lineOffset = yIndex % 8;
|
||||||
? static_cast<int16_t>(readOnlyAddressSpace[tileAddr])
|
const uint8_t tileRowData1 =
|
||||||
: readOnlyAddressSpace[tileAddr];
|
readOnlyAddressSpace[tileDataAddr + (lineOffset * 2)];
|
||||||
|
const uint8_t tileRowData2 =
|
||||||
|
readOnlyAddressSpace[tileDataAddr + (lineOffset * 2) + 1];
|
||||||
|
|
||||||
uint16_t tileDataAddr;
|
// get pixel data (2 bits)
|
||||||
if (signedIndex)
|
const uint8_t colourBit = 7 - (xIndex % 8);
|
||||||
tileDataAddr = tileDataTableAddr + (((tileID + 128) % 256) * 16); // For signed, wrap around
|
const uint8_t colourNum = ((tileRowData2 >> colourBit) & 0x1) << 1 |
|
||||||
else
|
((tileRowData1 >> colourBit) & 0x1);
|
||||||
tileDataAddr = tileDataTableAddr + (tileID * 16);
|
|
||||||
|
|
||||||
|
// Apply the BGP register for palette mapping
|
||||||
|
const uint8_t palette =
|
||||||
|
(readOnlyAddressSpace.memoryLayout.BGP >> (colourNum * 2)) & 0x3;
|
||||||
|
|
||||||
const uint8_t lineOffset = yIndex % 8;
|
currentLinePixels[pixel] = getColourFromPalette(palette);
|
||||||
const uint8_t tileRowData1 = readOnlyAddressSpace[tileDataAddr + (lineOffset * 2)];
|
}
|
||||||
const uint8_t tileRowData2 = readOnlyAddressSpace[tileDataAddr + (lineOffset * 2) + 1];
|
windowLineCounter += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// oam/sprites
|
||||||
|
if (LCDCBitEnabled(OBJ_ENABLE)) {
|
||||||
|
uint32_t oamPixels[RESOLUTION_X];
|
||||||
|
std::fill_n(oamPixels, RESOLUTION_X, 0);
|
||||||
|
|
||||||
//get pixel data (2 bits)
|
constexpr Word oamAddrStart = 0xFE00;
|
||||||
const uint8_t colourBit = 7 - (xIndex % 8);
|
const int spriteHeight = LCDCBitEnabled(OBJ_SIZE) ? 16 : 8;
|
||||||
const uint8_t colourNum = ((tileRowData2 >> colourBit) & 0x1) << 1 | ((tileRowData1 >> colourBit) &
|
|
||||||
0x1);
|
|
||||||
|
|
||||||
// Apply the BGP register for palette mapping
|
int objects[10] = {0};
|
||||||
const uint8_t palette = (readOnlyAddressSpace.memoryLayout.BGP >> (colourNum * 2)) & 0x3;
|
int found = 0;
|
||||||
|
|
||||||
currentLinePixels[pixel] = getColourFromPalette(palette);
|
// oam hold 40 objects
|
||||||
}
|
// find first 10
|
||||||
windowLineCounter += 1;
|
for (int i = 0; i < 40 && found < 10; i++) {
|
||||||
}
|
const int offset = i * 4;
|
||||||
}
|
const int yPos = readOnlyAddressSpace[oamAddrStart + offset] - 16;
|
||||||
// oam/sprites
|
|
||||||
if (LCDCBitEnabled(OBJ_ENABLE)) {
|
|
||||||
uint32_t oamPixels[RESOLUTION_X];
|
|
||||||
std::fill_n(oamPixels, RESOLUTION_X, 0);
|
|
||||||
|
|
||||||
constexpr
|
if (line >= yPos && line <= yPos + spriteHeight - 1) {
|
||||||
Word oamAddrStart = 0xFE00;
|
objects[found] = oamAddrStart + offset;
|
||||||
const int spriteHeight = LCDCBitEnabled(OBJ_SIZE) ? 16 : 8;
|
found += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
int objects[10] = {0};
|
// sort by xPos (lower has higher priority when rendering) and then earlier
|
||||||
int found = 0;
|
// objects
|
||||||
|
for (int i = 0; i < found; i++) {
|
||||||
|
for (int j = 0; j < found - i - 1; j++) {
|
||||||
|
const int xPos1 = readOnlyAddressSpace[objects[j] + 1];
|
||||||
|
const int xPos2 = readOnlyAddressSpace[objects[j + 1] + 1];
|
||||||
|
|
||||||
//oam hold 40 objects
|
if (xPos1 > xPos2) {
|
||||||
//find first 10
|
const int tmp = objects[j];
|
||||||
for (int i = 0; i < 40 && found < 10; i++) {
|
objects[j] = objects[j + 1];
|
||||||
const int offset = i * 4;
|
objects[j + 1] = tmp;
|
||||||
const int yPos = readOnlyAddressSpace[oamAddrStart + offset] - 16;
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (line >= yPos && line <= yPos + spriteHeight - 1) {
|
for (int objectIndex = found - 1; objectIndex >= 0; objectIndex--) {
|
||||||
objects[found] = oamAddrStart + offset;
|
const int yPos = readOnlyAddressSpace[objects[objectIndex]] - 16;
|
||||||
found += 1;
|
const int xPos = readOnlyAddressSpace[objects[objectIndex] + 1] - 8;
|
||||||
}
|
const int tileIndex = readOnlyAddressSpace[objects[objectIndex] + 2];
|
||||||
}
|
const Byte attributes = readOnlyAddressSpace[objects[objectIndex] + 3];
|
||||||
|
|
||||||
//sort by xPos (lower has higher priority when rendering) and then earlier objects
|
const bool priority = oamBitEnabled(attributes, PRIORITY);
|
||||||
for (int i = 0; i < found; i++) {
|
const bool yFlip = oamBitEnabled(attributes, Y_FLIP);
|
||||||
for (int j = 0; j < found - i - 1; j++) {
|
const bool xFlip = oamBitEnabled(attributes, X_FLIP);
|
||||||
const int xPos1 = readOnlyAddressSpace[objects[j] + 1];
|
// get obp0 or obj1
|
||||||
const int xPos2 = readOnlyAddressSpace[objects[j + 1] + 1];
|
const Byte objPalette = oamBitEnabled(attributes, OBJ_PALETTE)
|
||||||
|
? addressSpace.memoryLayout.OBP1
|
||||||
|
: addressSpace.memoryLayout.OBP0;
|
||||||
|
|
||||||
if (xPos1 > xPos2) {
|
for (int pixel = xPos; pixel < RESOLUTION_X && pixel < xPos + 8;
|
||||||
const int tmp = objects[j];
|
pixel++) {
|
||||||
objects[j] = objects[j + 1];
|
constexpr Word objectTileAddr = 0x8000;
|
||||||
objects[j + 1] = tmp;
|
if (pixel < 0)
|
||||||
}
|
continue;
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
const uint32_t colour = currentLinePixels[pixel];
|
||||||
|
const Byte BGP = readOnlyAddressSpace.memoryLayout.BGP;
|
||||||
|
if (priority && (colour == getColourFromPalette((BGP >> 2) & 0x3) ||
|
||||||
|
colour == getColourFromPalette((BGP >> 4) & 0x3) ||
|
||||||
|
colour == getColourFromPalette((BGP >> 6) & 0x3))) {
|
||||||
|
oamPixels[pixel] = colour;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
for (int objectIndex = found - 1; objectIndex >= 0; objectIndex--) {
|
Byte objectX = pixel - xPos;
|
||||||
const int yPos = readOnlyAddressSpace[objects[objectIndex]] - 16;
|
Byte objectY = line - yPos;
|
||||||
const int xPos = readOnlyAddressSpace[objects[objectIndex] + 1] - 8;
|
if (xFlip)
|
||||||
const int tileIndex = readOnlyAddressSpace[objects[objectIndex] + 2];
|
objectX = 7 - objectX;
|
||||||
const Byte attributes = readOnlyAddressSpace[objects[objectIndex] + 3];
|
if (yFlip)
|
||||||
|
objectY = (spriteHeight - 1) - objectY;
|
||||||
|
|
||||||
const bool priority = oamBitEnabled(attributes, PRIORITY);
|
const Word objTileDataAddr =
|
||||||
const bool yFlip = oamBitEnabled(attributes, Y_FLIP);
|
spriteHeight == 8 ? objectTileAddr + (tileIndex * 16)
|
||||||
const bool xFlip = oamBitEnabled(attributes, X_FLIP);
|
: objectTileAddr + ((tileIndex & 0xFE) * 16);
|
||||||
//get obp0 or obj1
|
|
||||||
const Byte objPalette = oamBitEnabled(attributes, OBJ_PALETTE)
|
|
||||||
? addressSpace.memoryLayout.OBP1
|
|
||||||
: addressSpace.memoryLayout.OBP0;
|
|
||||||
|
|
||||||
for (int pixel = xPos; pixel < RESOLUTION_X && pixel < xPos + 8; pixel++) {
|
const Byte tileRow = objectY * 2;
|
||||||
constexpr
|
const Byte tileRowData1 =
|
||||||
Word objectTileAddr = 0x8000;
|
readOnlyAddressSpace[objTileDataAddr + tileRow];
|
||||||
if (pixel < 0)
|
const Byte tileRowData2 =
|
||||||
continue;
|
readOnlyAddressSpace[objTileDataAddr + tileRow + 1];
|
||||||
|
|
||||||
const uint32_t colour = currentLinePixels[pixel];
|
const int bit = 7 - objectX;
|
||||||
const Byte BGP = readOnlyAddressSpace.memoryLayout.BGP;
|
const int colorIndex =
|
||||||
if (priority && (colour == getColourFromPalette((BGP >> 2) & 0x3) ||
|
((tileRowData2 >> bit) & 1) << 1 | ((tileRowData1 >> bit) & 1);
|
||||||
colour == getColourFromPalette((BGP >> 4) & 0x3) ||
|
|
||||||
colour == getColourFromPalette((BGP >> 6) & 0x3)
|
|
||||||
)) {
|
|
||||||
oamPixels[pixel] = colour;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
Byte objectX = pixel - xPos;
|
// 0 is always transparent
|
||||||
Byte objectY = line - yPos;
|
if (colorIndex != 0) {
|
||||||
if (xFlip)
|
const uint8_t paletteColor = (objPalette >> (colorIndex * 2)) & 0x3;
|
||||||
objectX = 7 - objectX;
|
const uint32_t finalColor = getColourFromPalette(paletteColor);
|
||||||
if (yFlip)
|
oamPixels[pixel] = finalColor;
|
||||||
objectY = (spriteHeight - 1) - objectY;
|
} else if (oamPixels[pixel] == 0) {
|
||||||
|
oamPixels[pixel] = currentLinePixels[pixel];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const Word objTileDataAddr = spriteHeight == 8
|
// help ensure correct interaction with "BG OVER OBJ" flag
|
||||||
? objectTileAddr + (tileIndex * 16)
|
for (int i = 0; i < RESOLUTION_X; i++) {
|
||||||
: objectTileAddr + ((tileIndex & 0xFE) * 16);
|
if (oamPixels[i] != currentLinePixels[i] && oamPixels[i] != 0)
|
||||||
|
currentLinePixels[i] = oamPixels[i];
|
||||||
const Byte tileRow = objectY * 2;
|
}
|
||||||
const Byte tileRowData1 = readOnlyAddressSpace[objTileDataAddr + tileRow];
|
}
|
||||||
const Byte tileRowData2 = readOnlyAddressSpace[objTileDataAddr + tileRow + 1];
|
|
||||||
|
|
||||||
const int bit = 7 - objectX;
|
|
||||||
const int colorIndex = ((tileRowData2 >> bit) & 1) << 1 | ((tileRowData1 >> bit) & 1);
|
|
||||||
|
|
||||||
// 0 is always transparent
|
|
||||||
if (colorIndex != 0) {
|
|
||||||
const uint8_t paletteColor = (objPalette >> (colorIndex * 2)) & 0x3;
|
|
||||||
const uint32_t finalColor = getColourFromPalette(paletteColor);
|
|
||||||
oamPixels[pixel] = finalColor;
|
|
||||||
}
|
|
||||||
else if (oamPixels[pixel] == 0) {
|
|
||||||
oamPixels[pixel] = currentLinePixels[pixel];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//help ensure correct interaction with "BG OVER OBJ" flag
|
|
||||||
for (int i = 0; i < RESOLUTION_X; i++) {
|
|
||||||
if (oamPixels[i] != currentLinePixels[i] && oamPixels[i] != 0)
|
|
||||||
currentLinePixels[i] = oamPixels[i];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void GameBoy::SDL2setup() {
|
void GameBoy::SDL2setup() {
|
||||||
SDL_Init(SDL_INIT_EVERYTHING);
|
SDL_Init(SDL_INIT_EVERYTHING);
|
||||||
screen = SDL_CreateWindow("GameBoy++",
|
screen = SDL_CreateWindow("GameBoy++", SDL_WINDOWPOS_UNDEFINED,
|
||||||
SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,
|
SDL_WINDOWPOS_UNDEFINED, RESOLUTION_X, RESOLUTION_Y,
|
||||||
RESOLUTION_X, RESOLUTION_Y,
|
SDL_WINDOW_RESIZABLE);
|
||||||
0);
|
|
||||||
|
|
||||||
// Create an SDL renderer to draw on the window
|
// Create an SDL renderer to draw on the window
|
||||||
renderer = SDL_CreateRenderer(screen, -1, SDL_RENDERER_PRESENTVSYNC | SDL_RENDERER_ACCELERATED);
|
renderer = SDL_CreateRenderer(
|
||||||
|
screen, -1, SDL_RENDERER_PRESENTVSYNC | SDL_RENDERER_ACCELERATED);
|
||||||
|
|
||||||
// Create an SDL texture to hold the framebuffer data
|
// Create an SDL texture to hold the framebuffer data
|
||||||
texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_ARGB8888,
|
texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_ARGB8888,
|
||||||
SDL_TEXTUREACCESS_STREAMING, RESOLUTION_X, RESOLUTION_Y);
|
SDL_TEXTUREACCESS_STREAMING, RESOLUTION_X,
|
||||||
|
RESOLUTION_Y);
|
||||||
}
|
}
|
||||||
|
|
||||||
void GameBoy::SDL2destroy() const {
|
void GameBoy::SDL2destroy() const {
|
||||||
SDL_DestroyTexture(texture);
|
SDL_DestroyTexture(texture);
|
||||||
SDL_DestroyRenderer(renderer);
|
SDL_DestroyRenderer(renderer);
|
||||||
SDL_DestroyWindow(screen);
|
SDL_DestroyWindow(screen);
|
||||||
SDL_Quit();
|
SDL_Quit();
|
||||||
}
|
}
|
||||||
|
|
||||||
void GameBoy::SDL2present() {
|
void GameBoy::SDL2present() {
|
||||||
SDL_UpdateTexture(texture, nullptr, framebuffer, RESOLUTION_X * sizeof(uint32_t));
|
SDL_UpdateTexture(texture, nullptr, framebuffer,
|
||||||
SDL_RenderClear(renderer);
|
RESOLUTION_X * sizeof(uint32_t));
|
||||||
SDL_RenderCopy(renderer, texture, nullptr, nullptr);
|
SDL_RenderClear(renderer);
|
||||||
|
SDL_RenderCopy(renderer, texture, nullptr, nullptr);
|
||||||
|
SDL_RenderSetLogicalSize(renderer, RESOLUTION_X, RESOLUTION_Y);
|
||||||
|
|
||||||
|
frameTime = SDL_GetTicks() - frameStart;
|
||||||
|
|
||||||
frameTime = SDL_GetTicks() - frameStart;
|
if (frameDelay > frameTime) {
|
||||||
|
SDL_Delay(frameDelay - frameTime);
|
||||||
|
}
|
||||||
|
|
||||||
if (frameDelay > frameTime) {
|
SDL_RenderPresent(renderer);
|
||||||
SDL_Delay(frameDelay - frameTime);
|
frameStart = SDL_GetTicks();
|
||||||
}
|
rendered = true;
|
||||||
|
|
||||||
SDL_RenderPresent(renderer);
|
|
||||||
frameStart = SDL_GetTicks();
|
|
||||||
rendered = true;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,43 +1,39 @@
|
|||||||
#ifndef TESTING_HPP
|
#ifndef TESTING_HPP
|
||||||
#define TESTING_HPP
|
#define TESTING_HPP
|
||||||
|
|
||||||
|
#include "defines.hpp"
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <vector>
|
|
||||||
#include <tuple>
|
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include "defines.hpp"
|
#include <tuple>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
struct GameboyTestState {
|
struct GameboyTestState {
|
||||||
Word PC;
|
Word PC;
|
||||||
Word SP;
|
Word SP;
|
||||||
Byte A;
|
Byte A;
|
||||||
Byte F;
|
Byte F;
|
||||||
Byte B;
|
Byte B;
|
||||||
Byte C;
|
Byte C;
|
||||||
Byte D;
|
Byte D;
|
||||||
Byte E;
|
Byte E;
|
||||||
Byte H;
|
Byte H;
|
||||||
Byte L;
|
Byte L;
|
||||||
//Byte IME;
|
// Byte IME;
|
||||||
//Byte IE;
|
// Byte IE;
|
||||||
std::vector<std::tuple<Word, Byte>> RAM;
|
std::vector<std::tuple<Word, Byte>> RAM;
|
||||||
};
|
};
|
||||||
|
|
||||||
inline bool operator==(const GameboyTestState& lhs, const GameboyTestState& rhs) {
|
inline bool operator==(const GameboyTestState &lhs,
|
||||||
for (int i = 0; i < lhs.RAM.size(); i++) {
|
const GameboyTestState &rhs) {
|
||||||
if (std::get<1>(lhs.RAM[i]) != std::get<1>(rhs.RAM[i]))
|
for (int i = 0; i < lhs.RAM.size(); i++) {
|
||||||
return false;
|
if (std::get<1>(lhs.RAM[i]) != std::get<1>(rhs.RAM[i]))
|
||||||
}
|
return false;
|
||||||
return (lhs.A == rhs.A &&
|
}
|
||||||
lhs.F == rhs.F &&
|
return (lhs.A == rhs.A && lhs.F == rhs.F && lhs.B == rhs.B &&
|
||||||
lhs.B == rhs.B &&
|
lhs.C == rhs.C && lhs.D == rhs.D && lhs.E == rhs.E &&
|
||||||
lhs.C == rhs.C &&
|
lhs.H == rhs.H && lhs.L == rhs.L);
|
||||||
lhs.D == rhs.D &&
|
|
||||||
lhs.E == rhs.E &&
|
|
||||||
lhs.H == rhs.H &&
|
|
||||||
lhs.L == rhs.L);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif //TESTING_HPP
|
#endif // TESTING_HPP
|
||||||
|
|||||||
@@ -1,43 +1,45 @@
|
|||||||
#include "gameboy.hpp"
|
#include "gameboy.hpp"
|
||||||
|
|
||||||
//handles most of the behavoir as described here: https://gbdev.io/pandocs/Timer_and_Divider_Registers.html#ff04--div-divider-register
|
// handles most of the behavoir as described here:
|
||||||
|
// https://gbdev.io/pandocs/Timer_and_Divider_Registers.html#ff04--div-divider-register
|
||||||
void GameBoy::timingHandler() {
|
void GameBoy::timingHandler() {
|
||||||
//can't do this as we use cycles for PPU timing but this is what should happen
|
// can't do this as we use cycles for PPU timing but this is what should
|
||||||
//addressSpace.memoryLayout.DIV = ((cycles / 4) >> 6) & 0xFF;
|
// happen addressSpace.memoryLayout.DIV = ((cycles / 4) >> 6) & 0xFF;
|
||||||
|
|
||||||
if (cycles - lastDivUpdate >= DIVIDER_REGISTER_FREQ) {
|
if (cycles - lastDivUpdate >= DIVIDER_REGISTER_FREQ) {
|
||||||
const uint8_t increments = (cycles - lastDivUpdate) / DIVIDER_REGISTER_FREQ;
|
const uint8_t increments = (cycles - lastDivUpdate) / DIVIDER_REGISTER_FREQ;
|
||||||
addressSpace.memoryLayout.DIV += increments;
|
addressSpace.memoryLayout.DIV += increments;
|
||||||
lastDivUpdate += increments * DIVIDER_REGISTER_FREQ;
|
lastDivUpdate += increments * DIVIDER_REGISTER_FREQ;
|
||||||
}
|
}
|
||||||
|
|
||||||
//if enabled
|
// if enabled
|
||||||
uint64_t TIMAFrequency = 0;
|
uint64_t TIMAFrequency = 0;
|
||||||
if (addressSpace.memoryLayout.TAC & 0x04) {
|
if (addressSpace.memoryLayout.TAC & 0x04) {
|
||||||
switch (addressSpace.memoryLayout.TAC & 0x03) {
|
switch (addressSpace.memoryLayout.TAC & 0x03) {
|
||||||
case 0:
|
case 0:
|
||||||
TIMAFrequency = 1024;
|
TIMAFrequency = 1024;
|
||||||
break;
|
break;
|
||||||
case 1:
|
case 1:
|
||||||
TIMAFrequency = 16;
|
TIMAFrequency = 16;
|
||||||
break;
|
break;
|
||||||
case 2:
|
case 2:
|
||||||
TIMAFrequency = 64;
|
TIMAFrequency = 64;
|
||||||
break;
|
break;
|
||||||
case 3:
|
case 3:
|
||||||
TIMAFrequency = 256;
|
TIMAFrequency = 256;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
//if TIMA overflowed and prevTMA != current TMA, use prevTMA (ie use prevTMA regardless)
|
// if TIMA overflowed and prevTMA != current TMA, use prevTMA (ie use
|
||||||
const int increments = (cycles - lastTIMAUpdate) / TIMAFrequency;
|
// prevTMA regardless)
|
||||||
if (cycles - lastTIMAUpdate >= TIMAFrequency) {
|
const int increments = (cycles - lastTIMAUpdate) / TIMAFrequency;
|
||||||
if (static_cast<int>(addressSpace.memoryLayout.TIMA) + increments > 255) {
|
if (cycles - lastTIMAUpdate >= TIMAFrequency) {
|
||||||
addressSpace.memoryLayout.TIMA = prevTMA + ((addressSpace.memoryLayout.TIMA + increments) % 256);
|
if (static_cast<int>(addressSpace.memoryLayout.TIMA) + increments > 255) {
|
||||||
setInterrupt(TIMER_INTERRUPT);
|
addressSpace.memoryLayout.TIMA =
|
||||||
}
|
prevTMA + ((addressSpace.memoryLayout.TIMA + increments) % 256);
|
||||||
else
|
setInterrupt(TIMER_INTERRUPT);
|
||||||
addressSpace.memoryLayout.TIMA += increments;
|
} else
|
||||||
lastTIMAUpdate += increments * TIMAFrequency;
|
addressSpace.memoryLayout.TIMA += increments;
|
||||||
}
|
lastTIMAUpdate += increments * TIMAFrequency;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user