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.

config.c 12 KiB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389
  1. /* Copyright (C) 2014-2015 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 "version.h"
  12. #define ROMS_DIR "roms"
  13. /*
  14. Print command-line help/usage.
  15. */
  16. static void print_help(const char *arg1)
  17. {
  18. printf("%s [-h] [-v] [-f] [-s <n>] [<rom_path>] ...\n"
  19. "\n"
  20. "basic options:\n"
  21. " -h, --help show this help message and exit\n"
  22. " -v, --version show crater's version number and exit\n"
  23. " -f, --fullscreen enable fullscreen mode\n"
  24. " -s, --scale <n> scale the game screen by an integer factor\n"
  25. " (applies to windowed mode only)\n"
  26. " <rom_path> path to the rom file to execute; if not given, will look\n"
  27. " in the roms/ directory and prompt the user\n"
  28. "\n"
  29. "advanced options:\n"
  30. " -g, --debug display information about emulation state while running,\n"
  31. " including register and memory values; can also pause\n"
  32. " emulation, set breakpoints, and change state\n"
  33. " -a, --assemble <in> [<out>] convert z80 assembly source code into a\n"
  34. " binary file that can be run by crater\n"
  35. " -d, --disassemble <in> [<out>] convert a binary file into z80 assembly\n"
  36. " source code\n"
  37. " -r, --overwrite allow crater to write assembler output to the same\n"
  38. " filename as the input\n",
  39. arg1);
  40. }
  41. /*
  42. Print crater's version.
  43. */
  44. static void print_version()
  45. {
  46. printf("crater %s\n", CRATER_VERSION);
  47. }
  48. /*
  49. Return whether the given string ends with the given suffix.
  50. */
  51. static bool ends_with(const char *input, const char *suffix)
  52. {
  53. size_t ilen = strlen(input), slen = strlen(suffix);
  54. if (ilen < slen)
  55. return false;
  56. return strcmp(input + (ilen - slen), suffix) == 0;
  57. }
  58. /*
  59. Load all potential ROM files in roms/ into a data structure.
  60. */
  61. static int get_rom_paths(char ***path_ptr)
  62. {
  63. DIR *dirp;
  64. struct dirent *entry;
  65. char **paths = NULL, *path;
  66. int psize = 8, npaths = 0;
  67. dirp = opendir(ROMS_DIR);
  68. if (dirp) {
  69. paths = malloc(sizeof(char*) * psize);
  70. if (!paths)
  71. OUT_OF_MEMORY()
  72. while ((entry = readdir(dirp))) {
  73. path = entry->d_name;
  74. if (ends_with(path, ".gg") || ends_with(path, ".bin")) {
  75. if (npaths >= psize) {
  76. paths = realloc(paths, sizeof(char*) * (psize *= 2));
  77. if (!paths)
  78. OUT_OF_MEMORY()
  79. }
  80. paths[npaths] = malloc(sizeof(char*) *
  81. (strlen(path) + strlen(ROMS_DIR) + 1));
  82. if (!paths[npaths])
  83. OUT_OF_MEMORY()
  84. strcpy(paths[npaths], ROMS_DIR "/");
  85. strcat(paths[npaths], path);
  86. npaths++;
  87. }
  88. }
  89. closedir(dirp);
  90. } else {
  91. WARN_ERRNO("couldn't open 'roms/'")
  92. }
  93. *path_ptr = paths;
  94. return npaths;
  95. }
  96. /*
  97. Find all potential ROM files in the roms/ directory, then ask the user
  98. which one they want to run.
  99. */
  100. static char* get_rom_path_from_user()
  101. {
  102. char **paths, *path, *input = NULL;
  103. int npaths, i;
  104. long int index;
  105. size_t size = 0;
  106. ssize_t len;
  107. npaths = get_rom_paths(&paths);
  108. for (i = 0; i < npaths; i++)
  109. printf("[%2d] %s\n", i + 1, paths[i]);
  110. if (npaths)
  111. printf("Enter a ROM number from above, or the path to a ROM image: ");
  112. else
  113. printf("Enter the path to a ROM image: ");
  114. len = getline(&input, &size, stdin);
  115. if (!input)
  116. OUT_OF_MEMORY()
  117. if (len > 0 && input[len - 1] == '\n')
  118. input[len - 1] = '\0';
  119. index = strtol(input, NULL, 10);
  120. if (index < 1 || index > npaths)
  121. path = input;
  122. else
  123. path = paths[index - 1];
  124. for (i = 0; i < npaths; i++) {
  125. if (paths[i] != path)
  126. free(paths[i]);
  127. }
  128. if (paths)
  129. free(paths);
  130. return path;
  131. }
  132. /*
  133. Parse the command-line arguments for any special flags.
  134. */
  135. static int parse_args(Config *config, int argc, char *argv[])
  136. {
  137. char *arg, *path;
  138. int i, paths_read = 0;
  139. for (i = 1; i < argc; i++) {
  140. arg = argv[i];
  141. if (arg[0] != '-') {
  142. // Parsing a path or other variable:
  143. if (paths_read >= 2) {
  144. ERROR("too many arguments given - emulator mode accepts one "
  145. "ROM file,\nand assembler mode accepts one input file "
  146. "and one output file")
  147. return CONFIG_EXIT_FAILURE;
  148. }
  149. path = malloc(sizeof(char) * (strlen(arg) + 1));
  150. if (!path)
  151. OUT_OF_MEMORY()
  152. strcpy(path, arg);
  153. if (paths_read == 1) {
  154. /* If this is the second path given, it can only be an output
  155. file for the assembler. If the assembler is not enabled by
  156. subsequent arguments, we'll throw an error. */
  157. config->dst_path = path;
  158. } else {
  159. /* Otherwise, put the argument in the expected place. If we put
  160. it in rom_path and the assembler is enabled by later
  161. arguments, we'll move it. */
  162. if (config->assemble || config->disassemble)
  163. config->src_path = path;
  164. else
  165. config->rom_path = path;
  166. }
  167. paths_read++;
  168. continue;
  169. }
  170. // Parsing an option:
  171. do
  172. arg++;
  173. while (arg[0] == '-');
  174. if (!strcmp(arg, "h") || !strcmp(arg, "help")) {
  175. print_help(argv[0]);
  176. return CONFIG_EXIT_SUCCESS;
  177. } else if (!strcmp(arg, "v") || !strcmp(arg, "version")) {
  178. print_version();
  179. return CONFIG_EXIT_SUCCESS;
  180. } else if (!strcmp(arg, "f") || !strcmp(arg, "fullscreen")) {
  181. config->fullscreen = true;
  182. } else if (!strcmp(arg, "s") || !strcmp(arg, "scale")) {
  183. if (i++ >= argc) {
  184. ERROR("the scale option requires an argument")
  185. return CONFIG_EXIT_FAILURE;
  186. }
  187. arg = argv[i];
  188. long scale = strtol(arg, NULL, 10);
  189. if (scale <= 0 || scale > SCALE_MAX) {
  190. ERROR("scale factor of %s is not an integer or is out of range", arg)
  191. return CONFIG_EXIT_FAILURE;
  192. }
  193. config->scale = scale;
  194. } else if (!strcmp(arg, "g") || !strcmp(arg, "debug")) {
  195. config->debug = true;
  196. } else if (!strcmp(arg, "a") || !strcmp(arg, "assemble")) {
  197. if (paths_read >= 1) {
  198. config->src_path = config->rom_path;
  199. config->rom_path = NULL;
  200. }
  201. config->assemble = true;
  202. } else if (!strcmp(arg, "d") || !strcmp(arg, "disassemble")) {
  203. if (paths_read >= 1) {
  204. config->src_path = config->rom_path;
  205. config->rom_path = NULL;
  206. }
  207. config->disassemble = true;
  208. } else if (!strcmp(arg, "r") || !strcmp(arg, "overwrite")) {
  209. config->overwrite = true;
  210. } else {
  211. ERROR("unknown argument: %s", argv[i])
  212. return CONFIG_EXIT_FAILURE;
  213. }
  214. }
  215. if (!config->assemble && !config->disassemble && paths_read >= 2) {
  216. ERROR("too many arguments given - emulator mode accepts one ROM file")
  217. return CONFIG_EXIT_FAILURE;
  218. }
  219. if (!config->assemble && !config->disassemble && paths_read == 0) {
  220. path = get_rom_path_from_user();
  221. if (path[0] == '\0') {
  222. ERROR("no ROM image given")
  223. return CONFIG_EXIT_FAILURE;
  224. }
  225. config->rom_path = path;
  226. }
  227. return CONFIG_OK;
  228. }
  229. /*
  230. If no output file is specified for the assembler, this function picks a
  231. filename based on the input file, replacing its extension with '.gg' or
  232. '.s' (or adding it, if none is present).
  233. */
  234. static void guess_assembler_output_file(Config* config)
  235. {
  236. char *src = config->src_path, *ptr = src + strlen(src) - 1,
  237. *ext = config->assemble ? ".gg" : ".s";
  238. size_t until_ext = ptr - src + 1;
  239. do {
  240. if (*ptr == '.') {
  241. until_ext = ptr - src;
  242. break;
  243. }
  244. } while (ptr-- >= src);
  245. config->dst_path = malloc(sizeof(char) * (until_ext + 4));
  246. if (!config->dst_path)
  247. OUT_OF_MEMORY()
  248. strcpy(stpncpy(config->dst_path, src, until_ext), ext);
  249. }
  250. /*
  251. Ensure that the combination of arguments in a config object are valid.
  252. Some modifications are made in the case of missing arguments, like guessing
  253. the filenames for assembler output files.
  254. */
  255. static bool sanity_check(Config* config)
  256. {
  257. bool assembler = config->assemble || config->disassemble;
  258. if (config->fullscreen && config->scale > 1) {
  259. ERROR("cannot specify a scale in fullscreen mode")
  260. return false;
  261. } else if (config->assemble && config->disassemble) {
  262. ERROR("cannot assemble and disassemble at the same time")
  263. return false;
  264. } else if (assembler && (config->debug || config->fullscreen || config->scale > 1)) {
  265. ERROR("cannot specify emulator options in assembler mode")
  266. return false;
  267. } else if (assembler && !config->src_path) {
  268. ERROR("assembler mode requires an input file")
  269. return false;
  270. }
  271. if (assembler && !config->dst_path) {
  272. guess_assembler_output_file(config);
  273. }
  274. if (assembler && !config->overwrite && !strcmp(config->src_path, config->dst_path)) {
  275. ERROR("refusing to overwrite the assembler input file; pass -r to override")
  276. return false;
  277. }
  278. return true;
  279. }
  280. /*
  281. Create a new config object and load default values into it.
  282. Return value is one of CONFIG_OK, CONFIG_EXIT_SUCCESS, CONFIG_EXIT_FAILURE
  283. and indicates how the caller should proceed.
  284. */
  285. int config_create(Config** config_ptr, int argc, char* argv[])
  286. {
  287. Config *config;
  288. int retval;
  289. if (!(config = malloc(sizeof(Config)))) {
  290. OUT_OF_MEMORY()
  291. return CONFIG_EXIT_FAILURE;
  292. }
  293. config->debug = false;
  294. config->assemble = false;
  295. config->disassemble = false;
  296. config->fullscreen = false;
  297. config->scale = 1;
  298. config->rom_path = NULL;
  299. config->src_path = NULL;
  300. config->dst_path = NULL;
  301. config->overwrite = false;
  302. retval = parse_args(config, argc, argv);
  303. if (retval == CONFIG_OK && !sanity_check(config))
  304. retval = CONFIG_EXIT_FAILURE;
  305. if (retval != CONFIG_OK) {
  306. config_destroy(config);
  307. return retval;
  308. }
  309. *config_ptr = config;
  310. return CONFIG_OK;
  311. }
  312. /*
  313. Destroy a config object previously created with config_create().
  314. */
  315. void config_destroy(Config *config)
  316. {
  317. if (config->rom_path)
  318. free(config->rom_path);
  319. if (config->src_path)
  320. free(config->src_path);
  321. if (config->dst_path)
  322. free(config->dst_path);
  323. free(config);
  324. }
  325. #ifdef DEBUG_MODE
  326. /*
  327. DEBUG FUNCTION: Print out all config arguments to stdout.
  328. */
  329. void config_dump_args(Config* config)
  330. {
  331. DEBUG("Dumping arguments:")
  332. DEBUG("- fullscreen: %s", config->fullscreen ? "true" : "false")
  333. DEBUG("- scale: %d", config->scale)
  334. DEBUG("- debug: %s", config->debug ? "true" : "false")
  335. DEBUG("- assemble: %s", config->assemble ? "true" : "false")
  336. DEBUG("- disassemble: %s", config->disassemble ? "true" : "false")
  337. if (config->rom_path)
  338. DEBUG("- rom_path: %s", config->rom_path)
  339. else
  340. DEBUG("- rom_path: (null)")
  341. if (config->src_path)
  342. DEBUG("- src_path: %s", config->src_path)
  343. else
  344. DEBUG("- src_path: (null)")
  345. if (config->dst_path)
  346. DEBUG("- dst_path: %s", config->dst_path)
  347. else
  348. DEBUG("- dst_path: (null)")
  349. DEBUG("- overwrite: %s", config->overwrite ? "true" : "false")
  350. }
  351. #endif