Browse Source

Refactor config parsing greatly; support new arguments.

master
Ben Kurtovic 9 years ago
parent
commit
7bbf32f52c
8 changed files with 448 additions and 206 deletions
  1. +20
    -168
      crater.c
  2. +340
    -0
      src/config.c
  3. +38
    -0
      src/config.h
  4. +0
    -27
      src/errors.h
  5. +35
    -0
      src/logging.h
  6. +11
    -7
      src/rom.c
  7. +3
    -3
      src/rom.h
  8. +1
    -1
      src/z80.h

+ 20
- 168
crater.c View File

@@ -1,190 +1,42 @@
/* Copyright (C) 2014-2015 Ben Kurtovic <ben.kurtovic@gmail.com>
Released under the terms of the MIT License. See LICENSE for details. */

#include <dirent.h>
#include <errno.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>

#include "src/errors.h"
#include "src/config.h"
#include "src/logging.h"
#include "src/rom.h"
#include "src/version.h"

#define ROMS_DIR "roms"

/* Print command-line help/usage. */
static void print_help(const char *arg1)
{
printf("%s [-h] [-v] [-f] [-s <n>] [<rom_path>] ...\n"
"\n"
"basic options:\n"
" -h, --help show this help message and exit\n"
" -v, --version show crater's version number and exit\n"
" -f, --fullscreen enable fullscreen mode\n"
" -s, --scale <n> scale the game screen by an integer factor\n"
" (applies to windowed mode only)\n"
" <rom_path> path to the rom file to execute; if not given, will look\n"
" in the roms/ directory and prompt the user\n"
"\n"
"advanced options:\n"
" -g, --debug display information about emulation state while running,\n"
" including register and memory values; can also pause\n"
" emulation, set breakpoints, and change state\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 code\n",
arg1);
}

/* Print crater's version. */
static void print_version()
{
printf("crater %s\n", CRATER_VERSION);
}

/* Parse the command-line arguments for any special flags. */
static void parse_args(int argc, char *argv[])
{
char *arg;
int i;

for (i = 1; i < argc; i++) {
arg = argv[i];
if (arg[0] != '-')
continue;
do
arg++;
while (arg[0] == '-');

if (!strcmp(arg, "h") || !strcmp(arg, "help")) {
print_help(argv[0]);
exit(0);
} else if (!strcmp(arg, "v") || !strcmp(arg, "version")) {
print_version();
exit(0);
// f fullscreen
// s scale
// g debug
// a assemble
// d disassemble
} else {
FATAL("unknown argument: %s", argv[i])
}
}
}

/* Return whether the given string ends with the given suffix. */
static bool ends_with(const char *input, const char *suffix)
{
size_t ilen = strlen(input), slen = strlen(suffix);

if (ilen < slen)
return false;
return strcmp(input + (ilen - slen), suffix) == 0;
}

/* Load all potential ROM files in roms/ into a data structure. */
static int get_rom_paths(char ***path_ptr)
{
DIR *dirp;
struct dirent *entry;
char **paths = NULL, *path;
int psize = 8, npaths = 0;

dirp = opendir(ROMS_DIR);
if (dirp) {
paths = malloc(sizeof(char*) * psize);
if (!paths)
OUT_OF_MEMORY()
while ((entry = readdir(dirp))) {
path = entry->d_name;
if (ends_with(path, ".gg") || ends_with(path, ".bin")) {
if (npaths >= psize) {
paths = realloc(paths, sizeof(char*) * (psize *= 2));
if (!paths)
OUT_OF_MEMORY()
}
paths[npaths] = malloc(sizeof(char*) *
(strlen(path) + strlen(ROMS_DIR) + 1));
if (!paths[npaths])
OUT_OF_MEMORY()
strcpy(paths[npaths], ROMS_DIR "/");
strcat(paths[npaths], path);
npaths++;
}
}
closedir(dirp);
} else {
WARN_ERRNO("couldn't open 'roms/'")
}
*path_ptr = paths;
return npaths;
}

