Intro
After wondering how the data on game
cartridges was accessed for emulation, I decided to build a device to do this
myself. I found this write up (https://blog.thijsalkema.de/blog/2013/05/14/game-boy-cartridge-dumping-on-a-raspberry-pi-part-1/) by xnyhps about using a Raspberry Pi to do
this and, since I had an unused one available, I decided make one myself.
Parts
Raspberry Pi
I used a Model B Pi, but any model will work. Model B will
require an IO expander though, as it doesn’t have enough GPIO pins. I used an
SD card to boot Raspbian to program the Pi.
MCP23017 I/O Expander
Using two of
these chips and a breadboard, I made the 26 GPIO pins that I needed available.
Cartridge
Header
In order to connect to the cartridge without rending the
cartridge unusable in the future, I used a cartridge header so I could solder
to the pins on it rather than the cartridge. Since the part I used was actually
a Nintendo DS Lite replacement, I had to break off some plastic on the sides.
NDS cartridge headers were originally meant to only fit for Gameboy Advance
games, but the pins are the same and Gameboy Color cartridges will fit after
breaking the plastic that prevents them from just sliding on.
Connecting
I first soldered a wire to each pin on the cartridge header.
The cartridge pinout can be found on page 8 of this document:
Afterwards, I connected the data wires and the read and write pins to the MCP
chips. A schematic is shown below.
I set up the MCP23017 chips
I2C addresses as 0x20 and 0x21. By using i2cdetect
–y 0 in the terminal, I tested that both I2C addresses were detected by the
Pi. The type of memory bank controller and number of ROM banks can be
determined by checking addresses 0x0147 and 0x0148, respectively. The code will
depend on this information. The cartridge I was testing (Mickey Mouse Racing)
is of type MBC5+RAM+BATTERY with 256 banks. This can be determined by comparing
the data in the bits above with page 57 of this pdf:
Finished product |
Code
1. #! /usr/bin/python
2.
3. # Read a Game Boy/Game Boy Color cartridge connected to two MCP23017 IO expanders.
4. # By Logan Blankenbeckler. Based on code
by Nathan Chantrell and Thijs Alkemade.
5. # GNU GPL V3
6.
7. import smbus
8. import sys
9. import getopt
10. import time
11.
12. f = None
13.
14. # For revision 2 Raspberry Pi, change to bus = smbus.SMBus(0) for revision 1.
15. bus = smbus.SMBus(0)
16.
17. address0 = 0x20
18. address1 = 0x21
19. bus.write_byte_data(address0,0x00,0x00) # Set all of bank A of 0x20 to outputs
20. bus.write_byte_data(address0,0x01,0x00) # Set all of bank B of 0x20 to outputs
21. bus.write_byte_data(address1,0x00,0xff) # Set all of bank A of 0x21 to inputs
22. bus.write_byte_data(address1,0x01,0x00) # Set all of bank B of 0x21 to outputs
23.
24. def checkbanks():
25. bus.write_byte_data(address0, 0x13, 0x47)
26. bus.write_byte_data(address0, 0x12, 0x01)
27. bus.write_byte_data(address1, 0x13, 0x00)
28. print(bus.read_byte_data(address1, 0x12))
29.
30. def write(address):
31. bus.write_byte_data(address0,0x13,address & 0xff)
32. bus.write_byte_data(address0,0x12,(address >> 8) & 0xff)
33.
34. # Set ~RD and WR.
35. bus.write_byte_data(address1,0x13,0x02)
36.
37. def read(start, end):
38. for read_address in range(start, end):
39. write(read_address)
40. time.sleep(0.0001)
41.
42. val = bus.read_byte_data(address1,0x12)
43. # print(val)
44.
45. if (read_address & 0xff == 0x00):
46. sys.stdout.write(".")
47. sys.stdout.flush()
48. f.write('%c' % val)
49. f.close()
50. def select(bank):
51. global f
52. tdelay = .001
53. f = open("file%d.gbc" % bank, 'w')
54. sys.stdout.write("\n%d: " % bank)
55.
56. write(0x3100) # set address to 9th bit of bank
57. time.sleep(tdelay)
58. bus.write_byte_data(address1,0x00,0x00) # set data bus as output
59. time.sleep(tdelay)
60. bus.write_byte_data(address1,0x12, 0) #(bank >> 8) & 0x01) # write bit 9 of bank
61. time.sleep(tdelay)
62. bus.write_byte_data(address1,0x13,0x01) # toggle write enable
63. time.sleep(.1)
64.
65. bus.write_byte_data(address1,0x13,0x02) # set back to read enable
66. time.sleep(tdelay)
67. #bus.write_byte_data(address1,0x12,0x00) # set data bus to zero
68.
69. write(0x2100) # set address to lower 8 bits of bank
70. time.sleep(tdelay)
71. #bus.write_byte_data(address1,0x00,0x00) # set data bus as output
72. #time.sleep(tdelay)
73. bus.write_byte_data(address1,0x12,(bank & 0xff)) # write lower 8 bits of bank
74. time.sleep(tdelay)
75. bus.write_byte_data(address1,0x13,0x01) # toggle write enabl
76. print("Writing")
77. time.sleep(.01)
78. bus.write_byte_data(address1,0x13,0x02) # set back to read enable
79. time.sleep(tdelay)
80.
81.
82. time.sleep(tdelay)
83. bus.write_byte_data(address1,0x00,0xff) # set data bus as input
84.
85. time.sleep(tdelay)
86.
87. select(0)
88. checkbanks()
89. read(0x0000, 0x4000)
90.
91. for bank in range(1,256):
92. select(bank)
93. checkbanks()
94. read(0x4000, 0x8000)
This code successfully dumped
Mickey Mouse Racing into files that could be properly read by an emulator. The
files will need to be concatenated after running the code, however, as each
memory bank is stored separately. The number of banks can be changed in the
last loop of the code. However, the select function may need to be altered for
memory bank controller types that are not MBC5, because MBC5 requires a ninth
bit in the ROM bank select to be written while some others require just eight. The
code takes a while to run (a little over an hour for me) but that will vary
with the number of ROM banks that need to be read.
I hope to continue working on
this project, perhaps making the code detect the memory bank controller type
and number of banks and applying the methods necessary. Since Gameboy Advance
cartridges fit the header and have essentially the same pin-out, using this
device to dump them may be a possibility as well.
Hopefully more to come!
Written by Logan Blankenbeckler
No comments:
Post a Comment