/* * 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 "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 VIDEO_SCALE 5 #define STEPPING 0 // set to 1 to step manually through program 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 = 5; int step_cycle = 0; 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 usage(char *program) { printf("usage: %s [scale] [speed] [romfile]\nscale - pixel scaling (~5 recommended)\nspeed - how many cycles per second should be run (60-1000 or so, depends on the game)\n", program); } 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[]) { if (argc < 4) { usage(argv[0]); exit(EXIT_FAILURE); } VIDEO_SCALE = atoi(argv[1]); chip8_init(); if (!load_rom(argv[3])) { fprintf(stderr, "cannot start interpreter\n"); exit(EXIT_FAILURE); } init_video(); const int fps = atoi(argv[2]); 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(); if (STEPPING) { if (step_cycle == 1) { chip8_cycle(); if (draw_flag) { update_video(); draw_flag = 0; } step_cycle = 0; } } else { chip8_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; }