diff --git a/crater.c b/crater.c index 3f01739..3c26f5b 100644 --- a/crater.c +++ b/crater.c @@ -1,190 +1,42 @@ /* Copyright (C) 2014-2015 Ben Kurtovic Released under the terms of the MIT License. See LICENSE for details. */ -#include -#include -#include -#include #include -#include -#include -#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" -"\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 scale the game screen by an integer factor\n" -" (applies to windowed mode only)\n" -" 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 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 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; } diff --git a/src/config.c b/src/config.c new file mode 100644 index 0000000..2addc36 --- /dev/null +++ b/src/config.c @@ -0,0 +1,340 @@ +/* Copyright (C) 2014-2015 Ben Kurtovic + Released under the terms of the MIT License. See LICENSE for details. */ + +#include +#include +#include +#include +#include +#include + +#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" +"\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 scale the game screen by an integer factor\n" +" (applies to windowed mode only)\n" +" 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 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 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 diff --git a/src/config.h b/src/config.h new file mode 100644 index 0000000..d8067dc --- /dev/null +++ b/src/config.h @@ -0,0 +1,38 @@ +/* Copyright (C) 2014-2015 Ben Kurtovic + Released under the terms of the MIT License. See LICENSE for details. */ + +#pragma once +#include + +#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 diff --git a/src/errors.h b/src/errors.h deleted file mode 100644 index 8287b16..0000000 --- a/src/errors.h +++ /dev/null @@ -1,27 +0,0 @@ -/* Copyright (C) 2014-2015 Ben Kurtovic - Released under the terms of the MIT License. See LICENSE for details. */ - -#pragma once - -#include -#include -#include - -/* 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") diff --git a/src/logging.h b/src/logging.h new file mode 100644 index 0000000..179a7c6 --- /dev/null +++ b/src/logging.h @@ -0,0 +1,35 @@ +/* Copyright (C) 2014-2015 Ben Kurtovic + Released under the terms of the MIT License. See LICENSE for details. */ + +#pragma once + +#include +#include +#include +#include + +/* 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") diff --git a/src/rom.c b/src/rom.c index d9f0d7f..109047a 100644 --- a/src/rom.c +++ b/src/rom.c @@ -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); } diff --git a/src/rom.h b/src/rom.h index 7923d14..5d493cc 100644 --- a/src/rom.h +++ b/src/rom.h @@ -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*); diff --git a/src/z80.h b/src/z80.h index 8dff618..7ed55a1 100644 --- a/src/z80.h +++ b/src/z80.h @@ -10,4 +10,4 @@ typedef struct { int pc; -} z80_type; +} Z80;