├── .github └── FUNDING.yml ├── .gitignore ├── LICENSE ├── README.md ├── arduinogameboy.fzz ├── gameboy_read └── gameboy_read.ino └── gameboy_write └── gameboy_write.ino /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: drhelius 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | .DS_Store 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2013 Ignacio Sanchez Gines 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ArduinoGameBoy - Arduino Game Boy Cartridge Dumper 2 | ======= 3 | Copyright © 2013 by Ignacio Sanchez 4 | 5 | ---------- 6 | 7 | Arduino based Game Boy cartridge reader and writer. 8 | 9 | It can dump ROM and RAM to an SD card. 10 | 11 | Follow me on Twitter: http://twitter.com/drhelius 12 | 13 | ---------- 14 | 15 | Features 16 | -------- 17 | 18 | - Designed for Arduino Mega 2560, but may be easily ported to other Arduinos. 19 | - SD card reading / writing 20 | - Auto selection of Memory Bank Controller (MBC1, MBC2, MBC3 and MBC5) 21 | - Dump cartridge header, ROM and RAM banks to SD. 22 | - Write RAM from SD back to the cartridge. 23 | - Log through serial connection. 24 | - Fritzing design. 25 | 26 | Todo List 27 | ----------- 28 | - Improve MBC dumping for higher rom bank counts. 29 | 30 | Pictures 31 | ----------- 32 | 33 | ![Screenshot](http://www.geardome.com/files/arduinogb/gameboy_read.png) 34 | ![Screenshot](http://www.geardome.com/files/arduinogb/arduino_gameboy_1.jpg) 35 | ![Screenshot](http://www.geardome.com/files/arduinogb/arduino_gameboy_2.jpg) 36 | 37 | License 38 | ------- 39 | 40 | ArduinoGameBoy 41 | 42 | Copyright (C) 2013 Ignacio Sanchez 43 | 44 | This program is free software: you can redistribute it and/or modify 45 | it under the terms of the GNU General Public License as published by 46 | the Free Software Foundation, either version 3 of the License, or 47 | any later version. 48 | 49 | This program is distributed in the hope that it will be useful, 50 | but WITHOUT ANY WARRANTY; without even the implied warranty of 51 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 52 | GNU General Public License for more details. 53 | 54 | You should have received a copy of the GNU General Public License 55 | along with this program. If not, see http://www.gnu.org/licenses/ 56 | -------------------------------------------------------------------------------- /arduinogameboy.fzz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/drhelius/arduinogameboy/eeb65e7db272998078cf7c6dea0d1d1366bf7536/arduinogameboy.fzz -------------------------------------------------------------------------------- /gameboy_read/gameboy_read.ino: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | 4 | /////////////////////////////////////////// 5 | /////////////////////////////////////////// 6 | 7 | const int SD_CS_PIN = 53; 8 | 9 | const int MBC_NOT_SUPPORTED = -1; 10 | const int MBC_NONE = 0; 11 | const int MBC_1 = 1; 12 | const int MBC_2 = 2; 13 | const int MBC_3 = 3; 14 | const int MBC_5 = 5; 15 | 16 | char romTitle[16]; 17 | byte romVersion; 18 | byte cartridgeType; 19 | int mbcType; 20 | boolean colorGameBoy; 21 | boolean superGameBoy; 22 | byte romSize; 23 | byte ramSize; 24 | byte romBanks; 25 | byte ramBanks; 26 | boolean cartridgeRTC; 27 | boolean cartridgeRumble; 28 | boolean cartridgeBattery; 29 | 30 | File dumpFile; 31 | 32 | char romFileName[] = "DUMP00.GB"; 33 | char ramFileName[] = "DUMP00.RAM"; 34 | 35 | /////////////////////////////////////////// 36 | /////////////////////////////////////////// 37 | 38 | void InitSD() 39 | { 40 | if (!SD.begin(SD_CS_PIN)) 41 | { 42 | Serial.println("SD CARD FAILED!"); 43 | Die(); 44 | } 45 | } 46 | 47 | /////////////////////////////////////////// 48 | /////////////////////////////////////////// 49 | 50 | void CreateROMFileInSD() 51 | { 52 | for (uint8_t i = 0; i < 100; i++) 53 | { 54 | romFileName[4] = i/10 + '0'; 55 | romFileName[5] = i%10 + '0'; 56 | if (!SD.exists(romFileName)) 57 | { 58 | dumpFile = SD.open(romFileName, FILE_WRITE); 59 | break; 60 | } 61 | } 62 | 63 | if (!dumpFile) 64 | { 65 | Serial.println("ROM FILE FAILED!"); 66 | Die(); 67 | } 68 | } 69 | 70 | /////////////////////////////////////////// 71 | /////////////////////////////////////////// 72 | 73 | void CreateRAMFileInSD() 74 | { 75 | for (uint8_t i = 0; i < 100; i++) 76 | { 77 | ramFileName[4] = i/10 + '0'; 78 | ramFileName[5] = i%10 + '0'; 79 | if (!SD.exists(ramFileName)) 80 | { 81 | dumpFile = SD.open(ramFileName, FILE_WRITE); 82 | break; 83 | } 84 | } 85 | 86 | if (!dumpFile) 87 | { 88 | Serial.println("RAM FILE FAILED!"); 89 | Die(); 90 | } 91 | } 92 | 93 | /////////////////////////////////////////// 94 | /////////////////////////////////////////// 95 | 96 | void DataBusAsInput() 97 | { 98 | DDRF = B00000000; 99 | } 100 | 101 | /////////////////////////////////////////// 102 | /////////////////////////////////////////// 103 | 104 | void DataBusAsOutput() 105 | { 106 | DDRF = B11111111; 107 | } 108 | 109 | /////////////////////////////////////////// 110 | /////////////////////////////////////////// 111 | 112 | byte GetByte(word address) 113 | { 114 | WriteAddress(address); 115 | PORTL = B00000101; 116 | delayMicroseconds(10); 117 | byte result = PINF; 118 | PORTL = B00000111; 119 | delayMicroseconds(10); 120 | return result; 121 | } 122 | 123 | /////////////////////////////////////////// 124 | /////////////////////////////////////////// 125 | 126 | byte GetRAMByte(word address) 127 | { 128 | WriteAddress(address); 129 | PORTL = B00000001; 130 | delayMicroseconds(10); 131 | byte result = PINF; 132 | PORTL = B00000111; 133 | delayMicroseconds(10); 134 | return result; 135 | } 136 | 137 | /////////////////////////////////////////// 138 | /////////////////////////////////////////// 139 | 140 | void PutByte(word address, byte data) 141 | { 142 | WriteAddress(address); 143 | PORTF = data; 144 | PORTL = B00000110; 145 | delayMicroseconds(10); 146 | PORTL = B00000111; 147 | delayMicroseconds(10); 148 | } 149 | 150 | /////////////////////////////////////////// 151 | /////////////////////////////////////////// 152 | 153 | void WriteAddress(word address) 154 | { 155 | PORTA = address & 0xFF; 156 | PORTK = (address >> 8) & 0xFF; 157 | } 158 | 159 | /////////////////////////////////////////// 160 | /////////////////////////////////////////// 161 | 162 | void DumpROM() 163 | { 164 | CreateROMFileInSD(); 165 | 166 | for (byte i = 0; i < romBanks; i++) 167 | { 168 | DumpROMBank(i); 169 | } 170 | 171 | dumpFile.close(); 172 | 173 | Serial.print("ROM DUMPED TO: "); 174 | Serial.println(romFileName); 175 | } 176 | 177 | /////////////////////////////////////////// 178 | /////////////////////////////////////////// 179 | 180 | void DumpROMBank(byte bank) 181 | { 182 | Serial.print("DUMPING ROM BANK "); 183 | Serial.println(bank); 184 | 185 | word offset = 0; 186 | 187 | if (bank > 0) 188 | { 189 | offset = 0x4000; 190 | SwitchROMBank(bank); 191 | } 192 | 193 | for (word address = 0; address < 0x4000; address++) 194 | { 195 | byte data = GetByte(address + offset); 196 | dumpFile.write(data); 197 | } 198 | 199 | dumpFile.flush(); 200 | 201 | Serial.println("DUMPED!"); 202 | } 203 | 204 | /////////////////////////////////////////// 205 | /////////////////////////////////////////// 206 | 207 | void SwitchROMBank(byte bank) 208 | { 209 | DataBusAsOutput(); 210 | 211 | switch (mbcType) 212 | { 213 | case MBC_1: 214 | { 215 | PutByte(0x2100, bank); 216 | break; 217 | } 218 | case MBC_2: 219 | case MBC_3: 220 | { 221 | PutByte(0x2100, bank); 222 | break; 223 | } 224 | case MBC_5: 225 | { 226 | PutByte(0x2100, bank); 227 | break; 228 | } 229 | } 230 | 231 | DataBusAsInput(); 232 | } 233 | 234 | /////////////////////////////////////////// 235 | /////////////////////////////////////////// 236 | 237 | void DumpRAM() 238 | { 239 | if (cartridgeBattery) 240 | { 241 | CreateRAMFileInSD(); 242 | EnableRAM(); 243 | 244 | int maxBanks = ramBanks; 245 | 246 | if (mbcType == MBC_2) 247 | { 248 | maxBanks = 1; 249 | } 250 | 251 | for (byte i = 0; i < maxBanks; i++) 252 | { 253 | DumpRAMBank(i); 254 | } 255 | 256 | DisableRAM(); 257 | 258 | dumpFile.close(); 259 | 260 | Serial.print("RAM DUMPED TO: "); 261 | Serial.println(ramFileName); 262 | } 263 | } 264 | 265 | /////////////////////////////////////////// 266 | /////////////////////////////////////////// 267 | 268 | void DumpRAMBank(byte bank) 269 | { 270 | word maxAddress = 0xC000; 271 | 272 | if (mbcType == MBC_2) 273 | { 274 | Serial.println("DUMPING MBC2 RAM"); 275 | maxAddress = 0xA200; 276 | } 277 | else 278 | { 279 | Serial.print("DUMPING RAM BANK "); 280 | Serial.println(bank); 281 | } 282 | 283 | SwitchRAMBank(bank); 284 | 285 | for (word address = 0xA000; address < maxAddress; address++) 286 | { 287 | byte data = GetRAMByte(address); 288 | dumpFile.write(data); 289 | } 290 | 291 | dumpFile.flush(); 292 | 293 | Serial.println("DUMPED!"); 294 | } 295 | 296 | /////////////////////////////////////////// 297 | /////////////////////////////////////////// 298 | 299 | void SwitchRAMBank(byte bank) 300 | { 301 | if (ramBanks > 1) 302 | { 303 | DataBusAsOutput(); 304 | 305 | switch (mbcType) 306 | { 307 | case MBC_1: 308 | case MBC_3: 309 | case MBC_5: 310 | { 311 | PutByte(0x4000, bank); 312 | break; 313 | } 314 | } 315 | 316 | DataBusAsInput(); 317 | } 318 | } 319 | 320 | /////////////////////////////////////////// 321 | /////////////////////////////////////////// 322 | 323 | void EnableRAM() 324 | { 325 | DataBusAsOutput(); 326 | PutByte(0x0000, 0x0A); 327 | delayMicroseconds(10); 328 | DataBusAsInput(); 329 | } 330 | 331 | /////////////////////////////////////////// 332 | /////////////////////////////////////////// 333 | 334 | void DisableRAM() 335 | { 336 | DataBusAsOutput(); 337 | PutByte(0x0000, 0x00); 338 | delay(50); 339 | DataBusAsInput(); 340 | } 341 | 342 | /////////////////////////////////////////// 343 | /////////////////////////////////////////// 344 | 345 | void GetTitle() 346 | { 347 | for (int i = 0x134; i < 0x143; i++) 348 | { 349 | romTitle[i - 0x0134] = GetByte(i); 350 | } 351 | } 352 | 353 | /////////////////////////////////////////// 354 | /////////////////////////////////////////// 355 | 356 | void GetRomBanks() 357 | { 358 | switch (romSize) 359 | { 360 | case 0x00: 361 | romBanks = 2; 362 | break; 363 | case 0x01: 364 | romBanks = 4; 365 | break; 366 | case 0x02: 367 | romBanks = 8; 368 | break; 369 | case 0x03: 370 | romBanks = 16; 371 | break; 372 | case 0x04: 373 | romBanks = 32; 374 | break; 375 | case 0x05: 376 | romBanks = 64; 377 | break; 378 | case 0x06: 379 | romBanks = 128; 380 | break; 381 | case 0x07: 382 | romBanks = 256; 383 | break; 384 | default: 385 | Serial.println("INVALID ROM SIZE!"); 386 | romBanks = 2; 387 | } 388 | } 389 | 390 | /////////////////////////////////////////// 391 | /////////////////////////////////////////// 392 | 393 | void GetRamBanks() 394 | { 395 | switch (ramSize) 396 | { 397 | case 0x00: 398 | ramBanks = 0; 399 | break; 400 | case 0x01: 401 | ramBanks = 1; 402 | break; 403 | case 0x02: 404 | ramBanks = 1; 405 | break; 406 | case 0x03: 407 | ramBanks = 4; 408 | break; 409 | case 0x04: 410 | ramBanks = 16; 411 | break; 412 | default: 413 | Serial.println("INVALID RAM SIZE!"); 414 | ramBanks = 0; 415 | } 416 | } 417 | 418 | /////////////////////////////////////////// 419 | /////////////////////////////////////////// 420 | 421 | void GetType() 422 | { 423 | cartridgeType = GetByte(0x0147); 424 | 425 | switch (cartridgeType) 426 | { 427 | case 0x00: 428 | // NO MBC 429 | case 0x08: 430 | // ROM 431 | // SRAM 432 | case 0x09: 433 | // ROM 434 | // SRAM 435 | // BATT 436 | mbcType = MBC_NONE; 437 | break; 438 | case 0x01: 439 | // MBC1 440 | case 0x02: 441 | // MBC1 442 | // SRAM 443 | case 0x03: 444 | // MBC1 445 | // SRAM 446 | // BATT 447 | case 0xFF: 448 | // Hack to accept HuC1 as a MBC1 449 | mbcType = MBC_1; 450 | break; 451 | case 0x05: 452 | // MBC2 453 | // SRAM 454 | case 0x06: 455 | // MBC2 456 | // SRAM 457 | // BATT 458 | mbcType = MBC_2; 459 | break; 460 | case 0x0F: 461 | // MBC3 462 | // TIMER 463 | // BATT 464 | case 0x10: 465 | // MBC3 466 | // TIMER 467 | // BATT 468 | // SRAM 469 | case 0x11: 470 | // MBC3 471 | case 0x12: 472 | // MBC3 473 | // SRAM 474 | case 0x13: 475 | // MBC3 476 | // BATT 477 | // SRAM 478 | case 0xFC: 479 | // Game Boy Camera 480 | mbcType = MBC_3; 481 | break; 482 | case 0x19: 483 | // MBC5 484 | case 0x1A: 485 | // MBC5 486 | // SRAM 487 | case 0x1B: 488 | // MBC5 489 | // BATT 490 | // SRAM 491 | case 0x1C: 492 | // RUMBLE 493 | case 0x1D: 494 | // RUMBLE 495 | // SRAM 496 | case 0x1E: 497 | // RUMBLE 498 | // BATT 499 | // SRAM 500 | mbcType = MBC_5; 501 | break; 502 | case 0x0B: 503 | // MMMO1 504 | case 0x0C: 505 | // MMM01 506 | // SRAM 507 | case 0x0D: 508 | // MMM01 509 | // SRAM 510 | // BATT 511 | case 0x15: 512 | // MBC4 513 | case 0x16: 514 | // MBC4 515 | // SRAM 516 | case 0x17: 517 | // MBC4 518 | // SRAM 519 | // BATT 520 | case 0x22: 521 | // MBC7 522 | // BATT 523 | // SRAM 524 | case 0x55: 525 | // GG 526 | case 0x56: 527 | // GS3 528 | case 0xFD: 529 | // TAMA 5 530 | case 0xFE: 531 | // HuC3 532 | mbcType = MBC_NOT_SUPPORTED; 533 | break; 534 | default: 535 | mbcType = MBC_NOT_SUPPORTED; 536 | } 537 | 538 | switch (cartridgeType) 539 | { 540 | case 0x03: 541 | case 0x06: 542 | case 0x09: 543 | case 0x0D: 544 | case 0x0F: 545 | case 0x10: 546 | case 0x13: 547 | case 0x17: 548 | case 0x1B: 549 | case 0x1E: 550 | case 0x22: 551 | case 0xFD: 552 | case 0xFF: 553 | cartridgeBattery = true; 554 | break; 555 | default: 556 | cartridgeBattery = false; 557 | } 558 | 559 | switch (cartridgeType) 560 | { 561 | case 0x0F: 562 | case 0x10: 563 | cartridgeRTC = true; 564 | break; 565 | default: 566 | cartridgeRTC = false; 567 | } 568 | 569 | switch (cartridgeType) 570 | { 571 | case 0x1C: 572 | case 0x1D: 573 | case 0x1E: 574 | cartridgeRumble = true; 575 | break; 576 | default: 577 | cartridgeRumble = false; 578 | } 579 | } 580 | 581 | /////////////////////////////////////////// 582 | /////////////////////////////////////////// 583 | 584 | void GatherMetadata() 585 | { 586 | GetTitle(); 587 | GetType(); 588 | byte color = GetByte(0x143); 589 | colorGameBoy = (color == 0x80) || (color == 0xC0); 590 | superGameBoy = GetByte(0x146) == 0x03; 591 | romSize = GetByte(0x148); 592 | ramSize = GetByte(0x149); 593 | GetRomBanks(); 594 | GetRamBanks(); 595 | romVersion = GetByte(0x14C); 596 | 597 | Serial.print("TITLE: "); 598 | Serial.println(romTitle); 599 | Serial.print("VERSION: "); 600 | Serial.println(romVersion); 601 | Serial.print("TYPE: 0x"); 602 | Serial.println(cartridgeType, HEX); 603 | Serial.print("MBC: "); 604 | Serial.println(mbcType); 605 | Serial.print("ROM SIZE: 0x"); 606 | Serial.println(romSize, HEX); 607 | Serial.print("ROM BANKS: "); 608 | Serial.println(romBanks); 609 | Serial.print("RAM SIZE: 0x"); 610 | Serial.println(ramSize, HEX); 611 | Serial.print("RAM BANKS: "); 612 | Serial.println(ramBanks); 613 | Serial.print("RAM BATTERY: "); 614 | Serial.println(cartridgeBattery); 615 | Serial.print("CGB: "); 616 | Serial.println(colorGameBoy); 617 | Serial.print("SGB: "); 618 | Serial.println(superGameBoy); 619 | Serial.print("RTC: "); 620 | Serial.println(cartridgeRTC); 621 | Serial.print("RUMBLE: "); 622 | Serial.println(cartridgeRumble); 623 | } 624 | 625 | /////////////////////////////////////////// 626 | /////////////////////////////////////////// 627 | 628 | boolean ValidCheckSum() 629 | { 630 | int checksum = 0; 631 | 632 | for (int j = 0x134; j < 0x14E; j++) 633 | { 634 | checksum += GetByte(j); 635 | } 636 | 637 | return ((checksum + 25) & 0xFF) == 0; 638 | } 639 | 640 | /////////////////////////////////////////// 641 | /////////////////////////////////////////// 642 | 643 | void Die() 644 | { 645 | Reset(); 646 | while (1) ; 647 | } 648 | 649 | /////////////////////////////////////////// 650 | /////////////////////////////////////////// 651 | 652 | void Reset() 653 | { 654 | DDRA = B11111111; // PORT A for Address bus LSB as output 655 | DDRK = B11111111; // PORT K for Address bus MSB as output 656 | DDRF = B00000000; // PORT F for Data bus as input 657 | DDRL = B00000111; // PORT F for RD, WR and CS as output 658 | 659 | PORTL = B00000000; // Set RD, WR and CS to HIGH 660 | PORTA = B00000000; 661 | PORTK = B00000000; 662 | } 663 | 664 | /////////////////////////////////////////// 665 | /////////////////////////////////////////// 666 | /////////////////////////////////////////// 667 | /////////////////////////////////////////// 668 | 669 | void setup() 670 | { 671 | Serial.begin(9600); 672 | pinMode(53, OUTPUT); // for SD card 673 | Reset(); 674 | } 675 | 676 | /////////////////////////////////////////// 677 | /////////////////////////////////////////// 678 | /////////////////////////////////////////// 679 | /////////////////////////////////////////// 680 | 681 | void loop() 682 | { 683 | Serial.println("== START =="); 684 | 685 | PORTL = B00000111; 686 | delayMicroseconds(10); 687 | 688 | InitSD(); 689 | 690 | GatherMetadata(); 691 | 692 | if (ValidCheckSum()) 693 | { 694 | Serial.println("CHECKSUM OK!"); 695 | } 696 | else 697 | { 698 | Serial.println("INVALID ROM!"); 699 | Die(); 700 | } 701 | 702 | DumpROM(); 703 | DumpRAM(); 704 | 705 | Serial.println("== END =="); 706 | 707 | Die(); 708 | } 709 | 710 | /////////////////////////////////////////// 711 | /////////////////////////////////////////// 712 | 713 | -------------------------------------------------------------------------------- /gameboy_write/gameboy_write.ino: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | 4 | /////////////////////////////////////////// 5 | /////////////////////////////////////////// 6 | 7 | const int SD_CS_PIN = 53; 8 | 9 | const int MBC_NOT_SUPPORTED = -1; 10 | const int MBC_NONE = 0; 11 | const int MBC_1 = 1; 12 | const int MBC_2 = 2; 13 | const int MBC_3 = 3; 14 | const int MBC_5 = 5; 15 | 16 | char romTitle[16]; 17 | byte romVersion; 18 | byte cartridgeType; 19 | int mbcType; 20 | boolean colorGameBoy; 21 | boolean superGameBoy; 22 | byte romSize; 23 | byte ramSize; 24 | byte romBanks; 25 | byte ramBanks; 26 | boolean cartridgeRTC; 27 | boolean cartridgeRumble; 28 | boolean cartridgeBattery; 29 | 30 | File saveFile; 31 | 32 | char ramFileName[] = "DUMP00.RAM"; 33 | 34 | /////////////////////////////////////////// 35 | /////////////////////////////////////////// 36 | 37 | void InitSD() 38 | { 39 | if (!SD.begin(SD_CS_PIN)) 40 | { 41 | Serial.println("SD CARD FAILED!"); 42 | Die(); 43 | } 44 | } 45 | 46 | /////////////////////////////////////////// 47 | /////////////////////////////////////////// 48 | 49 | void LoadRAMFileInSD() 50 | { 51 | saveFile = SD.open(ramFileName, FILE_READ); 52 | 53 | if (!saveFile) 54 | { 55 | Serial.println("RAM FILE FAILED!"); 56 | Die(); 57 | } 58 | } 59 | 60 | /////////////////////////////////////////// 61 | /////////////////////////////////////////// 62 | 63 | void DataBusAsInput() 64 | { 65 | DDRF = B00000000; 66 | } 67 | 68 | /////////////////////////////////////////// 69 | /////////////////////////////////////////// 70 | 71 | void DataBusAsOutput() 72 | { 73 | DDRF = B11111111; 74 | } 75 | 76 | /////////////////////////////////////////// 77 | /////////////////////////////////////////// 78 | 79 | byte GetByte(word address) 80 | { 81 | WriteAddress(address); 82 | PORTL = B00000101; 83 | delayMicroseconds(10); 84 | byte result = PINF; 85 | PORTL = B00000111; 86 | delayMicroseconds(10); 87 | return result; 88 | } 89 | 90 | /////////////////////////////////////////// 91 | /////////////////////////////////////////// 92 | 93 | byte GetRAMByte(word address) 94 | { 95 | WriteAddress(address); 96 | PORTL = B00000001; 97 | delayMicroseconds(10); 98 | byte result = PINF; 99 | PORTL = B00000111; 100 | delayMicroseconds(10); 101 | return result; 102 | } 103 | 104 | /////////////////////////////////////////// 105 | /////////////////////////////////////////// 106 | 107 | void PutByte(word address, byte data) 108 | { 109 | WriteAddress(address); 110 | PORTF = data; 111 | PORTL = B00000110; 112 | delayMicroseconds(10); 113 | PORTL = B00000111; 114 | delayMicroseconds(10); 115 | } 116 | 117 | /////////////////////////////////////////// 118 | /////////////////////////////////////////// 119 | 120 | byte PutRAMByte(word address, byte data) 121 | { 122 | WriteAddress(address); 123 | PORTF = data; 124 | PORTL = B00000010; 125 | delayMicroseconds(10); 126 | PORTL = B00000111; 127 | delayMicroseconds(10); 128 | } 129 | 130 | /////////////////////////////////////////// 131 | /////////////////////////////////////////// 132 | 133 | void WriteAddress(word address) 134 | { 135 | PORTA = address & 0xFF; 136 | PORTK = (address >> 8) & 0xFF; 137 | } 138 | 139 | /////////////////////////////////////////// 140 | /////////////////////////////////////////// 141 | 142 | void WriteRAM() 143 | { 144 | if (cartridgeBattery) 145 | { 146 | LoadRAMFileInSD(); 147 | EnableRAM(); 148 | 149 | int maxBanks = ramBanks; 150 | 151 | if (mbcType == MBC_2) 152 | { 153 | maxBanks = 1; 154 | } 155 | 156 | for (byte i = 0; i < maxBanks; i++) 157 | { 158 | WriteRAMBank(i); 159 | } 160 | 161 | DisableRAM(); 162 | 163 | saveFile.close(); 164 | 165 | Serial.print("RAM WRITTEN FROM: "); 166 | Serial.println(ramFileName); 167 | } 168 | } 169 | 170 | /////////////////////////////////////////// 171 | /////////////////////////////////////////// 172 | 173 | void WriteRAMBank(byte bank) 174 | { 175 | word maxAddress = 0xC000; 176 | 177 | if (mbcType == MBC_2) 178 | { 179 | Serial.println("WRITING MBC2 RAM"); 180 | maxAddress = 0xA200; 181 | } 182 | else 183 | { 184 | Serial.print("WRITING RAM BANK "); 185 | Serial.println(bank); 186 | } 187 | 188 | SwitchRAMBank(bank); 189 | 190 | DataBusAsOutput(); 191 | 192 | for (word address = 0xA000; address < maxAddress; address++) 193 | { 194 | byte data = saveFile.read(); 195 | PutRAMByte(address, data); 196 | /*if ((address != 0xA000) && ((address % 16) == 0)) 197 | Serial.println(" "); 198 | Serial.print(" "); 199 | Serial.print(data, HEX);*/ 200 | } 201 | 202 | DataBusAsInput(); 203 | 204 | Serial.println("WRITTEN!"); 205 | } 206 | 207 | /////////////////////////////////////////// 208 | /////////////////////////////////////////// 209 | 210 | void SwitchRAMBank(byte bank) 211 | { 212 | if (ramBanks > 1) 213 | { 214 | DataBusAsOutput(); 215 | 216 | switch (mbcType) 217 | { 218 | case MBC_1: 219 | case MBC_3: 220 | case MBC_5: 221 | { 222 | PutByte(0x4000, bank); 223 | break; 224 | } 225 | } 226 | 227 | DataBusAsInput(); 228 | } 229 | } 230 | 231 | /////////////////////////////////////////// 232 | /////////////////////////////////////////// 233 | 234 | void EnableRAM() 235 | { 236 | DataBusAsOutput(); 237 | PutByte(0x0000, 0x0A); 238 | delayMicroseconds(10); 239 | DataBusAsInput(); 240 | } 241 | 242 | /////////////////////////////////////////// 243 | /////////////////////////////////////////// 244 | 245 | void DisableRAM() 246 | { 247 | DataBusAsOutput(); 248 | PutByte(0x0000, 0x00); 249 | delay(50); 250 | DataBusAsInput(); 251 | } 252 | 253 | /////////////////////////////////////////// 254 | /////////////////////////////////////////// 255 | 256 | void GetTitle() 257 | { 258 | for (int i = 0x134; i < 0x143; i++) 259 | { 260 | romTitle[i - 0x0134] = GetByte(i); 261 | } 262 | } 263 | 264 | /////////////////////////////////////////// 265 | /////////////////////////////////////////// 266 | 267 | void GetRomBanks() 268 | { 269 | switch (romSize) 270 | { 271 | case 0x00: 272 | romBanks = 2; 273 | break; 274 | case 0x01: 275 | romBanks = 4; 276 | break; 277 | case 0x02: 278 | romBanks = 8; 279 | break; 280 | case 0x03: 281 | romBanks = 16; 282 | break; 283 | case 0x04: 284 | romBanks = 32; 285 | break; 286 | case 0x05: 287 | romBanks = 64; 288 | break; 289 | case 0x06: 290 | romBanks = 128; 291 | break; 292 | case 0x07: 293 | romBanks = 256; 294 | break; 295 | default: 296 | Serial.println("INVALID ROM SIZE!"); 297 | romBanks = 2; 298 | } 299 | } 300 | 301 | /////////////////////////////////////////// 302 | /////////////////////////////////////////// 303 | 304 | void GetRamBanks() 305 | { 306 | switch (ramSize) 307 | { 308 | case 0x00: 309 | ramBanks = 0; 310 | break; 311 | case 0x01: 312 | ramBanks = 1; 313 | break; 314 | case 0x02: 315 | ramBanks = 1; 316 | break; 317 | case 0x03: 318 | ramBanks = 4; 319 | break; 320 | case 0x04: 321 | ramBanks = 16; 322 | break; 323 | default: 324 | Serial.println("INVALID RAM SIZE!"); 325 | ramBanks = 0; 326 | } 327 | } 328 | 329 | /////////////////////////////////////////// 330 | /////////////////////////////////////////// 331 | 332 | void GetType() 333 | { 334 | cartridgeType = GetByte(0x0147); 335 | 336 | switch (cartridgeType) 337 | { 338 | case 0x00: 339 | // NO MBC 340 | case 0x08: 341 | // ROM 342 | // SRAM 343 | case 0x09: 344 | // ROM 345 | // SRAM 346 | // BATT 347 | mbcType = MBC_NONE; 348 | break; 349 | case 0x01: 350 | // MBC1 351 | case 0x02: 352 | // MBC1 353 | // SRAM 354 | case 0x03: 355 | // MBC1 356 | // SRAM 357 | // BATT 358 | case 0xFF: 359 | // Hack to accept HuC1 as a MBC1 360 | mbcType = MBC_1; 361 | break; 362 | case 0x05: 363 | // MBC2 364 | // SRAM 365 | case 0x06: 366 | // MBC2 367 | // SRAM 368 | // BATT 369 | mbcType = MBC_2; 370 | break; 371 | case 0x0F: 372 | // MBC3 373 | // TIMER 374 | // BATT 375 | case 0x10: 376 | // MBC3 377 | // TIMER 378 | // BATT 379 | // SRAM 380 | case 0x11: 381 | // MBC3 382 | case 0x12: 383 | // MBC3 384 | // SRAM 385 | case 0x13: 386 | // MBC3 387 | // BATT 388 | // SRAM 389 | case 0xFC: 390 | // Game Boy Camera 391 | mbcType = MBC_3; 392 | break; 393 | case 0x19: 394 | // MBC5 395 | case 0x1A: 396 | // MBC5 397 | // SRAM 398 | case 0x1B: 399 | // MBC5 400 | // BATT 401 | // SRAM 402 | case 0x1C: 403 | // RUMBLE 404 | case 0x1D: 405 | // RUMBLE 406 | // SRAM 407 | case 0x1E: 408 | // RUMBLE 409 | // BATT 410 | // SRAM 411 | mbcType = MBC_5; 412 | break; 413 | case 0x0B: 414 | // MMMO1 415 | case 0x0C: 416 | // MMM01 417 | // SRAM 418 | case 0x0D: 419 | // MMM01 420 | // SRAM 421 | // BATT 422 | case 0x15: 423 | // MBC4 424 | case 0x16: 425 | // MBC4 426 | // SRAM 427 | case 0x17: 428 | // MBC4 429 | // SRAM 430 | // BATT 431 | case 0x22: 432 | // MBC7 433 | // BATT 434 | // SRAM 435 | case 0x55: 436 | // GG 437 | case 0x56: 438 | // GS3 439 | case 0xFD: 440 | // TAMA 5 441 | case 0xFE: 442 | // HuC3 443 | mbcType = MBC_NOT_SUPPORTED; 444 | break; 445 | default: 446 | mbcType = MBC_NOT_SUPPORTED; 447 | } 448 | 449 | switch (cartridgeType) 450 | { 451 | case 0x03: 452 | case 0x06: 453 | case 0x09: 454 | case 0x0D: 455 | case 0x0F: 456 | case 0x10: 457 | case 0x13: 458 | case 0x17: 459 | case 0x1B: 460 | case 0x1E: 461 | case 0x22: 462 | case 0xFD: 463 | case 0xFF: 464 | cartridgeBattery = true; 465 | break; 466 | default: 467 | cartridgeBattery = false; 468 | } 469 | 470 | switch (cartridgeType) 471 | { 472 | case 0x0F: 473 | case 0x10: 474 | cartridgeRTC = true; 475 | break; 476 | default: 477 | cartridgeRTC = false; 478 | } 479 | 480 | switch (cartridgeType) 481 | { 482 | case 0x1C: 483 | case 0x1D: 484 | case 0x1E: 485 | cartridgeRumble = true; 486 | break; 487 | default: 488 | cartridgeRumble = false; 489 | } 490 | } 491 | 492 | /////////////////////////////////////////// 493 | /////////////////////////////////////////// 494 | 495 | void GatherMetadata() 496 | { 497 | GetTitle(); 498 | GetType(); 499 | byte color = GetByte(0x143); 500 | colorGameBoy = (color == 0x80) || (color == 0xC0); 501 | superGameBoy = GetByte(0x146) == 0x03; 502 | romSize = GetByte(0x148); 503 | ramSize = GetByte(0x149); 504 | GetRomBanks(); 505 | GetRamBanks(); 506 | romVersion = GetByte(0x14C); 507 | 508 | Serial.print("TITLE: "); 509 | Serial.println(romTitle); 510 | Serial.print("VERSION: "); 511 | Serial.println(romVersion); 512 | Serial.print("TYPE: 0x"); 513 | Serial.println(cartridgeType, HEX); 514 | Serial.print("MBC: "); 515 | Serial.println(mbcType); 516 | Serial.print("ROM SIZE: 0x"); 517 | Serial.println(romSize, HEX); 518 | Serial.print("ROM BANKS: "); 519 | Serial.println(romBanks); 520 | Serial.print("RAM SIZE: 0x"); 521 | Serial.println(ramSize, HEX); 522 | Serial.print("RAM BANKS: "); 523 | Serial.println(ramBanks); 524 | Serial.print("RAM BATTERY: "); 525 | Serial.println(cartridgeBattery); 526 | Serial.print("CGB: "); 527 | Serial.println(colorGameBoy); 528 | Serial.print("SGB: "); 529 | Serial.println(superGameBoy); 530 | Serial.print("RTC: "); 531 | Serial.println(cartridgeRTC); 532 | Serial.print("RUMBLE: "); 533 | Serial.println(cartridgeRumble); 534 | } 535 | 536 | /////////////////////////////////////////// 537 | /////////////////////////////////////////// 538 | 539 | boolean ValidCheckSum() 540 | { 541 | int checksum = 0; 542 | 543 | for (int j = 0x134; j < 0x14E; j++) 544 | { 545 | checksum += GetByte(j); 546 | } 547 | 548 | return ((checksum + 25) & 0xFF) == 0; 549 | } 550 | 551 | /////////////////////////////////////////// 552 | /////////////////////////////////////////// 553 | 554 | void Die() 555 | { 556 | Reset(); 557 | while (1) ; 558 | } 559 | 560 | /////////////////////////////////////////// 561 | /////////////////////////////////////////// 562 | 563 | void Reset() 564 | { 565 | DDRA = B11111111; // PORT A for Address bus LSB as output 566 | DDRK = B11111111; // PORT K for Address bus MSB as output 567 | DDRF = B00000000; // PORT F for Data bus as input 568 | DDRL = B00000111; // PORT F for RD, WR and CS as output 569 | 570 | PORTL = B00000000; // Set RD, WR and CS to HIGH 571 | PORTA = B00000000; 572 | PORTK = B00000000; 573 | } 574 | 575 | /////////////////////////////////////////// 576 | /////////////////////////////////////////// 577 | /////////////////////////////////////////// 578 | /////////////////////////////////////////// 579 | 580 | void setup() 581 | { 582 | Serial.begin(9600); 583 | pinMode(53, OUTPUT); // for SD card 584 | Reset(); 585 | } 586 | 587 | /////////////////////////////////////////// 588 | /////////////////////////////////////////// 589 | /////////////////////////////////////////// 590 | /////////////////////////////////////////// 591 | 592 | void loop() 593 | { 594 | Serial.println("== START =="); 595 | 596 | PORTL = B00000111; 597 | delayMicroseconds(10); 598 | 599 | InitSD(); 600 | 601 | GatherMetadata(); 602 | 603 | if (ValidCheckSum()) 604 | { 605 | Serial.println("CHECKSUM OK!"); 606 | } 607 | else 608 | { 609 | Serial.println("INVALID ROM!"); 610 | Die(); 611 | } 612 | 613 | WriteRAM(); 614 | 615 | Serial.println("== END =="); 616 | 617 | Die(); 618 | } 619 | 620 | /////////////////////////////////////////// 621 | /////////////////////////////////////////// 622 | 623 | --------------------------------------------------------------------------------