From 2c5e8ebb762e121ef1b99d89b3739b811cc641bb Mon Sep 17 00:00:00 2001 From: William Kiem Lafond Date: Mon, 22 Sep 2025 03:35:31 +0800 Subject: [PATCH 1/7] implemented everyhting, not fully t4estsd --- gameboy-hardware/cpu/cpu.cpp | 50 +++++++++++++++++++++++++++--- gameboy-hardware/cpu/cpu.h | 13 ++++++-- gameboy-hardware/memory/memory.cpp | 22 +++++++++++-- gameboy-hardware/memory/memory.h | 11 +++++-- main.cpp | 11 ++++++- 5 files changed, 96 insertions(+), 11 deletions(-) diff --git a/gameboy-hardware/cpu/cpu.cpp b/gameboy-hardware/cpu/cpu.cpp index 9345e2a..4dfbbd7 100644 --- a/gameboy-hardware/cpu/cpu.cpp +++ b/gameboy-hardware/cpu/cpu.cpp @@ -3,15 +3,22 @@ // #include "cpu.h" + +#include "../memory/memory.h" + +#include #include namespace GameBoy { -CPU::CPU() : a_{0}, f_{0}, b_{0}, c_{0}, d_{0}, e_{0}, h_{0}, l_{0} -{ - reset_registers(); +CPU::CPU() +: a_{0}, f_{0}, b_{0}, c_{0}, d_{0}, e_{0}, h_{0}, l_{0}, pc_{0}, sp_{0} {} + +void CPU::attach_memory(constexpr std::shared_ptr mem) { + memory_ = mem; } -void CPU::reset_registers() { + +void CPU::reset_registers_fast() { // Set initial values according to original GameBoy (DMG) boot ROM specs // https://gbdev.io/pandocs/Power_Up_Sequence.html?highlight=boot#console-state-after-boot-rom-hand-off a_ = 0x01; @@ -26,6 +33,41 @@ void CPU::reset_registers() { sp_ = 0xFFFE; } +void CPU::reset_registers_auth() { + a_ = 0; + f_ = 0; + b_ = 0; + c_ = 0; + d_ = 0; + e_ = 0; + h_ = 0; + l_ = 0; + pc_ = 0; + sp_ = 0; + sp_ = 0x0000; + pc_ = 0x0000; // start executing at boot ROM +} + +int CPU::step() { + if (!memory_) throw std::runtime_error("There is no Memory attached to CPU"); + const uint16_t pc_before = pc_; + uint8_t opcode = memory_->read_byte_at((pc_++)); // ++ after means read at pc_ then increment pc_ + + // debug for temporary use + std::cout << std::hex << "PC=" << pc_before << " OPC=" << (int)opcode << "\n"; + + // decode/execute (skeleton) + // TODO: remove this and use chip-8 switch table + switch (opcode) { + case 0x00: // NOP + // do nothing + return 4; + default: + // For now, just pretend it took 4 cycles + return 4; + } +} + uint8_t CPU::get_register_at(Reg8 reg) const { switch (reg) { case Reg8::A : return a_; diff --git a/gameboy-hardware/cpu/cpu.h b/gameboy-hardware/cpu/cpu.h index 0e687c9..4ecb6a5 100644 --- a/gameboy-hardware/cpu/cpu.h +++ b/gameboy-hardware/cpu/cpu.h @@ -5,16 +5,23 @@ #ifndef CPU_H #define CPU_H #include +#include #endif //CPU_H namespace GameBoy { +class Memory; enum class Reg8; class CPU { public: CPU(); - void reset_registers(); + + void attach_memory(std::shared_ptr mem); + void reset_registers_fast(); // fake simulated for development purposes + void reset_registers_auth(); // authentic power-on boot for registers + + int step(); // Getters uint8_t get_register_at(Reg8 reg) const; @@ -44,10 +51,12 @@ class CPU { // rest = 0 (lower 4 bit) }; - // TODO: Implement memory access (bus) reference + std::shared_ptr memory_; + // TODO: Implement opcode fetch-decode-execute }; + enum class Reg8 { A, F, B, C, D, E, H, L }; diff --git a/gameboy-hardware/memory/memory.cpp b/gameboy-hardware/memory/memory.cpp index 318e4a6..490b05c 100644 --- a/gameboy-hardware/memory/memory.cpp +++ b/gameboy-hardware/memory/memory.cpp @@ -12,8 +12,10 @@ Memory::Memory() : } uint8_t Memory::read_byte_at(uint16_t address) { - uint8_t byte = memory_array->at(address); - return byte; + if (boot_rom_enabled && address < 0x0100) { + return boot_array->at(address); + } + return memory_array->at(address); } void Memory::write_byte_at(uint16_t address, uint8_t value) { @@ -28,5 +30,21 @@ void Memory::load_rom(const std::vector& rom_data) { memory_array->begin() ); } + +void Memory::load_boot(const std::vector& boot_data) { + size_t load_size = std::min(boot_data.size(), boot_array->size()); + std::copy( + boot_data.begin(), + boot_data.begin() + load_size, + boot_array->begin() + ); + boot_rom_enabled = true; +} + +void Memory::set_boot_enabled(bool on) { + boot_rom_enabled = on; +} + + } diff --git a/gameboy-hardware/memory/memory.h b/gameboy-hardware/memory/memory.h index 3739b76..bf47b80 100644 --- a/gameboy-hardware/memory/memory.h +++ b/gameboy-hardware/memory/memory.h @@ -15,15 +15,22 @@ class Memory { // CORE Memory(); static constexpr size_t MEM_SIZE = 0x10000; // 65535 bytes + 1 byte (or 64 KB) + static constexpr size_t BOOT_ROM_SIZE = 0x100; + uint8_t read_byte_at(uint16_t address); void write_byte_at(uint16_t address, uint8_t value); // ROM related void load_rom(const std::vector& rom_data); // since gb is 8bit architecture, gb roms opcodes are in uint_8 - // TODO: add the ROM boot-rom boolean - // TODO: load-bootrom or something + void load_boot(const std::vector& boot_data); + void set_boot_enabled(bool on); + private: const std::unique_ptr> memory_array; + + bool boot_rom_enabled = false; // (0x0000 - 0x00FF) <- boot rom data + const std::unique_ptr> boot_array; + }; } diff --git a/main.cpp b/main.cpp index 998d338..d53eca8 100644 --- a/main.cpp +++ b/main.cpp @@ -80,10 +80,19 @@ int main(int argc, char *argv[]) // validate the rom if (!gb_platform->validate_rom_bytes(rom_data)) throw std::runtime_error("End the program due to failed ROM validation."); - // load the rom + // load the rom and memory gb_platform->load_rom_into_memory(rom_data); + cpu_instance->attach_memory(memory_instance); // load the boot rom + std::vector bootrom; // Todo: change temporary to have a CLI parsed args + bool use_bootrom = true; // Todo: change temporary to have a CLI parsed args + if (use_bootrom) { + memory_instance->load_boot(bootrom); // maps into 0x0000-0x00FF + cpu_instance->reset_registers_auth(); // PC=0x0000 (execute boot code) + } else { + cpu_instance->reset_registers_fast(); // PC=0x0100 (skip boot) + } // start the game loop gb_platform->run(); // TODO: change the actual game loop to run indefinetely (not a fixed timer) From 3ac97e65691052920945ef743f30cfc604e5c7ca Mon Sep 17 00:00:00 2001 From: William Kiem Lafond Date: Thu, 5 Feb 2026 22:33:22 -0500 Subject: [PATCH 2/7] added cmake presets for clion debug and build --- CMakePresets.json | 22 ++++++++++++++++++++++ gameboy-hardware/cpu/cpu.cpp | 4 ++-- main.cpp | 26 ++++++++++++-------------- 3 files changed, 36 insertions(+), 16 deletions(-) create mode 100644 CMakePresets.json diff --git a/CMakePresets.json b/CMakePresets.json new file mode 100644 index 0000000..47504e8 --- /dev/null +++ b/CMakePresets.json @@ -0,0 +1,22 @@ +{ + "version": 3, + + "configurePresets": [ + { + "name": "debug", + "displayName": "Debug (GameDaddy)", + "binaryDir": "build/debug", + + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Debug" + } + } + ], + + "buildPresets": [ + { + "name": "debug", + "configurePreset": "debug" + } + ] +} diff --git a/gameboy-hardware/cpu/cpu.cpp b/gameboy-hardware/cpu/cpu.cpp index 4dfbbd7..4a79145 100644 --- a/gameboy-hardware/cpu/cpu.cpp +++ b/gameboy-hardware/cpu/cpu.cpp @@ -14,7 +14,7 @@ namespace GameBoy { CPU::CPU() : a_{0}, f_{0}, b_{0}, c_{0}, d_{0}, e_{0}, h_{0}, l_{0}, pc_{0}, sp_{0} {} -void CPU::attach_memory(constexpr std::shared_ptr mem) { +void CPU::attach_memory( std::shared_ptr mem) { memory_ = mem; } @@ -96,4 +96,4 @@ void CPU::set_register(Reg8 reg, uint8_t value) { } } -} \ No newline at end of file +} diff --git a/main.cpp b/main.cpp index d53eca8..6397e12 100644 --- a/main.cpp +++ b/main.cpp @@ -50,12 +50,12 @@ int main(int argc, char *argv[]) return 0; } - // initialized all the hardware + // 1) initialized hardware std::shared_ptr cpu_instance = std::make_shared(); std::shared_ptr memory_instance = std::make_shared(); // TODO : add the rest of the hardware parts - // initialize the gui + // 2) initialize gui bool use_gui = true; // TODO : Implement a toggle off for CLI mode std::shared_ptr screen; if (use_gui) { @@ -64,35 +64,33 @@ int main(int argc, char *argv[]) // TODO Implement the CLI mode } - // initialized the platform + // 3) initialized the platform auto gb_platform = std::make_shared( cpu_instance, memory_instance ); gb_platform->setDisplay(screen); - // read the rom from path + // POWER-ON GAMEDADDYYY! ٩(ˊᗜˋ*)ノ --------------------------------------------------------------------------------- + + // 1) read rom from path std::streamsize rom_size = rom_file.tellg(); // tellg gets pointer position (end of file) rom_file.seekg(0, std::ios::beg); // move to beginnging to start reading rom data std::vector rom_data(rom_size); rom_file.read(reinterpret_cast(rom_data.data()), rom_size); - // validate the rom + // 2) validate the rom if (!gb_platform->validate_rom_bytes(rom_data)) throw std::runtime_error("End the program due to failed ROM validation."); - // load the rom and memory + // 3) load the rom and memory gb_platform->load_rom_into_memory(rom_data); cpu_instance->attach_memory(memory_instance); - // load the boot rom + // 4) load the boot rom (fast boot in this case) std::vector bootrom; // Todo: change temporary to have a CLI parsed args - bool use_bootrom = true; // Todo: change temporary to have a CLI parsed args - if (use_bootrom) { - memory_instance->load_boot(bootrom); // maps into 0x0000-0x00FF - cpu_instance->reset_registers_auth(); // PC=0x0000 (execute boot code) - } else { - cpu_instance->reset_registers_fast(); // PC=0x0100 (skip boot) - } + cpu_instance->reset_registers_fast(); // PC=0x0100 (skip boot) + + // start the game loop gb_platform->run(); // TODO: change the actual game loop to run indefinetely (not a fixed timer) From 729dc32acf97f11fa721096c766ced5ef2ee7212 Mon Sep 17 00:00:00 2001 From: William Kiem Lafond Date: Thu, 5 Feb 2026 22:46:10 -0500 Subject: [PATCH 3/7] added arguments scripts with zed keybinds --- .zed/tasks.json | 10 ++++++++++ scripts/debug.sh | 7 +++++++ scripts/run.sh | 7 +++++++ 3 files changed, 24 insertions(+) create mode 100644 .zed/tasks.json create mode 100755 scripts/debug.sh create mode 100755 scripts/run.sh diff --git a/.zed/tasks.json b/.zed/tasks.json new file mode 100644 index 0000000..e75b469 --- /dev/null +++ b/.zed/tasks.json @@ -0,0 +1,10 @@ +[ + { + "label": "GameDaddy: Run (SDL + Tetris)", + "command": "./scripts/run.sh", + }, + { + "label": "GameDaddy: Debug (LLDB + Tetris)", + "command": "./scripts/debug.sh", + }, +] diff --git a/scripts/debug.sh b/scripts/debug.sh new file mode 100755 index 0000000..1f290bb --- /dev/null +++ b/scripts/debug.sh @@ -0,0 +1,7 @@ +#!/usr/bin/env bash +set -e + +cmake --preset debug +cmake --build --preset debug -j + +lldb ./build/debug/gamedaddy -- roms/pokemon-red.gb diff --git a/scripts/run.sh b/scripts/run.sh new file mode 100755 index 0000000..d565363 --- /dev/null +++ b/scripts/run.sh @@ -0,0 +1,7 @@ +#!/usr/bin/env bash +set -e + +cmake --preset debug +cmake --build --preset debug -j + +./build/debug/gamedaddy roms/pokemon-red.gb From 6e5fb87714e8a0d4777efde3d8a13c2920d87d48 Mon Sep 17 00:00:00 2001 From: William Kiem Lafond Date: Fri, 6 Feb 2026 01:44:19 -0500 Subject: [PATCH 4/7] added scripts for cmake build --- .zed/tasks.json | 8 ++++++-- main.cpp | 10 +++++----- scripts/build.sh | 5 +++++ 3 files changed, 16 insertions(+), 7 deletions(-) create mode 100755 scripts/build.sh diff --git a/.zed/tasks.json b/.zed/tasks.json index e75b469..3d12116 100644 --- a/.zed/tasks.json +++ b/.zed/tasks.json @@ -1,10 +1,14 @@ [ { - "label": "GameDaddy: Run (SDL + Tetris)", + "label": "GameDaddy: Build (pokemon-red)", + "command": "./scripts/build.sh", + }, + { + "label": "GameDaddy: Run (pokemon-red)", "command": "./scripts/run.sh", }, { - "label": "GameDaddy: Debug (LLDB + Tetris)", + "label": "GameDaddy: Debug (pokemon-red)", "command": "./scripts/debug.sh", }, ] diff --git a/main.cpp b/main.cpp index 6397e12..152a393 100644 --- a/main.cpp +++ b/main.cpp @@ -80,7 +80,8 @@ int main(int argc, char *argv[]) rom_file.read(reinterpret_cast(rom_data.data()), rom_size); // 2) validate the rom - if (!gb_platform->validate_rom_bytes(rom_data)) throw std::runtime_error("End the program due to failed ROM validation."); + if (!gb_platform->validate_rom_bytes(rom_data)) + throw std::runtime_error("End the program due to failed ROM validation."); // 3) load the rom and memory gb_platform->load_rom_into_memory(rom_data); @@ -90,11 +91,10 @@ int main(int argc, char *argv[]) std::vector bootrom; // Todo: change temporary to have a CLI parsed args cpu_instance->reset_registers_fast(); // PC=0x0100 (skip boot) - - - // start the game loop + // 5) start the game loop gb_platform->run(); // TODO: change the actual game loop to run indefinetely (not a fixed timer) SDL_Quit(); + // End of all SDL subsystems + destruct layer return 0; -} \ No newline at end of file +} diff --git a/scripts/build.sh b/scripts/build.sh new file mode 100755 index 0000000..286607a --- /dev/null +++ b/scripts/build.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env bash +set -e + +cmake --preset debug +cmake --build --preset debug -j From 57004dc94478a8735a671c0c79e4b7546942b938 Mon Sep 17 00:00:00 2001 From: William Kiem Lafond Date: Sat, 7 Feb 2026 01:12:26 -0500 Subject: [PATCH 5/7] commented out all authentic boot related code in validation --- gameboy-hardware/rom/rom-validation.cpp | 52 ++++++++++++++----------- gameboy-hardware/rom/test.cpp | 0 2 files changed, 30 insertions(+), 22 deletions(-) create mode 100644 gameboy-hardware/rom/test.cpp diff --git a/gameboy-hardware/rom/rom-validation.cpp b/gameboy-hardware/rom/rom-validation.cpp index b03bc73..12dca55 100644 --- a/gameboy-hardware/rom/rom-validation.cpp +++ b/gameboy-hardware/rom/rom-validation.cpp @@ -98,33 +98,38 @@ static uint16_t global_checksum(const std::vector& rom_data) { return static_cast(checksum * 0xFFFF); // truncate the first 4 hex digits (32 -> 16) } -// add validation rom +/* Main Cartridge ROM Validation function + Input : uint8_t rom_Data buffer + Returns: RomValidationResult Type +*/ + GameBoy::RomValidationResult validate_rom_file(const std::vector& rom_data) { GameBoy::RomValidationResult out; - // rom size check + // 0) rom size check if (rom_data.size() < MIN_ROM_SIZE) { out.errors.emplace_back("ROM Header is too small. Must be larger than 0x0150 bytes."); return out; } - // 1) nintendo logo check - for (size_t k = 0; k < NINTENDO_LOGO.size(); ++k) { - if (rom_data.at(OFF_LOGO_BEG + k) != NINTENDO_LOGO[k]) { - out.errors.emplace_back("ROM header has incorrect Nintendo Logo at "); - out.errors.emplace_back(std::to_string(OFF_LOGO_BEG)); - return out; - } - } - // 2) header check - uint8_t actual_head_check = rom_data.at(OFF_HEAD_CHECK); - uint8_t calcul_head_check = header_checksum(rom_data); - if (actual_head_check != calcul_head_check) { - out.errors.emplace_back( - std::format("Header Checksum Failed.\nActual at: {}.\nCalculated at: {}", - actual_head_check,calcul_head_check)); - return out; - } + // COMMENTED OUT BECAUSE FAST BOOT DOES NOT NEED IT + // // 1) nintendo logo check + // for (size_t k = 0; k < NINTENDO_LOGO.size(); ++k) { + // if (rom_data.at(OFF_LOGO_BEG + k) != NINTENDO_LOGO[k]) { + // out.errors.emplace_back("ROM header has incorrect Nintendo Logo at "); + // out.errors.emplace_back(std::to_string(OFF_LOGO_BEG)); + // return out; + // } + // } + // // 2) header check + // uint8_t actual_head_check = rom_data.at(OFF_HEAD_CHECK); + // uint8_t calcul_head_check = header_checksum(rom_data); + // if (actual_head_check != calcul_head_check) { + // out.errors.emplace_back( + // std::format("Header Checksum Failed.\nActual at: {}.\nCalculated at: {}", + // actual_head_check,calcul_head_check)); + // return out; + // } // // 3) global check (only do warning) // auto stored_global = static_cast(rom_data.at(OFF_GLOB_CHECK) << 8 | rom_data.at(OFF_GLOB_CHECK + 1)); // uint16_t calc_global = global_checksum(rom_data); @@ -181,12 +186,15 @@ GameBoy::RomValidationResult validate_rom_file(const std::vector& rom_d } // FINALLY, all checks done? return output - // ok = no hard errors (warnings allowed) + // ok = no hard errors (warnings allowed) bool has_hard_error = false; for (auto& s : out.errors) { - if (s.rfind("Warning:", 0) != 0) { has_hard_error = true; break; } + if (s.rfind("Warning:", 0) != 0) { + has_hard_error = true; + break; + } } out.ok = !has_hard_error; return out; } -} \ No newline at end of file +} diff --git a/gameboy-hardware/rom/test.cpp b/gameboy-hardware/rom/test.cpp new file mode 100644 index 0000000..e69de29 From 4829fdf600eac2891094eab01328efe6e4a15463 Mon Sep 17 00:00:00 2001 From: William Kiem Lafond Date: Sat, 7 Feb 2026 01:38:20 -0500 Subject: [PATCH 6/7] modified SDL in cmake bug --- CMakeLists.txt | 5 ++--- gameboy-hardware/cpu/cpu.cpp | 29 +++++++++++++++-------------- gameboy-hardware/cpu/cpu.h | 4 ++-- gameboy-hardware/rom/test.cpp | 0 main.cpp | 5 +++-- 5 files changed, 22 insertions(+), 21 deletions(-) delete mode 100644 gameboy-hardware/rom/test.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 3ed1f46..8e32a96 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -48,13 +48,12 @@ add_executable(gamedaddy # SDL via imported targets from Homebrew packages find_package(SDL2 CONFIG REQUIRED) find_package(SDL2_image CONFIG REQUIRED) -# If/when you add audio: -# find_package(SDL2_mixer CONFIG REQUIRED) +# find_package(SDL2_mixer CONFIG REQUIRED) for audio target_link_libraries(gamedaddy PRIVATE SDL2::SDL2 SDL2_image::SDL2_image - # SDL2_mixer::SDL2_mixer + #SDL2_mixer::SDL2_mixer ) target_compile_features(gamedaddy PRIVATE cxx_std_20) diff --git a/gameboy-hardware/cpu/cpu.cpp b/gameboy-hardware/cpu/cpu.cpp index 4a79145..794a1fd 100644 --- a/gameboy-hardware/cpu/cpu.cpp +++ b/gameboy-hardware/cpu/cpu.cpp @@ -33,20 +33,21 @@ void CPU::reset_registers_fast() { sp_ = 0xFFFE; } -void CPU::reset_registers_auth() { - a_ = 0; - f_ = 0; - b_ = 0; - c_ = 0; - d_ = 0; - e_ = 0; - h_ = 0; - l_ = 0; - pc_ = 0; - sp_ = 0; - sp_ = 0x0000; - pc_ = 0x0000; // start executing at boot ROM -} +// COMMENTED OUT BECAUSE WE ARE ONLY USING FAST BOOT +// void CPU::reset_registers_auth() { +// a_ = 0; +// f_ = 0; +// b_ = 0; +// c_ = 0; +// d_ = 0; +// e_ = 0; +// h_ = 0; +// l_ = 0; +// pc_ = 0; +// sp_ = 0; +// sp_ = 0x0000; +// pc_ = 0x0000; // start executing at boot ROM +// } int CPU::step() { if (!memory_) throw std::runtime_error("There is no Memory attached to CPU"); diff --git a/gameboy-hardware/cpu/cpu.h b/gameboy-hardware/cpu/cpu.h index 4ecb6a5..1d8a6a8 100644 --- a/gameboy-hardware/cpu/cpu.h +++ b/gameboy-hardware/cpu/cpu.h @@ -19,7 +19,7 @@ class CPU { void attach_memory(std::shared_ptr mem); void reset_registers_fast(); // fake simulated for development purposes - void reset_registers_auth(); // authentic power-on boot for registers + //void reset_registers_auth(); // authentic power-on boot for registers int step(); @@ -61,4 +61,4 @@ enum class Reg8 { A, F, B, C, D, E, H, L }; -} \ No newline at end of file +} diff --git a/gameboy-hardware/rom/test.cpp b/gameboy-hardware/rom/test.cpp deleted file mode 100644 index e69de29..0000000 diff --git a/main.cpp b/main.cpp index 152a393..3538c0a 100644 --- a/main.cpp +++ b/main.cpp @@ -71,6 +71,7 @@ int main(int argc, char *argv[]) ); gb_platform->setDisplay(screen); + // POWER-ON GAMEDADDYYY! ٩(ˊᗜˋ*)ノ --------------------------------------------------------------------------------- // 1) read rom from path @@ -83,13 +84,13 @@ int main(int argc, char *argv[]) if (!gb_platform->validate_rom_bytes(rom_data)) throw std::runtime_error("End the program due to failed ROM validation."); - // 3) load the rom and memory + // 3) load the rom and ram (only after validation) gb_platform->load_rom_into_memory(rom_data); cpu_instance->attach_memory(memory_instance); // 4) load the boot rom (fast boot in this case) std::vector bootrom; // Todo: change temporary to have a CLI parsed args - cpu_instance->reset_registers_fast(); // PC=0x0100 (skip boot) + cpu_instance->reset_registers_fast(); // now PC=0x0100 (skip boot) // 5) start the game loop gb_platform->run(); // TODO: change the actual game loop to run indefinetely (not a fixed timer) From ff32b32a5cd01c202fff070fe9caf1dbf504856f Mon Sep 17 00:00:00 2001 From: William Kiem Lafond Date: Sat, 7 Feb 2026 02:06:24 -0500 Subject: [PATCH 7/7] fixed gitignore --- .gitignore | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.gitignore b/.gitignore index d572ad5..5ccc0fb 100644 --- a/.gitignore +++ b/.gitignore @@ -19,3 +19,7 @@ build/ # Logs *.log + +# editor tooling +compile_commands.json +.cache/