From de2cf71068cf3134ec4b50e9e3aef66c1ded45e4 Mon Sep 17 00:00:00 2001 From: Ben Kurtovic Date: Sat, 30 Apr 2016 22:21:22 -0500 Subject: [PATCH] Clean up and refactor. --- crater.c | 14 ++---- src/config.c | 32 ++++++++----- src/disassembler.c | 6 +-- src/emulator.c | 130 ++++++++++++++++++++++++++++++++++++----------------- src/emulator.h | 6 ++- src/gamegear.c | 27 +++++++++-- src/gamegear.h | 12 +++-- src/rom.c | 16 +++---- src/rom.h | 2 +- src/vdp.c | 23 +++++----- src/vdp.h | 2 + 11 files changed, 176 insertions(+), 94 deletions(-) diff --git a/crater.c b/crater.c index 2cd7195..b4138df 100644 --- a/crater.c +++ b/crater.c @@ -8,7 +8,6 @@ #include "src/config.h" #include "src/disassembler.h" #include "src/emulator.h" -#include "src/gamegear.h" #include "src/logging.h" #include "src/rom.h" @@ -35,21 +34,16 @@ int main(int argc, char *argv[]) retval = disassemble_file(config->src_path, config->dst_path); retval = retval ? EXIT_SUCCESS : EXIT_FAILURE; } else { - ROM *rom; + ROM rom; const char* errmsg; if ((errmsg = rom_open(&rom, config->rom_path))) { ERROR("couldn't load ROM image '%s': %s", config->rom_path, errmsg) retval = EXIT_FAILURE; } else { - GameGear *gg = gamegear_create(); - - printf("crater: emulating: %s\n", rom->name); - gamegear_load(gg, rom); - emulate(gg); - - gamegear_destroy(gg); - rom_close(rom); + printf("crater: emulating: %s\n", rom.name); + emulate(&rom, config->fullscreen, config->scale); + rom_close(&rom); } } diff --git a/src/config.c b/src/config.c index f6c9518..8cda225 100644 --- a/src/config.c +++ b/src/config.c @@ -38,7 +38,7 @@ static void print_help(const char *arg1) " -g, --debug show logging information while running; add twice (-gg)\n" " to show more detailed logs, including an emulator trace\n" " -s, --scale scale the game screen by an integer factor\n" -" (applies to windowed mode only)\n" +" (applies to windowed mode only; defaults to 4)\n" " -a, --assemble [] convert z80 assembly source code into a\n" " binary file that can be run by crater\n" " -d, --disassemble [] convert a binary file into z80 assembly\n" @@ -307,7 +307,7 @@ static int parse_args(Config *config, int argc, char *argv[]) filename based on the input file, replacing its extension with ".gg" or ".asm" (or adding it, if none is present). */ -static void guess_assembler_output_file(Config* config) +static void guess_assembler_output_file(Config *config) { char *src = config->src_path, *ptr = src + strlen(src) - 1, *ext = config->assemble ? ".gg" : ".asm"; @@ -326,28 +326,39 @@ static void guess_assembler_output_file(Config* config) /* Ensure that the combination of arguments in a config object are valid. - - Some modifications are made in the case of missing arguments, like guessing - the filenames for assembler output files. */ -static bool sanity_check(Config* config) +static bool sanity_check(Config *config) { bool assembler = config->assemble || config->disassemble; - if (config->fullscreen && config->scale > 1) { + if (config->fullscreen && config->scale) { ERROR("cannot specify a scale in fullscreen mode") return false; } else if (config->assemble && config->disassemble) { ERROR("cannot assemble and disassemble at the same time") return false; - } else if (assembler && (config->fullscreen || config->scale > 1)) { + } else if (assembler && (config->fullscreen || config->scale)) { ERROR("cannot specify emulator options in assembler mode") return false; } else if (assembler && !config->src_path) { ERROR("assembler mode requires an input file") return false; } + return true; +} +/* + Set some default values for missing arguments. + + Some additional sanity checking is done. +*/ +static bool set_defaults(Config *config) +{ + bool assembler = config->assemble || config->disassemble; + + if (!config->scale) { + config->scale = 4; + } if (assembler && !config->dst_path) { guess_assembler_output_file(config); } @@ -355,7 +366,6 @@ static bool sanity_check(Config* config) ERROR("refusing to overwrite the assembler input file; pass -r to override") return false; } - return true; } @@ -376,14 +386,14 @@ int config_create(Config** config_ptr, int argc, char* argv[]) config->assemble = false; config->disassemble = false; config->fullscreen = false; - config->scale = 1; + config->scale = 0; config->rom_path = NULL; config->src_path = NULL; config->dst_path = NULL; config->overwrite = false; retval = parse_args(config, argc, argv); - if (retval == CONFIG_OK && !sanity_check(config)) + if (retval == CONFIG_OK && !(sanity_check(config) && set_defaults(config))) retval = CONFIG_EXIT_FAILURE; if (retval != CONFIG_OK) { config_destroy(config); diff --git a/src/disassembler.c b/src/disassembler.c index 699bbdc..9dbc565 100644 --- a/src/disassembler.c +++ b/src/disassembler.c @@ -406,7 +406,7 @@ static bool write_disassembly(const char *path, char **lines) */ bool disassemble_file(const char *src_path, const char *dst_path) { - ROM *rom; + ROM rom; const char *errmsg; char **lines; @@ -416,8 +416,8 @@ bool disassemble_file(const char *src_path, const char *dst_path) return false; } - lines = disassemble(rom); - rom_close(rom); + lines = disassemble(&rom); + rom_close(&rom); DEBUG("Writing output file") return write_disassembly(dst_path, lines); diff --git a/src/emulator.c b/src/emulator.c index 8c5cab7..1347f1f 100644 --- a/src/emulator.c +++ b/src/emulator.c @@ -2,105 +2,155 @@ Released under the terms of the MIT License. See LICENSE for details. */ #include +#include #include #include "emulator.h" +#include "gamegear.h" #include "logging.h" +#include "util.h" -#define SCREEN_WIDTH 3 * (160 + 96) // TODO: define elsewhere; use scale -#define SCREEN_HEIGHT 3 * (144 + 48) +typedef struct { + GameGear *gg; + SDL_Window *window; + SDL_Renderer *renderer; + SDL_Texture *texture; + uint32_t *pixels; +} Emulator; -static GameGear *global_gg; - -static SDL_Window *window; -SDL_Renderer* renderer; +static Emulator emu; /* - Signal handler for SIGINT. + Signal handler for SIGINT. Tells the GameGear to power off, if it exists. */ static void handle_sigint(int sig) { (void) sig; - if (global_gg) - gamegear_power_off(global_gg); + if (emu.gg) + gamegear_power_off(emu.gg); // Safe! } /* - TODO: ... + Set up SDL for drawing the game. */ -static void setup_graphics() +static void setup_graphics(bool fullscreen, unsigned scale) { if (SDL_Init(SDL_INIT_VIDEO) < 0) FATAL("SDL failed to initialize: %s", SDL_GetError()); - SDL_CreateWindowAndRenderer(SCREEN_WIDTH, SCREEN_HEIGHT, - SDL_WINDOW_BORDERLESS/* |SDL_WINDOW_OPENGL|SDL_WINDOW_RESIZABLE ? */, - &window, &renderer); + uint32_t flags; + if (fullscreen) + flags = SDL_WINDOW_FULLSCREEN_DESKTOP; + else + flags = SDL_WINDOW_BORDERLESS|SDL_WINDOW_RESIZABLE; + + SDL_CreateWindowAndRenderer( + scale * GG_SCREEN_WIDTH, scale * GG_SCREEN_HEIGHT, + flags, &emu.window, &emu.renderer); - if (!window) + if (!emu.window) FATAL("SDL failed to create a window: %s", SDL_GetError()); - if (!renderer) + if (!emu.renderer) FATAL("SDL failed to create a renderer: %s", SDL_GetError()); - SDL_SetRenderDrawColor(renderer, 0x33, 0x33, 0x33, 0xFF); - SDL_RenderClear(renderer); - SDL_RenderPresent(renderer); + emu.texture = SDL_CreateTexture(emu.renderer, SDL_PIXELFORMAT_ARGB8888, + SDL_TEXTUREACCESS_STREAMING, GG_SCREEN_WIDTH, GG_SCREEN_HEIGHT); + + if (!emu.texture) + FATAL("SDL failed to create a texture: %s", SDL_GetError()); + + emu.pixels = cr_malloc( + sizeof(uint32_t) * GG_SCREEN_WIDTH * GG_SCREEN_HEIGHT); + + SDL_RenderSetLogicalSize(emu.renderer, GG_SCREEN_WIDTH, GG_SCREEN_HEIGHT); + SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "nearest"); + SDL_SetWindowTitle(emu.window, "crater"); + SDL_ShowCursor(SDL_DISABLE); + + SDL_SetRenderDrawColor(emu.renderer, 0x00, 0x00, 0x00, 0xFF); + SDL_RenderClear(emu.renderer); + SDL_RenderPresent(emu.renderer); } /* - GameGear callback: handle SDL logic at the end of a frame. + Actually send the pixel data to the screen. */ -static void draw_frame(GameGear *gg) +static void draw_frame() { - SDL_RenderPresent(renderer); + SDL_UpdateTexture(emu.texture, NULL, emu.pixels, + GG_SCREEN_WIDTH * sizeof(uint32_t)); + SDL_SetRenderDrawColor(emu.renderer, 0x00, 0x00, 0x00, 0xFF); + SDL_RenderClear(emu.renderer); + SDL_RenderCopy(emu.renderer, emu.texture, NULL, NULL); + SDL_RenderPresent(emu.renderer); +} +/* + Handle SDL events, mainly quit events and button presses. +*/ +static void handle_events(GameGear *gg) +{ SDL_Event e; while (SDL_PollEvent(&e)) { if (e.type == SDL_QUIT) { gamegear_power_off(gg); return; } + // TODO: buttons } +} - SDL_SetRenderDrawColor(renderer, 0x33, 0x33, 0x33, 0xFF); - SDL_RenderClear(renderer); +/* + GameGear callback: Draw the current frame and handle SDL event logic. +*/ +static void frame_callback(GameGear *gg) +{ + draw_frame(); + handle_events(gg); } /* - TODO: ... + Clean up SDL stuff allocated in setup_graphics(). */ static void cleanup_graphics() { - SDL_DestroyRenderer(renderer); - SDL_DestroyWindow(window); - window = NULL; - renderer = NULL; + free(emu.pixels); + SDL_DestroyTexture(emu.texture); + SDL_DestroyRenderer(emu.renderer); + SDL_DestroyWindow(emu.window); SDL_Quit(); + + emu.window = NULL; + emu.renderer = NULL; + emu.texture = NULL; } /* - Emulate a Game Gear. Handle I/O with the host computer. + Emulate a ROM in a Game Gear while handling I/O with the host computer. Block until emulation is finished. */ -void emulate(GameGear *gg) +void emulate(ROM *rom, bool fullscreen, unsigned scale) { - global_gg = gg; + emu.gg = gamegear_create(); signal(SIGINT, handle_sigint); - gamegear_set_callback(gg, draw_frame); - setup_graphics(); + setup_graphics(fullscreen, scale); + + gamegear_attach_callback(emu.gg, frame_callback); + gamegear_attach_display(emu.gg, emu.pixels); + gamegear_load(emu.gg, rom); - gamegear_simulate(gg); + gamegear_simulate(emu.gg); - if (gamegear_get_exception(gg)) - ERROR("caught exception: %s", gamegear_get_exception(gg)) + if (gamegear_get_exception(emu.gg)) + ERROR("caught exception: %s", gamegear_get_exception(emu.gg)) else WARN("caught signal, stopping...") if (DEBUG_LEVEL) - gamegear_print_state(gg); + gamegear_print_state(emu.gg); cleanup_graphics(); - gamegear_clear_callback(gg); signal(SIGINT, SIG_DFL); - global_gg = NULL; + gamegear_destroy(emu.gg); + emu.gg = NULL; } diff --git a/src/emulator.h b/src/emulator.h index 844f0b9..d4b52b6 100644 --- a/src/emulator.h +++ b/src/emulator.h @@ -3,8 +3,10 @@ #pragma once -#include "gamegear.h" +#include + +#include "rom.h" /* Functions */ -void emulate(GameGear*); +void emulate(ROM*, bool, unsigned); diff --git a/src/gamegear.c b/src/gamegear.c index 73f2084..e7aa5aa 100644 --- a/src/gamegear.c +++ b/src/gamegear.c @@ -18,6 +18,11 @@ /* Create and return a pointer to a new GameGear object. + + The GameGear operates in headless mode by default (i.e., without any + noticeable output). You'll probably want to attach a frame-completion + callback with gamegear_attach_callback() and a display with + gamegear_attach_display(). */ GameGear* gamegear_create() { @@ -93,18 +98,34 @@ void gamegear_power_off(GameGear *gg) /* Set a callback to be triggered whenever the GameGear completes a frame. + + The callback is passed a reference to the GameGear object. */ -void gamegear_set_callback(GameGear *gg, GGFrameCallback callback) +void gamegear_attach_callback(GameGear *gg, GGFrameCallback callback) { gg->callback = callback; } /* - Reset the GameGear's frame callback function. + Set a display to written to whenever the GameGear draws a pixel. + + The array must be (GG_SCREEN_WIDTH * GG_SCREEN_HEIGHT) pixels large, where + each pixel is a 32-bit integer in ARGB order (i.e., A is the top 8 bits). +*/ +void gamegear_attach_display(GameGear *gg, uint32_t *pixels) +{ + gg->vdp.pixels = pixels; +} + +/* + Reset any callbacks or displays attached to the GameGear. + + This returns the GameGear to headless mode. */ -void gamegear_clear_callback(GameGear *gg) +void gamegear_detach(GameGear *gg) { gg->callback = NULL; + gg->vdp.pixels = NULL; } /* diff --git a/src/gamegear.h b/src/gamegear.h index f16324f..de3e6b3 100644 --- a/src/gamegear.h +++ b/src/gamegear.h @@ -12,6 +12,9 @@ #include "rom.h" #include "z80.h" +#define GG_SCREEN_WIDTH (160 + 96) +#define GG_SCREEN_HEIGHT (144 + 48) + #define GG_FPS 60 #define GG_EXC_BUFF_SIZE 128 @@ -37,8 +40,11 @@ GameGear* gamegear_create(); void gamegear_destroy(GameGear*); void gamegear_load(GameGear*, const ROM*); void gamegear_simulate(GameGear*); -void gamegear_power_off(GameGear*); -void gamegear_set_callback(GameGear*, GGFrameCallback); -void gamegear_clear_callback(GameGear*); +void gamegear_power_off(GameGear*); // TODO: generic "gamegear_input()" with a power-off option + +void gamegear_attach_callback(GameGear*, GGFrameCallback); +void gamegear_attach_display(GameGear*, uint32_t*); +void gamegear_detach(GameGear*); + const char* gamegear_get_exception(GameGear*); void gamegear_print_state(const GameGear*); diff --git a/src/rom.c b/src/rom.c index 4e5294e..d2e9215 100644 --- a/src/rom.c +++ b/src/rom.c @@ -154,15 +154,13 @@ static bool find_and_read_header(ROM *rom) } /* - Create and load a ROM image located at the given path. + Load a ROM image located at the given path. - rom_ptr will point to the new object if created successfully, and NULL will - be returned. Otherwise, rom_ptr will not be modified and an error string - will be returned. The error string should not be freed. + NULL will be returned if the ROM is opened successfully. Otherwise, and an + error string will be returned. The error string should not be freed. */ -const char* rom_open(ROM **rom_ptr, const char *path) +const char* rom_open(ROM *rom, const char *path) { - ROM *rom; FILE *fp; struct stat st; @@ -178,8 +176,6 @@ const char* rom_open(ROM **rom_ptr, const char *path) return (st.st_mode & S_IFDIR) ? rom_err_isdir : rom_err_notfile; } - rom = cr_malloc(sizeof(ROM)); - // Set defaults: rom->name = NULL; rom->data = NULL; @@ -227,18 +223,16 @@ const char* rom_open(ROM **rom_ptr, const char *path) return rom_err_sms; } - *rom_ptr = rom; return NULL; } /* - Free a ROM object previously created with rom_open(). + Free memory previously allocated by the ROM during rom_open(). */ void rom_close(ROM *rom) { free(rom->name); free(rom->data); - free(rom); } /* diff --git a/src/rom.h b/src/rom.h index 2fba0fc..7c356b0 100644 --- a/src/rom.h +++ b/src/rom.h @@ -42,7 +42,7 @@ typedef struct { /* Functions */ -const char* rom_open(ROM**, const char*); +const char* rom_open(ROM*, const char*); void rom_close(ROM*); const char* rom_product(const ROM*); const char* rom_region(const ROM*); diff --git a/src/vdp.c b/src/vdp.c index f2c8f28..77ece3d 100644 --- a/src/vdp.c +++ b/src/vdp.c @@ -12,13 +12,16 @@ #define CODE_REG_WRITE 2 #define CODE_CRAM_WRITE 3 -extern SDL_Renderer* renderer; - /* Initialize the Video Display Processor (VDP). + + The VDP will write to its pixels array whenever it draws a scanline. It + defaults to NULL, but you should set it to something if you want to see its + output. */ void vdp_init(VDP *vdp) { + vdp->pixels = NULL; vdp->vram = cr_malloc(sizeof(uint8_t) * VDP_VRAM_SIZE); vdp->cram = cr_malloc(sizeof(uint8_t) * VDP_CRAM_SIZE); memset(vdp->vram, 0x00, VDP_VRAM_SIZE); @@ -105,19 +108,16 @@ static uint8_t get_backdrop_addr(const VDP *vdp) /* TODO: ... */ -static void draw_pixel(uint8_t y, uint8_t x, uint16_t color) +static void draw_pixel(VDP *vdp, uint8_t y, uint8_t x, uint16_t color) { uint8_t r = 0x11 * (color & 0x000F); uint8_t g = 0x11 * ((color & 0x00F0) >> 4); uint8_t b = 0x11 * ((color & 0x0F00) >> 8); - SDL_SetRenderDrawColor(renderer, r, g, b, 0xFF); + uint32_t argb = (0xFF << 24) + (r << 16) + (g << 8) + b; - uint8_t i, j; - for (i = 0; i < 3; i++) { - for (j = 0; j < 3; j++) - SDL_RenderDrawPoint(renderer, 3 * x + i, 3 * y + j); - } + // TODO + vdp->pixels[y * (160 + 96) + x] = argb; } /* @@ -154,7 +154,7 @@ static void draw_background(VDP *vdp) (((bp3 >> i) & 1) << 3); color = vdp->cram[2 * idx] + (vdp->cram[2 * idx + 1] << 8); offx = hflip ? (col * 8 + (7 - i)) : (col * 8 + i); - draw_pixel(vdp->v_counter, offx, color); + draw_pixel(vdp, vdp->v_counter, offx, color); } } } @@ -181,6 +181,9 @@ static void draw_sprites(VDP *vdp) */ static void draw_scanline(VDP *vdp) { + if (!vdp->pixels) + return; + draw_background(vdp); draw_sprites(vdp); } diff --git a/src/vdp.h b/src/vdp.h index 46b8d22..7adb197 100644 --- a/src/vdp.h +++ b/src/vdp.h @@ -14,6 +14,8 @@ /* Structs */ typedef struct { + uint32_t *pixels; + uint8_t *vram; uint8_t *cram; uint8_t regs[VDP_REGS];