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.
 
 
 
 
 

478 lines
12 KiB

  1. /* Copyright (C) 2014-2015 Ben Kurtovic <ben.kurtovic@gmail.com>
  2. Released under the terms of the MIT License. See LICENSE for details. */
  3. #include <errno.h>
  4. #include <stdio.h>
  5. #include <string.h>
  6. #include <sys/stat.h>
  7. #include "assembler.h"
  8. #include "logging.h"
  9. #include "util.h"
  10. #define DEFAULT_HEADER_OFFSET 0x7FF0
  11. #define DEFAULT_REGION "GG Export"
  12. #define SYMBOL_TABLE_BUCKETS 128
  13. /* Internal structs */
  14. struct ASMLine {
  15. char *data;
  16. size_t length;
  17. const Line *original;
  18. const char *filename;
  19. struct ASMLine *next;
  20. struct ASMLine *include;
  21. };
  22. typedef struct ASMLine ASMLine;
  23. struct ASMInclude {
  24. LineBuffer *lines;
  25. struct ASMInclude *next;
  26. };
  27. typedef struct ASMInclude ASMInclude;
  28. struct ASMInstruction {
  29. size_t offset;
  30. uint8_t length;
  31. uint8_t b1, b2, b3, b4;
  32. uint8_t virtual_byte;
  33. char *symbol;
  34. struct ASMInstruction *next;
  35. };
  36. typedef struct ASMInstruction ASMInstruction;
  37. struct ASMSymbol {
  38. size_t offset;
  39. char *symbol;
  40. struct ASMSymbol *next;
  41. };
  42. typedef struct ASMSymbol ASMSymbol;
  43. typedef struct {
  44. ASMSymbol *buckets[SYMBOL_TABLE_BUCKETS];
  45. } ASMSymbolTable;
  46. typedef struct {
  47. size_t offset;
  48. bool checksum;
  49. uint32_t product_code;
  50. uint8_t version;
  51. uint8_t region;
  52. uint8_t rom_size;
  53. } ASMHeaderInfo;
  54. typedef struct {
  55. ASMHeaderInfo header;
  56. bool optimizer;
  57. size_t rom_size;
  58. ASMLine *lines;
  59. ASMInclude *includes;
  60. ASMInstruction *instructions;
  61. ASMSymbolTable *symtable;
  62. } AssemblerState;
  63. /*
  64. Deallocate a LineBuffer previously created with read_source_file().
  65. */
  66. static void free_line_buffer(LineBuffer *buffer)
  67. {
  68. Line *line = buffer->lines, *temp;
  69. while (line) {
  70. temp = line->next;
  71. free(line->data);
  72. free(line);
  73. line = temp;
  74. }
  75. free(buffer->filename);
  76. free(buffer);
  77. }
  78. /*
  79. Read the contents of the source file at the given path into a line buffer.
  80. Return the buffer if reading was successful; it must be freed with
  81. free_line_buffer() when done. Return NULL if an error occurred while
  82. reading. A message will be printed to stderr in this case.
  83. */
  84. static LineBuffer* read_source_file(const char *path)
  85. {
  86. FILE *fp;
  87. struct stat st;
  88. if (!(fp = fopen(path, "r"))) {
  89. ERROR_ERRNO("couldn't open source file")
  90. return NULL;
  91. }
  92. if (fstat(fileno(fp), &st)) {
  93. fclose(fp);
  94. ERROR_ERRNO("couldn't open source file")
  95. return NULL;
  96. }
  97. if (!(st.st_mode & S_IFREG)) {
  98. fclose(fp);
  99. ERROR("couldn't open source file: %s", st.st_mode & S_IFDIR ?
  100. "Is a directory" : "Is not a regular file")
  101. return NULL;
  102. }
  103. LineBuffer *source = malloc(sizeof(LineBuffer));
  104. if (!source)
  105. OUT_OF_MEMORY()
  106. source->lines = NULL;
  107. source->filename = malloc(sizeof(char) * (strlen(path) + 1));
  108. if (!source->filename)
  109. OUT_OF_MEMORY()
  110. strcpy(source->filename, path);
  111. Line dummy = {.next = NULL};
  112. Line *line, *prev = &dummy;
  113. size_t lineno = 1;
  114. while (1) {
  115. char *data = NULL;
  116. size_t cap = 0;
  117. ssize_t len;
  118. if ((len = getline(&data, &cap, fp)) < 0) {
  119. if (feof(fp))
  120. break;
  121. if (errno == ENOMEM)
  122. OUT_OF_MEMORY()
  123. ERROR_ERRNO("couldn't read source file")
  124. fclose(fp);
  125. source->lines = dummy.next;
  126. free_line_buffer(source);
  127. return NULL;
  128. }
  129. line = malloc(sizeof(Line));
  130. if (!line)
  131. OUT_OF_MEMORY()
  132. line->data = data;
  133. line->length = feof(fp) ? len : (len - 1);
  134. line->lineno = lineno++;
  135. line->next = NULL;
  136. prev->next = line;
  137. prev = line;
  138. }
  139. fclose(fp);
  140. source->lines = dummy.next;
  141. return source;
  142. }
  143. /*
  144. Write an assembled binary file to the given path.
  145. Return whether the file was written successfully. On error, a message is
  146. printed to stderr.
  147. */
  148. static bool write_binary_file(const char *path, const uint8_t *data, size_t size)
  149. {
  150. FILE *fp;
  151. if (!(fp = fopen(path, "wb"))) {
  152. ERROR_ERRNO("couldn't open destination file")
  153. return false;
  154. }
  155. if (!fwrite(data, size, 1, fp)) {
  156. fclose(fp);
  157. ERROR_ERRNO("couldn't write to destination file")
  158. return false;
  159. }
  160. fclose(fp);
  161. return true;
  162. }
  163. /*
  164. Print an ErrorInfo object returned by assemble() to the given stream.
  165. */
  166. void error_info_print(const ErrorInfo *error_info, FILE *file)
  167. {
  168. // TODO
  169. fprintf(file, "Error: Unknown error");
  170. }
  171. /*
  172. Destroy an ErrorInfo object created by assemble().
  173. */
  174. void error_info_destroy(ErrorInfo *error_info)
  175. {
  176. if (!error_info)
  177. return;
  178. // TODO
  179. free(error_info);
  180. }
  181. /*
  182. Initialize default values in an AssemblerState object.
  183. */
  184. static void init_state(AssemblerState *state)
  185. {
  186. state->header.offset = DEFAULT_HEADER_OFFSET;
  187. state->header.checksum = true;
  188. state->header.product_code = 0;
  189. state->header.version = 0;
  190. state->header.region = region_string_to_code(DEFAULT_REGION);
  191. state->header.rom_size = 0;
  192. state->optimizer = false;
  193. state->rom_size = 0;
  194. state->lines = NULL;
  195. state->includes = NULL;
  196. state->instructions = NULL;
  197. state->symtable = NULL;
  198. }
  199. /*
  200. Deallocate an ASMLine list.
  201. */
  202. static void free_asm_lines(ASMLine *line)
  203. {
  204. while (line) {
  205. ASMLine *temp = line->next;
  206. free(line->data);
  207. free_asm_lines(line->include);
  208. free(line);
  209. line = temp;
  210. }
  211. }
  212. /*
  213. Deallocate an ASMInclude list.
  214. */
  215. static void free_asm_includes(ASMInclude *include)
  216. {
  217. while (include) {
  218. ASMInclude *temp = include->next;
  219. free_line_buffer(include->lines);
  220. free(include);
  221. include = temp;
  222. }
  223. }
  224. /*
  225. Deallocate an ASMInstruction list.
  226. */
  227. static void free_asm_instructions(ASMInstruction *inst)
  228. {
  229. while (inst) {
  230. ASMInstruction *temp = inst->next;
  231. if (inst->symbol)
  232. free(inst->symbol);
  233. free(inst);
  234. inst = temp;
  235. }
  236. }
  237. /*
  238. Deallocate an ASMSymbolTable.
  239. */
  240. static void free_asm_symtable(ASMSymbolTable *symtable)
  241. {
  242. if (!symtable)
  243. return;
  244. for (size_t bucket = 0; bucket < SYMBOL_TABLE_BUCKETS; bucket++) {
  245. ASMSymbol *sym = symtable->buckets[bucket], *temp;
  246. while (sym) {
  247. temp = sym->next;
  248. free(sym->symbol);
  249. free(sym);
  250. sym = temp;
  251. }
  252. }
  253. free(symtable);
  254. }
  255. /*
  256. Preprocess the LineBuffer into ASMLines. Change some state along the way.
  257. This function processes include directives, so read_source_file() may be
  258. called multiple times (along with the implications that has), and
  259. state->includes may be modified.
  260. On success, state->lines is modified and NULL is returned. On error, an
  261. ErrorInfo object is returned, and state->lines and state->includes are not
  262. modified.
  263. */
  264. static ErrorInfo* preprocess(AssemblerState *state, const LineBuffer *source)
  265. {
  266. // TODO
  267. // state->header.offset <-- check in list of acceptable values
  268. // state->header.checksum <-- boolean check
  269. // state->header.product_code <-- range check
  270. // state->header.version <-- range check
  271. // state->header.region <-- string conversion, check
  272. // state->header.rom_size <-- value/range check
  273. // state->optimizer <-- boolean check
  274. // state->rom_size <-- value check
  275. // if giving rom size, check header offset is in rom size range
  276. // if giving reported and actual rom size, check reported is <= actual
  277. // ensure no duplicate explicit assignments
  278. return NULL;
  279. }
  280. /*
  281. Tokenize ASMLines into ASMInstructions.
  282. On success, state->instructions is modified and NULL is returned. On error,
  283. an ErrorInfo object is returned and state->instructions is not modified.
  284. state->symtable may or may not be modified regardless of success.
  285. */
  286. static ErrorInfo* tokenize(AssemblerState *state)
  287. {
  288. // TODO
  289. // verify no instructions clash with header offset
  290. // if rom size is set, verify nothing overflows
  291. return NULL;
  292. }
  293. /*
  294. Resolve default placeholder values in assembler state, such as ROM size.
  295. On success, no new heap objects are allocated. On error, an ErrorInfo
  296. object is returned.
  297. */
  298. static ErrorInfo* resolve_defaults(AssemblerState *state)
  299. {
  300. // TODO
  301. // if (!state.rom_size)
  302. // set to max possible >= 32 KB, or error if too many instructions
  303. // if (state.header.rom_size)
  304. // check reported rom size is <= actual rom size
  305. // if (!state.header.rom_size)
  306. // set to actual rom size
  307. return NULL;
  308. }
  309. /*
  310. Resolve symbol placeholders in instructions such as jumps and branches.
  311. On success, no new heap objects are allocated. On error, an ErrorInfo
  312. object is returned.
  313. */
  314. static ErrorInfo* resolve_symbols(AssemblerState *state)
  315. {
  316. // TODO
  317. return NULL;
  318. }
  319. /*
  320. Convert finalized ASMInstructions into a binary data block.
  321. This function should never fail.
  322. */
  323. static void serialize_binary(AssemblerState *state, uint8_t *binary)
  324. {
  325. // TODO
  326. for (size_t i = 0; i < state->rom_size; i++)
  327. binary[i] = 'X';
  328. }
  329. /*
  330. Assemble the z80 source code in the source code buffer into binary data.
  331. If successful, return the size of the assembled binary data and change
  332. *binary_ptr to point to the assembled ROM data buffer. *binary_ptr must be
  333. free()'d when finished.
  334. If an error occurred, return 0 and update *ei_ptr to point to an ErrorInfo
  335. object which can be shown to the user with error_info_print(). The
  336. ErrorInfo object must be destroyed with error_info_destroy() when finished.
  337. In either case, only one of *binary_ptr and *ei_ptr is modified.
  338. */
  339. size_t assemble(const LineBuffer *source, uint8_t **binary_ptr, ErrorInfo **ei_ptr)
  340. {
  341. AssemblerState state;
  342. ErrorInfo *error_info;
  343. size_t retval = 0;
  344. init_state(&state);
  345. if ((error_info = preprocess(&state, source)))
  346. goto error;
  347. if (!(state.symtable = malloc(sizeof(ASMSymbolTable))))
  348. OUT_OF_MEMORY()
  349. for (size_t bucket = 0; bucket < SYMBOL_TABLE_BUCKETS; bucket++)
  350. state.symtable->buckets[bucket] = NULL;
  351. if ((error_info = tokenize(&state)))
  352. goto error;
  353. if ((error_info = resolve_defaults(&state)))
  354. goto error;
  355. if ((error_info = resolve_symbols(&state)))
  356. goto error;
  357. uint8_t *binary = malloc(sizeof(uint8_t) * state.rom_size);
  358. if (!binary)
  359. OUT_OF_MEMORY()
  360. serialize_binary(&state, binary);
  361. *binary_ptr = binary;
  362. retval = state.rom_size;
  363. goto cleanup;
  364. error:
  365. *ei_ptr = error_info;
  366. cleanup:
  367. free_asm_lines(state.lines);
  368. free_asm_includes(state.includes);
  369. free_asm_instructions(state.instructions);
  370. free_asm_symtable(state.symtable);
  371. return retval;
  372. }
  373. /*
  374. Assemble the z80 source code at the input path into a binary file.
  375. Return true if the operation was a success and false if it was a failure.
  376. Errors are printed to STDOUT; if the operation was successful then nothing
  377. is printed.
  378. */
  379. bool assemble_file(const char *src_path, const char *dst_path)
  380. {
  381. LineBuffer *source = read_source_file(src_path);
  382. if (!source)
  383. return false;
  384. uint8_t *binary;
  385. ErrorInfo *error_info;
  386. size_t size = assemble(source, &binary, &error_info);
  387. free_line_buffer(source);
  388. if (!size) {
  389. error_info_print(error_info, stderr);
  390. error_info_destroy(error_info);
  391. return false;
  392. }
  393. bool success = write_binary_file(dst_path, binary, size);
  394. free(binary);
  395. return success;
  396. }