super-famicom.cpp 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704
  1. struct SuperFamicom : Cartridge {
  2. auto name() -> string override { return "Super Famicom"; }
  3. auto extensions() -> vector<string> override { return {"sfc", "smc", "swc", "fig"}; }
  4. auto export(string location) -> vector<uint8_t> override;
  5. auto heuristics(vector<uint8_t>& data, string location) -> string override;
  6. auto region() const -> string;
  7. auto revision() const -> string;
  8. auto board() const -> string;
  9. auto title() const -> string;
  10. auto serial() const -> string;
  11. auto romSize() const -> uint;
  12. auto programRomSize() const -> uint;
  13. auto dataRomSize() const -> uint;
  14. auto expansionRomSize() const -> uint;
  15. auto firmwareRomSize() const -> uint;
  16. auto ramSize() const -> uint;
  17. auto expansionRamSize() const -> uint;
  18. auto nonVolatile() const -> bool;
  19. auto size() const -> uint { return data.size(); }
  20. auto scoreHeader(uint address) -> uint;
  21. auto firmwareARM() const -> string;
  22. auto firmwareEXNEC() const -> string;
  23. auto firmwareGB() const -> string;
  24. auto firmwareHITACHI() const -> string;
  25. auto firmwareNEC() const -> string;
  26. array_view<uint8_t> data;
  27. string location;
  28. natural headerAddress;
  29. };
  30. auto SuperFamicom::export(string location) -> vector<uint8_t> {
  31. vector<uint8_t> data;
  32. auto files = directory::files(location, "*.rom");
  33. append(data, {location, "program.rom"});
  34. append(data, {location, "data.rom" });
  35. for(auto& file : files.match("*.program.rom")) append(data, {location, file});
  36. for(auto& file : files.match("*.data.rom" )) append(data, {location, file});
  37. for(auto& file : files.match("*.boot.rom" )) append(data, {location, file});
  38. return data;
  39. }
  40. auto SuperFamicom::heuristics(vector<uint8_t>& data, string location) -> string {
  41. this->data = data;
  42. this->location = location;
  43. if((size() & 0x7fff) == 512) {
  44. //remove header if present
  45. memory::move(&data[0], &data[512], size() - 512);
  46. data.resize(size() - 512);
  47. }
  48. //ignore images too small to be valid
  49. if(size() < 0x8000) return {};
  50. uint LoROM = scoreHeader( 0x7fb0);
  51. uint HiROM = scoreHeader( 0xffb0);
  52. uint ExLoROM = scoreHeader(0x407fb0);
  53. uint ExHiROM = scoreHeader(0x40ffb0);
  54. if(ExLoROM) ExLoROM += 4;
  55. if(ExHiROM) ExHiROM += 4;
  56. if(LoROM >= HiROM && LoROM >= ExLoROM && LoROM >= ExHiROM) headerAddress = 0x7fb0;
  57. else if(HiROM >= ExLoROM && HiROM >= ExHiROM) headerAddress = 0xffb0;
  58. else if(ExLoROM >= ExHiROM) headerAddress = 0x407fb0;
  59. else headerAddress = 0x40ffb0;
  60. string s;
  61. auto& output = s;
  62. s += "game\n";
  63. s +={" name: ", Media::name(location), "\n"};
  64. s +={" label: ", Media::name(location), "\n"};
  65. s +={" title: ", title(), "\n"};
  66. s +={" region: ", region(), "\n"};
  67. s +={" revision: ", revision(), "\n"};
  68. s +={" board: ", board(), "\n"};
  69. auto board = this->board().trimRight("#A", 1L).split("-");
  70. if(auto size = romSize()) {
  71. if(board(0) == "SPC7110" && size > 0x100000) {
  72. s += " memory\n";
  73. s += " type: ROM\n";
  74. s += " size: 0x100000\n";
  75. s += " content: Program\n";
  76. s += " memory\n";
  77. s += " type: ROM\n";
  78. s +={" size: 0x", hex(size - 0x100000), "\n"};
  79. s += " content: Data\n";
  80. } else if(board(0) == "EXSPC7110" && size == 0x700000) {
  81. //Tengai Maykou Zero (fan translation)
  82. s += " memory\n";
  83. s += " type: ROM\n";
  84. s += " size: 0x100000\n";
  85. s += " content: Program\n";
  86. s += " memory\n";
  87. s += " type: ROM\n";
  88. s += " size: 0x500000\n";
  89. s += " content: Data\n";
  90. s += " memory\n";
  91. s += " type: ROM\n";
  92. s += " size: 0x100000\n";
  93. s += " content: Expansion\n";
  94. } else {
  95. s += " memory\n";
  96. s += " type: ROM\n";
  97. s +={" size: 0x", hex(size), "\n"};
  98. s += " content: Program\n";
  99. }
  100. }
  101. if(auto size = ramSize()) {
  102. s += " memory\n";
  103. s += " type: RAM\n";
  104. s +={" size: 0x", hex(size), "\n"};
  105. s += " content: Save\n";
  106. }
  107. if(auto size = expansionRamSize()) {
  108. s += " memory\n";
  109. s += " type: RAM\n";
  110. s +={" size: 0x", hex(size), "\n"};
  111. s += " content: Save\n";
  112. }
  113. if(board(0) == "ARM") {
  114. s += " memory\n";
  115. s += " type: ROM\n";
  116. s += " size: 0x20000\n";
  117. s += " content: Program\n";
  118. s += " manufacturer: SETA\n";
  119. s += " architecture: ARM6\n";
  120. s +={" identifier: ", firmwareARM(), "\n"};
  121. s += " memory\n";
  122. s += " type: ROM\n";
  123. s += " size: 0x8000\n";
  124. s += " content: Data\n";
  125. s += " manufacturer: SETA\n";
  126. s += " architecture: ARM6\n";
  127. s +={" identifier: ", firmwareARM(), "\n"};
  128. s += " memory\n";
  129. s += " type: RAM\n";
  130. s += " size: 0x4000\n";
  131. s += " content: Data\n";
  132. s += " manufacturer: SETA\n";
  133. s += " architecture: ARM6\n";
  134. s +={" identifier: ", firmwareARM(), "\n"};
  135. s += " volatile\n";
  136. s += " oscillator\n";
  137. s += " frequency: 21440000\n";
  138. } else if(board(0) == "BS" && board(1) == "MCC") {
  139. s += " memory\n";
  140. s += " type: RAM\n";
  141. s += " size: 0x80000\n";
  142. s += " content: Download\n";
  143. } else if(board(0) == "EXNEC") {
  144. s += " memory\n";
  145. s += " type: ROM\n";
  146. s += " size: 0xc000\n";
  147. s += " content: Program\n";
  148. s += " manufacturer: NEC\n";
  149. s += " architecture: uPD96050\n";
  150. s +={" identifier: ", firmwareEXNEC(), "\n"};
  151. s += " memory\n";
  152. s += " type: ROM\n";
  153. s += " size: 0x1000\n";
  154. s += " content: Data\n";
  155. s += " manufacturer: NEC\n";
  156. s += " architecture: uPD96050\n";
  157. s +={" identifier: ", firmwareEXNEC(), "\n"};
  158. s += " memory\n";
  159. s += " type: RAM\n";
  160. s += " size: 0x1000\n";
  161. s += " content: Data\n";
  162. s += " manufacturer: NEC\n";
  163. s += " architecture: uPD96050\n";
  164. s +={" identifier: ", firmwareEXNEC(), "\n"};
  165. s += " oscillator\n";
  166. s +={" frequency: ", firmwareEXNEC() == "ST010" ? 11000000 : 15000000, "\n"};
  167. } else if(board(0) == "GB") {
  168. s += " memory\n";
  169. s += " type: ROM\n";
  170. s += " size: 0x100\n";
  171. s += " content: Boot\n";
  172. s += " manufacturer: Nintendo\n";
  173. s += " architecture: SM83\n";
  174. s +={" identifier: ", firmwareGB(), "\n"};
  175. if(firmwareGB() == "SGB2") {
  176. s += " oscillator\n";
  177. s += " frequency: 20971520\n";
  178. }
  179. } else if(board(0) == "GSU") {
  180. //todo: MARIO CHIP 1 uses CPU oscillator
  181. s += " oscillator\n";
  182. s += " frequency: 21440000\n";
  183. } else if(board(0) == "HITACHI") {
  184. s += " memory\n";
  185. s += " type: ROM\n";
  186. s += " size: 0xc00\n";
  187. s += " content: Data\n";
  188. s += " manufacturer: Hitachi\n";
  189. s += " architecture: HG51BS169\n";
  190. s +={" identifier: ", firmwareHITACHI(), "\n"};
  191. s += " memory\n";
  192. s += " type: RAM\n";
  193. s += " size: 0xc00\n";
  194. s += " content: Data\n";
  195. s += " manufacturer: Hitachi\n";
  196. s += " architecture: HG51BS169\n";
  197. s +={" identifier: ", firmwareHITACHI(), "\n"};
  198. s += " volatile\n";
  199. s += " oscillator\n";
  200. s += " frequency: 20000000\n";
  201. } else if(board(0) == "NEC") {
  202. s += " memory\n";
  203. s += " type: ROM\n";
  204. s += " size: 0x1800\n";
  205. s += " content: Program\n";
  206. s += " manufacturer: NEC\n";
  207. s += " architecture: uPD7725\n";
  208. s +={" identifier: ", firmwareNEC(), "\n"};
  209. s += " memory\n";
  210. s += " type: ROM\n";
  211. s += " size: 0x800\n";
  212. s += " content: Data\n";
  213. s += " manufacturer: NEC\n";
  214. s += " architecture: uPD7725\n";
  215. s +={" identifier: ", firmwareNEC(), "\n"};
  216. s += " memory\n";
  217. s += " type: RAM\n";
  218. s += " size: 0x200\n";
  219. s += " content: Data\n";
  220. s += " manufacturer: NEC\n";
  221. s += " architecture: uPD7725\n";
  222. s +={" identifier: ", firmwareNEC(), "\n"};
  223. s += " volatile\n";
  224. s += " oscillator\n";
  225. s += " frequency: 7600000\n";
  226. } else if(board(0) == "SA1" || board(1) == "SA1") { //SA1-* or BS-SA1-*
  227. s += " memory\n";
  228. s += " type: RAM\n";
  229. s += " size: 0x800\n";
  230. s += " content: Internal\n";
  231. s += " volatile\n";
  232. }
  233. if(board.right() == "EPSONRTC") {
  234. s += " memory\n";
  235. s += " type: RTC\n";
  236. s += " size: 0x10\n";
  237. s += " content: Time\n";
  238. s += " manufacturer: Epson\n";
  239. } else if(board.right() == "SHARPRTC") {
  240. s += " memory\n";
  241. s += " type: RTC\n";
  242. s += " size: 0x10\n";
  243. s += " content: Time\n";
  244. s += " manufacturer: Sharp\n";
  245. }
  246. return output;
  247. }
  248. auto SuperFamicom::region() const -> string {
  249. string region;
  250. char A = data[headerAddress + 0x02]; //game type
  251. char B = data[headerAddress + 0x03]; //game code
  252. char C = data[headerAddress + 0x04]; //game code
  253. char D = data[headerAddress + 0x05]; //region code (new; sometimes ambiguous)
  254. auto E = data[headerAddress + 0x29]; //region code (old)
  255. auto valid = [](char n) { return (n >= '0' && n <= '9') || (n >= 'A' && n <= 'Z'); };
  256. if(data[headerAddress + 0x2a] == 0x33 && valid(A) && valid(B) & valid(C) & valid(D)) {
  257. string code{A, B, C, D};
  258. if(D == 'B') region = {"SNS-", code, "-BRA"};
  259. if(D == 'C') region = {"SNSN-", code, "-ROC"};
  260. if(D == 'D') region = {"SNSP-", code, "-NOE"};
  261. if(D == 'E') region = {"SNS-", code, "-USA"};
  262. if(D == 'F') region = {"SNSP-", code, "-FRA"};
  263. if(D == 'H') region = {"SNSP-", code, "-HOL"};
  264. if(D == 'I') region = {"SNSP-", code, "-ITA"};
  265. if(D == 'J') region = {"SHVC-", code, "-JPN"};
  266. if(D == 'K') region = {"SNSN-", code, "-KOR"};
  267. if(D == 'N') region = {"SNS-", code, "-CAN"};
  268. if(D == 'P') region = {"SNSP-", code, "-EUR"};
  269. if(D == 'S') region = {"SNSP-", code, "-ESP"};
  270. if(D == 'U') region = {"SNSP-", code, "-AUS"};
  271. if(D == 'W') region = {"SNSP-", code, "-SCN"};
  272. }
  273. if(!region) {
  274. if(E == 0x00) region = {"JPN"};
  275. if(E == 0x01) region = {"USA"};
  276. if(E == 0x02) region = {"EUR"};
  277. if(E == 0x03) region = {"SCN"};
  278. if(E == 0x06) region = {"FRA"};
  279. if(E == 0x07) region = {"HOL"};
  280. if(E == 0x08) region = {"ESP"};
  281. if(E == 0x09) region = {"NOE"};
  282. if(E == 0x0a) region = {"ITA"};
  283. if(E == 0x0b) region = {"ROC"};
  284. if(E == 0x0d) region = {"KOR"};
  285. if(E == 0x0f) region = {"CAN"};
  286. if(E == 0x10) region = {"BRA"};
  287. if(E == 0x11) region = {"AUS"};
  288. }
  289. return region ? region : "NTSC";
  290. }
  291. auto SuperFamicom::revision() const -> string {
  292. string revision;
  293. char A = data[headerAddress + 0x02]; //game type
  294. char B = data[headerAddress + 0x03]; //game code
  295. char C = data[headerAddress + 0x04]; //game code
  296. char D = data[headerAddress + 0x05]; //region code (new; sometimes ambiguous)
  297. auto E = data[headerAddress + 0x29]; //region code (old)
  298. uint F = data[headerAddress + 0x2b]; //revision code
  299. auto valid = [](char n) { return (n >= '0' && n <= '9') || (n >= 'A' && n <= 'Z'); };
  300. if(data[headerAddress + 0x2a] == 0x33 && valid(A) && valid(B) & valid(C) & valid(D)) {
  301. string code{A, B, C, D};
  302. if(D == 'B') revision = {"SNS-", code, "-", F};
  303. if(D == 'C') revision = {"SNSN-", code, "-", F};
  304. if(D == 'D') revision = {"SNSP-", code, "-", F};
  305. if(D == 'E') revision = {"SNS-", code, "-", F};
  306. if(D == 'F') revision = {"SNSP-", code, "-", F};
  307. if(D == 'H') revision = {"SNSP-", code, "-", F};
  308. if(D == 'I') revision = {"SNSP-", code, "-", F};
  309. if(D == 'J') revision = {"SHVC-", code, "-", F};
  310. if(D == 'K') revision = {"SNSN-", code, "-", F};
  311. if(D == 'N') revision = {"SNS-", code, "-", F};
  312. if(D == 'P') revision = {"SNSP-", code, "-", F};
  313. if(D == 'S') revision = {"SNSP-", code, "-", F};
  314. if(D == 'U') revision = {"SNSP-", code, "-", F};
  315. if(D == 'W') revision = {"SNSP-", code, "-", F};
  316. }
  317. if(!revision) {
  318. revision = {"1.", F};
  319. }
  320. return revision ? revision : string{"1.", F};
  321. }
  322. //format: [slot]-[coprocessor]-[mapper]-[ram]-[rtc]
  323. auto SuperFamicom::board() const -> string {
  324. string board;
  325. auto mapMode = data[headerAddress + 0x25];
  326. auto cartridgeTypeLo = data[headerAddress + 0x26] & 15;
  327. auto cartridgeTypeHi = data[headerAddress + 0x26] >> 4;
  328. auto cartridgeSubType = data[headerAddress + 0x0f];
  329. string mode;
  330. if(mapMode == 0x20 || mapMode == 0x30) mode = "LOROM-";
  331. if(mapMode == 0x21 || mapMode == 0x31) mode = "HIROM-";
  332. if(mapMode == 0x22 || mapMode == 0x32) mode = "SDD1-";
  333. if(mapMode == 0x23 || mapMode == 0x33) mode = "SA1-";
  334. if(mapMode == 0x25 || mapMode == 0x35) mode = "EXHIROM-";
  335. if(mapMode == 0x2a || mapMode == 0x3a) mode = "SPC7110-";
  336. //many games will store an extra title character, overwriting the map mode
  337. //further, ExLoROM mode is unofficial, and lacks a mapping mode value
  338. if(!mode) {
  339. if(headerAddress == 0x7fb0) mode = "LOROM-";
  340. if(headerAddress == 0xffb0) mode = "HIROM-";
  341. if(headerAddress == 0x407fb0) mode = "EXLOROM-";
  342. if(headerAddress == 0x40ffb0) mode = "EXHIROM-";
  343. }
  344. bool epsonRTC = false;
  345. bool sharpRTC = false;
  346. if(serial() == "A9PJ") {
  347. //Sufami Turbo (JPN)
  348. board.append("ST-", mode);
  349. } else if(serial() == "ZBSJ") {
  350. //BS-X: Sore wa Namae o Nusumareta Machi no Monogatari (JPN)
  351. board.append("BS-MCC-");
  352. } else if(serial() == "042J") {
  353. //Super Game Boy 2
  354. board.append("GB-", mode);
  355. } else if(serial().match("Z??J")) {
  356. board.append("BS-", mode);
  357. } else if(cartridgeTypeLo >= 0x3) {
  358. if(cartridgeTypeHi == 0x0) board.append("NEC-", mode);
  359. if(cartridgeTypeHi == 0x1) board.append("GSU-");
  360. if(cartridgeTypeHi == 0x2) board.append("OBC1-", mode);
  361. if(cartridgeTypeHi == 0x3) board.append("SA1-");
  362. if(cartridgeTypeHi == 0x4) board.append("SDD1-");
  363. if(cartridgeTypeHi == 0x5) board.append(mode), sharpRTC = true;
  364. if(cartridgeTypeHi == 0xe && cartridgeTypeLo == 0x3) board.append("GB-", mode);
  365. if(cartridgeTypeHi == 0xf && cartridgeTypeLo == 0x5 && cartridgeSubType == 0x00) board.append("SPC7110-");
  366. if(cartridgeTypeHi == 0xf && cartridgeTypeLo == 0x9 && cartridgeSubType == 0x00) board.append("SPC7110-"), epsonRTC = true;
  367. if(cartridgeTypeHi == 0xf && cartridgeSubType == 0x01) board.append("EXNEC-", mode);
  368. if(cartridgeTypeHi == 0xf && cartridgeSubType == 0x02) board.append("ARM-", mode);
  369. if(cartridgeTypeHi == 0xf && cartridgeSubType == 0x10) board.append("HITACHI-", mode);
  370. }
  371. if(!board) board.append(mode);
  372. if(ramSize() || expansionRamSize()) board.append("RAM-");
  373. if(epsonRTC) board.append("EPSONRTC-");
  374. if(sharpRTC) board.append("SHARPRTC-");
  375. board.trimRight("-", 1L);
  376. if(board.beginsWith( "LOROM-RAM") && romSize() <= 0x200000) board.append("#A");
  377. if(board.beginsWith("NEC-LOROM-RAM") && romSize() <= 0x100000) board.append("#A");
  378. //Tengai Makyou Zero (fan translation)
  379. if(board.beginsWith("SPC7110-") && data.size() == 0x700000) board.prepend("EX");
  380. return board;
  381. }
  382. auto SuperFamicom::title() const -> string {
  383. string label;
  384. for(uint n = 0; n < 0x15; n++) {
  385. auto x = data[headerAddress + 0x10 + n];
  386. auto y = n == 0x14 ? 0 : data[headerAddress + 0x11 + n];
  387. //null terminator (padding)
  388. if(x == 0x00 || x == 0xff);
  389. //ASCII
  390. else if(x >= 0x20 && x <= 0x7e) label.append((char)x);
  391. //Shift-JIS (half-width katakana)
  392. else if(x == 0xa1) label.append("。");
  393. else if(x == 0xa2) label.append("「");
  394. else if(x == 0xa3) label.append("」");
  395. else if(x == 0xa4) label.append("、");
  396. else if(x == 0xa5) label.append("・");
  397. else if(x == 0xa6) label.append("ヲ");
  398. else if(x == 0xa7) label.append("ァ");
  399. else if(x == 0xa8) label.append("ィ");
  400. else if(x == 0xa9) label.append("ゥ");
  401. else if(x == 0xaa) label.append("ェ");
  402. else if(x == 0xab) label.append("ォ");
  403. else if(x == 0xac) label.append("ャ");
  404. else if(x == 0xad) label.append("ュ");
  405. else if(x == 0xae) label.append("ョ");
  406. else if(x == 0xaf) label.append("ッ");
  407. else if(x == 0xb0) label.append("ー");
  408. else if(x == 0xb1) label.append( "ア");
  409. else if(x == 0xb2) label.append( "イ");
  410. else if(x == 0xb3) label.append(y == 0xde ? "ヴ" : "ウ");
  411. else if(x == 0xb4) label.append( "エ");
  412. else if(x == 0xb5) label.append( "オ");
  413. else if(x == 0xb6) label.append(y == 0xde ? "ガ" : "カ");
  414. else if(x == 0xb7) label.append(y == 0xde ? "ギ" : "キ");
  415. else if(x == 0xb8) label.append(y == 0xde ? "グ" : "ク");
  416. else if(x == 0xb9) label.append(y == 0xde ? "ゲ" : "ケ");
  417. else if(x == 0xba) label.append(y == 0xde ? "ゴ" : "コ");
  418. else if(x == 0xbb) label.append(y == 0xde ? "ザ" : "サ");
  419. else if(x == 0xbc) label.append(y == 0xde ? "ジ" : "シ");
  420. else if(x == 0xbd) label.append(y == 0xde ? "ズ" : "ス");
  421. else if(x == 0xbe) label.append(y == 0xde ? "ゼ" : "セ");
  422. else if(x == 0xbf) label.append(y == 0xde ? "ゾ" : "ソ");
  423. else if(x == 0xc0) label.append(y == 0xde ? "ダ" : "タ");
  424. else if(x == 0xc1) label.append(y == 0xde ? "ヂ" : "チ");
  425. else if(x == 0xc2) label.append(y == 0xde ? "ヅ" : "ツ");
  426. else if(x == 0xc3) label.append(y == 0xde ? "デ" : "テ");
  427. else if(x == 0xc4) label.append(y == 0xde ? "ド" : "ト");
  428. else if(x == 0xc5) label.append("ナ");
  429. else if(x == 0xc6) label.append("ニ");
  430. else if(x == 0xc7) label.append("ヌ");
  431. else if(x == 0xc8) label.append("ネ");
  432. else if(x == 0xc9) label.append("ノ");
  433. else if(x == 0xca) label.append(y == 0xdf ? "パ" : y == 0xde ? "バ" : "ハ");
  434. else if(x == 0xcb) label.append(y == 0xdf ? "ピ" : y == 0xde ? "ビ" : "ヒ");
  435. else if(x == 0xcc) label.append(y == 0xdf ? "プ" : y == 0xde ? "ブ" : "フ");
  436. else if(x == 0xcd) label.append(y == 0xdf ? "ペ" : y == 0xde ? "ベ" : "ヘ");
  437. else if(x == 0xce) label.append(y == 0xdf ? "ポ" : y == 0xde ? "ボ" : "ホ");
  438. else if(x == 0xcf) label.append("マ");
  439. else if(x == 0xd0) label.append("ミ");
  440. else if(x == 0xd1) label.append("ム");
  441. else if(x == 0xd2) label.append("メ");
  442. else if(x == 0xd3) label.append("モ");
  443. else if(x == 0xd4) label.append("ヤ");
  444. else if(x == 0xd5) label.append("ユ");
  445. else if(x == 0xd6) label.append("ヨ");
  446. else if(x == 0xd7) label.append("ラ");
  447. else if(x == 0xd8) label.append("リ");
  448. else if(x == 0xd9) label.append("ル");
  449. else if(x == 0xda) label.append("レ");
  450. else if(x == 0xdb) label.append("ロ");
  451. else if(x == 0xdc) label.append("ワ");
  452. else if(x == 0xdd) label.append("ン");
  453. else if(x == 0xde) label.append("\xef\xbe\x9e"); //dakuten
  454. else if(x == 0xdf) label.append("\xef\xbe\x9f"); //handakuten
  455. //unknown
  456. else label.append("?");
  457. //(han)dakuten skip
  458. if(y == 0xde && x == 0xb3) n++;
  459. if(y == 0xde && x >= 0xb6 && x <= 0xc4) n++;
  460. if(y == 0xde && x >= 0xca && x <= 0xce) n++;
  461. if(y == 0xdf && x >= 0xca && y <= 0xce) n++;
  462. }
  463. return label.strip();
  464. }
  465. auto SuperFamicom::serial() const -> string {
  466. char A = data[headerAddress + 0x02]; //game type
  467. char B = data[headerAddress + 0x03]; //game code
  468. char C = data[headerAddress + 0x04]; //game code
  469. char D = data[headerAddress + 0x05]; //region code (new; sometimes ambiguous)
  470. auto valid = [](char n) { return (n >= '0' && n <= '9') || (n >= 'A' && n <= 'Z'); };
  471. if(data[headerAddress + 0x2a] == 0x33 && valid(A) && valid(B) & valid(C) & valid(D)) {
  472. return {A, B, C, D};
  473. }
  474. return "";
  475. }
  476. auto SuperFamicom::romSize() const -> uint {
  477. return size() - firmwareRomSize();
  478. }
  479. auto SuperFamicom::programRomSize() const -> uint {
  480. if(board().beginsWith("SPC7110-")) return 0x100000;
  481. if(board().beginsWith("EXSPC7110-")) return 0x100000;
  482. return romSize();
  483. }
  484. auto SuperFamicom::dataRomSize() const -> uint {
  485. if(board().beginsWith("SPC7110-")) return romSize() - 0x100000;
  486. if(board().beginsWith("EXSPC7110-")) return 0x500000;
  487. return 0;
  488. }
  489. auto SuperFamicom::expansionRomSize() const -> uint {
  490. if(board().beginsWith("EXSPC7110-")) return 0x100000;
  491. return 0;
  492. }
  493. //detect if any firmware is appended to the ROM image, and return its size if so
  494. auto SuperFamicom::firmwareRomSize() const -> uint {
  495. auto cartridgeTypeLo = data[headerAddress + 0x26] & 15;
  496. auto cartridgeTypeHi = data[headerAddress + 0x26] >> 4;
  497. auto cartridgeSubType = data[headerAddress + 0x0f];
  498. if(serial() == "042J" || (cartridgeTypeLo == 0x3 && cartridgeTypeHi == 0xe)) {
  499. //Game Boy
  500. if((size() & 0x7fff) == 0x100) return 0x100;
  501. }
  502. if(cartridgeTypeLo >= 0x3 && cartridgeTypeHi == 0xf && cartridgeSubType == 0x10) {
  503. //Hitachi HG51BS169
  504. if((size() & 0x7fff) == 0xc00) return 0xc00;
  505. }
  506. if(cartridgeTypeLo >= 0x3 && cartridgeTypeHi == 0x0) {
  507. //NEC uPD7725
  508. if((size() & 0x7fff) == 0x2000) return 0x2000;
  509. }
  510. if(cartridgeTypeLo >= 0x3 && cartridgeTypeHi == 0xf && cartridgeSubType == 0x01) {
  511. //NEC uPD96050
  512. if((size() & 0xffff) == 0xd000) return 0xd000;
  513. }
  514. if(cartridgeTypeLo >= 0x3 && cartridgeTypeHi == 0xf && cartridgeSubType == 0x02) {
  515. //ARM6
  516. if((size() & 0x3ffff) == 0x28000) return 0x28000;
  517. }
  518. return 0;
  519. }
  520. auto SuperFamicom::ramSize() const -> uint {
  521. auto ramSize = data[headerAddress + 0x28] & 7;
  522. if(ramSize) return 1024 << ramSize;
  523. return 0;
  524. }
  525. auto SuperFamicom::expansionRamSize() const -> uint {
  526. if(data[headerAddress + 0x2a] == 0x33) {
  527. auto ramSize = data[headerAddress + 0x0d] & 7;
  528. if(ramSize) return 1024 << ramSize;
  529. }
  530. if((data[headerAddress + 0x26] >> 4) == 1) {
  531. //GSU: Starfox / Starwing lacks an extended header; but still has expansion RAM
  532. return 0x8000;
  533. }
  534. return 0;
  535. }
  536. auto SuperFamicom::nonVolatile() const -> bool {
  537. auto cartridgeTypeLo = data[headerAddress + 0x26] & 15;
  538. return cartridgeTypeLo == 0x2 || cartridgeTypeLo == 0x5 || cartridgeTypeLo == 0x6;
  539. }
  540. auto SuperFamicom::scoreHeader(uint address) -> uint {
  541. int score = 0;
  542. if(size() < address + 0x50) return score;
  543. uint8_t mapMode = data[address + 0x25] & ~0x10; //ignore FastROM bit
  544. uint16_t complement = data[address + 0x2c] << 0 | data[address + 0x2d] << 8;
  545. uint16_t checksum = data[address + 0x2e] << 0 | data[address + 0x2f] << 8;
  546. uint16_t resetVector = data[address + 0x4c] << 0 | data[address + 0x4d] << 8;
  547. if(resetVector < 0x8000) return score; //$00:0000-7fff is never ROM data
  548. uint8_t opcode = data[(address & ~0x7fff) | (resetVector & 0x7fff)]; //first instruction executed
  549. //most likely opcodes
  550. if(opcode == 0x78 //sei
  551. || opcode == 0x18 //clc (clc; xce)
  552. || opcode == 0x38 //sec (sec; xce)
  553. || opcode == 0x9c //stz $nnnn (stz $4200)
  554. || opcode == 0x4c //jmp $nnnn
  555. || opcode == 0x5c //jml $nnnnnn
  556. ) score += 8;
  557. //plausible opcodes
  558. if(opcode == 0xc2 //rep #$nn
  559. || opcode == 0xe2 //sep #$nn
  560. || opcode == 0xad //lda $nnnn
  561. || opcode == 0xae //ldx $nnnn
  562. || opcode == 0xac //ldy $nnnn
  563. || opcode == 0xaf //lda $nnnnnn
  564. || opcode == 0xa9 //lda #$nn
  565. || opcode == 0xa2 //ldx #$nn
  566. || opcode == 0xa0 //ldy #$nn
  567. || opcode == 0x20 //jsr $nnnn
  568. || opcode == 0x22 //jsl $nnnnnn
  569. ) score += 4;
  570. //implausible opcodes
  571. if(opcode == 0x40 //rti
  572. || opcode == 0x60 //rts
  573. || opcode == 0x6b //rtl
  574. || opcode == 0xcd //cmp $nnnn
  575. || opcode == 0xec //cpx $nnnn
  576. || opcode == 0xcc //cpy $nnnn
  577. ) score -= 4;
  578. //least likely opcodes
  579. if(opcode == 0x00 //brk #$nn
  580. || opcode == 0x02 //cop #$nn
  581. || opcode == 0xdb //stp
  582. || opcode == 0x42 //wdm
  583. || opcode == 0xff //sbc $nnnnnn,x
  584. ) score -= 8;
  585. if(checksum + complement == 0xffff) score += 4;
  586. if(address == 0x7fb0 && mapMode == 0x20) score += 2;
  587. if(address == 0xffb0 && mapMode == 0x21) score += 2;
  588. return max(0, score);
  589. }
  590. auto SuperFamicom::firmwareARM() const -> string {
  591. return "ST018";
  592. }
  593. auto SuperFamicom::firmwareEXNEC() const -> string {
  594. if(title() == "EXHAUST HEAT2") return "ST010";
  595. if(title() == "F1 ROC II") return "ST010";
  596. if(title() == "2DAN MORITA SHOUGI") return "ST011";
  597. return "ST010";
  598. }
  599. auto SuperFamicom::firmwareGB() const -> string {
  600. if(title() == "Super GAMEBOY") return "SGB1";
  601. if(title() == "Super GAMEBOY2") return "SGB2";
  602. return "SGB1";
  603. }
  604. auto SuperFamicom::firmwareHITACHI() const -> string {
  605. return "Cx4";
  606. }
  607. auto SuperFamicom::firmwareNEC() const -> string {
  608. if(title() == "PILOTWINGS") return "DSP1";
  609. if(title() == "DUNGEON MASTER") return "DSP2";
  610. if(title() == "SDガンダムGX") return "DSP3";
  611. if(title() == "PLANETS CHAMP TG3000") return "DSP4";
  612. if(title() == "TOP GEAR 3000") return "DSP4";
  613. return "DSP1B";
  614. }