game-boy.cpp 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322
  1. struct GameBoy : Cartridge {
  2. auto name() -> string override { return "Game Boy"; }
  3. auto extensions() -> vector<string> override { return {"gb"}; }
  4. auto export(string location) -> vector<uint8_t> override;
  5. auto heuristics(vector<uint8_t>& data, string location) -> string override;
  6. };
  7. auto GameBoy::export(string location) -> vector<uint8_t> {
  8. vector<uint8_t> data;
  9. append(data, {location, "program.rom"});
  10. return data;
  11. }
  12. auto GameBoy::heuristics(vector<uint8_t>& data, string location) -> string {
  13. if(data.size() < 0x4000) return {};
  14. uint headerAddress = data.size() < 0x8000 ? data.size() : data.size() - 0x8000;
  15. auto read = [&](uint offset) { return data[headerAddress + offset]; };
  16. if(read(0x0104) == 0xce && read(0x0105) == 0xed && read(0x0106) == 0x66 && read(0x0107) == 0x66
  17. && read(0x0108) == 0xcc && read(0x0109) == 0x0d && read(0x0147) >= 0x0b && read(0x0147) <= 0x0d
  18. ) {
  19. //MMM01 stores header at bottom of data[]
  20. } else {
  21. //all other mappers store header at top of data[]
  22. headerAddress = 0;
  23. }
  24. bool black = (read(0x0143) & 0xc0) == 0x80; //cartridge works in DMG+CGB mode
  25. bool clear = (read(0x0143) & 0xc0) == 0xc0; //cartridge works in CGB mode only
  26. bool ram = false;
  27. bool battery = false;
  28. bool eeprom = false;
  29. bool flash = false;
  30. bool rtc = false;
  31. bool accelerometer = false;
  32. bool rumble = false;
  33. uint romSize = 0;
  34. uint ramSize = 0;
  35. uint eepromSize = 0;
  36. uint eepromWidth = 0;
  37. uint flashSize = 0;
  38. uint rtcSize = 0;
  39. string mapper = "MBC0";
  40. switch(read(0x0147)) {
  41. case 0x00:
  42. mapper = "MBC0";
  43. break;
  44. case 0x01:
  45. mapper = "MBC1";
  46. break;
  47. case 0x02:
  48. mapper = "MBC1";
  49. ram = true;
  50. break;
  51. case 0x03:
  52. mapper = "MBC1";
  53. battery = true;
  54. ram = true;
  55. break;
  56. case 0x05:
  57. mapper = "MBC2";
  58. ram = true;
  59. break;
  60. case 0x06:
  61. mapper = "MBC2";
  62. battery = true;
  63. ram = true;
  64. break;
  65. case 0x08:
  66. mapper = "MBC0";
  67. ram = true;
  68. break;
  69. case 0x09:
  70. mapper = "MBC0";
  71. battery = true;
  72. ram = true;
  73. break;
  74. case 0x0b:
  75. mapper = "MMM01";
  76. break;
  77. case 0x0c:
  78. mapper = "MMM01";
  79. ram = true;
  80. break;
  81. case 0x0d:
  82. mapper = "MMM01";
  83. battery = true;
  84. ram = true;
  85. break;
  86. case 0x0f:
  87. mapper = "MBC3";
  88. battery = true;
  89. rtc = true;
  90. break;
  91. case 0x10:
  92. mapper = "MBC3";
  93. battery = true;
  94. ram = true;
  95. rtc = true;
  96. break;
  97. case 0x11:
  98. mapper = "MBC3";
  99. break;
  100. case 0x12:
  101. mapper = "MBC3";
  102. ram = true;
  103. break;
  104. case 0x13:
  105. mapper = "MBC3";
  106. battery = true;
  107. ram = true;
  108. break;
  109. case 0x19:
  110. mapper = "MBC5";
  111. break;
  112. case 0x1a:
  113. mapper = "MBC5";
  114. ram = true;
  115. break;
  116. case 0x1b:
  117. mapper = "MBC5";
  118. battery = true;
  119. ram = true;
  120. break;
  121. case 0x1c:
  122. mapper = "MBC5";
  123. rumble = true;
  124. break;
  125. case 0x1d:
  126. mapper = "MBC5";
  127. ram = true;
  128. rumble = true;
  129. break;
  130. case 0x1e:
  131. mapper = "MBC5";
  132. battery = true;
  133. ram = true;
  134. rumble = true;
  135. break;
  136. case 0x20:
  137. mapper = "MBC6";
  138. flash = true;
  139. battery = true;
  140. ram = true;
  141. break;
  142. case 0x22:
  143. mapper = "MBC7";
  144. battery = true;
  145. eeprom = true;
  146. accelerometer = true;
  147. rumble = true;
  148. break;
  149. case 0xfc:
  150. mapper = "CAMERA";
  151. break;
  152. case 0xfd:
  153. mapper = "TAMA";
  154. battery = true;
  155. ram = true;
  156. rtc = true;
  157. break;
  158. case 0xfe:
  159. mapper = "HuC3";
  160. break;
  161. case 0xff:
  162. mapper = "HuC1";
  163. battery = true;
  164. ram = true;
  165. break;
  166. }
  167. //Game Boy: title = $0134-0143
  168. //Game Boy Color (early games): title = $0134-0142; model = $0143
  169. //Game Boy Color (later games): title = $0134-013e; serial = $013f-0142; model = $0143
  170. string title;
  171. for(uint n : range(black || clear ? 15 : 16)) {
  172. char byte = read(0x0134 + n);
  173. if(byte < 0x20 || byte > 0x7e) byte = ' ';
  174. title.append(byte);
  175. }
  176. string serial = title.slice(-4);
  177. if(!black && !clear) serial = "";
  178. for(auto& byte : serial) {
  179. if(byte >= 'A' && byte <= 'Z') continue;
  180. //invalid serial
  181. serial = "";
  182. break;
  183. }
  184. title.trimRight(serial, 1L); //remove the serial from the title, if it exists
  185. title.strip(); //remove any excess whitespace from the title
  186. switch(read(0x0148)) { default:
  187. case 0x00: romSize = 2 * 16 * 1024; break;
  188. case 0x01: romSize = 4 * 16 * 1024; break;
  189. case 0x02: romSize = 8 * 16 * 1024; break;
  190. case 0x03: romSize = 16 * 16 * 1024; break;
  191. case 0x04: romSize = 32 * 16 * 1024; break;
  192. case 0x05: romSize = 64 * 16 * 1024; break;
  193. case 0x06: romSize = 128 * 16 * 1024; break;
  194. case 0x07: romSize = 256 * 16 * 1024; break;
  195. case 0x52: romSize = 72 * 16 * 1024; break;
  196. case 0x53: romSize = 80 * 16 * 1024; break;
  197. case 0x54: romSize = 96 * 16 * 1024; break;
  198. }
  199. switch(read(0x0149)) { default:
  200. case 0x00: ramSize = 0 * 1024; break;
  201. case 0x01: ramSize = 2 * 1024; break;
  202. case 0x02: ramSize = 8 * 1024; break;
  203. case 0x03: ramSize = 32 * 1024; break;
  204. case 0x04: ramSize = 128 * 1024; break;
  205. case 0x05: ramSize = 64 * 1024; break;
  206. }
  207. if(mapper == "MBC2" && ram) ramSize = 256;
  208. if(mapper == "MBC6" && ram) ramSize = 32 * 1024;
  209. if(mapper == "TAMA" && ram) ramSize = 32;
  210. if(mapper == "MBC6" && flash) flashSize = 1024 * 1024;
  211. //Game Boy header does not specify EEPROM size: detect via game title instead
  212. //Command Master: EEPROM = 512 bytes
  213. //Kirby Tilt 'n' Tumble: EEPROM = 256 bytes
  214. //Korokoro Kirby: EEPROM = 256 bytes
  215. if(mapper == "MBC7" && eeprom) {
  216. eepromSize = 256; //fallback guess; supported values are 128, 256, 512, 1024, 2048
  217. eepromWidth = 16; //93LCx6 supports 8-bit mode, but no licensed games use it
  218. if(title == "CMASTER" && serial == "KCEJ") eepromSize = 512;
  219. if(title == "KIRBY TNT" && serial == "KTNE") eepromSize = 256;
  220. if(title == "KORO2 KIRBY" && serial == "KKKJ") eepromSize = 256;
  221. }
  222. if(mapper == "MBC3" && rtc) rtcSize = 13;
  223. if(mapper == "TAMA" && rtc) rtcSize = 21;
  224. string s;
  225. s += "game\n";
  226. s +={" name: ", Media::name(location), "\n"};
  227. s +={" label: ", Media::name(location), "\n"};
  228. s +={" title: ", title, "\n"};
  229. if(serial)
  230. s +={" serial: ", serial, "\n"};
  231. s +={" board: ", mapper, "\n"};
  232. s += " memory\n";
  233. s += " type: ROM\n";
  234. s +={" size: 0x", hex(data.size()), "\n"};
  235. s += " content: Program\n";
  236. if(ram && ramSize) {
  237. s += " memory\n";
  238. s += " type: RAM\n";
  239. s +={" size: 0x", hex(ramSize), "\n"};
  240. s += " content: Save\n";
  241. if(!battery)
  242. s += " volatile\n";
  243. }
  244. if(eeprom && eepromSize) {
  245. s += " memory\n";
  246. s += " type: EEPROM\n";
  247. s +={" size: 0x", hex(eepromSize), "\n"};
  248. s +={" width: ", eepromWidth, "\n"};
  249. s += " content: Save\n";
  250. }
  251. if(flash && flashSize) {
  252. s += " memory\n";
  253. s += " type: Flash\n";
  254. s +={" size: 0x", hex(flashSize), "\n"};
  255. s += " content: Download\n";
  256. }
  257. if(rtc && rtcSize) {
  258. s += " memory\n";
  259. s += " type: RTC\n";
  260. s +={" size: 0x", hex(rtcSize), "\n"};
  261. s += " content: Time\n";
  262. }
  263. if(accelerometer)
  264. s += " accelerometer\n";
  265. if(rumble)
  266. s += " rumble\n";
  267. return s;
  268. }