An emulator, assembler, and disassembler for the Sega Game Gear
Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.
 
 
 
 
 

391 строка
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. }