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.
 
 
 
 
 

425 lignes
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. }