From 588ce89b19a53c76daa2d2a65ec713606c8b8444 Mon Sep 17 00:00:00 2001 From: Tiger Date: Sat, 9 Sep 2023 19:57:57 +0200 Subject: [PATCH] Initial commit --- .gitignore | 3 ++ .idea/.gitignore | 8 +++ CMakeLists.txt | 21 ++++++++ include/chip8.h | 23 ++++++++ include/date.h | 15 ++++++ include/display.h | 19 +++++++ include/instruction.h | 14 +++++ include/instruction_handler.h | 17 ++++++ include/logger.h | 60 +++++++++++++++++++++ main.cpp | 49 +++++++++++++++++ roms/Chip8 emulator Logo [Garstyciuks].ch8 | Bin 0 -> 288 bytes src/chip8.cpp | 36 +++++++++++++ src/date.cpp | 18 +++++++ src/display.cpp | 32 +++++++++++ src/instruction.cpp | 25 +++++++++ src/instruction_handler.cpp | 28 ++++++++++ 16 files changed, 368 insertions(+) create mode 100644 .gitignore create mode 100644 .idea/.gitignore create mode 100644 CMakeLists.txt create mode 100644 include/chip8.h create mode 100644 include/date.h create mode 100644 include/display.h create mode 100644 include/instruction.h create mode 100644 include/instruction_handler.h create mode 100644 include/logger.h create mode 100644 main.cpp create mode 100644 roms/Chip8 emulator Logo [Garstyciuks].ch8 create mode 100644 src/chip8.cpp create mode 100644 src/date.cpp create mode 100644 src/display.cpp create mode 100644 src/instruction.cpp create mode 100644 src/instruction_handler.cpp diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..fd10bc6 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +.idea/ +.idea/.gitignore +cmake-build-debug \ No newline at end of file diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..13566b8 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..eea136c --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,21 @@ +cmake_minimum_required(VERSION 3.26) +project(chip8) + +set(CMAKE_CXX_STANDARD 23) + +find_package(SDL2 REQUIRED) + +add_executable(chip8 main.cpp + include/chip8.h + src/chip8.cpp + include/logger.h + include/date.h + src/date.cpp + include/display.h + src/display.cpp + include/instruction.h + src/instruction.cpp + include/instruction_handler.h + src/instruction_handler.cpp) + +target_link_libraries(chip8 ${SDL2_LIBRARIES}) \ No newline at end of file diff --git a/include/chip8.h b/include/chip8.h new file mode 100644 index 0000000..4d7a3fa --- /dev/null +++ b/include/chip8.h @@ -0,0 +1,23 @@ +#ifndef CHIP8_CHIP8_H +#define CHIP8_CHIP8_H + +#include + +class InstructionHandler; +class Display; + +class Chip8 { +private: + unsigned char memory[0xFFF] = {}; + unsigned char v[0x10] = {}; + unsigned short pc; + Display* display; + InstructionHandler* instructionHandler; +public: + Chip8(); + ~Chip8(); + void loadRom(const std::string& file); + [[nodiscard]] Display* getDisplay() const; + void emulateCycle(); +}; +#endif //CHIP8_CHIP8_H diff --git a/include/date.h b/include/date.h new file mode 100644 index 0000000..088a1d3 --- /dev/null +++ b/include/date.h @@ -0,0 +1,15 @@ +#ifndef CHIP8_DATE_H +#define CHIP8_DATE_H + +#include + +class Date { +public: + explicit Date(std::chrono::system_clock::time_point tp); + static Date now(); + std::string to_string() const; +private: + std::chrono::system_clock::time_point tp; +}; + +#endif //CHIP8_DATE_H diff --git a/include/display.h b/include/display.h new file mode 100644 index 0000000..50213d2 --- /dev/null +++ b/include/display.h @@ -0,0 +1,19 @@ +#ifndef CHIP8_DISPLAY_H +#define CHIP8_DISPLAY_H + +#define STATE_ON 1 +#define STATE_OFF 0 + +#include +#include "chip8.h" + +class Display { +private: + bool flagRedraw = false; + bool display[64 * 32] = {}; +public: + void draw(SDL_Renderer*, const Chip8&); + [[nodiscard]] bool needsRedraw() const; + void clear(); +}; +#endif //CHIP8_DISPLAY_H diff --git a/include/instruction.h b/include/instruction.h new file mode 100644 index 0000000..905a687 --- /dev/null +++ b/include/instruction.h @@ -0,0 +1,14 @@ +#ifndef CHIP8_INSTRUCTION_H +#define CHIP8_INSTRUCTION_H +class Instruction { +private: + const unsigned short opcode; +public: + explicit Instruction(unsigned short); + [[nodiscard]] unsigned short nnn() const; + [[nodiscard]] unsigned short n() const; + [[nodiscard]] unsigned short kk() const; + [[nodiscard]] unsigned char x() const; + [[nodiscard]] unsigned char y() const; +}; +#endif //CHIP8_INSTRUCTION_H diff --git a/include/instruction_handler.h b/include/instruction_handler.h new file mode 100644 index 0000000..3956f8a --- /dev/null +++ b/include/instruction_handler.h @@ -0,0 +1,17 @@ +#ifndef CHIP8_INSTRUCTION_HANDLER_H +#define CHIP8_INSTRUCTION_HANDLER_H + +#include +#include "chip8.h" +#include "instruction.h" + +class InstructionHandler { +private: + typedef void (*Handler)(const Chip8&, Instruction); + std::map handlers = {}; + static void handle0000(const Chip8&, Instruction); +public: + InstructionHandler(); + void handleInstruction(unsigned short, const Chip8&, Instruction); +}; +#endif //CHIP8_INSTRUCTION_HANDLER_H diff --git a/include/logger.h b/include/logger.h new file mode 100644 index 0000000..627abce --- /dev/null +++ b/include/logger.h @@ -0,0 +1,60 @@ +#ifndef CHIP8_LOGGER_H +#define CHIP8_LOGGER_H + +#include +#include +#include "date.h" + +enum LogType { + INFO, + DEBUG, + WARN, + ERROR +}; +class Logger { +private: + static const char* logTypeToString(LogType logType) { + switch (logType) { + case LogType::INFO: + return "INFO"; + case LogType::DEBUG: + return "DEBUG"; + case LogType::WARN: + return "WARN"; + case LogType::ERROR: + return "ERROR"; + } + + return ""; + } +public: + template + static void log(LogType logType, Args&&... args) { + std::ostream& stream = (logType == LogType::ERROR) ? std::cerr : std::cout; + stream << "[" << logTypeToString(logType) << "] [" << Date::now().to_string() << "]"; + ((stream << " " << args), ...); + stream << std::endl; + } + + template + static void info(Args&&... args) { + log(LogType::INFO, std::forward(args)...); + } + + template + static void debug(Args&&... args) { + log(LogType::DEBUG, std::forward(args)...); + } + + template + static void warn(Args&&... args) { + log(LogType::WARN, std::forward(args)...); + } + + template + static void error(Args&&... args) { + log(LogType::ERROR, std::forward(args)...); + } +}; + +#endif //CHIP8_LOGGER_H diff --git a/main.cpp b/main.cpp new file mode 100644 index 0000000..4043529 --- /dev/null +++ b/main.cpp @@ -0,0 +1,49 @@ +#include +#include +#include "include/logger.h" + +int main() { + if (SDL_Init(SDL_INIT_EVERYTHING) < 0) { + Logger::error("Cannot initialize SDL:", SDL_GetError()); + return EXIT_FAILURE; + } + + SDL_Window* window = SDL_CreateWindow("Chip8 Emulator", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, 1024, 512, SDL_WINDOW_SHOWN); + + if (window == nullptr) { + Logger::error("Cannot create SDL window:", SDL_GetError()); + return EXIT_FAILURE; + } + + SDL_Renderer* renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED); + + if (renderer == nullptr) { + Logger::error("Cannot create SDL renderer:", SDL_GetError()); + return EXIT_FAILURE; + } + + bool run = true; + SDL_Event e; + + while (run) { + while (SDL_PollEvent(&e) != 0) { + if (e.type == SDL_QUIT) { + run = false; + } + else if (e.type == SDL_KEYDOWN) { + if (e.key.keysym.sym == SDLK_ESCAPE) { + run = false; + } + } + } + + SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255); + SDL_RenderClear(renderer); + SDL_RenderPresent(renderer); + } + + SDL_DestroyRenderer(renderer); + SDL_DestroyWindow(window); + SDL_Quit(); + return 0; +} diff --git a/roms/Chip8 emulator Logo [Garstyciuks].ch8 b/roms/Chip8 emulator Logo [Garstyciuks].ch8 new file mode 100644 index 0000000000000000000000000000000000000000..c0e5923fb6039afd703d796fb86225e54931e909 GIT binary patch literal 288 zcmZR0kid}0ki@Y_!NEaE%27c`;DW>_xdIL$t^|fc4u*FO=%C&qJ|H|Kgy9d%7m+JK z+Fn6lLt9IW;UCj4u2(>s00;qt5(W@e0#vI72G&p-(>iqHO2GPo_A)?N)(``<-a%<} n_2~SNknn(bhkBs>S46(B`~ljlt)Z`A52Rml{bKruuABh?Dy1yW literal 0 HcmV?d00001 diff --git a/src/chip8.cpp b/src/chip8.cpp new file mode 100644 index 0000000..7b3123d --- /dev/null +++ b/src/chip8.cpp @@ -0,0 +1,36 @@ +#include +#include + +#include "../include/chip8.h" +#include "../include/instruction.h" +#include "../include/instruction_handler.h" +#include "../include/display.h" + +Chip8::Chip8() : instructionHandler(), display() { + this->pc = 0x200; +} + +Chip8::~Chip8() { + delete display; + delete instructionHandler; +} + +void Chip8::loadRom(const std::string& file) { + FILE* game = fopen(("roms/" + file).c_str(), "rb"); + if (game == nullptr) { + + exit(EXIT_FAILURE); + } + + fread(this->memory + 0x200, sizeof(char), 0xFFF - 0x200, game); + fclose(game); +} + +Display* Chip8::getDisplay() const { + return this->display; +} + +void Chip8::emulateCycle() { + unsigned short opcode = (this->memory[this->pc] << 8 | this->memory[this->pc + 1]); + this->instructionHandler->handleInstruction(opcode & 0xF000, *this, Instruction(opcode)); +} diff --git a/src/date.cpp b/src/date.cpp new file mode 100644 index 0000000..4025a50 --- /dev/null +++ b/src/date.cpp @@ -0,0 +1,18 @@ +#include "../include/date.h" + +Date::Date(std::chrono::system_clock::time_point tp) { + this->tp = tp; +} + +Date Date::now() { + return Date(std::chrono::system_clock::now()); +} + +std::string Date::to_string() const { + auto now_c = std::chrono::system_clock::to_time_t(tp); + std::tm* now_tm = std::localtime(&now_c); + + std::ostringstream oss; + oss << std::put_time(now_tm, "%Y-%m-%d %H:%M:%S"); + return oss.str(); +} diff --git a/src/display.cpp b/src/display.cpp new file mode 100644 index 0000000..e44da3f --- /dev/null +++ b/src/display.cpp @@ -0,0 +1,32 @@ +#include "../include/display.h" + +void Display::draw(SDL_Renderer * renderer, const Chip8& chip8) { + SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255); + SDL_RenderClear(renderer); + SDL_SetRenderDrawColor(renderer, 255, 255, 255, 255); + + for (int y = 0; y < 32; y++) { + for (int x = 0; x < 64; x++) { + if (display[(y * 64) + x] != STATE_OFF) { + SDL_Rect r; + r.x = x + 10; + r.y = y + 10; + r.w = 10; + r.h = 10; + SDL_RenderFillRect(renderer, &r); + } + } + } + + SDL_RenderPresent(renderer); + this->flagRedraw = false; +} + +bool Display::needsRedraw() const { + return this->flagRedraw; +} + +void Display::clear() { + std::fill(display, display + 64 * 32, false); + this->flagRedraw = true; +} diff --git a/src/instruction.cpp b/src/instruction.cpp new file mode 100644 index 0000000..f9c2503 --- /dev/null +++ b/src/instruction.cpp @@ -0,0 +1,25 @@ +#include "../include/instruction.h" + +Instruction::Instruction(unsigned short opcode) : opcode(opcode) { + +} + +unsigned short Instruction::nnn() const { + return this->opcode & 0x0FFF; +} + +unsigned short Instruction::n() const { + return this->opcode & 0x000F; +} + +unsigned short Instruction::kk() const { + return this->opcode & 0x00FF; +} + +unsigned char Instruction::x() const { + return (this->opcode & 0x0F00) >> 8; +} + +unsigned char Instruction::y() const { + return (this->opcode & 0x00F0) >> 4; +} \ No newline at end of file diff --git a/src/instruction_handler.cpp b/src/instruction_handler.cpp new file mode 100644 index 0000000..2cf21f0 --- /dev/null +++ b/src/instruction_handler.cpp @@ -0,0 +1,28 @@ +#include "../include/instruction_handler.h" +#include "../include/logger.h" +#include "../include/display.h" + +InstructionHandler::InstructionHandler() { + this->handlers[0x0000] = handle0000; +} + +void InstructionHandler::handleInstruction(unsigned short instructionNumber, const Chip8& chip8, Instruction instruction) { + if (!this->handlers.contains(instructionNumber)) { + Logger::error("Unregistered instruction", instructionNumber); + return; + } + + this->handlers[instructionNumber](chip8, instruction); +} + +void InstructionHandler::handle0000(const Chip8& chip8, Instruction instruction) { + switch (instruction.kk()) { + case 0x00E0: + chip8.getDisplay()->clear(); + break; + case 0x00EE: + + break; + } +} +