/* Find all potential ROM files in the roms/ directory, then ask the user which
one they want to run. */
static char* get_rom_path_from_user()
{
char **paths, *path, *input = NULL;
int npaths, i;
long int index;
size_t size = 0;
ssize_t len;

npaths = get_rom_paths(&paths);
for (i = 0; i < npaths; i++)
printf("[%2d] %s\n", i + 1, paths[i]);
if (npaths)
printf("Enter a ROM number from above, or the path to a ROM image: ");
else
printf("Enter the path to a ROM image: ");

len = getline(&input, &size, stdin);
if (!input)
OUT_OF_MEMORY()
if (len > 0 && input[len - 1] == '\n')
input[len - 1] = '\0';
index = strtol(input, NULL, 10);
if (index < 1 || index > npaths)
path = input;
else
path = paths[index - 1];

for (i = 0; i < npaths; i++) {
if (paths[i] != path)
free(paths[i]);
}
if (paths)
free(paths);
return path;
}

/*
Main function.
*/
int main(int argc, char *argv[])
{
char *rom_path;
rom_type *rom;
Config *config;
ROM *rom;
int retval;

retval = config_create(&config, argc, argv);
if (retval != CONFIG_OK)
return retval == CONFIG_EXIT_SUCCESS ? EXIT_SUCCESS : EXIT_FAILURE;

parse_args(argc, argv);
printf("crater: a Sega Game Gear emulator\n\n");
rom_path = argc > 1 ? argv[1] : get_rom_path_from_user();
if (rom_path[0] == '\0')
FATAL("no image given")

if (!(rom = rom_open(rom_path))) {
#ifdef DEBUG_MODE
config_dump_args(config);
#endif

if (!(rom = rom_open(config->rom_path))) {
if (errno == ENOMEM)
OUT_OF_MEMORY()
else
FATAL_ERRNO("couldn't load ROM image '%s'", rom_path)
FATAL_ERRNO("couldn't load ROM image '%s'", config->rom_path)
}
if (argc <= 1)
free(rom_path);
printf("Loaded ROM image: %s.\n", rom->name);

// TODO: start from here

rom_close(rom);
return 0;
config_destroy(config);
return EXIT_SUCCESS;
}

+ 340
- 0
src/config.c View File

@@ -0,0 +1,340 @@
/* Copyright (C) 2014-2015 Ben Kurtovic <ben.kurtovic@gmail.com>
Released under the terms of the MIT License. See LICENSE for details. */

#include <dirent.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>

#include "config.h"
#include "logging.h"
#include "version.h"

#define ROMS_DIR "roms"

/*
Print command-line help/usage.
*/
static void print_help(const char *arg1)
{
printf("%s [-h] [-v] [-f] [-s <n>] [<rom_path>] ...\n"
"\n"
"basic options:\n"
" -h, --help show this help message and exit\n"
" -v, --version show crater's version number and exit\n"
" -f, --fullscreen enable fullscreen mode\n"
" -s, --scale <n> scale the game screen by an integer factor\n"
" (applies to windowed mode only)\n"
" <rom_path> path to the rom file to execute; if not given, will look\n"
" in the roms/ directory and prompt the user\n"
"\n"
"advanced options:\n"
" -g, --debug display information about emulation state while running,\n"
" including register and memory values; can also pause\n"
" emulation, set breakpoints, and change state\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 code\n",
arg1);
}

/*
Print crater's version.
*/
static void print_version()
{
printf("crater %s\n", CRATER_VERSION);
}

/*
Return whether the given string ends with the given suffix.
*/
static bool ends_with(const char *input, const char *suffix)
{
size_t ilen = strlen(input), slen = strlen(suffix);

if (ilen < slen)
return false;
return strcmp(input + (ilen - slen), suffix) == 0;
}

/*
Load all potential ROM files in roms/ into a data structure.
*/
static int get_rom_paths(char ***path_ptr)
{
DIR *dirp;
struct dirent *entry;
char **paths = NULL, *path;
int psize = 8, npaths = 0;

dirp = opendir(ROMS_DIR);
if (dirp) {
paths = malloc(sizeof(char*) * psize);
if (!paths)
OUT_OF_MEMORY()
while ((entry = readdir(dirp))) {
path = entry->d_name;
if (ends_with(path, ".gg") || ends_with(path, ".bin")) {
if (npaths >= psize) {
paths = realloc(paths, sizeof(char*) * (psize *= 2));
if (!paths)
OUT_OF_MEMORY()
}
paths[npaths] = malloc(sizeof(char*) *
(strlen(path) + strlen(ROMS_DIR) + 1));
if (!paths[npaths])
OUT_OF_MEMORY()
strcpy(paths[npaths], ROMS_DIR "/");
strcat(paths[npaths], path);
npaths++;
}
}
closedir(dirp);
} else {
WARN_ERRNO("couldn't open 'roms/'")
}
*path_ptr = paths;
return npaths;
}

/*
Find all potential ROM files in the roms/ directory, then ask the user
which one they want to run.
*/
static char* get_rom_path_from_user()
{
char **paths, *path, *input = NULL;
int npaths, i;
long int index;
size_t size = 0;
ssize_t len;

npaths = get_rom_paths(&paths);
for (i = 0; i < npaths; i++)
printf("[%2d] %s\n", i + 1, paths[i]);
if (npaths)
printf("Enter a ROM number from above, or the path to a ROM image: ");
else
printf("Enter the path to a ROM image: ");

len = getline(&input, &size, stdin);
if (!input)
OUT_OF_MEMORY()
if (len > 0 && input[len - 1] == '\n')
input[len - 1] = '\0';
index = strtol(input, NULL, 10);
if (index < 1 || index > npaths)
path = input;
else
path = paths[index - 1];

for (i = 0; i < npaths; i++) {
if (paths[i] != path)
free(paths[i]);
}
if (paths)
free(paths);
return path;
}

/*
Parse the command-line arguments for any special flags.
*/
static int parse_args(Config *config, int argc, char *argv[])
{
char *arg, *path;
int i, paths_read = 0;

for (i = 1; i < argc; i++) {
arg = argv[i];
if (arg[0] != '-') {
// Parsing a path or other variable:
if (paths_read >= 2) {
ERROR("too many arguments given - emulator mode accepts one "
"ROM file,\nand assembler mode accepts one input file "
"and one output file")
return CONFIG_EXIT_FAILURE;
}

path = malloc(sizeof(char) * (strlen(arg) + 1));
strcpy(path, arg);

if (paths_read == 1) {
/* If this is the second path given, it can only be an output
file for the assembler. If the assembler is not enabled by
subsequent arguments, we'll throw an error. */
config->dst_path = path;
} else {
/* Otherwise, put the argument in the expected place. If we put
it in rom_path and the assembler is enabled by later
arguments, we'll move it. */
if (config->assemble || config->disassemble)
config->src_path = path;
else
config->rom_path = path;
}

paths_read++;
continue;
}

// Parsing an option:
do
arg++;
while (arg[0] == '-');

if (!strcmp(arg, "h") || !strcmp(arg, "help")) {
print_help(argv[0]);
return CONFIG_EXIT_SUCCESS;
} else if (!strcmp(arg, "v") || !strcmp(arg, "version")) {
print_version();
return CONFIG_EXIT_SUCCESS;
} else if (!strcmp(arg, "f") || !strcmp(arg, "fullscreen")) {
config->fullscreen = true;
} else if (!strcmp(arg, "s") || !strcmp(arg, "scale")) {
if (i++ >= argc) {
ERROR("the scale option requires an argument")
return CONFIG_EXIT_FAILURE;
}
arg = argv[i];
long scale = strtol(arg, NULL, 10);
if (scale <= 0 || scale > SCALE_MAX) {
ERROR("scale factor of %s is not an integer or is out of range", arg)
return CONFIG_EXIT_FAILURE;
}
} else if (!strcmp(arg, "g") || !strcmp(arg, "debug")) {
config->debug = true;
} else if (!strcmp(arg, "a") || !strcmp(arg, "assemble")) {
if (paths_read >= 1) {
config->src_path = config->rom_path;
config->rom_path = NULL;
}
config->assemble = true;
} else if (!strcmp(arg, "d") || !strcmp(arg, "disassemble")) {
if (paths_read >= 1) {
config->src_path = config->rom_path;
config->rom_path = NULL;
}
config->disassemble = true;
} else {
ERROR("unknown argument: %s", argv[i])
return CONFIG_EXIT_FAILURE;
}
}

if (!config->assemble && !config->disassemble && paths_read >= 2) {
ERROR("too many arguments given - emulator mode accepts one ROM file")
return CONFIG_EXIT_FAILURE;
}
if (!config->assemble && !config->disassemble && paths_read == 0) {
path = get_rom_path_from_user();
if (path[0] == '\0') {
ERROR("no ROM image given")
return CONFIG_EXIT_FAILURE;
}
config->rom_path = path;
}

return CONFIG_OK;
}

/*
Ensure that the combination of arguments in a config object are valid.
*/
bool sanity_check(Config* config)
{
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 ((config->assemble || config->disassemble) &&
(config->debug || config->fullscreen || config->scale)) {
ERROR("cannot specify emulator options in assembler mode")
return false;
} else if ((config->assemble || config->disassemble) &&
!(config->src_path && config->dst_path)) {
ERROR("assembler mode requires both an input and an output file")
return false;
}
return true;
}

/*
Create a new config object and load default values into it.
*/
int config_create(Config** config_ptr, int argc, char* argv[])
{
Config *config;
int retval;

if (!(config = malloc(sizeof(Config)))) {
OUT_OF_MEMORY()
return CONFIG_EXIT_FAILURE;
}

config->debug = false;
config->assemble = false;
config->disassemble = false;
config->fullscreen = false;
config->scale = 1;
config->rom_path = NULL;
config->src_path = NULL;
config->dst_path = NULL;

retval = parse_args(config, argc, argv);
if (retval == CONFIG_OK && !sanity_check(config))
retval = CONFIG_EXIT_FAILURE;
if (retval != CONFIG_OK) {
config_destroy(config);
return retval;
}

*config_ptr = config;
return CONFIG_OK;
}

/*
Destroy a config object previously created with config_create().
*/
void config_destroy(Config *config)
{
if (config->rom_path)
free(config->rom_path);
if (config->src_path)
free(config->src_path);
if (config->dst_path)
free(config->dst_path);
free(config);
}

#ifdef DEBUG_MODE
/*
DEBUG FUNCTION: Print out all config arguments to stdout.
*/
void config_dump_args(Config* config)
{
DEBUG("Dumping arguments:")
DEBUG("- fullscreen: %d", config->fullscreen)
DEBUG("- scale: %d", config->scale)
DEBUG("- debug: %d", config->debug)
DEBUG("- assemble: %d", config->assemble)
DEBUG("- disassemble: %d", config->disassemble)
if (config->rom_path)
DEBUG("- rom_path: %s", config->rom_path)
else
DEBUG("- rom_path: NULL")
if (config->src_path)
DEBUG("- src_path: %s", config->src_path)
else
DEBUG("- src_path: NULL")
if (config->dst_path)
DEBUG("- dst_path: %s", config->dst_path)
else
DEBUG("- dst_path: NULL")
}
#endif

+ 38
- 0
src/config.h View File

@@ -0,0 +1,38 @@
/* Copyright (C) 2014-2015 Ben Kurtovic <ben.kurtovic@gmail.com>
Released under the terms of the MIT License. See LICENSE for details. */

#pragma once
#include <stdbool.h>

#define CONFIG_OK 0
#define CONFIG_EXIT_SUCCESS 1
#define CONFIG_EXIT_FAILURE 2

/*
We need some sort of maximum scale - with a native resolution of 160 x 144,
a scale factor of 1024 will let us go up to 163,840 x 147,456 pixels.
No one has a screen this large.
*/
#define SCALE_MAX 1024

/* Structs */

typedef struct {
bool debug;
bool assemble;
bool disassemble;
bool fullscreen;
unsigned scale;
char *rom_path;
char *src_path;
char *dst_path;
} Config;

/* Functions */

int config_create(Config**, int, char*[]);
void config_destroy(Config*);

#ifdef DEBUG_MODE
void config_dump_args(Config*);
#endif

+ 0
- 27
src/errors.h View File

@@ -1,27 +0,0 @@
/* Copyright (C) 2014-2015 Ben Kurtovic <ben.kurtovic@gmail.com>
Released under the terms of the MIT License. See LICENSE for details. */

#pragma once

#include <errno.h>
#include <stdio.h>
#include <stdlib.h>

/* Internal usage only */

#define LOG_MSG(level, extra, after, ...) { \
fprintf(stderr, level ": " __VA_ARGS__); \
extra; \
fprintf(stderr, ".\n"); \
after; \
}
#define PRINT_ERRNO() fprintf(stderr, ": %s", strerror(errno))

