#include "SDL/SDL.h" #include #include #include #include #include #include #include #define X (op & 0x0F00) >> 8 #define Y (op & 0x00F0) >> 4 #define N op & 0x000F #define NN op & 0x00FF #define NNN op & 0x0FFF #ifndef SCALE #define SCALE 20 #endif #ifndef WAIT #define WAIT 100 #endif typedef uint8_t byte; typedef uint16_t word; byte memory[4096]; byte v[16]; word i = 0; word pc = 0x200; word sp = 0; word stack[16]; byte dt = 0; byte st = 0; byte key[16]; byte gfx[64][32]; byte oldgfx[64][32]; word op = 0; SDL_Surface* s; SDL_Event e; Uint8 fg; Uint8 bg; Uint8* wav_buf; void init_chip8(void) { srand(time(NULL)); for (register uint_fast8_t t = 0; t < 16; ++t) { v[t] = 0; stack[t] = 0; key[t] = 0; } for (register uint_fast8_t x = 0; x < 64; ++x) for (register uint_fast8_t y = 0; y < 32; ++y) gfx[x][y] = 0; const byte 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 }; for (register uint_fast8_t t = 0; t < 80; ++t) memory[t] = fontset[t]; for (register uint_fast16_t t = 80; t < 4096; ++t) memory[t] = 0; } void cleanup(void) { SDL_FreeSurface(s); SDL_Quit(); } uint_fast8_t init_SDL(const char* path) { if (SDL_Init(SDL_INIT_VIDEO)) { fputs("ERROR: Failed to initialize SDL\n", stderr); return 0; } s = SDL_SetVideoMode(64 * SCALE, 32 * SCALE, 8, SDL_HWSURFACE); if (!s) { fputs("ERROR: Failed to create SDL surface\n", stderr); return 0; } if (atexit(cleanup)) { fputs("ERROR: Failed to set exit function\n", stderr); SDL_FreeSurface(s); SDL_Quit(); return 0; } char caption[7 + strlen(path)]; sprintf(caption, "CHIP-8 - %s", path); SDL_WM_SetCaption(caption, NULL); return 1; } long get_file_size(FILE* f) { if (fseek(f, 0, SEEK_END)) { fputs("ERROR: Failed to get file size\n", stderr); return 0; } const long s = ftell(f); if (s == -1) { fputs("ERROR: Failed to get file size\n", stderr); return 0; } rewind(f); return s; } uint_fast8_t load_rom(const char* path) { FILE* f = fopen(path, "rb"); if (!f) { fputs("ERROR: Failed to open rom file\n", stderr); return 0; } const long s = get_file_size(f); if (!s) { if (fclose(f)) fputs("ERROR: Failed to close rom file\n", stderr); return 0; } if (s > 3072) { fputs("ERROR: Rom file too big\n", stderr); if (fclose(f)) fputs("ERROR: Failed to close rom file\n", stderr); return 0; } fread(memory + 0x200, sizeof(byte), s, f); if (ferror(f)) { fputs("ERROR: Failed to read rom file\n", stderr); if (fclose(f)) fputs("ERROR: Failed to close rom file\n", stderr); return 0; } if (fclose(f)) { fputs("ERROR: Failed to close rom file\n", stderr); return 0; } fprintf(stderr, "INFO: %ldB loaded from %s/%s\n", s, getenv("PWD"), path); fputs("Loaded ROM:\n", stderr); uint_fast8_t c = 0; for (long t = 0; t < s; t += 2) { fprintf( stderr, "%04X ", (memory[t + 0x200] << 8) + memory[t + 0x200 + 1]); if (c++ == 7) { putchar('\n'); c = 0; } } putchar('\n'); return 1; } static inline void event_handler(void) { if (e.type == SDL_KEYDOWN) { switch (e.key.keysym.sym) { case SDLK_0: key[0x0] = 1; break; case SDLK_1: key[0x1] = 1; break; case SDLK_2: key[0x2] = 1; break; case SDLK_3: key[0x3] = 1; break; case SDLK_4: key[0x4] = 1; break; case SDLK_5: key[0x5] = 1; break; case SDLK_6: key[0x6] = 1; break; case SDLK_7: key[0x7] = 1; break; case SDLK_8: key[0x8] = 1; break; case SDLK_9: key[0x9] = 1; break; case SDLK_q: key[0xA] = 1; break; case SDLK_w: key[0xB] = 1; break; case SDLK_e: key[0xC] = 1; break; case SDLK_a: key[0xD] = 1; break; case SDLK_s: key[0xE] = 1; break; case SDLK_d: key[0xF] = 1; break; case SDLK_ESCAPE: exit(0); default: fputs("WARNING: Unhandled input detected\n", stderr); } } else if (e.type == SDL_KEYUP) { switch (e.key.keysym.sym) { case SDLK_0: key[0x0] = 0; break; case SDLK_1: key[0x1] = 0; break; case SDLK_2: key[0x2] = 0; break; case SDLK_3: key[0x3] = 0; break; case SDLK_4: key[0x4] = 0; break; case SDLK_5: key[0x5] = 0; break; case SDLK_6: key[0x6] = 0; break; case SDLK_7: key[0x7] = 0; break; case SDLK_8: key[0x8] = 0; break; case SDLK_9: key[0x9] = 0; break; case SDLK_q: key[0xA] = 0; break; case SDLK_w: key[0xB] = 0; break; case SDLK_e: key[0xC] = 0; break; case SDLK_a: key[0xD] = 0; break; case SDLK_s: key[0xE] = 0; break; case SDLK_d: key[0xF] = 0; break; default: break; } } else if (e.type == SDL_QUIT) { exit(0); } } static inline void render(void) { SDL_Rect r; r.h = SCALE; r.w = SCALE; for (register uint_fast8_t x = 0; x < 64; x++) for (register uint_fast8_t y = 0; y < 32; y++) if (gfx[x][y] ^ oldgfx[x][y]) { r.x = x * SCALE; r.y = y * SCALE; SDL_FillRect(s, &r, (gfx[x][y]) ? fg : bg); } SDL_Flip(s); } static inline uint_fast8_t op_handler(void) { switch (op) { case 0x00E0: for (register uint_fast8_t x = 0; x < 64; ++x) for (register uint_fast8_t y = 0; y < 32; ++y) gfx[x][y] = 0; SDL_Rect r; r.h = 32 * SCALE; r.w = 64 * SCALE; r.x = 0; r.y = 0; SDL_FillRect(s, &r, bg); pc += 2; return 1; case 0x00EE: pc = stack[--sp]; pc += 2; return 1; default: switch (op & 0xF0FF) { case 0xE09E: if (key[v[X]]) { pc += 2; key[v[X]] = 0; } pc += 2; return 1; case 0xE0A1: if (!key[v[X]]) pc += 2; pc += 2; return 1; case 0xF007: v[X] = dt; pc += 2; return 1; case 0xF00A: for (register uint_fast8_t t = 0; t < 16; ++t) if (key[t]) { v[X] = t; key[t] = 0; pc += 2; return 1; } return 1; case 0xF015: dt = v[X]; pc += 2; return 1; case 0xF018: st = v[X]; pc += 2; return 1; case 0xF01E: i += v[X]; pc += 2; return 1; case 0xF029: i = v[X] * 5; pc += 2; return 1; case 0xF033: memory[i] = v[X] / 100; memory[i + 1] = (v[X] % 100) / 10; memory[i + 2] = (v[X] % 100) % 10; pc += 2; return 1; case 0xF055: for (register uint_fast8_t t = 0; t < X; ++t) memory[i + t] = v[t]; pc += 2; return 1; case 0xF065: for (register uint_fast8_t t = 0; t < X; ++t) v[t] = memory[i + t]; pc += 2; return 1; default: switch (op & 0xF00F) { case 0x5000: if (v[X] == v[Y]) pc += 2; pc += 2; return 1; case 0x8000: v[X] = v[Y]; pc += 2; return 1; case 0x8001: v[X] |= v[Y]; pc += 2; return 1; case 0x8002: v[X] &= v[Y]; pc += 2; return 1; case 0x8003: v[X] ^= v[Y]; pc += 2; return 1; case 0x8004: v[0xF] = (v[X] > 0xFF - v[Y]); v[X] += v[Y]; pc += 2; return 1; case 0x8005: v[0xF] = (v[X] >= v[Y]); v[X] -= v[Y]; pc += 2; return 1; case 0x8006: v[0xF] = v[X] & 0x0001; v[X] >>= 1; pc += 2; return 1; case 0x8007: v[0xF] = (v[X] <= v[Y]); v[X] = v[Y] - v[X]; pc += 2; return 1; case 0x800E: v[0xF] = v[X] >> 7; v[X] <<= 1; pc += 2; return 1; case 0x9000: if (v[X] != v[Y]) pc += 2; pc += 2; return 1; default: switch (op & 0xF000) { case 0x1000: pc = NNN; return 1; case 0x2000: stack[sp++] = pc; pc = NNN; return 1; case 0x3000: if (v[X] == (NN)) pc += 2; pc += 2; return 1; case 0x4000: if (v[X] != (NN)) pc += 2; pc += 2; return 1; case 0x6000: v[X] = NN; pc += 2; return 1; case 0x7000: v[X] += NN; pc += 2; return 1; case 0xA000: i = NNN; pc += 2; return 1; case 0xB000: pc = v[0] + (NNN); return 1; case 0xC000: v[X] = (rand() % 255) & NN; pc += 2; return 1; case 0xD000: for (register uint_fast8_t x = 0; x < 64; ++x) for (register uint_fast8_t y = 0; y < 32; ++y) oldgfx[x][y] = gfx[x][y]; v[0xF] = 0; for (register uint_fast8_t x = 0; x < (N); ++x) for (register uint_fast8_t y = 0; y < 8; ++y) if (memory[i + x] & (0x80 >> y)) { if (gfx[v[X] + y][v[Y] + x]) v[0xF] = 1; gfx[v[X] + y][v[Y] + x] ^= 1; } render(); pc += 2; return 1; default: fprintf(stderr, "ERROR: Unhandled opcode: %04X\n", op); sleep(3); return 0; } } } } } void timers() { if (dt) --dt; if (st) --st; } int main(int argc, char** argv) { if (argc < 2) { fputs("USAGE: chip8 ", stderr); return 1; } if (access(argv[1], F_OK)) { fputs("ERROR: Provided ROM file does not exist\n", stderr); return 1; } if (access(argv[1], R_OK)) { fputs("ERROR: No read permission to provided ROM file\n", stderr); return 1; } init_chip8(); if (!load_rom(argv[1])) return 1; if (!init_SDL(argv[1])) return 1; fg = SDL_MapRGB(s->format, 255, 255, 255); bg = SDL_MapRGB(s->format, 0, 0, 0); struct sigaction sa; memset(&sa, 0, sizeof(struct sigaction)); sa.sa_handler = timers; if (sigaction(SIGALRM, &sa, 0)) { fputs("ERROR: Failed to set SIGALRM handler", stderr); return 1; } ualarm(16667, 16667); loop: op = (memory[pc] << 8) + memory[pc + 1]; while (SDL_PollEvent(&e)) event_handler(); if (!op_handler()) exit(1); usleep(WAIT); goto loop; }