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