/* Public error logging macros */

#define FATAL(...) LOG_MSG("Error", {}, exit(EXIT_FAILURE), __VA_ARGS__)
#define FATAL_ERRNO(...) LOG_MSG("Error", PRINT_ERRNO(), exit(EXIT_FAILURE), __VA_ARGS__)
#define WARN(...) LOG_MSG("Warning", {}, {}, __VA_ARGS__)
#define WARN_ERRNO(...) LOG_MSG("Warning", PRINT_ERRNO(), {}, __VA_ARGS__)

#define OUT_OF_MEMORY() FATAL("couldn't allocate enough memory")

+ 35
- 0
src/logging.h View File

@@ -0,0 +1,35 @@
/* Copyright (C) 2014-2015 Ben Kurtovic <ben.kurtovic@gmail.com>
Released under the terms of the MIT License. See LICENSE for details. */

#pragma once

#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

/* Internal usage only */

#define LOG_MSG(level, extra, tail, after, ...) { \
fprintf(stderr, level ": " __VA_ARGS__); \
extra; \
fprintf(stderr, tail ? ".\n" : "\n"); \
after; \
}
#define PRINT_ERRNO() fprintf(stderr, ": %s", strerror(errno))

/* Public logging macros */

#define FATAL(...) LOG_MSG("Error", {}, 1, exit(EXIT_FAILURE), __VA_ARGS__)
#define FATAL_ERRNO(...) LOG_MSG("Error", PRINT_ERRNO(), 1, exit(EXIT_FAILURE), __VA_ARGS__)
#define ERROR(...) LOG_MSG("Error", {}, 1, {}, __VA_ARGS__)
#define ERROR_ERRNO(...) LOG_MSG("Error", PRINT_ERRNO(), 1, {}, __VA_ARGS__)
#define WARN(...) LOG_MSG("Warning", {}, 1, {}, __VA_ARGS__)
#define WARN_ERRNO(...) LOG_MSG("Warning", PRINT_ERRNO(), 1, {}, __VA_ARGS__)

