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.
 
 
 
 
 

342 lines
10 KiB

  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 code\n",
  36. arg1);
  37. }
  38. /*
  39. Print crater's version.
  40. */
  41. static void print_version()
  42. {
  43. printf("crater %s\n", CRATER_VERSION);
  44. }
  45. /*
  46. Return whether the given string ends with the given suffix.
  47. */
  48. static bool ends_with(const char *input, const char *suffix)
  49. {
  50. size_t ilen = strlen(input), slen = strlen(suffix);
  51. if (ilen < slen)
  52. return false;
  53. return strcmp(input + (ilen - slen), suffix) == 0;
  54. }
  55. /*
  56. Load all potential ROM files in roms/ into a data structure.
  57. */
  58. static int get_rom_paths(char ***path_ptr)
  59. {
  60. DIR *dirp;
  61. struct dirent *entry;
  62. char **paths = NULL, *path;
  63. int psize = 8, npaths = 0;
  64. dirp = opendir(ROMS_DIR);
  65. if (dirp) {
  66. paths = malloc(sizeof(char*) * psize);
  67. if (!paths)
  68. OUT_OF_MEMORY()
  69. while ((entry = readdir(dirp))) {
  70. path = entry->d_name;
  71. if (ends_with(path, ".gg") || ends_with(path, ".bin")) {
  72. if (npaths >= psize) {
  73. paths = realloc(paths, sizeof(char*) * (psize *= 2));
  74. if (!paths)
  75. OUT_OF_MEMORY()
  76. }
  77. paths[npaths] = malloc(sizeof(char*) *
  78. (strlen(path) + strlen(ROMS_DIR) + 1));
  79. if (!paths[npaths])
  80. OUT_OF_MEMORY()
  81. strcpy(paths[npaths], ROMS_DIR "/");
  82. strcat(paths[npaths], path);
  83. npaths++;
  84. }
  85. }
  86. closedir(dirp);
  87. } else {
  88. WARN_ERRNO("couldn't open 'roms/'")
  89. }
  90. *path_ptr = paths;
  91. return npaths;
  92. }
  93. /*
  94. Find all potential ROM files in the roms/ directory, then ask the user
  95. which one they want to run.
  96. */
  97. static char* get_rom_path_from_user()
  98. {
  99. char **paths, *path, *input = NULL;
  100. int npaths, i;
  101. long int index;
  102. size_t size = 0;
  103. ssize_t len;
  104. npaths = get_rom_paths(&paths);
  105. for (i = 0; i < npaths; i++)
  106. printf("[%2d] %s\n", i + 1, paths[i]);
  107. if (npaths)
  108. printf("Enter a ROM number from above, or the path to a ROM image: ");
  109. else
  110. printf("Enter the path to a ROM image: ");
  111. len = getline(&input, &size, stdin);
  112. if (!input)
  113. OUT_OF_MEMORY()
  114. if (len > 0 && input[len - 1] == '\n')
  115. input[len - 1] = '\0';
  116. index = strtol(input, NULL, 10);
  117. if (index < 1 || index > npaths)
  118. path = input;
  119. else
  120. path = paths[index - 1];
  121. for (i = 0; i < npaths; i++) {
  122. if (paths[i] != path)
  123. free(paths[i]);
  124. }
  125. if (paths)
  126. free(paths);
  127. return path;
  128. }
  129. /*
  130. Parse the command-line arguments for any special flags.
  131. */
  132. static int parse_args(Config *config, int argc, char *argv[])
  133. {
  134. char *arg, *path;
  135. int i, paths_read = 0;
  136. for (i = 1; i < argc; i++) {
  137. arg = argv[i];
  138. if (arg[0] != '-') {
  139. // Parsing a path or other variable:
  140. if (paths_read >= 2) {
  141. ERROR("too many arguments given - emulator mode accepts one "
  142. "ROM file,\nand assembler mode accepts one input file "
  143. "and one output file")
  144. return CONFIG_EXIT_FAILURE;
  145. }
  146. path = malloc(sizeof(char) * (strlen(arg) + 1));
  147. strcpy(path, arg);
  148. if (paths_read == 1) {
  149. /* If this is the second path given, it can only be an output
  150. file for the assembler. If the assembler is not enabled by
  151. subsequent arguments, we'll throw an error. */
  152. config->dst_path = path;
  153. } else {
  154. /* Otherwise, put the argument in the expected place. If we put
  155. it in rom_path and the assembler is enabled by later
  156. arguments, we'll move it. */
  157. if (config->assemble || config->disassemble)
  158. config->src_path = path;
  159. else
  160. config->rom_path = path;
  161. }
  162. paths_read++;
  163. continue;
  164. }
  165. // Parsing an option:
  166. do
  167. arg++;
  168. while (arg[0] == '-');
  169. if (!strcmp(arg, "h") || !strcmp(arg, "help")) {
  170. print_help(argv[0]);
  171. return CONFIG_EXIT_SUCCESS;
  172. } else if (!strcmp(arg, "v") || !strcmp(arg, "version")) {
  173. print_version();
  174. return CONFIG_EXIT_SUCCESS;
  175. } else if (!strcmp(arg, "f") || !strcmp(arg, "fullscreen")) {
  176. config->fullscreen = true;
  177. } else if (!strcmp(arg, "s") || !strcmp(arg, "scale")) {
  178. if (i++ >= argc) {
  179. ERROR("the scale option requires an argument")
  180. return CONFIG_EXIT_FAILURE;
  181. }
  182. arg = argv[i];
  183. long scale = strtol(arg, NULL, 10);
  184. if (scale <= 0 || scale > SCALE_MAX) {
  185. ERROR("scale factor of %s is not an integer or is out of range", arg)
  186. return CONFIG_EXIT_FAILURE;
  187. }
  188. config->scale = scale;
  189. } else if (!strcmp(arg, "g") || !strcmp(arg, "debug")) {
  190. config->debug = true;
  191. } else if (!strcmp(arg, "a") || !strcmp(arg, "assemble")) {
  192. if (paths_read >= 1) {
  193. config->src_path = config->rom_path;
  194. config->rom_path = NULL;
  195. }
  196. config->assemble = true;
  197. } else if (!strcmp(arg, "d") || !strcmp(arg, "disassemble")) {
  198. if (paths_read >= 1) {
  199. config->src_path = config->rom_path;
  200. config->rom_path = NULL;
  201. }
  202. config->disassemble = true;
  203. } else {
  204. ERROR("unknown argument: %s", argv[i])
  205. return CONFIG_EXIT_FAILURE;
  206. }
  207. }
  208. if (!config->assemble && !config->disassemble && paths_read >= 2) {
  209. ERROR("too many arguments given - emulator mode accepts one ROM file")
  210. return CONFIG_EXIT_FAILURE;
  211. }
  212. if (!config->assemble && !config->disassemble && paths_read == 0) {
  213. path = get_rom_path_from_user();
  214. if (path[0] == '\0') {
  215. ERROR("no ROM image given")
  216. return CONFIG_EXIT_FAILURE;
  217. }
  218. config->rom_path = path;
  219. }
  220. return CONFIG_OK;
  221. }
  222. /*
  223. Ensure that the combination of arguments in a config object are valid.
  224. */
  225. bool sanity_check(Config* config)
  226. {
  227. if (config->fullscreen && config->scale > 1) {
  228. ERROR("cannot specify a scale in fullscreen mode")
  229. return false;
  230. } else if (config->assemble && config->disassemble) {
  231. ERROR("cannot assemble and disassemble at the same time")
  232. return false;
  233. } else if ((config->assemble || config->disassemble) &&
  234. (config->debug || config->fullscreen || config->scale > 1)) {
  235. ERROR("cannot specify emulator options in assembler mode")
  236. return false;
  237. } else if ((config->assemble || config->disassemble) &&
  238. !(config->src_path && config->dst_path)) {
  239. ERROR("assembler mode requires both an input and an output file")
  240. return false;
  241. }
  242. return true;
  243. }
  244. /*
  245. Create a new config object and load default values into it.
  246. */
  247. int config_create(Config** config_ptr, int argc, char* argv[])
  248. {
  249. Config *config;
  250. int retval;
  251. if (!(config = malloc(sizeof(Config)))) {
  252. OUT_OF_MEMORY()
  253. return CONFIG_EXIT_FAILURE;
  254. }
  255. config->debug = false;
  256. config->assemble = false;
  257. config->disassemble = false;
  258. config->fullscreen = false;
  259. config->scale = 1;
  260. config->rom_path = NULL;
  261. config->src_path = NULL;
  262. config->dst_path = NULL;
  263. retval = parse_args(config, argc, argv);
  264. if (retval == CONFIG_OK && !sanity_check(config))
  265. retval = CONFIG_EXIT_FAILURE;
  266. if (retval != CONFIG_OK) {
  267. config_destroy(config);
  268. return retval;
  269. }
  270. *config_ptr = config;
  271. return CONFIG_OK;
  272. }
  273. /*
  274. Destroy a config object previously created with config_create().
  275. */
  276. void config_destroy(Config *config)
  277. {
  278. if (config->rom_path)
  279. free(config->rom_path);
  280. if (config->src_path)
  281. free(config->src_path);
  282. if (config->dst_path)
  283. free(config->dst_path);
  284. free(config);
  285. }
  286. #ifdef DEBUG_MODE
  287. /*
  288. DEBUG FUNCTION: Print out all config arguments to stdout.
  289. */
  290. void config_dump_args(Config* config)
  291. {
  292. DEBUG("Dumping arguments:")
  293. DEBUG("- fullscreen: %s", config->fullscreen ? "true" : "false")
  294. DEBUG("- scale: %d", config->scale)
  295. DEBUG("- debug: %s", config->debug ? "true" : "false")
  296. DEBUG("- assemble: %s", config->assemble ? "true" : "false")
  297. DEBUG("- disassemble: %s", config->disassemble ? "true" : "false")
  298. if (config->rom_path)
  299. DEBUG("- rom_path: %s", config->rom_path)
  300. else
  301. DEBUG("- rom_path: (null)")
  302. if (config->src_path)
  303. DEBUG("- src_path: %s", config->src_path)
  304. else
  305. DEBUG("- src_path: (null)")
  306. if (config->dst_path)
  307. DEBUG("- dst_path: %s", config->dst_path)
  308. else
  309. DEBUG("- dst_path: (null)")
  310. }
  311. #endif