about summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--gba/Makefile2
-rw-r--r--gba/source/call_into_middle_of_titlescreen_func.s13
-rw-r--r--gba/source/main.c250
-rw-r--r--gba/source/payload.c18
-rw-r--r--gba/source/payload.h12
-rw-r--r--gba/source/saveblocks.h15
-rw-r--r--gba/start/pkjb_crt0.s4
7 files changed, 299 insertions, 15 deletions
diff --git a/gba/Makefile b/gba/Makefile index f9cb296..b28e0e1 100644 --- a/gba/Makefile +++ b/gba/Makefile
@@ -32,7 +32,7 @@ INCLUDES :=
32#--------------------------------------------------------------------------------- 32#---------------------------------------------------------------------------------
33ARCH := -mthumb -mthumb-interwork 33ARCH := -mthumb -mthumb-interwork
34 34
35CFLAGS := -g -Wall -O3\ 35CFLAGS := -g -Wall -Wno-multichar -O3\
36 -mcpu=arm7tdmi -mtune=arm7tdmi\ 36 -mcpu=arm7tdmi -mtune=arm7tdmi\
37 -fomit-frame-pointer\ 37 -fomit-frame-pointer\
38 -ffast-math \ 38 -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 @@
1 .text
2 .code 16
3
4 .global call_into_middle_of_titlescreen_func
5 .thumb_func
6call_into_middle_of_titlescreen_func:
7 push {lr}
8 push {r4-r5}
9 @ use r4 (which already got saved to the stack) as scratch space to reserve a variable amount of stack space
10 mov r4,sp
11 sub r4,r4,r1
12 mov sp,r4
13 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 @@
1/* 1/*
2 * Example Gen3-multiboot payload by slipstream/RoL 2017. 2 * Example Gen3-multiboot payload by slipstream/RoL 2017.
3 * Supports only English Ruby, v1.0-1.2.
4 * 3 *
5 * This software may be modified and distributed under the terms 4 * This software may be modified and distributed under the terms
6 * of the MIT license. See the LICENSE file for details. 5 * of the MIT license. See the LICENSE file for details.
6 *
7 * main.c: setup, call payload, return gracefully back to game
7 */ 8 */
8#include <gba.h> 9#include <gba.h>
10#include "payload.h"
11
12void call_into_middle_of_titlescreen_func(u32 addr,u32 stackspace);
9 13
10int main(void) { 14int main(void) {
11 // check the ROM code, make sure this game is supported. 15 // check the ROM code, make sure this game is supported.
12 char* ROM = 0x8000000; 16 u8* ROM = (u8*) 0x8000000;
13 17
14 if ((*(u32*)(&ROM[0xAC])) != 'EVXA') return 0; // Pokémon Ruby english, nothing else supported! 18 u32 gamecode = (*(u32*)(&ROM[0xAC]));
15 19
16 void(*loadsave)(char a1); 20 void(*loadsave)(char a1);
21 void(*mainloop)();
22 pSaveBlock1 gSaveBlock1;
23 pSaveBlock2 gSaveBlock2;
24 pSaveBlock3 gSaveBlock3;
25 u32 titlemid = 0;
17 // get the address of the save loading function. 26 // get the address of the save loading function.
18 switch (ROM[0xBC]) { // version number 27 switch (gamecode) {
19 case 0: 28 // --- R/S ---
20 loadsave = 0x8125EC9; 29 case 'DVXA': // Ruby German
30 case 'DPXA': // Sapphire German
31 // TODO: detect debug ROM?
32 gSaveBlock1 = (pSaveBlock1) 0x2025734;
33 gSaveBlock2 = (pSaveBlock2) 0x2024EA4;
34 gSaveBlock3 = (pSaveBlock3) 0x20300A0;
35 loadsave = (void(*)(char)) 0x8126249; // same for v1.0 + v1.1
36 mainloop = (void(*)()) 0x80003D9;
37 break;
38 case 'FVXA': // Ruby French
39 case 'FPXA': // Sapphire French
40 gSaveBlock1 = (pSaveBlock1) 0x2025734;
41 gSaveBlock2 = (pSaveBlock2) 0x2024EA4;
42 gSaveBlock3 = (pSaveBlock3) 0x20300A0;
43 loadsave = (void(*)(char)) 0x8126351; // same for v1.0 + v1.1
44 mainloop = (void(*)()) 0x80003D9;
45 break;
46 case 'IVXA': // Ruby Italian
47 case 'IPXA': // Sapphire Italian
48 gSaveBlock1 = (pSaveBlock1) 0x2025734;
49 gSaveBlock2 = (pSaveBlock2) 0x2024EA4;
50 gSaveBlock3 = (pSaveBlock3) 0x20300A0;
51 loadsave = (void(*)(char)) 0x8126249; // same for v1.0 + v1.1
52 mainloop = (void(*)()) 0x80003D9;
53 break;
54 case 'SVXA': // Ruby Spanish
55 case 'SPXA': // Sapphire Spanish
56 gSaveBlock1 = (pSaveBlock1) 0x2025734;
57 gSaveBlock2 = (pSaveBlock2) 0x2024EA4;
58 gSaveBlock3 = (pSaveBlock3) 0x20300A0;
59 loadsave = (void(*)(char)) 0x8126349; // same for v1.0 + v1.1
60 mainloop = (void(*)()) 0x80003D9;
61 break;
62 case 'EVXA': // Ruby English
63 case 'EPXA': // Sapphire English
64 gSaveBlock1 = (pSaveBlock1) 0x2025734;
65 gSaveBlock2 = (pSaveBlock2) 0x2024EA4;
66 gSaveBlock3 = (pSaveBlock3) 0x20300A0;
67 mainloop = (void(*)()) 0x80002A5;
68 switch (ROM[0xBC]) { // version number
69 case 0:
70 loadsave = (void(*)(char)) 0x8125EC9;
71 break;
72 case 1:
73 case 2:
74 loadsave = (void(*)(char)) 0x8125EE9;
75 break;
76 default:
77 return 0; // unsupported version
78 }
79 break;
80 case 'JVXA': // Ruby Japanese
81 case 'JPXA': // Sapphire Japanese
82 gSaveBlock1 = (pSaveBlock1) 0x2025494;
83 gSaveBlock2 = (pSaveBlock2) 0x2024C04;
84 gSaveBlock3 = (pSaveBlock3) 0x202FDBC;
85 loadsave = (void(*)(char)) 0x8120d05; // same for v1.0 + v1.1
86 mainloop = (void(*)()) 0x80002A9;
87 break;
88 /// --- FR/LG ---
89 // In FR/LG, the function that initialises the save-block pointers to default does not set up saveblock3.
90 // Which will need to be set up before loading the save if we want boxed Pokémon to not disappear.
91 // Oh, and loadsave() offset is different between FR and LG...
92 case 'DRPB': // FireRed German
93 case 'DGPB': // LeafGreen German
94 gSaveBlock1 = (pSaveBlock1) 0x202552C;
95 gSaveBlock2 = (pSaveBlock2) 0x2024588;
96 gSaveBlock3 = (pSaveBlock3) 0x2029314;
97 *(pSaveBlock3*)(0x3004f60) = gSaveBlock3;
98 loadsave = (void(*)(char)) ( (gamecode << 8) == 'RPB\x00' ? 0x80da721 : 0x80da6f5 );
99 mainloop = (void(*)()) 0x8000425;
100 titlemid = 0x80791df;
101 break;
102 case 'FRPB': // FireRed French
103 case 'FGPB': // LeafGreen French
104 gSaveBlock1 = (pSaveBlock1) 0x202552C;
105 gSaveBlock2 = (pSaveBlock2) 0x2024588;
106 gSaveBlock3 = (pSaveBlock3) 0x2029314;
107 *(pSaveBlock3*)(0x3004f60) = gSaveBlock3;
108 loadsave = (void(*)(char)) ( (gamecode << 8) == 'RPB\x00' ? 0x80da7e1 : 0x80da7b5 );
109 mainloop = (void(*)()) 0x8000417;
110 titlemid = 0x807929f;
111 break;
112 case 'IRPB': // FireRed Italian
113 case 'IGPB': // LeafGreen Italian
114 gSaveBlock1 = (pSaveBlock1) 0x202552C;
115 gSaveBlock2 = (pSaveBlock2) 0x2024588;
116 gSaveBlock3 = (pSaveBlock3) 0x2029314;
117 *(pSaveBlock3*)(0x3004f60) = gSaveBlock3;
118 loadsave = (void(*)(char)) ( (gamecode << 8) == 'RPB\x00' ? 0x80da721 : 0x80da6f5 );
119 mainloop = (void(*)()) 0x8000425;
120 titlemid = 0x80791cb;
21 break; 121 break;
22 case 1: 122 case 'SRPB': // FireRed Spanish
23 case 2: 123 case 'SGPB': // LeafGreen Spanish
24 loadsave = 0x8125EE9; 124 gSaveBlock1 = (pSaveBlock1) 0x202552C;
125 gSaveBlock2 = (pSaveBlock2) 0x2024588;
126 gSaveBlock3 = (pSaveBlock3) 0x2029314;
127 *(pSaveBlock3*)(0x3004f60) = gSaveBlock3;
128 loadsave = (void(*)(char)) ( (gamecode << 8) == 'RPB\x00' ? 0x80da809 : 0x80da7dd );
129 mainloop = (void(*)()) 0x8000417;
130 titlemid = 0x80792b3;
131 break;
132 case 'ERPB': // FireRed English
133 case 'EGPB': // LeafGreen English
134 gSaveBlock1 = (pSaveBlock1) 0x202552C;
135 gSaveBlock2 = (pSaveBlock2) 0x2024588;
136 gSaveBlock3 = (pSaveBlock3) 0x2029314;
137 *(pSaveBlock3*)(0x3005010) = gSaveBlock3;
138 switch (ROM[0xBC]) { // version number
139 case 0:
140 loadsave = (void(*)(char)) ( (gamecode << 8) == 'RPB\x00' ? 0x80da4fd : 0x80da4d1 );
141 mainloop = (void(*)()) 0x800041b;
142 titlemid = 0x807927b;
143 break;
144 case 1:
145 loadsave = (void(*)(char)) ( (gamecode << 8) == 'RPB\x00' ? 0x80da511 : 0x80da4e5 );
146 mainloop = (void(*)()) 0x8000429;
147 titlemid = 0x807928f;
148 break;
149 default:
150 return 0; // unsupported version
151 }
152 break;
153 case 'JRPB': // FireRed Japanese
154 case 'JGPB': // LeafGreen Japanese
155 gSaveBlock1 = (pSaveBlock1) 0x202548C;
156 gSaveBlock2 = (pSaveBlock2) 0x20244E8;
157 gSaveBlock3 = (pSaveBlock3) 0x202924C;
158 *(pSaveBlock3*)(0x3005050) = gSaveBlock3;
159 switch (ROM[0xBC]) { // version number
160 case 0:
161 loadsave = (void(*)(char)) ( (gamecode << 8) == 'RPB\x00' ? 0x80db4e5 : 0x80db4b9 );
162 mainloop = (void(*)()) 0x800041b;
163 titlemid = ( (gamecode << 8) == 'RPB\x00' ? 0x8078a0d : 0x8078a0f );
164 break;
165 case 1:
166 if ((gamecode << 8) == 'GPB\x00') {
167 // LeafGreen v1.1 Japanese is undumped.
168 // Therefore, it is unsupported.
169 // I will make guesses at the offsets in the comments, but I will not actually implement them until LeafGreen v1.1 is dumped.
170 return 0;
171 }
172 loadsave = (void(*)(char)) 0x80db529; // potential LG1.1 address: 0x80db4fd
173 mainloop = (void(*)()) 0x8000417;
174 titlemid = 0x8078987; // potential LG1.1 address: 0x8078989
175 break;
176 default:
177 return 0; // unsupported version
178 }
179 break;
180 /// --- Emerald ---
181 // In Emerald, the saveblock pointer that isn't set up is saveblock1 (in FR/LG it was saveblock3).
182 // The initial save loading code after the copyright screen is also updated, now it sets up ASLR/crypto here before loading the save.
183 case 'DEPB': // Emerald German
184 gSaveBlock1 = (pSaveBlock1) 0x2025A00;
185 gSaveBlock2 = (pSaveBlock2) 0x2024A54;
186 gSaveBlock3 = (pSaveBlock3) 0x2029808;
187 *(pSaveBlock1*)(0x3005d8c) = gSaveBlock1;
188 loadsave = (void(*)(char)) 0x8153075;
189 mainloop = (void(*)()) 0x800042b;
190 titlemid = 0x816fdb5;
191 break;
192 case 'FEPB': // Emerald French
193 gSaveBlock1 = (pSaveBlock1) 0x2025A00;
194 gSaveBlock2 = (pSaveBlock2) 0x2024A54;
195 gSaveBlock3 = (pSaveBlock3) 0x2029808;
196 *(pSaveBlock1*)(0x3005d8c) = gSaveBlock1;
197 loadsave = (void(*)(char)) 0x815319d;
198 mainloop = (void(*)()) 0x800042b;
199 titlemid = 0x816fedd;
200 break;
201 case 'IEPB': // Emerald Italian
202 gSaveBlock1 = (pSaveBlock1) 0x2025A00;
203 gSaveBlock2 = (pSaveBlock2) 0x2024A54;
204 gSaveBlock3 = (pSaveBlock3) 0x2029808;
205 *(pSaveBlock1*)(0x3005d8c) = gSaveBlock1;
206 loadsave = (void(*)(char)) 0x8153065;
207 mainloop = (void(*)()) 0x800042b;
208 titlemid = 0x816fda5;
209 break;
210 case 'SEPB': // Emerald Spanish
211 gSaveBlock1 = (pSaveBlock1) 0x2025A00;
212 gSaveBlock2 = (pSaveBlock2) 0x2024A54;
213 gSaveBlock3 = (pSaveBlock3) 0x2029808;
214 *(pSaveBlock1*)(0x3005d8c) = gSaveBlock1;
215 loadsave = (void(*)(char)) 0x8153175;
216 mainloop = (void(*)()) 0x800042b;
217 titlemid = 0x816feb5;
218 break;
219 case 'EEPB': // Emerald English
220 gSaveBlock1 = (pSaveBlock1) 0x2025A00;
221 gSaveBlock2 = (pSaveBlock2) 0x2024A54;
222 gSaveBlock3 = (pSaveBlock3) 0x2029808;
223 *(pSaveBlock1*)(0x3005d8c) = gSaveBlock1;
224 loadsave = (void(*)(char)) 0x81534d1;
225 mainloop = (void(*)()) 0x800042b;
226 titlemid = 0x817014d;
227 break;
228 case 'JEPB': // Emerald Japanese
229 gSaveBlock1 = (pSaveBlock1) 0x20256A4;
230 gSaveBlock2 = (pSaveBlock2) 0x20246F8;
231 gSaveBlock3 = (pSaveBlock3) 0x20294AC;
232 *(pSaveBlock1*)(0x3005aec) = gSaveBlock1;
233 loadsave = (void(*)(char)) 0x815340d;
234 mainloop = (void(*)()) 0x800042b;
235 titlemid = 0x816ff45;
25 break; 236 break;
26 default: 237 default:
27 return 0; //bail out 238 return 0; // this game isn't supported
28 } 239 }
29 loadsave(0); 240 loadsave(0);
30 // now the save is loaded, we can do what we want with the loaded blocks. 241 // now the save is loaded, we can do what we want with the loaded blocks.
31 // here as a small PoC, changing first letter of player name to 'z'. 242 // time to call the payload.
32 u8* gSaveBlock2 = 0x2024EA4; 243 payload(gSaveBlock1,gSaveBlock2,gSaveBlock3);
33 gSaveBlock2[0] = 0xee; // 'z' 244 // In FR/LG/Emerald, just returning to the game is unwise.
245 // The game reloads the savefile.
246 // 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)
247 // So, better bypass the title screen and get the game to return directly to the Continue/New Game screen.
248 // 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.
249 // 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.
250 // Here goes...
251 if (titlemid) {
252 // Function reserves an extra 4 bytes of stack space in FireRed, and none in Emerald.
253 call_into_middle_of_titlescreen_func(titlemid,((gamecode << 8) == 'EPB\x00' ? 0 : 4));
254 }
34 // Now we've done what we want, time to return to the game. 255 // Now we've done what we want, time to return to the game.
35 // Can't just return, the game will reload the save. 256 // Can't just return, the game will reload the save.
36 // So let's just call the main-loop directly ;) 257 // So let's just call the main-loop directly ;)
37 void(*mainloop)() = 0x80002A5;
38 // turn the sound back on before we head back to the game 258 // turn the sound back on before we head back to the game
39 *(vu16 *)(REG_BASE + 0x84) = 0x8f; 259 *(vu16 *)(REG_BASE + 0x84) = 0x8f;
260 // re-enable interrupts
261 REG_IME = 1;
40 mainloop(); 262 mainloop();
41 // Anything past here will not be executed. 263 // Anything past here will not be executed.
42 return 0; 264 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 @@
1/*
2 * Example Gen3-multiboot payload by slipstream/RoL 2017.
3 *
4 * This software may be modified and distributed under the terms
5 * of the MIT license. See the LICENSE file for details.
6 *
7 * payload.c: place where user payload should go :)
8 */
9
10#include <gba.h>
11#include "payload.h"
12
13// Your payload code should obviously go into the body of this, the payload function.
14void payload(pSaveBlock1 SaveBlock1,pSaveBlock2 SaveBlock2,pSaveBlock3 SaveBlock3) {
15 // This example payload will modify the first character of the player's name.
16 // It will change to 'z'. You can see the character encoding table here: http://bulbapedia.bulbagarden.net/wiki/Character_encoding_in_Generation_III
17 SaveBlock2[0] = 0xee; // 'z'
18} \ 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 @@
1/*
2 * Example Gen3-multiboot payload by slipstream/RoL 2017.
3 *
4 * This software may be modified and distributed under the terms
5 * of the MIT license. See the LICENSE file for details.
6 *
7 * payload.h: header file describing payload function
8 */
9
10#include "saveblocks.h"
11
12void 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 @@
1/*
2 * Example Gen3-multiboot payload by slipstream/RoL 2017.
3 *
4 * This software may be modified and distributed under the terms
5 * of the MIT license. See the LICENSE file for details.
6 *
7 * saveblocks.h: describes saveblock structures for all of Gen 3 (yay!)
8 */
9
10// TODO: this entire file. Placeholders for now, fill in later, if I can be bothered.
11// I don't really want to make a fork of pokeruby's headers for now...
12
13typedef u8 SaveBlock1, *pSaveBlock1;
14typedef u8 SaveBlock2, *pSaveBlock2;
15typedef 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:
85 ldr r1, =0x4000084 85 ldr r1, =0x4000084
86 mov r0, #0x8F 86 mov r0, #0x8F
87 strh r0, [r1] 87 strh r0, [r1]
88@; Also turn interrupts back on
89 ldr r1, =0x4000208
90 mov r0, #1
91 str r0, [r1]
88 pop {pc} 92 pop {pc}
89 93
90@--------------------------------------------------------------------------------- 94@---------------------------------------------------------------------------------