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.
 
 
 
 
 

425 lines
11 KiB

  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 <stdio.h>
  4. #include <stdlib.h>
  5. #include <string.h>
  6. #include <time.h>
  7. #include "disassembler.h"
  8. #include "disassembler/arguments.h"
  9. #include "disassembler/mnemonics.h"
  10. #include "disassembler/sizes.h"
  11. #include "mmu.h"
  12. #include "rom.h"
  13. #include "util.h"
  14. #include "version.h"
  15. #define HRULE \
  16. "----------------------------------------------------------------------------"
  17. #define NUM_BANKS(rom) \
  18. (((rom)->size + MMU_ROM_BANK_SIZE - 1) / MMU_ROM_BANK_SIZE)
  19. #define MAX_BYTES_PER_LINE 16
  20. /* Structs and things */
  21. typedef struct {
  22. size_t cap, len;
  23. char **lines;
  24. } Disassembly;
  25. typedef enum {
  26. DT_BINARY = 0,
  27. DT_CODE,
  28. DT_HEADER
  29. } DataType;
  30. typedef struct {
  31. size_t index;
  32. size_t size;
  33. int8_t slot;
  34. const uint8_t *data;
  35. DataType *types;
  36. } ROMBank;
  37. /*
  38. Format a sequence of bytes of a certain length as a pretty string.
  39. The result must be freed by the caller.
  40. */
  41. static char* format_bytestring(const uint8_t *bytes, size_t size)
  42. {
  43. if (!size)
  44. return NULL;
  45. char *str = cr_malloc(sizeof(char) * 16);
  46. size_t offset = 2, i;
  47. uint8_t b = bytes[0];
  48. if (b == 0xCB || b == 0xDD || b == 0xED || b == 0xFD) {
  49. offset--;
  50. if ((b == 0xDD || b == 0xFD) && bytes[1] == 0xCB)
  51. offset--;
  52. }
  53. for (i = 0; i < offset; i++) {
  54. str[3 * i] = str[3 * i + 1] = str[3 * i + 2] = ' ';
  55. }
  56. for (i = 0; i < size; i++) {
  57. snprintf(&str[3 * (i + offset)], 3, "%02X", bytes[i]);
  58. str[3 * (i + offset) + 2] = ' ';
  59. }
  60. str[3 * (size + offset) - 1] = '\0';
  61. return str;
  62. }
  63. /*
  64. Free the given DisasInstr struct.
  65. */
  66. void disas_instr_free(DisasInstr *instr)
  67. {
  68. free(instr->bytestr);
  69. free(instr->line);
  70. free(instr);
  71. }
  72. /*
  73. Disassemble a single instruction starting at the given address.
  74. Return a dynamically allocated structure containing various interesting
  75. fields. This must be freed by the user with disas_instr_free().
  76. */
  77. DisasInstr* disassemble_instruction(const uint8_t *bytes)
  78. {
  79. size_t size = get_instr_size(bytes);
  80. char *bytestr = format_bytestring(bytes, size);
  81. char *mnemonic = decode_mnemonic(bytes);
  82. char *args = decode_arguments(bytes);
  83. char *line;
  84. if (args) {
  85. line = cr_malloc(strlen(mnemonic) + strlen(args) + 2);
  86. sprintf(line, "%s\t%s", mnemonic, args);
  87. free(args);
  88. } else {
  89. line = cr_strdup(mnemonic);
  90. }
  91. DisasInstr *instr = cr_malloc(sizeof(DisasInstr));
  92. instr->size = size;
  93. instr->bytestr = bytestr;
  94. instr->line = line;
  95. return instr;
  96. }
  97. /*
  98. Append a line to the end of a disassembly.
  99. */
  100. static void write_line(Disassembly *dis, char *line)
  101. {
  102. dis->lines[dis->len++] = line;
  103. if (dis->len >= dis->cap) {
  104. dis->cap *= 2;
  105. dis->lines = cr_realloc(dis->lines, sizeof(char*) * dis->cap);
  106. }
  107. }
  108. /*
  109. Macro that wraps write_line() in a printf-like interface.
  110. */
  111. #define WRITE_LINE_(dis, fmt, ...) \
  112. do { \
  113. char *tmp_buffer_; \
  114. if (asprintf(&tmp_buffer_, fmt "\n", __VA_ARGS__) < 0) \
  115. OUT_OF_MEMORY() \
  116. write_line(dis, tmp_buffer_); \
  117. } while(0);
  118. #define WRITE_LINE(dis, ...) WRITE_LINE_(dis, __VA_ARGS__, NULL)
  119. /*
  120. Write some metadata comments to the top of the disassembly.
  121. */
  122. static void write_metadata(Disassembly *dis, const ROM *rom)
  123. {
  124. time_t t;
  125. struct tm *tm_info;
  126. char buf[64];
  127. time(&t);
  128. tm_info = localtime(&t);
  129. strftime(buf, sizeof buf, "on %a %b %d, %Y at %H:%M:%S", tm_info);
  130. WRITE_LINE(dis, ";; GAME GEAR ROM DISASSEMBLY")
  131. WRITE_LINE(dis, ";; File: %s", rom->name)
  132. WRITE_LINE(dis, ";; Generated %s by crater %s", buf, CRATER_VERSION)
  133. WRITE_LINE(dis, ";; " HRULE)
  134. WRITE_LINE(dis, "")
  135. }
  136. /*
  137. Given a size, fill 'output' with a pretty string. Modified from rom.c.
  138. */
  139. static char* size_to_string(char *output, size_t size)
  140. {
  141. if (size >= (1 << 20))
  142. sprintf(output, "%zu MB", size >> 20);
  143. else
  144. sprintf(output, "%zu KB", size >> 10);
  145. return output;
  146. }
  147. /*
  148. Extract appropriate assembler directives from a ROM's header.
  149. */
  150. static void disassemble_header(Disassembly *dis, const ROM *rom)
  151. {
  152. char buf[64];
  153. const char *size, *product, *region, *declsize;
  154. DEBUG("Disassembling header")
  155. size = size_to_string(buf, rom->size);
  156. product = rom_product(rom);
  157. region = rom_region(rom);
  158. declsize = size_to_string(buf, size_code_to_bytes(rom->declared_size));
  159. WRITE_LINE(dis, ".rom_size\t\"%s\"%s\t; $%zX bytes in %zu banks",
  160. size, strlen(size) < 6 ? "\t" : "", rom->size, NUM_BANKS(rom))
  161. WRITE_LINE(dis, ".rom_header\t$%04X",
  162. rom->header_location)
  163. WRITE_LINE(dis, ".rom_checksum\t%s",
  164. (rom->reported_checksum == rom->expected_checksum) ? "on" : "off")
  165. WRITE_LINE(dis, ".rom_product\t%u\t\t; %s",
  166. rom->product_code, product ? product : "(unknown)")
  167. WRITE_LINE(dis, ".rom_version\t%u",
  168. rom->version)
  169. WRITE_LINE(dis, ".rom_region\t%u\t\t; %s",
  170. rom->region_code, region ? region : "(unknown)")
  171. WRITE_LINE(dis, ".rom_declsize\t$%X\t\t; %s",
  172. rom->declared_size, declsize)
  173. }
  174. /*
  175. Initialize and return an array of ROMBank objects for the given ROM.
  176. */
  177. static ROMBank* init_banks(const ROM *rom)
  178. {
  179. size_t nbanks = NUM_BANKS(rom), i;
  180. ROMBank *banks = cr_malloc(sizeof(ROMBank) * (nbanks + 1));
  181. DataType *types = cr_calloc(sizeof(DataType), rom->size);
  182. for (i = 0; i < nbanks; i++) {
  183. banks[i].index = i;
  184. if (i == nbanks - 1 && rom->size % MMU_ROM_BANK_SIZE)
  185. banks[i].size = rom->size % MMU_ROM_BANK_SIZE;
  186. else
  187. banks[i].size = MMU_ROM_BANK_SIZE;
  188. banks[i].slot = -1;
  189. banks[i].data = rom->data + (i * MMU_ROM_BANK_SIZE);
  190. banks[i].types = types + (i * MMU_ROM_BANK_SIZE);
  191. }
  192. banks[nbanks].data = NULL; // Sentinel
  193. return banks;
  194. }
  195. /*
  196. Deallocate the given array of ROM banks.
  197. */
  198. static void free_banks(ROMBank *banks)
  199. {
  200. free(banks[0].types);
  201. free(banks);
  202. }
  203. /*
  204. Return the offset in bytes of the first address in the given bank.
  205. */
  206. static size_t get_bank_offset(const ROMBank *bank)
  207. {
  208. return MMU_ROM_BANK_SIZE * ((bank->slot >= 0) ? bank->slot :
  209. (bank->index > 2) ? 2 : bank->index);
  210. }
  211. /*
  212. Mark the ROM's header as non-binary/non-code inside of the relevant bank.
  213. */
  214. static void mark_header(const ROM *rom, ROMBank *banks)
  215. {
  216. size_t i;
  217. for (i = 0; i < HEADER_SIZE; i++)
  218. banks[0].types[rom->header_location + i] = DT_HEADER;
  219. }
  220. /*
  221. Render a line of binary data within a block.
  222. */
  223. static void render_binary(Disassembly *dis, size_t *idx, const ROMBank *bank)
  224. {
  225. size_t span = 1, tabs, i;
  226. while (span < MAX_BYTES_PER_LINE && *idx + span < bank->size &&
  227. bank->types[*idx + span] == DT_BINARY)
  228. span++;
  229. char buf[4 * MAX_BYTES_PER_LINE + 1];
  230. for (i = 0; i < span; i++)
  231. sprintf(buf + 4 * i, "$%02X ", bank->data[*idx + i]);
  232. buf[4 * span - 1] = '\0';
  233. char padding[16];
  234. tabs = span <= 16 ? 8 - (span - 1) / 2 : 1;
  235. padding[tabs] = '\0';
  236. while (tabs-- > 0)
  237. padding[tabs] = '\t';
  238. WRITE_LINE(dis, ".byte %s%s; $%04zX", buf, padding, *idx)
  239. (*idx) += span;
  240. }
  241. /*
  242. Render a single instruction within a block.
  243. */
  244. static void render_code(Disassembly *dis, size_t *idx, const ROMBank *bank)
  245. {
  246. DisasInstr *instr = disassemble_instruction(bank->data + *idx);
  247. char padding[16], *split;
  248. if ((split = strchr(instr->line, '\t'))) {
  249. size_t tabs = (40 - (instr->line + strlen(instr->line) - split)) / 8;
  250. padding[tabs] = '\0';
  251. while (tabs-- > 0)
  252. padding[tabs] = '\t';
  253. } else {
  254. strcpy(padding, "\t\t\t\t\t");
  255. }
  256. WRITE_LINE(dis, "\t%s%s\t; $%04zX: %s",
  257. instr->line, padding, get_bank_offset(bank) + *idx, instr->bytestr)
  258. (*idx) += instr->size;
  259. disas_instr_free(instr);
  260. }
  261. /*
  262. Render fully analyzed banks into lines of disassembly.
  263. */
  264. static void render_banks(Disassembly *dis, const ROMBank *banks)
  265. {
  266. size_t bn = 0, idx;
  267. DEBUG("Rendering lines")
  268. while (banks[bn].data) {
  269. TRACE("Rendering bank 0x%02zX (0x%06zX-0x%06zX)", bn,
  270. bn * MMU_ROM_BANK_SIZE, bn * MMU_ROM_BANK_SIZE + banks[bn].size)
  271. WRITE_LINE(dis, "")
  272. WRITE_LINE(dis, ";; " HRULE)
  273. WRITE_LINE(dis, "")
  274. WRITE_LINE(dis, ".block $%02zX", bn)
  275. idx = 0;
  276. while (idx < banks[bn].size) {
  277. switch (banks[bn].types[idx]) {
  278. case DT_BINARY:
  279. render_binary(dis, &idx, &banks[bn]);
  280. break;
  281. case DT_CODE:
  282. render_code(dis, &idx, &banks[bn]);
  283. break;
  284. case DT_HEADER:
  285. idx += HEADER_SIZE;
  286. break;
  287. default:
  288. FATAL("invalid data type %d at addr 0x%06zX",
  289. banks[bn].types[idx], bn * MMU_ROM_BANK_SIZE + idx)
  290. }
  291. }
  292. bn++;
  293. }
  294. }
  295. /*
  296. Disassemble a ROM into an array of strings, each storing one source line.
  297. Each line is newline-terminated. The array itself is terminated with a NULL
  298. element. Each line, and the overall array, must be free()d by the caller.
  299. */
  300. char** disassemble(const ROM *rom)
  301. {
  302. Disassembly dis = {.cap = 16, .len = 0};
  303. dis.lines = cr_malloc(sizeof(char*) * dis.cap);
  304. write_metadata(&dis, rom);
  305. disassemble_header(&dis, rom);
  306. ROMBank *banks = init_banks(rom);
  307. mark_header(rom, banks);
  308. // TODO: analyze(): set DT_CODE (future: make labels, slots) where appropriate
  309. for (size_t i = 0; i < 0x1000; i++)
  310. banks[0].types[i] = DT_CODE;
  311. render_banks(&dis, banks);
  312. free_banks(banks);
  313. write_line(&dis, NULL);
  314. return dis.lines;
  315. }
  316. /*
  317. Write a disassembly created by disassemble() to the given output file.
  318. Return whether the file was written successfully. This function frees the
  319. disassembly along the way.
  320. */
  321. static bool write_disassembly(const char *path, char **lines)
  322. {
  323. FILE *fp;
  324. char **itr = lines;
  325. if (!(fp = fopen(path, "w"))) {
  326. ERROR_ERRNO("couldn't open destination file")
  327. return false;
  328. }
  329. while (*itr) {
  330. if (!fwrite(*itr, strlen(*itr), 1, fp)) {
  331. fclose(fp);
  332. do free(*itr); while (*(++itr));
  333. ERROR_ERRNO("couldn't write to destination file")
  334. return false;
  335. }
  336. free(*itr);
  337. itr++;
  338. }
  339. fclose(fp);
  340. free(lines);
  341. return true;
  342. }
  343. /*
  344. Disassemble the binary file at the input path into z80 source code.
  345. Return true if the operation was a success and false if it was a failure.
  346. Errors are printed to STDOUT; if the operation was successful then nothing
  347. is printed.
  348. */
  349. bool disassemble_file(const char *src_path, const char *dst_path)
  350. {
  351. ROM rom;
  352. const char *errmsg;
  353. char **lines;
  354. DEBUG("Disassembling: %s -> %s", src_path, dst_path)
  355. if ((errmsg = rom_open(&rom, src_path))) {
  356. ERROR("couldn't load ROM image '%s': %s", src_path, errmsg)
  357. return false;
  358. }
  359. lines = disassemble(&rom);
  360. rom_close(&rom);
  361. DEBUG("Writing output file")
  362. return write_disassembly(dst_path, lines);
  363. }