An emulator, assembler, and disassembler for the Sega Game Gear
Nevar pievienot vairāk kā 25 tēmas Tēmai ir jāsākas ar burtu vai ciparu, tā var saturēt domu zīmes ('-') un var būt līdz 35 simboliem gara.

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