Added handler for 0x1000 and added documentation for most functions and classes
							parent
							
								
									632873078b
								
							
						
					
					
						commit
						fce922f6c6
					
				|  | @ -7,21 +7,70 @@ | |||
| 
 | ||||
| class InstructionHandler; | ||||
| 
 | ||||
| /**
 | ||||
|  * The main Chip8 used to emulate/interpret the Chip8 program | ||||
|  */ | ||||
| class Chip8 { | ||||
| private: | ||||
|     /**
 | ||||
|      * The memory of the Chip8 program | ||||
|      */ | ||||
|     unsigned char memory[0xFFF] = {}; | ||||
| 
 | ||||
|     /**
 | ||||
|      * The 16 general purpose 8-bit registers, generally used to store memory addresses | ||||
|      */ | ||||
|     unsigned char v[0x10] = {}; | ||||
| 
 | ||||
|     /**
 | ||||
|      * Stack of 16-bit values, used to store addresses the interpreter should return to when finished with a subroutine | ||||
|      */ | ||||
|     std::stack<unsigned short> stack{}; | ||||
| 
 | ||||
|     /**
 | ||||
|      * The program counter which holds the currently executing address | ||||
|      */ | ||||
|     unsigned short pc; | ||||
| 
 | ||||
|     /**
 | ||||
|      * The Display object holding information and functions used for the display | ||||
|      */ | ||||
|     Display display; | ||||
| 
 | ||||
|     /**
 | ||||
|      * The instruction handler used to handle instructions | ||||
|      */ | ||||
|     InstructionHandler* instructionHandler; | ||||
| public: | ||||
|     Chip8(); | ||||
|     ~Chip8(); | ||||
|     void loadRom(const std::string&); | ||||
| 
 | ||||
|     /**
 | ||||
|      * Loads a rom in the memory | ||||
|      * @param rom The rom to be loaded in the memory | ||||
|      */ | ||||
|     void loadRom(const std::string& rom); | ||||
| 
 | ||||
|     /**
 | ||||
|      * Gets the Display object | ||||
|      * @return | ||||
|      */ | ||||
|     [[nodiscard]] Display getDisplay() const; | ||||
| 
 | ||||
|     /**
 | ||||
|      * Cycle to handle the next instruction | ||||
|      */ | ||||
|     void emulateCycle(); | ||||
| 
 | ||||
|     /**
 | ||||
|      * Pops a 16-bit value from the stack | ||||
|      * @return a unsigned 16-bit integer | ||||
|      */ | ||||
|     [[nodiscard]] unsigned short popFromStack(); | ||||
|     void setProgramCounter(unsigned short); | ||||
| 
 | ||||
|     /**
 | ||||
|      * Sets the program counter (PC) to the given value | ||||
|      */ | ||||
|     void setProgramCounter(unsigned short value); | ||||
| }; | ||||
| #endif //CHIP8_CHIP8_H
 | ||||
|  |  | |||
|  | @ -3,12 +3,32 @@ | |||
| 
 | ||||
| #include <chrono> | ||||
| 
 | ||||
| /**
 | ||||
|  * Class used to work with dates and times | ||||
|  */ | ||||
| class Date { | ||||
| public: | ||||
|     /**
 | ||||
|      * Creates a new Date object with the given time_point | ||||
|      * @param tp the time_point object | ||||
|      */ | ||||
|     explicit Date(std::chrono::system_clock::time_point tp); | ||||
| 
 | ||||
|     /**
 | ||||
|      * Gets a Date object with the current time as time_point | ||||
|      * @return The Date object | ||||
|      */ | ||||
|     static Date now(); | ||||
|     std::string to_string() const; | ||||
| 
 | ||||
|     /**
 | ||||
|      * Converts the time_point to a string with format "Y-%m-%d %H:%M:%S" | ||||
|      * @return the formatted datetime string | ||||
|      */ | ||||
|     [[nodiscard]] std::string to_string() const; | ||||
| private: | ||||
|     /**
 | ||||
|      * The time_point to use | ||||
|      */ | ||||
|     std::chrono::system_clock::time_point tp; | ||||
| }; | ||||
| 
 | ||||
