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.
 
 
 
 
 

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