An emulator, assembler, and disassembler for the Sega Game Gear
Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.
 
 
 
 
 

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