chip8/src/main.c

613 lines
16 KiB
C

#include "SDL2/SDL.h"
#include <SDL2/SDL_mixer.h>
#include <signal.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#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 200
#endif
typedef uint8_t byte;
typedef uint16_t word;
byte dt = 0;
byte gfx[64][32];
word i = 0;
byte key[16];
byte memory[4096];
byte oldgfx[64][32];
word op = 0;
word pc = 0x200;
word sp = 0;
byte st = 0;
word stack[16];
byte v[16];
SDL_Event e;
SDL_Surface* s;
SDL_Window* w;
Mix_Chunk* buzzer;
Uint32 fg;
Uint32 bg;
void
init_chip8(void)
{
srand(time(NULL));
memset(v, 0, 16 * sizeof(byte));
memset(stack, 0, 16 * sizeof(word));
memset(key, 0, 16 * sizeof(byte));
memset(gfx, 0, 32 * 64 * sizeof(byte));
memset(oldgfx, 0, 32 * 64 * sizeof(byte));
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
};
memcpy(memory, fontset, 80 * sizeof(byte));
memset(memory + 80, 0, 4016);
}
void
cleanup(void)
{
Mix_FreeChunk(buzzer);
Mix_Quit();
SDL_FreeSurface(s);
SDL_DestroyWindow(w);
SDL_Quit();
}
uint_fast8_t
init_SDL(const char* path)
{
if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO)) {
fputs("ERROR: Failed to initialize SDL\n", stderr);
return 0;
}
char caption[7 + strlen(path)];
sprintf(caption, "CHIP-8 - %s", path);
w = SDL_CreateWindow(caption,
SDL_WINDOWPOS_UNDEFINED,
SDL_WINDOWPOS_UNDEFINED,
64 * SCALE,
32 * SCALE,
SDL_WINDOW_SHOWN);
if (!w) {
fputs("ERROR: Failed to create a window\n", stderr);
return 0;
}
s = SDL_GetWindowSurface(w);
if (!s) {
fputs("ERROR: Failed to create SDL surface\n", stderr);
return 0;
}
if (Mix_OpenAudio(44100, MIX_DEFAULT_FORMAT, 1, 2048)) {
fputs("ERROR: Failed to open audio\n", stderr);
return 0;
}
buzzer = Mix_LoadWAV("resources/buzzer.wav");
if (!buzzer) {
fputs("ERROR: Failed to load sound effects\n", stderr);
return 0;
}
if (atexit(cleanup)) {
fputs("ERROR: Failed to set exit function\n", stderr);
SDL_FreeSurface(s);
SDL_Quit();
return 0;
}
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_UpdateWindowSurface(w);
}
static inline uint_fast8_t
op_handler(void)
{
switch (op) {
case 0x00E0:
memset(gfx, 0, 32 * 64 * sizeof(byte));
SDL_Rect r;
r.h = 32 * SCALE;
r.w = 64 * SCALE;
r.x = 0;
r.y = 0;
SDL_FillRect(s, &r, bg);
SDL_UpdateWindowSurface(w);
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:
memcpy(memory + i, v, ((X) + 1) * sizeof(byte));
i += (X) + 1;
pc += 2;
return 1;
case 0xF065:
memcpy(v, memory + i, ((X) + 1) * sizeof(byte));
i += (X) + 1;
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:
memcpy(oldgfx, gfx, 32 * 64 * sizeof(byte));
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) {
Mix_PlayChannel(-1, buzzer, 0);
--st;
}
}
int
main(int argc, char** argv)
{
if (argc < 2) {
fputs("USAGE: chip8 <FILE>", 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;
}