|  |  | |||
|  | @ -6,13 +6,36 @@ | |||
| 
 | ||||
| #include <SDL2/SDL.h> | ||||
| 
 | ||||
| /**
 | ||||
|  * The display class used to handle drawing pixels to the SDL renderer | ||||
|  */ | ||||
| class Display { | ||||
| private: | ||||
|     /**
 | ||||
|      * Flag whether the display should be redrawn | ||||
|      */ | ||||
|     bool flagRedraw = false; | ||||
| 
 | ||||
|     /**
 | ||||
|      * The 64x32-pixel monochrome display | ||||
|      */ | ||||
|     bool display[64 * 32] = {}; | ||||
| public: | ||||
|     void draw(SDL_Renderer*); | ||||
|     /**
 | ||||
|      * Draws the current display to the SDL renderer | ||||
|      * @param renderer the SDL renderer to draw to | ||||
|      */ | ||||
|     void draw(SDL_Renderer* renderer); | ||||
| 
 | ||||
|     /**
 | ||||
|      * Gets whether the display should be redrawn | ||||
|      * @return true if the display should be redrawn, false otherwise | ||||
|      */ | ||||
|     [[nodiscard]] bool needsRedraw() const; | ||||
| 
 | ||||
|     /**
 | ||||
|      * Clears the display | ||||
|      */ | ||||
|     void clear(); | ||||
| }; | ||||
| #endif //CHIP8_DISPLAY_H
 | ||||
|  |  | |||
|  | @ -1,14 +1,45 @@ | |||
| #ifndef CHIP8_INSTRUCTION_H | ||||
| #define CHIP8_INSTRUCTION_H | ||||
| /**
 | ||||
|  * The instruction class stores the opcode and allows easier access to the Chip8 values | ||||
|  */ | ||||
| class Instruction { | ||||
| private: | ||||
|     /**
 | ||||
|      * The basic opcode | ||||
|      */ | ||||
|     const unsigned short opcode; | ||||
| public: | ||||
|     explicit Instruction(unsigned short); | ||||
|     explicit Instruction(unsigned short opcode); | ||||
| 
 | ||||
|     /**
 | ||||
|      * Gets the NNN Chip8 value | ||||
|      * @return | ||||
|      */ | ||||
|     [[nodiscard]] unsigned short nnn() const; | ||||
|     [[nodiscard]] unsigned short n() const; | ||||
| 
 | ||||
|     /**
 | ||||
|      * Gets the N Chip8 value | ||||
|      * @return | ||||
|      */ | ||||
|     [[nodiscard]] unsigned char n() const; | ||||
| 
 | ||||
|     /**
 | ||||
|      * Gets the KK/Byte/NN Chip8 value | ||||
|      * @return | ||||
|      */ | ||||
|     [[nodiscard]] unsigned short kk() const; | ||||
| 
 | ||||
|     /**
 | ||||
|      * Gets the X Chip8 value | ||||
|      * @return | ||||
|      */ | ||||
|     [[nodiscard]] unsigned char x() const; | ||||
| 
 | ||||
|     /**
 | ||||
|      * Gets the Y Chip8 value | ||||
|      * @return | ||||
|      */ | ||||
|     [[nodiscard]] unsigned char y() const; | ||||
| }; | ||||
| #endif //CHIP8_INSTRUCTION_H
 | ||||
|  |  | |||
|  | @ -5,13 +5,39 @@ | |||
| #include "chip8.h" | ||||
| #include "instruction.h" | ||||
| 
 | ||||
