From d448823ffb540cb462548552efe4b5650e66de95 Mon Sep 17 00:00:00 2001 From: slipstream/RoL Date: Mon, 20 Feb 2017 18:29:22 +0000 Subject: Add support for almost all Gen III games MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The only Gen III game unsupported should be Pokémon LeafGreen v1.1 (Japan); as it is undumped. A couple of bugs have also been fixed. The code has also been refactored a little; now payload code goes into `payload.c`. The codebase is also ready for a planned future change to include savedata structure definitions. This will be done when it's done -- PRs to help would be appreciated! --- gba/Makefile | 2 +- gba/source/call_into_middle_of_titlescreen_func.s | 13 ++ gba/source/main.c | 250 ++++++++++++++++++++-- gba/source/payload.c | 18 ++ gba/source/payload.h | 12 ++ gba/source/saveblocks.h | 15 ++ gba/start/pkjb_crt0.s | 4 + 7 files changed, 299 insertions(+), 15 deletions(-) create mode 100644 gba/source/call_into_middle_of_titlescreen_func.s create mode 100644 gba/source/payload.c create mode 100644 gba/source/payload.h create mode 100644 gba/source/saveblocks.h diff --git a/gba/Makefile b/gba/Makefile index f9cb296..b28e0e1 100644 --- a/gba/Makefile +++ b/gba/Makefile @@ -32,7 +32,7 @@ INCLUDES := #--------------------------------------------------------------------------------- ARCH := -mthumb -mthumb-interwork -CFLAGS := -g -Wall -O3\ +CFLAGS := -g -Wall -Wno-multichar -O3\ -mcpu=arm7tdmi -mtune=arm7tdmi\ -fomit-frame-pointer\ -ffast-math \ diff --git a/gba/source/call_into_middle_of_titlescreen_func.s b/gba/source/call_into_middle_of_titlescreen_func.s new file mode 100644 index 0000000..7908f6b --- /dev/null +++ b/gba/source/call_into_middle_of_titlescreen_func.s @@ -0,0 +1,13 @@ + .text + .code 16 + + .global call_into_middle_of_titlescreen_func + .thumb_func +call_into_middle_of_titlescreen_func: + push {lr} + push {r4-r5} + @ use r4 (which already got saved to the stack) as scratch space to reserve a variable amount of stack space + mov r4,sp + sub r4,r4,r1 + mov sp,r4 + bx r0 \ No newline at end of file diff --git a/gba/source/main.c b/gba/source/main.c index ce6969b..5e2b708 100644 --- a/gba/source/main.c +++ b/gba/source/main.c @@ -1,42 +1,264 @@ /* * Example Gen3-multiboot payload by slipstream/RoL 2017. - * Supports only English Ruby, v1.0-1.2. * * This software may be modified and distributed under the terms * of the MIT license. See the LICENSE file for details. + * + * main.c: setup, call payload, return gracefully back to game */ #include +#include "payload.h" + +void call_into_middle_of_titlescreen_func(u32 addr,u32 stackspace); int main(void) { // check the ROM code, make sure this game is supported. - char* ROM = 0x8000000; + u8* ROM = (u8*) 0x8000000; - if ((*(u32*)(&ROM[0xAC])) != 'EVXA') return 0; // Pokémon Ruby english, nothing else supported! + u32 gamecode = (*(u32*)(&ROM[0xAC])); void(*loadsave)(char a1); + void(*mainloop)(); + pSaveBlock1 gSaveBlock1; + pSaveBlock2 gSaveBlock2; + pSaveBlock3 gSaveBlock3; + u32 titlemid = 0; // get the address of the save loading function. - switch (ROM[0xBC]) { // version number - case 0: - loadsave = 0x8125EC9; + switch (gamecode) { + // --- R/S --- + case 'DVXA': // Ruby German + case 'DPXA': // Sapphire German + // TODO: detect debug ROM? + gSaveBlock1 = (pSaveBlock1) 0x2025734; + gSaveBlock2 = (pSaveBlock2) 0x2024EA4; + gSaveBlock3 = (pSaveBlock3) 0x20300A0; + loadsave = (void(*)(char)) 0x8126249; // same for v1.0 + v1.1 + mainloop = (void(*)()) 0x80003D9; + break; + case 'FVXA': // Ruby French + case 'FPXA': // Sapphire French + gSaveBlock1 = (pSaveBlock1) 0x2025734; + gSaveBlock2 = (pSaveBlock2) 0x2024EA4; + gSaveBlock3 = (pSaveBlock3) 0x20300A0; + loadsave = (void(*)(char)) 0x8126351; // same for v1.0 + v1.1 + mainloop = (void(*)()) 0x80003D9; + break; + case 'IVXA': // Ruby Italian + case 'IPXA': // Sapphire Italian + gSaveBlock1 = (pSaveBlock1) 0x2025734; + gSaveBlock2 = (pSaveBlock2) 0x2024EA4; + gSaveBlock3 = (pSaveBlock3) 0x20300A0; + loadsave = (void(*)(char)) 0x8126249; // same for v1.0 + v1.1 + mainloop = (void(*)()) 0x80003D9; + break; + case 'SVXA': // Ruby Spanish + case 'SPXA': // Sapphire Spanish + gSaveBlock1 = (pSaveBlock1) 0x2025734; + gSaveBlock2 = (pSaveBlock2) 0x2024EA4; + gSaveBlock3 = (pSaveBlock3) 0x20300A0; + loadsave = (void(*)(char)) 0x8126349; // same for v1.0 + v1.1 + mainloop = (void(*)()) 0x80003D9; + break; + case 'EVXA': // Ruby English + case 'EPXA': // Sapphire English + gSaveBlock1 = (pSaveBlock1) 0x2025734; + gSaveBlock2 = (pSaveBlock2) 0x2024EA4; + gSaveBlock3 = (pSaveBlock3) 0x20300A0; + mainloop = (void(*)()) 0x80002A5; + switch (ROM[0xBC]) { // version number + case 0: + loadsave = (void(*)(char)) 0x8125EC9; + break; + case 1: + case 2: + loadsave = (void(*)(char)) 0x8125EE9; + break; + default: + return 0; // unsupported version + } + break; + case 'JVXA': // Ruby Japanese + case 'JPXA': // Sapphire Japanese + gSaveBlock1 = (pSaveBlock1) 0x2025494; + gSaveBlock2 = (pSaveBlock2) 0x2024C04; + gSaveBlock3 = (pSaveBlock3) 0x202FDBC; + loadsave = (void(*)(char)) 0x8120d05; // same for v1.0 + v1.1 + mainloop = (void(*)()) 0x80002A9; + break; + /// --- FR/LG --- + // In FR/LG, the function that initialises the save-block pointers to default does not set up saveblock3. + // Which will need to be set up before loading the save if we want boxed Pokémon to not disappear. + // Oh, and loadsave() offset is different between FR and LG... + case 'DRPB': // FireRed German + case 'DGPB': // LeafGreen German + gSaveBlock1 = (pSaveBlock1) 0x202552C; + gSaveBlock2 = (pSaveBlock2) 0x2024588; + gSaveBlock3 = (pSaveBlock3) 0x2029314; + *(pSaveBlock3*)(0x3004f60) = gSaveBlock3; + loadsave = (void(*)(char)) ( (gamecode << 8) == 'RPB\x00' ? 0x80da721 : 0x80da6f5 ); + mainloop = (void(*)()) 0x8000425; + titlemid = 0x80791df; + break; + case 'FRPB': // FireRed French + case 'FGPB': // LeafGreen French + gSaveBlock1 = (pSaveBlock1) 0x202552C; + gSaveBlock2 = (pSaveBlock2) 0x2024588; + gSaveBlock3 = (pSaveBlock3) 0x2029314; + *(pSaveBlock3*)(0x3004f60) = gSaveBlock3; + loadsave = (void(*)(char)) ( (gamecode << 8) == 'RPB\x00' ? 0x80da7e1 : 0x80da7b5 ); + mainloop = (void(*)()) 0x8000417; + titlemid = 0x807929f; + break; + case 'IRPB': // FireRed Italian + case 'IGPB': // LeafGreen Italian + gSaveBlock1 = (pSaveBlock1) 0x202552C; + gSaveBlock2 = (pSaveBlock2) 0x2024588; + gSaveBlock3 = (pSaveBlock3) 0x2029314; + *(pSaveBlock3*)(0x3004f60) = gSaveBlock3; + loadsave = (void(*)(char)) ( (gamecode << 8) == 'RPB\x00' ? 0x80da721 : 0x80da6f5 ); + mainloop = (void(*)()) 0x8000425; + titlemid = 0x80791cb; break; - case 1: - case 2: - loadsave = 0x8125EE9; + case 'SRPB': // FireRed Spanish + case 'SGPB': // LeafGreen Spanish + gSaveBlock1 = (pSaveBlock1) 0x202552C; + gSaveBlock2 = (pSaveBlock2) 0x2024588; + gSaveBlock3 = (pSaveBlock3) 0x2029314; + *(pSaveBlock3*)(0x3004f60) = gSaveBlock3; + loadsave = (void(*)(char)) ( (gamecode << 8) == 'RPB\x00' ? 0x80da809 : 0x80da7dd ); + mainloop = (void(*)()) 0x8000417; + titlemid = 0x80792b3; + break; + case 'ERPB': // FireRed English + case 'EGPB': // LeafGreen English + gSaveBlock1 = (pSaveBlock1) 0x202552C; + gSaveBlock2 = (pSaveBlock2) 0x2024588; + gSaveBlock3 = (pSaveBlock3) 0x2029314; + *(pSaveBlock3*)(0x3005010) = gSaveBlock3; + switch (ROM[0xBC]) { // version number + case 0: + loadsave = (void(*)(char)) ( (gamecode << 8) == 'RPB\x00' ? 0x80da4fd : 0x80da4d1 ); + mainloop = (void(*)()) 0x800041b; + titlemid = 0x807927b; + break; + case 1: + loadsave = (void(*)(char)) ( (gamecode << 8) == 'RPB\x00' ? 0x80da511 : 0x80da4e5 ); + mainloop = (void(*)()) 0x8000429; + titlemid = 0x807928f; + break; + default: + return 0; // unsupported version + } + break; + case 'JRPB': // FireRed Japanese + case 'JGPB': // LeafGreen Japanese + gSaveBlock1 = (pSaveBlock1) 0x202548C; + gSaveBlock2 = (pSaveBlock2) 0x20244E8; + gSaveBlock3 = (pSaveBlock3) 0x202924C; + *(pSaveBlock3*)(0x3005050) = gSaveBlock3; + switch (ROM[0xBC]) { // version number + case 0: + loadsave = (void(*)(char)) ( (gamecode << 8) == 'RPB\x00' ? 0x80db4e5 : 0x80db4b9 ); + mainloop = (void(*)()) 0x800041b; + titlemid = ( (gamecode << 8) == 'RPB\x00' ? 0x8078a0d : 0x8078a0f ); + break; + case 1: + if ((gamecode << 8) == 'GPB\x00') { + // LeafGreen v1.1 Japanese is undumped. + // Therefore, it is unsupported. + // I will make guesses at the offsets in the comments, but I will not actually implement them until LeafGreen v1.1 is dumped. + return 0; + } + loadsave = (void(*)(char)) 0x80db529; // potential LG1.1 address: 0x80db4fd + mainloop = (void(*)()) 0x8000417; + titlemid = 0x8078987; // potential LG1.1 address: 0x8078989 + break; + default: + return 0; // unsupported version + } + break; + /// --- Emerald --- + // In Emerald, the saveblock pointer that isn't set up is saveblock1 (in FR/LG it was saveblock3). + // The initial save loading code after the copyright screen is also updated, now it sets up ASLR/crypto here before loading the save. + case 'DEPB': // Emerald German + gSaveBlock1 = (pSaveBlock1) 0x2025A00; + gSaveBlock2 = (pSaveBlock2) 0x2024A54; + gSaveBlock3 = (pSaveBlock3) 0x2029808; + *(pSaveBlock1*)(0x3005d8c) = gSaveBlock1; + loadsave = (void(*)(char)) 0x8153075; + mainloop = (void(*)()) 0x800042b; + titlemid = 0x816fdb5; + break; + case 'FEPB': // Emerald French + gSaveBlock1 = (pSaveBlock1) 0x2025A00; + gSaveBlock2 = (pSaveBlock2) 0x2024A54; + gSaveBlock3 = (pSaveBlock3) 0x2029808; + *(pSaveBlock1*)(0x3005d8c) = gSaveBlock1; + loadsave = (void(*)(char)) 0x815319d; + mainloop = (void(*)()) 0x800042b; + titlemid = 0x816fedd; + break; + case 'IEPB': // Emerald Italian + gSaveBlock1 = (pSaveBlock1) 0x2025A00; + gSaveBlock2 = (pSaveBlock2) 0x2024A54; + gSaveBlock3 = (pSaveBlock3) 0x2029808; + *(pSaveBlock1*)(0x3005d8c) = gSaveBlock1; + loadsave = (void(*)(char)) 0x8153065; + mainloop = (void(*)()) 0x800042b; + titlemid = 0x816fda5; + break; + case 'SEPB': // Emerald Spanish + gSaveBlock1 = (pSaveBlock1) 0x2025A00; + gSaveBlock2 = (pSaveBlock2) 0x2024A54; + gSaveBlock3 = (pSaveBlock3) 0x2029808; + *(pSaveBlock1*)(0x3005d8c) = gSaveBlock1; + loadsave = (void(*)(char)) 0x8153175; + mainloop = (void(*)()) 0x800042b; + titlemid = 0x816feb5; + break; + case 'EEPB': // Emerald English + gSaveBlock1 = (pSaveBlock1) 0x2025A00; + gSaveBlock2 = (pSaveBlock2) 0x2024A54; + gSaveBlock3 = (pSaveBlock3) 0x2029808; + *(pSaveBlock1*)(0x3005d8c) = gSaveBlock1; + loadsave = (void(*)(char)) 0x81534d1; + mainloop = (void(*)()) 0x800042b; + titlemid = 0x817014d; + break; + case 'JEPB': // Emerald Japanese + gSaveBlock1 = (pSaveBlock1) 0x20256A4; + gSaveBlock2 = (pSaveBlock2) 0x20246F8; + gSaveBlock3 = (pSaveBlock3) 0x20294AC; + *(pSaveBlock1*)(0x3005aec) = gSaveBlock1; + loadsave = (void(*)(char)) 0x815340d; + mainloop = (void(*)()) 0x800042b; + titlemid = 0x816ff45; break; default: - return 0; //bail out + return 0; // this game isn't supported } loadsave(0); // now the save is loaded, we can do what we want with the loaded blocks. - // here as a small PoC, changing first letter of player name to 'z'. - u8* gSaveBlock2 = 0x2024EA4; - gSaveBlock2[0] = 0xee; // 'z' + // time to call the payload. + payload(gSaveBlock1,gSaveBlock2,gSaveBlock3); + // In FR/LG/Emerald, just returning to the game is unwise. + // The game reloads the savefile. + // In FR/LG, this is done at the title screen after setting ASLR/saveblock-crypto up. (probably because at initial save-load, SaveBlock3 ptr isn't set up lol) + // So, better bypass the title screen and get the game to return directly to the Continue/New Game screen. + // In Emerald, the save reload happens after the Continue option was chosen, so we have no choice but to bypass everything and get the game to go straight to the overworld. + // Easiest way to do this is to call into the middle of the function we want, using an ASM wrapper to set up the stack. + // Here goes... + if (titlemid) { + // Function reserves an extra 4 bytes of stack space in FireRed, and none in Emerald. + call_into_middle_of_titlescreen_func(titlemid,((gamecode << 8) == 'EPB\x00' ? 0 : 4)); + } // Now we've done what we want, time to return to the game. // Can't just return, the game will reload the save. // So let's just call the main-loop directly ;) - void(*mainloop)() = 0x80002A5; // turn the sound back on before we head back to the game *(vu16 *)(REG_BASE + 0x84) = 0x8f; + // re-enable interrupts + REG_IME = 1; mainloop(); // Anything past here will not be executed. return 0; diff --git a/gba/source/payload.c b/gba/source/payload.c new file mode 100644 index 0000000..7015774 --- /dev/null +++ b/gba/source/payload.c @@ -0,0 +1,18 @@ +/* + * Example Gen3-multiboot payload by slipstream/RoL 2017. + * + * This software may be modified and distributed under the terms + * of the MIT license. See the LICENSE file for details. + * + * payload.c: place where user payload should go :) + */ + +#include +#include "payload.h" + +// Your payload code should obviously go into the body of this, the payload function. +void payload(pSaveBlock1 SaveBlock1,pSaveBlock2 SaveBlock2,pSaveBlock3 SaveBlock3) { + // This example payload will modify the first character of the player's name. + // It will change to 'z'. You can see the character encoding table here: http://bulbapedia.bulbagarden.net/wiki/Character_encoding_in_Generation_III + SaveBlock2[0] = 0xee; // 'z' +} \ No newline at end of file diff --git a/gba/source/payload.h b/gba/source/payload.h new file mode 100644 index 0000000..3f9ca62 --- /dev/null +++ b/gba/source/payload.h @@ -0,0 +1,12 @@ +/* + * Example Gen3-multiboot payload by slipstream/RoL 2017. + * + * This software may be modified and distributed under the terms + * of the MIT license. See the LICENSE file for details. + * + * payload.h: header file describing payload function + */ + +#include "saveblocks.h" + +void payload(pSaveBlock1 SaveBlock1,pSaveBlock2 SaveBlock2,pSaveBlock3 SaveBlock3); \ No newline at end of file diff --git a/gba/source/saveblocks.h b/gba/source/saveblocks.h new file mode 100644 index 0000000..45c127c --- /dev/null +++ b/gba/source/saveblocks.h @@ -0,0 +1,15 @@ +/* + * Example Gen3-multiboot payload by slipstream/RoL 2017. + * + * This software may be modified and distributed under the terms + * of the MIT license. See the LICENSE file for details. + * + * saveblocks.h: describes saveblock structures for all of Gen 3 (yay!) + */ + +// TODO: this entire file. Placeholders for now, fill in later, if I can be bothered. +// I don't really want to make a fork of pokeruby's headers for now... + +typedef u8 SaveBlock1, *pSaveBlock1; +typedef u8 SaveBlock2, *pSaveBlock2; +typedef u8 SaveBlock3, *pSaveBlock3; \ No newline at end of file diff --git a/gba/start/pkjb_crt0.s b/gba/start/pkjb_crt0.s index 1bf5bd7..0d9d657 100644 --- a/gba/start/pkjb_crt0.s +++ b/gba/start/pkjb_crt0.s @@ -85,6 +85,10 @@ start_vector: ldr r1, =0x4000084 mov r0, #0x8F strh r0, [r1] +@; Also turn interrupts back on + ldr r1, =0x4000208 + mov r0, #1 + str r0, [r1] pop {pc} @--------------------------------------------------------------------------------- -- cgit 1.4.1