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.
 
 
 
 
 

272 lines
7.5 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 <ctype.h>
  4. #include <errno.h>
  5. #include <stdio.h>
  6. #include <stdbool.h>
  7. #include <stdlib.h>
  8. #include <string.h>
  9. #include <sys/stat.h>
  10. #include "rom.h"
  11. #include "logging.h"
  12. #include "util.h"
  13. #define NUM_LOCATIONS 3
  14. #define SIZE_CODE_BUF 8
  15. static size_t header_locations[NUM_LOCATIONS] = {0x7FF0, 0x3FF0, 0x1FF0};
  16. /*
  17. @DEBUG_LEVEL
  18. Given a ROM size, return a pretty string.
  19. */
  20. static const char* size_to_string(size_t size)
  21. {
  22. static char buffer[SIZE_CODE_BUF];
  23. if (!size)
  24. strncpy(buffer, "unknown", SIZE_CODE_BUF);
  25. else if (size >= (1 << 20))
  26. snprintf(buffer, SIZE_CODE_BUF, "%zu MB", size >> 20);
  27. else
  28. snprintf(buffer, SIZE_CODE_BUF, "%zu KB", size >> 10);
  29. return buffer;
  30. }
  31. /*
  32. @DEBUG_LEVEL
  33. Print out the raw header to stdout.
  34. */
  35. static void print_header_dump(const uint8_t *header)
  36. {
  37. char header_hex[3 * HEADER_SIZE], header_chr[3 * HEADER_SIZE];
  38. for (int i = 0; i < HEADER_SIZE; i++) {
  39. snprintf(&header_hex[3 * i], 3, "%02X", header[i]);
  40. if (isprint(header[i]))
  41. snprintf(&header_chr[3 * i], 3, "%2c", header[i]);
  42. else {
  43. header_chr[3 * i] = ' ';
  44. header_chr[3 * i + 1] = '.';
  45. }
  46. header_hex[3 * i + 2] = header_chr[3 * i + 2] = ' ';
  47. }
  48. header_hex[3 * HEADER_SIZE - 1] = header_chr[3 * HEADER_SIZE - 1] = '\0';
  49. DEBUG("- header dump (hex): %s", header_hex)
  50. DEBUG("- header dump (chr): %s", header_chr)
  51. }
  52. /*
  53. @DEBUG_LEVEL
  54. Print out the analyzed header to stdout.
  55. */
  56. static void print_header_contents(const ROM *rom)
  57. {
  58. DEBUG("- header info:")
  59. if (rom->reported_checksum == rom->expected_checksum)
  60. DEBUG(" - checksum: 0x%04X (valid)", rom->reported_checksum)
  61. else
  62. DEBUG(" - checksum: 0x%04X (invalid, expected 0x%04X)",
  63. rom->reported_checksum, rom->expected_checksum)
  64. DEBUG(" - product code: %u (%s)", rom->product_code,
  65. rom_product(rom) ? rom_product(rom) : "unknown")
  66. DEBUG(" - version: %u", rom->version)
  67. DEBUG(" - region code: %u (%s)", rom->region_code,
  68. rom_region(rom) ? rom_region(rom) : "unknown")
  69. DEBUG(" - reported size: %s",
  70. size_to_string(size_code_to_bytes(rom->declared_size)))
  71. }
  72. /*
  73. Parse a ROM image's header, and return whether or not it is valid.
  74. The header is 16 bytes long, consisting of:
  75. byte 0: magic ('T')
  76. byte 1: magic ('M')
  77. byte 2: magic ('R')
  78. byte 3: magic (' ')
  79. byte 4: magic ('S')
  80. byte 5: magic ('E')
  81. byte 6: magic ('G')
  82. byte 7: magic ('A')
  83. byte 8: unused
  84. byte 9: unused
  85. byte A: checksum (LSB)
  86. byte B: checksum (MSB)
  87. byte C: product code (LSB)
  88. byte D: product code (middle byte)
  89. byte E (hi nibble): product code (most-significant nibble)
  90. byte E (lo nibble): version
  91. byte F (hi nibble): region code
  92. byte F (lo nibble): ROM size
  93. (Based on: http://www.smspower.org/Development/ROMHeader)
  94. */
  95. static bool parse_header(ROM *rom, const uint8_t *header)
  96. {
  97. if (DEBUG_LEVEL)
  98. print_header_dump(header);
  99. rom->reported_checksum = header[0xA] + (header[0xB] << 8);
  100. rom->expected_checksum = compute_checksum(rom->data, rom->size, header[0xF]);
  101. rom->product_code = bcd_decode(header[0xC]) +
  102. (bcd_decode(header[0xD]) * 100) + ((header[0xE] >> 4) * 10000);
  103. rom->version = header[0xE] & 0x0F;
  104. rom->region_code = header[0xF] >> 4;
  105. rom->declared_size = header[0xF] & 0xF;
  106. if (DEBUG_LEVEL)
  107. print_header_contents(rom);
  108. return true;
  109. }
  110. /*
  111. Find and read a ROM image's header, and return whether or not it is valid.
  112. */
  113. static bool find_and_read_header(ROM *rom)
  114. {
  115. size_t location, i;
  116. const uint8_t *header;
  117. DEBUG("- looking for header:")
  118. for (i = 0; i < NUM_LOCATIONS; i++) {
  119. location = header_locations[i];
  120. if (location + HEADER_SIZE > rom->size) {
  121. DEBUG(" - skipping location 0x%zX, out of range", location)
  122. continue;
  123. }
  124. DEBUG(" - trying location 0x%zX:", location)
  125. header = &rom->data[location];
  126. if (memcmp(header, rom_header_magic, HEADER_MAGIC_LEN)) {
  127. DEBUG(" - magic not present")
  128. }
  129. else {
  130. DEBUG(" - magic found")
  131. rom->header_location = location;
  132. return parse_header(rom, header);
  133. }
  134. }
  135. DEBUG(" - couldn't find header")
  136. return false;
  137. }
  138. /*
  139. Create and load a ROM image located at the given path.
  140. rom_ptr will point to the new object if created successfully, and NULL will
  141. be returned. Otherwise, rom_ptr will not be modified and an error string
  142. will be returned. The error string should not be freed.
  143. */
  144. const char* rom_open(ROM **rom_ptr, const char *path)
  145. {
  146. ROM *rom;
  147. FILE *fp;
  148. struct stat st;
  149. if (!(fp = fopen(path, "rb")))
  150. return strerror(errno);
  151. if (fstat(fileno(fp), &st)) {
  152. fclose(fp);
  153. return strerror(errno);
  154. }
  155. if (!(st.st_mode & S_IFREG)) {
  156. fclose(fp);
  157. return (st.st_mode & S_IFDIR) ? rom_err_isdir : rom_err_notfile;
  158. }
  159. rom = cr_malloc(sizeof(ROM));
  160. // Set defaults:
  161. rom->name = NULL;
  162. rom->data = NULL;
  163. rom->size = 0;
  164. rom->header_location = 0;
  165. rom->reported_checksum = 0;
  166. rom->expected_checksum = 0;
  167. rom->product_code = 0;
  168. rom->version = 0;
  169. rom->region_code = 0;
  170. rom->declared_size = 0;
  171. // Set rom->name:
  172. rom->name = cr_malloc(sizeof(char) * (strlen(path) + 1));
  173. strcpy(rom->name, path);
  174. DEBUG("Loading ROM %s:", rom->name)
  175. // Set rom->size:
  176. DEBUG("- size: %lld bytes (%s)", st.st_size, size_to_string(st.st_size))
  177. if (size_bytes_to_code(st.st_size) == INVALID_SIZE_CODE) {
  178. rom_close(rom);
  179. fclose(fp);
  180. return rom_err_badsize;
  181. }
  182. rom->size = st.st_size;
  183. // Set rom->data:
  184. rom->data = cr_malloc(sizeof(uint8_t) * st.st_size);
  185. if (!(fread(rom->data, st.st_size, 1, fp))) {
  186. rom_close(rom);
  187. fclose(fp);
  188. return rom_err_badread;
  189. }
  190. fclose(fp);
  191. // Parse the header:
  192. if (!find_and_read_header(rom)) {
  193. rom_close(rom);
  194. return rom_err_badheader;
  195. }
  196. if (rom->region_code == 3 || rom->region_code == 4) {
  197. // TODO: support SMS ROMs eventually?
  198. rom_close(rom);
  199. return rom_err_sms;
  200. }
  201. *rom_ptr = rom;
  202. return NULL;
  203. }
  204. /*
  205. Free a ROM object previously created with rom_open().
  206. */
  207. void rom_close(ROM *rom)
  208. {
  209. free(rom->name);
  210. free(rom->data);
  211. free(rom);
  212. }
  213. /*
  214. Return a string explanation of this ROM's product code.
  215. NULL is returned if the product code is not known.
  216. Information from: http://www.smspower.org/Development/ProductCodes
  217. */
  218. const char* rom_product(const ROM *rom)
  219. {
  220. uint32_t developer = rom->product_code / 1000;
  221. if (developer == 2)
  222. return "Sega of America";
  223. if (developer == 3)
  224. return "Sega of Japan";
  225. if (developer > 10 && developer < 160)
  226. return get_third_party_developer(developer);
  227. return NULL;
  228. }
  229. /*
  230. Return the region this ROM was intended for, based on header information.
  231. NULL is returned if the region code is invalid.
  232. */
  233. const char* rom_region(const ROM *rom)
  234. {
  235. return region_code_to_string(rom->region_code);
  236. }