diff options
Diffstat (limited to 'gba/source/main.c')
-rw-r--r-- | gba/source/main.c | 250 |
1 files changed, 236 insertions, 14 deletions
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 | |||
12 | void call_into_middle_of_titlescreen_func(u32 addr,u32 stackspace); | ||
9 | 13 | ||
10 | int main(void) { | 14 | int 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; |