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 if (gba->cpu->components[GBA_COMPONENT_CHEAT_DEVICE]) {
169 ARMHotplugDetach(gba->cpu, GBA_COMPONENT_CHEAT_DEVICE);
170 }
171 gba->cpu->components[GBA_COMPONENT_CHEAT_DEVICE] = &device->d;
172 ARMHotplugAttach(gba->cpu, GBA_COMPONENT_CHEAT_DEVICE);
173}
174
175void GBACheatAddSet(struct GBACheatDevice* device, struct GBACheatSet* cheats) {
176 *GBACheatSetsAppend(&device->cheats) = cheats;
177 _addBreakpoint(device, cheats);
178 _patchROM(device, cheats);
179}
180
181void GBACheatRemoveSet(struct GBACheatDevice* device, struct GBACheatSet* cheats) {
182 size_t i;
183 for (i = 0; i < GBACheatSetsSize(&device->cheats); ++i) {
184 if (*GBACheatSetsGetPointer(&device->cheats, i) == cheats) {
185 break;
186 }
187 }
188 if (i == GBACheatSetsSize(&device->cheats)) {
189 return;
190 }
191 GBACheatSetsShift(&device->cheats, i, 1);
192 _unpatchROM(device, cheats);
193 _removeBreakpoint(device, cheats);
194}
195
196bool GBACheatAddAutodetect(struct GBACheatSet* set, uint32_t op1, uint32_t op2) {
197 uint32_t o1 = op1;
198 uint32_t o2 = op2;
199 char line[18] = "XXXXXXXX XXXXXXXX";
200 snprintf(line, sizeof(line), "%08X %08X", op1, op2);
201 GBACheatRegisterLine(set, line);
202
203 switch (set->gsaVersion) {
204 case 0:
205 // Try to detect GameShark version
206 GBACheatDecryptGameShark(&o1, &o2, GBACheatGameSharkSeeds);
207 if ((o1 & 0xF0000000) == 0xF0000000 && !(o2 & 0xFFFFFCFE)) {
208 GBACheatSetGameSharkVersion(set, 1);
209 return GBACheatAddGameSharkRaw(set, o1, o2);
210 }
211 o1 = op1;
212 o2 = op2;
213 GBACheatDecryptGameShark(&o1, &o2, GBACheatProActionReplaySeeds);
214 if ((o1 & 0xFE000000) == 0xC4000000 && !(o2 & 0xFFFF0000)) {
215 GBACheatSetGameSharkVersion(set, 3);
216 return GBACheatAddProActionReplayRaw(set, o1, o2);
217 }
218 break;
219 case 1:
220 GBACheatDecryptGameShark(&o1, &o2, set->gsaSeeds);
221 return GBACheatAddGameSharkRaw(set, o1, o2);
222 case 3:
223 GBACheatDecryptGameShark(&o1, &o2, set->gsaSeeds);
224 return GBACheatAddProActionReplayRaw(set, o1, o2);
225 }
226 return false;
227}
228
229bool GBACheatAutodetectLine(struct GBACheatSet* cheats, const char* line) {
230 uint32_t op1;
231 uint32_t op2;
232 line = hex32(line, &op1);
233 if (!line) {
234 return false;
235 }
236 while (*line == ' ') {
237 ++line;
238 }
239 line = hex32(line, &op2);
240 if (!line) {
241 return false;
242 }
243 return GBACheatAddAutodetect(cheats, op1, op2);
244}
245
246bool GBACheatParseFile(struct GBACheatDevice* device, struct VFile* vf) {
247 char cheat[MAX_LINE_LENGTH];
248 struct GBACheatSet* set = 0;
249 struct GBACheatSet* newSet;
250 int gsaVersion = 0;
251 bool nextDisabled = false;
252 bool reset = false;
253 while (true) {
254 size_t i = 0;
255 ssize_t bytesRead = vf->readline(vf, cheat, sizeof(cheat));
256 if (bytesRead == 0) {
257 break;
258 }
259 if (bytesRead < 0) {
260 return false;
261 }
262 while (isspace((int) cheat[i])) {
263 ++i;
264 }
265 switch (cheat[i]) {
266 case '#':
267 do {
268 ++i;
269 } while (isspace((int) cheat[i]));
270 cheat[strlen(cheat) - 1] = '\0'; // Remove trailing newline
271 newSet = malloc(sizeof(*set));
272 GBACheatSetInit(newSet, &cheat[i]);
273 newSet->enabled = !nextDisabled;
274 nextDisabled = false;
275 if (set) {
276 GBACheatAddSet(device, set);
277 }
278 if (set && !reset) {
279 GBACheatSetCopyProperties(newSet, set);
280 } else {
281 GBACheatSetGameSharkVersion(newSet, gsaVersion);
282 }
283 reset = false;
284 set = newSet;
285 break;
286 case '!':
287 do {
288 ++i;
289 } while (isspace((int) cheat[i]));
290 if (strncasecmp(&cheat[i], "GSAv", 4) == 0 || strncasecmp(&cheat[i], "PARv", 4) == 0) {
291 i += 4;
292 gsaVersion = atoi(&cheat[i]);
293 break;
294 }
295 if (strcasecmp(&cheat[i], "disabled") == 0) {
296 nextDisabled = true;
297 break;
298 }
299 if (strcasecmp(&cheat[i], "reset") == 0) {
300 reset = true;
301 break;
302 }
303 break;
304 default:
305 if (!set) {
306 set = malloc(sizeof(*set));
307 GBACheatSetInit(set, 0);
308 set->enabled = !nextDisabled;
309 nextDisabled = false;
310 GBACheatSetGameSharkVersion(set, gsaVersion);
311 }
312 GBACheatAddLine(set, cheat);
313 break;
314 }
315 }
316 if (set) {
317 GBACheatAddSet(device, set);
318 }
319 return true;
320}
321
322bool GBACheatSaveFile(struct GBACheatDevice* device, struct VFile* vf) {
323 static const char lineStart[3] = "# ";
324 static const char lineEnd = '\n';
325
326 struct GBACheatHook* lastHook = 0;
327
328 size_t i;
329 for (i = 0; i < GBACheatSetsSize(&device->cheats); ++i) {
330 struct GBACheatSet* set = *GBACheatSetsGetPointer(&device->cheats, i);
331 if (lastHook && set->hook != lastHook) {
332 static const char* resetDirective = "!reset\n";
333 vf->write(vf, resetDirective, strlen(resetDirective));
334 }
335 switch (set->gsaVersion) {
336 case 1: {
337 static const char* versionDirective = "!GSAv1\n";
338 vf->write(vf, versionDirective, strlen(versionDirective));
339 break;
340 }
341 case 3: {
342 static const char* versionDirective = "!PARv3\n";
343 vf->write(vf, versionDirective, strlen(versionDirective));
344 break;
345 }
346 default:
347 break;
348 }
349 lastHook = set->hook;
350 if (!set->enabled) {
351 static const char* disabledDirective = "!disabled\n";
352 vf->write(vf, disabledDirective, strlen(disabledDirective));
353 }
354
355 vf->write(vf, lineStart, 2);
356 if (set->name) {
357 vf->write(vf, set->name, strlen(set->name));
358 }
359 vf->write(vf, &lineEnd, 1);
360 size_t c;
361 for (c = 0; c < StringListSize(&set->lines); ++c) {
362 const char* line = *StringListGetPointer(&set->lines, c);
363 vf->write(vf, line, strlen(line));
364 vf->write(vf, &lineEnd, 1);
365 }
366 }
367 return true;
368}
369
370bool GBACheatAddVBALine(struct GBACheatSet* cheats, const char* line) {
371 uint32_t address;
372 uint8_t op;
373 uint32_t value = 0;
374 int width = 0;
375 const char* lineNext = hex32(line, &address);
376 if (!lineNext) {
377 return false;
378 }
379 if (lineNext[0] != ':') {
380 return false;
381 }
382 ++lineNext;
383 while (width < 4) {
384 lineNext = hex8(lineNext, &op);
385 if (!lineNext) {
386 break;
387 }
388 value <<= 8;
389 value |= op;
390 ++width;
391 }
392 if (width == 0 || width == 3) {
393 return false;
394 }
395
396 struct GBACheat* cheat = GBACheatListAppend(&cheats->list);
397 cheat->address = address;
398 cheat->operandOffset = 0;
399 cheat->addressOffset = 0;
400 cheat->repeat = 1;
401 cheat->type = CHEAT_ASSIGN;
402 cheat->width = width;
403 cheat->operand = value;
404 GBACheatRegisterLine(cheats, line);
405 return true;
406}
407
408bool GBACheatAddLine(struct GBACheatSet* cheats, const char* line) {
409 uint32_t op1;
410 uint16_t op2;
411 uint16_t op3;
412 const char* lineNext = hex32(line, &op1);
413 if (!lineNext) {
414 return false;
415 }
416 if (lineNext[0] == ':') {
417 return GBACheatAddVBALine(cheats, line);
418 }
419 while (isspace((int) lineNext[0])) {
420 ++lineNext;
421 }
422 lineNext = hex16(lineNext, &op2);
423 if (!lineNext) {
424 return false;
425 }
426 if (!lineNext[0] || isspace((int) lineNext[0])) {
427 return GBACheatAddCodeBreaker(cheats, op1, op2);
428 }
429 lineNext = hex16(lineNext, &op3);
430 if (!lineNext) {
431 return false;
432 }
433 uint32_t realOp2 = op2;
434 realOp2 <<= 16;
435 realOp2 |= op3;
436 return GBACheatAddAutodetect(cheats, op1, realOp2);
437}
438
439void GBACheatRefresh(struct GBACheatDevice* device, struct GBACheatSet* cheats) {
440 if (!cheats->enabled) {
441 return;
442 }
443 bool condition = true;
444 int conditionRemaining = 0;
445 int negativeConditionRemaining = 0;
446 _patchROM(device, cheats);
447
448 size_t nCodes = GBACheatListSize(&cheats->list);
449 size_t i;
450 for (i = 0; i < nCodes; ++i) {
451 if (conditionRemaining > 0) {
452 --conditionRemaining;
453 if (!condition) {
454 continue;
455 }
456 } else if (negativeConditionRemaining > 0) {
457 conditionRemaining = negativeConditionRemaining - 1;
458 negativeConditionRemaining = 0;
459 condition = !condition;
460 if (!condition) {
461 continue;
462 }
463 } else {
464 condition = true;
465 }
466 struct GBACheat* cheat = GBACheatListGetPointer(&cheats->list, i);
467 int32_t value = 0;
468 int32_t operand = cheat->operand;
469 uint32_t operationsRemaining = cheat->repeat;
470 uint32_t address = cheat->address;
471 bool performAssignment = false;
472 for (; operationsRemaining; --operationsRemaining) {
473 switch (cheat->type) {
474 case CHEAT_ASSIGN:
475 value = operand;
476 performAssignment = true;
477 break;
478 case CHEAT_ASSIGN_INDIRECT:
479 value = operand;
480 address = _readMem(device->p->cpu, address + cheat->addressOffset, 4);
481 performAssignment = true;
482 break;
483 case CHEAT_AND:
484 value = _readMem(device->p->cpu, address, cheat->width) & operand;
485 performAssignment = true;
486 break;
487 case CHEAT_ADD:
488 value = _readMem(device->p->cpu, address, cheat->width) + operand;
489 performAssignment = true;
490 break;
491 case CHEAT_OR:
492 value = _readMem(device->p->cpu, address, cheat->width) | operand;
493 performAssignment = true;
494 break;
495 case CHEAT_IF_EQ:
496 condition = _readMem(device->p->cpu, address, cheat->width) == operand;
497 conditionRemaining = cheat->repeat;
498 negativeConditionRemaining = cheat->negativeRepeat;
499 break;
500 case CHEAT_IF_NE:
501 condition = _readMem(device->p->cpu, address, cheat->width) != operand;
502 conditionRemaining = cheat->repeat;
503 negativeConditionRemaining = cheat->negativeRepeat;
504 break;
505 case CHEAT_IF_LT:
506 condition = _readMem(device->p->cpu, address, cheat->width) < operand;
507 conditionRemaining = cheat->repeat;
508 negativeConditionRemaining = cheat->negativeRepeat;
509 break;
510 case CHEAT_IF_GT:
511 condition = _readMem(device->p->cpu, address, cheat->width) > operand;
512 conditionRemaining = cheat->repeat;
513 negativeConditionRemaining = cheat->negativeRepeat;
514 break;
515 case CHEAT_IF_ULT:
516 condition = (uint32_t) _readMem(device->p->cpu, address, cheat->width) < (uint32_t) operand;
517 conditionRemaining = cheat->repeat;
518 negativeConditionRemaining = cheat->negativeRepeat;
519 break;
520 case CHEAT_IF_UGT:
521 condition = (uint32_t) _readMem(device->p->cpu, address, cheat->width) > (uint32_t) operand;
522 conditionRemaining = cheat->repeat;
523 negativeConditionRemaining = cheat->negativeRepeat;
524 break;
525 case CHEAT_IF_AND:
526 condition = _readMem(device->p->cpu, address, cheat->width) & operand;
527 conditionRemaining = cheat->repeat;
528 negativeConditionRemaining = cheat->negativeRepeat;
529 break;
530 case CHEAT_IF_LAND:
531 condition = _readMem(device->p->cpu, address, cheat->width) && operand;
532 conditionRemaining = cheat->repeat;
533 negativeConditionRemaining = cheat->negativeRepeat;
534 break;
535 }
536
537 if (performAssignment) {
538 _writeMem(device->p->cpu, address, cheat->width, value);
539 }
540
541 address += cheat->addressOffset;
542 operand += cheat->operandOffset;
543 }
544 }
545}
546
547void GBACheatSetCopyProperties(struct GBACheatSet* newSet, struct GBACheatSet* set) {
548 newSet->gsaVersion = set->gsaVersion;
549 memcpy(newSet->gsaSeeds, set->gsaSeeds, sizeof(newSet->gsaSeeds));
550 if (set->hook) {
551 if (newSet->hook) {
552 --newSet->hook->refs;
553 if (newSet->hook->refs == 0) {
554 free(newSet->hook);
555 }
556 }
557 newSet->hook = set->hook;
558 ++newSet->hook->refs;
559 }
560}
561
562void GBACheatDeviceInit(void* cpu, struct mCPUComponent* component) {
563 struct GBACheatDevice* device = (struct GBACheatDevice*) component;
564 device->p = (struct GBA*) ((struct ARMCore*) cpu)->master;
565 size_t i;
566 for (i = 0; i < GBACheatSetsSize(&device->cheats); ++i) {
567 struct GBACheatSet* cheats = *GBACheatSetsGetPointer(&device->cheats, i);
568 _addBreakpoint(device, cheats);
569 _patchROM(device, cheats);
570 }
571}
572
573void GBACheatDeviceDeinit(struct mCPUComponent* component) {
574 struct GBACheatDevice* device = (struct GBACheatDevice*) component;
575 size_t i;
576 for (i = GBACheatSetsSize(&device->cheats); i--;) {
577 struct GBACheatSet* cheats = *GBACheatSetsGetPointer(&device->cheats, i);
578 _unpatchROM(device, cheats);
579 _removeBreakpoint(device, cheats);
580 }
581}