#include #include #include #include #include #include #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 frame = 0; int press = 0; int wait = 0; int store = 0; while (1) { if (DEBUG) printf("0x%x ", state.program_counter); if (frame == 10) { frame = 0; 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; } 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() % 255) & ((byte) opcode); if (DEBUG) printf("Storing random in %i (%x)\n", reg, state.registers[reg]); break; case 0xD000: // draw sprite 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: if (DEBUG) printf("Copying delay timer to reg %i (%hx)", reg, state.registers[reg]); state.registers[reg] = delay; break; case 0x0A: wait = 1; store = reg; break; case 0x15: delay = state.registers[reg]; break; case 0x18: sound = state.registers[reg]; break; case 0x1E: 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: state.address_I = state.registers[reg] * 5; break; case 0x33:; 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: for (int i = 0; i <= reg; ++i) state.memory[state.address_I + i] = state.registers[i]; break; case 0x65: for (int i = 0; i <= reg + 1; ++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"); 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(1); ++frame; // SDL_DestroyWindow(window); // SDL_Quit(); } }