diff --git a/.clang-format b/.clang-format index 4c1bb9e..e527086 100644 --- a/.clang-format +++ b/.clang-format @@ -1,39 +1,59 @@ BasedOnStyle: LLVM + +# ---- Indentation ---- IndentWidth: 4 TabWidth: 4 UseTab: Never -# Keep namespaces flush-left (no indent inside) +# Do NOT indent inside namespaces NamespaceIndentation: None -# Braces +# Indent access specifiers (public / private) +IndentAccessModifiers: true +AccessModifierOffset: -2 + +# Indent case labels inside switch +IndentCaseLabels: true + +# ---- Line length ---- +ColumnLimit: 110 + +# ---- Braces & control flow ---- BreakBeforeBraces: Attach -AllowShortFunctionsOnASingleLine: Empty AllowShortIfStatementsOnASingleLine: false AllowShortLoopsOnASingleLine: false +AllowShortBlocksOnASingleLine: Never +AllowShortFunctionsOnASingleLine: Empty -# Constructor initializer list formatting -ConstructorInitializerIndentWidth: 4 -BreakConstructorInitializers: AfterColon -ConstructorInitializerAllOnOneLineOrOnePerLine: false +# ---- Alignment ---- +AlignAfterOpenBracket: Align +AlignOperands: Align +AlignTrailingComments: true -# Pointer and reference alignment +# ---- Spacing ---- +SpaceBeforeParens: ControlStatements +SpacesInParentheses: false +SpacesInSquareBrackets: false + +# ---- Pointers / references ---- PointerAlignment: Left ReferenceAlignment: Left +DerivePointerAlignment: false -# Column limit -ColumnLimit: 120 +# ---- Constructors ---- +BreakConstructorInitializers: AfterColon +ConstructorInitializerIndentWidth: 4 -# Spaces -SpaceBeforeParens: ControlStatements -SpacesInParentheses: false -SpacesInSquareBrackets: false -SpacesInAngles: false -SpaceBeforeAssignmentOperators: true +# ---- Templates / lists ---- +AlwaysBreakTemplateDeclarations: Yes Cpp11BracedListStyle: true -# Indent access specifiers (public:, private:) 1 level -AccessModifierOffset: -4 +# ---- Includes ---- +SortIncludes: true +IncludeBlocks: Preserve -# Indent case labels inside switch -IndentCaseLabels: true +# --- 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/include/helpers.h b/include/helpers.h index 874ec38..117dc07 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..30b2443 100644 --- a/src/_platform/display/display_interface.h +++ b/src/_platform/display/display_interface.h @@ -7,17 +7,18 @@ 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; -protected: - int display_width = 0; - int display_height = 0; - // base classes or interfaces should not have private methods/fields + 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 }; -} +} // 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..1443442 100644 --- a/src/_platform/display/impl/sdl_gui.cpp +++ b/src/_platform/display/impl/sdl_gui.cpp @@ -6,146 +6,157 @@ #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 } ); - +/** + * @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); - // 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); + return; } - - /** - * @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; + 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 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 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 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 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 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); - } +/** + * @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); +} - /** - * 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 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); +} - /** - * @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()); - } +/** + * 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..a6bf134 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: - 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. + 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. }; -} +} // namespace GameBoy -#endif //SDL_GUI_H +#endif // SDL_GUI_H diff --git a/src/_platform/platform.cpp b/src/_platform/platform.cpp index 01ce97f..092f5eb 100644 --- a/src/_platform/platform.cpp +++ b/src/_platform/platform.cpp @@ -4,31 +4,27 @@ #include "platform.h" -#include #include #include +#include #include +#include "../cartridge/rom/rom.h" #include "../gameboy/memory/memory.h" -#include "../cartridge/rom/rom-validation.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_) { @@ -56,12 +52,12 @@ void Platform::run_frame() { // draw vertical line for (int r = center_row - 5; r <= center_row + 5; ++r) { - display_->draw_pixel(center_col, r, true, 3); // black + 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 + display_->draw_pixel(c, center_row, true, 3); // black } display_->present_idle(); @@ -97,7 +93,8 @@ 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; + for (std::string& e : res.errors) + std::cerr << " - " << e << std::endl; } return res.ok; } diff --git a/src/_platform/platform.h b/src/_platform/platform.h index 05eba69..6318bfc 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 - 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 -#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 ca78094..4fb6dee 100644 --- a/src/cartridge/cart.cpp +++ b/src/cartridge/cart.cpp @@ -1,7 +1,41 @@ - #include "cart.h" +#include "cartridge/mbc/mbc.h" +#include "cartridge/rom/rom.h" 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); + + 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::alloc_ram_() { + ram_.resize(ram_size_bytes(ram_size_code_), 0xFF); // most hardware inits with high } + +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_)); + } +} + +} // namespace Cartridge diff --git a/src/cartridge/cart.h b/src/cartridge/cart.h index c5424e7..7706aea 100644 --- a/src/cartridge/cart.h +++ b/src/cartridge/cart.h @@ -1,58 +1,48 @@ -/// We removed the SGB functionality - #pragma once + #include "mbc/mbc.h" +#include #include -#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"} -}; +// 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; class Cart { -private: - uint8_t cart_type_; // 0x0147 - uint8_t rom_size_; // 0x0148 - uint8_t ram_size_; // 0x0149 + 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); - std::vector rom_; - std::vector ram_; - std::unique_ptr mbc_; + // 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_; + } -public: - Cart(); + 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 call_read(uint8_t); - uint8_t call_write(uint8_t); + 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 4dd696a..cb77316 100644 --- a/src/cartridge/mbc/mbc.cpp +++ b/src/cartridge/mbc/mbc.cpp @@ -10,6 +10,8 @@ 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); @@ -17,18 +19,30 @@ static uint32_t rom_bank_count_from_bytes(std::size_t rom_size) { 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; + if (ram_size == 0) + return 0; + if (ram_size <= 0x2000) + return 1; 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; @@ -49,5 +63,4 @@ uint8_t MBC1::read(uint16_t addr) { return 0; } - -} +} // namespace Cartridge diff --git a/src/cartridge/mbc/mbc.h b/src/cartridge/mbc/mbc.h index e1c8a23..2b64a2c 100644 --- a/src/cartridge/mbc/mbc.h +++ b/src/cartridge/mbc/mbc.h @@ -8,36 +8,59 @@ namespace Cartridge { -// MBC Interface (pure virtual/abstract) +// MBC Interface (pure virtual/abstract) --------------------------------------------------------------------- class MBC { -public: - virtual ~MBC() = default; - virtual uint8_t read(uint16_t addr) = 0; - virtual void write(uint16_t addr, uint8_t value) = 0; + 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; }; +/// POLY IMPLEMENTATIONS OF MBC INTERFACE: +/// --------------------------------------------------------------------- -class MBC1 final : public MBC { -public: - MBC1(const std::vector& rom, std::vector& ram); +// 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_; + uint8_t read(uint16_t addr) override; + void write(uint16_t addr, uint8_t value) override; - bool ram_enabled_ = false; + 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) {} - // 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 read(uint16_t addr) override; + void write(uint16_t addr, uint8_t value) override; - uint32_t rom_bank_count_ = 0; - uint32_t ram_bank_count_ = 0; + private: + const std::vector& rom_; + std::vector& ram_; - 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-validation.h b/src/cartridge/rom/rom-validation.h deleted file mode 100644 index 3076a90..0000000 --- a/src/cartridge/rom/rom-validation.h +++ /dev/null @@ -1,20 +0,0 @@ -// -// Created by William Kiem Lafond on 2025-09-17. -// -#pragma once -#include -#include -#include - -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; -}; - -RomValidationResult validate_rom_file(const std::vector& rom); -}; diff --git a/src/cartridge/rom/rom-validation.cpp b/src/cartridge/rom/rom.cpp similarity index 62% rename from src/cartridge/rom/rom-validation.cpp rename to src/cartridge/rom/rom.cpp index 247891e..91039ed 100644 --- a/src/cartridge/rom/rom-validation.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 "rom.h" #include "../../../include/helpers.h" +#include "../../../include/units.h" #include "../cart.h" -#include "rom-validation.h" - -#include -#include -#include #include +#include +#include #include using GameBoy::units::KiB; @@ -19,39 +17,42 @@ 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_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 - {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 size_t OFF_ROM_BEGIN = 0x0100; +static constexpr size_t OFF_LOGO_BEG = 0x0104; // necessary for boot-rom + +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 + +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; @@ -96,32 +97,26 @@ 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)) { - out.errors.emplace_back(Gameboy::msg("Error Wrong Rom Size: ", rom_data.size(), - "and mapped to", 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_bytes(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)."); } @@ -133,11 +128,19 @@ 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 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: @@ -161,4 +164,4 @@ Cartridge::RomValidationResult validate_rom_file(const std::vector& rom return out; } -} +} // namespace Cartridge diff --git a/src/cartridge/rom/rom.h b/src/cartridge/rom/rom.h new file mode 100644 index 0000000..4886532 --- /dev/null +++ b/src/cartridge/rom/rom.h @@ -0,0 +1,25 @@ +// +// 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; + 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); +}; // namespace Cartridge diff --git a/src/gameboy/cpu/cpu.cpp b/src/gameboy/cpu/cpu.cpp index 195fe5c..da57068 100644 --- a/src/gameboy/cpu/cpu.cpp +++ b/src/gameboy/cpu/cpu.cpp @@ -11,10 +11,9 @@ 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) { +void CPU::attach_memory(std::shared_ptr mem) { memory_ = mem; } @@ -33,9 +32,9 @@ void CPU::reset_registers_fast() { sp_ = 0xFFFE; } - int CPU::step() { - if (!memory_) throw std::runtime_error("There is no Memory attached to CPU"); + 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_ @@ -46,40 +45,66 @@ int CPU::step() { // TODO: remove this and use chip-8 switch table (or jump threading) switch (opcode) { case 0x00: // NOP - // do nothing - return 4; + // do nothing + return 4; default: // For now, just pretend it took 4 cycles - return 4; + 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"); } } -} +} // namespace GameBoy diff --git a/src/gameboy/cpu/cpu.h b/src/gameboy/cpu/cpu.h index 0ba0d27..5d9ee1f 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,51 +16,56 @@ 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 }; -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..bf63ad2 100644 --- a/src/gameboy/memory/memory.cpp +++ b/src/gameboy/memory/memory.cpp @@ -6,8 +6,7 @@ // namespace GameBoy { -Memory::Memory() : - memory_array(std::make_unique>()) { +Memory::Memory() : memory_array(std::make_unique>()) { memory_array->fill(0); } @@ -23,21 +22,14 @@ void Memory::write_byte_at(uint16_t address, uint8_t 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() - ); + std::copy(boot_data.begin(), boot_data.begin() + load_size, boot_array->begin()); boot_rom_enabled = true; } @@ -45,6 +37,4 @@ void Memory::set_boot_enabled(bool on) { boot_rom_enabled = on; } - -} - +} // namespace GameBoy diff --git a/src/gameboy/memory/memory.h b/src/gameboy/memory/memory.h index bf47b80..c807cd3 100644 --- a/src/gameboy/memory/memory.h +++ b/src/gameboy/memory/memory.h @@ -11,28 +11,28 @@ 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 -#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..4d64efa 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,17 +1,16 @@ -#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[]) -{ +int main(int argc, char* argv[]) { if (SDL_Init(SDL_INIT_EVERYTHING) != 0) { std::cerr << "Error initializing SDL: " << SDL_GetError() << std::endl; return 1; @@ -26,26 +25,28 @@ int main(int argc, char *argv[]) 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 + 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)"); + 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 >"); + 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("Running {}", argv[0]) << std::endl; std::cout << std::format("---> ROM: {}", rom_path) << std::endl; std::cout << "-------------------------------------------------------" << std::endl; - } - catch (const std::exception& e) { + } catch (const std::exception& e) { std::cerr << "Error: " << e.what() << std::endl; return 0; } @@ -65,18 +66,15 @@ int main(int argc, char *argv[]) } // 3) initialized the platform - auto gb_platform = std::make_shared( - cpu_instance, - memory_instance - ); + 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 + 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); @@ -89,7 +87,7 @@ int main(int argc, char *argv[]) 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 + 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 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;