An emulator, assembler, and disassembler for the Sega Game Gear
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

462 lines
14 KiB

  1. /* Copyright (C) 2014-2017 Ben Kurtovic <ben.kurtovic@gmail.com>
  2. Released under the terms of the MIT License. See LICENSE for details. */
  3. #include <dirent.h>
  4. #include <errno.h>
  5. #include <stdio.h>
  6. #include <stdlib.h>
  7. #include <string.h>
  8. #include <sys/types.h>
  9. #include "config.h"
  10. #include "logging.h"
  11. #include "util.h"
  12. #include "version.h"
  13. typedef struct {
  14. int argc;
  15. char **argv;
  16. int i, j;
  17. int paths_read;
  18. } Arguments;
  19. /*
  20. Print command-line help/usage.
  21. */
  22. static void print_help(const char *arg1)
  23. {
  24. printf("%s [-h] [-v] [-f] [-s <n>] [<rom_path>] ...\n"
  25. "\n"
  26. "basic options:\n"
  27. " -h, --help show this help message and exit\n"
  28. " -v, --version show crater's version number and exit\n"
  29. " -f, --fullscreen enable fullscreen mode\n"
  30. " <rom_path> path to the rom file to execute; if not given, will look\n"
  31. " in the roms/ directory and prompt the user\n"
  32. "\n"
  33. "advanced options:\n"
  34. " -g, --debug show logging information while running; add twice (-gg)\n"
  35. " to show more detailed logs, including an emulator trace\n"
  36. " -x, --scale <n> scale the game screen by an integer factor\n"
  37. " (applies to windowed mode only; defaults to 4)\n"
  38. " -s, --save <path> save cartridge RAM (\"battery save\") to the given file\n"
  39. " (defaults to <rom_path>.sav)\n"
  40. " -n, --no-save disable saving cartridge RAM entirely\n"
  41. " -a, --assemble <in> [<out>]\n"
  42. " convert z80 assembly source code into a binary file that\n"
  43. " can be run by crater\n"
  44. " -d, --disassemble <in> [<out>]\n"
  45. " convert a binary file into z80 assembly source code\n"
  46. " -r, --overwrite allow crater to write assembler output to the same\n"
  47. " filename as the input\n",
  48. arg1);
  49. }
  50. /*
  51. Print crater's version.
  52. */
  53. static void print_version()
  54. {
  55. printf("crater %s\n", CRATER_VERSION);
  56. }
  57. /*
  58. Return whether the given string ends with the given suffix.
  59. */
  60. static bool ends_with(const char *input, const char *suffix)
  61. {
  62. size_t ilen = strlen(input), slen = strlen(suffix);
  63. if (ilen < slen)
  64. return false;
  65. return strcmp(input + (ilen - slen), suffix) == 0;
  66. }
  67. /*
  68. Load all potential ROM files in roms/ into a data structure.
  69. */
  70. static int get_rom_paths(char ***path_ptr)
  71. {
  72. DIR *dirp;
  73. struct dirent *entry;
  74. char **paths = NULL, *path;
  75. int psize = 8, npaths = 0;
  76. dirp = opendir(ROMS_DIR);
  77. if (dirp) {
  78. paths = cr_malloc(sizeof(char*) * psize);
  79. while ((entry = readdir(dirp))) {
  80. path = entry->d_name;
  81. if (ends_with(path, ".gg") || ends_with(path, ".bin")) {
  82. if (npaths >= psize) {
  83. paths = cr_realloc(paths, sizeof(char*) * (psize *= 2));
  84. }
  85. paths[npaths] = cr_malloc(sizeof(char*) *
  86. (strlen(path) + strlen(ROMS_DIR) + 1));
  87. strcpy(paths[npaths], ROMS_DIR "/");
  88. strcat(paths[npaths], path);
  89. npaths++;
  90. }
  91. }
  92. closedir(dirp);
  93. } else {
  94. WARN_ERRNO("couldn't open '" ROMS_DIR "/'")
  95. }
  96. *path_ptr = paths;
  97. return npaths;
  98. }
  99. /*
  100. Find all potential ROM files in the roms/ directory, then ask the user
  101. which one they want to run.
  102. */
  103. static char* get_rom_path_from_user()
  104. {
  105. char **paths, *path, *input = NULL;
  106. int npaths, i;
  107. long int index;
  108. size_t size = 0;
  109. ssize_t len;
  110. npaths = get_rom_paths(&paths);
  111. for (i = 0; i < npaths; i++)
  112. printf("[%2d] %s\n", i + 1, paths[i]);
  113. if (npaths)
  114. printf("Enter a ROM number from above, or the path to a ROM image: ");
  115. else
  116. printf("Enter the path to a ROM image: ");
  117. len = getline(&input, &size, stdin);
  118. if (!input)
  119. OUT_OF_MEMORY()
  120. if (len > 0 && input[len - 1] == '\n')
  121. input[len - 1] = '\0';
  122. index = strtol(input, NULL, 10);
  123. if (index < 1 || index > npaths)
  124. path = input;
  125. else
  126. path = paths[index - 1];
  127. for (i = 0; i < npaths; i++) {
  128. if (paths[i] != path)
  129. free(paths[i]);
  130. }
  131. free(paths);
  132. return path;
  133. }
  134. /*
  135. Consume and return the next argument, or NULL if it is at the end.
  136. */
  137. static const char* consume_next(Arguments *args)
  138. {
  139. static char partial[3] = {'-', '\0', '\0'};
  140. char *curr = args->argv[args->i];
  141. if (!curr)
  142. return NULL;
  143. if (curr[0] == '-' && curr[1] != '-' && curr[1] != '\0') {
  144. // Need to handle single dash arg clusters like -asdf <=> -a -s -d -f
  145. if (curr[args->j]) {
  146. partial[1] = curr[args->j];
  147. args->j++;
  148. return partial;
  149. }
  150. args->i++;
  151. args->j = 1;
  152. return consume_next(args);
  153. }
  154. args->i++;
  155. return curr;
  156. }
  157. /*
  158. Parse a single positional (typically a path) command-line argument.
  159. */
  160. static int parse_pos_arg(Config *config, Arguments *args, const char *arg)
  161. {
  162. if (args->paths_read >= 2) {
  163. ERROR("too many arguments given: emulator mode accepts one ROM file,\n"
  164. "and assembler mode accepts one input file and one output file")
  165. return CONFIG_EXIT_FAILURE;
  166. }
  167. char *path = cr_strdup(arg);
  168. if (args->paths_read == 1) {
  169. /* If this is the second path given, it can only be an output file for
  170. the assembler. If the assembler is not enabled by subsequent
  171. arguments, we'll throw an error. */
  172. config->dst_path = path;
  173. } else {
  174. /* Otherwise, put the argument in the expected place. If we put it in
  175. rom_path and the assembler is enabled by later arguments, we'll
  176. move it. */
  177. if (config->assemble || config->disassemble)
  178. config->src_path = path;
  179. else
  180. config->rom_path = path;
  181. }
  182. args->paths_read++;
  183. return CONFIG_OK;
  184. }
  185. /*
  186. Check if the given argument matches the given short or long form.
  187. */
  188. static bool arg_check(const char *arg, const char *t1, const char *t2)
  189. {
  190. return !strcmp(arg, t1) || !strcmp(arg, t2);
  191. }
  192. /*
  193. Parse a single optional ("flag") command-line argument.
  194. */
  195. static int parse_opt_arg(Config *config, Arguments *args, const char *arg)
  196. {
  197. do
  198. arg++;
  199. while (*arg == '-');
  200. if (arg_check(arg, "h", "help")) {
  201. print_help(args->argv[0]);
  202. return CONFIG_EXIT_SUCCESS;
  203. }
  204. else if (arg_check(arg, "v", "version")) {
  205. print_version();
  206. return CONFIG_EXIT_SUCCESS;
  207. }
  208. else if (arg_check(arg, "f", "fullscreen")) {
  209. config->fullscreen = true;
  210. }
  211. else if (arg_check(arg, "x", "scale")) {
  212. const char *next = consume_next(args);
  213. if (!next) {
  214. ERROR("the scale option requires an argument")
  215. return CONFIG_EXIT_FAILURE;
  216. }
  217. long scale = strtol(next, NULL, 10);
  218. if (scale <= 0 || scale > SCALE_MAX) {
  219. ERROR("scale factor of %s is not an integer or is out of range", next)
  220. return CONFIG_EXIT_FAILURE;
  221. }
  222. config->scale = scale;
  223. }
  224. else if (arg_check(arg, "s", "save")) {
  225. const char *next = consume_next(args);
  226. if (!next) {
  227. ERROR("the save option requires an argument")
  228. return CONFIG_EXIT_FAILURE;
  229. }
  230. free(config->sav_path);
  231. config->sav_path = cr_strdup(next);
  232. }
  233. else if (arg_check(arg, "n", "no-save")) {
  234. config->no_saving = true;
  235. }
  236. else if (arg_check(arg, "g", "debug")) {
  237. config->debug++;
  238. }
  239. else if (arg_check(arg, "a", "assemble")) {
  240. if (args->paths_read >= 1) {
  241. config->src_path = config->rom_path;
  242. config->rom_path = NULL;
  243. }
  244. config->assemble = true;
  245. }
  246. else if (arg_check(arg, "d", "disassemble")) {
  247. if (args->paths_read >= 1) {
  248. config->src_path = config->rom_path;
  249. config->rom_path = NULL;
  250. }
  251. config->disassemble = true;
  252. }
  253. else if (arg_check(arg, "r", "overwrite")) {
  254. config->overwrite = true;
  255. }
  256. else {
  257. ERROR("unknown argument: %s", arg)
  258. return CONFIG_EXIT_FAILURE;
  259. }
  260. return CONFIG_OK;
  261. }
  262. /*
  263. Parse the command-line arguments for any special flags.
  264. */
  265. static int parse_args(Config *config, int argc, char *argv[])
  266. {
  267. Arguments args = {argc, argv, 1, 1, 0};
  268. const char *arg;
  269. int retval;
  270. while ((arg = consume_next(&args))) {
  271. if (arg[0] != '-')
  272. retval = parse_pos_arg(config, &args, arg);
  273. else
  274. retval = parse_opt_arg(config, &args, arg);
  275. if (retval != CONFIG_OK)
  276. return retval;
  277. }
  278. if (!config->assemble && !config->disassemble) {
  279. if (args.paths_read >= 2) {
  280. ERROR("too many arguments given - emulator mode accepts one ROM file")
  281. return CONFIG_EXIT_FAILURE;
  282. }
  283. if (args.paths_read == 0) {
  284. char *path = get_rom_path_from_user();
  285. if (path[0] == '\0') {
  286. ERROR("no ROM image given")
  287. return CONFIG_EXIT_FAILURE;
  288. }
  289. config->rom_path = path;
  290. }
  291. }
  292. return CONFIG_OK;
  293. }
  294. /*
  295. If no output file is specified for the assembler, this function picks a
  296. filename based on the input file, replacing its extension with ".gg" or
  297. ".asm" (or adding it, if none is present).
  298. */
  299. static void guess_assembler_output_file(Config *config)
  300. {
  301. char *src = config->src_path, *ptr = src + strlen(src) - 1,
  302. *ext = config->assemble ? ".gg" : ".asm";
  303. size_t until_ext = ptr - src + 1;
  304. do {
  305. if (*ptr == '.') {
  306. until_ext = ptr - src;
  307. break;
  308. }
  309. } while (ptr-- >= src);
  310. config->dst_path = cr_malloc(sizeof(char) * (until_ext + 5));
  311. strcpy(stpncpy(config->dst_path, src, until_ext), ext);
  312. }
  313. /*
  314. Ensure that the combination of arguments in a config object are valid.
  315. */
  316. static bool sanity_check(Config *config)
  317. {
  318. bool assembler = config->assemble || config->disassemble;
  319. if (config->fullscreen && config->scale) {
  320. ERROR("cannot specify a scale in fullscreen mode")
  321. return false;
  322. } else if (config->assemble && config->disassemble) {
  323. ERROR("cannot assemble and disassemble at the same time")
  324. return false;
  325. } else if (assembler && (config->fullscreen || config->scale)) {
  326. ERROR("cannot specify emulator options in assembler mode")
  327. return false;
  328. } else if (assembler && !config->src_path) {
  329. ERROR("assembler mode requires an input file")
  330. return false;
  331. }
  332. return true;
  333. }
  334. /*
  335. Set some default values for missing arguments.
  336. Some additional sanity checking is done.
  337. */
  338. static bool set_defaults(Config *config)
  339. {
  340. bool assembler = config->assemble || config->disassemble;
  341. if (!config->scale) {
  342. config->scale = 4;
  343. }
  344. if (assembler && !config->dst_path) {
  345. guess_assembler_output_file(config);
  346. }
  347. if (assembler && !config->overwrite && !strcmp(config->src_path, config->dst_path)) {
  348. ERROR("refusing to overwrite the assembler input file; pass -r to override")
  349. return false;
  350. }
  351. if (!assembler && !config->sav_path && !config->no_saving) {
  352. config->sav_path = cr_malloc(sizeof(char) *
  353. (strlen(config->rom_path) + 4));
  354. strcpy(config->sav_path, config->rom_path);
  355. strcat(config->sav_path, ".sav");
  356. }
  357. return true;
  358. }
  359. /*
  360. Create a new config object and load default values into it.
  361. Return value is one of CONFIG_OK, CONFIG_EXIT_SUCCESS, CONFIG_EXIT_FAILURE
  362. and indicates how the caller should proceed. If the caller should exit,
  363. then the config object should *not* be freed; otherwise it should be freed
  364. with config_destroy() when the caller is ready.
  365. */
  366. int config_create(Config** config_ptr, int argc, char* argv[])
  367. {
  368. Config *config = cr_malloc(sizeof(Config));
  369. int retval;
  370. config->debug = 0;
  371. config->assemble = false;
  372. config->disassemble = false;
  373. config->fullscreen = false;
  374. config->scale = 0;
  375. config->rom_path = NULL;
  376. config->sav_path = NULL;
  377. config->src_path = NULL;
  378. config->dst_path = NULL;
  379. config->overwrite = false;
  380. config->no_saving = false;
  381. retval = parse_args(config, argc, argv);
  382. if (retval == CONFIG_OK && !(sanity_check(config) && set_defaults(config)))
  383. retval = CONFIG_EXIT_FAILURE;
  384. if (retval != CONFIG_OK) {
  385. config_destroy(config);
  386. return retval;
  387. }
  388. *config_ptr = config;
  389. return CONFIG_OK;
  390. }
  391. /*
  392. Destroy a config object previously created with config_create().
  393. */
  394. void config_destroy(Config *config)
  395. {
  396. free(config->rom_path);
  397. free(config->sav_path);
  398. free(config->src_path);
  399. free(config->dst_path);
  400. free(config);
  401. }
  402. /*
  403. @DEBUG_LEVEL
  404. Print out all config arguments to stdout.
  405. */
  406. void config_dump_args(const Config* config)
  407. {
  408. DEBUG("Dumping arguments:")
  409. DEBUG("- debug: %d", config->debug)
  410. DEBUG("- assemble: %s", config->assemble ? "true" : "false")
  411. DEBUG("- disassemble: %s", config->disassemble ? "true" : "false")
  412. DEBUG("- fullscreen: %s", config->fullscreen ? "true" : "false")
  413. DEBUG("- scale: %d", config->scale)
  414. DEBUG("- rom_path: %s", config->rom_path ? config->rom_path : "(null)")
  415. DEBUG("- sav_path: %s", config->sav_path ? config->sav_path : "(null)")
  416. DEBUG("- src_path: %s", config->src_path ? config->src_path : "(null)")
  417. DEBUG("- dst_path: %s", config->dst_path ? config->dst_path : "(null)")
  418. DEBUG("- overwrite: %s", config->overwrite ? "true" : "false")
  419. DEBUG("- no_saving: %s", config->no_saving ? "true" : "false")
  420. }