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 GBACheatAddVBALine(struct GBACheatSet* cheats, const char* line) {
363 uint32_t address;
364 uint8_t op;
365 uint32_t value = 0;
366 int width = 0;
367 const char* lineNext = hex32(line, &address);
368 if (!lineNext) {
369 return false;
370 }
371 if (lineNext[0] != ':') {
372 return false;
373 }
374 ++lineNext;
375 while (width < 4) {
376 lineNext = hex8(lineNext, &op);
377 if (!lineNext) {
378 break;
379 }
380 value <<= 8;
381 value |= op;
382 ++width;
383 }
384 if (width == 0 || width == 3) {
385 return false;
386 }
387
388 struct GBACheat* cheat = GBACheatListAppend(&cheats->list);
389 cheat->address = address;
390 cheat->operandOffset = 0;
391 cheat->addressOffset = 0;
392 cheat->repeat = 1;
393 cheat->type = CHEAT_ASSIGN;
394 cheat->width = width;
395 cheat->operand = value;
396 GBACheatRegisterLine(cheats, line);
397 return true;
398}
399
400bool GBACheatAddLine(struct GBACheatSet* cheats, const char* line) {
401 uint32_t op1;
402 uint16_t op2;
403 uint16_t op3;
404 const char* lineNext = hex32(line, &op1);
405 if (!lineNext) {
406 return false;
407 }
408 if (lineNext[0] == ':') {
409 return GBACheatAddVBALine(cheats, line);
410 }
411 while (isspace((int) lineNext[0])) {
412 ++lineNext;
413 }
414 lineNext = hex16(lineNext, &op2);
415 if (!lineNext) {
416 return false;
417 }
418 if (!lineNext[0] || isspace((int) lineNext[0])) {
419 return GBACheatAddCodeBreaker(cheats, op1, op2);
420 }
421 lineNext = hex16(lineNext, &op3);
422 if (!lineNext) {
423 return false;
424 }
425 uint32_t realOp2 = op2;
426 realOp2 <<= 16;
427 realOp2 |= op3;
428 return GBACheatAddAutodetect(cheats, op1, realOp2);
429}
430
431void GBACheatRefresh(struct GBACheatDevice* device, struct GBACheatSet* cheats) {
432 if (!cheats->enabled) {
433 return;
434 }
435 bool condition = true;
436 int conditionRemaining = 0;
437 int negativeConditionRemaining = 0;
438 _patchROM(device, cheats);
439
440 size_t nCodes = GBACheatListSize(&cheats->list);
441 size_t i;
442 for (i = 0; i < nCodes; ++i) {
443 if (conditionRemaining > 0) {
444 --conditionRemaining;
445 if (!condition) {
446 continue;
447 }
448 } else if (negativeConditionRemaining > 0) {
449 conditionRemaining = negativeConditionRemaining - 1;
450 negativeConditionRemaining = 0;
451 condition = !condition;
452 if (!condition) {
453 continue;
454 }
455 } else {
456 condition = true;
457 }
458 struct GBACheat* cheat = GBACheatListGetPointer(&cheats->list, i);
459 int32_t value = 0;
460 int32_t operand = cheat->operand;
461 uint32_t operationsRemaining = cheat->repeat;
462 uint32_t address = cheat->address;
463 bool performAssignment = false;
464 for (; operationsRemaining; --operationsRemaining) {
465 switch (cheat->type) {
466 case CHEAT_ASSIGN:
467 value = operand;
468 performAssignment = true;
469 break;
470 case CHEAT_ASSIGN_INDIRECT:
471 value = operand;
472 address = _readMem(device->p->cpu, address + cheat->addressOffset, 4);
473 performAssignment = true;
474 break;
475 case CHEAT_AND:
476 value = _readMem(device->p->cpu, address, cheat->width) & operand;
477 performAssignment = true;
478 break;
479 case CHEAT_ADD:
480 value = _readMem(device->p->cpu, address, cheat->width) + operand;
481 performAssignment = true;
482 break;
483 case CHEAT_OR:
484 value = _readMem(device->p->cpu, address, cheat->width) | operand;
485 performAssignment = true;
486 break;
487 case CHEAT_IF_EQ:
488 condition = _readMem(device->p->cpu, address, cheat->width) == operand;
489 conditionRemaining = cheat->repeat;
490 negativeConditionRemaining = cheat->negativeRepeat;
491 break;
492 case CHEAT_IF_NE:
493 condition = _readMem(device->p->cpu, address, cheat->width) != operand;
494 conditionRemaining = cheat->repeat;
495 negativeConditionRemaining = cheat->negativeRepeat;
496 break;
497 case CHEAT_IF_LT:
498 condition = _readMem(device->p->cpu, address, cheat->width) < operand;
499 conditionRemaining = cheat->repeat;
500 negativeConditionRemaining = cheat->negativeRepeat;
501 break;
502 case CHEAT_IF_GT:
503 condition = _readMem(device->p->cpu, address, cheat->width) > operand;
504 conditionRemaining = cheat->repeat;
505 negativeConditionRemaining = cheat->negativeRepeat;
506 break;
507 case CHEAT_IF_ULT:
508 condition = (uint32_t) _readMem(device->p->cpu, address, cheat->width) < (uint32_t) operand;
509 conditionRemaining = cheat->repeat;
510 negativeConditionRemaining = cheat->negativeRepeat;
511 break;
512 case CHEAT_IF_UGT:
513 condition = (uint32_t) _readMem(device->p->cpu, address, cheat->width) > (uint32_t) operand;
514 conditionRemaining = cheat->repeat;
515 negativeConditionRemaining = cheat->negativeRepeat;
516 break;
517 case CHEAT_IF_AND:
518 condition = _readMem(device->p->cpu, address, cheat->width) & operand;
519 conditionRemaining = cheat->repeat;
520 negativeConditionRemaining = cheat->negativeRepeat;
521 break;
522 case CHEAT_IF_LAND:
523 condition = _readMem(device->p->cpu, address, cheat->width) && operand;
524 conditionRemaining = cheat->repeat;
525 negativeConditionRemaining = cheat->negativeRepeat;
526 break;
527 }
528
529 if (performAssignment) {
530 _writeMem(device->p->cpu, address, cheat->width, value);
531 }
532
533 address += cheat->addressOffset;
534 operand += cheat->operandOffset;
535 }
536 }
537}
538
539void GBACheatSetCopyProperties(struct GBACheatSet* newSet, struct GBACheatSet* set) {
540 newSet->gsaVersion = set->gsaVersion;
541 memcpy(newSet->gsaSeeds, set->gsaSeeds, sizeof(newSet->gsaSeeds));
542 if (set->hook) {
543 if (newSet->hook) {
544 --newSet->hook->refs;
545 if (newSet->hook->refs == 0) {
546 free(newSet->hook);
547 }
548 }
549 newSet->hook = set->hook;
550 ++newSet->hook->refs;
551 }
552}
553
554void GBACheatDeviceInit(struct ARMCore* cpu, struct ARMComponent* component) {
555 struct GBACheatDevice* device = (struct GBACheatDevice*) component;
556 device->p = (struct GBA*) cpu->master;
557 size_t i;
558 for (i = 0; i < GBACheatSetsSize(&device->cheats); ++i) {
559 struct GBACheatSet* cheats = *GBACheatSetsGetPointer(&device->cheats, i);
560 _addBreakpoint(device, cheats);
561 _patchROM(device, cheats);
562 }
563}
564
565void GBACheatDeviceDeinit(struct ARMComponent* component) {
566 struct GBACheatDevice* device = (struct GBACheatDevice*) component;
567 size_t i;
568 for (i = GBACheatSetsSize(&device->cheats); i--;) {
569 struct GBACheatSet* cheats = *GBACheatSetsGetPointer(&device->cheats, i);
570 _unpatchROM(device, cheats);
571 _removeBreakpoint(device, cheats);
572 }
573}