| /**
 | ||||
|  * The InstructionHandler class holds the Chip8 instruction set and all handlers | ||||
|  */ | ||||
| class InstructionHandler { | ||||
| private: | ||||
|     /**
 | ||||
|      * The typedef for the handler functions | ||||
|      */ | ||||
|     typedef void (*Handler)(Chip8&, Instruction); | ||||
| 
 | ||||
|     /**
 | ||||
|      * Map which stores all instructions with their handler | ||||
|      */ | ||||
|     std::map<unsigned short, Handler> handlers = {}; | ||||
| 
 | ||||
|     /**
 | ||||
|      * Handles Chip8 instruction 0x0000, as well as 0x00E0 and 0x00EE | ||||
|      */ | ||||
|     static void handle0000(Chip8&, Instruction); | ||||
| 
 | ||||
|     /**
 | ||||
|      * Handles Chip8 instruction 0x1000 | ||||
|      */ | ||||
|     static void handle1000(Chip8&, Instruction); | ||||
| public: | ||||
|     InstructionHandler(); | ||||
|     void handleInstruction(unsigned short, Chip8&, Instruction); | ||||
| 
 | ||||
|     /**
 | ||||
|      * Attempts to handle a chip8 instruction | ||||
|      * @param instructionNumber the 16-bit value to determine the instruction | ||||
|      * @param chip8 the Chip8 instance | ||||
|      * @param instruction the Instruction instance | ||||
|      */ | ||||
|     void handleInstruction(unsigned short instructionNumber, Chip8& chip8, Instruction instruction); | ||||
| }; | ||||
| #endif //CHIP8_INSTRUCTION_HANDLER_H
 | ||||
|  |  | |||
|  | @ -5,14 +5,26 @@ | |||
| #include <string> | ||||
| #include "date.h" | ||||
| 
 | ||||
| /**
 | ||||
|  * The LogType of a log | ||||
|  */ | ||||
| enum LogType { | ||||
|     INFO, | ||||
|     DEBUG, | ||||
|     WARN, | ||||
|     ERROR | ||||
| }; | ||||
| 
 | ||||
