Browse Source

Clean up and refactor.

master
Ben Kurtovic 8 years ago
parent
commit
de2cf71068
11 changed files with 176 additions and 94 deletions
  1. +4
    -10
      crater.c
  2. +21
    -11
      src/config.c
  3. +3
    -3
      src/disassembler.c
  4. +90
    -40
      src/emulator.c
  5. +4
    -2
      src/emulator.h
  6. +24
    -3
      src/gamegear.c
  7. +9
    -3
      src/gamegear.h
  8. +5
    -11
      src/rom.c
  9. +1
    -1
      src/rom.h
  10. +13
    -10
      src/vdp.c
  11. +2
    -0
      src/vdp.h

+ 4
- 10
crater.c View File

@@ -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);
}
}



+ 21
- 11
src/config.c View File

@@ -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);


+ 3
- 3
src/disassembler.c View File

@@ -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);


+ 90
- 40
src/emulator.c View File

@@ -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;
}

+ 4
- 2
src/emulator.h View File

@@ -3,8 +3,10 @@

#pragma once

#include "gamegear.h"
#include <stdbool.h>

#include "rom.h"

/* Functions */

void emulate(GameGear*);
void emulate(ROM*, bool, unsigned);

+ 24
- 3
src/gamegear.c View File

@@ -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;
}

/*


+ 9
- 3
src/gamegear.h View File

@@ -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*);

+ 5
- 11
src/rom.c View File

@@ -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);
}

/*


+ 1
- 1
src/rom.h View File

@@ -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*);

+ 13
- 10
src/vdp.c View File

@@ -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);
}


+ 2
- 0
src/vdp.h View File

@@ -14,6 +14,8 @@
/* Structs */

typedef struct {
uint32_t *pixels;

uint8_t *vram;
uint8_t *cram;
uint8_t regs[VDP_REGS];


Loading…
Cancel
Save