src/gba/cheats.c (view raw)
1/* Copyright (c) 2013-2015 Jeffrey Pfau
2 *
3 * This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6#include <mgba/internal/gba/cheats.h>
7
8#include <mgba/core/core.h>
9#include <mgba/internal/gba/gba.h>
10#include <mgba-util/string.h>
11#include "gba/cheats/gameshark.h"
12#include "gba/cheats/parv3.h"
13
14#define MAX_LINE_LENGTH 128
15
16DEFINE_VECTOR(GBACheatPatchList, struct GBACheatPatch);
17
18static void _addBreakpoint(struct mCheatDevice* device, struct GBACheatSet* cheats) {
19 if (!device->p || !cheats->hook) {
20 return;
21 }
22 ++cheats->hook->reentries;
23 if (cheats->hook->reentries > 1) {
24 return;
25 }
26 GBASetBreakpoint(device->p->board, &device->d, cheats->hook->address, cheats->hook->mode, &cheats->hook->patchedOpcode);
27}
28
29static void _removeBreakpoint(struct mCheatDevice* device, struct GBACheatSet* cheats) {
30 if (!device->p || !cheats->hook) {
31 return;
32 }
33 --cheats->hook->reentries;
34 if (cheats->hook->reentries > 0) {
35 return;
36 }
37 GBAClearBreakpoint(device->p->board, cheats->hook->address, cheats->hook->mode, cheats->hook->patchedOpcode);
38}
39
40static void _patchROM(struct mCheatDevice* device, struct GBACheatSet* cheats) {
41 if (!device->p) {
42 return;
43 }
44 size_t i;
45 for (i = 0; i < GBACheatPatchListSize(&cheats->romPatches); ++i) {
46 struct GBACheatPatch* patch = GBACheatPatchListGetPointer(&cheats->romPatches, i);
47 if (patch->applied) {
48 continue;
49 }
50 GBAPatch16(device->p->cpu, patch->address, patch->newValue, &patch->oldValue);
51 patch->applied = true;
52 }
53}
54
55static void _unpatchROM(struct mCheatDevice* device, struct GBACheatSet* cheats) {
56 if (!device->p) {
57 return;
58 }
59 size_t i;
60 for (i = 0; i < GBACheatPatchListSize(&cheats->romPatches); ++i) {
61 struct GBACheatPatch* patch = GBACheatPatchListGetPointer(&cheats->romPatches, i);
62 if (!patch->applied) {
63 continue;
64 }
65 GBAPatch16(device->p->cpu, patch->address, patch->oldValue, NULL);
66 patch->applied = false;
67 }
68}
69
70static void GBACheatSetDeinit(struct mCheatSet* set);
71static void GBACheatAddSet(struct mCheatSet* cheats, struct mCheatDevice* device);
72static void GBACheatRemoveSet(struct mCheatSet* cheats, struct mCheatDevice* device);
73static void GBACheatRefresh(struct mCheatSet* cheats, struct mCheatDevice* device);
74static void GBACheatSetCopyProperties(struct mCheatSet* set, struct mCheatSet* oldSet);
75static void GBACheatParseDirectives(struct mCheatSet*, const struct StringList* directives);
76static void GBACheatDumpDirectives(struct mCheatSet*, struct StringList* directives);
77static bool GBACheatAddLine(struct mCheatSet*, const char* line, int type);
78
79static struct mCheatSet* GBACheatSetCreate(struct mCheatDevice* device, const char* name) {
80 UNUSED(device);
81 struct GBACheatSet* set = malloc(sizeof(*set));
82 mCheatSetInit(&set->d, name);
83 set->incompleteCheat = -1;
84 set->incompletePatch = 0;
85 set->currentBlock = -1;
86 set->gsaVersion = 0;
87 set->cbRngState = 0;
88 set->cbMaster = 0;
89 set->remainingAddresses = 0;
90 set->hook = NULL;
91
92 set->d.deinit = GBACheatSetDeinit;
93 set->d.add = GBACheatAddSet;
94 set->d.remove = GBACheatRemoveSet;
95
96 set->d.addLine = GBACheatAddLine;
97 set->d.copyProperties = GBACheatSetCopyProperties;
98
99 set->d.parseDirectives = GBACheatParseDirectives;
100 set->d.dumpDirectives = GBACheatDumpDirectives;
101
102 set->d.refresh = GBACheatRefresh;
103
104 GBACheatPatchListInit(&set->romPatches, 4);
105 return &set->d;
106}
107
108struct mCheatDevice* GBACheatDeviceCreate(void) {
109 struct mCheatDevice* device = malloc(sizeof(*device));
110 mCheatDeviceCreate(device);
111 device->createSet = GBACheatSetCreate;
112 return device;
113}
114
115static void GBACheatSetDeinit(struct mCheatSet* set) {
116 struct GBACheatSet* gbaset = (struct GBACheatSet*) set;
117 if (gbaset->hook) {
118 --gbaset->hook->refs;
119 if (gbaset->hook->refs == 0) {
120 free(gbaset->hook);
121 }
122 }
123 GBACheatPatchListDeinit(&gbaset->romPatches);
124}
125
126static void GBACheatAddSet(struct mCheatSet* cheats, struct mCheatDevice* device) {
127 struct GBACheatSet* gbaset = (struct GBACheatSet*) cheats;
128 _addBreakpoint(device, gbaset);
129 _patchROM(device, gbaset);
130}
131
132static void GBACheatRemoveSet(struct mCheatSet* cheats, struct mCheatDevice* device) {
133 struct GBACheatSet* gbaset = (struct GBACheatSet*) cheats;
134 _unpatchROM(device, gbaset);
135 _removeBreakpoint(device, gbaset);
136}
137
138static bool GBACheatAddAutodetect(struct GBACheatSet* set, uint32_t op1, uint32_t op2) {
139 uint32_t o1 = op1;
140 uint32_t o2 = op2;
141 char line[18] = "XXXXXXXX XXXXXXXX";
142 snprintf(line, sizeof(line), "%08X %08X", op1, op2);
143
144 int gsaP, rgsaP, parP, rparP;
145 int maxProbability = INT_MIN;
146 switch (set->gsaVersion) {
147 case 0:
148 // Try to detect GameShark version
149 GBACheatDecryptGameShark(&o1, &o2, GBACheatGameSharkSeeds);
150 gsaP = GBACheatGameSharkProbability(o1, o2);
151 o1 = op1;
152 o2 = op2;
153 if (gsaP > maxProbability) {
154 maxProbability = gsaP;
155 GBACheatSetGameSharkVersion(set, GBA_GS_GSAV1);
156 }
157
158 GBACheatDecryptGameShark(&o1, &o2, GBACheatProActionReplaySeeds);
159 parP = GBACheatProActionReplayProbability(o1, o2);
160 if (parP > maxProbability) {
161 maxProbability = parP;
162 GBACheatSetGameSharkVersion(set, GBA_GS_PARV3);
163 }
164
165 rgsaP = GBACheatGameSharkProbability(op1, op1);
166 if (rgsaP > maxProbability) {
167 maxProbability = rgsaP;
168 GBACheatSetGameSharkVersion(set, GBA_GS_GSAV1_RAW);
169 }
170
171 rparP = GBACheatProActionReplayProbability(op1, op1);
172 if (rparP > maxProbability) {
173 maxProbability = rparP;
174 GBACheatSetGameSharkVersion(set, GBA_GS_PARV3_RAW);
175 }
176
177 if (set->gsaVersion < 3) {
178 return GBACheatAddGameShark(set, op1, op2);
179 } else {
180 return GBACheatAddProActionReplay(set, op1, op2);
181 }
182 break;
183 case 1:
184 case 2:
185 return GBACheatAddGameShark(set, o1, o2);
186 case 3:
187 case 4:
188 return GBACheatAddProActionReplay(set, o1, o2);
189 }
190 return false;
191}
192
193bool GBACheatAddVBALine(struct GBACheatSet* cheats, const char* line) {
194 uint32_t address;
195 uint8_t op;
196 uint32_t value = 0;
197 int width = 0;
198 const char* lineNext = hex32(line, &address);
199 if (!lineNext) {
200 return false;
201 }
202 if (lineNext[0] != ':') {
203 return false;
204 }
205 ++lineNext;
206 while (width < 4) {
207 lineNext = hex8(lineNext, &op);
208 if (!lineNext) {
209 break;
210 }
211 value <<= 8;
212 value |= op;
213 ++width;
214 }
215 if (width == 0 || width == 3) {
216 return false;
217 }
218
219 struct mCheat* cheat = mCheatListAppend(&cheats->d.list);
220 cheat->address = address;
221 cheat->operandOffset = 0;
222 cheat->addressOffset = 0;
223 cheat->repeat = 1;
224 cheat->type = CHEAT_ASSIGN;
225 cheat->width = width;
226 cheat->operand = value;
227 return true;
228}
229
230bool GBACheatAddLine(struct mCheatSet* set, const char* line, int type) {
231 struct GBACheatSet* cheats = (struct GBACheatSet*) set;
232 switch (type) {
233 case GBA_CHEAT_AUTODETECT:
234 break;
235 case GBA_CHEAT_CODEBREAKER:
236 return GBACheatAddCodeBreakerLine(cheats, line);
237 case GBA_CHEAT_GAMESHARK:
238 return GBACheatAddGameSharkLine(cheats, line);
239 case GBA_CHEAT_PRO_ACTION_REPLAY:
240 return GBACheatAddProActionReplayLine(cheats, line);
241 case GBA_CHEAT_VBA:
242 return GBACheatAddVBALine(cheats, line);
243 default:
244 return false;
245 }
246
247 uint32_t op1;
248 uint16_t op2;
249 uint16_t op3;
250 const char* lineNext = hex32(line, &op1);
251 if (!lineNext) {
252 return false;
253 }
254 if (lineNext[0] == ':') {
255 return GBACheatAddVBALine(cheats, line);
256 }
257 while (isspace((int) lineNext[0])) {
258 ++lineNext;
259 }
260 lineNext = hex16(lineNext, &op2);
261 if (!lineNext) {
262 return false;
263 }
264 if (!lineNext[0] || isspace((int) lineNext[0])) {
265 return GBACheatAddCodeBreaker(cheats, op1, op2);
266 }
267 lineNext = hex16(lineNext, &op3);
268 if (!lineNext) {
269 return false;
270 }
271 uint32_t realOp2 = op2;
272 realOp2 <<= 16;
273 realOp2 |= op3;
274 return GBACheatAddAutodetect(cheats, op1, realOp2);
275}
276
277static void GBACheatRefresh(struct mCheatSet* cheats, struct mCheatDevice* device) {
278 struct GBACheatSet* gbaset = (struct GBACheatSet*) cheats;
279 if (cheats->enabled) {
280 _patchROM(device, gbaset);
281 if (gbaset->hook && !gbaset->hook->reentries) {
282 _addBreakpoint(device, gbaset);
283 }
284 } else {
285 _unpatchROM(device, gbaset);
286 }
287}
288
289static void GBACheatSetCopyProperties(struct mCheatSet* set, struct mCheatSet* oldSet) {
290 struct GBACheatSet* newSet = (struct GBACheatSet*) set;
291 struct GBACheatSet* gbaset = (struct GBACheatSet*) oldSet;
292 newSet->gsaVersion = gbaset->gsaVersion;
293 memcpy(newSet->gsaSeeds, gbaset->gsaSeeds, sizeof(newSet->gsaSeeds));
294 newSet->cbRngState = gbaset->cbRngState;
295 newSet->cbMaster = gbaset->cbMaster;
296 memcpy(newSet->cbSeeds, gbaset->cbSeeds, sizeof(newSet->cbSeeds));
297 memcpy(newSet->cbTable, gbaset->cbTable, sizeof(newSet->cbTable));
298 if (gbaset->hook) {
299 if (newSet->hook) {
300 --newSet->hook->refs;
301 if (newSet->hook->refs == 0) {
302 free(newSet->hook);
303 }
304 }
305 newSet->hook = gbaset->hook;
306 ++newSet->hook->refs;
307 }
308}
309
310static void GBACheatParseDirectives(struct mCheatSet* set, const struct StringList* directives) {
311 struct GBACheatSet* cheats = (struct GBACheatSet*) set;
312 size_t d;
313 for (d = 0; d < StringListSize(directives); ++d) {
314 const char* directive = *StringListGetConstPointer(directives, d);
315 if (strcmp(directive, "GSAv1") == 0) {
316 GBACheatSetGameSharkVersion(cheats, GBA_GS_GSAV1);
317 continue;
318 }
319 if (strcmp(directive, "GSAv1 raw") == 0) {
320 GBACheatSetGameSharkVersion(cheats, GBA_GS_GSAV1_RAW);
321 continue;
322 }
323 if (strcmp(directive, "PARv3") == 0) {
324 GBACheatSetGameSharkVersion(cheats, GBA_GS_PARV3);
325 continue;
326 }
327 if (strcmp(directive, "PARv3 raw") == 0) {
328 GBACheatSetGameSharkVersion(cheats, GBA_GS_PARV3_RAW);
329 continue;
330 }
331 }
332}
333
334static void GBACheatDumpDirectives(struct mCheatSet* set, struct StringList* directives) {
335 struct GBACheatSet* cheats = (struct GBACheatSet*) set;
336
337 // TODO: Check previous directives
338 size_t d;
339 for (d = 0; d < StringListSize(directives); ++d) {
340 free(*StringListGetPointer(directives, d));
341 }
342 StringListClear(directives);
343
344 char** directive;
345 switch (cheats->gsaVersion) {
346 case 1:
347 directive = StringListAppend(directives);
348 *directive = strdup("GSAv1");
349 break;
350 case 2:
351 directive = StringListAppend(directives);
352 *directive = strdup("GSAv1 raw");
353 break;
354 case 3:
355 directive = StringListAppend(directives);
356 *directive = strdup("PARv3");
357 break;
358 case 4:
359 directive = StringListAppend(directives);
360 *directive = strdup("PARv3 raw");
361 break;
362 }
363}
364
365int GBACheatAddressIsReal(uint32_t address) {
366 switch (address >> BASE_OFFSET) {
367 case REGION_BIOS:
368 return -0x80;
369 break;
370 case REGION_WORKING_RAM:
371 if ((address & OFFSET_MASK) > SIZE_WORKING_RAM) {
372 return -0x40;
373 }
374 return 0x20;
375 case REGION_WORKING_IRAM:
376 if ((address & OFFSET_MASK) > SIZE_WORKING_IRAM) {
377 return -0x40;
378 }
379 return 0x20;
380 case REGION_IO:
381 if ((address & OFFSET_MASK) > SIZE_IO) {
382 return -0x80;
383 }
384 return 0x10;
385 case REGION_OAM:
386 if ((address & OFFSET_MASK) > SIZE_OAM) {
387 return -0x80;
388 }
389 return -0x8;
390 case REGION_VRAM:
391 if ((address & OFFSET_MASK) > SIZE_VRAM) {
392 return -0x80;
393 }
394 return -0x8;
395 case REGION_PALETTE_RAM:
396 if ((address & OFFSET_MASK) > SIZE_PALETTE_RAM) {
397 return -0x80;
398 }
399 return -0x8;
400 case REGION_CART0:
401 case REGION_CART0_EX:
402 case REGION_CART1:
403 case REGION_CART1_EX:
404 case REGION_CART2:
405 case REGION_CART2_EX:
406 return -0x8;
407 case REGION_CART_SRAM:
408 case REGION_CART_SRAM_MIRROR:
409 if ((address & OFFSET_MASK) > SIZE_CART_FLASH512) {
410 return -0x80;
411 }
412 return -0x8;
413 default:
414 return -0xC0;
415 }
416}