From 2e8efaae8947f6d61df4fb38ac7e9b0d263e5908 Mon Sep 17 00:00:00 2001 From: William Kiem Lafond Date: Thu, 19 Feb 2026 02:49:12 -0500 Subject: [PATCH 1/9] untested, implmenation of rom-only cart --- src/cartridge/cart.cpp | 36 ++++++++++++++++++++++++++++++ src/cartridge/cart.h | 26 +++++++++++++++------- src/cartridge/mbc/mbc.cpp | 20 +++++++++++++---- src/cartridge/mbc/mbc.h | 46 +++++++++++++++++++++++++++++++-------- 4 files changed, 107 insertions(+), 21 deletions(-) diff --git a/src/cartridge/cart.cpp b/src/cartridge/cart.cpp index ca78094..d577731 100644 --- a/src/cartridge/cart.cpp +++ b/src/cartridge/cart.cpp @@ -1,7 +1,43 @@ #include "cart.h" +#include "cartridge/mbc/mbc.h" namespace Cartridge { +static constexpr uint16_t OFF_CARTRIDGE_T = 0x0147; +static constexpr uint16_t OFF_ROM_SIZE = 0x0148; +static constexpr uint16_t OFF_RAM_SIZE = 0x0149; + +Cart::Cart(std::vector rom) + : rom_(std::move(rom)) +{ + cart_type_ = rom_.at(OFF_CARTRIDGE_T); + rom_size_code_ = rom_.at(OFF_ROM_SIZE); + ram_size_code_ = rom_.at(OFF_RAM_SIZE); + + alloc_ram_(); + attach_mbc_(); +} + +uint8_t Cart::call_read(uint16_t addr) { + return mbc_->read(addr); +} + +void Cart::call_write(uint16_t addr, uint8_t value) { + mbc_->write(addr, value); +} + +void Cart::attach_mbc_() { + switch (cart_type_) { + case 0x00: // ROM-ONLY + mbc_ = std::make_unique(rom_, ram_); + break; + case 0x01: + mbc_ = std::make_unique(rom_, ram_); + break; + default: + throw std::runtime_error("Unsupported cartridge type: " + std::to_string(cart_type_)); + } +} } diff --git a/src/cartridge/cart.h b/src/cartridge/cart.h index c5424e7..2c8a39f 100644 --- a/src/cartridge/cart.h +++ b/src/cartridge/cart.h @@ -40,19 +40,29 @@ inline const std::unordered_map CARTRIDGE_TYPES = { }; class Cart { +public: + explicit Cart(std::vector rom); + + // forward functions to mbc_ + uint8_t call_read(uint16_t addr); + void call_write(uint16_t addr, uint8_t value); + + // state-safe getters + uint8_t cart_type() const { return cart_type_;} + uint8_t rom_size_code() const { return rom_size_code_;} + uint8_t ram_size_code() const { return ram_size_code_;} + private: - uint8_t cart_type_; // 0x0147 - uint8_t rom_size_; // 0x0148 - uint8_t ram_size_; // 0x0149 + uint8_t cart_type_ = 0; // byte at 0x0147 ex: 0x00 for ROM ONLY + uint8_t rom_size_code_ = 0; // '' 0x0148 + uint8_t ram_size_code_ = 0; // '' 0x0149 std::vector rom_; std::vector ram_; std::unique_ptr mbc_; -public: - Cart(); - - uint8_t call_read(uint8_t); - uint8_t call_write(uint8_t); + void attach_mbc_(); + void alloc_ram_(); }; + } diff --git a/src/cartridge/mbc/mbc.cpp b/src/cartridge/mbc/mbc.cpp index 4dd696a..eb8512d 100644 --- a/src/cartridge/mbc/mbc.cpp +++ b/src/cartridge/mbc/mbc.cpp @@ -10,6 +10,7 @@ namespace Cartridge { +/// General Helpers for all MBC-related operations ------------------------------------------------------------ static uint32_t rom_bank_count_from_bytes(std::size_t rom_size) { // each bank is 16KB return static_cast(rom_size / 0x4000); @@ -22,13 +23,24 @@ static uint32_t ram_bank_count_bytes(std::size_t ram_size) { return static_cast(ram_size / 0x2000); } -MBC1::MBC1(const std::vector& rom, std::vector& ram) - : rom_(rom), ram_(ram) -{ - // TODO constructor init functions +/// ROM ONLY (0x00) --------------------------------------------------------- +uint8_t RomOnly::read(uint16_t addr) { + if (addr <= 0x7FFF) { + if (addr > rom_.size()) { // edge case: inside the valid direct mapping, but after the last rom byte + return 0xFF; // pull high + } + return rom_[addr]; // direct mapping else (correct rom-only mapping) + } + else { // pull high everywhere else (ram location included) + return 0xFF; + } +} +void RomOnly::write(uint16_t addr, uint8_t value) { + return; // no write in rom (a.k.a read-ONLY-memory } +/// MBC1 - (0x01) ------------------------------------------------------------- uint32_t MBC1::clamp_rom_bank_(uint32_t bank) const { // TODO return 0; diff --git a/src/cartridge/mbc/mbc.h b/src/cartridge/mbc/mbc.h index e1c8a23..4a5c279 100644 --- a/src/cartridge/mbc/mbc.h +++ b/src/cartridge/mbc/mbc.h @@ -8,35 +8,63 @@ namespace Cartridge { -// MBC Interface (pure virtual/abstract) +// MBC Interface (pure virtual/abstract) --------------------------------------------------------------------- class MBC { public: virtual ~MBC() = default; + MBC(const MBC&) = delete; // mbc cant be copied via: MBC new_mbc(mbc) + MBC& operator=(const MBC&) = delete; // mbc cant be copy-assign via: new_mbc = mbc + MBC(MBC&&) = delete; // mbc cant be moved via: MBC new_mbc = std::move(mbc) + MBC& operator=(MBC&&) = delete; // mbc cant be move-assign via: new_mbc = std::move(mbc) virtual uint8_t read(uint16_t addr) = 0; - virtual void write(uint16_t addr, uint8_t value) = 0; + virtual void write(uint16_t addr, uint8_t value) = 0; + +protected: + MBC() = default; }; +/// POLY IMPLEMENTATIONS OF MBC INTERFACE: --------------------------------------------------------------------- + +// ROM ONLY (0x00) +// - no bank, +// - no external ram (or optional) +class RomOnly final : public MBC { +public: + explicit RomOnly(const std::vector& rom, + std::vector& ram) + : rom_(rom), ram_(ram) {} + + uint8_t read(uint16_t addr) override; + void write(uint16_t addr, uint8_t value) override; + +private: + const std::vector& rom_; + std::vector& ram_; +}; + + +// MBC1 (0x01) class MBC1 final : public MBC { public: - MBC1(const std::vector& rom, std::vector& ram); + explicit MBC1(const std::vector& rom, + std::vector& ram) + : rom_(ram), ram_(ram) {} uint8_t read(uint16_t addr) override; - void write(uint16_t addr, uint8_t value) override; + void write(uint16_t addr, uint8_t value) override; + private: const std::vector& rom_; - std::vector& ram_; + std::vector& ram_; bool ram_enabled_ = false; - // MBC1 registers uint8_t rom_bank_low5_ = 1; // 5 bits uint8_t bank_high2_ = 0; // 2 bits - uint8_t mode_ = 0; // 0=ROM\ - + uint8_t mode_ = 0; // 0=ROM uint32_t rom_bank_count_ = 0; uint32_t ram_bank_count_ = 0; - uint32_t clamp_rom_bank_(uint32_t bank) const; uint32_t clamp_ram_bank_(uint32_t bank) const; }; From f78144dd754e59bad0969c5c17be614449597a7f Mon Sep 17 00:00:00 2001 From: William Kiem Lafond Date: Thu, 19 Feb 2026 13:38:28 -0500 Subject: [PATCH 2/9] refactored mbc interface and renamed rom-validation to rom --- src/_platform/platform.cpp | 2 +- src/cartridge/cart.cpp | 24 ++++++++++++++++++- src/cartridge/mbc/mbc.h | 18 +++++++------- .../rom/{rom-validation.cpp => rom.cpp} | 2 +- src/cartridge/rom/{rom-validation.h => rom.h} | 0 test/rom_validation_test.cpp | 2 +- 6 files changed, 36 insertions(+), 12 deletions(-) rename src/cartridge/rom/{rom-validation.cpp => rom.cpp} (99%) rename src/cartridge/rom/{rom-validation.h => rom.h} (100%) diff --git a/src/_platform/platform.cpp b/src/_platform/platform.cpp index 01ce97f..2134057 100644 --- a/src/_platform/platform.cpp +++ b/src/_platform/platform.cpp @@ -10,7 +10,7 @@ #include #include "../gameboy/memory/memory.h" -#include "../cartridge/rom/rom-validation.h" +#include "../cartridge/rom/rom.h" namespace GameBoy { // TODO: implement the commented parts diff --git a/src/cartridge/cart.cpp b/src/cartridge/cart.cpp index d577731..fd8b812 100644 --- a/src/cartridge/cart.cpp +++ b/src/cartridge/cart.cpp @@ -1,6 +1,7 @@ #include "cart.h" #include "cartridge/mbc/mbc.h" +#include namespace Cartridge { @@ -19,20 +20,40 @@ Cart::Cart(std::vector rom) attach_mbc_(); } +/* + * Forward function to mbc's read. + */ uint8_t Cart::call_read(uint16_t addr) { return mbc_->read(addr); } +/* + * Forward function to mbc's write. + */ void Cart::call_write(uint16_t addr, uint8_t value) { mbc_->write(addr, value); } +/* + * Resize ram_ into "ram size", specified with ram_size_code_ at 0x149. + * Return: Void (implicitly reshapes vector ram_ size) + */ +void Cart::alloc_ram_() { + switch(ram_size_code_) { + case 0x00: + case 0x01 + } +} + +/* + * Forward function to mbc's read. + */ void Cart::attach_mbc_() { switch (cart_type_) { case 0x00: // ROM-ONLY mbc_ = std::make_unique(rom_, ram_); break; - case 0x01: + case 0x01: // MBC 1 mbc_ = std::make_unique(rom_, ram_); break; default: @@ -40,4 +61,5 @@ void Cart::attach_mbc_() { } } + } diff --git a/src/cartridge/mbc/mbc.h b/src/cartridge/mbc/mbc.h index 4a5c279..401f577 100644 --- a/src/cartridge/mbc/mbc.h +++ b/src/cartridge/mbc/mbc.h @@ -10,17 +10,19 @@ namespace Cartridge { // MBC Interface (pure virtual/abstract) --------------------------------------------------------------------- class MBC { +protected: + MBC() = default; + public: - virtual ~MBC() = default; - MBC(const MBC&) = delete; // mbc cant be copied via: MBC new_mbc(mbc) - MBC& operator=(const MBC&) = delete; // mbc cant be copy-assign via: new_mbc = mbc - MBC(MBC&&) = delete; // mbc cant be moved via: MBC new_mbc = std::move(mbc) - MBC& operator=(MBC&&) = delete; // mbc cant be move-assign via: new_mbc = std::move(mbc) + virtual ~MBC() = default; + + MBC(const MBC&) = delete; // mbc cant be copied via: MBC new_mbc(mbc) + MBC& operator=(const MBC&) = delete; // mbc cant be copy-assign via: new_mbc = mbc + MBC(MBC&&) = delete; // mbc cant be moved via: MBC new_mbc = std::move(mbc) + MBC& operator=(MBC&&) = delete; // mbc cant be move-assign via: new_mbc = std::move(mbc) + virtual uint8_t read(uint16_t addr) = 0; virtual void write(uint16_t addr, uint8_t value) = 0; - -protected: - MBC() = default; }; diff --git a/src/cartridge/rom/rom-validation.cpp b/src/cartridge/rom/rom.cpp similarity index 99% rename from src/cartridge/rom/rom-validation.cpp rename to src/cartridge/rom/rom.cpp index 247891e..002444a 100644 --- a/src/cartridge/rom/rom-validation.cpp +++ b/src/cartridge/rom/rom.cpp @@ -5,7 +5,7 @@ #include "../../../include/units.h" #include "../../../include/helpers.h" #include "../cart.h" -#include "rom-validation.h" +#include "rom.h" #include diff --git a/src/cartridge/rom/rom-validation.h b/src/cartridge/rom/rom.h similarity index 100% rename from src/cartridge/rom/rom-validation.h rename to src/cartridge/rom/rom.h diff --git a/test/rom_validation_test.cpp b/test/rom_validation_test.cpp index 7f425ac..076a578 100644 --- a/test/rom_validation_test.cpp +++ b/test/rom_validation_test.cpp @@ -1,5 +1,5 @@ #include -#include "cartridge/rom/rom-validation.h" +#include "cartridge/rom/rom.h" using namespace Cartridge; From 4c776462cc2963ad0cba97c7b91ea9af4ad01fd0 Mon Sep 17 00:00:00 2001 From: William Kiem Lafond Date: Sun, 1 Mar 2026 15:20:00 -0500 Subject: [PATCH 3/9] format codebase with clang-format --- .clang-format | 56 ++-- include/helpers.h | 53 ++-- include/units.h | 2 +- src/_platform/display/display_interface.h | 9 +- src/_platform/display/impl/cli.h | 2 +- src/_platform/display/impl/sdl_gui.cpp | 281 +++++++++++---------- src/_platform/display/impl/sdl_gui.h | 13 +- src/_platform/platform.cpp | 123 +++++---- src/_platform/platform.h | 31 ++- src/cartridge/cart.cpp | 55 ++-- src/cartridge/cart.h | 60 ++--- src/cartridge/mbc/mbc.cpp | 53 ++-- src/cartridge/mbc/mbc.h | 53 ++-- src/cartridge/rom/rom.cpp | 241 ++++++++++-------- src/cartridge/rom/rom.h | 6 +- src/gameboy/cpu/cpu.cpp | 135 ++++++---- src/gameboy/cpu/cpu.h | 47 ++-- src/gameboy/interrupts/interrupts.h | 2 +- src/gameboy/io/instructions/instructions.h | 2 +- src/gameboy/io/joypad/joypad.h | 2 +- src/gameboy/io/ppu/ppu.h | 2 +- src/gameboy/io/sound/sound.h | 2 +- src/gameboy/memory/memory.cpp | 40 ++- src/gameboy/memory/memory.h | 12 +- src/main.cpp | 175 +++++++------ 25 files changed, 735 insertions(+), 722 deletions(-) diff --git a/.clang-format b/.clang-format index 4c1bb9e..6731d63 100644 --- a/.clang-format +++ b/.clang-format @@ -1,39 +1,47 @@ BasedOnStyle: LLVM -IndentWidth: 4 -TabWidth: 4 + +# Indentation +IndentWidth: 2 +TabWidth: 2 UseTab: Never -# Keep namespaces flush-left (no indent inside) -NamespaceIndentation: None +# Line length +ColumnLimit: 110 -# Braces +# Braces & control flow BreakBeforeBraces: Attach -AllowShortFunctionsOnASingleLine: Empty AllowShortIfStatementsOnASingleLine: false AllowShortLoopsOnASingleLine: false +AllowShortFunctionsOnASingleLine: Empty +AllowShortBlocksOnASingleLine: Never -# Constructor initializer list formatting -ConstructorInitializerIndentWidth: 4 -BreakConstructorInitializers: AfterColon -ConstructorInitializerAllOnOneLineOrOnePerLine: false - -# Pointer and reference alignment -PointerAlignment: Left -ReferenceAlignment: Left +# Access specifiers (important for multi-class files) +IndentAccessModifiers: true +AccessModifierOffset: -2 -# Column limit -ColumnLimit: 120 +# Alignment +AlignAfterOpenBracket: Align +AlignOperands: Align +AlignTrailingComments: true -# Spaces +# Spacing SpaceBeforeParens: ControlStatements SpacesInParentheses: false SpacesInSquareBrackets: false -SpacesInAngles: false -SpaceBeforeAssignmentOperators: true -Cpp11BracedListStyle: true -# Indent access specifiers (public:, private:) 1 level -AccessModifierOffset: -4 +# Pointers / references +PointerAlignment: Left +ReferenceAlignment: Left +DerivePointerAlignment: false + +# Constructors +BreakConstructorInitializers: AfterColon +ConstructorInitializerIndentWidth: 2 + +# Templates & lists +AlwaysBreakTemplateDeclarations: Yes +Cpp11BracedListStyle: true -# Indent case labels inside switch -IndentCaseLabels: true +# Includes +SortIncludes: true +IncludeBlocks: Preserve diff --git a/include/helpers.h b/include/helpers.h index 874ec38..e4e524e 100644 --- a/include/helpers.h +++ b/include/helpers.h @@ -3,32 +3,31 @@ #include namespace Gameboy { - /* - * Message helper for formatting one arugment. - * - * Examples: - * msg("PC = ", pc); - */ - template - static std::string msg(const char* prefix, T value) { - std::ostringstream oss; - oss << prefix << value; - return oss.str(); - } - - /* - * Message helper that formats one argue in the middle as well as the end. - * - * Examples: - * msg("I am ", 21, " years and ", 3.0/4.0); - * msg("Bank ", bank, " selected: ", romBank); - */ - template - static std::string msg(const char* prefix, A a, - const char* mid, B b) { - std::ostringstream oss; - oss << prefix << a << mid << b; - return oss.str(); - } +/* + * Message helper for formatting one arugment. + * + * Examples: + * msg("PC = ", pc); + */ +template +static std::string msg(const char* prefix, T value) { + std::ostringstream oss; + oss << prefix << value; + return oss.str(); +} +/* + * Message helper that formats one argue in the middle as well as the end. + * + * Examples: + * msg("I am ", 21, " years and ", 3.0/4.0); + * msg("Bank ", bank, " selected: ", romBank); + */ +template +static std::string msg(const char* prefix, A a, const char* mid, B b) { + std::ostringstream oss; + oss << prefix << a << mid << b; + return oss.str(); } + +} // namespace Gameboy diff --git a/include/units.h b/include/units.h index 02f3b27..366d8fd 100644 --- a/include/units.h +++ b/include/units.h @@ -10,4 +10,4 @@ namespace GameBoy::units { static constexpr std::size_t KiB = 1024; static constexpr std::size_t MiB = 1024 * KiB; -} +} // namespace GameBoy::units diff --git a/src/_platform/display/display_interface.h b/src/_platform/display/display_interface.h index 22df04f..54c0b36 100644 --- a/src/_platform/display/display_interface.h +++ b/src/_platform/display/display_interface.h @@ -7,17 +7,18 @@ namespace GameBoy { class DisplayInterface { -public: + public: virtual ~DisplayInterface() = default; // uses compiler default deconstructor virtual void clear() = 0; virtual void present_idle() = 0; virtual void draw_pixel(int col, int row, bool draw_on, int color_index) = 0; virtual int update_screen(int* gfx_buffer_ptr) = 0; -protected: + + protected: int display_width = 0; int display_height = 0; // base classes or interfaces should not have private methods/fields }; -} +} // namespace GameBoy -#endif //DISPLAY_INTERFACE_H \ No newline at end of file +#endif // DISPLAY_INTERFACE_H \ No newline at end of file diff --git a/src/_platform/display/impl/cli.h b/src/_platform/display/impl/cli.h index 97a5b9d..7e39ffc 100644 --- a/src/_platform/display/impl/cli.h +++ b/src/_platform/display/impl/cli.h @@ -5,4 +5,4 @@ #ifndef CLI_H #define CLI_H -#endif //CLI_H +#endif // CLI_H diff --git a/src/_platform/display/impl/sdl_gui.cpp b/src/_platform/display/impl/sdl_gui.cpp index b6af028..ca6828d 100644 --- a/src/_platform/display/impl/sdl_gui.cpp +++ b/src/_platform/display/impl/sdl_gui.cpp @@ -6,146 +6,153 @@ #include +#include #include #include -#include namespace GameBoy { - /** - * @brief Constructs the GUI with a window, renderer, and palette. - * - * Initializes SDL2 window and renderer, sets logical size to 64x32 for GameBoy, - * configures the background color based on intro flag, creates a paletted surface, - * and prepares the initial render. - * - * @param name Title of the SDL window. - * @param w Width of the window in pixels. - * @param h Height of the window in pixels. - * @param is_intro If true, sets up the intro selection screen background color. - * @throws std::runtime_error if the SDL window or renderer cannot be created. - */ - SDLGui::SDLGui(int w, int h) { - // set protected display resolutions - display_width = w; - display_height = h; - - // initialize a window display - this->win = SDL_CreateWindow("GameDaddy",SDL_WINDOWPOS_CENTERED,SDL_WINDOWPOS_CENTERED, w, h, - SDL_WINDOW_RESIZABLE); - if (!win) - throw std::runtime_error("GameDaddy's GUI could not be opened!"); - this->ren = SDL_CreateRenderer(win, -1, SDL_RENDERER_ACCELERATED); - if (!ren) throw std::runtime_error("SDL_CreateRenderer failed"); - SDL_RenderSetLogicalSize(this->ren, 64, 32); // fixed 64, 32 to allow responsive scaling - SDL_SetRenderDrawColor(ren, 10, 10, 10, 255); - SDL_Surface* screen_surface = SDL_CreateRGBSurfaceWithFormat(0,w,h,1, - SDL_PIXELFORMAT_INDEX8); - - // handle exceptions - if (!screen_surface) { - SDL_Log("Surface has no palette?!\n"); - SDL_FreeSurface(screen_surface); - return; - } - SDL_Palette* palette = screen_surface->format->palette; - if (!palette) { - SDL_Log("Surface has no palette?!\n"); - SDL_FreeSurface(screen_surface); - return; - } - SDL_SetPaletteColors(palette, init_colors(), 0, 4); - screen_texture = SDL_CreateTextureFromSurface(ren, screen_surface); - screen_rect = std::make_unique( SDL_Rect{ 0, 0, w, h } ); - - SDL_FreeSurface(screen_surface); - // one time clear + present so you see something right away - SDL_RenderClear(ren); - SDL_RenderCopy(ren, screen_texture, NULL, screen_rect.get()); - SDL_RenderPresent(ren); - } - - /** - * @brief Initializes the color palette for the display (black and white). - * - * Allocates an array of two SDL_Color values and sets index 0 to black - * and index 1 to white for GameBoy's monochrome display. - * - * @return Pointer to the dynamically allocated SDL_Color array. - */ - SDL_Color* SDLGui::init_colors() { - colors = static_cast(malloc(sizeof(SDL_Color) * 4)); - - // DMG (aka original GameBoy) 4-color palette wheel - colors[0] = SDL_Color{255, 255, 255, 255}; // White - colors[1] = SDL_Color{192, 192, 192, 255}; // Light Gray - colors[2] = SDL_Color{96, 96, 96, 255}; // Dark Gray - colors[3] = SDL_Color{0, 0, 0, 255}; // Black - - return colors; - } - - /** - * @brief Destructor for the GUI, cleans up SDL resources and allocated memory. - * - * Destroys the SDL texture, renderer, and window, and frees the palette colors. - */ - SDLGui::~SDLGui() { - if (screen_texture) SDL_DestroyTexture(screen_texture); - if (ren) SDL_DestroyRenderer(ren); - if (win) SDL_DestroyWindow(win); - if (colors) free(colors); - ren = nullptr; win = nullptr; screen_texture = nullptr; screen_rect = nullptr; - } - - /** - * @brief Clears the renderer with a default background color. - * - * Sets the draw color to a dark grey and clears the current rendering target. - */ - void SDLGui::clear() { - SDL_SetRenderDrawColor(ren, 20, 20, 20, 255); - SDL_RenderClear(ren); - } - - /** - * @brief Presents the current rendered frame to the display without clearing. - * - * Useful for idle or static frames where only presenting is needed. - */ - void SDLGui::present_idle() { - SDL_RenderPresent(ren); - } - - /** - * Draws a single GameBoy “pixel” as a filled rectangle on the screen. - * - * - * @param col The horizontal coordinate of the pixel (0–63). - * @param row The vertical coordinate of the pixel (0–31). - * @param on If true, draw the pixel (white); if false, do nothing (pixel remains off). - * @param color_index Index 0-3 for the dmg color palette - */ - void SDLGui::draw_pixel(int col, int row, bool on, int color_index) - { - if (!on) return; - SDL_Color pixel_color = colors[color_index]; - SDL_SetRenderDrawColor(ren, pixel_color.r, pixel_color.g, pixel_color.b, pixel_color.a); - SDL_Rect r{col, row, 1, 1}; // draws a 1x1 logical pixel in canvas always - SDL_RenderFillRect(ren, &r); - } - - /** - * @brief Updates the screen texture from the graphics buffer and renders it. - * - * Copies the provided 8-bit graphics buffer into the SDL texture, then - * renders the texture to the window. - * - * @param gfx_ptr Pointer to the graphics buffer (uint8_t array) sized width * height. - * @return The result code from SDL_RenderCopy (0 on success, negative on failure). - */ - int SDLGui::update_screen(int* gfx_ptr) { - SDL_UpdateTexture(screen_texture, nullptr, gfx_ptr, display_width * sizeof(int)); - return SDL_RenderCopy(ren, screen_texture, NULL, screen_rect.get()); - } +/** + * @brief Constructs the GUI with a window, renderer, and palette. + * + * Initializes SDL2 window and renderer, sets logical size to 64x32 for GameBoy, + * configures the background color based on intro flag, creates a paletted surface, + * and prepares the initial render. + * + * @param name Title of the SDL window. + * @param w Width of the window in pixels. + * @param h Height of the window in pixels. + * @param is_intro If true, sets up the intro selection screen background color. + * @throws std::runtime_error if the SDL window or renderer cannot be created. + */ +SDLGui::SDLGui(int w, int h) { + // set protected display resolutions + display_width = w; + display_height = h; + + // initialize a window display + this->win = SDL_CreateWindow("GameDaddy", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, w, h, + SDL_WINDOW_RESIZABLE); + if (!win) + throw std::runtime_error("GameDaddy's GUI could not be opened!"); + this->ren = SDL_CreateRenderer(win, -1, SDL_RENDERER_ACCELERATED); + if (!ren) + throw std::runtime_error("SDL_CreateRenderer failed"); + SDL_RenderSetLogicalSize(this->ren, 64, 32); // fixed 64, 32 to allow responsive scaling + SDL_SetRenderDrawColor(ren, 10, 10, 10, 255); + SDL_Surface* screen_surface = SDL_CreateRGBSurfaceWithFormat(0, w, h, 1, SDL_PIXELFORMAT_INDEX8); + + // handle exceptions + if (!screen_surface) { + SDL_Log("Surface has no palette?!\n"); + SDL_FreeSurface(screen_surface); + return; + } + SDL_Palette* palette = screen_surface->format->palette; + if (!palette) { + SDL_Log("Surface has no palette?!\n"); + SDL_FreeSurface(screen_surface); + return; + } + SDL_SetPaletteColors(palette, init_colors(), 0, 4); + screen_texture = SDL_CreateTextureFromSurface(ren, screen_surface); + screen_rect = std::make_unique(SDL_Rect{0, 0, w, h}); + + SDL_FreeSurface(screen_surface); + // one time clear + present so you see something right away + SDL_RenderClear(ren); + SDL_RenderCopy(ren, screen_texture, NULL, screen_rect.get()); + SDL_RenderPresent(ren); +} + +/** + * @brief Initializes the color palette for the display (black and white). + * + * Allocates an array of two SDL_Color values and sets index 0 to black + * and index 1 to white for GameBoy's monochrome display. + * + * @return Pointer to the dynamically allocated SDL_Color array. + */ +SDL_Color* SDLGui::init_colors() { + colors = static_cast(malloc(sizeof(SDL_Color) * 4)); + + // DMG (aka original GameBoy) 4-color palette wheel + colors[0] = SDL_Color{255, 255, 255, 255}; // White + colors[1] = SDL_Color{192, 192, 192, 255}; // Light Gray + colors[2] = SDL_Color{96, 96, 96, 255}; // Dark Gray + colors[3] = SDL_Color{0, 0, 0, 255}; // Black + + return colors; +} + +/** + * @brief Destructor for the GUI, cleans up SDL resources and allocated memory. + * + * Destroys the SDL texture, renderer, and window, and frees the palette colors. + */ +SDLGui::~SDLGui() { + if (screen_texture) + SDL_DestroyTexture(screen_texture); + if (ren) + SDL_DestroyRenderer(ren); + if (win) + SDL_DestroyWindow(win); + if (colors) + free(colors); + ren = nullptr; + win = nullptr; + screen_texture = nullptr; + screen_rect = nullptr; +} +/** + * @brief Clears the renderer with a default background color. + * + * Sets the draw color to a dark grey and clears the current rendering target. + */ +void SDLGui::clear() { + SDL_SetRenderDrawColor(ren, 20, 20, 20, 255); + SDL_RenderClear(ren); } + +/** + * @brief Presents the current rendered frame to the display without clearing. + * + * Useful for idle or static frames where only presenting is needed. + */ +void SDLGui::present_idle() { + SDL_RenderPresent(ren); +} + +/** + * Draws a single GameBoy “pixel” as a filled rectangle on the screen. + * + * + * @param col The horizontal coordinate of the pixel (0–63). + * @param row The vertical coordinate of the pixel (0–31). + * @param on If true, draw the pixel (white); if false, do nothing (pixel remains off). + * @param color_index Index 0-3 for the dmg color palette + */ +void SDLGui::draw_pixel(int col, int row, bool on, int color_index) { + if (!on) + return; + SDL_Color pixel_color = colors[color_index]; + SDL_SetRenderDrawColor(ren, pixel_color.r, pixel_color.g, pixel_color.b, pixel_color.a); + SDL_Rect r{col, row, 1, 1}; // draws a 1x1 logical pixel in canvas always + SDL_RenderFillRect(ren, &r); +} + +/** + * @brief Updates the screen texture from the graphics buffer and renders it. + * + * Copies the provided 8-bit graphics buffer into the SDL texture, then + * renders the texture to the window. + * + * @param gfx_ptr Pointer to the graphics buffer (uint8_t array) sized width * height. + * @return The result code from SDL_RenderCopy (0 on success, negative on failure). + */ +int SDLGui::update_screen(int* gfx_ptr) { + SDL_UpdateTexture(screen_texture, nullptr, gfx_ptr, display_width * sizeof(int)); + return SDL_RenderCopy(ren, screen_texture, NULL, screen_rect.get()); +} + +} // namespace GameBoy diff --git a/src/_platform/display/impl/sdl_gui.h b/src/_platform/display/impl/sdl_gui.h index 1f6f436..70ef639 100644 --- a/src/_platform/display/impl/sdl_gui.h +++ b/src/_platform/display/impl/sdl_gui.h @@ -5,9 +5,9 @@ #ifndef GUI_H #define GUI_H -#include #include #include +#include #include #include "../display_interface.h" @@ -15,22 +15,23 @@ namespace GameBoy { class SDLGui : public DisplayInterface { -public: + public: SDLGui(int w, int h); ~SDLGui() override; void clear() override; void present_idle() override; void draw_pixel(int col, int row, bool draw_on, int color_index) override; int update_screen(int* gfx_ptr) override; -private: + + private: SDL_Color* init_colors(); SDL_Window* win = nullptr; - SDL_Texture* screen_texture; + SDL_Texture* screen_texture; std::unique_ptr screen_rect; SDL_Renderer* ren = nullptr; SDL_Color* colors; // add private helpers for gui related stuff if needed. }; -} +} // namespace GameBoy -#endif //SDL_GUI_H +#endif // SDL_GUI_H diff --git a/src/_platform/platform.cpp b/src/_platform/platform.cpp index 2134057..a538f7d 100644 --- a/src/_platform/platform.cpp +++ b/src/_platform/platform.cpp @@ -4,101 +4,98 @@ #include "platform.h" -#include #include #include +#include #include -#include "../gameboy/memory/memory.h" #include "../cartridge/rom/rom.h" +#include "../gameboy/memory/memory.h" namespace GameBoy { // TODO: implement the commented parts -Platform::Platform( - std::shared_ptr cpu_instance, - std::shared_ptr memory_instance - ): - cpu_ { cpu_instance }, - memory_ { memory_instance} -// std::shared_ptr ppu_instance, -// std::shared_ptr joypad_instance, -// std::shared_ptr sound_instance, -// ) : // member initializer list -// cpu_ { cpu_instance }, -// ppu_ { ppu_instance }, -// joypad_ { joypad_instance }, -// sound_ { sound_instance }, -// gui_ { gui_instance } +Platform::Platform(std::shared_ptr cpu_instance, + std::shared_ptr memory_instance) : + cpu_{cpu_instance}, memory_{memory_instance} // std::shared_ptr ppu_instance, + // std::shared_ptr joypad_instance, + // std::shared_ptr sound_instance, + // ) : // member initializer list + // cpu_ { cpu_instance }, + // ppu_ { ppu_instance }, + // joypad_ { joypad_instance }, + // sound_ { sound_instance }, + // gui_ { gui_instance } { - // constructor body - if (!cpu_ | !memory_) { - std::cerr << "Platform: Invalid instantiation of Platform Layer\n" << std::endl; - return; - } + // constructor body + if (!cpu_ | !memory_) { + std::cerr << "Platform: Invalid instantiation of Platform Layer\n" << std::endl; + return; + } } void Platform::setDisplay(std::shared_ptr display_instance) { - display_ = display_instance; - if (!display_) { - std::cerr << "Platform: DisplayInterface instance is null\n"; - } + display_ = display_instance; + if (!display_) { + std::cerr << "Platform: DisplayInterface instance is null\n"; + } } void Platform::run_frame() { - if (!display_) { - std::cerr << "Platform: Cannot run frame without a display\n"; - return; - } + if (!display_) { + std::cerr << "Platform: Cannot run frame without a display\n"; + return; + } - auto frame_start_time = std::chrono::steady_clock::now(); + auto frame_start_time = std::chrono::steady_clock::now(); - display_->clear(); + display_->clear(); - // draw vertical line - for (int r = center_row - 5; r <= center_row + 5; ++r) { - display_->draw_pixel(center_col, r, true, 3); // black - } + // draw vertical line + for (int r = center_row - 5; r <= center_row + 5; ++r) { + display_->draw_pixel(center_col, r, true, 3); // black + } - // draw horizontal line - for (int c = center_col - 5; c <= center_col + 5; ++c) { - display_->draw_pixel(c, center_row, true, 3); // black - } + // draw horizontal line + for (int c = center_col - 5; c <= center_col + 5; ++c) { + display_->draw_pixel(c, center_row, true, 3); // black + } - display_->present_idle(); + display_->present_idle(); - auto frame_end_time = std::chrono::steady_clock::now(); - auto elapsed = std::chrono::duration_cast(frame_end_time - frame_start_time); - auto time_to_wait = cycle_period - elapsed; + auto frame_end_time = std::chrono::steady_clock::now(); + auto elapsed = std::chrono::duration_cast(frame_end_time - frame_start_time); + auto time_to_wait = cycle_period - elapsed; - if (time_to_wait.count() > 0) - std::this_thread::sleep_for(time_to_wait); + if (time_to_wait.count() > 0) + std::this_thread::sleep_for(time_to_wait); } void Platform::run() { - SDL_Event e; - bool quit = false; - while (!quit) { - while (SDL_PollEvent(&e)) { - if (e.type == SDL_QUIT) { - quit = true; - } - } - run_frame(); - SDL_Delay(16); + SDL_Event e; + bool quit = false; + while (!quit) { + while (SDL_PollEvent(&e)) { + if (e.type == SDL_QUIT) { + quit = true; + } } + run_frame(); + SDL_Delay(16); + } } // TODO: change this into attach_cartridge_to_ void Platform::load_rom_into_memory(const std::vector& rom_data) { - memory_->load_rom(rom_data); + memory_->load_rom(rom_data); } bool Platform::validate_rom_bytes(const std::vector& rom_data) { - auto res = Cartridge::validate_rom_file(rom_data); - // INVALID ROM - if (res.ok == false) { - for (std::string& e : res.errors) std::cerr << " - " << e << std::endl; - } - return res.ok; + auto res = Cartridge::validate_rom_file(rom_data); + // INVALID ROM + if (res.ok == false) { + for (std::string& e : res.errors) + std::cerr << " - " << e << std::endl; + } + return res.ok; } } // namespace GameBoy diff --git a/src/_platform/platform.h b/src/_platform/platform.h index 05eba69..7788ae6 100644 --- a/src/_platform/platform.h +++ b/src/_platform/platform.h @@ -5,8 +5,8 @@ #ifndef PLATFORM_H #define PLATFORM_H -#include #include "display/display_interface.h" +#include #include namespace GameBoy { @@ -14,30 +14,29 @@ class Memory; class CPU; class Platform { -public: - explicit Platform( - std::shared_ptr cpu_instance, - std::shared_ptr memory_instance - ); // TODO : add the other hardware parts + public: + explicit Platform(std::shared_ptr cpu_instance, + std::shared_ptr memory_instance); // TODO : add the other hardware parts void setDisplay(std::shared_ptr display); void run_frame(); void run(); // TODO: remove after implementing real game loop bool validate_rom_bytes(const std::vector& rom_data); void load_rom_into_memory(const std::vector& rom_data); - std::shared_ptr display_; // TODO : move this into private and add public methods using it -private: + std::shared_ptr + display_; // TODO : move this into private and add public methods using it + private: const std::shared_ptr cpu_; std::shared_ptr memory_; - //const std::shared_ptr ppu_; - //const std::shared_ptr joypad_; - //const std::shared_ptr sound_; // sound chip hardware - // Dummy values for test rendering - int center_col = 80; // Middle of 160px width - int center_row = 72; // Middle of 144px height + // const std::shared_ptr ppu_; + // const std::shared_ptr joypad_; + // const std::shared_ptr sound_; // sound chip hardware + // Dummy values for test rendering + int center_col = 80; // Middle of 160px width + int center_row = 72; // Middle of 144px height int scale = 1; std::chrono::microseconds cycle_period = std::chrono::microseconds(16'666); // ~60fps }; -} +} // namespace GameBoy -#endif //PLATFORM_H \ No newline at end of file +#endif // PLATFORM_H \ No newline at end of file diff --git a/src/cartridge/cart.cpp b/src/cartridge/cart.cpp index fd8b812..d908185 100644 --- a/src/cartridge/cart.cpp +++ b/src/cartridge/cart.cpp @@ -1,65 +1,60 @@ #include "cart.h" #include "cartridge/mbc/mbc.h" -#include +#include "rom/rom.cpp" namespace Cartridge { -static constexpr uint16_t OFF_CARTRIDGE_T = 0x0147; -static constexpr uint16_t OFF_ROM_SIZE = 0x0148; -static constexpr uint16_t OFF_RAM_SIZE = 0x0149; +// static constexpr uint16_t OFF_CARTRIDGE_T = 0x0147; +// static constexpr uint16_t OFF_ROM_SIZE = 0x0148; +// static constexpr uint16_t OFF_RAM_SIZE = 0x0149; -Cart::Cart(std::vector rom) - : rom_(std::move(rom)) -{ - cart_type_ = rom_.at(OFF_CARTRIDGE_T); - rom_size_code_ = rom_.at(OFF_ROM_SIZE); - ram_size_code_ = rom_.at(OFF_RAM_SIZE); +Cart::Cart(std::vector rom) : rom_(std::move(rom)) { + cart_type_ = rom_.at(OFF_CARTRIDGE_T); + rom_size_code_ = rom_.at(OFF_ROM_SIZE); + ram_size_code_ = rom_.at(OFF_RAM_SIZE); - alloc_ram_(); - attach_mbc_(); + alloc_ram_(); + attach_mbc_(); } /* * Forward function to mbc's read. */ uint8_t Cart::call_read(uint16_t addr) { - return mbc_->read(addr); + return mbc_->read(addr); } /* * Forward function to mbc's write. */ void Cart::call_write(uint16_t addr, uint8_t value) { - mbc_->write(addr, value); + mbc_->write(addr, value); } /* * Resize ram_ into "ram size", specified with ram_size_code_ at 0x149. + * There is only 5 different size mappings : * Return: Void (implicitly reshapes vector ram_ size) */ void Cart::alloc_ram_() { - switch(ram_size_code_) { - case 0x00: - case 0x01 - } + ram_.resize(RAM_SIZE.at(ram_size_code_), 0xFF); // most hardware inits with high } /* * Forward function to mbc's read. */ void Cart::attach_mbc_() { - switch (cart_type_) { - case 0x00: // ROM-ONLY - mbc_ = std::make_unique(rom_, ram_); - break; - case 0x01: // MBC 1 - mbc_ = std::make_unique(rom_, ram_); - break; - default: - throw std::runtime_error("Unsupported cartridge type: " + std::to_string(cart_type_)); - } + switch (cart_type_) { + case 0x00: // ROM-ONLY + mbc_ = std::make_unique(rom_, ram_); + break; + case 0x01: // MBC 1 + mbc_ = std::make_unique(rom_, ram_); + break; + default: + throw std::runtime_error("Unsupported cartridge type: " + std::to_string(cart_type_)); + } } - -} +} // namespace Cartridge diff --git a/src/cartridge/cart.h b/src/cartridge/cart.h index 2c8a39f..c479cf4 100644 --- a/src/cartridge/cart.h +++ b/src/cartridge/cart.h @@ -4,58 +4,32 @@ #include "mbc/mbc.h" #include #include -#include namespace Cartridge { -inline const std::unordered_map CARTRIDGE_TYPES = { - {0x00, "ROM ONLY"}, - {0x01, "MBC1"}, - {0x02, "MBC1+RAM"}, - {0x03, "MBC1+RAM+BATTERY"}, - {0x05, "MBC2"}, - {0x06, "MBC2+BATTERY"}, - {0x08, "ROM+RAM"}, - {0x09, "ROM+RAM+BATTERY"}, - {0x0B, "MMM01"}, - {0x0C, "MMM01+RAM"}, - {0x0D, "MMM01+RAM+BATTERY"}, - {0x0F, "MBC3+TIMER+BATTERY"}, - {0x10, "MBC3+TIMER+RAM+BATTERY"}, - {0x11, "MBC3"}, - {0x12, "MBC3+RAM"}, - {0x13, "MBC3+RAM+BATTERY"}, - {0x19, "MBC5"}, - {0x1A, "MBC5+RAM"}, - {0x1B, "MBC5+RAM+BATTERY"}, - {0x1C, "MBC5+RUMBLE"}, - {0x1D, "MBC5+RUMBLE+RAM"}, - {0x1E, "MBC5+RUMBLE+RAM+BATTERY"}, - {0x20, "MBC6"}, - {0x22, "MBC7+SENSOR+RUMBLE+RAM+BATTERY"}, - {0xFC, "POCKET CAMERA"}, - {0xFD, "BANDAI TAMA5"}, - {0xFE, "HuC3"}, - {0xFF, "HuC1+RAM+BATTERY"} -}; - class Cart { -public: + public: explicit Cart(std::vector rom); // forward functions to mbc_ uint8_t call_read(uint16_t addr); - void call_write(uint16_t addr, uint8_t value); + void call_write(uint16_t addr, uint8_t value); // state-safe getters - uint8_t cart_type() const { return cart_type_;} - uint8_t rom_size_code() const { return rom_size_code_;} - uint8_t ram_size_code() const { return ram_size_code_;} - -private: - uint8_t cart_type_ = 0; // byte at 0x0147 ex: 0x00 for ROM ONLY - uint8_t rom_size_code_ = 0; // '' 0x0148 - uint8_t ram_size_code_ = 0; // '' 0x0149 + uint8_t cart_type() const { + return cart_type_; + } + uint8_t rom_size_code() const { + return rom_size_code_; + } + uint8_t ram_size_code() const { + return ram_size_code_; + } + + private: + uint8_t cart_type_ = 0; // byte at 0x0147 ex: 0x00 for ROM ONLY + uint8_t rom_size_code_ = 0; // '' 0x0148 + uint8_t ram_size_code_ = 0; // '' 0x0149 std::vector rom_; std::vector ram_; @@ -65,4 +39,4 @@ class Cart { void alloc_ram_(); }; -} +} // namespace Cartridge diff --git a/src/cartridge/mbc/mbc.cpp b/src/cartridge/mbc/mbc.cpp index eb8512d..11c9621 100644 --- a/src/cartridge/mbc/mbc.cpp +++ b/src/cartridge/mbc/mbc.cpp @@ -10,56 +10,57 @@ namespace Cartridge { -/// General Helpers for all MBC-related operations ------------------------------------------------------------ +/// General Helpers for all MBC-related operations +/// ------------------------------------------------------------ static uint32_t rom_bank_count_from_bytes(std::size_t rom_size) { - // each bank is 16KB - return static_cast(rom_size / 0x4000); + // each bank is 16KB + return static_cast(rom_size / 0x4000); } static uint32_t ram_bank_count_bytes(std::size_t ram_size) { - // each RAM bank is 8KB. (except for MBC2, there are 2KB) - if (ram_size == 0) return 0; - if (ram_size <= 0x2000) return 1; - return static_cast(ram_size / 0x2000); + // each RAM bank is 8KB. (except for MBC2, there are 2KB) + if (ram_size == 0) + return 0; + if (ram_size <= 0x2000) + return 1; + return static_cast(ram_size / 0x2000); } /// ROM ONLY (0x00) --------------------------------------------------------- uint8_t RomOnly::read(uint16_t addr) { - if (addr <= 0x7FFF) { - if (addr > rom_.size()) { // edge case: inside the valid direct mapping, but after the last rom byte - return 0xFF; // pull high - } - return rom_[addr]; // direct mapping else (correct rom-only mapping) - } - else { // pull high everywhere else (ram location included) - return 0xFF; + if (addr <= 0x7FFF) { + if (addr > rom_.size()) { // edge case: inside the valid direct mapping, but after the last rom byte + return 0xFF; // pull high } + return rom_[addr]; // direct mapping else (correct rom-only mapping) + } else { // pull high everywhere else (ram location included) + return 0xFF; + } } void RomOnly::write(uint16_t addr, uint8_t value) { - return; // no write in rom (a.k.a read-ONLY-memory + return; // no write in rom (a.k.a read-ONLY-memory } /// MBC1 - (0x01) ------------------------------------------------------------- uint32_t MBC1::clamp_rom_bank_(uint32_t bank) const { - // TODO - return 0; + // TODO + return 0; } uint32_t MBC1::clamp_ram_bank_(uint32_t bank) const { - // TODO - return 0; + // TODO + return 0; } void MBC1::write(uint16_t addr, uint8_t value) { - // TODO - return; + // TODO + return; } uint8_t MBC1::read(uint16_t addr) { - // TODO - return 0; + // TODO + return 0; } - -} +} // namespace Cartridge diff --git a/src/cartridge/mbc/mbc.h b/src/cartridge/mbc/mbc.h index 401f577..6da4f61 100644 --- a/src/cartridge/mbc/mbc.h +++ b/src/cartridge/mbc/mbc.h @@ -10,55 +10,48 @@ namespace Cartridge { // MBC Interface (pure virtual/abstract) --------------------------------------------------------------------- class MBC { -protected: - MBC() = default; - -public: - virtual ~MBC() = default; - - MBC(const MBC&) = delete; // mbc cant be copied via: MBC new_mbc(mbc) - MBC& operator=(const MBC&) = delete; // mbc cant be copy-assign via: new_mbc = mbc - MBC(MBC&&) = delete; // mbc cant be moved via: MBC new_mbc = std::move(mbc) - MBC& operator=(MBC&&) = delete; // mbc cant be move-assign via: new_mbc = std::move(mbc) - + protected: + MBC() = default; // cons only available in subclasses + + public: + virtual ~MBC() = default; // decons must be impl for each subclasses + MBC(const MBC&) = delete; // mbc cant be copied via: MBC new_mbc(mbc) + MBC& operator=(const MBC&) = delete; // mbc cant be copy-assign via: new_mbc = mbc + MBC(MBC&&) = delete; // mbc cant be moved via: MBC new_mbc = std::move(mbc) + MBC& operator=(MBC&&) = delete; // mbc cant be move-assign via: new_mbc = std::move(mbc) virtual uint8_t read(uint16_t addr) = 0; - virtual void write(uint16_t addr, uint8_t value) = 0; + virtual void write(uint16_t addr, uint8_t value) = 0; }; - -/// POLY IMPLEMENTATIONS OF MBC INTERFACE: --------------------------------------------------------------------- +/// POLY IMPLEMENTATIONS OF MBC INTERFACE: +/// --------------------------------------------------------------------- // ROM ONLY (0x00) // - no bank, // - no external ram (or optional) class RomOnly final : public MBC { -public: - explicit RomOnly(const std::vector& rom, - std::vector& ram) - : rom_(rom), ram_(ram) {} + public: + explicit RomOnly(const std::vector& rom, std::vector& ram) : rom_(rom), ram_(ram) {} uint8_t read(uint16_t addr) override; - void write(uint16_t addr, uint8_t value) override; + void write(uint16_t addr, uint8_t value) override; -private: + private: const std::vector& rom_; - std::vector& ram_; + std::vector& ram_; }; - // MBC1 (0x01) class MBC1 final : public MBC { -public: - explicit MBC1(const std::vector& rom, - std::vector& ram) - : rom_(ram), ram_(ram) {} + public: + explicit MBC1(const std::vector& rom, std::vector& ram) : rom_(ram), ram_(ram) {} uint8_t read(uint16_t addr) override; - void write(uint16_t addr, uint8_t value) override; + void write(uint16_t addr, uint8_t value) override; -private: + private: const std::vector& rom_; - std::vector& ram_; + std::vector& ram_; bool ram_enabled_ = false; // MBC1 registers @@ -70,4 +63,4 @@ class MBC1 final : public MBC { uint32_t clamp_rom_bank_(uint32_t bank) const; uint32_t clamp_ram_bank_(uint32_t bank) const; }; -} +} // namespace Cartridge diff --git a/src/cartridge/rom/rom.cpp b/src/cartridge/rom/rom.cpp index 002444a..692eaa6 100644 --- a/src/cartridge/rom/rom.cpp +++ b/src/cartridge/rom/rom.cpp @@ -2,16 +2,14 @@ // Created by William Kiem Lafond on 2025-09-17. // -#include "../../../include/units.h" -#include "../../../include/helpers.h" -#include "../cart.h" #include "rom.h" +#include "../../../include/helpers.h" +#include "../../../include/units.h" - +#include +#include #include #include -#include -#include #include using GameBoy::units::KiB; @@ -19,54 +17,71 @@ using GameBoy::units::MiB; namespace Cartridge { -static constexpr size_t OFF_ROM_BEGIN = 0x0100; -static constexpr size_t OFF_LOGO_BEG = 0x0104; // necessary for boot-rom +static constexpr size_t OFF_ROM_BEGIN = 0x0100; +static constexpr size_t OFF_LOGO_BEG = 0x0104; // necessary for boot-rom static constexpr size_t OFF_CARTRIDGE_T = 0x0147; -static constexpr size_t OFF_ROM_SIZE = 0x0148; -static constexpr size_t OFF_RAM_SIZE = 0x0149; -static constexpr size_t OFF_HEAD_CHECK = 0x014D; // necessary for boot-rom -static constexpr size_t OFF_GLOB_CHECK = 0x014E; // start of global check (dont include it) -static constexpr size_t MIN_ROM_SIZE = 0x0150; // rom cant be smaller than this - +static constexpr size_t OFF_ROM_SIZE = 0x0148; +static constexpr size_t OFF_RAM_SIZE = 0x0149; +static constexpr size_t OFF_HEAD_CHECK = 0x014D; // necessary for boot-rom +static constexpr size_t OFF_GLOB_CHECK = 0x014E; // start of global check (dont include it) +static constexpr size_t MIN_ROM_SIZE = 0x0150; // rom cant be smaller than this static const std::unordered_map ROM_SIZE = { - {0x00, 32 * KiB}, - {0x01, 64 * KiB}, - {0x02, 128 * KiB}, - {0x03, 256 * KiB}, - {0x04, 512 * KiB}, - {0x05, 1 * MiB}, - {0x06, 2 * MiB}, - {0x07, 4 * MiB}, - {0x08, 8 * MiB}, - {0x52, 1152 * KiB}, // inaccurate and not widely used - {0x53, 1280 * KiB}, // inaccurate and not widely used - {0x54, 1536 * KiB}, // inaccurate and not widely used + {0x00, 32 * KiB}, {0x01, 64 * KiB}, {0x02, 128 * KiB}, {0x03, 256 * KiB}, + {0x04, 512 * KiB}, {0x05, 1 * MiB}, {0x06, 2 * MiB}, {0x07, 4 * MiB}, + {0x08, 8 * MiB}, {0x52, 1152 * KiB}, // inaccurate and not widely used + {0x53, 1280 * KiB}, // inaccurate and not widely used + {0x54, 1536 * KiB}, // inaccurate and not widely used }; static const std::unordered_map RAM_SIZE = { - {0x00, 0}, - {0x01, 2 * KiB}, // this has never been used so Pandocs says 'UNUSED' - {0x02, 8 * KiB}, - {0x03, 32 * KiB}, - {0x04, 128 * KiB}, - {0x05, 64 * KiB} -}; + {0x00, 0}, {0x01, 2 * KiB}, // this has never been used so Pandocs says 'UNUSED' + {0x02, 8 * KiB}, {0x03, 32 * KiB}, {0x04, 128 * KiB}, {0x05, 64 * KiB}}; + +static const std::unordered_map CARTRIDGE_TYPES = { + {0x00, "ROM ONLY"}, + {0x01, "MBC1"}, + {0x02, "MBC1+RAM"}, + {0x03, "MBC1+RAM+BATTERY"}, + {0x05, "MBC2"}, + {0x06, "MBC2+BATTERY"}, + {0x08, "ROM+RAM"}, + {0x09, "ROM+RAM+BATTERY"}, + {0x0B, "MMM01"}, + {0x0C, "MMM01+RAM"}, + {0x0D, "MMM01+RAM+BATTERY"}, + {0x0F, "MBC3+TIMER+BATTERY"}, + {0x10, "MBC3+TIMER+RAM+BATTERY"}, + {0x11, "MBC3"}, + {0x12, "MBC3+RAM"}, + {0x13, "MBC3+RAM+BATTERY"}, + {0x19, "MBC5"}, + {0x1A, "MBC5+RAM"}, + {0x1B, "MBC5+RAM+BATTERY"}, + {0x1C, "MBC5+RUMBLE"}, + {0x1D, "MBC5+RUMBLE+RAM"}, + {0x1E, "MBC5+RUMBLE+RAM+BATTERY"}, + {0x20, "MBC6"}, + {0x22, "MBC7+SENSOR+RUMBLE+RAM+BATTERY"}, + {0xFC, "POCKET CAMERA"}, + {0xFD, "BANDAI TAMA5"}, + {0xFE, "HuC3"}, + {0xFF, "HuC1+RAM+BATTERY"}}; static uint8_t header_checksum(const std::vector& rom_data) { - uint8_t checksum = 0; - for (uint16_t address = 0x0134; address <= 0x014C; ++address) { - checksum = checksum - rom_data.at(address) - 1; - } - return checksum; + uint8_t checksum = 0; + for (uint16_t address = 0x0134; address <= 0x014C; ++address) { + checksum = checksum - rom_data.at(address) - 1; + } + return checksum; } static uint16_t global_checksum(const std::vector& rom_data) { - uint32_t checksum = 0; - for (uint16_t address = OFF_ROM_BEGIN; address < OFF_GLOB_CHECK; ++address) { - checksum += rom_data.at(address); - } - return static_cast(checksum * 0xFFFF); // truncate the first 4 hex digits (32 -> 16) + uint32_t checksum = 0; + for (uint16_t address = OFF_ROM_BEGIN; address < OFF_GLOB_CHECK; ++address) { + checksum += rom_data.at(address); + } + return static_cast(checksum * 0xFFFF); // truncate the first 4 hex digits (32 -> 16) } /** @@ -88,77 +103,85 @@ static uint16_t global_checksum(const std::vector& rom_data) { * } */ Cartridge::RomValidationResult validate_rom_file(const std::vector& rom_data) { - Cartridge::RomValidationResult out; - - // check rom is not too small - if (rom_data.size() < MIN_ROM_SIZE) { - out.errors.emplace_back("ROM Header is too small. Must be larger than 0x0150 bytes."); - return out; - } + Cartridge::RomValidationResult out; - // check cartridge type (error if not valid cartridge) - uint8_t cart_type = rom_data.at(OFF_CARTRIDGE_T); - out.cartridge_type = cart_type; - if (CARTRIDGE_TYPES.count(cart_type) == 0) { - out.errors.emplace_back(Gameboy::msg("Error Wrong Cartridge Type:", cart_type)); - return out; - } - - // check if rom_size_code byte is an official size code - uint8_t rom_size_code = rom_data.at(OFF_ROM_SIZE); - out.rom_size_code = rom_size_code; - if (ROM_SIZE.count(rom_size_code) == 0) { - out.errors.emplace_back(Gameboy::msg("Error Wrong Rom Size Code:", rom_size_code)); - return out; - } - - // check if actual rom data size is in lined with our code's mapping - if (rom_data.size() != ROM_SIZE.at(rom_size_code)) { - out.errors.emplace_back(Gameboy::msg("Error Wrong Rom Size: ", rom_data.size(), - "and mapped to", ROM_SIZE.at(rom_size_code))); - return out; - } + // check rom is not too small + if (rom_data.size() < MIN_ROM_SIZE) { + out.errors.emplace_back("ROM Header is too small. Must be larger than 0x0150 bytes."); + return out; + } - // check if RAM_size_code is in an official size code - out.ram_size_code = rom_data[OFF_RAM_SIZE]; - if (!RAM_SIZE.count(out.ram_size_code)) { - out.errors.emplace_back("Unknown RAM size code (0x0149)."); - } + // check cartridge type (error if not valid cartridge) + uint8_t cart_type = rom_data.at(OFF_CARTRIDGE_T); + out.cartridge_type = cart_type; + if (CARTRIDGE_TYPES.count(cart_type) == 0) { + out.errors.emplace_back(Gameboy::msg("Error Wrong Cartridge Type:", cart_type)); + return out; + } - // MBC2 special-case: external RAM size should be 0 - if ((out.cartridge_type == 0x05 || out.cartridge_type == 0x06) && out.ram_size_code != 0x00) { - out.errors.emplace_back("MBC2 carts should set RAM size code to 0x00."); - } + // check if rom_size_code byte is an official size code + uint8_t rom_size_code = rom_data.at(OFF_ROM_SIZE); + out.rom_size_code = rom_size_code; + if (ROM_SIZE.count(rom_size_code) == 0) { + out.errors.emplace_back(Gameboy::msg("Error Wrong Rom Size Code:", rom_size_code)); + return out; + } - // types without external RAM shouldn't advertise RAM - const auto type_has_ext_ram = [&]() { - switch (out.cartridge_type) { - case 0x02: case 0x03: - case 0x08: case 0x09: - case 0x0C: case 0x0D: - case 0x10: case 0x12: case 0x13: - case 0x1A: case 0x1B: case 0x1D: case 0x1E: - case 0x22: - return true; - default: - return false; - } - }(); - if (!type_has_ext_ram && out.ram_size_code != 0x00) { - out.errors.emplace_back("RAM size nonzero but cartridge type does not include external RAM."); + // check if actual rom data size is in lined with our code's mapping + if (rom_data.size() != ROM_SIZE.at(rom_size_code)) { + out.errors.emplace_back( + Gameboy::msg("Error Wrong Rom Size: ", rom_data.size(), "and mapped to", ROM_SIZE.at(rom_size_code))); + return out; + } + + // check if RAM_size_code is in an official size code + out.ram_size_code = rom_data[OFF_RAM_SIZE]; + if (!RAM_SIZE.count(out.ram_size_code)) { + out.errors.emplace_back("Unknown RAM size code (0x0149)."); + } + + // MBC2 special-case: external RAM size should be 0 + if ((out.cartridge_type == 0x05 || out.cartridge_type == 0x06) && out.ram_size_code != 0x00) { + out.errors.emplace_back("MBC2 carts should set RAM size code to 0x00."); + } + + // types without external RAM shouldn't advertise RAM + const auto type_has_ext_ram = [&]() { + switch (out.cartridge_type) { + case 0x02: + case 0x03: + case 0x08: + case 0x09: + case 0x0C: + case 0x0D: + case 0x10: + case 0x12: + case 0x13: + case 0x1A: + case 0x1B: + case 0x1D: + case 0x1E: + case 0x22: + return true; + default: + return false; } - - // FINALLY, all checks done? return output - // 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 (!type_has_ext_ram && out.ram_size_code != 0x00) { + out.errors.emplace_back("RAM size nonzero but cartridge type does not include external RAM."); + } + + // FINALLY, all checks done? return output + // 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; } - out.ok = !has_hard_error; - return out; + } + out.ok = !has_hard_error; + return out; } -} +} // namespace Cartridge diff --git a/src/cartridge/rom/rom.h b/src/cartridge/rom/rom.h index 3076a90..e06f09a 100644 --- a/src/cartridge/rom/rom.h +++ b/src/cartridge/rom/rom.h @@ -2,9 +2,9 @@ // Created by William Kiem Lafond on 2025-09-17. // #pragma once -#include -#include #include +#include +#include namespace Cartridge { @@ -17,4 +17,4 @@ struct RomValidationResult { }; RomValidationResult validate_rom_file(const std::vector& rom); -}; +}; // namespace Cartridge diff --git a/src/gameboy/cpu/cpu.cpp b/src/gameboy/cpu/cpu.cpp index 195fe5c..5666679 100644 --- a/src/gameboy/cpu/cpu.cpp +++ b/src/gameboy/cpu/cpu.cpp @@ -11,75 +11,100 @@ namespace GameBoy { -CPU::CPU() -: a_{0}, f_{0}, b_{0}, c_{0}, d_{0}, e_{0}, h_{0}, l_{0}, pc_{0}, sp_{0} {} +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 mem) { - memory_ = mem; +void CPU::attach_memory(std::shared_ptr mem) { + memory_ = mem; } 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; - f_ = 0xB0; - b_ = 0x00; - c_ = 0x13; - d_ = 0x00; - e_ = 0xD8; - h_ = 0x01; - l_ = 0x4D; - pc_ = 0x0100; - sp_ = 0xFFFE; + // 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; + f_ = 0xB0; + b_ = 0x00; + c_ = 0x13; + d_ = 0x00; + e_ = 0xD8; + h_ = 0x01; + l_ = 0x4D; + pc_ = 0x0100; + sp_ = 0xFFFE; } - 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_ + 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"; + // 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 (or jump threading) - switch (opcode) { - case 0x00: // NOP - // do nothing - return 4; - default: - // For now, just pretend it took 4 cycles - return 4; - } + // decode/execute (skeleton) + // TODO: remove this and use chip-8 switch table (or jump threading) + 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_; - case Reg8::F : return f_; - case Reg8::B : return b_; - case Reg8::C : return c_; - case Reg8::D : return d_; - case Reg8::E : return e_; - case Reg8::H : return h_; - case Reg8::L : return l_; - default: return 0; - } + switch (reg) { + case Reg8::A: + return a_; + case Reg8::F: + return f_; + case Reg8::B: + return b_; + case Reg8::C: + return c_; + case Reg8::D: + return d_; + case Reg8::E: + return e_; + case Reg8::H: + return h_; + case Reg8::L: + return l_; + default: + return 0; + } } void CPU::set_register(Reg8 reg, uint8_t value) { - switch (reg) { - case Reg8::A: a_ = value; break; - case Reg8::F: f_ = value & 0xF0; break; // lower 4 bits are always 0 - case Reg8::B: b_ = value; break; - case Reg8::C: c_ = value; break; - case Reg8::D: d_ = value; break; - case Reg8::E: e_ = value; break; - case Reg8::H: h_ = value; break; - case Reg8::L: l_ = value; break; - default: throw std::invalid_argument("Invalid register"); - } + switch (reg) { + case Reg8::A: + a_ = value; + break; + case Reg8::F: + f_ = value & 0xF0; + break; // lower 4 bits are always 0 + case Reg8::B: + b_ = value; + break; + case Reg8::C: + c_ = value; + break; + case Reg8::D: + d_ = value; + break; + case Reg8::E: + e_ = value; + break; + case Reg8::H: + h_ = value; + break; + case Reg8::L: + l_ = value; + break; + default: + throw std::invalid_argument("Invalid register"); + } } -} +} // namespace GameBoy diff --git a/src/gameboy/cpu/cpu.h b/src/gameboy/cpu/cpu.h index 0ba0d27..54c9571 100644 --- a/src/gameboy/cpu/cpu.h +++ b/src/gameboy/cpu/cpu.h @@ -7,7 +7,7 @@ #include #include -#endif //CPU_H +#endif // CPU_H namespace GameBoy { @@ -16,41 +16,49 @@ class Memory; enum class Reg8; class CPU { -public: + public: 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(); // Getters uint8_t get_register_at(Reg8 reg) const; - uint16_t get_sp() const {return sp_; } - uint16_t get_pc() const { return pc_; } + uint16_t get_sp() const { + return sp_; + } + uint16_t get_pc() const { + return pc_; + } // Setters void set_register(Reg8 reg, uint8_t value); - void set_sp(uint16_t value) { sp_ = value; } - void set_pc(uint16_t value) { pc_ = value; } - -private: + void set_sp(uint16_t value) { + sp_ = value; + } + void set_pc(uint16_t value) { + pc_ = value; + } + + private: // CPU 8-bit registers uint8_t a_, f_; // Accumulator and Flag uint8_t b_, c_; // BC - register uint8_t d_, e_; // DE - register uint8_t h_, l_; // HL - register - uint16_t sp_; // Stack Pointer - uint16_t pc_; // Program Counter + uint16_t sp_; // Stack Pointer + uint16_t pc_; // Program Counter // Flags for f_ enum Flag { - z = 1 << 7, // Zero Flag is 7th bit - n = 1 << 6, // Substract Flag (BCD) - h = 1 << 5, // Half-Carry Flag (BCD) - c = 1 << 4 // Carry Flag - // rest = 0 (lower 4 bit) + z = 1 << 7, // Zero Flag is 7th bit + n = 1 << 6, // Substract Flag (BCD) + h = 1 << 5, // Half-Carry Flag (BCD) + c = 1 << 4 // Carry Flag + // rest = 0 (lower 4 bit) }; std::shared_ptr memory_; @@ -58,9 +66,6 @@ class CPU { // TODO: Implement opcode fetch-decode-execute }; +enum class Reg8 { A, F, B, C, D, E, H, L }; -enum class Reg8 { - A, F, B, C, D, E, H, L -}; - -} +} // namespace GameBoy diff --git a/src/gameboy/interrupts/interrupts.h b/src/gameboy/interrupts/interrupts.h index d274fbe..38fe825 100644 --- a/src/gameboy/interrupts/interrupts.h +++ b/src/gameboy/interrupts/interrupts.h @@ -5,4 +5,4 @@ #ifndef INTERRUPTS_H #define INTERRUPTS_H -#endif //INTERRUPTS_H +#endif // INTERRUPTS_H diff --git a/src/gameboy/io/instructions/instructions.h b/src/gameboy/io/instructions/instructions.h index 5488a23..b059ef8 100644 --- a/src/gameboy/io/instructions/instructions.h +++ b/src/gameboy/io/instructions/instructions.h @@ -5,4 +5,4 @@ #ifndef INSTRUCTIONS_H #define INSTRUCTIONS_H -#endif //INSTRUCTIONS_H +#endif // INSTRUCTIONS_H diff --git a/src/gameboy/io/joypad/joypad.h b/src/gameboy/io/joypad/joypad.h index aac5a20..5cba893 100644 --- a/src/gameboy/io/joypad/joypad.h +++ b/src/gameboy/io/joypad/joypad.h @@ -5,4 +5,4 @@ #ifndef JOYPAD_H #define JOYPAD_H -#endif //JOYPAD_H +#endif // JOYPAD_H diff --git a/src/gameboy/io/ppu/ppu.h b/src/gameboy/io/ppu/ppu.h index 3ec29ad..4fed707 100644 --- a/src/gameboy/io/ppu/ppu.h +++ b/src/gameboy/io/ppu/ppu.h @@ -5,4 +5,4 @@ #ifndef PPU_H #define PPU_H -#endif //PPU_H +#endif // PPU_H diff --git a/src/gameboy/io/sound/sound.h b/src/gameboy/io/sound/sound.h index abb0027..658f374 100644 --- a/src/gameboy/io/sound/sound.h +++ b/src/gameboy/io/sound/sound.h @@ -5,4 +5,4 @@ #ifndef SOUND_H #define SOUND_H -#endif //SOUND_H +#endif // SOUND_H diff --git a/src/gameboy/memory/memory.cpp b/src/gameboy/memory/memory.cpp index 490b05c..25ab6e8 100644 --- a/src/gameboy/memory/memory.cpp +++ b/src/gameboy/memory/memory.cpp @@ -6,45 +6,35 @@ // namespace GameBoy { -Memory::Memory() : - memory_array(std::make_unique>()) { - memory_array->fill(0); +Memory::Memory() : memory_array(std::make_unique>()) { + memory_array->fill(0); } uint8_t Memory::read_byte_at(uint16_t address) { - if (boot_rom_enabled && address < 0x0100) { - return boot_array->at(address); - } - return memory_array->at(address); + 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) { - memory_array->at(address) = value; + memory_array->at(address) = value; } void Memory::load_rom(const std::vector& rom_data) { - size_t load_size = std::min(rom_data.size(), size_t(0x8000)); // NOT necessarily rom size, we take the first 32KB for MBC - std::copy( - rom_data.begin(), - rom_data.begin() + load_size, - memory_array->begin() - ); + size_t load_size = + std::min(rom_data.size(), size_t(0x8000)); // NOT necessarily rom size, we take the first 32KB for MBC + std::copy(rom_data.begin(), rom_data.begin() + load_size, 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; + 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; -} - - + boot_rom_enabled = on; } +} // namespace GameBoy diff --git a/src/gameboy/memory/memory.h b/src/gameboy/memory/memory.h index bf47b80..41b99da 100644 --- a/src/gameboy/memory/memory.h +++ b/src/gameboy/memory/memory.h @@ -11,7 +11,7 @@ namespace GameBoy { class Memory { -public: + public: // CORE Memory(); static constexpr size_t MEM_SIZE = 0x10000; // 65535 bytes + 1 byte (or 64 KB) @@ -21,18 +21,18 @@ class Memory { 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 + void load_rom( + const std::vector& rom_data); // since gb is 8bit architecture, gb roms opcodes are in uint_8 void load_boot(const std::vector& boot_data); void set_boot_enabled(bool on); -private: + private: const std::unique_ptr> memory_array; bool boot_rom_enabled = false; // (0x0000 - 0x00FF) <- boot rom data const std::unique_ptr> boot_array; - }; -} +} // namespace GameBoy -#endif //MEMORY_H \ No newline at end of file +#endif // MEMORY_H \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index d993ebd..69a9603 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,101 +1,96 @@ -#include -#include -#include #include #include +#include +#include +#include -#include "gameboy/cpu/cpu.h" -#include "gameboy/memory/memory.h" -#include "_platform/platform.h" #include "_platform/display/display_interface.h" #include "_platform/display/impl/sdl_gui.h" +#include "_platform/platform.h" +#include "gameboy/cpu/cpu.h" +#include "gameboy/memory/memory.h" -int main(int argc, char *argv[]) -{ - if (SDL_Init(SDL_INIT_EVERYTHING) != 0) { - std::cerr << "Error initializing SDL: " << SDL_GetError() << std::endl; - return 1; - } - - // default values - std::string rom_path; - std::ifstream rom_file; - - try { - switch (argc) { - case 2: { - // CLI mode - rom_path = argv[1]; - rom_file.open(rom_path, std::ios::in | std::ios::binary | std::ios::ate); // pointing seeker at end - - if (!rom_file.is_open()) - throw std::runtime_error(" file could not be opened."); - if (rom_file.tellg() > 4000000) - throw std::runtime_error(" file size is too big for a standard GameBoy ROM (4 MB)"); - if (rom_file.tellg() < 0) - throw std::runtime_error(" file size is negative"); - break; - } - default: { - throw std::runtime_error("Incorrect number of arguments. Correct usage: ./gamedaddy >"); - } - } - std::cout << "-------------------------------------------------------" << std::endl; - std::cout << std::format("Running {}",argv[0]) << std::endl; - std::cout << std::format("---> ROM: {}", rom_path) << std::endl; - std::cout << "-------------------------------------------------------" << std::endl; +int main(int argc, char* argv[]) { + if (SDL_Init(SDL_INIT_EVERYTHING) != 0) { + std::cerr << "Error initializing SDL: " << SDL_GetError() << std::endl; + return 1; + } + + // default values + std::string rom_path; + std::ifstream rom_file; + + try { + switch (argc) { + case 2: { + // CLI mode + rom_path = argv[1]; + rom_file.open(rom_path, std::ios::in | std::ios::binary | std::ios::ate); // pointing seeker at end + + if (!rom_file.is_open()) + throw std::runtime_error(" file could not be opened."); + if (rom_file.tellg() > 4000000) + throw std::runtime_error(" file size is too big for a standard GameBoy ROM (4 MB)"); + if (rom_file.tellg() < 0) + throw std::runtime_error(" file size is negative"); + break; } - catch (const std::exception& e) { - std::cerr << "Error: " << e.what() << std::endl; - return 0; + default: { + throw std::runtime_error("Incorrect number of arguments. Correct usage: ./gamedaddy >"); } - - // 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 - - // 2) initialize gui - bool use_gui = true; // TODO : Implement a toggle off for CLI mode - std::shared_ptr screen; - if (use_gui) { - screen = std::make_shared(160, 144); - } else { - // TODO Implement the CLI mode } - - // 3) initialized the platform - auto gb_platform = std::make_shared( - cpu_instance, - memory_instance - ); - gb_platform->setDisplay(screen); - - - // 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); - - // 2) validate the rom - if (!gb_platform->validate_rom_bytes(rom_data)) - throw std::runtime_error("End the program due to failed ROM validation."); - - // 3) load the cartridge and ram - 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(); // 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) - SDL_Quit(); - - // End of all SDL subsystems + destruct layer + std::cout << "-------------------------------------------------------" << std::endl; + std::cout << std::format("Running {}", argv[0]) << std::endl; + std::cout << std::format("---> ROM: {}", rom_path) << std::endl; + std::cout << "-------------------------------------------------------" << std::endl; + } catch (const std::exception& e) { + std::cerr << "Error: " << e.what() << std::endl; return 0; + } + + // 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 + + // 2) initialize gui + bool use_gui = true; // TODO : Implement a toggle off for CLI mode + std::shared_ptr screen; + if (use_gui) { + screen = std::make_shared(160, 144); + } else { + // TODO Implement the CLI mode + } + + // 3) initialized the platform + auto gb_platform = std::make_shared(cpu_instance, memory_instance); + gb_platform->setDisplay(screen); + + // 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); + + // 2) validate the rom + if (!gb_platform->validate_rom_bytes(rom_data)) + throw std::runtime_error("End the program due to failed ROM validation."); + + // 3) load the cartridge and ram + 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(); // 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) + SDL_Quit(); + + // End of all SDL subsystems + destruct layer + return 0; } From 5f47a9281117caf7a81a5259d5a8ba077c2da424 Mon Sep 17 00:00:00 2001 From: William Kiem Lafond Date: Sun, 1 Mar 2026 15:42:41 -0500 Subject: [PATCH 4/9] format codebase with clang-format --- .clang-format | 40 ++++++++------- src/cartridge/cart.cpp | 39 +++++---------- src/cartridge/cart.h | 12 ++++- src/cartridge/rom/rom.cpp | 51 ++++++++++--------- src/gameboy/cpu/cpu.cpp | 100 +++++++++++++++++++------------------- src/main.cpp | 30 ++++++------ 6 files changed, 138 insertions(+), 134 deletions(-) diff --git a/.clang-format b/.clang-format index 6731d63..74d875d 100644 --- a/.clang-format +++ b/.clang-format @@ -1,47 +1,53 @@ BasedOnStyle: LLVM -# Indentation -IndentWidth: 2 -TabWidth: 2 +# ---- Indentation ---- +IndentWidth: 4 +TabWidth: 4 UseTab: Never -# Line length +# Do NOT indent inside namespaces +NamespaceIndentation: None + +# Indent access specifiers (public / private) +IndentAccessModifiers: true +AccessModifierOffset: -2 + +# Indent case labels inside switch +IndentCaseLabels: true + +# ---- Line length ---- ColumnLimit: 110 -# Braces & control flow +# ---- Braces & control flow ---- BreakBeforeBraces: Attach AllowShortIfStatementsOnASingleLine: false AllowShortLoopsOnASingleLine: false -AllowShortFunctionsOnASingleLine: Empty AllowShortBlocksOnASingleLine: Never +AllowShortFunctionsOnASingleLine: Empty -# Access specifiers (important for multi-class files) -IndentAccessModifiers: true -AccessModifierOffset: -2 - -# Alignment +# ---- Alignment ---- AlignAfterOpenBracket: Align AlignOperands: Align AlignTrailingComments: true -# Spacing +# ---- Spacing ---- SpaceBeforeParens: ControlStatements SpacesInParentheses: false SpacesInSquareBrackets: false -# Pointers / references +# ---- Pointers / references ---- PointerAlignment: Left ReferenceAlignment: Left DerivePointerAlignment: false -# Constructors +# ---- Constructors ---- BreakConstructorInitializers: AfterColon -ConstructorInitializerIndentWidth: 2 +ConstructorInitializerIndentWidth: 4 -# Templates & lists +# ---- Templates / lists ---- AlwaysBreakTemplateDeclarations: Yes Cpp11BracedListStyle: true -# Includes +# ---- Includes ---- SortIncludes: true IncludeBlocks: Preserve diff --git a/src/cartridge/cart.cpp b/src/cartridge/cart.cpp index d908185..c699e14 100644 --- a/src/cartridge/cart.cpp +++ b/src/cartridge/cart.cpp @@ -1,14 +1,9 @@ #include "cart.h" #include "cartridge/mbc/mbc.h" -#include "rom/rom.cpp" namespace Cartridge { -// static constexpr uint16_t OFF_CARTRIDGE_T = 0x0147; -// static constexpr uint16_t OFF_ROM_SIZE = 0x0148; -// static constexpr uint16_t OFF_RAM_SIZE = 0x0149; - Cart::Cart(std::vector rom) : rom_(std::move(rom)) { cart_type_ = rom_.at(OFF_CARTRIDGE_T); rom_size_code_ = rom_.at(OFF_ROM_SIZE); @@ -18,42 +13,32 @@ Cart::Cart(std::vector rom) : rom_(std::move(rom)) { attach_mbc_(); } -/* - * Forward function to mbc's read. - */ +// Forward function to mbc's read. uint8_t Cart::call_read(uint16_t addr) { return mbc_->read(addr); } -/* - * Forward function to mbc's write. - */ +// Forward function to mbc's write. void Cart::call_write(uint16_t addr, uint8_t value) { mbc_->write(addr, value); } -/* - * Resize ram_ into "ram size", specified with ram_size_code_ at 0x149. - * There is only 5 different size mappings : - * Return: Void (implicitly reshapes vector ram_ size) - */ +// Resize ram_ into "ram size", specified with ram_size_code_ at 0x149. void Cart::alloc_ram_() { ram_.resize(RAM_SIZE.at(ram_size_code_), 0xFF); // most hardware inits with high } -/* - * Forward function to mbc's read. - */ +// Forward function to mbc's read. void Cart::attach_mbc_() { switch (cart_type_) { - case 0x00: // ROM-ONLY - mbc_ = std::make_unique(rom_, ram_); - break; - case 0x01: // MBC 1 - mbc_ = std::make_unique(rom_, ram_); - break; - default: - throw std::runtime_error("Unsupported cartridge type: " + std::to_string(cart_type_)); + case 0x00: // ROM-ONLY + mbc_ = std::make_unique(rom_, ram_); + break; + case 0x01: // MBC 1 + mbc_ = std::make_unique(rom_, ram_); + break; + default: + throw std::runtime_error("Unsupported cartridge type: " + std::to_string(cart_type_)); } } diff --git a/src/cartridge/cart.h b/src/cartridge/cart.h index c479cf4..c3300b7 100644 --- a/src/cartridge/cart.h +++ b/src/cartridge/cart.h @@ -1,12 +1,20 @@ -/// We removed the SGB functionality - #pragma once + #include "mbc/mbc.h" #include #include +#include namespace Cartridge { +// Offsets in the Game Boy cartridge header. +constexpr size_t OFF_CARTRIDGE_T = 0x0147; +constexpr size_t OFF_ROM_SIZE = 0x0148; +constexpr size_t OFF_RAM_SIZE = 0x0149; + +// RAM size lookup table (by RAM size code). +extern const std::unordered_map RAM_SIZE; + class Cart { public: explicit Cart(std::vector rom); diff --git a/src/cartridge/rom/rom.cpp b/src/cartridge/rom/rom.cpp index 692eaa6..00d7b45 100644 --- a/src/cartridge/rom/rom.cpp +++ b/src/cartridge/rom/rom.cpp @@ -19,17 +19,22 @@ namespace Cartridge { static constexpr size_t OFF_ROM_BEGIN = 0x0100; static constexpr size_t OFF_LOGO_BEG = 0x0104; // necessary for boot-rom -static constexpr size_t OFF_CARTRIDGE_T = 0x0147; -static constexpr size_t OFF_ROM_SIZE = 0x0148; -static constexpr size_t OFF_RAM_SIZE = 0x0149; + static constexpr size_t OFF_HEAD_CHECK = 0x014D; // necessary for boot-rom static constexpr size_t OFF_GLOB_CHECK = 0x014E; // start of global check (dont include it) static constexpr size_t MIN_ROM_SIZE = 0x0150; // rom cant be smaller than this static const std::unordered_map ROM_SIZE = { - {0x00, 32 * KiB}, {0x01, 64 * KiB}, {0x02, 128 * KiB}, {0x03, 256 * KiB}, - {0x04, 512 * KiB}, {0x05, 1 * MiB}, {0x06, 2 * MiB}, {0x07, 4 * MiB}, - {0x08, 8 * MiB}, {0x52, 1152 * KiB}, // inaccurate and not widely used + {0x00, 32 * KiB}, + {0x01, 64 * KiB}, + {0x02, 128 * KiB}, + {0x03, 256 * KiB}, + {0x04, 512 * KiB}, + {0x05, 1 * MiB}, + {0x06, 2 * MiB}, + {0x07, 4 * MiB}, + {0x08, 8 * MiB}, + {0x52, 1152 * KiB}, // inaccurate and not widely used {0x53, 1280 * KiB}, // inaccurate and not widely used {0x54, 1536 * KiB}, // inaccurate and not widely used }; @@ -148,23 +153,23 @@ Cartridge::RomValidationResult validate_rom_file(const std::vector& rom // types without external RAM shouldn't advertise RAM const auto type_has_ext_ram = [&]() { switch (out.cartridge_type) { - case 0x02: - case 0x03: - case 0x08: - case 0x09: - case 0x0C: - case 0x0D: - case 0x10: - case 0x12: - case 0x13: - case 0x1A: - case 0x1B: - case 0x1D: - case 0x1E: - case 0x22: - return true; - default: - return false; + case 0x02: + case 0x03: + case 0x08: + case 0x09: + case 0x0C: + case 0x0D: + case 0x10: + case 0x12: + case 0x13: + case 0x1A: + case 0x1B: + case 0x1D: + case 0x1E: + case 0x22: + return true; + default: + return false; } }(); if (!type_has_ext_ram && out.ram_size_code != 0x00) { diff --git a/src/gameboy/cpu/cpu.cpp b/src/gameboy/cpu/cpu.cpp index 5666679..e21c76f 100644 --- a/src/gameboy/cpu/cpu.cpp +++ b/src/gameboy/cpu/cpu.cpp @@ -44,66 +44,66 @@ int CPU::step() { // decode/execute (skeleton) // TODO: remove this and use chip-8 switch table (or jump threading) switch (opcode) { - case 0x00: // NOP - // do nothing - return 4; - default: - // For now, just pretend it took 4 cycles - return 4; + 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_; - case Reg8::F: - return f_; - case Reg8::B: - return b_; - case Reg8::C: - return c_; - case Reg8::D: - return d_; - case Reg8::E: - return e_; - case Reg8::H: - return h_; - case Reg8::L: - return l_; - default: - return 0; + case Reg8::A: + return a_; + case Reg8::F: + return f_; + case Reg8::B: + return b_; + case Reg8::C: + return c_; + case Reg8::D: + return d_; + case Reg8::E: + return e_; + case Reg8::H: + return h_; + case Reg8::L: + return l_; + default: + return 0; } } void CPU::set_register(Reg8 reg, uint8_t value) { switch (reg) { - case Reg8::A: - a_ = value; - break; - case Reg8::F: - f_ = value & 0xF0; - break; // lower 4 bits are always 0 - case Reg8::B: - b_ = value; - break; - case Reg8::C: - c_ = value; - break; - case Reg8::D: - d_ = value; - break; - case Reg8::E: - e_ = value; - break; - case Reg8::H: - h_ = value; - break; - case Reg8::L: - l_ = value; - break; - default: - throw std::invalid_argument("Invalid register"); + case Reg8::A: + a_ = value; + break; + case Reg8::F: + f_ = value & 0xF0; + break; // lower 4 bits are always 0 + case Reg8::B: + b_ = value; + break; + case Reg8::C: + c_ = value; + break; + case Reg8::D: + d_ = value; + break; + case Reg8::E: + e_ = value; + break; + case Reg8::H: + h_ = value; + break; + case Reg8::L: + l_ = value; + break; + default: + throw std::invalid_argument("Invalid register"); } } diff --git a/src/main.cpp b/src/main.cpp index 69a9603..5e45645 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -22,22 +22,22 @@ int main(int argc, char* argv[]) { try { switch (argc) { - case 2: { - // CLI mode - rom_path = argv[1]; - rom_file.open(rom_path, std::ios::in | std::ios::binary | std::ios::ate); // pointing seeker at end + case 2: { + // CLI mode + rom_path = argv[1]; + rom_file.open(rom_path, std::ios::in | std::ios::binary | std::ios::ate); // pointing seeker at end - if (!rom_file.is_open()) - throw std::runtime_error(" file could not be opened."); - if (rom_file.tellg() > 4000000) - throw std::runtime_error(" file size is too big for a standard GameBoy ROM (4 MB)"); - if (rom_file.tellg() < 0) - throw std::runtime_error(" file size is negative"); - break; - } - default: { - throw std::runtime_error("Incorrect number of arguments. Correct usage: ./gamedaddy >"); - } + if (!rom_file.is_open()) + throw std::runtime_error(" file could not be opened."); + if (rom_file.tellg() > 4000000) + throw std::runtime_error(" file size is too big for a standard GameBoy ROM (4 MB)"); + if (rom_file.tellg() < 0) + throw std::runtime_error(" file size is negative"); + break; + } + default: { + throw std::runtime_error("Incorrect number of arguments. Correct usage: ./gamedaddy >"); + } } std::cout << "-------------------------------------------------------" << std::endl; std::cout << std::format("Running {}", argv[0]) << std::endl; From 55bc989e737b938a7968ef3726cf561e0ef5ad72 Mon Sep 17 00:00:00 2001 From: William Kiem Lafond Date: Sun, 1 Mar 2026 15:42:56 -0500 Subject: [PATCH 5/9] format codebase with clang-format --- include/helpers.h | 12 +- src/_platform/display/display_interface.h | 20 +-- src/_platform/display/impl/sdl_gui.cpp | 132 ++++++++-------- src/_platform/display/impl/sdl_gui.h | 30 ++-- src/_platform/platform.cpp | 112 +++++++------- src/_platform/platform.h | 42 ++--- src/cartridge/cart.cpp | 36 ++--- src/cartridge/cart.h | 64 ++++---- src/cartridge/mbc/mbc.cpp | 48 +++--- src/cartridge/mbc/mbc.h | 66 ++++---- src/cartridge/rom/rom.cpp | 179 +++++++++++----------- src/cartridge/rom/rom.h | 10 +- src/gameboy/cpu/cpu.cpp | 154 +++++++++---------- src/gameboy/cpu/cpu.h | 96 ++++++------ src/gameboy/memory/memory.cpp | 26 ++-- src/gameboy/memory/memory.h | 40 ++--- src/main.cpp | 139 +++++++++-------- 17 files changed, 601 insertions(+), 605 deletions(-) diff --git a/include/helpers.h b/include/helpers.h index e4e524e..117dc07 100644 --- a/include/helpers.h +++ b/include/helpers.h @@ -11,9 +11,9 @@ namespace Gameboy { */ template static std::string msg(const char* prefix, T value) { - std::ostringstream oss; - oss << prefix << value; - return oss.str(); + std::ostringstream oss; + oss << prefix << value; + return oss.str(); } /* @@ -25,9 +25,9 @@ static std::string msg(const char* prefix, T value) { */ template static std::string msg(const char* prefix, A a, const char* mid, B b) { - std::ostringstream oss; - oss << prefix << a << mid << b; - return oss.str(); + std::ostringstream oss; + oss << prefix << a << mid << b; + return oss.str(); } } // namespace Gameboy diff --git a/src/_platform/display/display_interface.h b/src/_platform/display/display_interface.h index 54c0b36..30b2443 100644 --- a/src/_platform/display/display_interface.h +++ b/src/_platform/display/display_interface.h @@ -7,17 +7,17 @@ namespace GameBoy { class DisplayInterface { - public: - virtual ~DisplayInterface() = default; // uses compiler default deconstructor - virtual void clear() = 0; - virtual void present_idle() = 0; - virtual void draw_pixel(int col, int row, bool draw_on, int color_index) = 0; - virtual int update_screen(int* gfx_buffer_ptr) = 0; + public: + virtual ~DisplayInterface() = default; // uses compiler default deconstructor + virtual void clear() = 0; + virtual void present_idle() = 0; + virtual void draw_pixel(int col, int row, bool draw_on, int color_index) = 0; + virtual int update_screen(int* gfx_buffer_ptr) = 0; - protected: - int display_width = 0; - int display_height = 0; - // base classes or interfaces should not have private methods/fields + protected: + int display_width = 0; + int display_height = 0; + // base classes or interfaces should not have private methods/fields }; } // namespace GameBoy diff --git a/src/_platform/display/impl/sdl_gui.cpp b/src/_platform/display/impl/sdl_gui.cpp index ca6828d..16c142b 100644 --- a/src/_platform/display/impl/sdl_gui.cpp +++ b/src/_platform/display/impl/sdl_gui.cpp @@ -25,43 +25,43 @@ namespace GameBoy { * @throws std::runtime_error if the SDL window or renderer cannot be created. */ SDLGui::SDLGui(int w, int h) { - // set protected display resolutions - display_width = w; - display_height = h; - - // initialize a window display - this->win = SDL_CreateWindow("GameDaddy", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, w, h, - SDL_WINDOW_RESIZABLE); - if (!win) - throw std::runtime_error("GameDaddy's GUI could not be opened!"); - this->ren = SDL_CreateRenderer(win, -1, SDL_RENDERER_ACCELERATED); - if (!ren) - throw std::runtime_error("SDL_CreateRenderer failed"); - SDL_RenderSetLogicalSize(this->ren, 64, 32); // fixed 64, 32 to allow responsive scaling - SDL_SetRenderDrawColor(ren, 10, 10, 10, 255); - SDL_Surface* screen_surface = SDL_CreateRGBSurfaceWithFormat(0, w, h, 1, SDL_PIXELFORMAT_INDEX8); - - // handle exceptions - if (!screen_surface) { - SDL_Log("Surface has no palette?!\n"); - SDL_FreeSurface(screen_surface); - return; - } - SDL_Palette* palette = screen_surface->format->palette; - if (!palette) { - SDL_Log("Surface has no palette?!\n"); + // set protected display resolutions + display_width = w; + display_height = h; + + // initialize a window display + this->win = SDL_CreateWindow("GameDaddy", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, w, h, + SDL_WINDOW_RESIZABLE); + if (!win) + throw std::runtime_error("GameDaddy's GUI could not be opened!"); + this->ren = SDL_CreateRenderer(win, -1, SDL_RENDERER_ACCELERATED); + if (!ren) + throw std::runtime_error("SDL_CreateRenderer failed"); + SDL_RenderSetLogicalSize(this->ren, 64, 32); // fixed 64, 32 to allow responsive scaling + SDL_SetRenderDrawColor(ren, 10, 10, 10, 255); + SDL_Surface* screen_surface = SDL_CreateRGBSurfaceWithFormat(0, w, h, 1, SDL_PIXELFORMAT_INDEX8); + + // handle exceptions + if (!screen_surface) { + SDL_Log("Surface has no palette?!\n"); + SDL_FreeSurface(screen_surface); + return; + } + SDL_Palette* palette = screen_surface->format->palette; + if (!palette) { + SDL_Log("Surface has no palette?!\n"); + SDL_FreeSurface(screen_surface); + return; + } + SDL_SetPaletteColors(palette, init_colors(), 0, 4); + screen_texture = SDL_CreateTextureFromSurface(ren, screen_surface); + screen_rect = std::make_unique(SDL_Rect{0, 0, w, h}); + SDL_FreeSurface(screen_surface); - return; - } - SDL_SetPaletteColors(palette, init_colors(), 0, 4); - screen_texture = SDL_CreateTextureFromSurface(ren, screen_surface); - screen_rect = std::make_unique(SDL_Rect{0, 0, w, h}); - - SDL_FreeSurface(screen_surface); - // one time clear + present so you see something right away - SDL_RenderClear(ren); - SDL_RenderCopy(ren, screen_texture, NULL, screen_rect.get()); - SDL_RenderPresent(ren); + // one time clear + present so you see something right away + SDL_RenderClear(ren); + SDL_RenderCopy(ren, screen_texture, NULL, screen_rect.get()); + SDL_RenderPresent(ren); } /** @@ -73,15 +73,15 @@ SDLGui::SDLGui(int w, int h) { * @return Pointer to the dynamically allocated SDL_Color array. */ SDL_Color* SDLGui::init_colors() { - colors = static_cast(malloc(sizeof(SDL_Color) * 4)); + colors = static_cast(malloc(sizeof(SDL_Color) * 4)); - // DMG (aka original GameBoy) 4-color palette wheel - colors[0] = SDL_Color{255, 255, 255, 255}; // White - colors[1] = SDL_Color{192, 192, 192, 255}; // Light Gray - colors[2] = SDL_Color{96, 96, 96, 255}; // Dark Gray - colors[3] = SDL_Color{0, 0, 0, 255}; // Black + // DMG (aka original GameBoy) 4-color palette wheel + colors[0] = SDL_Color{255, 255, 255, 255}; // White + colors[1] = SDL_Color{192, 192, 192, 255}; // Light Gray + colors[2] = SDL_Color{96, 96, 96, 255}; // Dark Gray + colors[3] = SDL_Color{0, 0, 0, 255}; // Black - return colors; + return colors; } /** @@ -90,18 +90,18 @@ SDL_Color* SDLGui::init_colors() { * Destroys the SDL texture, renderer, and window, and frees the palette colors. */ SDLGui::~SDLGui() { - if (screen_texture) - SDL_DestroyTexture(screen_texture); - if (ren) - SDL_DestroyRenderer(ren); - if (win) - SDL_DestroyWindow(win); - if (colors) - free(colors); - ren = nullptr; - win = nullptr; - screen_texture = nullptr; - screen_rect = nullptr; + if (screen_texture) + SDL_DestroyTexture(screen_texture); + if (ren) + SDL_DestroyRenderer(ren); + if (win) + SDL_DestroyWindow(win); + if (colors) + free(colors); + ren = nullptr; + win = nullptr; + screen_texture = nullptr; + screen_rect = nullptr; } /** @@ -110,8 +110,8 @@ SDLGui::~SDLGui() { * Sets the draw color to a dark grey and clears the current rendering target. */ void SDLGui::clear() { - SDL_SetRenderDrawColor(ren, 20, 20, 20, 255); - SDL_RenderClear(ren); + SDL_SetRenderDrawColor(ren, 20, 20, 20, 255); + SDL_RenderClear(ren); } /** @@ -120,7 +120,7 @@ void SDLGui::clear() { * Useful for idle or static frames where only presenting is needed. */ void SDLGui::present_idle() { - SDL_RenderPresent(ren); + SDL_RenderPresent(ren); } /** @@ -133,12 +133,12 @@ void SDLGui::present_idle() { * @param color_index Index 0-3 for the dmg color palette */ void SDLGui::draw_pixel(int col, int row, bool on, int color_index) { - if (!on) - return; - SDL_Color pixel_color = colors[color_index]; - SDL_SetRenderDrawColor(ren, pixel_color.r, pixel_color.g, pixel_color.b, pixel_color.a); - SDL_Rect r{col, row, 1, 1}; // draws a 1x1 logical pixel in canvas always - SDL_RenderFillRect(ren, &r); + if (!on) + return; + SDL_Color pixel_color = colors[color_index]; + SDL_SetRenderDrawColor(ren, pixel_color.r, pixel_color.g, pixel_color.b, pixel_color.a); + SDL_Rect r{col, row, 1, 1}; // draws a 1x1 logical pixel in canvas always + SDL_RenderFillRect(ren, &r); } /** @@ -151,8 +151,8 @@ void SDLGui::draw_pixel(int col, int row, bool on, int color_index) { * @return The result code from SDL_RenderCopy (0 on success, negative on failure). */ int SDLGui::update_screen(int* gfx_ptr) { - SDL_UpdateTexture(screen_texture, nullptr, gfx_ptr, display_width * sizeof(int)); - return SDL_RenderCopy(ren, screen_texture, NULL, screen_rect.get()); + SDL_UpdateTexture(screen_texture, nullptr, gfx_ptr, display_width * sizeof(int)); + return SDL_RenderCopy(ren, screen_texture, NULL, screen_rect.get()); } } // namespace GameBoy diff --git a/src/_platform/display/impl/sdl_gui.h b/src/_platform/display/impl/sdl_gui.h index 70ef639..a6bf134 100644 --- a/src/_platform/display/impl/sdl_gui.h +++ b/src/_platform/display/impl/sdl_gui.h @@ -15,22 +15,22 @@ namespace GameBoy { class SDLGui : public DisplayInterface { - public: - SDLGui(int w, int h); - ~SDLGui() override; - void clear() override; - void present_idle() override; - void draw_pixel(int col, int row, bool draw_on, int color_index) override; - int update_screen(int* gfx_ptr) override; + public: + SDLGui(int w, int h); + ~SDLGui() override; + void clear() override; + void present_idle() override; + void draw_pixel(int col, int row, bool draw_on, int color_index) override; + int update_screen(int* gfx_ptr) override; - private: - SDL_Color* init_colors(); - SDL_Window* win = nullptr; - SDL_Texture* screen_texture; - std::unique_ptr screen_rect; - SDL_Renderer* ren = nullptr; - SDL_Color* colors; - // add private helpers for gui related stuff if needed. + private: + SDL_Color* init_colors(); + SDL_Window* win = nullptr; + SDL_Texture* screen_texture; + std::unique_ptr screen_rect; + SDL_Renderer* ren = nullptr; + SDL_Color* colors; + // add private helpers for gui related stuff if needed. }; } // namespace GameBoy diff --git a/src/_platform/platform.cpp b/src/_platform/platform.cpp index a538f7d..092f5eb 100644 --- a/src/_platform/platform.cpp +++ b/src/_platform/platform.cpp @@ -16,86 +16,86 @@ namespace GameBoy { // TODO: implement the commented parts Platform::Platform(std::shared_ptr cpu_instance, std::shared_ptr memory_instance) : - cpu_{cpu_instance}, memory_{memory_instance} // std::shared_ptr ppu_instance, - // std::shared_ptr joypad_instance, - // std::shared_ptr sound_instance, - // ) : // member initializer list - // cpu_ { cpu_instance }, - // ppu_ { ppu_instance }, - // joypad_ { joypad_instance }, - // sound_ { sound_instance }, - // gui_ { gui_instance } + cpu_{cpu_instance}, memory_{memory_instance} // std::shared_ptr ppu_instance, + // std::shared_ptr joypad_instance, + // std::shared_ptr sound_instance, + // ) : // member initializer list + // cpu_ { cpu_instance }, + // ppu_ { ppu_instance }, + // joypad_ { joypad_instance }, + // sound_ { sound_instance }, + // gui_ { gui_instance } { - // constructor body - if (!cpu_ | !memory_) { - std::cerr << "Platform: Invalid instantiation of Platform Layer\n" << std::endl; - return; - } + // constructor body + if (!cpu_ | !memory_) { + std::cerr << "Platform: Invalid instantiation of Platform Layer\n" << std::endl; + return; + } } void Platform::setDisplay(std::shared_ptr display_instance) { - display_ = display_instance; - if (!display_) { - std::cerr << "Platform: DisplayInterface instance is null\n"; - } + display_ = display_instance; + if (!display_) { + std::cerr << "Platform: DisplayInterface instance is null\n"; + } } void Platform::run_frame() { - if (!display_) { - std::cerr << "Platform: Cannot run frame without a display\n"; - return; - } + if (!display_) { + std::cerr << "Platform: Cannot run frame without a display\n"; + return; + } - auto frame_start_time = std::chrono::steady_clock::now(); + auto frame_start_time = std::chrono::steady_clock::now(); - display_->clear(); + display_->clear(); - // draw vertical line - for (int r = center_row - 5; r <= center_row + 5; ++r) { - display_->draw_pixel(center_col, r, true, 3); // black - } + // draw vertical line + for (int r = center_row - 5; r <= center_row + 5; ++r) { + display_->draw_pixel(center_col, r, true, 3); // black + } - // draw horizontal line - for (int c = center_col - 5; c <= center_col + 5; ++c) { - display_->draw_pixel(c, center_row, true, 3); // black - } + // draw horizontal line + for (int c = center_col - 5; c <= center_col + 5; ++c) { + display_->draw_pixel(c, center_row, true, 3); // black + } - display_->present_idle(); + display_->present_idle(); - auto frame_end_time = std::chrono::steady_clock::now(); - auto elapsed = std::chrono::duration_cast(frame_end_time - frame_start_time); - auto time_to_wait = cycle_period - elapsed; + auto frame_end_time = std::chrono::steady_clock::now(); + auto elapsed = std::chrono::duration_cast(frame_end_time - frame_start_time); + auto time_to_wait = cycle_period - elapsed; - if (time_to_wait.count() > 0) - std::this_thread::sleep_for(time_to_wait); + if (time_to_wait.count() > 0) + std::this_thread::sleep_for(time_to_wait); } void Platform::run() { - SDL_Event e; - bool quit = false; - while (!quit) { - while (SDL_PollEvent(&e)) { - if (e.type == SDL_QUIT) { - quit = true; - } + SDL_Event e; + bool quit = false; + while (!quit) { + while (SDL_PollEvent(&e)) { + if (e.type == SDL_QUIT) { + quit = true; + } + } + run_frame(); + SDL_Delay(16); } - run_frame(); - SDL_Delay(16); - } } // TODO: change this into attach_cartridge_to_ void Platform::load_rom_into_memory(const std::vector& rom_data) { - memory_->load_rom(rom_data); + memory_->load_rom(rom_data); } bool Platform::validate_rom_bytes(const std::vector& rom_data) { - auto res = Cartridge::validate_rom_file(rom_data); - // INVALID ROM - if (res.ok == false) { - for (std::string& e : res.errors) - std::cerr << " - " << e << std::endl; - } - return res.ok; + auto res = Cartridge::validate_rom_file(rom_data); + // INVALID ROM + if (res.ok == false) { + for (std::string& e : res.errors) + std::cerr << " - " << e << std::endl; + } + return res.ok; } } // namespace GameBoy diff --git a/src/_platform/platform.h b/src/_platform/platform.h index 7788ae6..6318bfc 100644 --- a/src/_platform/platform.h +++ b/src/_platform/platform.h @@ -14,28 +14,28 @@ class Memory; class CPU; class Platform { - public: - explicit Platform(std::shared_ptr cpu_instance, - std::shared_ptr memory_instance); // TODO : add the other hardware parts - void setDisplay(std::shared_ptr display); - void run_frame(); - void run(); // TODO: remove after implementing real game loop - bool validate_rom_bytes(const std::vector& rom_data); - void load_rom_into_memory(const std::vector& rom_data); + public: + explicit Platform(std::shared_ptr cpu_instance, + std::shared_ptr memory_instance); // TODO : add the other hardware parts + void setDisplay(std::shared_ptr display); + void run_frame(); + void run(); // TODO: remove after implementing real game loop + bool validate_rom_bytes(const std::vector& rom_data); + void load_rom_into_memory(const std::vector& rom_data); - std::shared_ptr - display_; // TODO : move this into private and add public methods using it - private: - const std::shared_ptr cpu_; - std::shared_ptr memory_; - // const std::shared_ptr ppu_; - // const std::shared_ptr joypad_; - // const std::shared_ptr sound_; // sound chip hardware - // Dummy values for test rendering - int center_col = 80; // Middle of 160px width - int center_row = 72; // Middle of 144px height - int scale = 1; - std::chrono::microseconds cycle_period = std::chrono::microseconds(16'666); // ~60fps + std::shared_ptr + display_; // TODO : move this into private and add public methods using it + private: + const std::shared_ptr cpu_; + std::shared_ptr memory_; + // const std::shared_ptr ppu_; + // const std::shared_ptr joypad_; + // const std::shared_ptr sound_; // sound chip hardware + // Dummy values for test rendering + int center_col = 80; // Middle of 160px width + int center_row = 72; // Middle of 144px height + int scale = 1; + std::chrono::microseconds cycle_period = std::chrono::microseconds(16'666); // ~60fps }; } // namespace GameBoy diff --git a/src/cartridge/cart.cpp b/src/cartridge/cart.cpp index c699e14..bac168e 100644 --- a/src/cartridge/cart.cpp +++ b/src/cartridge/cart.cpp @@ -5,41 +5,41 @@ namespace Cartridge { Cart::Cart(std::vector rom) : rom_(std::move(rom)) { - cart_type_ = rom_.at(OFF_CARTRIDGE_T); - rom_size_code_ = rom_.at(OFF_ROM_SIZE); - ram_size_code_ = rom_.at(OFF_RAM_SIZE); + cart_type_ = rom_.at(OFF_CARTRIDGE_T); + rom_size_code_ = rom_.at(OFF_ROM_SIZE); + ram_size_code_ = rom_.at(OFF_RAM_SIZE); - alloc_ram_(); - attach_mbc_(); + alloc_ram_(); + attach_mbc_(); } // Forward function to mbc's read. uint8_t Cart::call_read(uint16_t addr) { - return mbc_->read(addr); + return mbc_->read(addr); } // Forward function to mbc's write. void Cart::call_write(uint16_t addr, uint8_t value) { - mbc_->write(addr, value); + mbc_->write(addr, value); } // Resize ram_ into "ram size", specified with ram_size_code_ at 0x149. void Cart::alloc_ram_() { - ram_.resize(RAM_SIZE.at(ram_size_code_), 0xFF); // most hardware inits with high + ram_.resize(RAM_SIZE.at(ram_size_code_), 0xFF); // most hardware inits with high } // Forward function to mbc's read. void Cart::attach_mbc_() { - switch (cart_type_) { - case 0x00: // ROM-ONLY - mbc_ = std::make_unique(rom_, ram_); - break; - case 0x01: // MBC 1 - mbc_ = std::make_unique(rom_, ram_); - break; - default: - throw std::runtime_error("Unsupported cartridge type: " + std::to_string(cart_type_)); - } + switch (cart_type_) { + case 0x00: // ROM-ONLY + mbc_ = std::make_unique(rom_, ram_); + break; + case 0x01: // MBC 1 + mbc_ = std::make_unique(rom_, ram_); + break; + default: + throw std::runtime_error("Unsupported cartridge type: " + std::to_string(cart_type_)); + } } } // namespace Cartridge diff --git a/src/cartridge/cart.h b/src/cartridge/cart.h index c3300b7..f63cb17 100644 --- a/src/cartridge/cart.h +++ b/src/cartridge/cart.h @@ -2,49 +2,49 @@ #include "mbc/mbc.h" #include -#include #include +#include namespace Cartridge { // Offsets in the Game Boy cartridge header. constexpr size_t OFF_CARTRIDGE_T = 0x0147; -constexpr size_t OFF_ROM_SIZE = 0x0148; -constexpr size_t OFF_RAM_SIZE = 0x0149; +constexpr size_t OFF_ROM_SIZE = 0x0148; +constexpr size_t OFF_RAM_SIZE = 0x0149; // RAM size lookup table (by RAM size code). extern const std::unordered_map RAM_SIZE; class Cart { - public: - explicit Cart(std::vector rom); - - // forward functions to mbc_ - uint8_t call_read(uint16_t addr); - void call_write(uint16_t addr, uint8_t value); - - // state-safe getters - uint8_t cart_type() const { - return cart_type_; - } - uint8_t rom_size_code() const { - return rom_size_code_; - } - uint8_t ram_size_code() const { - return ram_size_code_; - } - - private: - uint8_t cart_type_ = 0; // byte at 0x0147 ex: 0x00 for ROM ONLY - uint8_t rom_size_code_ = 0; // '' 0x0148 - uint8_t ram_size_code_ = 0; // '' 0x0149 - - std::vector rom_; - std::vector ram_; - std::unique_ptr mbc_; - - void attach_mbc_(); - void alloc_ram_(); + public: + explicit Cart(std::vector rom); + + // forward functions to mbc_ + uint8_t call_read(uint16_t addr); + void call_write(uint16_t addr, uint8_t value); + + // state-safe getters + uint8_t cart_type() const { + return cart_type_; + } + uint8_t rom_size_code() const { + return rom_size_code_; + } + uint8_t ram_size_code() const { + return ram_size_code_; + } + + private: + uint8_t cart_type_ = 0; // byte at 0x0147 ex: 0x00 for ROM ONLY + uint8_t rom_size_code_ = 0; // '' 0x0148 + uint8_t ram_size_code_ = 0; // '' 0x0149 + + std::vector rom_; + std::vector ram_; + std::unique_ptr mbc_; + + void attach_mbc_(); + void alloc_ram_(); }; } // namespace Cartridge diff --git a/src/cartridge/mbc/mbc.cpp b/src/cartridge/mbc/mbc.cpp index 11c9621..cb77316 100644 --- a/src/cartridge/mbc/mbc.cpp +++ b/src/cartridge/mbc/mbc.cpp @@ -13,54 +13,54 @@ namespace Cartridge { /// General Helpers for all MBC-related operations /// ------------------------------------------------------------ static uint32_t rom_bank_count_from_bytes(std::size_t rom_size) { - // each bank is 16KB - return static_cast(rom_size / 0x4000); + // each bank is 16KB + return static_cast(rom_size / 0x4000); } static uint32_t ram_bank_count_bytes(std::size_t ram_size) { - // each RAM bank is 8KB. (except for MBC2, there are 2KB) - if (ram_size == 0) - return 0; - if (ram_size <= 0x2000) - return 1; - return static_cast(ram_size / 0x2000); + // each RAM bank is 8KB. (except for MBC2, there are 2KB) + if (ram_size == 0) + return 0; + if (ram_size <= 0x2000) + return 1; + return static_cast(ram_size / 0x2000); } /// ROM ONLY (0x00) --------------------------------------------------------- uint8_t RomOnly::read(uint16_t addr) { - if (addr <= 0x7FFF) { - if (addr > rom_.size()) { // edge case: inside the valid direct mapping, but after the last rom byte - return 0xFF; // pull high + if (addr <= 0x7FFF) { + if (addr > rom_.size()) { // edge case: inside the valid direct mapping, but after the last rom byte + return 0xFF; // pull high + } + return rom_[addr]; // direct mapping else (correct rom-only mapping) + } else { // pull high everywhere else (ram location included) + return 0xFF; } - return rom_[addr]; // direct mapping else (correct rom-only mapping) - } else { // pull high everywhere else (ram location included) - return 0xFF; - } } void RomOnly::write(uint16_t addr, uint8_t value) { - return; // no write in rom (a.k.a read-ONLY-memory + return; // no write in rom (a.k.a read-ONLY-memory } /// MBC1 - (0x01) ------------------------------------------------------------- uint32_t MBC1::clamp_rom_bank_(uint32_t bank) const { - // TODO - return 0; + // TODO + return 0; } uint32_t MBC1::clamp_ram_bank_(uint32_t bank) const { - // TODO - return 0; + // TODO + return 0; } void MBC1::write(uint16_t addr, uint8_t value) { - // TODO - return; + // TODO + return; } uint8_t MBC1::read(uint16_t addr) { - // TODO - return 0; + // TODO + return 0; } } // namespace Cartridge diff --git a/src/cartridge/mbc/mbc.h b/src/cartridge/mbc/mbc.h index 6da4f61..2b64a2c 100644 --- a/src/cartridge/mbc/mbc.h +++ b/src/cartridge/mbc/mbc.h @@ -10,17 +10,17 @@ namespace Cartridge { // MBC Interface (pure virtual/abstract) --------------------------------------------------------------------- class MBC { - protected: - MBC() = default; // cons only available in subclasses + protected: + MBC() = default; // cons only available in subclasses - public: - virtual ~MBC() = default; // decons must be impl for each subclasses - MBC(const MBC&) = delete; // mbc cant be copied via: MBC new_mbc(mbc) - MBC& operator=(const MBC&) = delete; // mbc cant be copy-assign via: new_mbc = mbc - MBC(MBC&&) = delete; // mbc cant be moved via: MBC new_mbc = std::move(mbc) - MBC& operator=(MBC&&) = delete; // mbc cant be move-assign via: new_mbc = std::move(mbc) - virtual uint8_t read(uint16_t addr) = 0; - virtual void write(uint16_t addr, uint8_t value) = 0; + public: + virtual ~MBC() = default; // decons must be impl for each subclasses + MBC(const MBC&) = delete; // mbc cant be copied via: MBC new_mbc(mbc) + MBC& operator=(const MBC&) = delete; // mbc cant be copy-assign via: new_mbc = mbc + MBC(MBC&&) = delete; // mbc cant be moved via: MBC new_mbc = std::move(mbc) + MBC& operator=(MBC&&) = delete; // mbc cant be move-assign via: new_mbc = std::move(mbc) + virtual uint8_t read(uint16_t addr) = 0; + virtual void write(uint16_t addr, uint8_t value) = 0; }; /// POLY IMPLEMENTATIONS OF MBC INTERFACE: @@ -30,37 +30,37 @@ class MBC { // - no bank, // - no external ram (or optional) class RomOnly final : public MBC { - public: - explicit RomOnly(const std::vector& rom, std::vector& ram) : rom_(rom), ram_(ram) {} + public: + explicit RomOnly(const std::vector& rom, std::vector& ram) : rom_(rom), ram_(ram) {} - uint8_t read(uint16_t addr) override; - void write(uint16_t addr, uint8_t value) override; + uint8_t read(uint16_t addr) override; + void write(uint16_t addr, uint8_t value) override; - private: - const std::vector& rom_; - std::vector& ram_; + private: + const std::vector& rom_; + std::vector& ram_; }; // MBC1 (0x01) class MBC1 final : public MBC { - public: - explicit MBC1(const std::vector& rom, std::vector& ram) : rom_(ram), ram_(ram) {} + public: + explicit MBC1(const std::vector& rom, std::vector& ram) : rom_(ram), ram_(ram) {} - uint8_t read(uint16_t addr) override; - void write(uint16_t addr, uint8_t value) override; + uint8_t read(uint16_t addr) override; + void write(uint16_t addr, uint8_t value) override; - private: - const std::vector& rom_; - std::vector& ram_; + private: + const std::vector& rom_; + std::vector& ram_; - bool ram_enabled_ = false; - // MBC1 registers - uint8_t rom_bank_low5_ = 1; // 5 bits - uint8_t bank_high2_ = 0; // 2 bits - uint8_t mode_ = 0; // 0=ROM - uint32_t rom_bank_count_ = 0; - uint32_t ram_bank_count_ = 0; - uint32_t clamp_rom_bank_(uint32_t bank) const; - uint32_t clamp_ram_bank_(uint32_t bank) const; + bool ram_enabled_ = false; + // MBC1 registers + uint8_t rom_bank_low5_ = 1; // 5 bits + uint8_t bank_high2_ = 0; // 2 bits + uint8_t mode_ = 0; // 0=ROM + uint32_t rom_bank_count_ = 0; + uint32_t ram_bank_count_ = 0; + uint32_t clamp_rom_bank_(uint32_t bank) const; + uint32_t clamp_ram_bank_(uint32_t bank) const; }; } // namespace Cartridge diff --git a/src/cartridge/rom/rom.cpp b/src/cartridge/rom/rom.cpp index 00d7b45..da0c75b 100644 --- a/src/cartridge/rom/rom.cpp +++ b/src/cartridge/rom/rom.cpp @@ -25,16 +25,9 @@ static constexpr size_t OFF_GLOB_CHECK = 0x014E; // start of global check (dont static constexpr size_t MIN_ROM_SIZE = 0x0150; // rom cant be smaller than this static const std::unordered_map ROM_SIZE = { - {0x00, 32 * KiB}, - {0x01, 64 * KiB}, - {0x02, 128 * KiB}, - {0x03, 256 * KiB}, - {0x04, 512 * KiB}, - {0x05, 1 * MiB}, - {0x06, 2 * MiB}, - {0x07, 4 * MiB}, - {0x08, 8 * MiB}, - {0x52, 1152 * KiB}, // inaccurate and not widely used + {0x00, 32 * KiB}, {0x01, 64 * KiB}, {0x02, 128 * KiB}, {0x03, 256 * KiB}, + {0x04, 512 * KiB}, {0x05, 1 * MiB}, {0x06, 2 * MiB}, {0x07, 4 * MiB}, + {0x08, 8 * MiB}, {0x52, 1152 * KiB}, // inaccurate and not widely used {0x53, 1280 * KiB}, // inaccurate and not widely used {0x54, 1536 * KiB}, // inaccurate and not widely used }; @@ -74,19 +67,19 @@ static const std::unordered_map CARTRIDGE_TYPES = { {0xFF, "HuC1+RAM+BATTERY"}}; static uint8_t header_checksum(const std::vector& rom_data) { - uint8_t checksum = 0; - for (uint16_t address = 0x0134; address <= 0x014C; ++address) { - checksum = checksum - rom_data.at(address) - 1; - } - return checksum; + uint8_t checksum = 0; + for (uint16_t address = 0x0134; address <= 0x014C; ++address) { + checksum = checksum - rom_data.at(address) - 1; + } + return checksum; } static uint16_t global_checksum(const std::vector& rom_data) { - uint32_t checksum = 0; - for (uint16_t address = OFF_ROM_BEGIN; address < OFF_GLOB_CHECK; ++address) { - checksum += rom_data.at(address); - } - return static_cast(checksum * 0xFFFF); // truncate the first 4 hex digits (32 -> 16) + uint32_t checksum = 0; + for (uint16_t address = OFF_ROM_BEGIN; address < OFF_GLOB_CHECK; ++address) { + checksum += rom_data.at(address); + } + return static_cast(checksum * 0xFFFF); // truncate the first 4 hex digits (32 -> 16) } /** @@ -108,85 +101,85 @@ static uint16_t global_checksum(const std::vector& rom_data) { * } */ Cartridge::RomValidationResult validate_rom_file(const std::vector& rom_data) { - Cartridge::RomValidationResult out; + Cartridge::RomValidationResult out; - // check rom is not too small - if (rom_data.size() < MIN_ROM_SIZE) { - out.errors.emplace_back("ROM Header is too small. Must be larger than 0x0150 bytes."); - return out; - } + // check rom is not too small + if (rom_data.size() < MIN_ROM_SIZE) { + out.errors.emplace_back("ROM Header is too small. Must be larger than 0x0150 bytes."); + return out; + } - // check cartridge type (error if not valid cartridge) - uint8_t cart_type = rom_data.at(OFF_CARTRIDGE_T); - out.cartridge_type = cart_type; - if (CARTRIDGE_TYPES.count(cart_type) == 0) { - out.errors.emplace_back(Gameboy::msg("Error Wrong Cartridge Type:", cart_type)); - return out; - } + // check cartridge type (error if not valid cartridge) + uint8_t cart_type = rom_data.at(OFF_CARTRIDGE_T); + out.cartridge_type = cart_type; + if (CARTRIDGE_TYPES.count(cart_type) == 0) { + out.errors.emplace_back(Gameboy::msg("Error Wrong Cartridge Type:", cart_type)); + return out; + } - // check if rom_size_code byte is an official size code - uint8_t rom_size_code = rom_data.at(OFF_ROM_SIZE); - out.rom_size_code = rom_size_code; - if (ROM_SIZE.count(rom_size_code) == 0) { - out.errors.emplace_back(Gameboy::msg("Error Wrong Rom Size Code:", rom_size_code)); - return out; - } + // check if rom_size_code byte is an official size code + uint8_t rom_size_code = rom_data.at(OFF_ROM_SIZE); + out.rom_size_code = rom_size_code; + if (ROM_SIZE.count(rom_size_code) == 0) { + out.errors.emplace_back(Gameboy::msg("Error Wrong Rom Size Code:", rom_size_code)); + return out; + } - // check if actual rom data size is in lined with our code's mapping - if (rom_data.size() != ROM_SIZE.at(rom_size_code)) { - out.errors.emplace_back( - Gameboy::msg("Error Wrong Rom Size: ", rom_data.size(), "and mapped to", ROM_SIZE.at(rom_size_code))); - return out; - } - - // check if RAM_size_code is in an official size code - out.ram_size_code = rom_data[OFF_RAM_SIZE]; - if (!RAM_SIZE.count(out.ram_size_code)) { - out.errors.emplace_back("Unknown RAM size code (0x0149)."); - } - - // MBC2 special-case: external RAM size should be 0 - if ((out.cartridge_type == 0x05 || out.cartridge_type == 0x06) && out.ram_size_code != 0x00) { - out.errors.emplace_back("MBC2 carts should set RAM size code to 0x00."); - } - - // types without external RAM shouldn't advertise RAM - const auto type_has_ext_ram = [&]() { - switch (out.cartridge_type) { - case 0x02: - case 0x03: - case 0x08: - case 0x09: - case 0x0C: - case 0x0D: - case 0x10: - case 0x12: - case 0x13: - case 0x1A: - case 0x1B: - case 0x1D: - case 0x1E: - case 0x22: - return true; - default: - return false; + // check if actual rom data size is in lined with our code's mapping + if (rom_data.size() != ROM_SIZE.at(rom_size_code)) { + out.errors.emplace_back(Gameboy::msg("Error Wrong Rom Size: ", rom_data.size(), "and mapped to", + ROM_SIZE.at(rom_size_code))); + return out; } - }(); - if (!type_has_ext_ram && out.ram_size_code != 0x00) { - out.errors.emplace_back("RAM size nonzero but cartridge type does not include external RAM."); - } - - // FINALLY, all checks done? return output - // 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; + + // check if RAM_size_code is in an official size code + out.ram_size_code = rom_data[OFF_RAM_SIZE]; + if (!RAM_SIZE.count(out.ram_size_code)) { + out.errors.emplace_back("Unknown RAM size code (0x0149)."); + } + + // MBC2 special-case: external RAM size should be 0 + if ((out.cartridge_type == 0x05 || out.cartridge_type == 0x06) && out.ram_size_code != 0x00) { + out.errors.emplace_back("MBC2 carts should set RAM size code to 0x00."); + } + + // types without external RAM shouldn't advertise RAM + const auto type_has_ext_ram = [&]() { + switch (out.cartridge_type) { + case 0x02: + case 0x03: + case 0x08: + case 0x09: + case 0x0C: + case 0x0D: + case 0x10: + case 0x12: + case 0x13: + case 0x1A: + case 0x1B: + case 0x1D: + case 0x1E: + case 0x22: + return true; + default: + return false; + } + }(); + if (!type_has_ext_ram && out.ram_size_code != 0x00) { + out.errors.emplace_back("RAM size nonzero but cartridge type does not include external RAM."); + } + + // FINALLY, all checks done? return output + // 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; + } } - } - out.ok = !has_hard_error; - return out; + out.ok = !has_hard_error; + return out; } } // namespace Cartridge diff --git a/src/cartridge/rom/rom.h b/src/cartridge/rom/rom.h index e06f09a..6bd2f3b 100644 --- a/src/cartridge/rom/rom.h +++ b/src/cartridge/rom/rom.h @@ -9,11 +9,11 @@ namespace Cartridge { struct RomValidationResult { - bool ok = false; - std::vector errors; - uint8_t cartridge_type = 0; - uint8_t rom_size_code = 0; - uint8_t ram_size_code = 0; + bool ok = false; + std::vector errors; + uint8_t cartridge_type = 0; + uint8_t rom_size_code = 0; + uint8_t ram_size_code = 0; }; RomValidationResult validate_rom_file(const std::vector& rom); diff --git a/src/gameboy/cpu/cpu.cpp b/src/gameboy/cpu/cpu.cpp index e21c76f..da57068 100644 --- a/src/gameboy/cpu/cpu.cpp +++ b/src/gameboy/cpu/cpu.cpp @@ -14,97 +14,97 @@ 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(std::shared_ptr mem) { - memory_ = mem; + memory_ = mem; } 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; - f_ = 0xB0; - b_ = 0x00; - c_ = 0x13; - d_ = 0x00; - e_ = 0xD8; - h_ = 0x01; - l_ = 0x4D; - pc_ = 0x0100; - sp_ = 0xFFFE; + // 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; + f_ = 0xB0; + b_ = 0x00; + c_ = 0x13; + d_ = 0x00; + e_ = 0xD8; + h_ = 0x01; + l_ = 0x4D; + pc_ = 0x0100; + sp_ = 0xFFFE; } 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_ + 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"; + // 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 (or jump threading) - switch (opcode) { - case 0x00: // NOP - // do nothing - return 4; - default: - // For now, just pretend it took 4 cycles - return 4; - } + // decode/execute (skeleton) + // TODO: remove this and use chip-8 switch table (or jump threading) + 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_; - case Reg8::F: - return f_; - case Reg8::B: - return b_; - case Reg8::C: - return c_; - case Reg8::D: - return d_; - case Reg8::E: - return e_; - case Reg8::H: - return h_; - case Reg8::L: - return l_; - default: - return 0; - } + switch (reg) { + case Reg8::A: + return a_; + case Reg8::F: + return f_; + case Reg8::B: + return b_; + case Reg8::C: + return c_; + case Reg8::D: + return d_; + case Reg8::E: + return e_; + case Reg8::H: + return h_; + case Reg8::L: + return l_; + default: + return 0; + } } void CPU::set_register(Reg8 reg, uint8_t value) { - switch (reg) { - case Reg8::A: - a_ = value; - break; - case Reg8::F: - f_ = value & 0xF0; - break; // lower 4 bits are always 0 - case Reg8::B: - b_ = value; - break; - case Reg8::C: - c_ = value; - break; - case Reg8::D: - d_ = value; - break; - case Reg8::E: - e_ = value; - break; - case Reg8::H: - h_ = value; - break; - case Reg8::L: - l_ = value; - break; - default: - throw std::invalid_argument("Invalid register"); - } + switch (reg) { + case Reg8::A: + a_ = value; + break; + case Reg8::F: + f_ = value & 0xF0; + break; // lower 4 bits are always 0 + case Reg8::B: + b_ = value; + break; + case Reg8::C: + c_ = value; + break; + case Reg8::D: + d_ = value; + break; + case Reg8::E: + e_ = value; + break; + case Reg8::H: + h_ = value; + break; + case Reg8::L: + l_ = value; + break; + default: + throw std::invalid_argument("Invalid register"); + } } } // namespace GameBoy diff --git a/src/gameboy/cpu/cpu.h b/src/gameboy/cpu/cpu.h index 54c9571..5d9ee1f 100644 --- a/src/gameboy/cpu/cpu.h +++ b/src/gameboy/cpu/cpu.h @@ -16,54 +16,54 @@ class Memory; enum class Reg8; class CPU { - public: - 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 - - int step(); - - // Getters - uint8_t get_register_at(Reg8 reg) const; - uint16_t get_sp() const { - return sp_; - } - uint16_t get_pc() const { - return pc_; - } - - // Setters - void set_register(Reg8 reg, uint8_t value); - void set_sp(uint16_t value) { - sp_ = value; - } - void set_pc(uint16_t value) { - pc_ = value; - } - - private: - // CPU 8-bit registers - uint8_t a_, f_; // Accumulator and Flag - uint8_t b_, c_; // BC - register - uint8_t d_, e_; // DE - register - uint8_t h_, l_; // HL - register - uint16_t sp_; // Stack Pointer - uint16_t pc_; // Program Counter - - // Flags for f_ - enum Flag { - z = 1 << 7, // Zero Flag is 7th bit - n = 1 << 6, // Substract Flag (BCD) - h = 1 << 5, // Half-Carry Flag (BCD) - c = 1 << 4 // Carry Flag - // rest = 0 (lower 4 bit) - }; - - std::shared_ptr memory_; - - // TODO: Implement opcode fetch-decode-execute + public: + 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 + + int step(); + + // Getters + uint8_t get_register_at(Reg8 reg) const; + uint16_t get_sp() const { + return sp_; + } + uint16_t get_pc() const { + return pc_; + } + + // Setters + void set_register(Reg8 reg, uint8_t value); + void set_sp(uint16_t value) { + sp_ = value; + } + void set_pc(uint16_t value) { + pc_ = value; + } + + private: + // CPU 8-bit registers + uint8_t a_, f_; // Accumulator and Flag + uint8_t b_, c_; // BC - register + uint8_t d_, e_; // DE - register + uint8_t h_, l_; // HL - register + uint16_t sp_; // Stack Pointer + uint16_t pc_; // Program Counter + + // Flags for f_ + enum Flag { + z = 1 << 7, // Zero Flag is 7th bit + n = 1 << 6, // Substract Flag (BCD) + h = 1 << 5, // Half-Carry Flag (BCD) + c = 1 << 4 // Carry Flag + // rest = 0 (lower 4 bit) + }; + + std::shared_ptr memory_; + + // TODO: Implement opcode fetch-decode-execute }; enum class Reg8 { A, F, B, C, D, E, H, L }; diff --git a/src/gameboy/memory/memory.cpp b/src/gameboy/memory/memory.cpp index 25ab6e8..bf63ad2 100644 --- a/src/gameboy/memory/memory.cpp +++ b/src/gameboy/memory/memory.cpp @@ -7,34 +7,34 @@ namespace GameBoy { Memory::Memory() : memory_array(std::make_unique>()) { - memory_array->fill(0); + memory_array->fill(0); } uint8_t Memory::read_byte_at(uint16_t address) { - if (boot_rom_enabled && address < 0x0100) { - return boot_array->at(address); - } - return memory_array->at(address); + 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) { - memory_array->at(address) = value; + memory_array->at(address) = value; } void Memory::load_rom(const std::vector& rom_data) { - size_t load_size = - std::min(rom_data.size(), size_t(0x8000)); // NOT necessarily rom size, we take the first 32KB for MBC - std::copy(rom_data.begin(), rom_data.begin() + load_size, memory_array->begin()); + size_t load_size = + std::min(rom_data.size(), size_t(0x8000)); // NOT necessarily rom size, we take the first 32KB for MBC + std::copy(rom_data.begin(), rom_data.begin() + load_size, 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; + 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; + boot_rom_enabled = on; } } // namespace GameBoy diff --git a/src/gameboy/memory/memory.h b/src/gameboy/memory/memory.h index 41b99da..c807cd3 100644 --- a/src/gameboy/memory/memory.h +++ b/src/gameboy/memory/memory.h @@ -11,26 +11,26 @@ namespace GameBoy { class Memory { - public: - // 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 - 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; + public: + // 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 + 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; }; } // namespace GameBoy diff --git a/src/main.cpp b/src/main.cpp index 5e45645..4d64efa 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -11,86 +11,89 @@ #include "gameboy/memory/memory.h" int main(int argc, char* argv[]) { - if (SDL_Init(SDL_INIT_EVERYTHING) != 0) { - std::cerr << "Error initializing SDL: " << SDL_GetError() << std::endl; - return 1; - } + if (SDL_Init(SDL_INIT_EVERYTHING) != 0) { + std::cerr << "Error initializing SDL: " << SDL_GetError() << std::endl; + return 1; + } - // default values - std::string rom_path; - std::ifstream rom_file; + // default values + std::string rom_path; + std::ifstream rom_file; - try { - switch (argc) { - case 2: { - // CLI mode - rom_path = argv[1]; - rom_file.open(rom_path, std::ios::in | std::ios::binary | std::ios::ate); // pointing seeker at end + try { + switch (argc) { + case 2: { + // CLI mode + rom_path = argv[1]; + rom_file.open(rom_path, + std::ios::in | std::ios::binary | std::ios::ate); // pointing seeker at end - if (!rom_file.is_open()) - throw std::runtime_error(" file could not be opened."); - if (rom_file.tellg() > 4000000) - throw std::runtime_error(" file size is too big for a standard GameBoy ROM (4 MB)"); - if (rom_file.tellg() < 0) - throw std::runtime_error(" file size is negative"); - break; - } - default: { - throw std::runtime_error("Incorrect number of arguments. Correct usage: ./gamedaddy >"); - } + if (!rom_file.is_open()) + throw std::runtime_error(" file could not be opened."); + if (rom_file.tellg() > 4000000) + throw std::runtime_error( + " file size is too big for a standard GameBoy ROM (4 MB)"); + if (rom_file.tellg() < 0) + throw std::runtime_error(" file size is negative"); + break; + } + default: { + throw std::runtime_error( + "Incorrect number of arguments. Correct usage: ./gamedaddy >"); + } + } + std::cout << "-------------------------------------------------------" << std::endl; + std::cout << std::format("Running {}", argv[0]) << std::endl; + std::cout << std::format("---> ROM: {}", rom_path) << std::endl; + std::cout << "-------------------------------------------------------" << std::endl; + } catch (const std::exception& e) { + std::cerr << "Error: " << e.what() << std::endl; + return 0; } - std::cout << "-------------------------------------------------------" << std::endl; - std::cout << std::format("Running {}", argv[0]) << std::endl; - std::cout << std::format("---> ROM: {}", rom_path) << std::endl; - std::cout << "-------------------------------------------------------" << std::endl; - } catch (const std::exception& e) { - std::cerr << "Error: " << e.what() << std::endl; - return 0; - } - // 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 + // 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 - // 2) initialize gui - bool use_gui = true; // TODO : Implement a toggle off for CLI mode - std::shared_ptr screen; - if (use_gui) { - screen = std::make_shared(160, 144); - } else { - // TODO Implement the CLI mode - } + // 2) initialize gui + bool use_gui = true; // TODO : Implement a toggle off for CLI mode + std::shared_ptr screen; + if (use_gui) { + screen = std::make_shared(160, 144); + } else { + // TODO Implement the CLI mode + } - // 3) initialized the platform - auto gb_platform = std::make_shared(cpu_instance, memory_instance); - gb_platform->setDisplay(screen); + // 3) initialized the platform + auto gb_platform = std::make_shared(cpu_instance, memory_instance); + gb_platform->setDisplay(screen); - // POWER-ON GAMEDADDYYY! ٩(ˊᗜˋ*)ノ - // --------------------------------------------------------------------------------- + // 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); + // 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); - // 2) 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."); - // 3) load the cartridge and ram - gb_platform->load_rom_into_memory(rom_data); - cpu_instance->attach_memory(memory_instance); + // 3) load the cartridge and ram + 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(); // now PC=0x0100 (skip boot) + // 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(); // 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) - SDL_Quit(); + // 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; + // End of all SDL subsystems + destruct layer + return 0; } From d8932cb5c858661e1078b57f06bac19ba5971465 Mon Sep 17 00:00:00 2001 From: William Kiem Lafond Date: Sun, 1 Mar 2026 15:44:37 -0500 Subject: [PATCH 6/9] format codebase with clang-format --- src/cartridge/rom/rom.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/cartridge/rom/rom.cpp b/src/cartridge/rom/rom.cpp index da0c75b..c979189 100644 --- a/src/cartridge/rom/rom.cpp +++ b/src/cartridge/rom/rom.cpp @@ -3,6 +3,7 @@ // #include "rom.h" +#include "../cart.h" #include "../../../include/helpers.h" #include "../../../include/units.h" From a888265d18776c304be2d55a1d3354d7e0cde9cb Mon Sep 17 00:00:00 2001 From: William Kiem Lafond Date: Mon, 2 Mar 2026 23:06:48 -0500 Subject: [PATCH 7/9] refactored all .clang format --- .clang-format | 6 + STYLES.md | 249 +++++++++++++++++++++++++ src/_platform/display/impl/sdl_gui.cpp | 6 +- src/cartridge/cart.h | 4 +- src/cartridge/rom/rom.cpp | 36 ++-- 5 files changed, 286 insertions(+), 15 deletions(-) create mode 100644 STYLES.md diff --git a/.clang-format b/.clang-format index 74d875d..e527086 100644 --- a/.clang-format +++ b/.clang-format @@ -51,3 +51,9 @@ Cpp11BracedListStyle: true # ---- Includes ---- SortIncludes: true IncludeBlocks: Preserve + +# --- Horizontal spacing is not allowed +AllowShortCaseLabelsOnASingleLine: false +AllowAllArgumentsOnNextLine: false +BinPackArguments: false +BinPackParameters: false diff --git a/STYLES.md b/STYLES.md new file mode 100644 index 0000000..3a2d08a --- /dev/null +++ b/STYLES.md @@ -0,0 +1,249 @@ +# C++ Style Guide + +This project follows an LLVM-inspired C++ style, tuned for emulator and +systems programming. + +Consistency and clarity are prioritized over cleverness. + +--- + +## Formatting + +Formatting is enforced via `clang-format`. + +- Base style: LLVM +- Indentation: **2 spaces** +- Tabs: **never** +- Brace style: K&R (opening brace on same line) +- Column limit: ~110 characters +- Access specifiers (`public`, `private`) are indented for readability, + especially when multiple classes exist in one file. + +### Formatting commands + +Format a single file: +```bash +clang-format -i path/to/file.cpp +```` + +Format multiple files: + +```bash +clang-format -i src/**/*.cpp include/**/*.h +``` + +Format only changed lines: + +```bash +git clang-format +``` + +--- + +## Naming Conventions + +### Namespaces + +* **PascalCase** + +```cpp +namespace GameBoy +namespace Cartridge +namespace CPU +``` + +--- + +### Types (classes, structs, enums) + +* **PascalCase** + +```cpp +class MBC1; +struct RomHeader; +enum class CartridgeType; +``` + +--- + +### Functions + +* **snake_case** + +```cpp +validate_rom_file(...) +clamp_rom_bank_(...) +``` + +--- + +### Variables + +* **snake_case** + +```cpp +rom_size_code +ram_enabled +``` + +--- + +### Member Variables + +* **snake_case with trailing underscore** + +```cpp +rom_bank_low5_ +ram_enabled_ +``` + +--- + +### Constants + +* **ALL_CAPS with underscores** +* Prefer `constexpr` over macros + +```cpp +constexpr uint16_t ROM_BEGIN = 0x0100; +constexpr uint16_t HEADER_CHECKSUM_OFFSET = 0x014D; +``` + +--- + +### Enums + +* Always use `enum class` +* Specify underlying type when relevant + +```cpp +enum class MBCType : uint8_t { + ROM_ONLY = 0x00, + MBC1 = 0x01, +}; +``` + +--- + +## C++ Language Rules + +### Constructors + +* Use `explicit` for all domain objects + +```cpp +explicit MBC1(const std::vector& rom, + std::vector& ram); +``` + +--- + +### Polymorphism + +* Use `override` for **every** overridden virtual function +* Use `final` for leaf classes +* Base destructors must be virtual + +--- + +### Copy / Move Semantics + +* Hardware-like identity objects (CPU, MBC, MMU, PPU) must not be copied or moved + +```cpp +MBC(const MBC&) = delete; +MBC& operator=(const MBC&) = delete; +MBC(MBC&&) = delete; +MBC& operator=(MBC&&) = delete; +``` + +--- + +### Ownership + +* Use `std::unique_ptr` for ownership +* Avoid raw `new` / `delete` +* Use references or raw pointers **only** for non-owning access + +--- + +## Comments + +### Philosophy + +* Code explains **what** +* Comments explain **why** + +--- + +### Hardware Behavior + +Hardware-specific logic **must be commented** and should reference +authoritative sources (e.g., Pan Docs). + +```cpp +// Pan Docs §Cartridge Header: +// Bytes 0x0134–0x014C are used for header checksum computation. +``` + +--- + +### Address Ranges + +Always document address ranges explicitly. + +```cpp +// ROM bank select register (0x2000–0x3FFF). +``` + +--- + +### Algorithms + +Use short block comments for non-trivial logic. + +```cpp +// Header checksum algorithm: +// 1. Iterate bytes 0x0134–0x014C +// 2. Subtract each byte and 1 from accumulator +// 3. Result must equal byte at 0x014D +``` + +--- + +### File Headers + +Each `.cpp` file should start with a short header: + +```cpp +// rom-validation.cpp +// Cartridge ROM header validation. +// Created by William Kiem Lafond on 2025-09-17. +``` + +Keep file headers factual and brief. + +--- + +### Avoid + +* Commenting obvious code +* Large prose blocks +* Stale comments (e.g., "last modified by") + +--- + +## Includes + +* Headers must be self-contained +* Do not use `using namespace` in headers +* Prefer forward declarations where reasonable + +--- + +## General Principles + +* Make illegal states unrepresentable +* Prefer early returns +* Prefer table-driven logic for hot paths +* Optimize for readability before micro-optimizations diff --git a/src/_platform/display/impl/sdl_gui.cpp b/src/_platform/display/impl/sdl_gui.cpp index 16c142b..1443442 100644 --- a/src/_platform/display/impl/sdl_gui.cpp +++ b/src/_platform/display/impl/sdl_gui.cpp @@ -30,7 +30,11 @@ SDLGui::SDLGui(int w, int h) { display_height = h; // initialize a window display - this->win = SDL_CreateWindow("GameDaddy", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, w, h, + this->win = SDL_CreateWindow("GameDaddy", + SDL_WINDOWPOS_CENTERED, + SDL_WINDOWPOS_CENTERED, + w, + h, SDL_WINDOW_RESIZABLE); if (!win) throw std::runtime_error("GameDaddy's GUI could not be opened!"); diff --git a/src/cartridge/cart.h b/src/cartridge/cart.h index f63cb17..7706aea 100644 --- a/src/cartridge/cart.h +++ b/src/cartridge/cart.h @@ -1,6 +1,7 @@ #pragma once #include "mbc/mbc.h" +#include #include #include #include @@ -12,9 +13,6 @@ constexpr size_t OFF_CARTRIDGE_T = 0x0147; constexpr size_t OFF_ROM_SIZE = 0x0148; constexpr size_t OFF_RAM_SIZE = 0x0149; -// RAM size lookup table (by RAM size code). -extern const std::unordered_map RAM_SIZE; - class Cart { public: explicit Cart(std::vector rom); diff --git a/src/cartridge/rom/rom.cpp b/src/cartridge/rom/rom.cpp index c979189..84bfa80 100644 --- a/src/cartridge/rom/rom.cpp +++ b/src/cartridge/rom/rom.cpp @@ -3,9 +3,9 @@ // #include "rom.h" -#include "../cart.h" #include "../../../include/helpers.h" #include "../../../include/units.h" +#include "../cart.h" #include #include @@ -26,16 +26,28 @@ static constexpr size_t OFF_GLOB_CHECK = 0x014E; // start of global check (dont static constexpr size_t MIN_ROM_SIZE = 0x0150; // rom cant be smaller than this static const std::unordered_map ROM_SIZE = { - {0x00, 32 * KiB}, {0x01, 64 * KiB}, {0x02, 128 * KiB}, {0x03, 256 * KiB}, - {0x04, 512 * KiB}, {0x05, 1 * MiB}, {0x06, 2 * MiB}, {0x07, 4 * MiB}, - {0x08, 8 * MiB}, {0x52, 1152 * KiB}, // inaccurate and not widely used - {0x53, 1280 * KiB}, // inaccurate and not widely used - {0x54, 1536 * KiB}, // inaccurate and not widely used + {0x00, 32 * KiB}, + {0x01, 64 * KiB}, + {0x02, 128 * KiB}, + {0x03, 256 * KiB}, + {0x04, 512 * KiB}, + {0x05, 1 * MiB}, + {0x06, 2 * MiB}, + {0x07, 4 * MiB}, + {0x08, 8 * MiB}, + {0x52, 1152 * KiB}, // inaccurate and not widely used + {0x53, 1280 * KiB}, // inaccurate and not widely used + {0x54, 1536 * KiB}, // inaccurate and not widely used }; -static const std::unordered_map RAM_SIZE = { - {0x00, 0}, {0x01, 2 * KiB}, // this has never been used so Pandocs says 'UNUSED' - {0x02, 8 * KiB}, {0x03, 32 * KiB}, {0x04, 128 * KiB}, {0x05, 64 * KiB}}; +static constexpr std::array RAM_SIZE = { + 0, // 0x00 + 2 * KiB, // 0x01 this has never been used so Pandocs says 'UNUSED' + 8 * KiB, // 0x02 + 32 * KiB, // 0x03 + 128 * KiB, // 0x04 + 64 * KiB, // 0x05 +}; static const std::unordered_map CARTRIDGE_TYPES = { {0x00, "ROM ONLY"}, @@ -128,14 +140,16 @@ Cartridge::RomValidationResult validate_rom_file(const std::vector& rom // check if actual rom data size is in lined with our code's mapping if (rom_data.size() != ROM_SIZE.at(rom_size_code)) { - out.errors.emplace_back(Gameboy::msg("Error Wrong Rom Size: ", rom_data.size(), "and mapped to", + out.errors.emplace_back(Gameboy::msg("Error Wrong Rom Size: ", + rom_data.size(), + "and mapped to", ROM_SIZE.at(rom_size_code))); return out; } // check if RAM_size_code is in an official size code out.ram_size_code = rom_data[OFF_RAM_SIZE]; - if (!RAM_SIZE.count(out.ram_size_code)) { + if (out.ram_size_code > 0x05) { out.errors.emplace_back("Unknown RAM size code (0x0149)."); } From 9a45b4c381c8e2d3382245e48920aeae35efe629 Mon Sep 17 00:00:00 2001 From: William Kiem Lafond Date: Mon, 2 Mar 2026 23:36:13 -0500 Subject: [PATCH 8/9] changed unord map to functions --- src/cartridge/cart.cpp | 3 +- src/cartridge/rom/rom.cpp | 96 +++++++++++++-------------------------- src/cartridge/rom/rom.h | 5 ++ 3 files changed, 39 insertions(+), 65 deletions(-) diff --git a/src/cartridge/cart.cpp b/src/cartridge/cart.cpp index bac168e..981f0e6 100644 --- a/src/cartridge/cart.cpp +++ b/src/cartridge/cart.cpp @@ -1,6 +1,7 @@ #include "cart.h" #include "cartridge/mbc/mbc.h" +#include "cartridge/rom/rom.h" namespace Cartridge { @@ -25,7 +26,7 @@ void Cart::call_write(uint16_t addr, uint8_t value) { // Resize ram_ into "ram size", specified with ram_size_code_ at 0x149. void Cart::alloc_ram_() { - ram_.resize(RAM_SIZE.at(ram_size_code_), 0xFF); // most hardware inits with high + ram_.resize(ram_size_bytes(ram_size_code_), 0xFF); // most hardware inits with high } // Forward function to mbc's read. diff --git a/src/cartridge/rom/rom.cpp b/src/cartridge/rom/rom.cpp index 84bfa80..e954b95 100644 --- a/src/cartridge/rom/rom.cpp +++ b/src/cartridge/rom/rom.cpp @@ -25,59 +25,35 @@ static constexpr size_t OFF_HEAD_CHECK = 0x014D; // necessary for boot-rom static constexpr size_t OFF_GLOB_CHECK = 0x014E; // start of global check (dont include it) static constexpr size_t MIN_ROM_SIZE = 0x0150; // rom cant be smaller than this -static const std::unordered_map ROM_SIZE = { - {0x00, 32 * KiB}, - {0x01, 64 * KiB}, - {0x02, 128 * KiB}, - {0x03, 256 * KiB}, - {0x04, 512 * KiB}, - {0x05, 1 * MiB}, - {0x06, 2 * MiB}, - {0x07, 4 * MiB}, - {0x08, 8 * MiB}, - {0x52, 1152 * KiB}, // inaccurate and not widely used - {0x53, 1280 * KiB}, // inaccurate and not widely used - {0x54, 1536 * KiB}, // inaccurate and not widely used -}; - -static constexpr std::array RAM_SIZE = { - 0, // 0x00 - 2 * KiB, // 0x01 this has never been used so Pandocs says 'UNUSED' - 8 * KiB, // 0x02 - 32 * KiB, // 0x03 - 128 * KiB, // 0x04 - 64 * KiB, // 0x05 -}; - -static const std::unordered_map CARTRIDGE_TYPES = { - {0x00, "ROM ONLY"}, - {0x01, "MBC1"}, - {0x02, "MBC1+RAM"}, - {0x03, "MBC1+RAM+BATTERY"}, - {0x05, "MBC2"}, - {0x06, "MBC2+BATTERY"}, - {0x08, "ROM+RAM"}, - {0x09, "ROM+RAM+BATTERY"}, - {0x0B, "MMM01"}, - {0x0C, "MMM01+RAM"}, - {0x0D, "MMM01+RAM+BATTERY"}, - {0x0F, "MBC3+TIMER+BATTERY"}, - {0x10, "MBC3+TIMER+RAM+BATTERY"}, - {0x11, "MBC3"}, - {0x12, "MBC3+RAM"}, - {0x13, "MBC3+RAM+BATTERY"}, - {0x19, "MBC5"}, - {0x1A, "MBC5+RAM"}, - {0x1B, "MBC5+RAM+BATTERY"}, - {0x1C, "MBC5+RUMBLE"}, - {0x1D, "MBC5+RUMBLE+RAM"}, - {0x1E, "MBC5+RUMBLE+RAM+BATTERY"}, - {0x20, "MBC6"}, - {0x22, "MBC7+SENSOR+RUMBLE+RAM+BATTERY"}, - {0xFC, "POCKET CAMERA"}, - {0xFD, "BANDAI TAMA5"}, - {0xFE, "HuC3"}, - {0xFF, "HuC1+RAM+BATTERY"}}; +size_t rom_size_bytes(uint8_t code) { + switch (code) { + case 0x00: return 32 * KiB; + case 0x01: return 64 * KiB; + case 0x02: return 128 * KiB; + case 0x03: return 256 * KiB; + case 0x04: return 512 * KiB; + case 0x05: return 1 * MiB; + case 0x06: return 2 * MiB; + case 0x07: return 4 * MiB; + case 0x08: return 8 * MiB; + case 0x52: return 1152 * KiB; // inaccurate and not widely used + case 0x53: return 1280 * KiB; // inaccurate and not widely used + case 0x54: return 1536 * KiB; // inaccurate and not widely used + default: return 0; + } +} + +size_t ram_size_bytes(uint8_t code) { + switch (code) { + case 0x00: return 0; + case 0x01: return 2 * KiB; + case 0x02: return 8 * KiB; + case 0x03: return 32 * KiB; + case 0x04: return 128 * KiB; + case 0x05: return 64 * KiB; + default: return 0; + } +} static uint8_t header_checksum(const std::vector& rom_data) { uint8_t checksum = 0; @@ -122,28 +98,20 @@ Cartridge::RomValidationResult validate_rom_file(const std::vector& rom return out; } - // check cartridge type (error if not valid cartridge) - uint8_t cart_type = rom_data.at(OFF_CARTRIDGE_T); - out.cartridge_type = cart_type; - if (CARTRIDGE_TYPES.count(cart_type) == 0) { - out.errors.emplace_back(Gameboy::msg("Error Wrong Cartridge Type:", cart_type)); - return out; - } - // check if rom_size_code byte is an official size code uint8_t rom_size_code = rom_data.at(OFF_ROM_SIZE); out.rom_size_code = rom_size_code; - if (ROM_SIZE.count(rom_size_code) == 0) { + if (rom_size_bytes(rom_size_code) == 0) { out.errors.emplace_back(Gameboy::msg("Error Wrong Rom Size Code:", rom_size_code)); return out; } // check if actual rom data size is in lined with our code's mapping - if (rom_data.size() != ROM_SIZE.at(rom_size_code)) { + if (rom_data.size() != rom_size_bytes(rom_size_code)) { out.errors.emplace_back(Gameboy::msg("Error Wrong Rom Size: ", rom_data.size(), "and mapped to", - ROM_SIZE.at(rom_size_code))); + rom_size_bytes(rom_size_code))); return out; } diff --git a/src/cartridge/rom/rom.h b/src/cartridge/rom/rom.h index 6bd2f3b..4886532 100644 --- a/src/cartridge/rom/rom.h +++ b/src/cartridge/rom/rom.h @@ -2,12 +2,17 @@ // Created by William Kiem Lafond on 2025-09-17. // #pragma once + #include #include #include namespace Cartridge { +// Lookup tables for Cartridge +size_t rom_size_bytes(uint8_t code); +size_t ram_size_bytes(uint8_t code); + struct RomValidationResult { bool ok = false; std::vector errors; From bb08b13a1bf82a016564cfe98fe18e5048324009 Mon Sep 17 00:00:00 2001 From: William Kiem Lafond Date: Thu, 12 Mar 2026 21:41:08 -0400 Subject: [PATCH 9/9] cleaned out comments for cart.cpp --- src/cartridge/cart.cpp | 5 ----- src/cartridge/rom/rom.cpp | 1 - 2 files changed, 6 deletions(-) diff --git a/src/cartridge/cart.cpp b/src/cartridge/cart.cpp index 981f0e6..4fb6dee 100644 --- a/src/cartridge/cart.cpp +++ b/src/cartridge/cart.cpp @@ -1,4 +1,3 @@ - #include "cart.h" #include "cartridge/mbc/mbc.h" #include "cartridge/rom/rom.h" @@ -14,22 +13,18 @@ Cart::Cart(std::vector rom) : rom_(std::move(rom)) { attach_mbc_(); } -// Forward function to mbc's read. uint8_t Cart::call_read(uint16_t addr) { return mbc_->read(addr); } -// Forward function to mbc's write. void Cart::call_write(uint16_t addr, uint8_t value) { mbc_->write(addr, value); } -// Resize ram_ into "ram size", specified with ram_size_code_ at 0x149. void Cart::alloc_ram_() { ram_.resize(ram_size_bytes(ram_size_code_), 0xFF); // most hardware inits with high } -// Forward function to mbc's read. void Cart::attach_mbc_() { switch (cart_type_) { case 0x00: // ROM-ONLY diff --git a/src/cartridge/rom/rom.cpp b/src/cartridge/rom/rom.cpp index e954b95..91039ed 100644 --- a/src/cartridge/rom/rom.cpp +++ b/src/cartridge/rom/rom.cpp @@ -10,7 +10,6 @@ #include #include #include -#include #include using GameBoy::units::KiB;