Saturday, January 23, 2016

Creating Gameboy Color Cartridge Dumps Using Raspberry Pi

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