├── .gitignore ├── CMakeLists.txt ├── LICENSE ├── README.md ├── chip-8.c ├── chip-8.h ├── disassembler.c ├── emulator.c ├── rom_picker.c ├── rom_picker.h ├── screenshots ├── chip8.png ├── screen1.png ├── screen2.png └── screen3.png ├── shell.html └── tests.c /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | build 3 | build_web 4 | roms 5 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.0) 2 | project(chip-8) 3 | enable_testing() 4 | 5 | set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsanitize=address") 6 | 7 | add_compile_options(-Wall -Wextra -Wpedantic -Wno-gnu-binary-literal) 8 | 9 | add_executable(disassembler disassembler.c chip-8.c) 10 | add_executable(tests tests.c chip-8.c) 11 | add_executable(emulator emulator.c chip-8.c rom_picker.c) 12 | 13 | add_test(NAME tests COMMAND tests) 14 | 15 | target_link_libraries(emulator ${RAYLIB_LIBRARY_PATH} m) 16 | target_include_directories(emulator PUBLIC "${RAYLIB_INCLUDE_PATH}") 17 | 18 | if(WIN32) 19 | target_link_libraries(raylib_client wsock32 ws2_32 opengl32 gdi32 winmm) 20 | target_link_libraries(raylib_server wsock32 ws2_32) 21 | endif() 22 | 23 | if (APPLE) 24 | target_link_libraries(emulator "-framework OpenGL -framework Cocoa -framework IOKit -framework CoreAudio -framework CoreVideo") 25 | endif (APPLE) 26 | 27 | if (EMSCRIPTEN) 28 | set_target_properties(emulator PROPERTIES LINK_FLAGS "-s USE_GLFW=3 \ 29 | --shell-file ${CMAKE_CURRENT_SOURCE_DIR}/shell.html \ 30 | -s ASYNCIFY \ 31 | -s ALLOW_MEMORY_GROWTH=1 \ 32 | --preload-file ${ROMS_DIR}@roms") 33 | 34 | set_target_properties(emulator PROPERTIES SUFFIX ".html") 35 | add_compile_definitions(ROMS_DIR_PATH="roms") 36 | else () 37 | add_compile_definitions(ROMS_DIR_PATH="${ROMS_DIR}") 38 | endif (EMSCRIPTEN) 39 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (C) 2023 BIAGINI Nathan 2 | 3 | This software is provided 'as-is', without any express or implied 4 | warranty. In no event will the authors be held liable for any damages 5 | arising from the use of this software. 6 | 7 | Permission is granted to anyone to use this software for any purpose, 8 | including commercial applications, and to alter it and redistribute it 9 | freely, subject to the following restrictions: 10 | 11 | 1. The origin of this software must not be misrepresented; you must not 12 | claim that you wrote the original software. If you use this software 13 | in a product, an acknowledgment in the product documentation would be 14 | appreciated but is not required. 15 | 2. Altered source versions must be plainly marked as such, and must not be 16 | misrepresented as being the original software. 17 | 3. This notice may not be removed or altered from any source distribution. 18 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Chip-8 emulator 2 | 3 | A Chip-8 emulator written in C with raylib. 4 | 5 | [Test it here](https://nath-biag.com/projects/emulator.html) (see the Controls section below) 6 | 7 | ![chip-8](screenshots/chip8.png) 8 | ![screen1](screenshots/screen1.png) 9 | ![screen2](screenshots/screen2.png) 10 | ![screen3](screenshots/screen3.png) 11 | 12 | ## Controls 13 | 14 | Chip-8 uses a 16 keys keyboard mapped as such: 15 | 16 | ``` 17 | 1 2 3 4 18 | 19 | A Z E R 20 | 21 | Q S D F 22 | 23 | W X C V 24 | ``` 25 | 26 | Actual controls depend on the ROM itself. 27 | 28 | ## Building 29 | 30 | ``` 31 | cmake -DRAYLIB_LIBRARY_PATH= -DRAYLIB_INCLUDE_PATH= 32 | make 33 | ctest # run unit tests 34 | ``` 35 | 36 | If you want to use the built in ROM picker, you need to provide a path to a directory containing your Chip-8 roms to the cmake command: 37 | 38 | `-DROMS_DIR=` 39 | 40 | The emulator can be compiled with emscripten to run in a web browser. 41 | 42 | ## Running 43 | 44 | `./emulator [ROM_PATH]` 45 | 46 | If `ROM_PATH` is provided, the emulator will run the specified ROM, otherwise it will let you pick a ROM from the provided directory (see Building section). 47 | 48 | ## Test ROMS and resources 49 | 50 | - [C8TECH10](http://devernay.free.fr/hacks/chip8/C8TECH10.HTM) 51 | - [chip8-test-suite](https://github.com/Timendus/chip8-test-suite) 52 | - [chip8-test-rom](https://github.com/corax89/chip8-test-rom) 53 | -------------------------------------------------------------------------------- /chip-8.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include "chip-8.h" 8 | 9 | #define ADDR(instr) (instr & 0xFFF) 10 | #define NIBBLE(instr) (instr & 0xF) 11 | #define HIGH_BYTE(instr) (instr >> 8) 12 | #define LOW_BYTE(instr) (instr & 0xFF) 13 | #define KEY_MASK(k) (0x1 << (0xF - k)) 14 | 15 | // --- op handlers --- 16 | static uint16_t JpAddrHandler(Chip8 *chip8, uint16_t instruction); 17 | static uint16_t CallAddrHandler(Chip8 *chip8, uint16_t instruction); 18 | static uint16_t RetHandler(Chip8 *chip8, uint16_t instruction); 19 | static uint16_t SeVxByteHandler(Chip8 *chip8, uint16_t instruction); 20 | static uint16_t SneVxByteHandler(Chip8 *chip8, uint16_t instruction); 21 | static uint16_t SeVxVyHandler(Chip8 *chip8, uint16_t instruction); 22 | static uint16_t LdVxByteHandler(Chip8 *chip8, uint16_t instruction); 23 | static uint16_t AddVxByteHandler(Chip8 *chip8, uint16_t instruction); 24 | static uint16_t LdVxVyHandler(Chip8 *chip8, uint16_t instruction); 25 | static uint16_t OrHandler(Chip8 *chip8, uint16_t instruction); 26 | static uint16_t AndHandler(Chip8 *chip8, uint16_t instruction); 27 | static uint16_t XorHandler(Chip8 *chip8, uint16_t instruction); 28 | static uint16_t AddVxVyHandler(Chip8 *chip8, uint16_t instruction); 29 | static uint16_t SubHandler(Chip8 *chip8, uint16_t instruction); 30 | static uint16_t SubnHandler(Chip8 *chip8, uint16_t instruction); 31 | static uint16_t ShrHandler(Chip8 *chip8, uint16_t instruction); 32 | static uint16_t ShlHandler(Chip8 *chip8, uint16_t instruction); 33 | static uint16_t SneVxVyHandler(Chip8 *chip8, uint16_t instruction); 34 | static uint16_t LdIAddrHandler(Chip8 *chip8, uint16_t instruction); 35 | static uint16_t JpV0AddrHandler(Chip8 *chip8, uint16_t instruction); 36 | static uint16_t RndHandler(Chip8 *chip8, uint16_t instruction); 37 | static uint16_t DrwHandler(Chip8 *chip8, uint16_t instruction); 38 | static uint16_t ClsHandler(Chip8 *chip8, uint16_t instruction); 39 | static uint16_t SkpHandler(Chip8 *chip8, uint16_t instruction); 40 | static uint16_t SknpHandler(Chip8 *chip8, uint16_t instruction); 41 | static uint16_t LdVxDtHandler(Chip8 *chip8, uint16_t instruction); 42 | static uint16_t LdVxKHandler(Chip8 *chip8, uint16_t instruction); 43 | static uint16_t LdDtVxHandler(Chip8 *chip8, uint16_t instruction); 44 | static uint16_t LdStVxHandler(Chip8 *chip8, uint16_t instruction); 45 | static uint16_t AddIVxHandler(Chip8 *chip8, uint16_t instruction); 46 | static uint16_t LdFVxHandler(Chip8 *chip8, uint16_t instruction); 47 | static uint16_t LdBVxHandler(Chip8 *chip8, uint16_t instruction); 48 | static uint16_t LdIVxHandler(Chip8 *chip8, uint16_t instruction); 49 | static uint16_t LdVxIHandler(Chip8 *chip8, uint16_t instruction); 50 | // ------------------- 51 | 52 | static void StoreDigitSpritesInMemory(Chip8 *chip8); 53 | static void PutAddrOnStack(Chip8 *chip8, uint16_t addr); 54 | static uint16_t GetAddrFromStack(Chip8 *chip8); 55 | static void GetInstructionRegisters(uint16_t instruction, uint8_t *reg_x, uint8_t *reg_y); 56 | static void DrawPixel(Chip8 *chip8, unsigned int draw_pos, uint8_t sprite_pixel, unsigned int *collision); 57 | 58 | void Chip8_Init(Chip8 *chip8) 59 | { 60 | srand(time(NULL)); 61 | memset(chip8, 0, sizeof(Chip8)); 62 | 63 | chip8->pc = PROGRAM_START_ADDR; 64 | chip8->program_len = 0; 65 | 66 | for (int i = 0; i < INSTRUCTION_COUNT; i++) 67 | { 68 | chip8->instruction_handlers[i] = NULL; 69 | } 70 | 71 | StoreDigitSpritesInMemory(chip8); 72 | 73 | chip8->instruction_handlers[RET] = RetHandler; 74 | chip8->instruction_handlers[JP_ADDR] = JpAddrHandler; 75 | chip8->instruction_handlers[JP_V0_ADDR] = JpV0AddrHandler; 76 | chip8->instruction_handlers[CALL_ADDR] = CallAddrHandler; 77 | 78 | chip8->instruction_handlers[SE_VX_BYTE] = SeVxByteHandler; 79 | chip8->instruction_handlers[SE_VX_VY] = SeVxVyHandler; 80 | chip8->instruction_handlers[SNE_VX_BYTE] = SneVxByteHandler; 81 | chip8->instruction_handlers[SNE_VX_VY] = SneVxVyHandler; 82 | chip8->instruction_handlers[SKP] = SkpHandler; 83 | chip8->instruction_handlers[SKNP] = SknpHandler; 84 | 85 | chip8->instruction_handlers[LD_VX_BYTE] = LdVxByteHandler; 86 | chip8->instruction_handlers[LD_VX_VY] = LdVxVyHandler; 87 | chip8->instruction_handlers[LD_I_ADDR] = LdIAddrHandler; 88 | chip8->instruction_handlers[LD_VX_DT] = LdVxDtHandler; 89 | chip8->instruction_handlers[LD_VX_K] = LdVxKHandler; 90 | chip8->instruction_handlers[LD_DT_VX] = LdDtVxHandler; 91 | chip8->instruction_handlers[LD_ST_VX] = LdStVxHandler; 92 | chip8->instruction_handlers[LD_F_VX] = LdFVxHandler; 93 | chip8->instruction_handlers[LD_B_VX] = LdBVxHandler; 94 | chip8->instruction_handlers[LD_I_VX] = LdIVxHandler; 95 | chip8->instruction_handlers[LD_VX_I] = LdVxIHandler; 96 | 97 | chip8->instruction_handlers[ADD_VX_BYTE] = AddVxByteHandler; 98 | chip8->instruction_handlers[ADD_VX_VY] = AddVxVyHandler; 99 | chip8->instruction_handlers[ADD_I_VX] = AddIVxHandler; 100 | chip8->instruction_handlers[SUB] = SubHandler; 101 | chip8->instruction_handlers[SUBN] = SubnHandler; 102 | chip8->instruction_handlers[SHR] = ShrHandler; 103 | chip8->instruction_handlers[SHL] = ShlHandler; 104 | chip8->instruction_handlers[RND] = RndHandler; 105 | 106 | chip8->instruction_handlers[OR] = OrHandler; 107 | chip8->instruction_handlers[AND] = AndHandler; 108 | chip8->instruction_handlers[XOR] = XorHandler; 109 | 110 | chip8->instruction_handlers[DRW] = DrwHandler; 111 | chip8->instruction_handlers[CLS] = ClsHandler; 112 | } 113 | 114 | void Chip8_Reset(Chip8 *chip8) 115 | { 116 | memset(chip8->v, 0, sizeof(chip8->v)); 117 | memset(chip8->stack, 0, sizeof(chip8->stack)); 118 | memset(chip8->display, 0, sizeof(chip8->display)); 119 | 120 | chip8->dt = 0; 121 | chip8->st = 0; 122 | chip8->i = 0; 123 | chip8->pc = PROGRAM_START_ADDR; 124 | chip8->sp = 0; 125 | chip8->time_acc = 0; 126 | } 127 | 128 | int Chip8_Load(Chip8 *chip8, uint8_t *data, unsigned int len) 129 | { 130 | if (len > RAM_SIZE - PROGRAM_START_ADDR) 131 | { 132 | return -1; 133 | } 134 | 135 | // load the program in RAM 136 | memcpy(chip8->mem + PROGRAM_START_ADDR, data, len); 137 | 138 | chip8->program_len = len; 139 | 140 | return 0; 141 | } 142 | 143 | int Chip8_LoadFromFile(Chip8 *chip8, const char *path) 144 | { 145 | FILE *f = fopen(path, "rb"); 146 | 147 | if (!f) 148 | { 149 | return -1; 150 | } 151 | 152 | uint8_t data[RAM_SIZE - PROGRAM_START_ADDR]; 153 | size_t len = fread(data, 1, sizeof(data), f); 154 | 155 | fclose(f); 156 | 157 | if (!len) 158 | { 159 | return -1; 160 | } 161 | 162 | return Chip8_Load(chip8, data, len); 163 | } 164 | 165 | int Chip8_GetNextInstruction(Chip8 *chip8, Chip8_InstructionType *instruction_type, uint16_t *instruction) 166 | { 167 | if (chip8->pc >= PROGRAM_START_ADDR + chip8->program_len) 168 | { 169 | return 0; 170 | } 171 | 172 | uint8_t high_byte = chip8->mem[chip8->pc]; 173 | uint8_t low_byte = chip8->mem[chip8->pc + 1]; 174 | uint8_t opcode = high_byte >> 4; 175 | 176 | *instruction_type = UNKNOWN_INSTRUCTION; 177 | *instruction = ((high_byte & 0x0F) << 8) | (uint16_t)low_byte; 178 | 179 | switch (opcode) 180 | { 181 | case 0x00: 182 | if (*instruction == 0xE0) 183 | { 184 | *instruction_type = CLS; 185 | break; 186 | } 187 | else if (*instruction == 0xEE) 188 | { 189 | *instruction_type = RET; 190 | break; 191 | } 192 | break; 193 | 194 | case 0x01: 195 | *instruction_type = JP_ADDR; 196 | break; 197 | 198 | case 0x02: 199 | *instruction_type = CALL_ADDR; 200 | break; 201 | 202 | case 0x03: 203 | *instruction_type = SE_VX_BYTE; 204 | break; 205 | 206 | case 0x04: 207 | *instruction_type = SNE_VX_BYTE; 208 | break; 209 | 210 | case 0x05: 211 | *instruction_type = SE_VX_VY; 212 | break; 213 | 214 | case 0x06: 215 | *instruction_type = LD_VX_BYTE; 216 | break; 217 | 218 | case 0x07: 219 | *instruction_type = ADD_VX_BYTE; 220 | break; 221 | 222 | case 0x08: 223 | switch (low_byte & 0x0F) 224 | { 225 | case 0x00: 226 | *instruction_type = LD_VX_VY; 227 | break; 228 | 229 | case 0x01: 230 | *instruction_type = OR; 231 | break; 232 | 233 | case 0x02: 234 | *instruction_type = AND; 235 | break; 236 | 237 | case 0x03: 238 | *instruction_type = XOR; 239 | break; 240 | 241 | case 0x04: 242 | *instruction_type = ADD_VX_VY; 243 | break; 244 | 245 | case 0x05: 246 | *instruction_type = SUB; 247 | break; 248 | 249 | case 0x06: 250 | *instruction_type = SHR; 251 | break; 252 | 253 | case 0x07: 254 | *instruction_type = SUBN; 255 | break; 256 | 257 | case 0x0E: 258 | *instruction_type = SHL; 259 | break; 260 | } 261 | break; 262 | 263 | case 0x09: 264 | *instruction_type = SNE_VX_VY; 265 | break; 266 | 267 | case 0x0A: 268 | *instruction_type = LD_I_ADDR; 269 | break; 270 | 271 | case 0x0B: 272 | *instruction_type = JP_V0_ADDR; 273 | break; 274 | 275 | case 0x0C: 276 | *instruction_type = RND; 277 | break; 278 | 279 | case 0x0D: 280 | *instruction_type = DRW; 281 | break; 282 | 283 | case 0x0E: 284 | switch (low_byte) 285 | { 286 | case 0x9E: 287 | *instruction_type = SKP; 288 | break; 289 | 290 | case 0xA1: 291 | *instruction_type = SKNP; 292 | break; 293 | } 294 | break; 295 | 296 | case 0x0F: 297 | switch (low_byte) 298 | { 299 | case 0x07: 300 | *instruction_type = LD_VX_DT; 301 | break; 302 | 303 | case 0x0A: 304 | *instruction_type = LD_VX_K; 305 | break; 306 | 307 | case 0x15: 308 | *instruction_type = LD_DT_VX; 309 | break; 310 | 311 | case 0x18: 312 | *instruction_type = LD_ST_VX; 313 | break; 314 | 315 | case 0x1E: 316 | *instruction_type = ADD_I_VX; 317 | break; 318 | 319 | case 0x29: 320 | *instruction_type = LD_F_VX; 321 | break; 322 | 323 | case 0x33: 324 | *instruction_type = LD_B_VX; 325 | break; 326 | 327 | case 0x55: 328 | *instruction_type = LD_I_VX; 329 | break; 330 | 331 | case 0x65: 332 | *instruction_type = LD_VX_I; 333 | break; 334 | } 335 | break; 336 | } 337 | 338 | return 1; 339 | } 340 | 341 | uint16_t Chip8_ExecuteInstruction(Chip8 *chip8, Chip8_InstructionType opcode, uint16_t instruction) 342 | { 343 | Chip8_InstructionHandler handler = chip8->instruction_handlers[opcode]; 344 | 345 | if (!handler) 346 | { 347 | return 0; 348 | } 349 | 350 | return handler(chip8, instruction); 351 | } 352 | 353 | unsigned int Chip8_GetPixel(Chip8 *chip8, unsigned int pos) 354 | { 355 | uint8_t byte = chip8->display[pos / 8]; 356 | unsigned int offset = 7 - (pos % 8); 357 | 358 | return (byte & (1 << offset)) >> offset; 359 | } 360 | 361 | void Chip8_SetGetKeysCallback(Chip8 *chip8, GetKeysCb cb) 362 | { 363 | chip8->get_keys = cb; 364 | } 365 | 366 | int Chip8_Tick(Chip8 *chip8) 367 | { 368 | Chip8_InstructionType instruction_type; 369 | uint16_t instruction; 370 | 371 | if (!Chip8_GetNextInstruction(chip8, &instruction_type, &instruction)) 372 | { 373 | return 0; 374 | } 375 | 376 | chip8->pc += Chip8_ExecuteInstruction(chip8, instruction_type, instruction); 377 | 378 | // Update timers 379 | 380 | chip8->time_acc += CPU_TICK_SECS; 381 | 382 | if (chip8->time_acc >= TIMER_TICK_SECS) 383 | { 384 | if (chip8->dt > 0) 385 | { 386 | chip8->dt--; 387 | } 388 | 389 | if (chip8->st > 0) 390 | { 391 | chip8->st--; 392 | } 393 | 394 | chip8->time_acc = 0; 395 | } 396 | 397 | return chip8->pc; 398 | } 399 | 400 | static void StoreDigitSpritesInMemory(Chip8 *chip8) 401 | { 402 | static uint8_t sprites[16][SPRITE_SIZE] = { 403 | {0xF0, 0x90, 0x90, 0x90, 0xF0}, 404 | {0x20, 0x60, 0x20, 0x20, 0x70}, 405 | {0xF0, 0x10, 0xF0, 0x80, 0xF0}, 406 | {0xF0, 0x10, 0xF0, 0x10, 0xF0}, 407 | {0x90, 0x90, 0xF0, 0x10, 0x10}, 408 | {0xF0, 0x80, 0xF0, 0x10, 0xF0}, 409 | {0xF0, 0x80, 0xF0, 0x90, 0xF0}, 410 | {0xF0, 0x10, 0x20, 0x40, 0x40}, 411 | {0xF0, 0x90, 0xF0, 0x90, 0xF0}, 412 | {0xF0, 0x90, 0xF0, 0x10, 0xF0}, 413 | {0xF0, 0x90, 0xF0, 0x90, 0x90}, 414 | {0xE0, 0x90, 0xE0, 0x90, 0xE0}, 415 | {0xF0, 0x80, 0x80, 0x80, 0xF0}, 416 | {0xE0, 0x90, 0x90, 0x90, 0xE0}, 417 | {0xF0, 0x80, 0xF0, 0x80, 0xF0}, 418 | {0xF0, 0x80, 0xF0, 0x80, 0x80}, 419 | }; 420 | 421 | for (size_t i = 0; i < sizeof(sprites); i++) 422 | { 423 | chip8->mem[i] = sprites[i / SPRITE_SIZE][i % SPRITE_SIZE]; 424 | } 425 | } 426 | 427 | static void PutAddrOnStack(Chip8 *chip8, uint16_t addr) 428 | { 429 | if (chip8->sp >= STACK_SIZE) 430 | { 431 | // stack overflow 432 | abort(); 433 | } 434 | 435 | chip8->stack[chip8->sp] = addr; 436 | chip8->sp++; 437 | } 438 | 439 | static uint16_t GetAddrFromStack(Chip8 *chip8) 440 | { 441 | if (chip8->sp == 0) 442 | { 443 | // nothing on the stack 444 | abort(); 445 | } 446 | 447 | chip8->sp--; 448 | 449 | return chip8->stack[chip8->sp]; 450 | } 451 | 452 | static void GetInstructionRegisters(uint16_t instruction, uint8_t *reg_x, uint8_t *reg_y) 453 | { 454 | *reg_x = HIGH_BYTE(instruction) & 0x0F; 455 | 456 | if (reg_y) *reg_y = (LOW_BYTE(instruction) & 0xF0) >> 4; 457 | } 458 | 459 | static uint16_t CallAddrHandler(Chip8 *chip8, uint16_t instruction) 460 | { 461 | PutAddrOnStack(chip8, chip8->pc); 462 | chip8->pc = ADDR(instruction); 463 | 464 | return 0; 465 | } 466 | 467 | static uint16_t JpAddrHandler(Chip8 *chip8, uint16_t instruction) 468 | { 469 | chip8->pc = ADDR(instruction); 470 | 471 | return 0; 472 | } 473 | 474 | static uint16_t RetHandler(Chip8 *chip8, uint16_t instruction) 475 | { 476 | (void)instruction; 477 | chip8->pc = GetAddrFromStack(chip8); 478 | 479 | return 2; 480 | } 481 | 482 | static uint16_t SeVxByteHandler(Chip8 *chip8, uint16_t instruction) 483 | { 484 | uint8_t reg_x; 485 | 486 | GetInstructionRegisters(instruction, ®_x, NULL); 487 | 488 | return chip8->v[reg_x] == LOW_BYTE(instruction) ? 4 : 2; 489 | } 490 | 491 | static uint16_t SneVxByteHandler(Chip8 *chip8, uint16_t instruction) 492 | { 493 | uint8_t reg_x; 494 | 495 | GetInstructionRegisters(instruction, ®_x, NULL); 496 | 497 | return chip8->v[reg_x] != LOW_BYTE(instruction) ? 4 : 2; 498 | } 499 | 500 | static uint16_t SeVxVyHandler(Chip8 *chip8, uint16_t instruction) 501 | { 502 | uint8_t reg_x, reg_y; 503 | 504 | GetInstructionRegisters(instruction, ®_x, ®_y); 505 | 506 | // skip next instruction if the value in register X == the value in register Y 507 | return chip8->v[reg_x] == chip8->v[reg_y] ? 4 : 2; 508 | } 509 | 510 | static uint16_t LdVxByteHandler(Chip8 *chip8, uint16_t instruction) 511 | { 512 | uint8_t reg_x; 513 | 514 | GetInstructionRegisters(instruction, ®_x, NULL); 515 | 516 | chip8->v[reg_x] = LOW_BYTE(instruction); 517 | 518 | return 2; 519 | } 520 | 521 | static uint16_t AddVxByteHandler(Chip8 *chip8, uint16_t instruction) 522 | { 523 | uint8_t reg_x; 524 | 525 | GetInstructionRegisters(instruction, ®_x, NULL); 526 | 527 | chip8->v[reg_x] += LOW_BYTE(instruction); 528 | 529 | return 2; 530 | } 531 | 532 | static uint16_t LdVxVyHandler(Chip8 *chip8, uint16_t instruction) 533 | { 534 | uint8_t reg_x, reg_y; 535 | 536 | GetInstructionRegisters(instruction, ®_x, ®_y); 537 | 538 | chip8->v[reg_x] = chip8->v[reg_y]; 539 | 540 | return 2; 541 | } 542 | 543 | static uint16_t OrHandler(Chip8 *chip8, uint16_t instruction) 544 | { 545 | uint8_t reg_x, reg_y; 546 | 547 | GetInstructionRegisters(instruction, ®_x, ®_y); 548 | 549 | chip8->v[reg_x] |= chip8->v[reg_y]; 550 | 551 | return 2; 552 | } 553 | 554 | static uint16_t AndHandler(Chip8 *chip8, uint16_t instruction) 555 | { 556 | uint8_t reg_x, reg_y; 557 | 558 | GetInstructionRegisters(instruction, ®_x, ®_y); 559 | 560 | chip8->v[reg_x] &= chip8->v[reg_y]; 561 | 562 | return 2; 563 | } 564 | 565 | static uint16_t XorHandler(Chip8 *chip8, uint16_t instruction) 566 | { 567 | uint8_t reg_x, reg_y; 568 | 569 | GetInstructionRegisters(instruction, ®_x, ®_y); 570 | 571 | chip8->v[reg_x] ^= chip8->v[reg_y]; 572 | 573 | return 2; 574 | } 575 | 576 | static uint16_t AddVxVyHandler(Chip8 *chip8, uint16_t instruction) 577 | { 578 | uint8_t reg_x, reg_y; 579 | 580 | GetInstructionRegisters(instruction, ®_x, ®_y); 581 | 582 | uint16_t res = chip8->v[reg_x] + chip8->v[reg_y]; 583 | 584 | chip8->v[0xF] = res > 0xFF ? 1 : 0; 585 | chip8->v[reg_x] = res & 0xFF; 586 | 587 | return 2; 588 | } 589 | 590 | static uint16_t SubHandler(Chip8 *chip8, uint16_t instruction) 591 | { 592 | uint8_t reg_x, reg_y; 593 | 594 | GetInstructionRegisters(instruction, ®_x, ®_y); 595 | 596 | chip8->v[0xF] = chip8->v[reg_x] > chip8->v[reg_y] ? 1 : 0; 597 | chip8->v[reg_x] -= chip8->v[reg_y]; 598 | 599 | return 2; 600 | } 601 | 602 | static uint16_t SubnHandler(Chip8 *chip8, uint16_t instruction) 603 | { 604 | uint8_t reg_x, reg_y; 605 | 606 | GetInstructionRegisters(instruction, ®_x, ®_y); 607 | 608 | chip8->v[0xF] = chip8->v[reg_y] > chip8->v[reg_x] ? 1 : 0; 609 | chip8->v[reg_x] = chip8->v[reg_y] - chip8->v[reg_x]; 610 | 611 | return 2; 612 | } 613 | 614 | static uint16_t ShrHandler(Chip8 *chip8, uint16_t instruction) 615 | { 616 | uint8_t reg_x; 617 | 618 | GetInstructionRegisters(instruction, ®_x, NULL); 619 | 620 | chip8->v[0xF] = chip8->v[reg_x] & 0x1; 621 | chip8->v[reg_x] /= 2; 622 | 623 | return 2; 624 | } 625 | 626 | static uint16_t ShlHandler(Chip8 *chip8, uint16_t instruction) 627 | { 628 | uint8_t reg_x; 629 | 630 | GetInstructionRegisters(instruction, ®_x, NULL); 631 | 632 | chip8->v[0xF] = (chip8->v[reg_x] & (0x1 << 7)) > 0; 633 | chip8->v[reg_x] *= 2; 634 | 635 | return 2; 636 | } 637 | 638 | static uint16_t SneVxVyHandler(Chip8 *chip8, uint16_t instruction) 639 | { 640 | uint8_t reg_x, reg_y; 641 | 642 | GetInstructionRegisters(instruction, ®_x, ®_y); 643 | 644 | return chip8->v[reg_x] != chip8->v[reg_y] ? 4 : 2; 645 | } 646 | 647 | static uint16_t LdIAddrHandler(Chip8 *chip8, uint16_t instruction) 648 | { 649 | chip8->i = ADDR(instruction); 650 | 651 | return 2; 652 | } 653 | 654 | static uint16_t JpV0AddrHandler(Chip8 *chip8, uint16_t instruction) 655 | { 656 | chip8->pc = ADDR(instruction) + chip8->v[0x0]; 657 | 658 | return 0; 659 | } 660 | 661 | static uint16_t RndHandler(Chip8 *chip8, uint16_t instruction) 662 | { 663 | uint8_t reg_x; 664 | uint8_t random = rand() % 256; 665 | 666 | GetInstructionRegisters(instruction, ®_x, NULL); 667 | 668 | chip8->v[reg_x] = random & LOW_BYTE(instruction); 669 | 670 | return 2; 671 | } 672 | 673 | static uint16_t DrwHandler(Chip8 *chip8, uint16_t instruction) 674 | { 675 | uint8_t reg_x, reg_y; 676 | 677 | GetInstructionRegisters(instruction, ®_x, ®_y); 678 | 679 | unsigned int start_x = chip8->v[reg_x]; 680 | unsigned int start_y = chip8->v[reg_y]; 681 | unsigned int sprite_height = NIBBLE(instruction); // sprite height 682 | unsigned int collision = 0; 683 | 684 | // printf("Draw sprite at (%d,%d)\n", start_x, start_y); 685 | 686 | for (unsigned int i = 0; i < sprite_height; i++) 687 | { 688 | uint8_t sprite_byte = chip8->mem[chip8->i + i]; 689 | 690 | for (unsigned int j = 0; j < 8; j++) 691 | { 692 | unsigned int x = (start_x + j) % DISPLAY_WIDTH; 693 | unsigned int y = (start_y + i) % DISPLAY_HEIGHT; 694 | unsigned int draw_pos = (y * DISPLAY_WIDTH) + x; 695 | unsigned int sprite_offset = 7 - j; 696 | uint8_t sprite_pixel = (sprite_byte & (0x1 << sprite_offset)) >> sprite_offset; // 0 or 1 697 | 698 | // printf("Draw pixel %d, %d (%d) = %d\n", x, y, draw_pos, sprite_pixel); 699 | DrawPixel(chip8, draw_pos, sprite_pixel, &collision); 700 | } 701 | } 702 | 703 | chip8->v[0xF] = collision; 704 | 705 | return 2; 706 | } 707 | 708 | static uint16_t ClsHandler(Chip8 *chip8, uint16_t instruction) 709 | { 710 | (void)instruction; 711 | memset(chip8->display, 0, DISPLAY_SIZE); 712 | return 2; 713 | } 714 | 715 | static uint16_t SkpHandler(Chip8 *chip8, uint16_t instruction) 716 | { 717 | uint8_t reg_x; 718 | 719 | GetInstructionRegisters(instruction, ®_x, NULL); 720 | 721 | return (chip8->get_keys() & KEY_MASK(chip8->v[reg_x])) > 0 ? 4 : 2; 722 | } 723 | 724 | static uint16_t SknpHandler(Chip8 *chip8, uint16_t instruction) 725 | { 726 | uint8_t reg_x; 727 | 728 | GetInstructionRegisters(instruction, ®_x, NULL); 729 | 730 | return (chip8->get_keys() & KEY_MASK(chip8->v[reg_x])) > 0 ? 2 : 4; 731 | } 732 | 733 | static uint16_t LdVxDtHandler(Chip8 *chip8, uint16_t instruction) 734 | { 735 | uint8_t reg_x; 736 | 737 | GetInstructionRegisters(instruction, ®_x, NULL); 738 | 739 | chip8->v[reg_x] = chip8->dt; 740 | 741 | return 2; 742 | } 743 | 744 | static uint16_t LdVxKHandler(Chip8 *chip8, uint16_t instruction) 745 | { 746 | uint8_t reg_x; 747 | uint16_t keys = chip8->get_keys(); 748 | 749 | GetInstructionRegisters(instruction, ®_x, NULL); 750 | 751 | // don't advance the PC until a key is pressed 752 | if (keys > 0) 753 | { 754 | for (int k = 0; k <= 0xF; k++) 755 | { 756 | if (keys & KEY_MASK(k)) 757 | { 758 | chip8->v[reg_x] = k; 759 | break; 760 | } 761 | } 762 | 763 | return 2; 764 | } 765 | 766 | return 0; 767 | } 768 | 769 | static uint16_t LdDtVxHandler(Chip8 *chip8, uint16_t instruction) 770 | { 771 | uint8_t reg_x; 772 | 773 | GetInstructionRegisters(instruction, ®_x, NULL); 774 | 775 | chip8->dt = chip8->v[reg_x]; 776 | 777 | return 2; 778 | } 779 | 780 | static uint16_t LdStVxHandler(Chip8 *chip8, uint16_t instruction) 781 | { 782 | uint8_t reg_x; 783 | 784 | GetInstructionRegisters(instruction, ®_x, NULL); 785 | 786 | chip8->st = chip8->v[reg_x]; 787 | 788 | return 2; 789 | } 790 | 791 | static uint16_t AddIVxHandler(Chip8 *chip8, uint16_t instruction) 792 | { 793 | uint8_t reg_x; 794 | 795 | GetInstructionRegisters(instruction, ®_x, NULL); 796 | 797 | chip8->i += chip8->v[reg_x]; 798 | 799 | return 2; 800 | } 801 | 802 | static uint16_t LdFVxHandler(Chip8 *chip8, uint16_t instruction) 803 | { 804 | uint8_t reg_x; 805 | 806 | GetInstructionRegisters(instruction, ®_x, NULL); 807 | 808 | uint8_t digit = chip8->v[reg_x] & 0x0F; 809 | 810 | chip8->i = digit * SPRITE_SIZE; 811 | 812 | return 2; 813 | } 814 | 815 | static uint16_t LdBVxHandler(Chip8 *chip8, uint16_t instruction) 816 | { 817 | uint8_t reg_x; 818 | 819 | GetInstructionRegisters(instruction, ®_x, NULL); 820 | 821 | uint8_t val = chip8->v[reg_x]; 822 | uint8_t hundreds_digit = val / 100; 823 | 824 | chip8->mem[chip8->i] = hundreds_digit; 825 | val -= hundreds_digit * 100; 826 | 827 | uint8_t tens_digit = val / 10; 828 | 829 | chip8->mem[chip8->i + 1] = tens_digit; 830 | val -= tens_digit * 10; 831 | 832 | chip8->mem[chip8->i + 2] = val; 833 | 834 | return 2; 835 | } 836 | 837 | static uint16_t LdIVxHandler(Chip8 *chip8, uint16_t instruction) 838 | { 839 | uint8_t reg_x; 840 | 841 | GetInstructionRegisters(instruction, ®_x, NULL); 842 | memcpy(chip8->mem + chip8->i, chip8->v, reg_x + 1); 843 | 844 | return 2; 845 | } 846 | 847 | static uint16_t LdVxIHandler(Chip8 *chip8, uint16_t instruction) 848 | { 849 | uint8_t reg_x; 850 | 851 | GetInstructionRegisters(instruction, ®_x, NULL); 852 | memcpy(chip8->v, chip8->mem + chip8->i, reg_x + 1); 853 | 854 | return 2; 855 | } 856 | 857 | static void DrawPixel(Chip8 *chip8, unsigned int draw_pos, uint8_t sprite_pixel, unsigned int *collision) 858 | { 859 | unsigned int display_index = draw_pos / 8; 860 | unsigned int offset = 7 - (draw_pos % 8); 861 | uint8_t display_byte = chip8->display[display_index]; 862 | uint8_t display_pixel = (display_byte & (0x1 << offset)) >> offset; // 0 or 1 863 | uint8_t draw_pixel = display_pixel ^ sprite_pixel; // 0 or 1 864 | uint8_t draw_mask = 0x1 << offset; 865 | 866 | if (*collision == 0 && display_pixel == 1 && draw_pixel == 0) *collision = 1; 867 | 868 | assert(display_index >= 0 && display_index < DISPLAY_SIZE); 869 | 870 | if (draw_pixel) 871 | { 872 | chip8->display[display_index] |= draw_mask; 873 | } 874 | else 875 | { 876 | chip8->display[display_index] &= ~draw_mask; 877 | } 878 | } 879 | -------------------------------------------------------------------------------- /chip-8.h: -------------------------------------------------------------------------------- 1 | #ifndef CHIP8_H 2 | #define CHIP8_H 3 | 4 | #include 5 | 6 | #define RAM_SIZE 4096 7 | #define PROGRAM_START_ADDR 0x200 8 | #define INSTRUCTION_COUNT 35 9 | #define REGISTER_COUNT 16 10 | #define STACK_SIZE 16 11 | #define DISPLAY_WIDTH 64 12 | #define DISPLAY_HEIGHT 32 13 | #define DISPLAY_SIZE ((DISPLAY_WIDTH * DISPLAY_HEIGHT) / 8) // display in bytes (1 pixel = 1 bit) 14 | #define SPRITE_SIZE 5 // in bytes 15 | #define CPU_FREQUENCY 500.0 16 | #define CPU_TICK_SECS (1 / CPU_FREQUENCY) 17 | #define TIMER_TICK_SECS (1 / 60.0) // timer ticks at 60Hz 18 | 19 | typedef struct Chip8 Chip8; 20 | 21 | typedef uint16_t (*Chip8_InstructionHandler)(Chip8 *, uint16_t); 22 | typedef uint16_t (*GetKeysCb)(void); 23 | 24 | struct Chip8 25 | { 26 | uint8_t v[REGISTER_COUNT]; // 16 8 bits general purpose registers 27 | uint8_t dt; // special purpose 8 bits register used for delay timer 28 | uint8_t st; // special purpose 8 bits register used for sound timer 29 | uint16_t i; // 16 bit register generally used to store memory addresses (only 12 lowest bits are used) 30 | uint16_t pc; // program counter 31 | uint8_t sp; // stack pointer 32 | uint16_t stack[STACK_SIZE]; // stack 33 | uint8_t mem[RAM_SIZE]; // RAM 34 | uint8_t display[DISPLAY_SIZE]; // pixels to display 35 | unsigned int program_len; // size of the program 36 | double time_acc; // time accumulator for timers 37 | Chip8_InstructionHandler instruction_handlers[INSTRUCTION_COUNT]; 38 | GetKeysCb get_keys; // is key pressed callback 39 | }; 40 | 41 | typedef enum Chip8_InstructionType 42 | { 43 | UNKNOWN_INSTRUCTION, 44 | 45 | // drawing 46 | 47 | CLS, 48 | DRW, 49 | 50 | // subroutines 51 | 52 | RET, 53 | JP_ADDR, 54 | JP_V0_ADDR, 55 | CALL_ADDR, 56 | 57 | // load 58 | 59 | LD_VX_BYTE, 60 | LD_VX_VY, 61 | LD_I_ADDR, 62 | LD_VX_DT, 63 | LD_VX_K, 64 | LD_DT_VX, 65 | LD_ST_VX, 66 | LD_F_VX, 67 | LD_B_VX, 68 | LD_I_VX, 69 | LD_VX_I, 70 | 71 | // arithmetic 72 | 73 | ADD_VX_BYTE, 74 | ADD_VX_VY, 75 | ADD_I_VX, 76 | SUB, 77 | SHR, 78 | SUBN, 79 | SHL, 80 | RND, 81 | 82 | // logic 83 | 84 | OR, 85 | AND, 86 | XOR, 87 | 88 | // branching 89 | 90 | SE_VX_BYTE, 91 | SNE_VX_BYTE, 92 | SE_VX_VY, 93 | SNE_VX_VY, 94 | SKP, 95 | SKNP 96 | } Chip8_InstructionType; 97 | 98 | void Chip8_Init(Chip8 *chip8); 99 | void Chip8_Reset(Chip8 *chip8); 100 | int Chip8_Load(Chip8 *chip8, uint8_t *data, unsigned int len); 101 | int Chip8_LoadFromFile(Chip8 *chip8, const char *path); 102 | int Chip8_GetNextInstruction(Chip8 *chip8, Chip8_InstructionType *instruction_type, uint16_t *instruction); 103 | uint16_t Chip8_ExecuteInstruction(Chip8 *chip8, Chip8_InstructionType opcode, uint16_t instruction); 104 | unsigned int Chip8_GetPixel(Chip8 *chip8, unsigned int pos); 105 | void Chip8_SetGetKeysCallback(Chip8 *chip8, GetKeysCb cb); 106 | int Chip8_Tick(Chip8 *chip8); 107 | 108 | #endif // CHIP8_H 109 | -------------------------------------------------------------------------------- /disassembler.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "chip-8.h" 5 | 6 | static void Disassemble(Chip8 *chip8); 7 | 8 | int main(int argc, char **argv) 9 | { 10 | if (argc != 2) 11 | { 12 | printf("Usage: disassembler ROM_PATH\n"); 13 | return 1; 14 | } 15 | 16 | const char *rom_path = argv[1]; 17 | Chip8 chip8; 18 | 19 | Chip8_Init(&chip8); 20 | 21 | if (Chip8_LoadFromFile(&chip8, rom_path) < 0) 22 | { 23 | printf("Failed to load ROM (path: %s)\n", rom_path); 24 | return 1; 25 | } 26 | 27 | printf("ROM loaded (program length: %d)\n", chip8.program_len); 28 | 29 | Disassemble(&chip8); 30 | 31 | return 0; 32 | } 33 | 34 | static void Disassemble(Chip8 *chip8) 35 | { 36 | Chip8_InstructionType instruction_type; 37 | uint16_t instruction; 38 | 39 | while (Chip8_GetNextInstruction(chip8, &instruction_type, &instruction)) 40 | { 41 | uint8_t x_reg = (instruction & 0x0F00) >> 8; 42 | uint8_t y_reg = (instruction & 0x00F0) >> 4; 43 | uint8_t low_byte = instruction & 0xFF; 44 | uint8_t nibble = low_byte & 0xF; 45 | 46 | printf("0x%x\t", chip8->pc); 47 | 48 | switch (instruction_type) 49 | { 50 | case UNKNOWN_INSTRUCTION: 51 | printf("NOP\n"); 52 | break; 53 | 54 | case CLS: 55 | printf("CLS\n"); 56 | break; 57 | 58 | case RET: 59 | printf("RET\n"); 60 | break; 61 | 62 | case JP_ADDR: 63 | printf("JP 0x%X\n", instruction); 64 | break; 65 | 66 | case CALL_ADDR: 67 | printf("CALL 0x%X\n", instruction); 68 | break; 69 | 70 | case SE_VX_BYTE: 71 | printf("SE V%X, 0x%X\n", x_reg, low_byte); 72 | break; 73 | 74 | case SNE_VX_BYTE: 75 | printf("SNE V%X, 0x%X\n", x_reg, low_byte); 76 | break; 77 | 78 | case SE_VX_VY: 79 | printf("SE V%X, V%X\n", x_reg, y_reg); 80 | break; 81 | 82 | case LD_VX_BYTE: 83 | printf("LD V%X, 0x%X\n", x_reg, low_byte); 84 | break; 85 | 86 | case ADD_VX_BYTE: 87 | printf("ADD V%X, 0x%X\n", x_reg, low_byte); 88 | break; 89 | 90 | case LD_VX_VY: 91 | printf("LD V%X, V%X\n", x_reg, y_reg); 92 | break; 93 | 94 | case OR: 95 | printf("OR V%X, V%X\n", x_reg, y_reg); 96 | break; 97 | 98 | case AND: 99 | printf("AND V%X, V%X\n", x_reg, y_reg); 100 | break; 101 | 102 | case XOR: 103 | printf("XOR V%X, V%X\n", x_reg, y_reg); 104 | break; 105 | 106 | case ADD_VX_VY: 107 | printf("ADD V%X, V%X\n", x_reg, y_reg); 108 | break; 109 | 110 | case SUB: 111 | printf("SUB V%X, V%X\n", x_reg, y_reg); 112 | break; 113 | 114 | case SHR: 115 | printf("SHR V%X {, V%X}\n", x_reg, y_reg); 116 | break; 117 | 118 | case SUBN: 119 | printf("SUBN V%X, V%X\n", x_reg, y_reg); 120 | break; 121 | 122 | case SHL: 123 | printf("SHL V%X {, V%X}\n", x_reg, y_reg); 124 | break; 125 | 126 | case SNE_VX_VY: 127 | printf("SNE V%X, V%X\n", x_reg, y_reg); 128 | break; 129 | 130 | case LD_I_ADDR: 131 | printf("LD I, 0x%X\n", instruction); 132 | break; 133 | 134 | case JP_V0_ADDR: 135 | printf("JP V0, 0x%X\n", instruction); 136 | break; 137 | 138 | case RND: 139 | printf("RND V%X, 0x%X\n", x_reg, low_byte); 140 | break; 141 | 142 | case DRW: 143 | printf("DRW V%X, V%X, 0x%X\n", x_reg, y_reg, nibble); 144 | break; 145 | 146 | case SKP: 147 | printf("SKP V%X\n", x_reg); 148 | break; 149 | 150 | case SKNP: 151 | printf("SKNP V%X\n", x_reg); 152 | break; 153 | 154 | case LD_VX_DT: 155 | printf("LD V%X, DT\n", x_reg); 156 | break; 157 | 158 | case LD_VX_K: 159 | printf("LD V%X, K\n", x_reg); 160 | break; 161 | 162 | case LD_DT_VX: 163 | printf("LD DT, V%X\n", x_reg); 164 | break; 165 | 166 | case LD_ST_VX: 167 | printf("LD ST, V%X\n", x_reg); 168 | break; 169 | 170 | case ADD_I_VX: 171 | printf("ADD I, V%X\n", x_reg); 172 | break; 173 | 174 | case LD_F_VX: 175 | printf("LD F, V%X\n", x_reg); 176 | break; 177 | 178 | case LD_B_VX: 179 | printf("LD B, V%X\n", x_reg); 180 | break; 181 | 182 | case LD_I_VX: 183 | printf("LD [I], V%X\n", x_reg); 184 | break; 185 | 186 | case LD_VX_I: 187 | printf("LD V%X, [I]\n", x_reg); 188 | break; 189 | 190 | default: 191 | abort(); 192 | } 193 | 194 | chip8->pc += 2; 195 | } 196 | } 197 | -------------------------------------------------------------------------------- /emulator.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "raylib.h" 6 | #include "chip-8.h" 7 | #include "rom_picker.h" 8 | 9 | #define GAME_WIDTH 640 10 | #define GAME_HEIGHT 320 11 | #define HUD_TOP_HEIGHT 25 12 | #define HUD_BOTTOM_HEIGHT 25 13 | #define SCREEN_WIDTH GAME_WIDTH 14 | #define SCREEN_HEIGHT (GAME_HEIGHT + HUD_TOP_HEIGHT + HUD_BOTTOM_HEIGHT) 15 | #define ROM_PICKER_FONT_SIZE 20 16 | #define HUD_FONT_SIZE 15 17 | 18 | typedef enum EmulatorStateType 19 | { 20 | STATE_ROM_SELECTION, 21 | STATE_GAME 22 | } EmulatorStateType; 23 | 24 | typedef struct EmulatorState 25 | { 26 | EmulatorStateType type; 27 | int (*init)(void *); 28 | void (*deinit)(void); 29 | void (*update)(void); 30 | } EmulatorState; 31 | 32 | typedef struct GameStateData 33 | { 34 | Chip8 chip8; 35 | void *pixels; 36 | RenderTexture2D display_render_texture; 37 | double last_time; 38 | double time_acc; 39 | } GameStateData; 40 | 41 | typedef struct RomSelectionData 42 | { 43 | RomPicker picker; 44 | } RomSelectionData; 45 | 46 | typedef struct EmulatorSkin 47 | { 48 | Color colors[4]; 49 | } EmulatorSkin; 50 | 51 | static int ChangeState(EmulatorStateType new_state_type, void *data); 52 | static void DrawGameScreen(RenderTexture2D display_render_texture); 53 | static void DrawHUD(void); 54 | static void UpdateScreen(Chip8 *chip8, RenderTexture2D display_render_texture, void *pixels); 55 | static void UpdateKeys(void); 56 | static uint16_t GetKeys(void); 57 | static int InitGameState(void *data); 58 | static void DeinitGameState(void); 59 | static void UpdateGameState(void); 60 | static int InitRomSelectionState(void *data); 61 | static void DeinitRomSelectionState(void); 62 | static void UpdateRomSelectionState(void); 63 | 64 | static EmulatorState states[2] = { 65 | (EmulatorState){STATE_ROM_SELECTION, InitRomSelectionState, DeinitRomSelectionState, UpdateRomSelectionState}, 66 | (EmulatorState){STATE_GAME, InitGameState, DeinitGameState, UpdateGameState}, 67 | }; 68 | 69 | static EmulatorSkin skin = { 70 | .colors = { 71 | (Color){64, 60, 52, 255}, 72 | (Color){140, 122, 105, 255}, 73 | (Color){217, 199, 184, 255}, 74 | (Color){13, 0, 0, 255} 75 | } 76 | }; 77 | 78 | static RomSelectionData rom_selection_data; 79 | static GameStateData game_state_data; 80 | 81 | // key mappings from 0 to 0xF (16 keys) 82 | static int key_mappings[16] = { 83 | KEY_X, // 0 84 | KEY_ONE, // 1 85 | KEY_TWO, // 2 86 | KEY_THREE, // 3 87 | KEY_Q, // 4 88 | KEY_W, // 5 89 | KEY_E, // 6 90 | KEY_A, // 7 91 | KEY_S, // 8 92 | KEY_D, // 9 93 | KEY_Z, // A 94 | KEY_C, // B 95 | KEY_FOUR, // C 96 | KEY_R, // D 97 | KEY_F, // E 98 | KEY_V // F 99 | }; 100 | 101 | static uint16_t keys = 0; 102 | static EmulatorState *current_state = NULL; 103 | static bool rom_picker_enabled = false; 104 | 105 | int main(int argc, char **argv) 106 | { 107 | InitWindow(SCREEN_WIDTH, SCREEN_HEIGHT, "Chip-8 Emulator"); 108 | 109 | if (argc == 1) 110 | { 111 | RomPicker_Init(&rom_selection_data.picker, ROMS_DIR_PATH); 112 | 113 | rom_picker_enabled = true; 114 | 115 | if (ChangeState(STATE_ROM_SELECTION, NULL) < 0) 116 | { 117 | goto error; 118 | } 119 | } 120 | else if (argc == 2) 121 | { 122 | if (ChangeState(STATE_GAME, argv[1]) < 0) 123 | { 124 | goto error; 125 | } 126 | } 127 | else 128 | { 129 | printf("Usage: emulator [ROM_PATH]\n"); 130 | goto error; 131 | } 132 | 133 | while (!WindowShouldClose()) 134 | { 135 | current_state->update(); 136 | } 137 | 138 | current_state->deinit(); 139 | CloseWindow(); 140 | return 0; 141 | 142 | error: 143 | fprintf(stderr, "Something went wrong!\n"); 144 | CloseWindow(); 145 | return 1; 146 | } 147 | 148 | static int ChangeState(EmulatorStateType new_state_type, void *data) 149 | { 150 | if (current_state) current_state->deinit(); 151 | 152 | current_state = &states[new_state_type]; 153 | return current_state->init(data); 154 | } 155 | 156 | static int InitGameState(void *data) 157 | { 158 | char *rom_path = data; 159 | 160 | memset(&game_state_data, 0, sizeof(GameStateData)); 161 | game_state_data.last_time = GetTime(); 162 | 163 | Chip8_Init(&game_state_data.chip8); 164 | Chip8_SetGetKeysCallback(&game_state_data.chip8, GetKeys); 165 | 166 | if (Chip8_LoadFromFile(&game_state_data.chip8, rom_path) < 0) 167 | { 168 | fprintf(stderr, "ERROR: Failed to load ROM (path: %s)\n", rom_path); 169 | return -1; 170 | } 171 | 172 | printf("ROM loaded (program length: %d)\n", game_state_data.chip8.program_len); 173 | 174 | game_state_data.pixels = malloc(sizeof(Color) * DISPLAY_WIDTH * DISPLAY_HEIGHT); 175 | memset(game_state_data.pixels, 0, sizeof(Color) * DISPLAY_WIDTH * DISPLAY_HEIGHT); 176 | 177 | game_state_data.display_render_texture = LoadRenderTexture(DISPLAY_WIDTH, DISPLAY_HEIGHT); 178 | 179 | return 0; 180 | } 181 | 182 | static void DeinitGameState(void) 183 | { 184 | UnloadRenderTexture(game_state_data.display_render_texture); 185 | free(game_state_data.pixels); 186 | } 187 | 188 | static void UpdateGameState(void) 189 | { 190 | if (rom_picker_enabled && IsKeyPressed(KEY_BACKSPACE)) 191 | { 192 | ChangeState(STATE_ROM_SELECTION, NULL); 193 | return; 194 | } 195 | 196 | if (IsKeyPressed(KEY_ENTER)) 197 | { 198 | // reset the ROM 199 | Chip8_Reset(&game_state_data.chip8); 200 | } 201 | 202 | double dt_secs = GetTime() - game_state_data.last_time; 203 | game_state_data.last_time = GetTime(); 204 | game_state_data.time_acc += dt_secs; 205 | 206 | while (game_state_data.time_acc >= CPU_TICK_SECS) 207 | { 208 | if (!Chip8_Tick(&game_state_data.chip8)) 209 | { 210 | break; 211 | } 212 | 213 | game_state_data.time_acc -= CPU_TICK_SECS; 214 | } 215 | 216 | if (game_state_data.chip8.st) 217 | { 218 | // TODO: play sound 219 | } 220 | 221 | UpdateScreen(&game_state_data.chip8, game_state_data.display_render_texture, game_state_data.pixels); 222 | UpdateKeys(); 223 | 224 | BeginDrawing(); 225 | ClearBackground(skin.colors[2]); 226 | DrawGameScreen(game_state_data.display_render_texture); 227 | DrawHUD(); 228 | EndDrawing(); 229 | } 230 | 231 | static int InitRomSelectionState(void *data) 232 | { 233 | (void)data; 234 | 235 | return 0; 236 | } 237 | 238 | static void DeinitRomSelectionState(void) {} 239 | 240 | static void UpdateRomSelectionState(void) 241 | { 242 | if (IsKeyPressed(KEY_DOWN)) 243 | { 244 | RomPicker_Down(&rom_selection_data.picker); 245 | } 246 | 247 | if (IsKeyPressed(KEY_UP)) 248 | { 249 | RomPicker_Up(&rom_selection_data.picker); 250 | } 251 | 252 | if (IsKeyPressed(KEY_SPACE) || IsKeyPressed(KEY_ENTER)) 253 | { 254 | char rom_path[ROM_PATH_MAX_LEN]; 255 | 256 | RomPicker_GetSelectedPath(&rom_selection_data.picker, rom_path); 257 | ChangeState(STATE_GAME, rom_path); 258 | } 259 | 260 | BeginDrawing(); 261 | ClearBackground(skin.colors[2]); 262 | 263 | // draw cursor 264 | 265 | int y = HUD_TOP_HEIGHT + (rom_selection_data.picker.cursor * ROM_PICKER_FONT_SIZE); 266 | 267 | DrawRectangle(0, y, SCREEN_WIDTH, ROM_PICKER_FONT_SIZE, skin.colors[0]); 268 | 269 | // draw rom list 270 | 271 | for (unsigned int i = 0; i < rom_selection_data.picker.rom_count; i++) 272 | { 273 | char *rom = rom_selection_data.picker.roms[i]; 274 | int rom_text_width = MeasureText(rom, ROM_PICKER_FONT_SIZE); 275 | Color color = i == rom_selection_data.picker.cursor ? skin.colors[2] : skin.colors[0]; 276 | 277 | DrawText(rom, SCREEN_WIDTH / 2 - rom_text_width / 2, HUD_TOP_HEIGHT + ROM_PICKER_FONT_SIZE * i, ROM_PICKER_FONT_SIZE, color); 278 | } 279 | 280 | DrawHUD(); 281 | EndDrawing(); 282 | } 283 | 284 | static void DrawGameScreen(RenderTexture2D display_render_texture) 285 | { 286 | DrawTexturePro( 287 | display_render_texture.texture, 288 | (Rectangle){0, 0, DISPLAY_WIDTH, DISPLAY_HEIGHT}, 289 | (Rectangle){0, HUD_TOP_HEIGHT, GAME_WIDTH, GAME_HEIGHT}, 290 | (Vector2){0, 0}, 291 | 0, 292 | WHITE); 293 | } 294 | 295 | static void DrawHUD(void) 296 | { 297 | const char *text; 298 | 299 | if (current_state->type == STATE_ROM_SELECTION) 300 | { 301 | text = "Select ROM (Up/Down + ENTER)"; 302 | } 303 | else if (current_state->type == STATE_GAME) 304 | { 305 | text = RomPicker_GetSelectedRomName(&rom_selection_data.picker); 306 | } 307 | 308 | int text_w = MeasureText(text, HUD_FONT_SIZE); 309 | 310 | DrawRectangle(0, 0, SCREEN_WIDTH, HUD_TOP_HEIGHT, skin.colors[1]); 311 | DrawRectangle(0, SCREEN_HEIGHT - HUD_BOTTOM_HEIGHT, SCREEN_WIDTH, HUD_BOTTOM_HEIGHT, skin.colors[1]); 312 | DrawText(text, SCREEN_WIDTH / 2 - text_w / 2, 5, HUD_FONT_SIZE, skin.colors[2]); 313 | DrawText(TextFormat("Frequency: %.1f", CPU_FREQUENCY), 10, SCREEN_HEIGHT - 18, HUD_FONT_SIZE, skin.colors[2]); 314 | 315 | if (current_state->type == STATE_GAME) 316 | { 317 | const char *back_text = "Backspace to return to ROM selection"; 318 | int back_text_w = MeasureText(back_text, HUD_FONT_SIZE); 319 | 320 | DrawText(back_text, SCREEN_WIDTH - (back_text_w + 5), SCREEN_HEIGHT - 18, HUD_FONT_SIZE, skin.colors[2]); 321 | } 322 | } 323 | 324 | static void UpdateScreen(Chip8 *chip8, RenderTexture2D display_render_texture, void *pixels) 325 | { 326 | for (int i = 0; i < DISPLAY_WIDTH * DISPLAY_HEIGHT; i++) 327 | { 328 | Color color = Chip8_GetPixel(chip8, i) ? skin.colors[3] : skin.colors[2]; 329 | 330 | memcpy(pixels + (i * sizeof(Color)), &color, sizeof(Color)); 331 | } 332 | 333 | UpdateTexture(display_render_texture.texture, pixels); 334 | } 335 | 336 | static void UpdateKeys(void) 337 | { 338 | keys = 0; 339 | 340 | for (int i = 0; i <= 0xF; i++) 341 | { 342 | if (IsKeyDown(key_mappings[i])) 343 | { 344 | keys |= (1 << (0xF - i)); 345 | } 346 | } 347 | } 348 | 349 | static uint16_t GetKeys(void) 350 | { 351 | return keys; 352 | } 353 | -------------------------------------------------------------------------------- /rom_picker.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "rom_picker.h" 7 | 8 | void RomPicker_Init(RomPicker *picker, const char *roms_path) 9 | { 10 | picker->rom_count = 0; 11 | picker->cursor = 0; 12 | 13 | strncpy(picker->roms_path, roms_path, ROM_PATH_MAX_LEN - 1); 14 | 15 | DIR *dir = opendir(roms_path); 16 | struct dirent *ent; 17 | 18 | if (!dir) 19 | { 20 | fprintf(stderr, "Failed to read ROMs directory\n"); 21 | abort(); 22 | } 23 | 24 | while ((ent = readdir(dir)) != NULL && picker->rom_count < MAX_ROMS) 25 | { 26 | if (ent->d_type == DT_REG) 27 | { 28 | strncpy(picker->roms[picker->rom_count], ent->d_name, ROM_NAME_MAX_LEN - 1); 29 | picker->rom_count++; 30 | } 31 | } 32 | 33 | closedir(dir); 34 | } 35 | 36 | void RomPicker_Up(RomPicker *picker) 37 | { 38 | picker->cursor = (picker->cursor - 1 + MAX_ROMS) % MAX_ROMS; 39 | } 40 | 41 | void RomPicker_Down(RomPicker *picker) 42 | { 43 | picker->cursor = (picker->cursor + 1) % MAX_ROMS; 44 | } 45 | 46 | void RomPicker_GetSelectedPath(RomPicker *picker, char rom_path[ROM_PATH_MAX_LEN]) 47 | { 48 | const char *selected_rom = picker->roms[picker->cursor]; 49 | 50 | snprintf(rom_path, ROM_PATH_MAX_LEN, "%s/%s", picker->roms_path, selected_rom); 51 | } 52 | 53 | const char *RomPicker_GetSelectedRomName(RomPicker *picker) 54 | { 55 | return picker->roms[picker->cursor]; 56 | } 57 | -------------------------------------------------------------------------------- /rom_picker.h: -------------------------------------------------------------------------------- 1 | #ifndef ROM_PICKER_H 2 | #define ROM_PICKER_H 3 | 4 | #define MAX_ROMS 16 5 | #define ROM_PATH_MAX_LEN 255 6 | #define ROM_NAME_MAX_LEN 25 7 | 8 | typedef struct RomPicker 9 | { 10 | char roms[MAX_ROMS][ROM_NAME_MAX_LEN]; 11 | char roms_path[ROM_PATH_MAX_LEN]; 12 | unsigned int rom_count; 13 | unsigned int cursor; 14 | } RomPicker; 15 | 16 | void RomPicker_Init(RomPicker *picker, const char *roms_path); 17 | void RomPicker_Up(RomPicker *picker); 18 | void RomPicker_Down(RomPicker *picker); 19 | void RomPicker_GetSelectedPath(RomPicker *picker, char rom_path[ROM_PATH_MAX_LEN]); 20 | const char *RomPicker_GetSelectedRomName(RomPicker *picker); 21 | 22 | #endif // ROM_PICKER_H 23 | -------------------------------------------------------------------------------- /screenshots/chip8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nathhB/chip8emulator/22214c66776fa8d7500f3dfaa89e77facc719962/screenshots/chip8.png -------------------------------------------------------------------------------- /screenshots/screen1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nathhB/chip8emulator/22214c66776fa8d7500f3dfaa89e77facc719962/screenshots/screen1.png -------------------------------------------------------------------------------- /screenshots/screen2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nathhB/chip8emulator/22214c66776fa8d7500f3dfaa89e77facc719962/screenshots/screen2.png -------------------------------------------------------------------------------- /screenshots/screen3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nathhB/chip8emulator/22214c66776fa8d7500f3dfaa89e77facc719962/screenshots/screen3.png -------------------------------------------------------------------------------- /shell.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Emscripten-Generated Code 7 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 109 | 110 | {{{ SCRIPT }}} 111 | 112 | 113 | -------------------------------------------------------------------------------- /tests.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "chip-8.h" 7 | 8 | static void TestGetInstruction(void); 9 | static void WriteInstructionInMemory(Chip8 *chip8, uint16_t instruction); 10 | static void TestJPAddr(void); 11 | static void TestCallAddr(void); 12 | static void TestRet(void); 13 | static void TestSeVxByte(void); 14 | static void TestSneVxByte(void); 15 | static void TestSeVxVy(void); 16 | static void TestLdVxByte(void); 17 | static void TestAddVxByte(void); 18 | static void TestLdVxVy(void); 19 | static void TestOr(void); 20 | static void TestAnd(void); 21 | static void TestXor(void); 22 | static void TestAddVxVy(void); 23 | static void TestSub(void); 24 | static void TestSubn(void); 25 | static void TestShr(void); 26 | static void TestShl(void); 27 | static void TestSneVxVy(void); 28 | static void TestLdIAddr(void); 29 | static void TestJpV0Addr(void); 30 | static void TestDrw(void); 31 | static void TestSkp(void); 32 | static void TestSknp(void); 33 | static void TestLdVxK(void); 34 | static void TestLdDtVx(void); 35 | static void TestLdStVx(void); 36 | static void TestAddIVx(void); 37 | static void TestLdFVx(void); 38 | static void TestLdBVx(void); 39 | static void TestLdIVx(void); 40 | static void TestLdVxI(void); 41 | 42 | int main(void) 43 | { 44 | TestGetInstruction(); 45 | TestJPAddr(); 46 | TestCallAddr(); 47 | TestRet(); 48 | TestSeVxByte(); 49 | TestSneVxByte(); 50 | TestSeVxVy(); 51 | TestLdVxByte(); 52 | TestAddVxByte(); 53 | TestLdVxVy(); 54 | TestOr(); 55 | TestAnd(); 56 | TestXor(); 57 | TestAddVxVy(); 58 | TestSub(); 59 | TestSubn(); 60 | TestShr(); 61 | TestShl(); 62 | TestSneVxVy(); 63 | TestLdIAddr(); 64 | TestJpV0Addr(); 65 | TestDrw(); 66 | TestSkp(); 67 | TestSknp(); 68 | TestLdVxK(); 69 | TestLdDtVx(); 70 | TestLdStVx(); 71 | TestAddIVx(); 72 | TestLdFVx(); 73 | TestLdBVx(); 74 | TestLdIVx(); 75 | TestLdVxI(); 76 | 77 | return 0; 78 | } 79 | 80 | static void TestGetInstruction(void) 81 | { 82 | Chip8 chip8; 83 | 84 | Chip8_Init(&chip8); 85 | 86 | Chip8_InstructionType instruction_type; 87 | uint16_t instruction; 88 | 89 | WriteInstructionInMemory(&chip8, 0x00E0); 90 | Chip8_GetNextInstruction(&chip8, &instruction_type, &instruction); 91 | assert(instruction_type == CLS); 92 | WriteInstructionInMemory(&chip8, 0x00EE); 93 | Chip8_GetNextInstruction(&chip8, &instruction_type, &instruction); 94 | assert(instruction_type == RET); 95 | WriteInstructionInMemory(&chip8, 0x12AB); 96 | Chip8_GetNextInstruction(&chip8, &instruction_type, &instruction); 97 | assert(instruction_type == JP_ADDR); 98 | WriteInstructionInMemory(&chip8, 0x22AB); 99 | Chip8_GetNextInstruction(&chip8, &instruction_type, &instruction); 100 | assert(instruction_type == CALL_ADDR); 101 | WriteInstructionInMemory(&chip8, 0x32AB); 102 | Chip8_GetNextInstruction(&chip8, &instruction_type, &instruction); 103 | assert(instruction_type == SE_VX_BYTE); 104 | WriteInstructionInMemory(&chip8, 0x42AB); 105 | Chip8_GetNextInstruction(&chip8, &instruction_type, &instruction); 106 | assert(instruction_type == SNE_VX_BYTE); 107 | WriteInstructionInMemory(&chip8, 0x52AB); 108 | Chip8_GetNextInstruction(&chip8, &instruction_type, &instruction); 109 | assert(instruction_type == SE_VX_VY); 110 | WriteInstructionInMemory(&chip8, 0x62AB); 111 | Chip8_GetNextInstruction(&chip8, &instruction_type, &instruction); 112 | assert(instruction_type == LD_VX_BYTE); 113 | WriteInstructionInMemory(&chip8, 0x72AB); 114 | Chip8_GetNextInstruction(&chip8, &instruction_type, &instruction); 115 | assert(instruction_type == ADD_VX_BYTE); 116 | WriteInstructionInMemory(&chip8, 0x82A0); 117 | Chip8_GetNextInstruction(&chip8, &instruction_type, &instruction); 118 | assert(instruction_type == LD_VX_VY); 119 | WriteInstructionInMemory(&chip8, 0x82A1); 120 | Chip8_GetNextInstruction(&chip8, &instruction_type, &instruction); 121 | assert(instruction_type == OR); 122 | WriteInstructionInMemory(&chip8, 0x82A2); 123 | Chip8_GetNextInstruction(&chip8, &instruction_type, &instruction); 124 | assert(instruction_type == AND); 125 | WriteInstructionInMemory(&chip8, 0x82A3); 126 | Chip8_GetNextInstruction(&chip8, &instruction_type, &instruction); 127 | assert(instruction_type == XOR); 128 | WriteInstructionInMemory(&chip8, 0x82A4); 129 | Chip8_GetNextInstruction(&chip8, &instruction_type, &instruction); 130 | assert(instruction_type == ADD_VX_VY); 131 | WriteInstructionInMemory(&chip8, 0x82A5); 132 | Chip8_GetNextInstruction(&chip8, &instruction_type, &instruction); 133 | assert(instruction_type == SUB); 134 | WriteInstructionInMemory(&chip8, 0x82A6); 135 | Chip8_GetNextInstruction(&chip8, &instruction_type, &instruction); 136 | assert(instruction_type == SHR); 137 | WriteInstructionInMemory(&chip8, 0x82A7); 138 | Chip8_GetNextInstruction(&chip8, &instruction_type, &instruction); 139 | assert(instruction_type == SUBN); 140 | WriteInstructionInMemory(&chip8, 0x82AE); 141 | Chip8_GetNextInstruction(&chip8, &instruction_type, &instruction); 142 | assert(instruction_type == SHL); 143 | WriteInstructionInMemory(&chip8, 0x91F0); 144 | Chip8_GetNextInstruction(&chip8, &instruction_type, &instruction); 145 | assert(instruction_type == SNE_VX_VY); 146 | WriteInstructionInMemory(&chip8, 0xA1F0); 147 | Chip8_GetNextInstruction(&chip8, &instruction_type, &instruction); 148 | assert(instruction_type == LD_I_ADDR); 149 | WriteInstructionInMemory(&chip8, 0xB1F2); 150 | Chip8_GetNextInstruction(&chip8, &instruction_type, &instruction); 151 | assert(instruction_type == JP_V0_ADDR); 152 | WriteInstructionInMemory(&chip8, 0xC1F2); 153 | Chip8_GetNextInstruction(&chip8, &instruction_type, &instruction); 154 | assert(instruction_type == RND); 155 | WriteInstructionInMemory(&chip8, 0xD1F2); 156 | Chip8_GetNextInstruction(&chip8, &instruction_type, &instruction); 157 | assert(instruction_type == DRW); 158 | WriteInstructionInMemory(&chip8, 0xE19E); 159 | Chip8_GetNextInstruction(&chip8, &instruction_type, &instruction); 160 | assert(instruction_type == SKP); 161 | WriteInstructionInMemory(&chip8, 0xE1A1); 162 | Chip8_GetNextInstruction(&chip8, &instruction_type, &instruction); 163 | assert(instruction_type == SKNP); 164 | WriteInstructionInMemory(&chip8, 0xF107); 165 | Chip8_GetNextInstruction(&chip8, &instruction_type, &instruction); 166 | assert(instruction_type == LD_VX_DT); 167 | WriteInstructionInMemory(&chip8, 0xF10A); 168 | Chip8_GetNextInstruction(&chip8, &instruction_type, &instruction); 169 | assert(instruction_type == LD_VX_K); 170 | WriteInstructionInMemory(&chip8, 0xFB15); 171 | Chip8_GetNextInstruction(&chip8, &instruction_type, &instruction); 172 | assert(instruction_type == LD_DT_VX); 173 | WriteInstructionInMemory(&chip8, 0xFB18); 174 | Chip8_GetNextInstruction(&chip8, &instruction_type, &instruction); 175 | assert(instruction_type == LD_ST_VX); 176 | WriteInstructionInMemory(&chip8, 0xFB1E); 177 | Chip8_GetNextInstruction(&chip8, &instruction_type, &instruction); 178 | assert(instruction_type == ADD_I_VX); 179 | WriteInstructionInMemory(&chip8, 0xFB29); 180 | Chip8_GetNextInstruction(&chip8, &instruction_type, &instruction); 181 | assert(instruction_type == LD_F_VX); 182 | WriteInstructionInMemory(&chip8, 0xFB33); 183 | Chip8_GetNextInstruction(&chip8, &instruction_type, &instruction); 184 | assert(instruction_type == LD_B_VX); 185 | WriteInstructionInMemory(&chip8, 0xFB55); 186 | Chip8_GetNextInstruction(&chip8, &instruction_type, &instruction); 187 | assert(instruction_type == LD_I_VX); 188 | WriteInstructionInMemory(&chip8, 0xFB65); 189 | Chip8_GetNextInstruction(&chip8, &instruction_type, &instruction); 190 | assert(instruction_type == LD_VX_I); 191 | WriteInstructionInMemory(&chip8, 0xFB12); 192 | Chip8_GetNextInstruction(&chip8, &instruction_type, &instruction); 193 | assert(instruction_type == UNKNOWN_INSTRUCTION); 194 | } 195 | 196 | static void WriteInstructionInMemory(Chip8 *chip8, uint16_t instruction) 197 | { 198 | chip8->mem[chip8->pc] = instruction >> 8; 199 | chip8->mem[chip8->pc + 1] = instruction & 0x00FF; 200 | chip8->program_len = 2; 201 | } 202 | 203 | static void TestJPAddr(void) 204 | { 205 | Chip8 chip8; 206 | 207 | Chip8_Init(&chip8); 208 | Chip8_ExecuteInstruction(&chip8, JP_ADDR, 0x12E); 209 | 210 | assert(chip8.pc == 0x12E); 211 | 212 | Chip8_ExecuteInstruction(&chip8, JP_ADDR, 0x3FF); 213 | 214 | assert(chip8.pc == 0x3FF); 215 | } 216 | 217 | static void TestCallAddr(void) 218 | { 219 | Chip8 chip8; 220 | 221 | Chip8_Init(&chip8); 222 | Chip8_ExecuteInstruction(&chip8, JP_ADDR, 0x12E); 223 | 224 | assert(chip8.pc == 0x12E); 225 | 226 | Chip8_ExecuteInstruction(&chip8, CALL_ADDR, 0x3FF); 227 | 228 | assert(chip8.pc == 0x3FF); 229 | assert(chip8.sp == 1); 230 | assert(chip8.stack[0] == 0x12E); 231 | 232 | Chip8_ExecuteInstruction(&chip8, CALL_ADDR, 0x4AB); 233 | 234 | assert(chip8.pc == 0x4AB); 235 | assert(chip8.sp == 2); 236 | assert(chip8.stack[0] == 0x12E); 237 | assert(chip8.stack[1] == 0x3FF); 238 | } 239 | 240 | static void TestRet(void) 241 | { 242 | Chip8 chip8; 243 | 244 | Chip8_Init(&chip8); 245 | Chip8_ExecuteInstruction(&chip8, JP_ADDR, 0x12E); 246 | Chip8_ExecuteInstruction(&chip8, CALL_ADDR, 0x3FF); 247 | Chip8_ExecuteInstruction(&chip8, CALL_ADDR, 0x4AB); 248 | 249 | assert(chip8.pc == 0x4AB); 250 | 251 | Chip8_ExecuteInstruction(&chip8, RET, 0x0); 252 | 253 | assert(chip8.pc == 0x3FF); 254 | assert(chip8.sp == 1); 255 | 256 | Chip8_ExecuteInstruction(&chip8, RET, 0x0); 257 | 258 | assert(chip8.pc == 0x12E); 259 | assert(chip8.sp == 0); 260 | } 261 | 262 | static void TestSeVxByte(void) 263 | { 264 | Chip8 chip8; 265 | 266 | Chip8_Init(&chip8); 267 | 268 | chip8.v[0x3] = 0x42; 269 | chip8.v[0xA] = 0x24; 270 | 271 | uint16_t ret = Chip8_ExecuteInstruction(&chip8, SE_VX_BYTE, 0x342); 272 | 273 | assert(ret == 4); 274 | 275 | ret = Chip8_ExecuteInstruction(&chip8, SE_VX_BYTE, 0xA42); 276 | 277 | assert(ret == 2); 278 | 279 | ret = Chip8_ExecuteInstruction(&chip8, SE_VX_BYTE, 0xA24); 280 | 281 | assert(ret == 4); 282 | } 283 | 284 | static void TestSneVxByte(void) 285 | { 286 | Chip8 chip8; 287 | 288 | Chip8_Init(&chip8); 289 | 290 | chip8.v[0x3] = 0x42; 291 | chip8.v[0xA] = 0x24; 292 | 293 | uint16_t ret = Chip8_ExecuteInstruction(&chip8, SNE_VX_BYTE, 0x342); 294 | 295 | assert(ret == 2); 296 | 297 | ret = Chip8_ExecuteInstruction(&chip8, SNE_VX_BYTE, 0xA42); 298 | 299 | assert(ret == 4); 300 | 301 | ret = Chip8_ExecuteInstruction(&chip8, SNE_VX_BYTE, 0xA24); 302 | 303 | assert(ret == 2); 304 | } 305 | 306 | static void TestSeVxVy(void) 307 | { 308 | Chip8 chip8; 309 | 310 | Chip8_Init(&chip8); 311 | 312 | chip8.v[0x3] = 0x42; 313 | chip8.v[0xA] = 0x24; 314 | chip8.v[0xF] = 0x42; 315 | 316 | uint16_t ret = Chip8_ExecuteInstruction(&chip8, SE_VX_VY, 0x3A0); 317 | 318 | assert(ret == 2); 319 | 320 | ret = Chip8_ExecuteInstruction(&chip8, SE_VX_VY, 0x3F0); 321 | 322 | assert(ret == 4); 323 | 324 | ret = Chip8_ExecuteInstruction(&chip8, SE_VX_VY, 0xAF0); 325 | 326 | assert(ret == 2); 327 | } 328 | 329 | static void TestLdVxByte(void) 330 | { 331 | Chip8 chip8; 332 | 333 | Chip8_Init(&chip8); 334 | 335 | uint16_t ret = Chip8_ExecuteInstruction(&chip8, LD_VX_BYTE, 0xF42); 336 | 337 | assert(ret == 2); 338 | assert(chip8.v[0xF] == 0x42); 339 | 340 | ret = Chip8_ExecuteInstruction(&chip8, LD_VX_BYTE, 0x2FF); 341 | 342 | assert(ret == 2); 343 | assert(chip8.v[0x2] == 0xFF); 344 | 345 | ret = Chip8_ExecuteInstruction(&chip8, LD_VX_BYTE, 0x212); 346 | 347 | assert(ret == 2); 348 | assert(chip8.v[0x2] == 0x12); 349 | } 350 | 351 | static void TestAddVxByte(void) 352 | { 353 | Chip8 chip8; 354 | 355 | Chip8_Init(&chip8); 356 | 357 | chip8.v[0xB] = 0x42; 358 | 359 | uint16_t ret = Chip8_ExecuteInstruction(&chip8, ADD_VX_BYTE, 0xB10); 360 | 361 | assert(ret == 2); 362 | assert(chip8.v[0xB] == 0x52); 363 | 364 | ret = Chip8_ExecuteInstruction(&chip8, ADD_VX_BYTE, 0xB24); 365 | 366 | assert(ret == 2); 367 | assert(chip8.v[0xB] == 0x76); 368 | } 369 | 370 | static void TestLdVxVy(void) 371 | { 372 | Chip8 chip8; 373 | 374 | Chip8_Init(&chip8); 375 | 376 | chip8.v[0xA] = 0x42; 377 | chip8.v[0xF] = 0x2F; 378 | 379 | uint16_t ret = Chip8_ExecuteInstruction(&chip8, LD_VX_VY, 0xBA0); 380 | 381 | assert(ret == 2); 382 | assert(chip8.v[0xB] == 0x42); 383 | 384 | ret = Chip8_ExecuteInstruction(&chip8, LD_VX_VY, 0xAF0); 385 | 386 | assert(ret == 2); 387 | assert(chip8.v[0xA] == 0x2F); 388 | 389 | ret = Chip8_ExecuteInstruction(&chip8, LD_VX_VY, 0xBA0); 390 | 391 | assert(ret == 2); 392 | assert(chip8.v[0xB] == 0x2F); 393 | } 394 | 395 | static void TestOr(void) 396 | { 397 | Chip8 chip8; 398 | 399 | Chip8_Init(&chip8); 400 | 401 | chip8.v[0x1] = 0b10101010; 402 | chip8.v[0x2] = 0b01010101; 403 | 404 | uint16_t ret = Chip8_ExecuteInstruction(&chip8, OR, 0x120); 405 | 406 | assert(ret == 2); 407 | assert(chip8.v[0x1] == 0xFF); 408 | } 409 | 410 | static void TestAnd(void) 411 | { 412 | Chip8 chip8; 413 | 414 | Chip8_Init(&chip8); 415 | 416 | chip8.v[0x1] = 0b10101010; 417 | chip8.v[0x2] = 0b01010101; 418 | 419 | uint16_t ret = Chip8_ExecuteInstruction(&chip8, AND, 0x120); 420 | 421 | assert(ret == 2); 422 | assert(chip8.v[0x1] == 0x0); 423 | } 424 | 425 | static void TestXor(void) 426 | { 427 | Chip8 chip8; 428 | 429 | Chip8_Init(&chip8); 430 | 431 | chip8.v[0x1] = 0b10101010; 432 | chip8.v[0x2] = 0b01010101; 433 | 434 | uint16_t ret = Chip8_ExecuteInstruction(&chip8, XOR, 0x120); 435 | 436 | assert(ret == 2); 437 | assert(chip8.v[0x1] == (0b10101010 ^ 0b01010101)); 438 | } 439 | 440 | static void TestAddVxVy(void) 441 | { 442 | Chip8 chip8; 443 | 444 | Chip8_Init(&chip8); 445 | 446 | chip8.v[0x1] = 0x2; 447 | chip8.v[0x2] = 0xF0; 448 | chip8.v[0x3] = 0xF7; 449 | 450 | uint16_t ret = Chip8_ExecuteInstruction(&chip8, ADD_VX_VY, 0x120); 451 | 452 | assert(ret == 2); 453 | assert(chip8.v[0x1] == 0xF2); 454 | assert(chip8.v[0xF] == 0); 455 | 456 | ret = Chip8_ExecuteInstruction(&chip8, ADD_VX_VY, 0x130); 457 | uint8_t res = (0xF2 + 0xF7) & 0xFF; 458 | 459 | assert(ret == 2); 460 | assert(chip8.v[0x1] == res); 461 | assert(chip8.v[0xF] == 1); 462 | } 463 | 464 | static void TestSub(void) 465 | { 466 | Chip8 chip8; 467 | 468 | Chip8_Init(&chip8); 469 | 470 | chip8.v[0x1] = 0x2; 471 | chip8.v[0x2] = 0xF0; 472 | chip8.v[0x3] = 0xF7; 473 | 474 | uint16_t ret = Chip8_ExecuteInstruction(&chip8, SUB, 0x210); 475 | 476 | assert(ret == 2); 477 | assert(chip8.v[0x2] == 0xF0 - 0x2); 478 | assert(chip8.v[0xF] == 1); 479 | 480 | ret = Chip8_ExecuteInstruction(&chip8, SUB, 0x230); 481 | uint8_t res = 0xEE - 0xF7; 482 | 483 | assert(ret == 2); 484 | assert(chip8.v[0x2] == res); 485 | assert(chip8.v[0xF] == 0); 486 | } 487 | 488 | static void TestSubn(void) 489 | { 490 | Chip8 chip8; 491 | 492 | Chip8_Init(&chip8); 493 | 494 | chip8.v[0x1] = 0x2; 495 | chip8.v[0x2] = 0xF0; 496 | chip8.v[0x3] = 0xF7; 497 | 498 | uint16_t ret = Chip8_ExecuteInstruction(&chip8, SUBN, 0x210); 499 | uint8_t res = 0x2 - 0xF0; 500 | 501 | assert(ret == 2); 502 | assert(chip8.v[0x2] == res); 503 | assert(chip8.v[0xF] == 0); 504 | 505 | ret = Chip8_ExecuteInstruction(&chip8, SUBN, 0x230); 506 | 507 | assert(ret == 2); 508 | assert(chip8.v[0x2] == 0xF7 - res); 509 | assert(chip8.v[0xF] == 1); 510 | } 511 | 512 | static void TestShr(void) 513 | { 514 | Chip8 chip8; 515 | 516 | Chip8_Init(&chip8); 517 | 518 | chip8.v[0x1] = 0x4; 519 | chip8.v[0x2] = 0x43; 520 | 521 | uint16_t ret = Chip8_ExecuteInstruction(&chip8, SHR, 0x100); 522 | 523 | assert(ret == 2); 524 | assert(chip8.v[0x1] == 0x2); 525 | assert(chip8.v[0xF] == 0); 526 | 527 | ret = Chip8_ExecuteInstruction(&chip8, SHR, 0x200); 528 | 529 | assert(ret == 2); 530 | assert(chip8.v[0x2] == 0x21); 531 | assert(chip8.v[0xF] == 1); 532 | } 533 | 534 | static void TestShl(void) 535 | { 536 | Chip8 chip8; 537 | 538 | Chip8_Init(&chip8); 539 | 540 | chip8.v[0x1] = 0x4; 541 | chip8.v[0x2] = 0xA2; 542 | 543 | uint16_t ret = Chip8_ExecuteInstruction(&chip8, SHL, 0x100); 544 | 545 | assert(ret == 2); 546 | assert(chip8.v[0x1] == 0x8); 547 | assert(chip8.v[0xF] == 0); 548 | 549 | ret = Chip8_ExecuteInstruction(&chip8, SHL, 0x200); 550 | uint8_t res = 0xA2 * 2; 551 | 552 | assert(ret == 2); 553 | assert(chip8.v[0x2] == res); 554 | assert(chip8.v[0xF] == 1); 555 | } 556 | 557 | static void TestSneVxVy(void) 558 | { 559 | Chip8 chip8; 560 | 561 | Chip8_Init(&chip8); 562 | 563 | chip8.v[0x3] = 0x42; 564 | chip8.v[0xA] = 0x24; 565 | chip8.v[0xD] = 0x24; 566 | 567 | uint16_t ret = Chip8_ExecuteInstruction(&chip8, SNE_VX_VY, 0x3A0); 568 | 569 | assert(ret == 4); 570 | 571 | ret = Chip8_ExecuteInstruction(&chip8, SNE_VX_VY, 0xAD0); 572 | 573 | assert(ret == 2); 574 | 575 | ret = Chip8_ExecuteInstruction(&chip8, SNE_VX_VY, 0xD30); 576 | 577 | assert(ret == 4); 578 | } 579 | 580 | static void TestLdIAddr(void) 581 | { 582 | Chip8 chip8; 583 | 584 | Chip8_Init(&chip8); 585 | 586 | uint16_t ret = Chip8_ExecuteInstruction(&chip8, LD_I_ADDR, 0xABC); 587 | 588 | assert(ret == 2); 589 | assert(chip8.i == 0xABC); 590 | } 591 | 592 | static void TestJpV0Addr(void) 593 | { 594 | Chip8 chip8; 595 | 596 | Chip8_Init(&chip8); 597 | 598 | chip8.v[0] = 0xF0; 599 | 600 | uint16_t ret = Chip8_ExecuteInstruction(&chip8, JP_V0_ADDR, 0xABC); 601 | 602 | assert(ret == 0); 603 | assert(chip8.pc == 0xABC + 0xF0); 604 | } 605 | 606 | static void TestDrw(void) 607 | { 608 | Chip8 chip8; 609 | 610 | Chip8_Init(&chip8); 611 | 612 | // sprite in memory (0 digit) 613 | chip8.mem[0x100] = 0xF0; 614 | chip8.mem[0x101] = 0x90; 615 | chip8.mem[0x102] = 0x90; 616 | chip8.mem[0x103] = 0x90; 617 | chip8.mem[0x104] = 0xF0; 618 | 619 | // sprite in memory (1 digit) 620 | chip8.mem[0x105] = 0x20; 621 | chip8.mem[0x106] = 0x60; 622 | chip8.mem[0x107] = 0x20; 623 | chip8.mem[0x108] = 0x20; 624 | chip8.mem[0x109] = 0x70; 625 | 626 | chip8.v[0x1] = 0x10; 627 | chip8.v[0x2] = 0x10; 628 | chip8.v[0x3] = 0x20; 629 | 630 | chip8.i = 0x100; // address to load the 0 digit sprite from 631 | 632 | uint16_t ret = Chip8_ExecuteInstruction(&chip8, DRW, 0x125); 633 | 634 | assert(ret == 2); 635 | assert(chip8.v[0xF] == 0); // no collision 636 | 637 | // should draw the 0 digit 638 | unsigned int pos = (0x10 * DISPLAY_WIDTH + 0x10) / 8; 639 | 640 | assert(chip8.display[pos] == 0xF0); 641 | pos = (0x11 * DISPLAY_WIDTH + 0x10) / 8; 642 | assert(chip8.display[pos] == 0x90); 643 | pos = (0x12 * DISPLAY_WIDTH + 0x10) / 8; 644 | assert(chip8.display[pos] == 0x90); 645 | pos = (0x13 * DISPLAY_WIDTH + 0x10) / 8; 646 | assert(chip8.display[pos] == 0x90); 647 | pos = (0x14 * DISPLAY_WIDTH + 0x10) / 8; 648 | assert(chip8.display[pos] == 0xF0); 649 | 650 | chip8.i = 0x105; // address to load the 1 digit sprite from 651 | 652 | ret = Chip8_ExecuteInstruction(&chip8, DRW, 0x325); 653 | 654 | assert(ret == 2); 655 | assert(chip8.v[0xF] == 0); // no collision 656 | 657 | pos = (0x10 * DISPLAY_WIDTH + 0x20) / 8; 658 | 659 | // should draw the 1 digit 660 | assert(chip8.display[pos] == 0x20); 661 | pos = (0x11 * DISPLAY_WIDTH + 0x20) / 8; 662 | assert(chip8.display[pos] == 0x60); 663 | pos = (0x12 * DISPLAY_WIDTH + 0x20) / 8; 664 | assert(chip8.display[pos] == 0x20); 665 | pos = (0x13 * DISPLAY_WIDTH + 0x20) / 8; 666 | assert(chip8.display[pos] == 0x20); 667 | pos = (0x14 * DISPLAY_WIDTH + 0x20) / 8; 668 | assert(chip8.display[pos] == 0x70); 669 | 670 | // should still draw the 0 digit 671 | pos = (0x10 * DISPLAY_WIDTH + 0x10) / 8; 672 | 673 | assert(chip8.display[pos] == 0xF0); 674 | pos = (0x11 * DISPLAY_WIDTH + 0x10) / 8; 675 | assert(chip8.display[pos] == 0x90); 676 | pos = (0x12 * DISPLAY_WIDTH + 0x10) / 8; 677 | assert(chip8.display[pos] == 0x90); 678 | pos = (0x13 * DISPLAY_WIDTH + 0x10) / 8; 679 | assert(chip8.display[pos] == 0x90); 680 | pos = (0x14 * DISPLAY_WIDTH + 0x10) / 8; 681 | assert(chip8.display[pos] == 0xF0); 682 | 683 | // draw another 1 digit on top of the 0 digit 684 | ret = Chip8_ExecuteInstruction(&chip8, DRW, 0x125); 685 | 686 | assert(ret == 2); 687 | assert(chip8.v[0xF] == 1); // collision 688 | 689 | pos = (0x10 * DISPLAY_WIDTH + 0x10) / 8; 690 | assert(chip8.display[pos] == (0xF0 ^ 0x20)); 691 | pos = (0x11 * DISPLAY_WIDTH + 0x10) / 8; 692 | assert(chip8.display[pos] == (0x90 ^ 0x60)); 693 | pos = (0x12 * DISPLAY_WIDTH + 0x10) / 8; 694 | assert(chip8.display[pos] == (0x90 ^ 0x20)); 695 | pos = (0x13 * DISPLAY_WIDTH + 0x10) / 8; 696 | assert(chip8.display[pos] == (0x90 ^ 0x20)); 697 | pos = (0x14 * DISPLAY_WIDTH + 0x10) / 8; 698 | assert(chip8.display[pos] == (0xF0 ^ 0x70)); 699 | } 700 | 701 | static uint8_t keys[0xF] = {0}; 702 | 703 | static uint16_t TestGetKeys(void) 704 | { 705 | uint16_t keys_bits = 0; 706 | 707 | for (int i = 0; i < 0xF; i++) 708 | { 709 | keys_bits |= (keys[i] << (0xF - i)); 710 | } 711 | 712 | return keys_bits; 713 | } 714 | 715 | static void TestSkp(void) 716 | { 717 | Chip8 chip8; 718 | 719 | Chip8_Init(&chip8); 720 | 721 | chip8.get_keys = TestGetKeys; 722 | chip8.v[0x3] = 0xA; 723 | chip8.v[0x4] = 0xB; 724 | 725 | keys[0xB] = 1; 726 | 727 | uint16_t ret = Chip8_ExecuteInstruction(&chip8, SKP, 0x300); 728 | 729 | assert(ret == 2); 730 | 731 | ret = Chip8_ExecuteInstruction(&chip8, SKP, 0x400); 732 | 733 | assert(ret == 4); 734 | } 735 | 736 | static void TestSknp(void) 737 | { 738 | Chip8 chip8; 739 | 740 | Chip8_Init(&chip8); 741 | 742 | chip8.get_keys = TestGetKeys; 743 | chip8.v[0x3] = 0xA; 744 | chip8.v[0x4] = 0xB; 745 | 746 | keys[0xB] = 1; 747 | 748 | uint16_t ret = Chip8_ExecuteInstruction(&chip8, SKNP, 0x300); 749 | 750 | assert(ret == 4); 751 | 752 | ret = Chip8_ExecuteInstruction(&chip8, SKNP, 0x400); 753 | 754 | assert(ret == 2); 755 | } 756 | 757 | static void TestLdVxK(void) 758 | { 759 | memset(keys, 0, sizeof(keys)); 760 | 761 | Chip8 chip8; 762 | 763 | Chip8_Init(&chip8); 764 | 765 | chip8.get_keys = TestGetKeys; 766 | 767 | assert(Chip8_ExecuteInstruction(&chip8, LD_VX_K, 0xE00) == 0); 768 | 769 | keys[0x2] = 1; 770 | 771 | assert(Chip8_ExecuteInstruction(&chip8, LD_VX_K, 0xE00) == 2); 772 | } 773 | 774 | static void TestLdDtVx(void) 775 | { 776 | Chip8 chip8; 777 | 778 | Chip8_Init(&chip8); 779 | 780 | chip8.v[0x1] = 1; 781 | chip8.v[0x2] = 2; 782 | 783 | assert(chip8.dt == 0); 784 | 785 | uint16_t ret = Chip8_ExecuteInstruction(&chip8, LD_DT_VX, 0x100); 786 | 787 | assert(ret == 2); 788 | assert(chip8.dt == 1); 789 | 790 | ret = Chip8_ExecuteInstruction(&chip8, LD_DT_VX, 0x200); 791 | 792 | assert(ret == 2); 793 | assert(chip8.dt == 2); 794 | } 795 | 796 | static void TestLdStVx(void) 797 | { 798 | Chip8 chip8; 799 | 800 | Chip8_Init(&chip8); 801 | 802 | chip8.v[0x1] = 1; 803 | chip8.v[0x2] = 2; 804 | 805 | assert(chip8.st == 0); 806 | 807 | uint16_t ret = Chip8_ExecuteInstruction(&chip8, LD_ST_VX, 0x100); 808 | 809 | assert(ret == 2); 810 | assert(chip8.st == 1); 811 | 812 | ret = Chip8_ExecuteInstruction(&chip8, LD_ST_VX, 0x200); 813 | 814 | assert(ret == 2); 815 | assert(chip8.st == 2); 816 | } 817 | 818 | static void TestAddIVx(void) 819 | { 820 | Chip8 chip8; 821 | 822 | Chip8_Init(&chip8); 823 | 824 | chip8.v[0x1] = 0x10; 825 | chip8.v[0x2] = 0x20; 826 | 827 | uint16_t ret = Chip8_ExecuteInstruction(&chip8, ADD_I_VX, 0x100); 828 | 829 | assert(ret == 2); 830 | assert(chip8.i == 0x10); 831 | 832 | ret = Chip8_ExecuteInstruction(&chip8, ADD_I_VX, 0x200); 833 | 834 | assert(ret == 2); 835 | assert(chip8.i == 0x30); 836 | } 837 | 838 | static void TestLdFVx(void) 839 | { 840 | Chip8 chip8; 841 | 842 | Chip8_Init(&chip8); 843 | 844 | chip8.v[0x1] = 0x1; 845 | chip8.v[0x3] = 0xA; 846 | 847 | uint16_t ret = Chip8_ExecuteInstruction(&chip8, LD_F_VX, 0x100); 848 | 849 | assert(ret == 2); 850 | assert(chip8.i == SPRITE_SIZE); 851 | 852 | ret = Chip8_ExecuteInstruction(&chip8, LD_F_VX, 0x200); 853 | 854 | assert(ret == 2); 855 | assert(chip8.i == 0); 856 | 857 | ret = Chip8_ExecuteInstruction(&chip8, LD_F_VX, 0x300); 858 | 859 | assert(ret == 2); 860 | assert(chip8.i == SPRITE_SIZE * 0xA); 861 | } 862 | 863 | static void TestLdBVx(void) 864 | { 865 | Chip8 chip8; 866 | 867 | Chip8_Init(&chip8); 868 | 869 | chip8.i = 0x250; 870 | 871 | chip8.v[0xB] = 242; 872 | 873 | uint16_t ret = Chip8_ExecuteInstruction(&chip8, LD_B_VX, 0xB00); 874 | 875 | assert(ret == 2); 876 | assert(chip8.mem[0x250] == 2); 877 | assert(chip8.mem[0x251] == 4); 878 | assert(chip8.mem[0x252] == 2); 879 | } 880 | 881 | static void TestLdIVx(void) 882 | { 883 | Chip8 chip8; 884 | 885 | Chip8_Init(&chip8); 886 | 887 | chip8.i = 0x250; 888 | 889 | chip8.v[0x0] = 0x10; 890 | chip8.v[0x1] = 0x20; 891 | chip8.v[0x2] = 0x30; 892 | chip8.v[0x3] = 0x40; 893 | chip8.v[0x4] = 0x50; 894 | chip8.v[0x5] = 0x60; 895 | 896 | uint16_t ret = Chip8_ExecuteInstruction(&chip8, LD_I_VX, 0x200); 897 | 898 | assert(ret == 2); 899 | assert(chip8.mem[0x250] == 0x10); 900 | assert(chip8.mem[0x251] == 0x20); 901 | assert(chip8.mem[0x252] == 0x30); 902 | assert(chip8.mem[0x253] == 0x0); 903 | assert(chip8.mem[0x254] == 0x0); 904 | assert(chip8.mem[0x255] == 0x0); 905 | 906 | ret = Chip8_ExecuteInstruction(&chip8, LD_I_VX, 0x400); 907 | 908 | assert(ret == 2); 909 | assert(chip8.mem[0x250] == 0x10); 910 | assert(chip8.mem[0x251] == 0x20); 911 | assert(chip8.mem[0x252] == 0x30); 912 | assert(chip8.mem[0x253] == 0x40); 913 | assert(chip8.mem[0x254] == 0x50); 914 | assert(chip8.mem[0x255] == 0x0); 915 | } 916 | 917 | static void TestLdVxI(void) 918 | { 919 | Chip8 chip8; 920 | 921 | Chip8_Init(&chip8); 922 | 923 | chip8.i = 0x250; 924 | 925 | chip8.mem[0x250] = 0x10; 926 | chip8.mem[0x251] = 0x20; 927 | chip8.mem[0x252] = 0x30; 928 | chip8.mem[0x253] = 0x40; 929 | chip8.mem[0x254] = 0x50; 930 | chip8.mem[0x255] = 0x60; 931 | 932 | uint16_t ret = Chip8_ExecuteInstruction(&chip8, LD_VX_I, 0x200); 933 | 934 | assert(ret == 2); 935 | assert(chip8.v[0x0] == 0x10); 936 | assert(chip8.v[0x1] == 0x20); 937 | assert(chip8.v[0x2] == 0x30); 938 | assert(chip8.v[0x3] == 0x0); 939 | assert(chip8.v[0x4] == 0x0); 940 | assert(chip8.v[0x5] == 0x0); 941 | } 942 | --------------------------------------------------------------------------------