#ifdef DEBUG_MODE
#define DEBUG(...) LOG_MSG("[DEBUG]", {}, 0, {}, __VA_ARGS__)
#define DEBUG_ERRNO(...) LOG_MSG("[DEBUG]", PRINT_ERRNO(), 0, {}, __VA_ARGS__)
#endif

#define OUT_OF_MEMORY() FATAL("couldn't allocate enough memory")

+ 11
- 7
src/rom.c View File

@@ -9,11 +9,13 @@

#include "rom.h"

/* Create and return a ROM object located at the given path. Return NULL if
there was an error; errno will be set appropriately. */
rom_type* rom_open(const char *path)
/*
Create and return a ROM object located at the given path. Return NULL if
there was an error; errno will be set appropriately.
*/
ROM* rom_open(const char *path)
{
rom_type *rom;
ROM *rom;
FILE* fp;
struct stat s;

@@ -31,7 +33,7 @@ rom_type* rom_open(const char *path)
return NULL;
}

if (!(rom = malloc(sizeof(rom_type)))) {
if (!(rom = malloc(sizeof(ROM)))) {
fclose(fp);
return NULL;
}
@@ -44,8 +46,10 @@ rom_type* rom_open(const char *path)
return rom;
}

/* Free a ROM object previously created with rom_open(). */
void rom_close(rom_type *rom)
/*
Free a ROM object previously created with rom_open().
*/
void rom_close(ROM *rom)
{
free(rom);
}

+ 3
- 3
src/rom.h View File

@@ -8,9 +8,9 @@
typedef struct {
char* name;
char* data;
} rom_type;
} ROM;

/* Functions */

rom_type* rom_open(const char*);
void rom_close(rom_type*);
ROM* rom_open(const char*);
void rom_close(ROM*);

+ 1
- 1
src/z80.h View File

@@ -10,4 +10,4 @@

typedef struct {
int pc;
} z80_type;
} Z80;

Loading…
Cancel
Save