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