| /**
 | ||||
|  * The logger class allows logging to either the output stream or the error stream | ||||
|  */ | ||||
| class Logger { | ||||
| private: | ||||
|     /**
 | ||||
|      * Converts the LogType enum to a string to display it in the log | ||||
|      * @param logType the LogType enum to convert | ||||
|      * @return the LogType as a string | ||||
|      */ | ||||
|     static const char* logTypeToString(LogType logType) { | ||||
|         switch (logType) { | ||||
|             case LogType::INFO: | ||||
|  | @ -28,6 +40,13 @@ private: | |||
|         return ""; | ||||
|     } | ||||
| public: | ||||
|     /**
 | ||||
|      * Writes log to either the 'cout' or 'cerr' streams | ||||
|      * | ||||
|      * @tparam Args Variadic template representing any number and type of arguments that | ||||
|      * @param logType The type of the log. If logType is LogType::ERROR, output to 'cerr' instead of 'cout' | ||||
|      * @param args The contents of the log, this can be any number of arguments | ||||
|      */ | ||||
|     template<typename... Args> | ||||
|     static void log(LogType logType, Args&&... args) { | ||||
|         std::ostream& stream = (logType == LogType::ERROR) ? std::cerr : std::cout; | ||||
|  | @ -36,21 +55,45 @@ public: | |||
|         stream << std::endl; | ||||
|     } | ||||
| 
 | ||||
|     /**
 | ||||
|      * Writes an info log to the cout stream | ||||
|      * | ||||
|      * @tparam Args @see self::log | ||||
|      * @param args @see self::log | ||||
|      */ | ||||
|     template<typename... Args> | ||||
|     static void info(Args&&... args) { | ||||
|         log(LogType::INFO, std::forward<Args>(args)...); | ||||
|     } | ||||
| 
 | ||||
|     /**
 | ||||
|     * Writes an debug log to the cout stream | ||||
|      * | ||||
|      * @tparam Args @see self::log | ||||
|      * @param args @see self::log | ||||
|      */ | ||||
|     template<typename... Args> | ||||
|     static void debug(Args&&... args) { | ||||
|         log(LogType::DEBUG, std::forward<Args>(args)...); | ||||
|     } | ||||
| 
 | ||||
|     /**
 | ||||
|      * Writes an warn log to the cout stream | ||||
|      * | ||||
|      * @tparam Args @see self::log | ||||
|      * @param args @see self::log | ||||
|      */ | ||||
|     template<typename... Args> | ||||
|     static void warn(Args&&... args) { | ||||
|         log(LogType::WARN, std::forward<Args>(args)...); | ||||
|     } | ||||
| 
 | ||||
|     /**
 | ||||
|      * Writes an error log to the cerr stream | ||||
|      * | ||||
|      * @tparam Args @see self::log | ||||
|      * @param args @see self::log | ||||
|      */ | ||||
|     template<typename... Args> | ||||
|     static void error(Args&&... args) { | ||||
|         log(LogType::ERROR, std::forward<Args>(args)...); | ||||
|  |  | |||
|  | @ -13,6 +13,13 @@ Chip8::~Chip8() { | |||
|     delete instructionHandler; | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  * Reads all bytes from 0x200 onward and put them in the memory. | ||||
|  * If the file does not exist, the program is exited. | ||||
|  * | ||||
|  * @param file The Chip8 rom file | ||||
|  * @todo Check if the Chip8 rom file is a valid rom file | ||||
|  */ | ||||
| void Chip8::loadRom(const std::string& file) { | ||||
|     FILE* game = fopen(("roms/" + file).c_str(), "rb"); | ||||
|     if (game == nullptr) { | ||||
|  | @ -28,12 +35,19 @@ Display Chip8::getDisplay() const { | |||
|     return this->display; | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  * This emulates one cycle by getting the opcode and handling the instruction corresponding that opcode | ||||
|  */ | ||||
| void Chip8::emulateCycle() { | ||||
|     unsigned short opcode = (this->memory[this->pc] << 8 | this->memory[this->pc + 1]); | ||||
|     this->pc += 2; | ||||
|     this->instructionHandler->handleInstruction(opcode & 0xF000, *this, Instruction(opcode)); | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  * Gets the value from the top of the stack, pop the top of the stack and returns the value | ||||
|  * @return A 16-bit value from the stack | ||||
|  */ | ||||
| unsigned short Chip8::popFromStack() { | ||||
|     unsigned short value = this->stack.top(); | ||||
|     this->stack.pop(); | ||||
|  |  | |||
|  | @ -1,5 +1,12 @@ | |||
| #include "../include/display.h" | ||||
| 
 | ||||
| /**
 | ||||
|  * Draws the display to the SDL renderer | ||||
|  * If the bool in the display is true, a white pixel will be written on that position | ||||
|  * The redraw flag is set to false afterwards | ||||
|  * | ||||
|  * @param renderer The SDL renderer | ||||
|  */ | ||||
| void Display::draw(SDL_Renderer * renderer) { | ||||
|     SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255); | ||||
|     SDL_RenderClear(renderer); | ||||
|  | @ -7,7 +14,7 @@ void Display::draw(SDL_Renderer * renderer) { | |||
| 
 | ||||
|     for (int y = 0; y < 32; y++) { | ||||
|         for (int x = 0; x < 64; x++) { | ||||
|             if (display[(y * 64) + x] != STATE_OFF) { | ||||
|             if (display[(y * 64) + x] == STATE_ON) { | ||||
|                 SDL_Rect r; | ||||
|                 r.x = x + 10; | ||||
|                 r.y = y + 10; | ||||
|  | @ -26,7 +33,10 @@ bool Display::needsRedraw() const { | |||
|     return this->flagRedraw; | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  * Clears the display by setting all pixels to false | ||||
|  */ | ||||
| void Display::clear() { | ||||
|     std::fill(display, display + 64 * 32, false); | ||||
|     std::fill(display, display + 64 * 32, STATE_OFF); | ||||
|     this->flagRedraw = true; | ||||
| } | ||||
|  |  | |||
|  | @ -4,22 +4,59 @@ Instruction::Instruction(unsigned short opcode) : opcode(opcode) { | |||
| 
 | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  * Gets the NNN Chip8 value, also known as the addr. | ||||
|  * This is a 12-bit value, the lowest 12 bits of the instruction. | ||||
|  * @return | ||||
|  */ | ||||
| unsigned short Instruction::nnn() const { | ||||
|     return this->opcode & 0x0FFF; | ||||
| } | ||||
| 
 | ||||
| unsigned short Instruction::n() const { | ||||
| /**
 | ||||
|  * Gets the N Chip8 value, also known as the nibble. | ||||
|  * This is a 4-bit value, the lowest 4 bits of the instruction. | ||||
|  * @return | ||||
|  */ | ||||
| unsigned char Instruction::n() const { | ||||
|     return this->opcode & 0x000F; | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  * Gets the KK Chip8 value, also known as byte (or NN by Wikipedia). | ||||
|  * The naming is inconsistent, as Wikipedia has it as NN | ||||
|  * and http://devernay.free.fr/hacks/chip8/C8TECH10.HTM has it as KK (with byte as alternative name).
 | ||||
|  * This is a 8-bit value, the lowest 8 bits of the instruction. | ||||
|  * @return | ||||
|  */ | ||||
| unsigned short Instruction::kk() const { | ||||
|     return this->opcode & 0x00FF; | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  * Gets the X Chip8 value. | ||||
|  * This is a 4-bit value, the lower 4 bits of the high byte of the instruction. | ||||
|  * The high byte is the second nibble. | ||||
|  * Example: | ||||
|  * | ||||
|  * 0101 1010 1100 0011 | ||||
|  * Here, the 1010 is the X value. | ||||
|  * @return | ||||
|  */ | ||||
| unsigned char Instruction::x() const { | ||||
|     return (this->opcode & 0x0F00) >> 8; | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  * Gets the Y Chip8 value. | ||||
|  * This is a 4-bit value, the upper 4 bits of the low byte of the instruction. | ||||
|  * The low byte is the second nibble. | ||||
|  * Example: | ||||
|  * | ||||
|  * 0101 1010 1100 0011 | ||||
|  * Here, the 1100 is the Y value. | ||||
|  * @return | ||||
|  */ | ||||
| unsigned char Instruction::y() const { | ||||
|     return (this->opcode & 0x00F0) >> 4; | ||||
| } | ||||
|  | @ -1,11 +1,19 @@ | |||
| #include "../include/instruction_handler.h" | ||||
| #include "../include/logger.h" | ||||
| #include "../include/display.h" | ||||
| 
 | ||||
| InstructionHandler::InstructionHandler() { | ||||
|     this->handlers[0x0000] = handle0000; | ||||
|     this->handlers[0x1000] = handle1000; | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  * Attempts to handle the Chip8 instruction by the given instruction number. | ||||
|  * If the handler doesn't exist for the instruction, an error is logged, but the program resumes. | ||||
|  * | ||||
|  * @param instructionNumber the 16-bit value to determine the instruction | ||||
|  * @param chip8 the Chip8 instance | ||||
|  * @param instruction the Instruction instance | ||||
|  */ | ||||
| void InstructionHandler::handleInstruction(unsigned short instructionNumber, Chip8& chip8, Instruction instruction) { | ||||
|     if (!this->handlers.contains(instructionNumber)) { | ||||
|         Logger::error("Unregistered instruction", instructionNumber); | ||||
|  | @ -15,6 +23,17 @@ void InstructionHandler::handleInstruction(unsigned short instructionNumber, Chi | |||
|     this->handlers[instructionNumber](chip8, instruction); | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  * Handles the 0x0000 Chip-8 instruction. | ||||
|  * This handles 2 other instructions: | ||||
|  * - 0x00E0 which is used to clear the screen. | ||||
|  * - 0x00EE which is used to return from a subroutine. | ||||
|  * | ||||
|  * In older interpreters, 0x0NNN was also used to jump to a machine code at NNN, but this shouldn't be implemented. | ||||
|  * | ||||
|  * @param chip8 The Chip8 instance | ||||
|  * @param instruction The Instruction instance | ||||
|  */ | ||||
| void InstructionHandler::handle0000(Chip8& chip8, Instruction instruction) { | ||||
|     switch (instruction.kk()) { | ||||
|         case 0x00E0: | ||||
|  | @ -26,3 +45,14 @@ void InstructionHandler::handle0000(Chip8& chip8, Instruction instruction) { | |||
|     } | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  * Handles the 0x1NNN Chip8 instruction which is used to jump to location NNN. | ||||
|  * This will set the program counter to the NNN value. | ||||
|  * | ||||
|  * @param chip8 The Chip8 instance | ||||
|  * @param instruction The Instruction instance | ||||
|  */ | ||||
| void InstructionHandler::handle1000(Chip8& chip8, Instruction instruction) { | ||||
|     chip8.setProgramCounter(instruction.nnn()); | ||||
| } | ||||
| 
 | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue