├── .gitignore ├── LICENSE ├── README.md ├── arduino └── pksploit_link_bridge │ ├── pksploit_link_bridge.ino │ └── pokemon.h ├── build ├── build.py ├── libgcc_s_sjlj-1.dll ├── libpng16-16.dll ├── libwinpthread-1.dll ├── rgbasm.exe ├── rgblink.exe ├── sample_config.ini └── zlib1.dll ├── gb_asm └── main.asm ├── pksploitlogo.png └── python ├── constants.py └── pksploit.py /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | build/config.txt 3 | build/dist/* 4 | build/config.ini 5 | .vscode/tasks.json 6 | *.pyc 7 | *.gb 8 | *.sav 9 | *.bin 10 | *.bak 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 BinaryCounter 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 |

2 | 3 |

4 | 5 | --- 6 | WARNING: UNDER HEAVY DEVELOPMENT! 7 | 8 | BOOTLEG FLASH FUNCTION SOMEWHAT UNTESTED BECAUSE I WRECKED MINE WHILE TRYING TO SOLDER A BATTERY TO IT. WORKED BEFORE I KILLED IT THO :P 9 | 10 | --- 11 | 12 | 13 | ## TL;DR 14 | 15 | This is a suite of tools allowing you to dump rom/save data and reflash save data on any GB and GBC cartridge using nothing but a Pokemon Gen 1 cart, a link cable and an Arduino compatible microcontroller (e.g. Arduino Nano or any ATmega368p board). 16 | 17 | Exploit and some arduino code based on: https://github.com/vaguilar/pokemon-red-cable-club-hack 18 | 19 | 20 | ## Features 21 | * **All Gen 1 carts supported** *(I think)*: Tested on US Pokemon Red, German Pokemon Blue, French Pokemon Yellow. 22 | * **ROM dumping:** Play those classic games on an emulator without relying on virus-riddled (and illegal) ROM download sites. 23 | * **SRAM dumping:** Save your childhood save games from imminent battery doom. 24 | * **SRAM writing:** Ever wanted to try out someone else's save game? 25 | * **Write and execute code:** Try out your small gameboy programs on actual hardware! 26 | * **Quick start without trading:** The included build script generates save files that directly jump into PkSploit's main routine after loading. Have your link up and running in only a few seconds! Those can (of course) be uploaded via PkSploit itself. 27 | 28 | *... And here's where it gets interesting ...* 29 | * **Erase/Rewrite ROM on flash based bootlegs/flashcarts!** Replace the game completely with whatever ROM you want. (Special patches to support saving on carts without battery soon(TM)) 30 | # 31 | **Yes that means you can replace that crappy bootleg romhack with Pokemon Prism, LSDj, Nanoloop or whatever you want!** 32 | 33 | *Did i mention those bootlegs cost like 4 USD and basically replace a flashcart!?* 34 | *Did i also mention that the required hardware to do all of this costs less than 2 USD and is easy to build!?* 35 | 36 | ## How? 37 | * The Arduino communicates with the Gameboy using the Link Port. It pretends to be another Gameboy running a Pokemon Gen 1 game, willing to trade. 38 | * When entering the trade, it sends corrupted party data which causes the gameboy to execute a chunk of the party data as code. 39 | * The code can be made to do pretty much anything that fits in ~192 byte, but in this instance it's a routine that reopens the serial link to provide a interface that can be used to read/write to any part of memory within the Gameboy's limitations. 40 | * Since the code is running in WRAM, you can cartswap to read/write to any gameboy cartridge. This works best on a Gameboy Color or Pocket, but should work on all gameboy platforms (better results when using a cheat device as passthrough. See FAQ) 41 | * Overwriting the bootleg works because it uses flash memory instead of ROM. These flash chips have a special command interface allowing to erase sectors and reprogram them. I discovered that this interface is accessable within the gameboy when i was [debugging/reverse engineering](https://gist.github.com/binarycounter/9bd93ef4271a11aee3e395d04b93ed3a) how my Pokemon bootleg could save without a battery. (Hint: The rom is hacked to save to flash instead of SRAM ;)) 42 | 43 | ## Hardware Prerequisites 44 | * Arduino compatible microcontroller, preferably one with 5V logic and >16Mhz clock speed. (E.g. an Atmega326p) 45 | * Gameboy DMG, Color, Pocket, Light, GBA, GBA SP... *Basically any Gameboy with a link port.* 46 | * Link Cable or any other way to connect the Arduino to the Gameboy's Link Interface 47 | 48 | ## Software Prerequisites 49 | * Python 2.7 50 | * PySerial 51 | * Arduino IDE 52 | * RGBDS (Included) 53 | 54 | ## Hardware Build 55 | *TODO. Basically cut a link cable in half and wire up 4 pins from it to the arduino.* 56 | ## Software Build 57 | **Currently only builds on Windows, but there's no reason it shouldn't work on other OS (I'm just lazy)** 58 | 1. Install Prerequisites 59 | 2. Clone: `git clone http://github.com/binarycounter/pksploit` (Or just download the repository as a zip) 60 | 3. Enter build directory: `cd PkSploit/build/` 61 | 4. Copy `sample_config.ini` to `config.ini` and edit your path, board name and port. 62 | 5. Build: `py build.py` 63 | 6. Enter python directory: `cd ../python/` 64 | 7. Run: `py pksploit.py` 65 | 66 | ## FAQ 67 | *Q: My bootleg doesn't save when i write to SRAM!* 68 | 69 | **A: Your bootleg likely doesn't include a battery and instead relies on patching the ROM to backup SRAM into flash. In some Pokemon bootlegs you can call `$3FA6` to trigger the routine that does this. For other bootleg games... I don't know. If you send me a tracelog or a romdump/patch i'll let you know if i can support it!** 70 | 71 | *Q: My gameboy keeps crashing or restarting when I attempt cartswapping!* 72 | 73 | **A: How many times did you try it? It can take me up to 10 times (on a bad day) before i successfully cartswap. Use the hacked save files to make attempts faster!** 74 | 75 | *Q: My gameboy still keeps crashing!* 76 | 77 | **A: Try a cheat device (e.g. Action Replay) as passthrough adapter. Those don't connect the RESET line between gameboy and cartridge. This prevents the gameboy from attempting to restart. You can also try putting tape over the 3rd Pin from the right, if you have more patience than me.** 78 | 79 | *Q: I looked into your code and.... what the....* 80 | 81 | **A: Yes, i know. Bare with me, this is my first serious python project. Feel free to refactor this mess...;)** 82 | 83 | 84 | ## Planned Features 85 | 86 | * Find and Port Code to other Link Cable related ACE exploits 87 | * Make GBA version (exploiting multiboot) to allow dump/write/flash of GBA bootlegs. 88 | 89 | --- 90 | 91 | **DISCLAIMER: I AM NOT RESPONSIBLE FOR ANYTHING, INCLUDING LOSS OF DATA, BROKEN GAMEBOYS, OR TEARS BECAUSE YOU ACCIDENTALLY OVERWRITE YOUR CHILDHOOD SAVE FILE WITH YOUR SICK 3 STARTER TEAM** 92 | 93 | **I AM NOT AFFILIATED OR ENDORSED BY NINTENDO. THIS REPOSITORY DOES NOT CONTAIN NINTENDO OR GAMEFREAK CODE OR DATA** 94 | -------------------------------------------------------------------------------- /arduino/pksploit_link_bridge/pksploit_link_bridge.ino: -------------------------------------------------------------------------------- 1 | // Original file by 2 | // Esteban Fuentealba 3 | // 2014/05/12 4 | 5 | //Heavily modified by BinaryCounter 6 | //to support the PkSploit Interface 7 | 8 | //Hope this satisfies the Apache License :/ 9 | 10 | 11 | // Link Cable Arduino Desc 12 | // 6 GND GND 13 | // 5 2 SC 14 | // 2 3 SI 15 | // 3 6 SO 16 | #include 17 | 18 | #include "pokemon.h" 19 | #include "data.h" 20 | 21 | //Faster digital read write code from 22 | //http://masteringarduino.blogspot.de/2013/10/fastest-and-smallest-digitalread-and.html 23 | #define portOfPin(P)\ 24 | (((P)>=0&&(P)<8)?&PORTD:(((P)>7&&(P)<14)?&PORTB:&PORTC)) 25 | #define ddrOfPin(P)\ 26 | (((P)>=0&&(P)<8)?&DDRD:(((P)>7&&(P)<14)?&DDRB:&DDRC)) 27 | #define pinOfPin(P)\ 28 | (((P)>=0&&(P)<8)?&PIND:(((P)>7&&(P)<14)?&PINB:&PINC)) 29 | #define pinIndex(P)((uint8_t)(P>13?P-14:P&7)) 30 | #define pinMask(P)((uint8_t)(1<0) 38 | #define isLow(P)((*(pinOfPin(P))& pinMask(P))==0) 39 | #define digitalState(P)((uint8_t)isHigh(P)) 40 | 41 | int volatile CLOCK_PIN = 2; 42 | int volatile SO_PIN = 6; 43 | int volatile SI_PIN = 3; 44 | int volatile data = 0; 45 | int volatile val = 0; 46 | byte volatile lsend = 0x00; 47 | int ledStatus = 13; 48 | 49 | unsigned volatile long lastReceive = 0; 50 | volatile byte outputBuffer = 0x00; 51 | volatile int counterRead = 0; 52 | volatile boolean sending = false; 53 | volatile trade_centre_state_t nextstate = PKSPLOIT_MENU; 54 | volatile int counter = 0; 55 | volatile byte command = 0x00; 56 | volatile int counter2 = 00; 57 | volatile int cmddata = -1; 58 | volatile connection_state_t connection_state = NOT_CONNECTED; 59 | volatile trade_centre_state_t trade_centre_state = INIT; 60 | char senddata[1024]; 61 | volatile int counter3=0; 62 | volatile boolean fillbuffer = false; 63 | 64 | 65 | void setup() { 66 | pinMode(SI_PIN, INPUT); 67 | digitalWrite( SI_PIN, HIGH); 68 | pinMode(SO_PIN, OUTPUT); 69 | pinMode(ledStatus, OUTPUT); 70 | digitalWrite(ledStatus, LOW); 71 | digitalWrite(SO_PIN, LOW); 72 | pinMode(CLOCK_PIN, INPUT); 73 | digitalWrite(CLOCK_PIN, HIGH); 74 | attachInterrupt( 0, clockInterrupt, RISING ); 75 | Serial.begin(28800); 76 | Serial.println("Boot complete"); 77 | } 78 | 79 | 80 | void clockInterrupt(void) { 81 | //timer=millis(); 82 | byte in; 83 | unsigned long t = 0; 84 | if(lastReceive > 0) { 85 | if( micros() - lastReceive > 120 ) { 86 | counterRead = 0; 87 | val = 0; 88 | in = 0x00; 89 | } 90 | } 91 | 92 | if(digitalState(SI_PIN) == HIGH){ 93 | val |= ( 1 << (7-counterRead) ); 94 | in |= ( 1 << (7-counterRead) ); 95 | } 96 | if(counterRead == 7) { 97 | //Serial.write((byte)val); 98 | outputBuffer = handleIncomingByte((byte)val); 99 | 100 | 101 | val = 0; 102 | in = 0x00; 103 | counterRead = -1; 104 | } 105 | 106 | counterRead++; 107 | lastReceive = micros(); 108 | while( ((digitalState(CLOCK_PIN) | CLOCK_PIN) & CLOCK_PIN) == 0); 109 | if(outputBuffer & 0x80 ? SO_PIN : 0!=0) 110 | { 111 | digitalHigh(SO_PIN); 112 | digitalHigh(ledStatus); 113 | }else{digitalLow(SO_PIN); 114 | digitalLow(ledStatus);} 115 | 116 | outputBuffer = outputBuffer << 1; 117 | 118 | } 119 | byte handleIncomingByte(byte in) { 120 | 121 | byte send = 0x00; 122 | 123 | switch(connection_state) { 124 | case NOT_CONNECTED: 125 | Serial.write("."); 126 | if (in==0xCD) 127 | {connection_state = TRADE_CENTRE; 128 | trade_centre_state = PKSPLOIT_MENU; 129 | Serial.print("StatusMQ");} 130 | 131 | if(in == PKMN_MASTER) 132 | send = PKMN_SLAVE; 133 | else if(in == PKMN_BLANK) 134 | send = PKMN_BLANK; 135 | else if(in == PKMN_CONNECTED) { 136 | send = PKMN_CONNECTED; 137 | connection_state = CONNECTED; 138 | Serial.print("StatusConnected"); 139 | } 140 | break; 141 | 142 | case CONNECTED: 143 | if(in == PKMN_CONNECTED) 144 | send = PKMN_CONNECTED; 145 | else if(in == PKMN_TRADE_CENTRE){ 146 | connection_state = TRADE_CENTRE; 147 | Serial.print("StatusTradeCenter");} 148 | else if(in == PKMN_COLOSSEUM) 149 | connection_state = COLOSSEUM; 150 | else if(in == PKMN_BREAK_LINK || in == PKMN_MASTER) { 151 | connection_state = NOT_CONNECTED; 152 | send = PKMN_BREAK_LINK; 153 | Serial.print("StatusDisconnected"); 154 | 155 | } else { 156 | send = in; 157 | } 158 | break; 159 | 160 | case TRADE_CENTRE: 161 | if(trade_centre_state == INIT && in == 0x00) { 162 | if(counter++ == 5) { 163 | trade_centre_state = READY_TO_GO; 164 | Serial.print("StatusSending"); 165 | } 166 | send = in; 167 | } else if(trade_centre_state == READY_TO_GO && (in & 0xF0) == 0xF0) { 168 | trade_centre_state = SEEN_FIRST_WAIT; 169 | send = in; 170 | } else if(trade_centre_state == SEEN_FIRST_WAIT && (in & 0xF0) != 0xF0) { 171 | send = in; 172 | counter = 0; 173 | trade_centre_state = SENDING_RANDOM_DATA; 174 | } else if(trade_centre_state == SENDING_RANDOM_DATA && (in & 0xF0) == 0xF0) { 175 | if(counter++ == 5) { 176 | trade_centre_state = WAITING_TO_SEND_DATA; 177 | } 178 | send = in; 179 | } else if(trade_centre_state == WAITING_TO_SEND_DATA && (in & 0xF0) != 0xF0) { 180 | counter = 0; 181 | send = pgm_read_byte_near(DATA_BLOCK+counter++); 182 | trade_centre_state = SENDING_DATA; 183 | } else if(trade_centre_state == SENDING_DATA) { 184 | send = pgm_read_byte_near(DATA_BLOCK+counter++); 185 | if(counter == 619) { 186 | trade_centre_state = PKSPLOIT_MENU; 187 | Serial.print("StatusMenu"); 188 | } 189 | } 190 | else if(trade_centre_state == PKSPLOIT_MENU) { 191 | 192 | if(Serial.available()<3 && !sending){trade_centre_state=nextstate;return send;} 193 | 194 | send=Serial.read(); 195 | if(sending==false){ //Not already sending, must be first byte (aka command) 196 | switch (send) 197 | { 198 | case 34: //Internal CMD 0x22, set nextstate to PKSPLOIT_DUMP_BLOCK 199 | nextstate=PKSPLOIT_DUMP_BLOCK; 200 | send=00; 201 | 202 | break; 203 | case 68: //Internal CMD 0x44, set nextstate to PKSPLOIT_SEND_BLOCK 204 | nextstate=PKSPLOIT_SEND_BLOCK; 205 | //Serial.print("e"); 206 | send=00; 207 | counter3=0; 208 | cmddata=5; 209 | break; 210 | case 119: 211 | //Failsafe to ensure we're still in sync, we don't want to write off by one data or garbage data to the gameboy 212 | if(Serial.read()!=0xDE){Serial.print("no");return 0x00;} 213 | if(Serial.read()!=0xAD){Serial.print("no");return 0x00;} 214 | if(Serial.read()!=0xBE){Serial.print("no");return 0x00;} 215 | if(Serial.read()!=0xEF){Serial.print("no");return 0x00;} 216 | cli(); 217 | fillbuffer=true; 218 | return 0x00; 219 | break; 220 | case 17: //Internal CMD 0x11 221 | counter2=Serial.read()*256+Serial.read(); 222 | send=00; 223 | 224 | break; 225 | } 226 | 227 | sending=true;} 228 | if(cmddata>0){cmddata--;} 229 | if(cmddata==0){sending=false;} 230 | if(!Serial.available()){sending=false;} 231 | 232 | } 233 | else if(trade_centre_state == PKSPLOIT_DUMP_BLOCK) { 234 | Serial.write(in); 235 | send=Serial.read(); 236 | if(counter2==0) 237 | { 238 | trade_centre_state = PKSPLOIT_MENU; 239 | nextstate = PKSPLOIT_MENU; 240 | Serial.print("StatusMenu"); 241 | } 242 | counter2--; 243 | 244 | 245 | 246 | }else if(trade_centre_state == PKSPLOIT_SEND_BLOCK) { 247 | send=senddata[counter3]; 248 | 249 | if(counter2==counter3) 250 | { 251 | //Serial.print(counter2); 252 | //Serial.print("-"); 253 | //Serial.print(counter3); 254 | counter2=0; 255 | counter3=0; 256 | trade_centre_state = PKSPLOIT_MENU; 257 | nextstate = PKSPLOIT_MENU; 258 | cmddata=-1; 259 | Serial.print("StatusMenu"); 260 | } 261 | counter3++; 262 | return send; 263 | 264 | 265 | } else { 266 | send = in; 267 | } 268 | break; 269 | 270 | case COLOSSEUM: 271 | send = in; 272 | break; 273 | 274 | default: 275 | send = in; 276 | break; 277 | } 278 | return send; 279 | } 280 | 281 | void loop() { 282 | if(fillbuffer==true) 283 | { 284 | Serial.print("buffer"); 285 | Serial.print(counter2); 286 | Serial.flush(); 287 | connection_state = NOT_CONNECTED; 288 | 289 | trade_centre_state = INIT; 290 | 291 | //Disconnect because of possible desync. reconnect should be seemless (fingers crossed) 292 | Serial.readBytes(senddata,counter2); 293 | 294 | fillbuffer=false; 295 | 296 | sei(); 297 | 298 | 299 | 300 | 301 | } 302 | } 303 | -------------------------------------------------------------------------------- /arduino/pksploit_link_bridge/pokemon.h: -------------------------------------------------------------------------------- 1 | // Original file by 2 | // Esteban Fuentealba 3 | // 2014/05/12 4 | 5 | //Added definitions for PkSploit related States. 6 | //to support the PkSploit Interface 7 | 8 | //Hope this satisfies the Apache License :/ 9 | 10 | typedef enum { 11 | NOT_CONNECTED, 12 | CONNECTED, 13 | TRADE_CENTRE, 14 | COLOSSEUM 15 | } connection_state_t; 16 | 17 | typedef enum { 18 | INIT, 19 | READY_TO_GO, 20 | SEEN_FIRST_WAIT, 21 | SENDING_RANDOM_DATA, 22 | WAITING_TO_SEND_DATA, 23 | START_SENDING_DATA, 24 | SENDING_DATA, 25 | PKSPLOIT_MENU, 26 | PKSPLOIT_SEND_BLOCK, 27 | PKSPLOIT_DUMP_BLOCK 28 | 29 | } trade_centre_state_t; 30 | 31 | typedef unsigned char byte; 32 | 33 | #define PKMN_BLANK 0x00 34 | 35 | #define ITEM_1_HIGHLIGHTED 0xD0 36 | #define ITEM_2_HIGHLIGHTED 0xD1 37 | #define ITEM_3_HIGHLIGHTED 0xD2 38 | #define ITEM_1_SELECTED 0xD4 39 | #define ITEM_2_SELECTED 0xD5 40 | #define ITEM_3_SELECTED 0xD6 41 | 42 | #define PKMN_MASTER 0x01 43 | #define PKMN_SLAVE 0x02 44 | #define PKMN_CONNECTED 0x60 45 | #define PKMN_WAIT 0x7F 46 | 47 | #define PKMN_ACTION 0x60 48 | 49 | #define PKMN_TRADE_CENTRE ITEM_1_SELECTED 50 | #define PKMN_COLOSSEUM ITEM_2_SELECTED 51 | #define PKMN_BREAK_LINK ITEM_3_SELECTED 52 | 53 | #define TRADE_CENTRE_WAIT 0xFD 54 | 55 | -------------------------------------------------------------------------------- /build/build.py: -------------------------------------------------------------------------------- 1 | #!python2 2 | import os 3 | import sys 4 | import shutil 5 | import ConfigParser 6 | 7 | 8 | print "PkSploit Build Script - Revision 24-11-17" 9 | 10 | if not os.path.exists("config.ini"): 11 | print "No config.ini file! Rename and edit sample_config.ini in this folder!" 12 | sys.exit(1) 13 | 14 | Config = ConfigParser.ConfigParser() 15 | Config.read("config.ini") 16 | 17 | #ConfigParser Helper function from the Python wiki 18 | #https://wiki.python.org/moin/ConfigParserExamples 19 | 20 | def ConfigSectionMap(section): 21 | dict1 = {} 22 | options = Config.options(section) 23 | for option in options: 24 | try: 25 | dict1[option] = Config.get(section, option) 26 | if dict1[option] == -1: 27 | DebugPrint("skip: %s" % option) 28 | except: 29 | print("exception on %s!" % option) 30 | dict1[option] = None 31 | return dict1 32 | 33 | 34 | 35 | choice="" 36 | everything=5 37 | if len(sys.argv) > 1: 38 | if sys.argv[1] == "full": 39 | choice=everything 40 | 41 | odir=ConfigSectionMap("General")["outputdir"] 42 | while choice not in range(1,6): 43 | print "---------------------------" 44 | print "What do you want to build?" 45 | print "" 46 | print "1. Assemble Gameboy ASM" 47 | print "2. Assemble Gameboy ASM (Patch Savefile for quick start)" 48 | print "3. Prepare Arduino Project" 49 | print "4. Build and Upload Arduino Project" 50 | print "5. Everything (run \"py build.py full\" to skip this menu in the future)" 51 | print "" 52 | print "9. Exit" 53 | try: 54 | choice = int(raw_input('--> ')) 55 | if choice == 9: 56 | sys.exit(0) 57 | except SystemExit: 58 | sys.exit(0) 59 | except: 60 | print "Invalid Number" 61 | choice = 0 62 | 63 | def assemble(): 64 | print "Assembling Gameboy Code with trade offsets" 65 | if os.path.exists(odir+"/gb_asm_trade/"): 66 | shutil.rmtree(odir+"/gb_asm_trade/") 67 | shutil.copytree("../gb_asm/",odir+"/gb_asm_trade/") 68 | fa=open(odir+"/gb_asm_trade/main.asm","rb") 69 | code=fa.read() 70 | fa.close() 71 | fa=open(odir+"/gb_asm_trade/main.asm","wb") 72 | fa.write("rOFFSET EQUS \"$c486\"\n\rrEXTRA EQUS \"\""+code) 73 | fa.close() 74 | os.system("rgbasm.exe -o "+odir+"temp.o "+odir+"gb_asm_trade/main.asm") 75 | os.system("rgblink.exe -o "+odir+"temp.gb "+odir+"temp.o") 76 | fi = open(odir+"temp.gb","rb") 77 | gb_rom = fi.read() 78 | fi.close() 79 | fo = open(odir+"main.bin", "wb") 80 | fo.write(gb_rom[0x150:0x214]) 81 | fo.close() 82 | print "Done Assembling Gameboy Code" 83 | return 84 | 85 | def makesave(): 86 | print "Assembling Gameboy Code with save file offsets" 87 | if os.path.exists(odir+"/gb_asm_save/"): 88 | shutil.rmtree(odir+"/gb_asm_save/") 89 | shutil.copytree("../gb_asm/",odir+"/gb_asm_save/") 90 | fa=open(odir+"/gb_asm_save/main.asm","rb") 91 | code=fa.read() 92 | fa.close() 93 | fa=open(odir+"/gb_asm_save/main.asm","wb") 94 | fa.write("rOFFSET EQUS \"$d280\"\n\rrEXTRA EQUS \"jr .turnoff\""+code) 95 | fa.close() 96 | os.system("rgbasm.exe -o "+odir+"temp_save.o "+odir+"gb_asm_save/main.asm") 97 | os.system("rgblink.exe -o "+odir+"temp_save.gb "+odir+"temp_save.o") 98 | fi = open(odir+"temp_save.gb","rb") 99 | gb_rom = fi.read() 100 | fi.close() 101 | fo = open(odir+"main_save.bin", "wb") 102 | fo.write(gb_rom[0x150:0x214]) 103 | fo.close() 104 | print "Done Assembling Gameboy Code" 105 | 106 | fsg=open("../savefile_templates/pokemon_blue_german.sav","rb") 107 | save=fsg.read() 108 | 109 | 110 | 111 | fsg.close() 112 | fp = open(odir+"main_save.bin","rb") 113 | code=fp.read() 114 | fp.close() 115 | 116 | save=save[:9847]+code+save[10043:] 117 | 118 | #generate valid checksum 119 | checksum=0 120 | save_data = map(ord, save[9624:13602]) 121 | for num,bb in enumerate(save_data): 122 | checksum=checksum+bb 123 | flip=0xFF 124 | checksum=chr((checksum%256)^flip) 125 | 126 | save=save[:13603]+checksum+save[13604:] 127 | 128 | fsgs = open(odir+"pokemon_blue_german_pksploit.sav", "wb") 129 | fsgs.write(save) 130 | fsgs.close() 131 | return 132 | 133 | 134 | def prepare(): 135 | print "Preparing Arduino Project" 136 | 137 | if not os.path.exists(odir+"main.bin"): 138 | print "Gameboy Code not assembled yet, doing that now..." 139 | assemble() 140 | if os.path.exists(odir+ConfigSectionMap("Arduino")['projectname']+"/"): 141 | shutil.rmtree(odir+ConfigSectionMap("Arduino")['projectname']+"/") 142 | shutil.copytree("../arduino/"+ConfigSectionMap("Arduino")["projectname"]+"/", odir+ConfigSectionMap("Arduino")["projectname"]+"/") 143 | 144 | #Data Preperation Code By 145 | #Esteban Fuentealba 146 | 147 | # load program to run 148 | fp = open(odir+"main.bin","rb") 149 | program_str = fp.read() 150 | fp.close() 151 | program = map(ord, program_str) 152 | data = [] 153 | 154 | # seed 155 | data += [182, 147, 113, 81, 51, 23, 228, 205, 184, 165] 156 | 157 | # preamble 158 | data += [253, 253, 253, 253, 253, 253, 253, 253] 159 | 160 | # party (bootstrap) 161 | party = [248, 0, 54, 253, 1, 62, 88, 197, 195, 0xd6, 0xc5, 6, 21, 21, 21, 21, 21, 21, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 206, 227, 227, 255, 33, 160, 195, 1, 136, 1, 62, 0, 205, 224, 54, 17, 24, 218, 33, 89, 196, 205, 85, 25, 195, 21, 218, 139, 142, 128, 131, 136, 141, 134, 232, 232, 232, 80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 64, 0, 0] 162 | data += party 163 | 164 | # preamble 165 | data += [253, 253, 253, 253, 253] 166 | 167 | # patchlist (196 bytes total) 168 | patchlist = [255, 255] + program + ([0] * 200) 169 | patchlist = patchlist[:196] 170 | data += patchlist 171 | 172 | party_and_patchlist = ", ".join(map(str, party + [253, 253, 253, 253, 253] + patchlist)) 173 | fileo = open(odir+ConfigSectionMap("Arduino")["projectname"]+"/data.h","wb") 174 | fileo.write("unsigned const char DATA_BLOCK[] PROGMEM = {" + party_and_patchlist + "};") 175 | fileo.close() 176 | print "Done Preparing Arduino Project" 177 | return 178 | 179 | 180 | def buildarduino(): 181 | print "Building and uploading Arduino Project" 182 | if not os.path.exists(odir+ConfigSectionMap("Arduino")["projectname"]+"/"): 183 | print "Arduino Project not prepared yet, doing that now..." 184 | prepare() 185 | os.system("\""+ConfigSectionMap("Arduino")['path']+"/arduino_debug.exe\" -v --upload "+odir+ConfigSectionMap("Arduino")["projectname"]+"/"+ConfigSectionMap("Arduino")["projectname"]+".ino --board "+ConfigSectionMap("Arduino")["board"]+" --port "+ConfigSectionMap("Arduino")["port"]) 186 | print "Done Building and uploading Arduino Project" 187 | return 188 | 189 | if not os.path.exists(odir): 190 | os.makedirs(odir) 191 | 192 | 193 | if choice == 1 or choice == everything: 194 | assemble() 195 | 196 | if choice == 2 or choice == everything: 197 | makesave() 198 | 199 | if choice == 3 or choice == everything: 200 | prepare() 201 | 202 | if choice == 4 or choice == everything: 203 | buildarduino() 204 | 205 | 206 | -------------------------------------------------------------------------------- /build/libgcc_s_sjlj-1.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/binarycounter/PkSploit/8c9ba99abb7018dfde181bdd1257941da2c41cd8/build/libgcc_s_sjlj-1.dll -------------------------------------------------------------------------------- /build/libpng16-16.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/binarycounter/PkSploit/8c9ba99abb7018dfde181bdd1257941da2c41cd8/build/libpng16-16.dll -------------------------------------------------------------------------------- /build/libwinpthread-1.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/binarycounter/PkSploit/8c9ba99abb7018dfde181bdd1257941da2c41cd8/build/libwinpthread-1.dll -------------------------------------------------------------------------------- /build/rgbasm.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/binarycounter/PkSploit/8c9ba99abb7018dfde181bdd1257941da2c41cd8/build/rgbasm.exe -------------------------------------------------------------------------------- /build/rgblink.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/binarycounter/PkSploit/8c9ba99abb7018dfde181bdd1257941da2c41cd8/build/rgblink.exe -------------------------------------------------------------------------------- /build/sample_config.ini: -------------------------------------------------------------------------------- 1 | #EDIT THIS CONFIG AND SAVE IT AS config.ini 2 | 3 | [General] 4 | outputdir=dist/ 5 | [Arduino] 6 | path=C:\Program Files (x86)\Arduino 7 | projectname=pksploit_link_bridge 8 | board=arduino:avr:nano:cpu=atmega328 9 | port=COM3 10 | -------------------------------------------------------------------------------- /build/zlib1.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/binarycounter/PkSploit/8c9ba99abb7018dfde181bdd1257941da2c41cd8/build/zlib1.dll -------------------------------------------------------------------------------- /gb_asm/main.asm: -------------------------------------------------------------------------------- 1 | ;Author: BinaryCounter (23-09-17) 2 | 3 | 4 | 5 | ;Uncomment one of the following 2 lines when not assembling using the buildscript. 6 | ;rOFFSET EQUS "$c486" ;OFFSET for Trade 7 | ;rOFFSET EQUS "$d280" ;OFFSET for SaveFile 8 | 9 | ;When manually assembling for SaveFile uncomment one of these linse too, to turnoff LCD (to prevent burn-in of pokemon center still frame) 10 | ;rEXTRA EQUS ".turnoff" ;Extra for Save 11 | ;rEXTRA EQUS "" ;Extra for trade 12 | 13 | valAA EQUS "$AA" 14 | val55 EQUS "$55" 15 | 16 | SECTION "Program Start",ROM0[$150] 17 | Boot:: 18 | 19 | .setup 20 | ld a, [$ffff] ;Disable those pesky serial interrupts, ugh. 21 | and $f7 22 | ld [$ffff], a 23 | 24 | ;set default vars 25 | 26 | ld a, $00 27 | ld [$FFF1], a 28 | 29 | ;Maybe draw something to the screen here? 30 | ;After that, disable interrupts so we have full control 31 | 32 | 33 | di 34 | 35 | 36 | .premenu 37 | 38 | ld a, $FF 39 | ld [$FFF0], a 40 | 41 | .menu 42 | 43 | ;Basic Command interface. 44 | ;Usage: GB sends $CD, Client responds with command 45 | ;Hex commands: 46 | ; $AA - Set Byte 47 | ; $55 - Run Block Transfer Routine (Read/Write blocks of memory) 48 | ; $33 - Jump to Address 49 | 50 | ld a, $CD 51 | call serial + rOFFSET 52 | cp $AA 53 | jr z, .setbyte 54 | cp $55 55 | jr z, .transfer 56 | cp $33 57 | jr z, .jump 58 | cp $66 59 | jr z, .turnoff 60 | jr .menu 61 | 62 | ;Command 66, jump to address 63 | ;Usage: GB waits for Vblank and turns off LCD. 64 | .turnoff: 65 | ld a, [$FF40] 66 | bit 7, a 67 | jr z, .premenu ;Return right away if screen already off 68 | ld a,[$FF44] ; Loop until in first part of vblank 69 | cp 145 70 | jr nz,.turnoff 71 | ld hl, $FF40 72 | res 7,[hl] 73 | jr .premenu 74 | 75 | ;Command 33, jump to address 76 | ;Usage: GB sends $10, Client responds with High byte of address, 77 | ; GB sends $20, Client responds with low byte of address. 78 | ; GB jumps to address, return to menu with a ret instruction 79 | 80 | .jump 81 | call getaddress + rOFFSET 82 | call callwrapper + rOFFSET 83 | jr .premenu 84 | 85 | 86 | 87 | ;Command AA, set byte 88 | ;Usage: GB sends $10, Client responds with High byte of address, 89 | ; GB sends $20, Client responds with low byte of address. 90 | ; GB sends $30, Client responds with byte to be written, 91 | ; command writes byte and returns to menu 92 | .setbyte 93 | call getaddress + rOFFSET 94 | ld a, $30 95 | call serial + rOFFSET 96 | ld [hl], a 97 | jr .menu 98 | 99 | 100 | ;Command 55, Block transfer 101 | ;Usage: GB sends $10, Client responds with High byte of byte count, 102 | ; GB sends $20, Client responds with low byte of byte count. 103 | ; GB sends $10, Client responds with High byte of start address, 104 | ; GB sends $20, Client responds with low byte of start address. 105 | ; Command performs block transfer, returns to menu 106 | 107 | .transfer 108 | ld a, [$FFF1] 109 | ld d, a ; load command Settings (See set byte for options) 110 | call getaddress + rOFFSET 111 | ld b, h 112 | ld c, l 113 | call getaddress + rOFFSET 114 | 115 | .loop1 116 | ld a, [hl] 117 | call serial + rOFFSET 118 | bit 0, d ;Is write bit set? 119 | jr z, .skipwrite 120 | bit 1, d ;Bootleg write set? 121 | jr z, .skipbootleg 122 | call bootlegwrite + rOFFSET 123 | .skipbootleg 124 | ld [hl], a 125 | .skipwrite 126 | inc hl 127 | ;------ 128 | dec bc 129 | ld a, b 130 | or c 131 | jr nz, .loop1 132 | ;loopend 133 | 134 | 135 | jr .premenu 136 | 137 | bootlegwrite: 138 | 139 | ld e, a 140 | ld a, [$FFF2] 141 | ld [$2100], a 142 | call delay + rOFFSET 143 | ld a, valAA 144 | ld [$0AAA], a 145 | nop 146 | ld a, val55 147 | ld [$0555], a 148 | nop 149 | ld a, $A0 150 | ld [$0AAA], a 151 | nop 152 | ld a,e 153 | ld [hl], a 154 | ; .waitforwrite 155 | ; ld a, [hl] 156 | ; cp b 157 | ; jr nz, .waitforwrite 158 | call delay + rOFFSET 159 | ret 160 | 161 | 162 | 163 | ;General-Purpose functions 164 | 165 | getaddress: ; GB sends $10, Client responds with High byte of address, 166 | ; GB sends $20, Client responds with low byte of address. 167 | ; destroys a, returns address in hl 168 | ld a, $10 169 | call serial + rOFFSET 170 | ld h, a 171 | ld a, $20 172 | call serial + rOFFSET 173 | ld l, a 174 | ret 175 | 176 | serial: ;writes value in A to serial and puts response in A 177 | 178 | 179 | ld [$ff01],a ;Serial Data Register 180 | ld a, $81 ; 181 | ld [$ff02],a ;Serial Mode 182 | .waitloop1 183 | ld a, [$ff02] 184 | and $80 185 | jr nz, .waitloop1 ;Waits for data 186 | call delay + rOFFSET 187 | ld a, [$ff01] 188 | ret 189 | 190 | callwrapper: 191 | jp hl 192 | 193 | delay: ;delays by value in FFF0 194 | ld a, [$FFF0] 195 | jr z, .skipdelay 196 | .wastetime 197 | dec a 198 | nop 199 | nop 200 | nop 201 | nop 202 | nop 203 | nop 204 | nop 205 | nop 206 | nop 207 | nop 208 | jr nz, .wastetime 209 | .skipdelay 210 | ret 211 | ;loopend 212 | -------------------------------------------------------------------------------- /pksploitlogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/binarycounter/PkSploit/8c9ba99abb7018dfde181bdd1257941da2c41cd8/pksploitlogo.png -------------------------------------------------------------------------------- /python/constants.py: -------------------------------------------------------------------------------- 1 | #Com port to use 2 | port="COM8" 3 | 4 | #Nintendo logo hex dump extracted from a original Pokemon Blue cart 5 | nintendologo = "".join(map(chr,[0xCE,0xED,0x66,0x66,0xCC,0x0D,0x00,0x0B,0x03,0x73,0x00,0x83,0x00,0x0C,0x00,0x0D,0x00,0x08,0x11,0x1F,0x88,0x89,0x00,0x0E,0xDC,0xCC,0x6E,0xE6,0xDD,0xDD,0xD9,0x99,0xBB,0xBB,0x67,0x63,0x6E,0x0E,0xEC,0xCC,0xDD,0xDC,0x99,0x9F,0xBB,0xB9,0x33,0x3E])) 6 | 7 | #PkSploit logo using Pokemon Blue tiles (does not work in rom/sram mode, only for trade ACE) 8 | pksploitlogo = "".join(map(chr,[0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x78,0x79,0x79,0x79,0x79,0x79,0x79,0x79,0x79,0x79,0x79,0x79,0x79,0x7A,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7B,0x7F,0x19,0x19,0x19,0x7F,0x7F,0x19,0x7F,0x7F,0x7F,0x19,0x7F,0x77,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7B,0x7F,0x19,0x7F,0x7F,0x19,0x7F,0x19,0x7F,0x7F,0x19,0x7F,0x7F,0x77,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7B,0x7F,0x19,0x7F,0x7F,0x19,0x7F,0x19,0x7F,0x19,0x7F,0x7F,0x7F,0x77,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7B,0x7F,0x19,0x19,0x19,0x7F,0x7F,0x19,0x19,0x7F,0x7F,0x7F,0x7F,0x77,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7B,0x7F,0x19,0x7F,0x7F,0x7F,0x7F,0x19,0x7F,0x19,0x7F,0x7F,0x7F,0x77,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7B,0x7F,0x19,0x7F,0x7F,0x7F,0x7F,0x19,0x7F,0x7F,0x19,0x7F,0x7F,0x77,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7B,0x7F,0x19,0x7F,0x7F,0x7F,0x7F,0x19,0x7F,0x7F,0x7F,0x19,0x7F,0x77,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7B,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x77,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7B,0x7F,0x7F,0x7F,0x92,0xAF,0xAB,0xAE,0xA8,0xB3,0x7F,0x7F,0x7F,0x77,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7C,0x76,0x76,0x76,0x76,0x76,0x76,0x76,0x76,0x76,0x76,0x76,0x76,0x7D,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F])) 9 | 10 | 11 | #Header information from http://gbdev.gg8.se/wiki/articles/The_Cartridge_Header 12 | rombanks=range(0,0x55) 13 | rombanks[0]=2 14 | rombanks[1]=4 15 | rombanks[2]=8 16 | rombanks[3]=16 17 | rombanks[4]=32 18 | rombanks[5]=64 19 | rombanks[6]=128 20 | rombanks[7]=256 21 | rombanks[8]=512 22 | rombanks[0x52]=72 23 | rombanks[0x53]=80 24 | rombanks[0x54]=96 25 | srambanks=range(0,6) 26 | srambanks[0]=0 27 | srambanks[1]=-1 # Special condition: Only 1/4th of a bank is used 28 | srambanks[2]=1 29 | srambanks[3]=4 30 | srambanks[4]=16 31 | srambanks[5]=8 32 | 33 | modeRead=0x00 34 | modeWrite=0x01 35 | modeFlash=0x03 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /python/pksploit.py: -------------------------------------------------------------------------------- 1 | #!python2 2 | import os 3 | import sys 4 | if (sys.version_info > (3, 0)): 5 | print("This script is for Python 2.x only. Sorry about that :/") 6 | exit(1) 7 | import serial 8 | import threading 9 | import time 10 | import Tkinter, tkFileDialog 11 | import constants 12 | 13 | #Let's define some stuff 14 | 15 | ser = serial.Serial() 16 | 17 | ser.baudrate = 28800 18 | ser.port = constants.port 19 | ser.timeout = 0.05 20 | 21 | state = "none" 22 | cmdstr="" 23 | cmdpar1=0 24 | cmdpar2=0 25 | cmdpar3=0 26 | cmdpar4=0 27 | cmddone=False 28 | cmdoutput="" 29 | lcdon=True 30 | mq=False 31 | 32 | #Change to 0xA9 and 0x56 if bootleg flash functions don't work 33 | valAA=0xAA 34 | val55=0x55 35 | 36 | 37 | root = Tkinter.Tk() 38 | root.withdraw() 39 | 40 | cartmbc=0 41 | cartbanks=0 42 | cartsrambanks=0 43 | cartname="" 44 | 45 | 46 | 47 | def main(): 48 | global state, mq 49 | state="noserial" 50 | 51 | print "Waiting for Serial Port to Open... (If the program hangs here, close your Arduino Software)." 52 | 53 | 54 | ser.open() #Open Serial Connection (This blocks until port is open) 55 | 56 | print "Serial port opened." 57 | 58 | state="serialopen" #Signal state of the port to serial handler 59 | 60 | #Start serial handler thread 61 | serialprocess = threading.Thread(target = serialf) 62 | serialprocess.daemon = True 63 | serialprocess.start() 64 | 65 | print "Waiting for Arduino Reset. (Press the reset button on your arduino if this hangs)" 66 | 67 | while state!="arduinoconnected": #Block until serial thread signals that the arduino is ready 68 | time.sleep(0.1) 69 | 70 | print "Reset complete. Please initiate the trade (or load the prepared save file) on the gameboy." 71 | 72 | while state!="pksploit_menu": #Block until serial thread signals that Gameboy loaded PkSploit 73 | time.sleep(0.1) 74 | if state=="pksploit_mq": #Special case for when the gameboy is already running PkSploit without trading (e.g from a rom, or after a restart) 75 | print "Quick Menu triggered. Gameboy is already running PkSploit!" 76 | state="pksploit_menu" 77 | mq=True 78 | print "Initializing..." 79 | 80 | 81 | #Start Cli interface thread 82 | interfaceprocess = threading.Thread(target = interfacef) 83 | interfaceprocess.daemon = True 84 | interfaceprocess.start() 85 | while True: 86 | time.sleep(1) 87 | 88 | 89 | 90 | 91 | 92 | #Main cli interface 93 | def interfacef(): 94 | global cmdoutput, state, mq 95 | if mq == False: 96 | time.sleep(2) 97 | initpksploit() 98 | while True: 99 | choice="" 100 | while choice not in range(1,20): 101 | print "---------------------------" 102 | print "What do you want to do?" 103 | print "" 104 | print "1. Display And Verify Header 5. Write SRAM (Deletes Save) 9. Bootleg/FC: Erase Flash" 105 | print "2. Dump ROM 6. Write Byte 10. Bootleg/FC: Flash ROM" 106 | print "3. Dump SRAM 7. Call address 11. Bootleg/FC: Flash Byte" 107 | print "4. Dump Custom Block 8. Restart Gameboy (Call $100) 12. Toggle LCD" 108 | print " " 109 | print "0. Exit" 110 | 111 | 112 | 113 | try: 114 | choice = int(raw_input('--> ')) 115 | if choice == 0: 116 | sys.exit(0) 117 | except SystemExit: 118 | os._exit(1) 119 | except: 120 | print "Invalid Number" 121 | choice = 0 122 | 123 | options = {1: verifyheader, 124 | 2: dumprom, 125 | 3: dumpsram, 126 | 4: dumpcustom, 127 | 5: writesram, 128 | 6: writebyte, 129 | 7: jumpto, 130 | 8: restartgb, 131 | 9: eraseflash, 132 | 10:flashrom, 133 | 11:flashbyte, 134 | 12:togglelcd, 135 | 13:flashexperiment, 136 | 14:flashromexpe, 137 | 15:teststuff} 138 | 139 | options[choice]() 140 | 141 | 142 | 143 | def initpksploit(): 144 | 145 | command("setbyte",0xFF,0x26,0x00,0x00) #Mute Sound 146 | command("turnofflcd",0,0,0,0) 147 | #command("send",0x9C,0x00,constants.pksploitlogo,constants.modeWrite) 148 | command("turnonlcd",0,0,0,0) 149 | command("setbyte",0x00,0x00,0x00,0x00) #Disable SRAM access for safety reasons 150 | command("setbyte",0xFF,0xF1,constants.modeRead,0x00) #Disable writing for safety reasons 151 | command("setbyte",0xFF,0xF0,0x00,0x00) #Let's speed this bi--- up 152 | return 153 | def verifyheader(): 154 | #Header information from http://gbdev.gg8.se/wiki/articles/The_Cartridge_Header 155 | #This doesn't actually verify the checksum, only the logo. But i figured, if 48 byte in a row are dumped correctly, the cart is probably working. 156 | global cmdoutput, nintendologo, cartmbc, cartbanks, srambanks, cartname, cartsrambanks 157 | command("dump",0x00,0x50,0x01,0x00) #Dump $50 bytes at address $0100 158 | if cmdoutput[4:-28]!=constants.nintendologo: #Check if logo on the cart matches nintendo logo 159 | print "Nintendo logo doesn't match." 160 | print "Cartridge possibly drity or broken." 161 | print "Header dump ($100-$150):" 162 | print " ".join("{:02x}".format(ord(c)) for c in cmdoutput) 163 | return False #Return false if it doesn't. Stops rom dump functions from dumping invalid roms 164 | else: 165 | print "Cart has a valid Nintendo Logo in the header" 166 | print "Cart name: "+cmdoutput[52:-12] 167 | cartname=cmdoutput[52:-12] 168 | 169 | if ord(cmdoutput[67:-12]) == 0x80: 170 | print "Cart has Gameboy Color enhancements" 171 | if ord(cmdoutput[67:-12]) == 0xC0: 172 | print "Cart Is a Gameboy Color Exclusive" 173 | 174 | if ord(cmdoutput[70:-9]) == 0x03: 175 | print "Cart has Super Gameboy enhancements" 176 | if ord(cmdoutput[74:-5]) == 0x00: 177 | print "Cart is a Japanese exclusive" 178 | if ord(cmdoutput[74:-5]) == 0x01: 179 | print "Cart is a Non-Japanese exclusive" 180 | print "Cart Type: "+str(ord(cmdoutput[71:-8])) 181 | cartmbc=ord(cmdoutput[71:-8]) 182 | print "ROM Size: "+str(constants.rombanks[ord(cmdoutput[72:-7])]*16)+ "KB / " + str(constants.rombanks[ord(cmdoutput[72:-7])])+ " Banks" 183 | cartbanks=constants.rombanks[ord(cmdoutput[72:-7])] 184 | 185 | if ord(cmdoutput[73:-6]) == 0: 186 | print "RAM Size: 0KB (Cart doesn't have SRAM)" 187 | else: 188 | if ord(cmdoutput[73:-6]) == -1: 189 | print "RAM Size: 2KB / 1 Bank (Only 1/4th of the bank is mapped)" 190 | else: 191 | print "RAM Size: " + str(constants.srambanks[ord(cmdoutput[73:-6])]*8) + "KB / " + str(constants.srambanks[ord(cmdoutput[73:-6])]) + " Bank(s)" 192 | 193 | cartsrambanks=constants.srambanks[ord(cmdoutput[73:-6])] 194 | 195 | print "Header dump ($100-$150):" 196 | print " ".join("{:02x}".format(ord(c)) for c in cmdoutput) 197 | 198 | 199 | return True 200 | 201 | def dumprom(): 202 | global root, cartname, cartbanks 203 | 204 | if verifyheader()==False: 205 | print "Header is broken, aborted dump." 206 | return False 207 | print "Validated header." 208 | file_path = tkFileDialog.asksaveasfilename(defaultextension=".gb", initialfile=''.join(e for e in cartname if e.isalnum())+".gb") #Get filename to write 209 | if file_path == "None" or file_path == "": 210 | return False 211 | fo = open(file_path, "wb") 212 | rom="" 213 | banks=cartbanks #Get number of banks 214 | i=0 215 | command("setbyte",0xFF,0xF0,0x00,0x00) 216 | command("dump",0x40,0x00,0x00,0x00) 217 | rom=cmdoutput 218 | fo.write(cmdoutput) 219 | i+=1 220 | print "Dumped "+str(i) +"/"+str(banks)+ " Banks." 221 | while i < banks: 222 | command("setbyte",0xFF,0xF0,0x00,0x00) 223 | command("setbyte",0x20,0x00,i,0x00) 224 | command("dump",0x40,0x00,0x40,0x00) 225 | rom+=cmdoutput 226 | fo.write(cmdoutput) 227 | fo.flush() 228 | os.fsync(fo) 229 | i+=1 230 | print "Dumped "+str(i) +"/"+str(banks)+ " Banks." 231 | 232 | print "Rom dump complete" 233 | 234 | 235 | fo.close() 236 | return rom 237 | 238 | 239 | def dumpsram(): 240 | global root, cartname, cartsrambanks 241 | 242 | if verifyheader()==False: 243 | print "Header is broken, aborted dump." 244 | return False 245 | print "Validated header." 246 | 247 | banks=cartsrambanks 248 | if banks == 0: 249 | print "No SRAM detected. aborted dump." 250 | return False 251 | 252 | file_path = tkFileDialog.asksaveasfilename(defaultextension=".sav", initialfile=''.join(e for e in cartname if e.isalnum())+".sav") 253 | if file_path == "None" or file_path == "": 254 | return False 255 | fo = open(file_path, "wb") 256 | sram="" 257 | command("setbyte",0x00,0x00,0x0a,0x00) #Enable SRAM 258 | if banks == -1: 259 | print "2KB SRAM dumping not supported yet." 260 | else: 261 | i=0 262 | while i < banks: 263 | i+=1 264 | 265 | command("setbyte",0x40,0x00,i-1,0x00) #Switch SRAM Bank 266 | command("dump",0x20,0x00,0xa0,0x00) 267 | sram=cmdoutput 268 | fo.write(cmdoutput) 269 | fo.flush() 270 | os.fsync(fo) 271 | print "Dumped "+str(i) +"/"+str(banks)+ " Banks." 272 | 273 | command("setbyte",0x00,0x00,0x00,0x00) #Disable SRAM 274 | print "SRAM dump complete" 275 | 276 | 277 | fo.close() 278 | return sram 279 | 280 | def dumpcustom(): 281 | a1 = int(raw_input('Start Address (High Byte)> '), 16) 282 | a2 = int(raw_input('Start Address (Low Byte)> '), 16) 283 | c1 = int(raw_input('Amount of bytes (High Byte)> '), 16) 284 | c2 = int(raw_input('Amount of bytes (Low Byte)> '), 16) 285 | command("dump",c1,c2,a1,a2) 286 | print " ".join("{:02x}".format(ord(c)) for c in cmdoutput) 287 | 288 | 289 | def writesram(): 290 | global root, cartname, cartsrambanks 291 | 292 | if verifyheader()==False: 293 | print "Header is broken, aborted write." 294 | return False 295 | print "Validated header." 296 | 297 | banks=cartsrambanks 298 | if banks == 0: 299 | print "No SRAM detected. aborting write." 300 | return False 301 | 302 | file_path = tkFileDialog.askopenfilename(defaultextension=".sav", initialfile=''.join(e for e in cartname if e.isalnum())+".sav") 303 | if file_path == "None" or file_path == "": 304 | return False 305 | fo = open(file_path, "rb") 306 | save = fo.read() 307 | sram="" 308 | 309 | command("setbyte",0x00,0x00,0x0a,0x00) #Enable SRAM 310 | 311 | if banks == -1: 312 | print "2KB SRAM writing not supported yet." 313 | else: 314 | i=0 315 | while i < banks: 316 | i+=1 317 | 318 | data=save[0+(i-1)*8192:8192+(i-1)*8192] 319 | command("setbyte",0x00,0x00,0x0a,0x00) #Enable SRAM 320 | command("setbyte",0x40,0x00,i-1,0x00) #Switch SRAM Bank 321 | 322 | #TODO make this mess into a loop 323 | command("send",0xA0,0x00,data[0:1024],constants.modeWrite) 324 | command("send",0xA4,0x00,data[1024:2048],constants.modeWrite) 325 | command("send",0xA8,0x00,data[2048:3072],constants.modeWrite) 326 | command("send",0xAC,0x00,data[3072:4096],constants.modeWrite) 327 | command("send",0xB0,0x00,data[4096:5120],constants.modeWrite) 328 | command("send",0xB4,0x00,data[5120:6144],constants.modeWrite) 329 | command("send",0xB8,0x00,data[6144:7168],constants.modeWrite) 330 | command("send",0xBC,0x00,data[7168:8192],constants.modeWrite) 331 | 332 | 333 | 334 | print "Wrote "+str(i) +"/"+str(banks)+ " Banks." 335 | command("setbyte",0xFF,0xF1,constants.modeRead,0x00) #Disable Writing 336 | command("setbyte",0x00,0x00,0x00,0x00) #Disable SRAM 337 | print "SRAM write complete" 338 | 339 | 340 | fo.close() 341 | return 342 | 343 | 344 | def writebyte(): 345 | a1 = int(raw_input(' Address (High Byte)> '), 16) 346 | a2 = int(raw_input(' Address (Low Byte)> '), 16) 347 | b = int(raw_input('Byte to be written> '), 16) 348 | command("setbyte",a1,a2,b,0x00) 349 | return 350 | 351 | def jumpto(): 352 | a1 = int(raw_input(' Address (High Byte)> '), 16) 353 | a2 = int(raw_input(' Address (Low Byte)> '), 16) 354 | command("jump",a1,a2,0,0) 355 | print "Jumped. PkSploit will be unresponsive until the Gameboy returns. (If it ever does)" 356 | return 357 | 358 | def restartgb(): 359 | command("jump",0x01,0x00,0,0) 360 | print "Restarted gameboy, exiting PkSploit." 361 | os._exit(1) 362 | return 363 | 364 | def eraseflash(): 365 | command("setbyte",0x21,0x00,0x00,0x00) 366 | command("setbyte",0x0A,0xAA,valAA,0x00) 367 | command("setbyte",0x05,0x55,val55,0x00) 368 | command("setbyte",0x0A,0xAA,0x80,0x00) 369 | command("setbyte",0x0A,0xAA,valAA,0x00) 370 | command("setbyte",0x05,0x55,val55,0x00) 371 | command("setbyte",0x0a,0xaa,0x10,0x00) 372 | print "Erasing flash..." 373 | print "This can take up to 30 seconds, so let's wait that long." 374 | time.sleep(30) 375 | return 376 | 377 | def flashrom(): 378 | print "Not yet implemented, sorry :/" 379 | return 380 | 381 | def togglelcd(): 382 | global lcdon 383 | if lcdon == True: 384 | command("turnofflcd",0,0,0,0) 385 | print "LCD off." 386 | else: 387 | command("turnonlcd",0,0,0,0) 388 | print "LCD on." 389 | return 390 | 391 | def teststuff(): 392 | data=constants.data[0:1024] 393 | c=chr(len(data) / 256) 394 | print "{:02x}".format(ord(c)) 395 | c=chr(len(data) % 256) 396 | print "{:02x}".format(ord(c)) 397 | return 398 | 399 | def flashromexpe(): 400 | global cmdoutput 401 | #data="".join(map(chr,[0xAF,0xEA,0x05,0xC0,0xFA,0x05,0xC0,0xA7,0xC0,0x76,0x00,0x18,0xF7,0xFF,0xFF,0xFF,0xC9,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xC9,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xC9,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xC9,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xC9,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xC9,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xC3,0x40,0xC2,0xFF,0xFF,0xFF,0xFF,0xFF,0xC3,0x43,0xC2,0xFF,0xFF,0xFF,0xFF,0xFF,0xD9,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xD9,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xD9,0x21,0x01,0xC0,0x01,0xFF,0x1F,0x18,0x0E,0x21,0x00,0x98,0x01,0x00,0x08,0x18,0x06,0x21,0x00,0x80,0x01,0x00,0x20,0xAF,0xC3,0x1C,0x0C,0xF5,0xF0,0x41,0xE6,0x02,0x28,0xFA,0xF0,0x41,0xE6,0x02,0x20,0xFA,0xF1,0xC9,0x3E,0x20,0xE0,0x00,0xF0,0x00,0xF0,0x00,0x2F,0xE6,0x0F,0xCB,0x37,0x47,0x3E,0x10,0xE0,0x00,0xF0,0x00,0xF0,0x00,0xF0,0x00,0xF0,0x00,0xF0,0x00,0xF0,0x00,0x2F,0xE6,0x0F,0xB0,0x47,0xFA,0x03,0xC0,0xA8,0xA0,0xEA,0x02,0xC0,0x78,0xEA,0x03,0xC0,0x3E,0x30,0xE0,0x00,0xC9,0xF0,0x40,0xCB,0x7F,0xC2,0x53,0x0F,0x11,0x00,0x98,0x06,0x12,0x0E,0x14,0x2A,0x12,0x13,0x0D,0x20,0xFA,0x0E,0x14,0x7B,0xC6,0x0C,0x30,0x01,0x14,0x5F,0x05,0x20,0xEE,0xC9,0x11,0x00,0x98,0x06,0x12,0x0E,0x14,0x2A,0xD6,0x20,0x12,0x13,0x0D,0x20,0xF8,0x0E,0x14,0x7B,0xC6,0x0C,0x30,0x01,0x14,0x5F,0x05,0x20,0xEC,0xC9,0xFF,0xFF,0xFF,0xFF,0xFF,0x00,0xC3,0x50,0x01,0xCE,0xED,0x66,0x66,0xCC,0x0D,0x00,0x0B,0x03,0x73,0x00,0x83,0x00,0x0C,0x00,0x0D,0x00,0x08,0x11,0x1F,0x88,0x89,0x00,0x0E,0xDC,0xCC,0x6E,0xE6,0xDD,0xDD,0xD9,0x99,0xBB,0xBB,0x67,0x63,0x6E,0x0E,0xEC,0xCC,0xDD,0xDC,0x99,0x9F,0xBB,0xB9,0x33,0x3E,0x42,0x4F,0x54,0x42,0x20,0x49,0x4E,0x56,0x49,0x54,0x45,0x00,0x00,0x00,0x00,0x00,0x44,0x53,0x00,0x19,0x00,0x00,0x01,0x33,0x00,0xED,0x90,0xC0,0xF5,0xF3,0xCD,0x61,0x00,0x3E,0x5D,0xEA,0x00,0xC0,0x3E,0xD9,0xEA,0x40,0xC2,0xEA,0x43,0xC2,0x3E,0x01,0xE0,0xFF,0xFB,0x21,0xA0,0xC2,0x11,0x04,0x01,0x06,0x30,0x1A,0x13,0xD5,0x5F,0xCD,0xDC,0x01,0xCB,0x33,0xCD,0xDC,0x01,0xD1,0x05,0x20,0xF0,0x21,0xA0,0xC2,0x11,0x10,0x80,0x0E,0x10,0x2A,0x47,0xF0,0x41,0xE6,0x02,0x20,0xFA,0x1A,0x13,0xB8,0x20,0x05,0x0D,0x20,0xF0,0x18,0x05,0x3E,0x01,0xEA,0x04,0xC0,0xF1,0xFE,0x11,0xC2,0x5C,0x03,0x3E,0x01,0xEA,0x04,0xC0,0xF0,0x44,0xFE,0x90,0x20,0xFA,0xAF,0xE0,0x40,0x01,0x00,0x04,0x21,0xDA,0x10,0x11,0x00,0x80,0xCD,0x72,0x0E,0x21,0xF4,0x01,0xCD,0xDF,0x00,0x3E,0xE4,0xE0,0x47,0xEE,0x75,0xE0,0x40,0xFB,0xCD,0x8A,0x00,0xFA,0x02,0xC0,0xCB,0x47,0xC2,0x5C,0x03,0x76,0x00,0x18,0xF1,0xAF,0x16,0x04,0x4B,0x07,0x07,0xCB,0x21,0x30,0x02,0xC6,0x03,0x15,0x20,0xF5,0x22,0x36,0x00,0x23,0x22,0x36,0x00,0x23,0xC9,0x20,0x20,0x20,0x20,0x21,0x20,0x57,0x41,0x52,0x4E,0x49,0x4E])) 402 | #data=[0xAF,0xEA,0x05,0xC0,0xFA,0x05,0xC0,0xA7,0xC0,0x76,0x00,0x18,0xF7,0xFF,0xFF,0xFF,0xC9,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xC9,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xC9,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xC9,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xC9,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xC9,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xC3,0x40,0xC2,0xFF,0xFF,0xFF,0xFF,0xFF,0xC3,0x43,0xC2,0xFF,0xFF,0xFF,0xFF,0xFF,0xD9,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xD9,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xD9,0x21,0x01,0xC0,0x01,0xFF,0x1F,0x18,0x0E,0x21,0x00,0x98,0x01,0x00,0x08,0x18,0x06,0x21,0x00,0x80,0x01,0x00,0x20,0xAF,0xC3,0x1C,0x0C,0xF5,0xF0,0x41,0xE6,0x02,0x28,0xFA,0xF0,0x41,0xE6,0x02,0x20,0xFA,0xF1,0xC9,0x3E,0x20,0xE0,0x00,0xF0,0x00,0xF0,0x00,0x2F,0xE6,0x0F,0xCB,0x37,0x47,0x3E,0x10,0xE0,0x00,0xF0,0x00,0xF0,0x00,0xF0,0x00,0xF0,0x00,0xF0,0x00,0xF0,0x00,0x2F,0xE6,0x0F,0xB0,0x47,0xFA,0x03,0xC0,0xA8,0xA0,0xEA,0x02,0xC0,0x78,0xEA,0x03,0xC0,0x3E,0x30,0xE0,0x00,0xC9,0xF0,0x40,0xCB,0x7F,0xC2,0x53,0x0F,0x11,0x00,0x98,0x06,0x12,0x0E,0x14,0x2A,0x12,0x13,0x0D,0x20,0xFA,0x0E,0x14,0x7B,0xC6,0x0C,0x30,0x01,0x14,0x5F,0x05,0x20,0xEE,0xC9,0x11,0x00,0x98,0x06,0x12,0x0E,0x14,0x2A,0xD6,0x20,0x12,0x13,0x0D,0x20,0xF8,0x0E,0x14,0x7B,0xC6,0x0C,0x30,0x01,0x14,0x5F,0x05,0x20,0xEC,0xC9,0xFF,0xFF,0xFF,0xFF,0xFF,0x00,0xC3,0x50,0x01,0xCE,0xED,0x66,0x66,0xCC,0x0D,0x00,0x0B,0x03,0x73,0x00,0x83,0x00,0x0C,0x00,0x0D,0x00,0x08,0x11,0x1F,0x88,0x89,0x00,0x0E,0xDC,0xCC,0x6E,0xE6,0xDD,0xDD,0xD9,0x99,0xBB,0xBB,0x67,0x63,0x6E,0x0E,0xEC,0xCC,0xDD,0xDC,0x99,0x9F,0xBB,0xB9,0x33,0x3E,0x42,0x4F,0x54,0x42,0x20,0x49,0x4E,0x56,0x49,0x54,0x45,0x00,0x00,0x00,0x00,0x00,0x44,0x53,0x00,0x19,0x00,0x00,0x01,0x33,0x00,0xED,0x90,0xC0,0xF5,0xF3,0xCD,0x61,0x00,0x3E,0x5D,0xEA,0x00,0xC0,0x3E,0xD9,0xEA,0x40,0xC2,0xEA,0x43,0xC2,0x3E,0x01,0xE0,0xFF,0xFB,0x21,0xA0,0xC2,0x11,0x04,0x01,0x06,0x30,0x1A,0x13,0xD5,0x5F,0xCD,0xDC,0x01,0xCB,0x33,0xCD,0xDC,0x01,0xD1,0x05,0x20,0xF0,0x21,0xA0,0xC2,0x11,0x10,0x80,0x0E,0x10,0x2A,0x47,0xF0,0x41,0xE6,0x02,0x20,0xFA,0x1A,0x13,0xB8,0x20,0x05,0x0D,0x20,0xF0,0x18,0x05,0x3E,0x01,0xEA,0x04,0xC0,0xF1,0xFE,0x11,0xC2,0x5C,0x03,0x3E,0x01,0xEA,0x04,0xC0,0xF0,0x44,0xFE,0x90,0x20,0xFA,0xAF,0xE0,0x40,0x01,0x00,0x04,0x21,0xDA,0x10,0x11,0x00,0x80,0xCD,0x72,0x0E,0x21,0xF4,0x01,0xCD,0xDF,0x00,0x3E,0xE4,0xE0,0x47,0xEE,0x75,0xE0,0x40,0xFB,0xCD,0x8A,0x00,0xFA,0x02,0xC0,0xCB,0x47,0xC2,0x5C,0x03,0x76,0x00,0x18,0xF1,0xAF,0x16,0x04,0x4B,0x07,0x07,0xCB,0x21,0x30,0x02,0xC6,0x03,0x15,0x20,0xF5,0x22,0x36,0x00,0x23,0x22,0x36,0x00,0x23,0xC9,0x20,0x20,0x20,0x20,0x21,0x20,0x57,0x41,0x52,0x4E,0x49,0x4E] 403 | 404 | #tetris as testrom 405 | file_path = tkFileDialog.askopenfilename(defaultextension=".gb", initialfile=''.join(e for e in cartname if e.isalnum())+".gb") 406 | if file_path == "None" or file_path == "": 407 | return False 408 | fo = open(file_path, "rb") 409 | data = fo.read() 410 | 411 | # for num,bb in enumerate(data): 412 | # #command("setbyte",0x21,0x00,0x00,0x00) 413 | # command("setbyte",0x0A,0xAA,valAA,0x00) 414 | # command("setbyte",0x05,0x55,val55,0x00) 415 | # command("setbyte",0x0A,0xAA,0xA0,0x00) 416 | # command("setbyte",0x00+num / 256,num % 256,bb,0x00) 417 | # print ".", 418 | # time.sleep(0.05) 419 | # time.sleep(2) 420 | # command("setbyte",0xFF,0xF2,0x00,0x00) 421 | # command("setbyte",0xFF,0xF1,0xFF,0x00) 422 | 423 | time.sleep(1) 424 | amount=len(data) 425 | steps=1024 426 | i=0 427 | j=0 428 | while i < amount: 429 | print i, 430 | print ":", 431 | print i / 256, 432 | print "-", 433 | print i % 256, 434 | command("setbyte",0xFF,0xF2, i / 16384 ,0x00) 435 | command("send",j , i % 256,data[i:i+steps],constants.modeFlash) 436 | i=i+steps 437 | print i 438 | j=j+4 439 | if j==128: 440 | j=64 441 | time.sleep(0.2) 442 | # command("setbyte",0x21,0x00,0x00,0x00) 443 | # time.sleep(0.2) 444 | # command("send",0x04,0x00,data[1024:2048],constants.modeFlash) 445 | # time.sleep(1) 446 | # command("setbyte",0x21,0x00,0x00,0x00) 447 | # time.sleep(0.2) 448 | # command("send",0x08,0x00,data[2048:3072],constants.modeFlash) 449 | # time.sleep(1) 450 | # command("setbyte",0x21,0x00,0x00,0x00) 451 | # time.sleep(0.2) 452 | # command("send",0x0C,0x00,data[3072:4096],constants.modeFlash) 453 | # time.sleep(1) 454 | # command("setbyte",0x21,0x00,0x00,0x00) 455 | # time.sleep(0.2) 456 | # command("send",0x10,0x00,data[4096:5120],constants.modeFlash) 457 | # time.sleep(1) 458 | # command("setbyte",0x21,0x00,0x00,0x00) 459 | # time.sleep(0.2) 460 | # command("send",0x14,0x00,data[5120:6144],constants.modeFlash) 461 | # time.sleep(1) 462 | # command("setbyte",0x21,0x00,0x00,0x00) 463 | # time.sleep(0.2) 464 | # command("send",0x18,0x00,data[6144:7168],constants.modeFlash) 465 | # time.sleep(1) 466 | # command("setbyte",0x21,0x00,0x00,0x00) 467 | # time.sleep(0.2) 468 | # command("send",0x1C,0x00,data[7168:8192],constants.modeFlash) 469 | 470 | 471 | time.sleep(5) 472 | command("dump",0x02,0x00,0x00,0x00) 473 | 474 | print " ".join("{:02x}".format(ord(c)) for c in cmdoutput) 475 | return 476 | def flashexperiment(): #Don't use 477 | global cmdoutput 478 | # command("setbyte",0x21,0x00,0x00,0x00) 479 | # command("setbyte",0x0A,0xAA,0xA9,0x00) 480 | # command("setbyte",0x05,0x55,0x56,0x00) 481 | # command("setbyte",0x0A,0xAA,0x80,0x00) 482 | # command("setbyte",0x0A,0xAA,0xA9,0x00) 483 | # command("setbyte",0x05,0x55,0x56,0x00) 484 | # #command("setbyte",0x0a,0xaa,0x10,0x00) 485 | # command("setbyte",0x00,0x00,0x30,0x00) 486 | # time.sleep(1) 487 | 488 | #command("setbyte",0xFF,0xF2,0x00,0x00) 489 | #command("setbyte",0xFF,0xF1,0x03,0x00) 490 | 491 | #data="".join(map(chr,range(0,20))) 492 | #command("send",0x00,0x00,data,0x00) 493 | 494 | command("setbyte",0x0A,0xAA,0xA9,0x00) 495 | command("setbyte",0x05,0x55,0x56,0x00) 496 | command("setbyte",0x0A,0xAA,0xA0,0x00) 497 | command("setbyte",0x00,0xF3,0xEF,0x00) 498 | time.sleep(1) 499 | command("setbyte",0x0A,0xAA,0xA9,0x00) 500 | command("setbyte",0x05,0x55,0x56,0x00) 501 | command("setbyte",0x0A,0xAA,0xA0,0x00) 502 | command("setbyte",0x00,0xF2,0xBE,0x00) 503 | time.sleep(1) 504 | command("setbyte",0x0A,0xAA,0xA9,0x00) 505 | command("setbyte",0x05,0x55,0x56,0x00) 506 | command("setbyte",0x0A,0xAA,0xA0,0x00) 507 | command("setbyte",0x00,0xF0,0xDE,0x00) 508 | time.sleep(1) 509 | command("setbyte",0x0A,0xAA,0xA9,0x00) 510 | command("setbyte",0x05,0x55,0x56,0x00) 511 | command("setbyte",0x0A,0xAA,0xA0,0x00) 512 | command("setbyte",0x00,0xF1,0xAD,0x00) 513 | time.sleep(1) 514 | 515 | 516 | 517 | 518 | 519 | command("dump",0x00,0xFF,0x00,0x00) 520 | print " ".join("{:02x}".format(ord(c)) for c in cmdoutput) 521 | return 522 | 523 | def flashbyte(): 524 | a1 = int(raw_input('A1> '), 16) 525 | a2 = int(raw_input('A2> '), 16) 526 | b = int(raw_input('B> '), 16) 527 | command("setbyte",0x0A,0xAA,valAA,0x00) 528 | command("setbyte",0x05,0x55,val55,0x00) 529 | command("setbyte",0x0A,0xAA,0xA0,0x00) 530 | command("setbyte",a1,a2,b,0x00) 531 | return 532 | 533 | 534 | def serialf(): 535 | global ser, state, cmdstr, cmdpar1, cmdpar2, cmdpar3, cmdpar4, cmddone, cmdoutput, lcdon 536 | buf="" 537 | 538 | while True: 539 | buf+=ser.read(1) 540 | # print buf 541 | if state == "serialopen": 542 | if "Boot complete" in buf: 543 | state="arduinoconnected" 544 | buf="" 545 | time.sleep(0.2) #Giving the arduino some time to init 546 | if state == "arduinoconnected": 547 | if "StatusConnected" in buf: 548 | print "Link established" 549 | buf="" 550 | if "StatusMQ" in buf: 551 | state="pksploit_mq" 552 | buf="" 553 | if "StatusTradeCenter" in buf: 554 | print "Gameboy selected Trade Center" 555 | buf="" 556 | if "StatusSending" in buf: 557 | print "Sending data..." 558 | buf="" 559 | if "StatusMenu" in buf: 560 | state="pksploit_menu" 561 | buf="" 562 | if state == "pksploit_menu": 563 | #wait for commands 564 | time.sleep(0.05) 565 | if cmdstr == "setcounter": 566 | ser.write(chr(0x11)) #Arduino command HEX 11: Set internal counter 567 | ser.write(chr(cmdpar1)) 568 | ser.write(chr(cmdpar2)) 569 | buf="" 570 | cmdstr="" 571 | cmdoutput="" 572 | cmddone=True 573 | if cmdstr == "setbyte": 574 | ser.write(chr(0xAA)) 575 | ser.write(chr(cmdpar1)) 576 | ser.write(chr(cmdpar2)) 577 | ser.write(chr(cmdpar3)) 578 | buf="" 579 | cmdstr="" 580 | cmdoutput="" 581 | cmddone=True 582 | if cmdstr == "turnofflcd": 583 | ser.write(chr(0x66)) 584 | ser.write(chr(0x00)) 585 | ser.write(chr(0x00)) 586 | buf="" 587 | cmdstr="" 588 | cmdoutput="" 589 | lcdon=False 590 | cmddone=True 591 | if cmdstr == "turnonlcd": 592 | ser.write(chr(0xFF)) 593 | ser.write(chr(0x40)) 594 | ser.write(chr(0xE3)) 595 | 596 | buf="" 597 | cmdstr="" 598 | cmdoutput="" 599 | lcdon=True 600 | cmddone=True 601 | if cmdstr == "jump": 602 | ser.write(chr(0x33)) 603 | ser.write(chr(cmdpar1)) 604 | ser.write(chr(cmdpar2)) 605 | buf="" 606 | cmdstr="" 607 | cmdoutput="" 608 | cmddone=True 609 | if cmdstr == "dump": 610 | ser.write(chr(0x11)) #Arduino command HEX 11: Set internal counter 611 | ser.write(chr(cmdpar1)) 612 | ser.write(chr(cmdpar2)) 613 | 614 | time.sleep(0.10) 615 | ser.write(chr(0x22)) #Arduino command HEX 22: Set block mode to "dump" 616 | 617 | ser.write(chr(0x55)) #PkSploit Command HEX 55: Block transfer 618 | ser.write(chr(cmdpar1)) #High byte of byte number 619 | ser.write(chr(cmdpar2)) #Low byte of byte number 620 | ser.write(chr(cmdpar3)) #High byte of start address 621 | ser.write(chr(cmdpar4)) #Low Byte of start address 622 | buf="" 623 | 624 | while "StatusMenu" not in buf: 625 | buf+=ser.read(1) 626 | 627 | if len(buf) % 10==0: 628 | print ".", 629 | print "." 630 | cmdoutput=buf[:-11] 631 | cmdstr="" 632 | buf="" 633 | cmddone=True 634 | if cmdstr == "send": 635 | ser.write(chr(0xAA)) #Setbyte command 636 | ser.write(chr(0xFF)) 637 | ser.write(chr(0xF1)) #Address FFF1 638 | ser.write(chr(cmdpar4)) #Set Write mode (See constants.py) 639 | 640 | ser.write(chr(0xAA)) #Setbyte command 641 | ser.write(chr(0xFF)) 642 | ser.write(chr(0xF0)) #Address FFF0 643 | ser.write(chr(0xFF)) #Set FF delay time to ensure data makes it to the gameboy uncorrupted 644 | time.sleep(0.10) 645 | #cmdpar3 is data to be sent 646 | ser.write(chr(0x11)) #Arduino command HEX 11: Set internal counter 647 | ser.write(chr(len(cmdpar3) / 256)) #High Byte of Byte counter 648 | ser.write(chr(len(cmdpar3) % 256)) #Low Byte of Byte counter 649 | ser.write(chr(0x00)) 650 | time.sleep(0.10) 651 | 652 | ser.write(chr(0x77)) 653 | ser.write(chr(0xDE)) 654 | ser.write(chr(0xAD)) 655 | ser.write(chr(0xBE)) 656 | ser.write(chr(0xEF)) # sync failsafe 657 | i=0 658 | while i