/* * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or *(at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include #include #include #include #include "chip8.h" /* references * https://austinmorlan.com/posts/chip8_emulator/ * http://www.multigesture.net/articles/how-to-write-an-emulator-chip-8-interpreter/ * http://mattmik.com/files/chip8/mastering/chip8.html (lots of info) * http://devernay.free.fr/hacks/chip8/C8TECH10.HTM#0.1 * http://www.emulator101.com/chip-8-sprites.html * https://slack-files.com/T3CH37TNX-F3RKEUKL4-b05ab4930d?nojsmode=1 */ #define DEFAULT_VIDEO_SCALE 10 #define DEFAULT_FPS 500 SDL_Window *window; SDL_Renderer *renderer; SDL_Texture *texture; void init_video(); void update_video(); void toggle_pixel(int x, int y); void quit(); void usage(char *); int handle_sdl_events(); void handle_key_down(int keycode); void handle_key_up(int keycode); uint8_t get_chip8_key(int keycode); void print_registers(); int video_scale = DEFAULT_VIDEO_SCALE; int fps = DEFAULT_FPS; int step_cycle = 0; char *rom = NULL; extern uint32_t video[WIDTH*HEIGHT]; extern int draw_flag; extern uint8_t key[KEY_SIZE]; extern uint8_t V[REGISTER_COUNT]; extern uint16_t I; extern uint16_t PC; extern int8_t SP; extern uint8_t delay_timer; extern uint8_t sound_timer; extern uint16_t stack[STACK_SIZE]; void quit() { SDL_DestroyTexture(texture); SDL_DestroyRenderer(renderer); SDL_DestroyWindow(window); SDL_Quit(); } void init_video() { SDL_Init(SDL_INIT_VIDEO); window = SDL_CreateWindow("chip8 interpreter", 0, 0, WIDTH*video_scale, HEIGHT*video_scale, SDL_WINDOW_SHOWN); renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED); texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_RGBA32, SDL_TEXTUREACCESS_STREAMING, WIDTH, HEIGHT); } void update_video() { SDL_UpdateTexture(texture, NULL, video, sizeof(video[0])*WIDTH); SDL_RenderClear(renderer); SDL_RenderCopy(renderer, texture, NULL, NULL); SDL_RenderPresent(renderer); } void toggle_pixel(int x, int y) { // TODO: clean uint32_t dos = video[WIDTH*y+x]; if (dos <= 0) video[WIDTH*y+x] = PIXEL_COLOR; else video[WIDTH*y+x] = 0; } void print_registers() { puts("DUMP"); puts("--------------------------"); for (int reg = 0; reg < REGISTER_COUNT; reg++) { printf("V[0x%01X] = 0x%02X\n", reg, V[reg]); } // minus 2 on PC because we increment PC by 2 at the start of decoding printf("PC = 0x%03X\nI = 0x%03X\nSP = 0x%02X\ndelay_timer = 0x%02X\nsound_timer = 0x%02X\n", PC-2, I, SP, delay_timer, sound_timer); puts("STACK:"); for (int i = 0; i < 16; i++) { printf("[%d] = 0x%03X", i, stack[i]); if (i == 7) puts(""); } puts(""); puts("--------------------------"); } void handle_key_down(int keycode) { switch (keycode) { /* detect interpreter debug keys */ case SDLK_p: print_registers(); break; case SDLK_PERIOD: step_cycle = 1; break; default: break; } uint8_t k = get_chip8_key(keycode); if (k > 0xF) return; /* unknown key */ key[k] = 1; } void handle_key_up(int keycode) { uint8_t k = get_chip8_key(keycode); if (k > 0xF) return; /* unknown key */ key[k] = 0; } uint8_t get_chip8_key(int keycode) { switch(keycode) { case SDLK_1: return 0x1; case SDLK_2: return 0x2; case SDLK_3: return 0x3; case SDLK_4: return 0xC; case SDLK_q: return 0x4; case SDLK_w: return 0x5; case SDLK_e: return 0x6; case SDLK_r: return 0xD; case SDLK_a: return 0x7; case SDLK_s: return 0x8; case SDLK_d: return 0x9; case SDLK_f: return 0xE; case SDLK_z: return 0xA; case SDLK_x: return 0x0; case SDLK_c: return 0xB; case SDLK_v: return 0xF; default: return 0xFF; } } int handle_sdl_events() { SDL_Event e; while (SDL_PollEvent(&e) != 0) { switch (e.type) { case SDL_QUIT: return 1; case SDL_KEYDOWN: { switch(e.key.keysym.sym) { case SDLK_ESCAPE: return 1; default: handle_key_down(e.key.keysym.sym); break; } break; } case SDL_KEYUP: handle_key_up(e.key.keysym.sym); break; default: break; } } return 0; } int main(int argc, char *argv[]) { char opt; int index; while ((opt = getopt(argc, argv, "s:f:")) != -1) { switch (opt) { case 's': video_scale = atoi(optarg); break; case 'f': fps = atoi(optarg); break; default: break; } } for (index = optind; index < argc; index++) { rom = argv[index]; } chip8_init(); if (!load_rom(rom)) { fprintf(stderr, "cannot start interpreter\n"); exit(EXIT_FAILURE); } init_video(); const uint32_t frame_delay = 1000/fps; uint32_t frame_start; uint32_t frame_time; int do_quit = 0; while(!do_quit) { frame_start = SDL_GetTicks(); // logic do_quit = handle_sdl_events(); chip8_cycle(); chip8_timer_cycle(); if (draw_flag) { update_video(); draw_flag = 0; } frame_time = SDL_GetTicks() - frame_start; if (frame_delay > frame_time) { SDL_Delay(frame_delay - frame_time); } } quit(); return EXIT_SUCCESS; }