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 "cheats.h"
7
8#include "gba/cheats/gameshark.h"
9#include "gba/cheats/parv3.h"
10#include "gba/gba.h"
11#include "util/string.h"
12#include "util/vfs.h"
13
14#define MAX_LINE_LENGTH 128
15
16const uint32_t GBA_CHEAT_DEVICE_ID = 0xABADC0DE;
17
18DEFINE_VECTOR(GBACheatList, struct GBACheat);
19DEFINE_VECTOR(GBACheatSets, struct GBACheatSet*);
20DEFINE_VECTOR(StringList, char*);
21
22static int32_t _readMem(struct ARMCore* cpu, uint32_t address, int width) {
23 switch (width) {
24 case 1:
25 return cpu->memory.load8(cpu, address, 0);
26 case 2:
27 return cpu->memory.load16(cpu, address, 0);
28 case 4:
29 return cpu->memory.load32(cpu, address, 0);
30 }
31 return 0;
32}
33
34static void _writeMem(struct ARMCore* cpu, uint32_t address, int width, int32_t value) {
35 switch (width) {
36 case 1:
37 cpu->memory.store8(cpu, address, value, 0);
38 break;
39 case 2:
40 cpu->memory.store16(cpu, address, value, 0);
41 break;
42 case 4:
43 cpu->memory.store32(cpu, address, value, 0);
44 break;
45 }
46}
47
48void GBACheatRegisterLine(struct GBACheatSet* cheats, const char* line) {
49 *StringListAppend(&cheats->lines) = strdup(line);
50}
51
52static void _addBreakpoint(struct GBACheatDevice* device, struct GBACheatSet* cheats) {
53 if (!device->p || !cheats->hook) {
54 return;
55 }
56 ++cheats->hook->reentries;
57 if (cheats->hook->reentries > 1) {
58 return;
59 }
60 GBASetBreakpoint(device->p, &device->d, cheats->hook->address, cheats->hook->mode, &cheats->hook->patchedOpcode);
61}
62
63static void _removeBreakpoint(struct GBACheatDevice* device, struct GBACheatSet* cheats) {
64 if (!device->p || !cheats->hook) {
65 return;
66 }
67 --cheats->hook->reentries;
68 if (cheats->hook->reentries > 0) {
69 return;
70 }
71 GBAClearBreakpoint(device->p, cheats->hook->address, cheats->hook->mode, cheats->hook->patchedOpcode);
72}
73
74static void _patchROM(struct GBACheatDevice* device, struct GBACheatSet* cheats) {
75 if (!device->p) {
76 return;
77 }
78 int i;
79 for (i = 0; i < MAX_ROM_PATCHES; ++i) {
80 if (!cheats->romPatches[i].exists || cheats->romPatches[i].applied) {
81 continue;
82 }
83 GBAPatch16(device->p->cpu, cheats->romPatches[i].address, cheats->romPatches[i].newValue, &cheats->romPatches[i].oldValue);
84 cheats->romPatches[i].applied = true;
85 }
86}
87
88static void _unpatchROM(struct GBACheatDevice* device, struct GBACheatSet* cheats) {
89 if (!device->p) {
90 return;
91 }
92 int i;
93 for (i = 0; i < MAX_ROM_PATCHES; ++i) {
94 if (!cheats->romPatches[i].exists || !cheats->romPatches[i].applied) {
95 continue;
96 }
97 GBAPatch16(device->p->cpu, cheats->romPatches[i].address, cheats->romPatches[i].oldValue, 0);
98 cheats->romPatches[i].applied = false;
99 }
100}
101
102static void GBACheatDeviceInit(struct ARMCore*, struct ARMComponent*);
103static void GBACheatDeviceDeinit(struct ARMComponent*);
104
105void GBACheatDeviceCreate(struct GBACheatDevice* device) {
106 device->d.id = GBA_CHEAT_DEVICE_ID;
107 device->d.init = GBACheatDeviceInit;
108 device->d.deinit = GBACheatDeviceDeinit;
109 GBACheatSetsInit(&device->cheats, 4);
110}
111
112void GBACheatDeviceDestroy(struct GBACheatDevice* device) {
113 size_t i;
114 for (i = 0; i < GBACheatSetsSize(&device->cheats); ++i) {
115 struct GBACheatSet* set = *GBACheatSetsGetPointer(&device->cheats, i);
116 GBACheatSetDeinit(set);
117 free(set);
118 }
119 GBACheatSetsDeinit(&device->cheats);
120}
121
122void GBACheatSetInit(struct GBACheatSet* set, const char* name) {
123 GBACheatListInit(&set->list, 4);
124 StringListInit(&set->lines, 4);
125 set->incompleteCheat = 0;
126 set->incompletePatch = 0;
127 set->currentBlock = 0;
128 set->gsaVersion = 0;
129 set->remainingAddresses = 0;
130 set->hook = 0;
131 int i;
132 for (i = 0; i < MAX_ROM_PATCHES; ++i) {
133 set->romPatches[i].exists = false;
134 }
135 if (name) {
136 set->name = strdup(name);
137 } else {
138 set->name = 0;
139 }
140 set->enabled = true;
141}
142
143void GBACheatSetDeinit(struct GBACheatSet* set) {
144 GBACheatListDeinit(&set->list);
145 size_t i;
146 for (i = 0; i < StringListSize(&set->lines); ++i) {
147 free(*StringListGetPointer(&set->lines, i));
148 }
149 if (set->name) {
150 free(set->name);
151 }
152 if (set->hook) {
153 --set->hook->refs;
154 if (set->hook->refs == 0) {
155 free(set->hook);
156 }
157 }
158}
159
160void GBACheatAttachDevice(struct GBA* gba, struct GBACheatDevice* device) {
161 if (gba->cpu->components[GBA_COMPONENT_CHEAT_DEVICE]) {
162 ARMHotplugDetach(gba->cpu, GBA_COMPONENT_CHEAT_DEVICE);
163 }
164 gba->cpu->components[GBA_COMPONENT_CHEAT_DEVICE] = &device->d;
165 ARMHotplugAttach(gba->cpu, GBA_COMPONENT_CHEAT_DEVICE);
166}
167
168void GBACheatAddSet(struct GBACheatDevice* device, struct GBACheatSet* cheats) {
169 *GBACheatSetsAppend(&device->cheats) = cheats;
170 _addBreakpoint(device, cheats);
171 _patchROM(device, cheats);
172}
173
174void GBACheatRemoveSet(struct GBACheatDevice* device, struct GBACheatSet* cheats) {
175 size_t i;
176 for (i = 0; i < GBACheatSetsSize(&device->cheats); ++i) {
177 if (*GBACheatSetsGetPointer(&device->cheats, i) == cheats) {
178 break;
179 }
180 }
181 if (i == GBACheatSetsSize(&device->cheats)) {
182 return;
183 }
184 GBACheatSetsShift(&device->cheats, i, 1);
185 _unpatchROM(device, cheats);
186 _removeBreakpoint(device, cheats);
187}
188
189bool GBACheatAddAutodetect(struct GBACheatSet* set, uint32_t op1, uint32_t op2) {
190 uint32_t o1 = op1;
191 uint32_t o2 = op2;
192 char line[18] = "XXXXXXXX XXXXXXXX";
193 snprintf(line, sizeof(line), "%08X %08X", op1, op2);
194 GBACheatRegisterLine(set, line);
195
196 switch (set->gsaVersion) {
197 case 0:
198 // Try to detect GameShark version
199 GBACheatDecryptGameShark(&o1, &o2, GBACheatGameSharkSeeds);
200 if ((o1 & 0xF0000000) == 0xF0000000 && !(o2 & 0xFFFFFCFE)) {
201 GBACheatSetGameSharkVersion(set, 1);
202 return GBACheatAddGameSharkRaw(set, o1, o2);
203 }
204 o1 = op1;
205 o2 = op2;
206 GBACheatDecryptGameShark(&o1, &o2, GBACheatProActionReplaySeeds);
207 if ((o1 & 0xFE000000) == 0xC4000000 && !(o2 & 0xFFFF0000)) {
208 GBACheatSetGameSharkVersion(set, 3);
209 return GBACheatAddProActionReplayRaw(set, o1, o2);
210 }
211 break;
212 case 1:
213 GBACheatDecryptGameShark(&o1, &o2, set->gsaSeeds);
214 return GBACheatAddGameSharkRaw(set, o1, o2);
215 case 3:
216 GBACheatDecryptGameShark(&o1, &o2, set->gsaSeeds);
217 return GBACheatAddProActionReplayRaw(set, o1, o2);
218 }
219 return false;
220}
221
222bool GBACheatAutodetectLine(struct GBACheatSet* cheats, const char* line) {
223 uint32_t op1;
224 uint32_t op2;
225 line = hex32(line, &op1);
226 if (!line) {
227 return false;
228 }
229 while (*line == ' ') {
230 ++line;
231 }
232 line = hex32(line, &op2);
233 if (!line) {
234 return false;
235 }
236 return GBACheatAddAutodetect(cheats, op1, op2);
237}
238
239bool GBACheatParseFile(struct GBACheatDevice* device, struct VFile* vf) {
240 char cheat[MAX_LINE_LENGTH];
241 struct GBACheatSet* set = 0;
242 struct GBACheatSet* newSet;
243 int gsaVersion = 0;
244 bool nextDisabled = false;
245 bool reset = false;
246 while (true) {
247 size_t i = 0;
248 ssize_t bytesRead = vf->readline(vf, cheat, sizeof(cheat));
249 if (bytesRead == 0) {
250 break;
251 }
252 if (bytesRead < 0) {
253 return false;
254 }
255 while (isspace((int) cheat[i])) {
256 ++i;
257 }
258 switch (cheat[i]) {
259 case '#':
260 do {
261 ++i;
262 } while (isspace((int) cheat[i]));
263 newSet = malloc(sizeof(*set));
264 GBACheatSetInit(newSet, &cheat[i]);
265 newSet->enabled = !nextDisabled;
266 nextDisabled = false;
267 if (set) {
268 GBACheatAddSet(device, set);
269 }
270 if (set && !reset) {
271 GBACheatSetCopyProperties(newSet, set);
272 } else {
273 GBACheatSetGameSharkVersion(newSet, gsaVersion);
274 }
275 reset = false;
276 set = newSet;
277 break;
278 case '!':
279 do {
280 ++i;
281 } while (isspace((int) cheat[i]));
282 if (strncasecmp(&cheat[i], "GSAv", 4) == 0 || strncasecmp(&cheat[i], "PARv", 4) == 0) {
283 i += 4;
284 gsaVersion = atoi(&cheat[i]);
285 break;
286 }
287 if (strcasecmp(&cheat[i], "disabled") == 0) {
288 nextDisabled = true;
289 break;
290 }
291 if (strcasecmp(&cheat[i], "reset") == 0) {
292 reset = true;
293 break;
294 }
295 break;
296 default:
297 if (!set) {
298 set = malloc(sizeof(*set));
299 GBACheatSetInit(set, 0);
300 set->enabled = !nextDisabled;
301 nextDisabled = false;
302 GBACheatSetGameSharkVersion(set, gsaVersion);
303 }
304 GBACheatAddLine(set, cheat);
305 break;
306 }
307 }
308 if (set) {
309 GBACheatAddSet(device, set);
310 }
311 return true;
312}
313
314bool GBACheatSaveFile(struct GBACheatDevice* device, struct VFile* vf) {
315 static const char lineStart[3] = "# ";
316 static const char lineEnd = '\n';
317
318 struct GBACheatHook* lastHook = 0;
319
320 size_t i;
321 for (i = 0; i < GBACheatSetsSize(&device->cheats); ++i) {
322 struct GBACheatSet* set = *GBACheatSetsGetPointer(&device->cheats, i);
323 if (lastHook && set->hook != lastHook) {
324 static const char* resetDirective = "!reset\n";
325 vf->write(vf, resetDirective, strlen(resetDirective));
326 }
327 switch (set->gsaVersion) {
328 case 1: {
329 static const char* versionDirective = "!GSAv1\n";
330 vf->write(vf, versionDirective, strlen(versionDirective));
331 break;
332 }
333 case 3: {
334 static const char* versionDirective = "!PARv3\n";
335 vf->write(vf, versionDirective, strlen(versionDirective));
336 break;
337 }
338 default:
339 break;
340 }
341 lastHook = set->hook;
342 if (!set->enabled) {
343 static const char* disabledDirective = "!disabled\n";
344 vf->write(vf, disabledDirective, strlen(disabledDirective));
345 }
346
347 vf->write(vf, lineStart, 2);
348 if (set->name) {
349 vf->write(vf, set->name, strlen(set->name));
350 }
351 vf->write(vf, &lineEnd, 1);
352 size_t c;
353 for (c = 0; c < StringListSize(&set->lines); ++c) {
354 const char* line = *StringListGetPointer(&set->lines, c);
355 vf->write(vf, line, strlen(line));
356 vf->write(vf, &lineEnd, 1);
357 }
358 }
359 return true;
360}
361
362bool GBACheatAddLine(struct GBACheatSet* cheats, const char* line) {
363 uint32_t op1;
364 uint16_t op2;
365 uint16_t op3;
366 line = hex32(line, &op1);
367 if (!line) {
368 return false;
369 }
370 while (isspace((int) line[0])) {
371 ++line;
372 }
373 line = hex16(line, &op2);
374 if (!line) {
375 return false;
376 }
377 if (!line[0] || isspace((int) line[0])) {
378 return GBACheatAddCodeBreaker(cheats, op1, op2);
379 }
380 line = hex16(line, &op3);
381 if (!line) {
382 return false;
383 }
384 uint32_t realOp2 = op2;
385 realOp2 <<= 16;
386 realOp2 |= op3;
387 return GBACheatAddAutodetect(cheats, op1, realOp2);
388}
389
390void GBACheatRefresh(struct GBACheatDevice* device, struct GBACheatSet* cheats) {
391 if (!cheats->enabled) {
392 return;
393 }
394 bool condition = true;
395 int conditionRemaining = 0;
396 int negativeConditionRemaining = 0;
397 _patchROM(device, cheats);
398
399 size_t nCodes = GBACheatListSize(&cheats->list);
400 size_t i;
401 for (i = 0; i < nCodes; ++i) {
402 if (conditionRemaining > 0) {
403 --conditionRemaining;
404 if (!condition) {
405 continue;
406 }
407 } else if (negativeConditionRemaining > 0) {
408 conditionRemaining = negativeConditionRemaining - 1;
409 negativeConditionRemaining = 0;
410 condition = !condition;
411 if (!condition) {
412 continue;
413 }
414 } else {
415 condition = true;
416 }
417 struct GBACheat* cheat = GBACheatListGetPointer(&cheats->list, i);
418 int32_t value = 0;
419 int32_t operand = cheat->operand;
420 uint32_t operationsRemaining = cheat->repeat;
421 uint32_t address = cheat->address;
422 bool performAssignment = false;
423 for (; operationsRemaining; --operationsRemaining) {
424 switch (cheat->type) {
425 case CHEAT_ASSIGN:
426 value = operand;
427 performAssignment = true;
428 break;
429 case CHEAT_ASSIGN_INDIRECT:
430 value = operand;
431 address = _readMem(device->p->cpu, address + cheat->addressOffset, 4);
432 performAssignment = true;
433 break;
434 case CHEAT_AND:
435 value = _readMem(device->p->cpu, address, cheat->width) & operand;
436 performAssignment = true;
437 break;
438 case CHEAT_ADD:
439 value = _readMem(device->p->cpu, address, cheat->width) + operand;
440 performAssignment = true;
441 break;
442 case CHEAT_OR:
443 value = _readMem(device->p->cpu, address, cheat->width) | operand;
444 performAssignment = true;
445 break;
446 case CHEAT_IF_EQ:
447 condition = _readMem(device->p->cpu, address, cheat->width) == operand;
448 conditionRemaining = cheat->repeat;
449 negativeConditionRemaining = cheat->negativeRepeat;
450 break;
451 case CHEAT_IF_NE:
452 condition = _readMem(device->p->cpu, address, cheat->width) != operand;
453 conditionRemaining = cheat->repeat;
454 negativeConditionRemaining = cheat->negativeRepeat;
455 break;
456 case CHEAT_IF_LT:
457 condition = _readMem(device->p->cpu, address, cheat->width) < operand;
458 conditionRemaining = cheat->repeat;
459 negativeConditionRemaining = cheat->negativeRepeat;
460 break;
461 case CHEAT_IF_GT:
462 condition = _readMem(device->p->cpu, address, cheat->width) > operand;
463 conditionRemaining = cheat->repeat;
464 negativeConditionRemaining = cheat->negativeRepeat;
465 break;
466 case CHEAT_IF_ULT:
467 condition = (uint32_t) _readMem(device->p->cpu, address, cheat->width) < (uint32_t) operand;
468 conditionRemaining = cheat->repeat;
469 negativeConditionRemaining = cheat->negativeRepeat;
470 break;
471 case CHEAT_IF_UGT:
472 condition = (uint32_t) _readMem(device->p->cpu, address, cheat->width) > (uint32_t) operand;
473 conditionRemaining = cheat->repeat;
474 negativeConditionRemaining = cheat->negativeRepeat;
475 break;
476 case CHEAT_IF_AND:
477 condition = _readMem(device->p->cpu, address, cheat->width) & operand;
478 conditionRemaining = cheat->repeat;
479 negativeConditionRemaining = cheat->negativeRepeat;
480 break;
481 case CHEAT_IF_LAND:
482 condition = _readMem(device->p->cpu, address, cheat->width) && operand;
483 conditionRemaining = cheat->repeat;
484 negativeConditionRemaining = cheat->negativeRepeat;
485 break;
486 }
487
488 if (performAssignment) {
489 _writeMem(device->p->cpu, address, cheat->width, value);
490 }
491
492 address += cheat->addressOffset;
493 operand += cheat->operandOffset;
494 }
495 }
496}
497
498void GBACheatSetCopyProperties(struct GBACheatSet* newSet, struct GBACheatSet* set) {
499 newSet->gsaVersion = set->gsaVersion;
500 memcpy(newSet->gsaSeeds, set->gsaSeeds, sizeof(newSet->gsaSeeds));
501 if (set->hook) {
502 if (newSet->hook) {
503 --newSet->hook->refs;
504 if (newSet->hook->refs == 0) {
505 free(newSet->hook);
506 }
507 }
508 newSet->hook = set->hook;
509 ++newSet->hook->refs;
510 }
511}
512
513void GBACheatDeviceInit(struct ARMCore* cpu, struct ARMComponent* component) {
514 struct GBACheatDevice* device = (struct GBACheatDevice*) component;
515 device->p = (struct GBA*) cpu->master;
516 size_t i;
517 for (i = 0; i < GBACheatSetsSize(&device->cheats); ++i) {
518 struct GBACheatSet* cheats = *GBACheatSetsGetPointer(&device->cheats, i);
519 _addBreakpoint(device, cheats);
520 _patchROM(device, cheats);
521 }
522}
523
524void GBACheatDeviceDeinit(struct ARMComponent* component) {
525 struct GBACheatDevice* device = (struct GBACheatDevice*) component;
526 size_t i;
527 for (i = GBACheatSetsSize(&device->cheats); i--;) {
528 struct GBACheatSet* cheats = *GBACheatSetsGetPointer(&device->cheats, i);
529 _unpatchROM(device, cheats);
530 _removeBreakpoint(device, cheats);
531 }
532}