├── .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 | 
34 | 
35 | 
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 |
--------------------------------------------------------------------------------