@@ -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); | |||
} | |||
} | |||
@@ -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 <n> 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 <in> [<out>] convert z80 assembly source code into a\n" | |||
" binary file that can be run by crater\n" | |||
" -d, --disassemble <in> [<out>] 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); | |||
@@ -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); | |||
@@ -2,105 +2,155 @@ | |||
Released under the terms of the MIT License. See LICENSE for details. */ | |||
#include <signal.h> | |||
#include <stdint.h> | |||
#include <SDL.h> | |||
#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; | |||
} |
@@ -3,8 +3,10 @@ | |||
#pragma once | |||
#include "gamegear.h" | |||
#include <stdbool.h> | |||
#include "rom.h" | |||
/* Functions */ | |||
void emulate(GameGear*); | |||
void emulate(ROM*, bool, unsigned); |
@@ -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; | |||
} | |||
/* | |||
@@ -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*); |
@@ -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); | |||
} | |||
/* | |||
@@ -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*); |
@@ -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); | |||
} | |||
@@ -14,6 +14,8 @@ | |||
/* Structs */ | |||
typedef struct { | |||
uint32_t *pixels; | |||
uint8_t *vram; | |||
uint8_t *cram; | |||
uint8_t regs[VDP_REGS]; | |||