Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,7 @@ build/

# Logs
*.log

# editor tooling
compile_commands.json
.cache/
14 changes: 14 additions & 0 deletions .zed/tasks.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
[
{
"label": "GameDaddy: Build (pokemon-red)",
"command": "./scripts/build.sh",
},
{
"label": "GameDaddy: Run (pokemon-red)",
"command": "./scripts/run.sh",
},
{
"label": "GameDaddy: Debug (pokemon-red)",
"command": "./scripts/debug.sh",
},
]
5 changes: 2 additions & 3 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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)
22 changes: 22 additions & 0 deletions CMakePresets.json
Original file line number Diff line number Diff line change
@@ -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"
}
]
}
53 changes: 48 additions & 5 deletions gameboy-hardware/cpu/cpu.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,22 @@
//

#include "cpu.h"

#include "../memory/memory.h"

#include <iostream>
#include <stdexcept>

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( std::shared_ptr<Memory> 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;
Expand All @@ -26,6 +33,42 @@ void CPU::reset_registers() {
sp_ = 0xFFFE;
}

// 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");
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_;
Expand Down Expand Up @@ -54,4 +97,4 @@ void CPU::set_register(Reg8 reg, uint8_t value) {
}
}

}
}
15 changes: 12 additions & 3 deletions gameboy-hardware/cpu/cpu.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,23 @@
#ifndef CPU_H
#define CPU_H
#include <cstdint>
#include <memory>

#endif //CPU_H

namespace GameBoy {
class Memory;
enum class Reg8;

class CPU {
public:
CPU();
void reset_registers();

void attach_memory(std::shared_ptr<Memory> 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;
Expand Down Expand Up @@ -44,12 +51,14 @@ class CPU {
// rest = 0 (lower 4 bit)
};

// TODO: Implement memory access (bus) reference
std::shared_ptr<Memory> memory_;

// TODO: Implement opcode fetch-decode-execute
};


enum class Reg8 {
A, F, B, C, D, E, H, L
};

}
}
22 changes: 20 additions & 2 deletions gameboy-hardware/memory/memory.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -28,5 +30,21 @@ void Memory::load_rom(const std::vector<uint8_t>& rom_data) {
memory_array->begin()
);
}

void Memory::load_boot(const std::vector<uint8_t>& 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;
}


}

11 changes: 9 additions & 2 deletions gameboy-hardware/memory/memory.h
Original file line number Diff line number Diff line change
Expand Up @@ -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<uint8_t>& 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<uint8_t>& boot_data);
void set_boot_enabled(bool on);

private:
const std::unique_ptr<std::array<uint8_t, MEM_SIZE>> memory_array;

bool boot_rom_enabled = false; // (0x0000 - 0x00FF) <- boot rom data
const std::unique_ptr<std::array<uint8_t, BOOT_ROM_SIZE>> boot_array;

};

}
Expand Down
52 changes: 30 additions & 22 deletions gameboy-hardware/rom/rom-validation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -98,33 +98,38 @@ static uint16_t global_checksum(const std::vector<uint8_t>& rom_data) {
return static_cast<uint16_t>(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<uint8_t>& 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<uint16_t>(rom_data.at(OFF_GLOB_CHECK) << 8 | rom_data.at(OFF_GLOB_CHECK + 1));
// uint16_t calc_global = global_checksum(rom_data);
Expand Down Expand Up @@ -181,12 +186,15 @@ GameBoy::RomValidationResult validate_rom_file(const std::vector<uint8_t>& 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;
}
}
}
28 changes: 18 additions & 10 deletions main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -50,12 +50,12 @@ int main(int argc, char *argv[])
return 0;
}

// initialized all the hardware
// 1) initialized hardware
std::shared_ptr<GameBoy::CPU> cpu_instance = std::make_shared<GameBoy::CPU>();
std::shared_ptr<GameBoy::Memory> memory_instance = std::make_shared<GameBoy::Memory>();
// 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<GameBoy::DisplayInterface> screen;
if (use_gui) {
Expand All @@ -64,30 +64,38 @@ int main(int argc, char *argv[])
// TODO Implement the CLI mode
}

// initialized the platform
// 3) initialized the platform
auto gb_platform = std::make_shared<GameBoy::Platform>(
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<uint8_t> rom_data(rom_size);
rom_file.read(reinterpret_cast<char*>(rom_data.data()), rom_size);

// validate the rom
if (!gb_platform->validate_rom_bytes(rom_data)) throw std::runtime_error("End the program due to failed ROM validation.");
// 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
// 3) load the rom and ram (only after validation)
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<uint8_t> bootrom; // Todo: change temporary to have a CLI parsed args
cpu_instance->reset_registers_fast(); // now 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;
}
}
5 changes: 5 additions & 0 deletions scripts/build.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#!/usr/bin/env bash
set -e

cmake --preset debug
cmake --build --preset debug -j
7 changes: 7 additions & 0 deletions scripts/debug.sh
Original file line number Diff line number Diff line change
@@ -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
Loading