Initial commit
This commit is contained in:
commit
b7edca10c2
28
Makefile
Normal file
28
Makefile
Normal file
@ -0,0 +1,28 @@
|
||||
CPPFLAGS = -D_DEFAULT_SOURCE -D_FOTIFY_SOURCE=2
|
||||
CFLAGS = $(CPPFLAGS) -std=c99 -pedantic -Wdefault -Wextra -march=native -mtune=native -O2
|
||||
LDFLAGS = -Wl,-z,relro,-z,now,-O2 -lSDL
|
||||
|
||||
default: bin obj bin/chip8
|
||||
|
||||
bin:
|
||||
mkdir bin
|
||||
|
||||
obj:
|
||||
mkdir obj
|
||||
|
||||
clean:
|
||||
rm -rf bin obj
|
||||
|
||||
bin/chip8: obj/main.o
|
||||
$(CC) -o $@ $^ $(LDFLAGS)
|
||||
|
||||
obj/%.o: src/%.c
|
||||
$(CC) $(CFLAGS) -c -o $@ $^
|
||||
|
||||
format:
|
||||
clang-format -i -style="{BasedOnStyle: mozilla, IndentWidth: 4}" src/main.c
|
||||
|
||||
.PHONY: test
|
||||
test: default
|
||||
@./bin/chip8 tests/TEST
|
||||
|
597
src/main.c
Normal file
597
src/main.c
Normal file
@ -0,0 +1,597 @@
|
||||
#include "SDL/SDL.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 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 <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;
|
||||
}
|
BIN
tests/TEST
Normal file
BIN
tests/TEST
Normal file
Binary file not shown.
Loading…
Reference in New Issue
Block a user