aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/Common.hpp19
-rw-r--r--src/Debugger.cpp6
-rw-r--r--src/Debugger.hpp16
-rw-r--r--src/Main.cpp54
-rw-r--r--src/Memory.cpp12
-rw-r--r--src/Memory.hpp13
-rw-r--r--src/Processor.cpp243
-rw-r--r--src/Processor.hpp51
-rw-r--r--src/SDL_stuff.cpp86
-rw-r--r--src/SDL_stuff.hpp33
10 files changed, 533 insertions, 0 deletions
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 <SDL2/SDL.h>
+
+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 <SDL2/SDL.h>
+#include <SDL2/SDL_ttf.h>
+
+#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 <SDL2/SDL.h>
+#include <SDL2/SDL_ttf.h>
+#include <iostream>
+#include <cstdlib>
+#include <string>
+
+#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 <memory>
+
+namespace DMG01
+{
+
+ Memory::Memory()
+ {
+ this->space = std::make_unique<unsigned char[]>(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 <memory>
+
+namespace DMG01
+{
+ class Memory
+ {
+ public:
+ std::unique_ptr<unsigned char[]> 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 <memory>
+#include <fstream>
+#include <string>
+#include <stdexcept>
+#include <unordered_map>
+#include <cstdlib>
+#include <bitset>
+#include <iostream>
+
+#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<char*>(memory.space.get()), f_size);
+ }
+
+ template <typename T1, typename T2>
+ inline void SM83::ld_imm(T1& reg, const T2& data)
+ {
+ reg = static_cast<T1>(data);
+ }
+
+ template <typename T1, typename T2>
+ inline void SM83::ld_reg(T1& reg1, const T2& reg2)
+ {
+ reg1 = reg2;
+ }
+
+ template <typename T>
+ inline void SM83::inc_reg(T& val)
+ {
+ val++;
+ }
+
+ template <typename T>
+ 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 <typename T1, typename T2>
+ 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 <cstdint>
+#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 <typename T1, typename T2>
+ inline void ld_imm(T1& reg, const T2& data);
+ template <typename T1, typename T2>
+ inline void ld_reg(T1& reg1, const T2& reg2);
+ template <typename T>
+ inline void inc_reg(T& val);
+ template <typename T>
+ inline void dec_reg(T& val);
+ inline void rlca(SM83& sm83);
+ template <typename T1, typename T2>
+ 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 <SDL2/SDL.h>
+#include <SDL2/SDL_ttf.h>
+#include <stdexcept>
+
+#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 <SDL2/SDL.h>
+#include <SDL2/SDL_ttf.h>
+
+#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};
+ };
+}