434 lines
13 KiB
C
434 lines
13 KiB
C
#include <stdint.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
#include <time.h>
|
|
#include <SDL2/SDL.h>
|
|
|
|
#define DEBUG 0
|
|
#define TDISP 0
|
|
|
|
typedef uint8_t byte;
|
|
typedef uint16_t word;
|
|
|
|
typedef struct {
|
|
byte memory[0x1000];
|
|
byte screen[64][32];
|
|
byte registers[0x10];
|
|
word stack[24];
|
|
byte input[0x10];
|
|
|
|
word address_I;
|
|
word program_counter;
|
|
byte stack_pointer;
|
|
} emulator_state_s;
|
|
|
|
byte chip8_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
|
|
};
|
|
|
|
byte keymap[0x10] = { SDL_SCANCODE_X, SDL_SCANCODE_1, SDL_SCANCODE_2, SDL_SCANCODE_3, SDL_SCANCODE_Q,
|
|
SDL_SCANCODE_W, SDL_SCANCODE_E, SDL_SCANCODE_A, SDL_SCANCODE_S, SDL_SCANCODE_D,
|
|
SDL_SCANCODE_Z, SDL_SCANCODE_C, SDL_SCANCODE_4, SDL_SCANCODE_R, SDL_SCANCODE_F, SDL_SCANCODE_V };
|
|
|
|
int main(int argc, char **argv) {
|
|
SDL_Window *window = NULL;
|
|
SDL_Renderer *renderer = NULL;
|
|
SDL_Init(SDL_INIT_VIDEO);
|
|
|
|
window = SDL_CreateWindow("Chip8 Emulator", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, 64 * 8, 32 * 8, SDL_WINDOW_SHOWN);
|
|
renderer = SDL_CreateRenderer(window, 0, SDL_RENDERER_ACCELERATED);
|
|
if (renderer == NULL) printf( "Renderer could not be created! SDL Error: %s\n", SDL_GetError() );
|
|
SDL_SetRenderDrawColor(renderer, 0xFF, 0xFF, 0xFF, 0xFF);
|
|
|
|
srand(time(NULL));
|
|
|
|
emulator_state_s state = { 0 }; // initialize everything to 0
|
|
state.program_counter = 0x200; // chip8 programs start at 0x200 address space
|
|
memcpy(state.memory, chip8_fontset, 80);
|
|
|
|
FILE *f = fopen(argv[1], "rb");
|
|
fread(&state.memory[0x200], 0x1000 - 0x200, 1, f); // see above comment
|
|
fclose(f);
|
|
|
|
SDL_Event e;
|
|
|
|
int delay = 0;
|
|
int sound = 0;
|
|
int press = 0;
|
|
int wait = 0;
|
|
int store = 0;
|
|
|
|
while (1) {
|
|
if (DEBUG) printf("0x%x ", state.program_counter);
|
|
|
|
if (delay > 0) --delay;
|
|
if (sound > 0) --sound;
|
|
memset(state.input, 0, 0x10);
|
|
press = 0;
|
|
|
|
// opcodes are 16 bits
|
|
|
|
SDL_PollEvent(&e);
|
|
if (e.type == SDL_QUIT) break;
|
|
|
|
// SDL_PumpEvents();
|
|
const byte *keystate = SDL_GetKeyboardState(NULL);
|
|
|
|
for (int i = 0; i < 0x10; ++i) {
|
|
if (keystate[keymap[i]]){
|
|
state.input[i] = 1;
|
|
press = 1;
|
|
}
|
|
|
|
}
|
|
|
|
if (wait && !press) continue;
|
|
if (wait && press)
|
|
{
|
|
for (int i = 0; i < 0x10; ++i) {
|
|
if (state.input[i] != 0)
|
|
{
|
|
state.registers[store] = i;
|
|
break;
|
|
}
|
|
}
|
|
wait = 0;
|
|
}
|
|
|
|
int ops = 0;
|
|
|
|
loop:;
|
|
|
|
word opcode = (state.memory[state.program_counter++] << 8) | (state.memory[state.program_counter++]);
|
|
|
|
switch (opcode & 0xF000)
|
|
{
|
|
case 0x0000: // screen clear or function return
|
|
if (opcode == 0x00E0){
|
|
memset(state.screen, 0, 64 * 32);
|
|
if (DEBUG) printf("Clearing screen ");
|
|
}
|
|
|
|
else if (opcode == 0x00EE) {
|
|
state.program_counter = state.stack[--state.stack_pointer];
|
|
if (DEBUG) printf("Returning to %x ", state.stack[state.stack_pointer]);
|
|
}
|
|
|
|
break;
|
|
|
|
case 0x1000: // goto statement
|
|
state.program_counter = opcode & 0x0FFF;
|
|
if (DEBUG) printf("Jumping to %x ", state.program_counter);
|
|
|
|
break;
|
|
|
|
case 0x2000: // CALL
|
|
state.stack[state.stack_pointer++] = state.program_counter;
|
|
state.program_counter = opcode & 0x0FFF;
|
|
if (DEBUG) printf("Calling subroutine at %x (returning to %x)", state.program_counter, state.stack[state.stack_pointer - 1]);
|
|
|
|
break;
|
|
|
|
case 0x3000:; // skip if reg 0x0F00 is equal to constant 0x00FF
|
|
int reg = (opcode & 0x0F00) >> 8;
|
|
if (DEBUG) printf("Comparing (==) register %i (%x) to %x ", reg, state.registers[reg], (byte) opcode);
|
|
if (state.registers[reg] == (byte) opcode){
|
|
state.program_counter += 2;
|
|
if (DEBUG) printf("and incrementing program_counter ");
|
|
}
|
|
|
|
break;
|
|
|
|
case 0x4000: // skip if not equal
|
|
reg = (opcode & 0x0F00) >> 8;
|
|
if (DEBUG) printf("Comparing (!=) register %i (%x) to %x ", reg, state.registers[reg], (byte) opcode);
|
|
if (state.registers[reg] != (byte) opcode){
|
|
state.program_counter += 2;
|
|
if (DEBUG) printf("and incrementing program_counter ");
|
|
}
|
|
|
|
break;
|
|
|
|
case 0x5000:; // skip if reg 0x0F00 is equal to 0x00F0
|
|
int first = (opcode & 0x0F00) >> 8;
|
|
int second = (opcode & 0x00F0) >> 4;
|
|
if (DEBUG) printf("Comparing (==) register %i (%x) to %i (%x) ", first, state.registers[first], second, state.registers[second]);
|
|
if (state.registers[first] == state.registers[second]){
|
|
state.program_counter += 2;
|
|
if (DEBUG) printf("and incrementing program_counter ");
|
|
}
|
|
|
|
break;
|
|
|
|
case 0x6000: // set reg to constant
|
|
reg = (opcode & 0x0F00) >> 8;
|
|
state.registers[reg] = (byte) opcode;
|
|
if (DEBUG) printf("Assigning %hx to %i", state.registers[reg], reg);
|
|
|
|
break;
|
|
|
|
case 0x7000: // add constant to reg
|
|
reg = (opcode & 0x0F00) >> 8;
|
|
state.registers[reg] += (byte) opcode;
|
|
if (DEBUG) printf("Add %hi to reg %i (%hi)", opcode & 0x00FF, reg, state.registers[reg]);
|
|
|
|
break;
|
|
|
|
case 0x8000:; // math/bitop operators
|
|
first = (opcode & 0x0F00) >> 8; // all use XY regs
|
|
second = (opcode & 0x00F0) >> 4;
|
|
|
|
switch (opcode & 0x000F)
|
|
{
|
|
case 0: // copy 1 register to another
|
|
state.registers[first] = state.registers[second];
|
|
if (DEBUG) printf("Copied register %i to %i (%hx)", first, second, state.registers[first]);
|
|
break;
|
|
|
|
case 1: // X = X | Y
|
|
state.registers[first] |= state.registers[second];
|
|
if (DEBUG) printf("OR register %i and %i (%hx)", first, second, state.registers[first]);
|
|
break;
|
|
|
|
case 2: // X = X & Y
|
|
state.registers[first] &= state.registers[second];
|
|
if (DEBUG) printf("AND register %i and %i (%hx)", first, second, state.registers[first]);
|
|
break;
|
|
|
|
case 3: // X = X ^ Y
|
|
state.registers[first] ^= state.registers[second];
|
|
if (DEBUG) printf("XOR register %i to %i (%hx)", first, second, state.registers[first]);
|
|
break;
|
|
|
|
case 4:; // X = X + Y, VF = X + 5 > 255
|
|
unsigned int add = state.registers[first] + state.registers[second];
|
|
if (add > 255) state.registers[0xF] = 1;
|
|
else state.registers[0xF] = 0;
|
|
state.registers[first] = (byte) add;
|
|
if (DEBUG) printf("ADD register %i to %i (%hx) ", second, first, state.registers[first]);
|
|
if (DEBUG) printf("VF is %hx", state.registers[0xF]);
|
|
break;
|
|
|
|
case 5: // X = X - Y, VF = X - Y < 0
|
|
state.registers[first] -= state.registers[second];
|
|
if (state.registers[first] > state.registers[second]) state.registers[0xF] = 0;
|
|
else state.registers[0xF] = 1;
|
|
if (DEBUG) printf("SUB register %i from %i (%hx) ", second, first, state.registers[first]);
|
|
if (DEBUG) printf("VF is %hx", state.registers[0xF]);
|
|
break;
|
|
|
|
case 6: // set least significant bit of X in VF, bitshift right
|
|
state.registers[0xF] = state.registers[first] & 0b00000001;
|
|
state.registers[first] /= 2;
|
|
if (DEBUG) printf("RS register %i (%hx) ", first, state.registers[first]);
|
|
if (DEBUG) printf("VF is %hx", state.registers[0xF]);
|
|
break;
|
|
|
|
case 7: // X = Y - X, VF = Y - X < 0
|
|
state.registers[first] = state.registers[second] - state.registers[first];
|
|
if (state.registers[second] > state.registers[first]) state.registers[0xF] = 1;
|
|
else state.registers[0xF] = 0;
|
|
if (DEBUG) printf("SUB %i from %i (%hx) ", first, second, state.registers[first]);
|
|
if (DEBUG) printf("VF is %hx", state.registers[0xF]);
|
|
break;
|
|
|
|
case 0xE:; // set msot significant bit of X in VF, bitshift left
|
|
state.registers[0xF] = (state.registers[first] & 0b10000000) >> 7;
|
|
state.registers[first] *= 2;
|
|
if (DEBUG) printf("LS register %i (%hx) ", first, state.registers[first]);
|
|
if (DEBUG) printf("VF is %hx", state.registers[0xF]);
|
|
break;
|
|
|
|
default: break;
|
|
|
|
} break;
|
|
|
|
case 0x9000: // skip if two regs are not equal
|
|
first = (opcode & 0x0F00) >> 8;
|
|
second = (opcode & 0x00F0) >> 4;
|
|
if (DEBUG) printf("Comparing (!=) register %i (%hx) to %i (%hx) ", first, state.registers[first], second, state.registers[second]);
|
|
if (state.registers[first] != state.registers[second]) {
|
|
state.program_counter += 2;
|
|
if (DEBUG) printf("and incrementing program_counter");
|
|
}
|
|
break;
|
|
|
|
case 0xA000: // assign I address
|
|
state.address_I = opcode & 0x0FFF;
|
|
if (DEBUG) printf("Assigning %x to I address ", opcode & 0x0FFF);
|
|
|
|
break;
|
|
|
|
case 0xB000: // jump to 0x0FFF + V0
|
|
state.program_counter = (opcode & 0x0FFF) + state.registers[0x0];
|
|
if (DEBUG) printf("Jumping to %x + %hx (%x)", opcode & 0x0FFF, state.registers[0], (opcode & 0x0FFF) + state.registers[0x0]);
|
|
|
|
break;
|
|
|
|
case 0xC000: // random generator
|
|
reg = (opcode & 0x0F00) >> 8;
|
|
state.registers[reg] = (rand() % 256) & ((byte) opcode);
|
|
if (DEBUG) printf("Storing random in %i (%x)\n", reg, state.registers[reg]);
|
|
|
|
break;
|
|
|
|
case 0xD000: // draw sprite
|
|
state.registers[0xF] = 0;
|
|
|
|
first = (opcode & 0x0F00) >> 8;
|
|
second = (opcode & 0x00F0) >> 4;
|
|
|
|
int x = state.registers[first];
|
|
int y = state.registers[second];
|
|
int h = (opcode & 0x000F);
|
|
|
|
for (int line = 0; line < h; ++line)
|
|
{
|
|
byte data = state.memory[state.address_I + line];
|
|
for (int xpos = 0; xpos < 8; ++xpos)
|
|
{
|
|
int ypos = y + line;
|
|
byte mask = 1 << 7 - xpos;
|
|
if (ypos > 31) ypos -= 32;
|
|
if (!(data & mask)) continue;
|
|
if (state.screen[x + xpos][ypos]){
|
|
state.registers[0xF] = 1;
|
|
}
|
|
state.screen[x + xpos][ypos] ^= 1;
|
|
}
|
|
}
|
|
|
|
if (DEBUG) printf("Drawing");
|
|
if (!TDISP) break;
|
|
printf("\n");
|
|
for (int y = 0; y < 32; ++y) {
|
|
for (int x = 0; x < 64; ++x) {
|
|
printf(state.screen[x][y] ? "O" : " ");
|
|
}
|
|
printf("\n");
|
|
}
|
|
|
|
break;
|
|
|
|
case 0xE000: // input handling
|
|
reg = (opcode & 0x0F00) >> 8;
|
|
switch ((byte) opcode)
|
|
{
|
|
case 0x9E: // skip if button pressed
|
|
if (DEBUG) printf("Testing if %i is pressed", state.registers[reg]);
|
|
if (state.input[state.registers[reg]]) state.program_counter += 2;
|
|
break;
|
|
|
|
case 0xA1: // skip if button *not* pressed
|
|
if (DEBUG) printf("Testing if %i is not pressed", state.registers[reg]);
|
|
if (!state.input[state.registers[reg]]) state.program_counter += 2;
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case 0xF000: // misc (timer, input, sound, memory)
|
|
reg = (opcode & 0x0F00) >> 8;
|
|
switch ((byte) opcode)
|
|
{
|
|
case 0x07: // set reg to delay time
|
|
if (DEBUG) printf("Copying delay timer to reg %i (%hx)", reg, state.registers[reg]);
|
|
state.registers[reg] = delay;
|
|
break;
|
|
|
|
case 0x0A: // wait for input and store it in reg
|
|
wait = 1;
|
|
store = reg;
|
|
break;
|
|
|
|
case 0x15: // set delay timer to value in reg
|
|
delay = state.registers[reg];
|
|
break;
|
|
|
|
case 0x18: // set sound timer to value in reg
|
|
sound = state.registers[reg];
|
|
break;
|
|
|
|
case 0x1E: // Add value stored in VX to I
|
|
state.address_I += state.registers[reg];
|
|
if (DEBUG) printf("Adding reg %i (%hx) to I (%x)", reg, state.registers[reg], state.address_I);
|
|
break;
|
|
|
|
case 0x29: // set I to address of font character
|
|
state.address_I = state.registers[reg] * 5;
|
|
break;
|
|
|
|
case 0x33:; // encode value in reg as a BCD at I, I+1, I+2
|
|
int value = state.registers[reg];
|
|
state.memory[state.address_I] = value / 100;
|
|
state.memory[state.address_I + 1] = (value / 10) % 10;
|
|
state.memory[state.address_I + 2] = value % 10;
|
|
break;
|
|
|
|
case 0x55: // dump registers V0 -> VX at I
|
|
for (int i = 0; i <= reg; ++i)
|
|
state.memory[state.address_I + i] = state.registers[i];
|
|
break;
|
|
|
|
case 0x65: // retrieve registers V0 -> VX from I
|
|
for (int i = 0; i <= reg; ++i)
|
|
state.registers[i] = state.memory[state.address_I + i];
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
if (DEBUG) printf("%x", opcode);
|
|
break;
|
|
}
|
|
if (DEBUG) printf("\n");
|
|
|
|
if (ops < 10)
|
|
{
|
|
ops++;
|
|
goto loop;
|
|
}
|
|
|
|
for (int x = 0; x < 64; ++x) {
|
|
for (int y = 0; y < 32; ++y)
|
|
{
|
|
SDL_Rect pixel = { .x = (x * 8), .y = (y * 8), .w = 8, .h = 8};
|
|
if (state.screen[x][y])
|
|
{
|
|
SDL_SetRenderDrawColor(renderer, 0xff, 0x69, 0xb4, 0xFF);
|
|
SDL_RenderFillRect(renderer, &pixel);
|
|
}
|
|
else
|
|
{
|
|
SDL_SetRenderDrawColor(renderer, 0, 0, 0, 0xFF);
|
|
SDL_RenderFillRect(renderer, &pixel);
|
|
}
|
|
}
|
|
}
|
|
|
|
SDL_RenderPresent(renderer);
|
|
|
|
SDL_Delay(16);
|
|
// SDL_DestroyWindow(window);
|
|
// SDL_Quit();
|
|
}
|
|
} |