From 32837712fedf4557ff44df7d5b8ddbd40c5f7990 Mon Sep 17 00:00:00 2001 From: kotorifan Date: Fri, 23 Jan 2026 15:15:48 +0100 Subject: Stupid problems with the old git repo. New one, unfortunately. --- src/Common.hpp | 19 +++++ src/Debugger.cpp | 6 ++ src/Debugger.hpp | 16 ++++ src/Main.cpp | 54 ++++++++++++ src/Memory.cpp | 12 +++ src/Memory.hpp | 13 +++ src/Processor.cpp | 243 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/Processor.hpp | 51 ++++++++++++ src/SDL_stuff.cpp | 86 +++++++++++++++++++ src/SDL_stuff.hpp | 33 ++++++++ 10 files changed, 533 insertions(+) create mode 100644 src/Common.hpp create mode 100644 src/Debugger.cpp create mode 100644 src/Debugger.hpp create mode 100644 src/Main.cpp create mode 100644 src/Memory.cpp create mode 100644 src/Memory.hpp create mode 100644 src/Processor.cpp create mode 100644 src/Processor.hpp create mode 100644 src/SDL_stuff.cpp create mode 100644 src/SDL_stuff.hpp (limited to 'src') diff --git a/src/Common.hpp b/src/Common.hpp new file mode 100644 index 0000000..82d37a9 --- /dev/null +++ b/src/Common.hpp @@ -0,0 +1,19 @@ +#pragma once + +#include + +namespace DMG01 +{ + /** * + * @name PROGRAM_NAME + * @brief This has to be the same name as in the Makefile, + * or there will be inconsistencies at some places + */ + constexpr const char* PROGRAM_NAME = "gameboy"; + constexpr uint32_t MEMORY_SIZE = 0xffff; + constexpr uint32_t WINDOW_SIZE_X = 800; + constexpr uint32_t WINDOW_SIZE_Y = 600; + constexpr const char* WINDOW_NAME_DEBUG = "gameboy debugger"; + constexpr const char* FONT_DEFAULT_DEBUG = "./res/pixelated-elegance-font/PixelatedEleganceRegular-ovyAA.ttf"; + constexpr uint32_t FONT_SIZE_DEFAULT_DEBUG = 20; +} diff --git a/src/Debugger.cpp b/src/Debugger.cpp new file mode 100644 index 0000000..e705451 --- /dev/null +++ b/src/Debugger.cpp @@ -0,0 +1,6 @@ +#include "SDL_stuff.hpp" +#include "Debugger.hpp" + +namespace DMG01 +{ +} diff --git a/src/Debugger.hpp b/src/Debugger.hpp new file mode 100644 index 0000000..fed6f93 --- /dev/null +++ b/src/Debugger.hpp @@ -0,0 +1,16 @@ +#pragma once +#include +#include + +#include "SDL_stuff.hpp" +#include "Common.hpp" + +namespace DMG01 +{ + class Debugger_Window : public Generic_SDL_Window + { + public: + char* debug_info_string; + void read_debug_info(); + }; +} diff --git a/src/Main.cpp b/src/Main.cpp new file mode 100644 index 0000000..6c3adf1 --- /dev/null +++ b/src/Main.cpp @@ -0,0 +1,54 @@ +#include +#include +#include +#include +#include + +#include "SDL_stuff.hpp" +#include "Debugger.hpp" +#include "Processor.hpp" +#include "Memory.hpp" +#include "Common.hpp" + +static inline void show_help() +{ + std::cout << "Usage: ./PROGRAM_NAME [EXECUTABLE] [OPTIONS]\n"; + std::cout << "Emulate a given DMG-01 ROM/executable.\n"; + std::cout << "The first argument is always the file to be executed\n"; +} + + +int main(int argc, char* argv[]) +{ + if(argc < 2) { + show_help(); + return EXIT_FAILURE; + } + DMG01::SM83 sm83; + DMG01::Memory memory; + std::string exec_file(argv[1]); + sm83.load_bin(exec_file, memory); + DMG01::Generic_SDL_Window debugger_window(800, 600); + debugger_window.create_window(); + debugger_window.update_text("Debugger started."); + static bool should_display = true; + sm83.stop_state = false; + while((sm83.pc < DMG01::MEMORY_SIZE) && should_display) { + char buffer[256]; + if(!sm83.stop_state) { + const uint8_t opcode = memory.space[sm83.pc]; + sm83.pc++; + sm83.process_opcodes(opcode, memory, sm83); + } + should_display = debugger_window.check_polling_event(); + snprintf(buffer, sizeof(buffer), + "PC: 0x%04X SP: 0x%04X 0x%04X BC: 0x%04X DE: 0x%04X HL: 0x%04X", + sm83.pc, sm83.sp, sm83.af.word, sm83.bc.word, sm83.de.word, sm83.hl.word); + debugger_window.update_text(buffer); + debugger_window.render_text(); + + SDL_Delay(1); + } + return EXIT_SUCCESS; +} + diff --git a/src/Memory.cpp b/src/Memory.cpp new file mode 100644 index 0000000..c90bfb0 --- /dev/null +++ b/src/Memory.cpp @@ -0,0 +1,12 @@ +#include "Memory.hpp" +#include "Common.hpp" +#include + +namespace DMG01 +{ + + Memory::Memory() + { + this->space = std::make_unique(0xffff); + } +} diff --git a/src/Memory.hpp b/src/Memory.hpp new file mode 100644 index 0000000..eecb0dd --- /dev/null +++ b/src/Memory.hpp @@ -0,0 +1,13 @@ +#pragma once + +#include + +namespace DMG01 +{ + class Memory + { + public: + std::unique_ptr space; + Memory(); + }; +} diff --git a/src/Processor.cpp b/src/Processor.cpp new file mode 100644 index 0000000..38cf9ba --- /dev/null +++ b/src/Processor.cpp @@ -0,0 +1,243 @@ +/** @file Processor.cpp */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "Processor.hpp" +#include "Memory.hpp" +#include "Common.hpp" + +/** * @name Register shorthands + * These macros are there so I don't have to write out + * the whole union's name every time I want to access + * a register. + * @{ + */ +#define R_A sm83.af.byte.higher +#define R_F sm83.af.byte.lower +#define R_AF sm83.af.word +#define R_H sm83.hl.byte.higher +#define R_L sm83.hl.byte.lower +#define R_HL sm83.hl.word +#define R_D sm83.de.byte.higher +#define R_E sm83.de.byte.lower +#define R_DE sm83.de.word +#define R_B sm83.bc.byte.higher +#define R_C sm83.bc.byte.lower +#define R_BC sm83.bc.word +#define R_PC sm83.pc +#define R_SP sm83.sp +/** @} */ + +/** * @name Flag shorthands + * Same as with the register shorthands, just + * convenience. + * @{ + */ +#define F_CARRY 4 +#define F_HALF_CARRY 5 +#define F_SUBSTRACTION 6 +#define F_ZERO 7 +/** @} */ + +#define ADD_CYC(val) add_cycles(cycles, val) + +namespace DMG01 +{ + + inline void SM83::add_cycles(Cycles& cycles, const uint32_t count) + { + cycles.total_cycles += count; + } + + inline void SM83::set_flag(uint8_t& f, const uint8_t index, const bool on) + { + std::bitset<8>(f).set(index, on); + } + + inline bool SM83::test_flag(uint8_t& f, const uint8_t index) + { + return std::bitset<8>(f).test(index); + } + + /* + @brief Loads the ROM/executable into the emulator + */ + void SM83::load_bin(const std::string& filename, Memory& memory) + { + std::ifstream f((filename), std::ios_base::binary); + if(!f) throw std::runtime_error("Failed to open the file"); + f.seekg(0, std::ios_base::end); + auto f_size = f.tellg(); + f.seekg(0, std::ios_base::beg); + f.read(reinterpret_cast(memory.space.get()), f_size); + } + + template + inline void SM83::ld_imm(T1& reg, const T2& data) + { + reg = static_cast(data); + } + + template + inline void SM83::ld_reg(T1& reg1, const T2& reg2) + { + reg1 = reg2; + } + + template + inline void SM83::inc_reg(T& val) + { + val++; + } + + template + inline void SM83::dec_reg(T& val) + { + val--; + } + + inline void SM83::rlca(SM83& sm83) + { + R_A = (R_A >> 1 | R_A << 7); + if(sm83.test_flag(R_F, F_CARRY)) + sm83.set_flag(R_F, F_CARRY, true); + else if(sm83.test_flag(R_F, F_CARRY)) + sm83.set_flag(R_F, F_CARRY, false); + } + + template + inline void SM83::add_reg(T1& aug, const T2& add) + { + aug =+ add; + } + + inline void SM83::rrca(SM83& sm83) + { + R_A = (R_A << 1 | R_A >> 7); + if(sm83.test_flag(R_F, F_CARRY)) + sm83.set_flag(R_F, F_CARRY, true); + else if(sm83.test_flag(R_F, F_CARRY)) + sm83.set_flag(R_F, F_CARRY, false); + } + + inline void SM83::stop(SM83& sm83, Memory& memory) + { + R_PC++; + memory.space[0xff04] = 0x00; + sm83.stop_state = true; + } + /* + @brief Processes opcodes and calls the according function + @param opcode The opcode it should process + */ + void SM83::process_opcodes(const uint8_t opcode, Memory& memory, SM83& sm83) + { + switch(opcode) + { + case 0x00: /* NOP */ + ADD_CYC(4); + break; + case 0x01: /* LD BC, n16 */ + ld_imm(R_BC, memory.space[R_PC++]); + ADD_CYC(12); + break; + case 0x02: /* LD (BC), A */ + ld_reg(memory.space[R_BC], R_A); + ADD_CYC(8); + break; + case 0x03: /* INC BC */ + inc_reg(R_BC); + ADD_CYC(8); + break; + case 0x04: /* INC B */ + inc_reg(R_B); + ADD_CYC(4); + break; + case 0x05: /* DEC B */ + dec_reg(R_B); + ADD_CYC(4); + break; + case 0x06: /* LD B, d6 */ + ld_imm(R_B, memory.space[R_PC++]); + ADD_CYC(8); + break; + case 0x07: /* RLCA */ + rlca(sm83); + ADD_CYC(4); + break; + case 0x08: /* LD (a16), SP */ + ld_imm(memory.space[R_PC++], R_SP); + ADD_CYC(20); + break; + case 0x09: + add_reg(R_HL, R_BC); + ADD_CYC(8); + break; + case 0x0a: + ld_reg(R_A, memory.space[R_BC]); + ADD_CYC(8); + break; + case 0x0b: + dec_reg(R_BC); + ADD_CYC(8); + break; + case 0x0c: + inc_reg(R_C); + ADD_CYC(4); + break; + case 0x0d: + dec_reg(R_C); + ADD_CYC(4); + break; + case 0x0e: + ld_imm(R_C, memory.space[R_PC++]); + ADD_CYC(8); + break; + case 0x0f: + rrca(sm83); + ADD_CYC(4); + break; + case 0x10: + stop(sm83, memory); + ADD_CYC(4); + break; + case 0x11: + ld_imm(R_DE, memory.space[R_PC++]); + ADD_CYC(12); + break; + case 0x12: + ld_imm(memory.space[R_DE], R_A); + ADD_CYC(8); + break; + case 0x13: + inc_reg(R_DE); + ADD_CYC(8); + break; + case 0x14: + inc_reg(R_D); + ADD_CYC(4); + break; + case 0x15: + dec_reg(R_D); + ADD_CYC(4); + break; + case 0x16: + ld_reg(R_DE, memory.space[R_PC++]); + ADD_CYC(8); + break; + case 0x017: + ADD_CYC + break; + default: + std::cout << "The program has seemingly run into an unrecognized opcode\n"; + break; + } + } +} diff --git a/src/Processor.hpp b/src/Processor.hpp new file mode 100644 index 0000000..b00c02c --- /dev/null +++ b/src/Processor.hpp @@ -0,0 +1,51 @@ +#pragma once + +#include +#include "Memory.hpp" + +namespace DMG01 +{ + class SM83 + { + public: + struct Cycles + { + uint32_t total_cycles; + }; + union Registers + { + uint16_t word; + struct + { + uint8_t higher; + uint8_t lower; + } byte; + }; + Registers af; + Registers hl; + Registers de; + Registers bc; + uint16_t pc; + uint16_t sp; + Cycles cycles; + bool stop_state; + inline void add_cycles(Cycles& cycles, const uint32_t count); + inline void set_flag(uint8_t& f, uint8_t index, bool on=true); + inline bool test_flag(uint8_t& f, const uint8_t index); + void load_bin(const std::string& filename, Memory& memory); + template + inline void ld_imm(T1& reg, const T2& data); + template + inline void ld_reg(T1& reg1, const T2& reg2); + template + inline void inc_reg(T& val); + template + inline void dec_reg(T& val); + inline void rlca(SM83& sm83); + template + inline void add_reg(T1& aug, const T2& add); + inline void rrca(SM83& sm83); + inline void stop(SM83& sm83, Memory& memory); + void process_opcodes(const uint8_t opcode, Memory& memory, SM83& sm83); + }; +} diff --git a/src/SDL_stuff.cpp b/src/SDL_stuff.cpp new file mode 100644 index 0000000..e2323e0 --- /dev/null +++ b/src/SDL_stuff.cpp @@ -0,0 +1,86 @@ +#include +#include +#include + +#include "SDL_stuff.hpp" +#include "Common.hpp" + +namespace DMG01 +{ + Generic_SDL_Window::Generic_SDL_Window(const uint32_t win_x, const uint32_t win_y) + { + this->win_x = win_x; + this->win_y = win_y; + } + Generic_SDL_Window::~Generic_SDL_Window() + { + SDL_DestroyTexture(this->texture); + SDL_DestroyRenderer(this->renderer); + SDL_DestroyWindow(this->window); + TTF_Quit(); + SDL_Quit(); + } + void Generic_SDL_Window::create_window() + { + if(SDL_Init(SDL_INIT_EVERYTHING) < 0) { + throw std::runtime_error("SDL couldn't be intitalized"); + } + if(TTF_Init() < 0) { + throw std::runtime_error("SDL_ttf couldn't be initalized"); + } + this->window = SDL_CreateWindow(WINDOW_NAME_DEBUG, + SDL_WINDOWPOS_CENTERED, + SDL_WINDOWPOS_CENTERED, + this->win_x, + this->win_y, + SDL_WINDOW_OPENGL); + if(!this->window) { + throw std::runtime_error("Failed to create SDL window"); + } + this->renderer = SDL_CreateRenderer(this->window, -1, 0); + if(!this->renderer) { + throw std::runtime_error("Failed to create SDL renderer"); + } + this->font = TTF_OpenFont(FONT_DEFAULT_DEBUG, + FONT_SIZE_DEFAULT_DEBUG); + if(!font) { + throw std::runtime_error("Couldn't load font for SDL window"); + } + } + + void Generic_SDL_Window::update_text(const char* message) + { + if(this->texture) SDL_DestroyTexture(this->texture); + SDL_Surface* tmp_surface = TTF_RenderText_Solid(this->font, message, this->white); + if(!tmp_surface) return; + this->texture = SDL_CreateTextureFromSurface(this->renderer, tmp_surface); + this->rectangle.x = 0; + this->rectangle.y = 0; + this->rectangle.w = tmp_surface->w; + this->rectangle.h = tmp_surface->h; + SDL_FreeSurface(tmp_surface); + } + bool Generic_SDL_Window::check_polling_event() + { + while(SDL_PollEvent(&this->event)) { + switch(event.type) { + case SDL_QUIT: return false; + } + } + return true; + } + void Generic_SDL_Window::render_text() + { + SDL_SetRenderDrawColor(this->renderer, + this->black.r, + this->black.g, + this->black.b, + this->black.a); + SDL_RenderClear(this->renderer); + if(this->texture) { + SDL_RenderCopy(this->renderer, this->texture, NULL, &this->rectangle); + } + SDL_RenderPresent(this->renderer); + } + +} diff --git a/src/SDL_stuff.hpp b/src/SDL_stuff.hpp new file mode 100644 index 0000000..b7ddf92 --- /dev/null +++ b/src/SDL_stuff.hpp @@ -0,0 +1,33 @@ +#pragma once + +#include +#include + +#include "Common.hpp" + +namespace DMG01 +{ + class Generic_SDL_Window + { + public: + SDL_Window* window; + SDL_Renderer* renderer; + SDL_Event event; + TTF_Font* font; + SDL_Surface* surface; + SDL_Texture* texture; + SDL_Rect rectangle; + Generic_SDL_Window(const uint32_t win_x, const uint32_t win_y); + ~Generic_SDL_Window(); + void create_window(); + void update_text(const char* message); + bool check_polling_event(); + void render_text(); + private: + uint32_t win_x; + uint32_t win_y; + const SDL_Color red = {255, 0, 0, 0}; + const SDL_Color black = {0, 0, 0, 255}; + const SDL_Color white = {255, 255, 255, 255}; + }; +} -- cgit v1.3