chip8

CHIP-8 emulator
git clone git://git.christosmarg.xyz/chip8.git
Log | Files | Refs | README | LICENSE

commit 88bebe70bbf6950b64d83c69c1c86c8c18d21e00
parent 097af219ccf7d31216828178597065c3b130764c
Author: Christos Margiolis <christos@margiolis.net>
Date:   Wed, 28 Apr 2021 17:38:43 +0300

added warn() and made minor stylistic changes

Diffstat:
MLICENSE | 41++++++++++++++++-------------------------
MREADME.md | 15+++------------
Mchip8.1 | 2+-
Mchip8.c | 814++++++++++++++++++++++++++++++++++++++++---------------------------------------
Mconfig.mk | 7+++----
5 files changed, 437 insertions(+), 442 deletions(-)

diff --git a/LICENSE b/LICENSE @@ -1,29 +1,20 @@ -BSD 3-Clause License +MIT License -Copyright (c) 2020-present, Christos Margiolis. -All rights reserved. +(c) 2020-Present Christos Margiolis <christos@christosmarg.xyz> -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the “Software”), to deal in +the Software without restriction, including without limitation the rights to use, +copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the +Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: -* Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. -* Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -* Neither the name of images.weserv.nl nor the names of its - contributors may be used to endorse or promote products derived from - this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/README.md b/README.md @@ -1,23 +1,14 @@ # CHIP-8 Emulator -Your typical CHIP-8 copy. This one uses SDL2 as a rendering API. - -## Dependencies - -* `make` -* `SDL2` +CHIP-8 emulator using SDL2. ## Usage ```shell -$ cd path/to/chip8 $ make -$ ./chip8 [../path/to/ROM] -$ make clean # optional +# make install clean ``` - -You can install `chip8` by running `sudo make install clean`. -The binary will be installed at `/usr/local/bin` +CHIP-8 will be installed at `/usr/local/bin` ## Screenshots diff --git a/chip8.1 b/chip8.1 @@ -6,7 +6,7 @@ .Nd a minimal CHIP\-8 emulator .Sh SYNOPSIS .Nm -.Op CHIP8_FILE +.Ar rom .Sh DESCRIPTION .Nm reads a CHIP\-8 ROM and executes it. It uses the SDL2 diff --git a/chip8.c b/chip8.c @@ -9,341 +9,333 @@ #ifdef _WIN_32 #include <Windows.h> -typedef unsigned int u_int32_t +typedef unsigned int u_int32_t typedef unsigned short u_int16_t -typedef unsigned char u_int8_t +typedef unsigned char u_int8_t #else /* !_WIN_32 */ #include <sys/types.h> #include <unistd.h> #endif /* _WIN_32 */ -#define VX_MASK(x) ((x & 0x0f00) >> 8) -#define VY_MASK(x) ((x & 0x00f0) >> 4) -#define NN_MASK(x) (x & 0x00ff) -#define NNN_MASK(x) (x & 0x0fff) -#define ROM_SIZE_MAX (4096 - 512) -#define EXECUTE(pc) do { pc += 2; } while (0) - -struct Chip8 { - u_int16_t I; - u_int16_t opcode; - u_int16_t pc; - u_int16_t sp; - u_int16_t stack[16]; - u_int8_t delaytimer; - u_int8_t drawflag; - u_int8_t gfx[64 * 32]; - u_int8_t keys[16]; - u_int8_t memory[4096]; - u_int8_t soundtimer; - u_int8_t V[16]; +#define VX_MASK(x) ((x & 0x0f00) >> 8) +#define VY_MASK(x) ((x & 0x00f0) >> 4) +#define NN_MASK(x) (x & 0x00ff) +#define NNN_MASK(x) (x & 0x0fff) +#define EXECUTE(pc) do { pc += 2; } while (0) + +struct chip8 { + u_int16_t stack[16]; + u_int16_t I; + u_int16_t opcode; + u_int16_t pc; + u_int16_t sp; + u_int8_t mem[4096]; + u_int8_t gfx[64 * 32]; + u_int8_t V[16]; + u_int8_t keys[16]; + u_int8_t delaytimer; + u_int8_t soundtimer; + u_int8_t drawflag; }; +static void chip8_init(struct chip8 *); +static void romload(struct chip8 *, const char *); +static void emulate(struct chip8 *); +static int decode(struct chip8 *); +static int evhandle(struct chip8 *); +static void render(SDL_Renderer *, SDL_Texture *, struct chip8 *); +static void warn(const char *, ...); +static void die(const char *, ...); + +static char *argv0; static const u_int8_t keymap[16] = { - SDLK_1, SDLK_2, SDLK_3, SDLK_4, - SDLK_q, SDLK_w, SDLK_e, SDLK_r, - SDLK_a, SDLK_s, SDLK_d, SDLK_f, - SDLK_z, SDLK_x, SDLK_c, SDLK_v + SDLK_1, SDLK_2, SDLK_3, SDLK_4, + SDLK_q, SDLK_w, SDLK_e, SDLK_r, + SDLK_a, SDLK_s, SDLK_d, SDLK_f, + SDLK_z, SDLK_x, SDLK_c, SDLK_v }; -static void chip8_init(struct Chip8 *); -static void rom_load(struct Chip8 *, const char *); -static void emulate(struct Chip8 *); -static int decode(struct Chip8 *); -static void timers_update(struct Chip8 *); -static int evts(struct Chip8 *); -static void render(SDL_Renderer *, SDL_Texture *, struct Chip8 *); -static void die(const char *, ...); - -#define I chip8->I -#define opcode chip8->opcode -#define pc chip8->pc -#define sp chip8->sp -#define stack chip8->stack -#define delaytimer chip8->delaytimer -#define drawflag chip8->drawflag -#define gfx chip8->gfx -#define keys chip8->keys -#define memory chip8->memory -#define soundtimer chip8->soundtimer -#define V chip8->V - -void -chip8_init(struct Chip8 *chip8) -{ - int i; - u_int8_t fontset[80] = { - 0xF0, 0x90, 0x90, 0x90, 0xF0, // 0 - 0x20, 0x60, 0x20, 0x20, 0x70, // 1 - 0xF0, 0x10, 0xF0, 0x80, 0xF0, // 2 - 0xF0, 0x10, 0xF0, 0x10, 0xF0, // 3 - 0x90, 0x90, 0xF0, 0x10, 0x10, // 4 - 0xF0, 0x80, 0xF0, 0x10, 0xF0, // 5 - 0xF0, 0x80, 0xF0, 0x90, 0xF0, // 6 - 0xF0, 0x10, 0x20, 0x40, 0x40, // 7 - 0xF0, 0x90, 0xF0, 0x90, 0xF0, // 8 - 0xF0, 0x90, 0xF0, 0x10, 0xF0, // 9 - 0xF0, 0x90, 0xF0, 0x90, 0x90, // A - 0xE0, 0x90, 0xE0, 0x90, 0xE0, // B - 0xF0, 0x80, 0x80, 0x80, 0xF0, // C - 0xE0, 0x90, 0x90, 0x90, 0xE0, // D - 0xF0, 0x80, 0xF0, 0x80, 0xF0, // E - 0xF0, 0x80, 0xF0, 0x80, 0x80 // F - }; - pc = 0x200; - opcode = 0; - I = 0; - sp = 0; - delaytimer = 0; - soundtimer = 0; - memset(V, 0, 16 * sizeof(u_int8_t)); - memset(keys, 0, 16 * sizeof(u_int8_t)); - memset(stack, 0, 16 * sizeof(u_int16_t)); - memset(gfx, 0, 2048 * sizeof(u_int8_t)); - memset(memory, 0, 4096 * sizeof(u_int8_t)); - for (i = 0; i < 80; memory[i] = fontset[i], i++) - ; -} - -void -rom_load(struct Chip8 *chip8, const char *fpath) +#define stack chip8->stack +#define I chip8->I +#define opcode chip8->opcode +#define pc chip8->pc +#define sp chip8->sp +#define mem chip8->mem +#define gfx chip8->gfx +#define V chip8->V +#define keys chip8->keys +#define delaytimer chip8->delaytimer +#define soundtimer chip8->soundtimer +#define drawflag chip8->drawflag + +static void +chip8_init(struct chip8 *chip8) { - FILE *rom; - size_t res; - long romsize; - int i; - char *buf; - - if ((rom = fopen(fpath, "rb")) == NULL) - die("error loading ROM (%s).", fpath); - fseek(rom, 0, SEEK_END); - romsize = ftell(rom); - rewind(rom); - - if ((buf = malloc(romsize * sizeof(char))) == NULL) - die("cannot allocate memory."); - if ((res = fread(buf, sizeof(char), (size_t)romsize, rom)) != romsize) - die("error reading ROM."); - if (romsize < ROM_SIZE_MAX) - for (i = 0; i < romsize; i++) - memory[i + 512] = (u_int8_t)buf[i]; - else - die("ROM cannot fit into memory."); - - fclose(rom); - free(buf); + u_int8_t fontset[80] = { + 0xF0, 0x90, 0x90, 0x90, 0xF0, // 0 + 0x20, 0x60, 0x20, 0x20, 0x70, // 1 + 0xF0, 0x10, 0xF0, 0x80, 0xF0, // 2 + 0xF0, 0x10, 0xF0, 0x10, 0xF0, // 3 + 0x90, 0x90, 0xF0, 0x10, 0x10, // 4 + 0xF0, 0x80, 0xF0, 0x10, 0xF0, // 5 + 0xF0, 0x80, 0xF0, 0x90, 0xF0, // 6 + 0xF0, 0x10, 0x20, 0x40, 0x40, // 7 + 0xF0, 0x90, 0xF0, 0x90, 0xF0, // 8 + 0xF0, 0x90, 0xF0, 0x10, 0xF0, // 9 + 0xF0, 0x90, 0xF0, 0x90, 0x90, // A + 0xE0, 0x90, 0xE0, 0x90, 0xE0, // B + 0xF0, 0x80, 0x80, 0x80, 0xF0, // C + 0xE0, 0x90, 0x90, 0x90, 0xE0, // D + 0xF0, 0x80, 0xF0, 0x80, 0xF0, // E + 0xF0, 0x80, 0xF0, 0x80, 0x80 // F + }; + int i; + + pc = 0x200; + opcode = 0; + I = 0; + sp = 0; + delaytimer = 0; + soundtimer = 0; + + memset(V, 0, sizeof(V)); + memset(keys, 0, sizeof(keys)); + memset(stack, 0, sizeof(stack)); + memset(gfx, 0, sizeof(gfx)); + memset(mem, 0, sizeof(mem)); + + for (i = 0; i < 80; i++) + mem[i] = fontset[i]; } - -void -emulate(struct Chip8 *chip8) +static void +romload(struct chip8 *chip8, const char *fpath) { - opcode = memory[pc] << 8 | memory[pc + 1]; - if (decode(chip8)) { - EXECUTE(pc); - timers_update(chip8); - } - printf("Opcode: %x\tMemory: %x\tI: %x\tSP: %x\tPC: %d\n", - opcode, memory[pc] << 8 | memory[pc + 1], I, sp, pc); + FILE *rom; + char *buf; + size_t res, romsize; + int i; + + if ((rom = fopen(fpath, "rb")) == NULL) + die("fopen: %s", fpath); + fseek(rom, 0, SEEK_END); + romsize = ftell(rom); + rewind(rom); + + if ((buf = malloc(romsize + 1)) == NULL) + die("malloc:"); + if ((res = fread(buf, sizeof(char), romsize, rom)) != romsize) + die("fread:"); + buf[romsize] = '\0'; + if (romsize < (4092 - 512)) + for (i = 0; i < romsize; i++) + mem[i + 512] = buf[i]; + else + die("ROM cannot fit into memory"); + + fclose(rom); + free(buf); } -int -decode(struct Chip8 *chip8) +static void +emulate(struct chip8 *chip8) { - int i; - - switch (opcode & 0xF000) { - case 0x0000: // 00E_ - switch (NN_MASK(opcode)) { - case 0xE0: // 00E0 - Clear screen - memset(gfx, 0, 2048 * sizeof(u_int8_t)); - drawflag = 1; - break; - case 0xEE: // 00EE - Return from subroutine - pc = stack[--sp]; - break; - default: - fprintf(stderr, "Unknown opcode: %x\n", opcode); - return 0; - } - break; - case 0x1000: // 1NNN - Jump to address NNN - pc = NNN_MASK(opcode) - 2; - break; - case 0x2000: // 2NNN - Call subroutine at NNN - stack[sp++] = pc; - pc = NNN_MASK(opcode) - 2; - case 0x3000: // 3NNN - Skip next instruction if VX == NN - if (V[VX_MASK(opcode)] == NN_MASK(opcode)) - EXECUTE(pc); - break; - case 0x4000: // 4NNN - Skip next instruction if VX != NN - if (V[VX_MASK(opcode)] != NN_MASK(opcode)) - EXECUTE(pc); - break; - case 0x5000: // 5XY0 - Skip next instruction if VX == VY - if (V[VX_MASK(opcode)] == V[VY_MASK(opcode)]) - EXECUTE(pc); - break; - case 0x6000: // 6XNN - Set VX to NN - V[VX_MASK(opcode)] = NN_MASK(opcode); - break; - case 0x7000: // 7XNN - Add NN to VX - V[VX_MASK(opcode)] += NN_MASK(opcode); - break; - case 0x8000: // 8XY_ - switch (opcode & 0x000F) { - case 0x0000: // 8XY0 - Set VX to VY - V[VX_MASK(opcode)] = V[VY_MASK(opcode)]; - break; - case 0x0001: // 8XY1 - Set VX to (VX OR VY) - V[VX_MASK(opcode)] |= V[VY_MASK(opcode)]; - break; - case 0x0002: // 8XY2 - Set VX to (VX AND VY) - V[VX_MASK(opcode)] &= V[VY_MASK(opcode)]; - break; - case 0x0003: // 8XY3 - Set VX to (VX XOR VY) - V[VX_MASK(opcode)] ^= V[VY_MASK(opcode)]; - break; - case 0x0004: // 8XY4 - Add VY to VX, VF = 1 if there is a carry - V[VX_MASK(opcode)] += V[VY_MASK(opcode)]; - V[0xF] = (V[VY_MASK(opcode)] > (0xFF - V[VX_MASK(opcode)])) ? 1 : 0; - break; - case 0x0005: // 8XY5 - Sub VY from VX, VF = 0 if there is a borrow - V[0xF] = (V[VY_MASK(opcode)] > V[VX_MASK(opcode)]) ? 0 : 1; - V[VX_MASK(opcode)] -= V[VY_MASK(opcode)]; - break; - case 0x0006: // 8XY6 - Shift VX right by 1. VF = LSB of VX before shift - V[0xF] = V[VX_MASK(opcode)] & 0x1; - V[VX_MASK(opcode)] >>= 1; - break; - case 0x0007: // 8XY7 - Set VX to VY-VX. VF = 0 if there is a borrow - V[0xF] = (V[VX_MASK(opcode)] > V[VY_MASK(opcode)]) ? 0 : 1; - V[VX_MASK(opcode)] = V[VY_MASK(opcode)] - V[VX_MASK(opcode)]; - break; - case 0x000E: // 8XYE - Shift VX left by 1. VF = MSB of VX before shift - V[0xF] = V[VX_MASK(opcode)] >> 7; - V[VX_MASK(opcode)] <<= 1; - break; - default: - fprintf(stderr, "Unknown opcode: %x\n", opcode); - return 0; - } - break; - case 0x9000: // 9XY0 - Skip next instruction if VX != VY - if (V[VX_MASK(opcode)] != V[VY_MASK(opcode)]) - EXECUTE(pc); - break; - case 0xA000: // ANNN - Set I to the address NNN - I = NNN_MASK(opcode); - break; - case 0xB000: // BNNN - Jump to NNN + V0 - pc = ((NNN_MASK(opcode)) + V[0]) - 2; - break; - case 0xC000: // CNNN - Set VX to random number masked by NN - V[VX_MASK(opcode)] = (rand() % (0xFF + 1)) & (NN_MASK(opcode)); - break; - case 0xD000: { // Draw an 8 pixel sprite at (VX, VY) - int yl, xl; - u_int16_t h = opcode & 0x000F; - u_int16_t pixel; - u_int8_t VX = V[VX_MASK(opcode)]; - u_int8_t VY = V[VY_MASK(opcode)]; - - V[0xF] = 0; - for (yl = 0; yl < h; yl++) { - pixel = memory[I + yl]; - for (xl = 0; xl < 8; xl++) { - if ((pixel & (0x80 >> xl)) != 0) { - if (gfx[VX + xl + ((VY + yl) * 64)] == 1) - V[0xF] = 1; - gfx[VX + xl + ((VY + yl) * 64)] ^= 1; - } - } - } - drawflag = 1; - } - break; - case 0xE000: // EX__ - switch (NN_MASK(opcode)) { - case 0x009E: // EX9E - Skip next instruction if key in VX is pressed - if (keys[V[VX_MASK(opcode)]]) - EXECUTE(pc); - break; - case 0x00A1: // EXA1 - Skip next instruction if key in VX isn't pressed - if (!keys[V[VX_MASK(opcode)]]) - EXECUTE(pc); - break; - default: - fprintf(stderr, "Unknown opcode: %x\n", opcode); - return 0; - } - break; - case 0xF000: // FX__ - switch (NN_MASK(opcode)) { - case 0x0007: // FX07 - Set VX to delaytimer - V[VX_MASK(opcode)] = delaytimer; - break; - case 0x000A: { // FX0A - Wait for key press and then store it in VX - int keypressed = 0; - - for (i = 0; i < 16; i++) { - if (keys[i]) { - V[VX_MASK(opcode)] = i; - keypressed = 1; - } - } - if (!keypressed) - return 0; - } - break; - case 0x0015: // FX15 - Set the delaytimer to VX - delaytimer = V[VX_MASK(opcode)]; - break; - case 0x0018: // FX18 - Set the soundtimer to VX - soundtimer = V[VX_MASK(opcode)]; - break; - case 0x001E: // FX1E - Add VX to I - V[0xF] = ((I + V[VX_MASK(opcode)]) > 0xFFF) ? 1 : 0; - I += V[VX_MASK(opcode)]; - break; - case 0x0029: // FX29 - Set I to the location of the sprite for char VX - I = V[VX_MASK(opcode)] * 0x5; - break; - case 0x0033: // FX33 - Store bin coded decimal of VX at I, I+1 and I+2 - memory[I] = V[VX_MASK(opcode)] / 100; - memory[I+1] = (V[VX_MASK(opcode)] / 10) % 10; - memory[I+2] = V[VX_MASK(opcode)] % 10; - break; - case 0x0055: // FX55 - Store V0 to VX in memory starting at I - for (i = 0; i <= (VX_MASK(opcode)); i++) - memory[I + i] = V[i]; - I += (VX_MASK(opcode)) + 1; - break; - case 0x0065: // FX65 - Fill V0 to VX with vals from memory starting at I - for (i = 0; i <= (VX_MASK(opcode)); i++) - V[i] = memory[I + i]; - I += (VX_MASK(opcode)) + 1; - break; - default: - fprintf(stderr, "Unknown opcode: %x\n", opcode); - return 0; - } - break; - default: - fputs("Unimplemented opcode\n", stderr); - return 0; - } - return 1; + opcode = mem[pc] << 8 | mem[pc + 1]; + if (decode(chip8)) { + EXECUTE(pc); + if (delaytimer > 0) + delaytimer--; + if (soundtimer > 0) + soundtimer--; + } + printf("Opcode: %x\tMemory: %x\tI: %x\tSP: %x\tPC: %d\n", + opcode, mem[pc] << 8 | mem[pc + 1], I, sp, pc); } -void -timers_update(struct Chip8 *chip8) +static int +decode(struct chip8 *chip8) { - if (delaytimer > 0) - delaytimer--; - if (soundtimer > 0) - soundtimer--; + int yl, xl, i, keypress = 0; + u_int16_t h, pixel; + u_int8_t VX, VY; + + switch (opcode & 0xF000) { + case 0x0000: // 00E_ + switch (NN_MASK(opcode)) { + case 0xE0: // 00E0 - Clear screen + memset(gfx, 0, sizeof(gfx)); + drawflag = 1; + break; + case 0xEE: // 00EE - Return from subroutine + pc = stack[--sp]; + break; + default: + warn("unknown opcode: %x\n", opcode); + return 0; + } + break; + case 0x1000: // 1NNN - Jump to address NNN + pc = NNN_MASK(opcode) - 2; + break; + case 0x2000: // 2NNN - Call subroutine at NNN + stack[sp++] = pc; + pc = NNN_MASK(opcode) - 2; + case 0x3000: // 3NNN - Skip next instruction if VX == NN + if (V[VX_MASK(opcode)] == NN_MASK(opcode)) + EXECUTE(pc); + break; + case 0x4000: // 4NNN - Skip next instruction if VX != NN + if (V[VX_MASK(opcode)] != NN_MASK(opcode)) + EXECUTE(pc); + break; + case 0x5000: // 5XY0 - Skip next instruction if VX == VY + if (V[VX_MASK(opcode)] == V[VY_MASK(opcode)]) + EXECUTE(pc); + break; + case 0x6000: // 6XNN - Set VX to NN + V[VX_MASK(opcode)] = NN_MASK(opcode); + break; + case 0x7000: // 7XNN - Add NN to VX + V[VX_MASK(opcode)] += NN_MASK(opcode); + break; + case 0x8000: // 8XY_ + switch (opcode & 0x000F) { + case 0x0000: // 8XY0 - Set VX to VY + V[VX_MASK(opcode)] = V[VY_MASK(opcode)]; + break; + case 0x0001: // 8XY1 - Set VX to (VX OR VY) + V[VX_MASK(opcode)] |= V[VY_MASK(opcode)]; + break; + case 0x0002: // 8XY2 - Set VX to (VX AND VY) + V[VX_MASK(opcode)] &= V[VY_MASK(opcode)]; + break; + case 0x0003: // 8XY3 - Set VX to (VX XOR VY) + V[VX_MASK(opcode)] ^= V[VY_MASK(opcode)]; + break; + case 0x0004: // 8XY4 - Add VY to VX, VF = 1 if there is a carry + V[VX_MASK(opcode)] += V[VY_MASK(opcode)]; + V[0xF] = (V[VY_MASK(opcode)] > (0xFF - V[VX_MASK(opcode)])) ? 1 : 0; + break; + case 0x0005: // 8XY5 - Sub VY from VX, VF = 0 if there is a borrow + V[0xF] = (V[VY_MASK(opcode)] > V[VX_MASK(opcode)]) ? 0 : 1; + V[VX_MASK(opcode)] -= V[VY_MASK(opcode)]; + break; + case 0x0006: // 8XY6 - Shift VX right by 1. VF = LSB of VX before shift + V[0xF] = V[VX_MASK(opcode)] & 0x1; + V[VX_MASK(opcode)] >>= 1; + break; + case 0x0007: // 8XY7 - Set VX to VY-VX. VF = 0 if there is a borrow + V[0xF] = (V[VX_MASK(opcode)] > V[VY_MASK(opcode)]) ? 0 : 1; + V[VX_MASK(opcode)] = V[VY_MASK(opcode)] - V[VX_MASK(opcode)]; + break; + case 0x000E: // 8XYE - Shift VX left by 1. VF = MSB of VX before shift + V[0xF] = V[VX_MASK(opcode)] >> 7; + V[VX_MASK(opcode)] <<= 1; + break; + default: + warn("unknown opcode: %x\n", opcode); + return 0; + } + break; + case 0x9000: // 9XY0 - Skip next instruction if VX != VY + if (V[VX_MASK(opcode)] != V[VY_MASK(opcode)]) + EXECUTE(pc); + break; + case 0xA000: // ANNN - Set I to the address NNN + I = NNN_MASK(opcode); + break; + case 0xB000: // BNNN - Jump to NNN + V0 + pc = ((NNN_MASK(opcode)) + V[0]) - 2; + break; + case 0xC000: // CNNN - Set VX to random number masked by NN + V[VX_MASK(opcode)] = (rand() % (0xFF + 1)) & (NN_MASK(opcode)); + break; + case 0xD000: // Draw an 8 pixel sprite at (VX, VY) + h = opcode & 0x000F; + VX = V[VX_MASK(opcode)]; + VY = V[VY_MASK(opcode)]; + V[0xF] = 0; + + for (yl = 0; yl < h; yl++) { + pixel = mem[I + yl]; + for (xl = 0; xl < 8; xl++) { + if ((pixel & (0x80 >> xl)) != 0) { + if (gfx[VX + xl + ((VY + yl) * 64)] == 1) + V[0xF] = 1; + gfx[VX + xl + ((VY + yl) * 64)] ^= 1; + } + } + } + drawflag = 1; + break; + case 0xE000: // EX__ + switch (NN_MASK(opcode)) { + case 0x009E: // EX9E - Skip next instruction if key in VX is pressed + if (keys[V[VX_MASK(opcode)]]) + EXECUTE(pc); + break; + case 0x00A1: // EXA1 - Skip next instruction if key in VX isn't pressed + if (!keys[V[VX_MASK(opcode)]]) + EXECUTE(pc); + break; + default: + warn("unknown opcode: %x\n", opcode); + return 0; + } + break; + case 0xF000: // FX__ + switch (NN_MASK(opcode)) { + case 0x0007: // FX07 - Set VX to delaytimer + V[VX_MASK(opcode)] = delaytimer; + break; + case 0x000A: // FX0A - Wait for key press and then store it in VX + for (i = 0; i < 16; i++) { + if (keys[i]) { + V[VX_MASK(opcode)] = i; + keypress = 1; + } + } + if (!keypress) + return 0; + break; + case 0x0015: // FX15 - Set the delaytimer to VX + delaytimer = V[VX_MASK(opcode)]; + break; + case 0x0018: // FX18 - Set the soundtimer to VX + soundtimer = V[VX_MASK(opcode)]; + break; + case 0x001E: // FX1E - Add VX to I + V[0xF] = ((I + V[VX_MASK(opcode)]) > 0xFFF) ? 1 : 0; + I += V[VX_MASK(opcode)]; + break; + case 0x0029: // FX29 - Set I to the location of the sprite for char VX + I = V[VX_MASK(opcode)] * 0x5; + break; + case 0x0033: // FX33 - Store bin coded decimal of VX at I, I+1 and I+2 + mem[I] = V[VX_MASK(opcode)] / 100; + mem[I+1] = (V[VX_MASK(opcode)] / 10) % 10; + mem[I+2] = V[VX_MASK(opcode)] % 10; + break; + case 0x0055: // FX55 - Store V0 to VX in memory starting at I + for (i = 0; i <= (VX_MASK(opcode)); i++) + mem[I + i] = V[i]; + I += (VX_MASK(opcode)) + 1; + break; + case 0x0065: // FX65 - Fill V0 to VX with vals from memory starting at I + for (i = 0; i <= (VX_MASK(opcode)); i++) + V[i] = mem[I + i]; + I += (VX_MASK(opcode)) + 1; + break; + default: + warn("unknown opcode: %x\n", opcode); + return 0; + } + break; + default: + warn("unimplemented opcode\n"); + return 0; + } + return 1; } #undef V @@ -351,7 +343,7 @@ timers_update(struct Chip8 *chip8) #undef opcode #undef I #undef sp -#undef memory +#undef mem #undef gfx #undef stack #undef keys @@ -359,106 +351,128 @@ timers_update(struct Chip8 *chip8) #undef soundtimer #undef drawflag -int -evts(struct Chip8 *chip8) +static int +evhandle(struct chip8 *chip8) { - SDL_Event e; - int i; - - while (SDL_PollEvent(&e)) { - if (e.type == SDL_QUIT || e.key.keysym.sym == SDLK_ESCAPE) - return 0; - if (e.type == SDL_KEYDOWN) - for (i = 0; i < 16; i++) - if (e.key.keysym.sym == keymap[i]) - chip8->keys[i] = 1; - if (e.type == SDL_KEYUP) - for (i = 0; i < 16; i++) - if (e.key.keysym.sym == keymap[i]) - chip8->keys[i] = 0; - } - return 1; + SDL_Event e; + int i; + + while (SDL_PollEvent(&e)) { + if (e.type == SDL_QUIT || e.key.keysym.sym == SDLK_ESCAPE) + return 0; + if (e.type == SDL_KEYUP || e.type == SDL_KEYDOWN) { + for (i = 0; i < 16; i++) { + if (e.key.keysym.sym == keymap[i]) { + if (e.type == SDL_KEYUP) + chip8->keys[i] = 0; + else if (e.type == SDL_KEYDOWN) + chip8->keys[i] = 1; + } + } + } + } + return 1; } -void -render(SDL_Renderer *ren, SDL_Texture *tex, struct Chip8 *chip8) +static void +render(SDL_Renderer *ren, SDL_Texture *tex, struct chip8 *chip8) { - int i; - u_int32_t pixels[2048]; - u_int8_t pixel; - - chip8->drawflag = 0; - for (i = 0; i < 2048; i++) { - pixel = chip8->gfx[i]; - pixels[i] = (0x00FFFFFF * pixel) | 0xFF000000; - } - SDL_UpdateTexture(tex, NULL, pixels, 64 * sizeof(Uint32)); - SDL_RenderClear(ren); - SDL_RenderCopy(ren, tex, NULL, NULL); - SDL_RenderPresent(ren); + u_int32_t pixels[2048]; + int i; + + chip8->drawflag = 0; + for (i = 0; i < 2048; i++) + pixels[i] = (0x00FFFFFF * chip8->gfx[i]) | 0xFF000000; + SDL_UpdateTexture(tex, NULL, pixels, 64 * sizeof(Uint32)); + SDL_RenderClear(ren); + SDL_RenderCopy(ren, tex, NULL, NULL); + SDL_RenderPresent(ren); } -void +static void +warn(const char *fmt, ...) +{ + va_list args; + + fprintf(stderr, "%s: ", argv0); + + va_start(args, fmt); + vfprintf(stderr, fmt, args); + va_end(args); +} + +static void die(const char *fmt, ...) { - va_list args; - - va_start(args, fmt); - vfprintf(stderr, fmt, args); - va_end(args); - - if (fmt[0] && fmt[strlen(fmt)-1] == ':') { - fputc(' ', stderr); - perror(NULL); - } else - fputc('\n', stderr); - - exit(EXIT_FAILURE); + va_list args; + + fprintf(stderr, "%s: ", argv0); + + va_start(args, fmt); + vfprintf(stderr, fmt, args); + va_end(args); + + if (fmt[0] && fmt[strlen(fmt)-1] == ':') { + fputc(' ', stderr); + perror(NULL); + } else + fputc('\n', stderr); + + exit(EXIT_FAILURE); } int main(int argc, char *argv[]) { - SDL_Window *win; - SDL_Renderer *ren; - SDL_Texture *tex; - struct Chip8 chip8; - int w = 1024, h = 512; - - srand(time(NULL)); - if (argc != 2) - die("usage: %s [ROM]", argv[0]); - if (SDL_Init(SDL_INIT_EVERYTHING) < 0) - die("cannot initalize SDL: %s", SDL_GetError()); - - win = SDL_CreateWindow("CHIP-8 Emulator", SDL_WINDOWPOS_UNDEFINED, - SDL_WINDOWPOS_UNDEFINED, w, h, - SDL_WINDOW_SHOWN); - ren = SDL_CreateRenderer(win, -1, 0); - SDL_RenderSetLogicalSize(ren, w, h); - tex = SDL_CreateTexture(ren, SDL_PIXELFORMAT_ARGB8888, - SDL_TEXTUREACCESS_STREAMING, 64, 32); - - if (!win || !ren || !tex) - die("SDL error: %s", SDL_GetError()); - - chip8_init(&chip8); - rom_load(&chip8, argv[1]); - - while (evts(&chip8)) { - emulate(&chip8); - if (chip8.drawflag) - render(ren, tex, &chip8); + SDL_Window *win; + SDL_Renderer *ren; + SDL_Texture *tex; + struct chip8 *chip8; + int w = 1024, h = 512; + + argv0 = *argv; + srand(time(NULL)); + if (argc != 2) { + fprintf(stderr, "usage: %s rom", argv0); + return 1; + } + if (SDL_Init(SDL_INIT_EVERYTHING) < 0) + die("SDL_Init: %s", SDL_GetError()); + + win = SDL_CreateWindow("CHIP-8 Emulator", SDL_WINDOWPOS_UNDEFINED, + SDL_WINDOWPOS_UNDEFINED, w, h, + SDL_WINDOW_SHOWN); + ren = SDL_CreateRenderer(win, -1, 0); + SDL_RenderSetLogicalSize(ren, w, h); + tex = SDL_CreateTexture(ren, SDL_PIXELFORMAT_ARGB8888, + SDL_TEXTUREACCESS_STREAMING, 64, 32); + + if (!win || !ren || !tex) + die("SDL error: %s", SDL_GetError()); + if ((chip8 = malloc(sizeof(struct chip8))) == NULL) + die("malloc:"); + + chip8_init(chip8); + romload(chip8, argv[1]); + + for (;;) { + if (!evhandle(chip8)) + break; + emulate(chip8); + if (chip8->drawflag) + render(ren, tex, chip8); #ifdef _WIN_32 - Sleep(1); + Sleep(1); #else /* !_WIN_32 */ - usleep(1500); + usleep(1500); #endif /* _WIN_32 */ - } + } + + free(chip8); + SDL_DestroyTexture(tex); + SDL_DestroyRenderer(ren); + SDL_DestroyWindow(win); + SDL_Quit(); - SDL_DestroyTexture(tex); - SDL_DestroyRenderer(ren); - SDL_DestroyWindow(win); - SDL_Quit(); - return EXIT_SUCCESS; + return EXIT_SUCCESS; } diff --git a/config.mk b/config.mk @@ -4,7 +4,7 @@ VERSION = 0 # paths PREFIX = /usr/local -MAN_DIR = ${PREFIX}/man/man1 +MAN_DIR = ${PREFIX}/share/man/man1 BIN_DIR = ${PREFIX}/bin # includes and libs @@ -13,9 +13,8 @@ LIBS = -Llib -lSDL2 # flags CPPFLAGS = -D_DEFAULT_SOURCE -D_BSD_SOURCE -D_POSIX_C_SOURCE=200809L \ - -DVERSION=\"${VERSION}\" -CFLAGS = -std=c99 -pedantic -Wall -Wno-deprecated-declarations \ - -O3 ${INCS} ${CPPFLAGS} + -D_XOPEN_SOURCE=700 -DVERSION=\"${VERSION}\" +CFLAGS = -std=c99 -pedantic -Wall -Os ${INCS} ${CPPFLAGS} LDFLAGS = ${LIBS} # utils