src/core/cheats.c (view raw)
1/* Copyright (c) 2013-2016 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 <mgba/core/cheats.h>
7
8#include <mgba/core/core.h>
9#include <mgba-util/string.h>
10#include <mgba-util/vfs.h>
11
12#define MAX_LINE_LENGTH 512
13#define MAX_CHEATS 1000
14
15const uint32_t M_CHEAT_DEVICE_ID = 0xABADC0DE;
16
17mLOG_DEFINE_CATEGORY(CHEATS, "Cheats", "core.cheats");
18
19DEFINE_VECTOR(mCheatList, struct mCheat);
20DEFINE_VECTOR(mCheatSets, struct mCheatSet*);
21
22static int32_t _readMem(struct mCore* core, uint32_t address, int width) {
23 switch (width) {
24 case 1:
25 return core->busRead8(core, address);
26 case 2:
27 return core->busRead16(core, address);
28 case 4:
29 return core->busRead32(core, address);
30 }
31 return 0;
32}
33
34static void _writeMem(struct mCore* core, uint32_t address, int width, int32_t value) {
35 switch (width) {
36 case 1:
37 core->busWrite8(core, address, value);
38 break;
39 case 2:
40 core->busWrite16(core, address, value);
41 break;
42 case 4:
43 core->busWrite32(core, address, value);
44 break;
45 }
46}
47
48static void mCheatDeviceInit(void*, struct mCPUComponent*);
49static void mCheatDeviceDeinit(struct mCPUComponent*);
50
51void mCheatDeviceCreate(struct mCheatDevice* device) {
52 device->d.id = M_CHEAT_DEVICE_ID;
53 device->d.init = mCheatDeviceInit;
54 device->d.deinit = mCheatDeviceDeinit;
55 device->autosave = false;
56 device->buttonDown = false;
57 mCheatSetsInit(&device->cheats, 4);
58}
59
60void mCheatDeviceDestroy(struct mCheatDevice* device) {
61 mCheatDeviceClear(device);
62 mCheatSetsDeinit(&device->cheats);
63 free(device);
64}
65
66void mCheatDeviceClear(struct mCheatDevice* device) {
67 size_t i;
68 for (i = 0; i < mCheatSetsSize(&device->cheats); ++i) {
69 struct mCheatSet* set = *mCheatSetsGetPointer(&device->cheats, i);
70 mCheatSetDeinit(set);
71 }
72 mCheatSetsClear(&device->cheats);
73}
74
75void mCheatSetInit(struct mCheatSet* set, const char* name) {
76 mCheatListInit(&set->list, 4);
77 StringListInit(&set->lines, 4);
78 if (name) {
79 set->name = strdup(name);
80 } else {
81 set->name = 0;
82 }
83 set->enabled = true;
84}
85
86void mCheatSetDeinit(struct mCheatSet* set) {
87 size_t i;
88 for (i = 0; i < StringListSize(&set->lines); ++i) {
89 free(*StringListGetPointer(&set->lines, i));
90 }
91 mCheatListDeinit(&set->list);
92 if (set->name) {
93 free(set->name);
94 }
95 StringListDeinit(&set->lines);
96 set->deinit(set);
97 free(set);
98}
99
100void mCheatSetRename(struct mCheatSet* set, const char* name) {
101 if (set->name) {
102 free(set->name);
103 set->name = NULL;
104 }
105 if (name) {
106 set->name = strdup(name);
107 }
108}
109
110bool mCheatAddLine(struct mCheatSet* set, const char* line, int type) {
111 if (!set->addLine(set, line, type)) {
112 return false;
113 }
114 *StringListAppend(&set->lines) = strdup(line);
115 return true;
116}
117
118void mCheatAddSet(struct mCheatDevice* device, struct mCheatSet* cheats) {
119 *mCheatSetsAppend(&device->cheats) = cheats;
120 cheats->add(cheats, device);
121}
122
123void mCheatRemoveSet(struct mCheatDevice* device, struct mCheatSet* cheats) {
124 size_t i;
125 for (i = 0; i < mCheatSetsSize(&device->cheats); ++i) {
126 if (*mCheatSetsGetPointer(&device->cheats, i) == cheats) {
127 break;
128 }
129 }
130 if (i == mCheatSetsSize(&device->cheats)) {
131 return;
132 }
133 mCheatSetsShift(&device->cheats, i, 1);
134 cheats->remove(cheats, device);
135}
136
137bool mCheatParseFile(struct mCheatDevice* device, struct VFile* vf) {
138 char cheat[MAX_LINE_LENGTH];
139 struct mCheatSet* set = NULL;
140 struct mCheatSet* newSet;
141 bool nextDisabled = false;
142 struct StringList directives;
143 StringListInit(&directives, 4);
144
145 while (true) {
146 size_t i = 0;
147 ssize_t bytesRead = vf->readline(vf, cheat, sizeof(cheat));
148 rtrim(cheat);
149 if (bytesRead == 0) {
150 break;
151 }
152 if (bytesRead < 0) {
153 StringListDeinit(&directives);
154 return false;
155 }
156 while (isspace((int) cheat[i])) {
157 ++i;
158 }
159 switch (cheat[i]) {
160 case '#':
161 do {
162 ++i;
163 } while (isspace((int) cheat[i]));
164 newSet = device->createSet(device, &cheat[i]);
165 newSet->enabled = !nextDisabled;
166 nextDisabled = false;
167 if (set) {
168 mCheatAddSet(device, set);
169 }
170 if (set) {
171 newSet->copyProperties(newSet, set);
172 }
173 newSet->parseDirectives(newSet, &directives);
174 set = newSet;
175 break;
176 case '!':
177 do {
178 ++i;
179 } while (isspace((int) cheat[i]));
180 if (strcasecmp(&cheat[i], "disabled") == 0) {
181 nextDisabled = true;
182 break;
183 }
184 if (strcasecmp(&cheat[i], "reset") == 0) {
185 size_t d;
186 for (d = 0; d < StringListSize(&directives); ++d) {
187 free(*StringListGetPointer(&directives, d));
188 }
189 StringListClear(&directives);
190 break;
191 }
192 *StringListAppend(&directives) = strdup(&cheat[i]);
193 break;
194 default:
195 if (!set) {
196 if (strncmp(cheat, "cheats = ", 9) == 0) {
197 // This is in libretro format, switch over to that parser
198 vf->seek(vf, 0, SEEK_SET);
199 StringListDeinit(&directives);
200 return mCheatParseLibretroFile(device, vf);
201 }
202 if (cheat[0] == '[') {
203 // This is in EZ Flash CHT format, switch over to that parser
204 vf->seek(vf, 0, SEEK_SET);
205 StringListDeinit(&directives);
206 return mCheatParseEZFChtFile(device, vf);
207 }
208 set = device->createSet(device, NULL);
209 set->enabled = !nextDisabled;
210 nextDisabled = false;
211 }
212 mCheatAddLine(set, cheat, 0);
213 break;
214 }
215 }
216 if (set) {
217 mCheatAddSet(device, set);
218 }
219 size_t d;
220 for (d = 0; d < StringListSize(&directives); ++d) {
221 free(*StringListGetPointer(&directives, d));
222 }
223 StringListClear(&directives);
224 StringListDeinit(&directives);
225 return true;
226}
227
228bool mCheatParseLibretroFile(struct mCheatDevice* device, struct VFile* vf) {
229 char cheat[MAX_LINE_LENGTH];
230 char parsed[MAX_LINE_LENGTH];
231 struct mCheatSet* set = NULL;
232 unsigned long i = 0;
233 bool startFound = false;
234
235 while (true) {
236 ssize_t bytesRead = vf->readline(vf, cheat, sizeof(cheat));
237 if (bytesRead == 0) {
238 break;
239 }
240 if (bytesRead < 0) {
241 return false;
242 }
243 if (cheat[0] == '\n') {
244 continue;
245 }
246 if (strncmp(cheat, "cheat", 5) != 0) {
247 return false;
248 }
249 char* underscore = strchr(&cheat[5], '_');
250 if (!underscore) {
251 if (!startFound && cheat[5] == 's') {
252 startFound = true;
253 char* eq = strchr(&cheat[6], '=');
254 if (!eq) {
255 return false;
256 }
257 ++eq;
258 while (isspace((int) eq[0])) {
259 if (eq[0] == '\0') {
260 return false;
261 }
262 ++eq;
263 }
264
265 char* end;
266 unsigned long nCheats = strtoul(eq, &end, 10);
267 if (end[0] != '\0' && !isspace(end[0])) {
268 return false;
269 }
270
271 if (nCheats > MAX_CHEATS) {
272 return false;
273 }
274
275 while (nCheats > mCheatSetsSize(&device->cheats)) {
276 struct mCheatSet* newSet = device->createSet(device, NULL);
277 if (!newSet) {
278 return false;
279 }
280 mCheatAddSet(device, newSet);
281 }
282 continue;
283 }
284 return false;
285 }
286 char* underscore2;
287 i = strtoul(&cheat[5], &underscore2, 10);
288 if (underscore2 != underscore) {
289 return false;
290 }
291 ++underscore;
292 char* eq = strchr(underscore, '=');
293 if (!eq) {
294 return false;
295 }
296 ++eq;
297 while (isspace((int) eq[0])) {
298 if (eq[0] == '\0') {
299 return false;
300 }
301 ++eq;
302 }
303
304 if (i >= mCheatSetsSize(&device->cheats)) {
305 return false;
306 }
307 set = *mCheatSetsGetPointer(&device->cheats, i);
308
309 if (strncmp(underscore, "desc", 4) == 0) {
310 parseQuotedString(eq, strlen(eq), parsed, sizeof(parsed));
311 mCheatSetRename(set, parsed);
312 } else if (strncmp(underscore, "enable", 6) == 0) {
313 set->enabled = strncmp(eq, "true\n", 5) == 0;
314 } else if (strncmp(underscore, "code", 4) == 0) {
315 parseQuotedString(eq, strlen(eq), parsed, sizeof(parsed));
316 char* cur = parsed;
317 char* next;
318 while ((next = strchr(cur, '+'))) {
319 next[0] = '\0';
320 mCheatAddLine(set, cur, 0);
321 cur = &next[1];
322 }
323 mCheatAddLine(set, cur, 0);
324
325 for (++i; i < mCheatSetsSize(&device->cheats); ++i) {
326 struct mCheatSet* newSet = *mCheatSetsGetPointer(&device->cheats, i);
327 newSet->copyProperties(newSet, set);
328 }
329 }
330 }
331 return true;
332}
333
334bool mCheatParseEZFChtFile(struct mCheatDevice* device, struct VFile* vf) {
335 char cheat[MAX_LINE_LENGTH];
336 char cheatName[MAX_LINE_LENGTH];
337 char miniline[32];
338 size_t cheatNameLength = 0;
339 struct mCheatSet* set = NULL;
340
341 cheatName[MAX_LINE_LENGTH - 1] = '\0';
342 while (true) {
343 ssize_t bytesRead = vf->readline(vf, cheat, sizeof(cheat));
344 if (bytesRead == 0) {
345 break;
346 }
347 if (bytesRead < 0) {
348 return false;
349 }
350 if (cheat[0] == '\n' || (bytesRead >= 2 && cheat[0] == '\r' && cheat[1] == '\n')) {
351 continue;
352 }
353
354 if (cheat[0] == '[') {
355 if (strncmp(cheat, "[GameInfo]", 10) == 0) {
356 break;
357 }
358 char* end = strchr(cheat, ']');
359 if (!end) {
360 return false;
361 }
362 char* name = gbkToUtf8(&cheat[1], end - cheat - 1);
363 strncpy(cheatName, name, sizeof(cheatName) - 1);
364 free(name);
365 cheatNameLength = strlen(cheatName);
366 continue;
367 }
368
369 char* eq = strchr(cheat, '=');
370 if (!eq) {
371 continue;
372 }
373 if (strncmp(cheat, "ON", eq - cheat) != 0) {
374 char* subname = gbkToUtf8(cheat, eq - cheat);
375 snprintf(&cheatName[cheatNameLength], sizeof(cheatName) - cheatNameLength - 1, ": %s", subname);
376 }
377 set = device->createSet(device, cheatName);
378 set->enabled = false;
379 mCheatAddSet(device, set);
380 cheatName[cheatNameLength] = '\0';
381 ++eq;
382
383 uint32_t gameptr = 0;
384 uint32_t hexval = 0;
385 int digit;
386 while (eq[0] != '\r' && eq[1] != '\n') {
387 if (cheat + bytesRead == eq || eq[0] == '\0') {
388 bytesRead = vf->readline(vf, cheat, sizeof(cheat));
389 eq = cheat;
390 if (bytesRead == 0) {
391 break;
392 }
393 if (bytesRead < 0) {
394 return false;
395 }
396 }
397 switch (eq[0]) {
398 case ',':
399 if (!gameptr) {
400 gameptr = hexval;
401 if (hexval < 0x40000) {
402 gameptr += 0x02000000;
403 } else {
404 gameptr += 0x03000000 - 0x40000;
405 }
406 } else {
407 if (hexval > 0xFF) {
408 return false;
409 }
410 snprintf(miniline, sizeof(miniline) - 1, "%08X:%02X", gameptr, hexval);
411 mCheatAddLine(set, miniline, 0);
412 ++gameptr;
413 }
414 hexval = 0;
415 break;
416 case ';':
417 if (hexval > 0xFF) {
418 return false;
419 }
420 snprintf(miniline, sizeof(miniline) - 1, "%08X:%02X", gameptr, hexval);
421 mCheatAddLine(set, miniline, 0);
422 hexval = 0;
423 gameptr = 0;
424 break;
425 default:
426 digit = hexDigit(eq[0]);
427 if (digit < 0) {
428 return false;
429 }
430 hexval <<= 4;
431 hexval |= digit;
432 break;
433 }
434 ++eq;
435 }
436 if (gameptr) {
437 if (hexval > 0xFF) {
438 return false;
439 }
440 snprintf(miniline, sizeof(miniline) - 1, "%08X:%02X", gameptr, hexval);
441 mCheatAddLine(set, miniline, 0);
442 }
443 }
444 return true;
445}
446
447bool mCheatSaveFile(struct mCheatDevice* device, struct VFile* vf) {
448 static const char lineStart[3] = "# ";
449 static const char lineEnd = '\n';
450 struct StringList directives;
451 StringListInit(&directives, 4);
452
453 size_t i;
454 for (i = 0; i < mCheatSetsSize(&device->cheats); ++i) {
455 struct mCheatSet* set = *mCheatSetsGetPointer(&device->cheats, i);
456 set->dumpDirectives(set, &directives);
457 if (!set->enabled) {
458 static const char* disabledDirective = "!disabled\n";
459 vf->write(vf, disabledDirective, strlen(disabledDirective));
460 }
461 size_t d;
462 for (d = 0; d < StringListSize(&directives); ++d) {
463 char directive[64];
464 ssize_t len = snprintf(directive, sizeof(directive) - 1, "!%s\n", *StringListGetPointer(&directives, d));
465 if (len > 1) {
466 vf->write(vf, directive, (size_t) len > sizeof(directive) ? sizeof(directive) : (size_t) len);
467 }
468 }
469
470 vf->write(vf, lineStart, 2);
471 if (set->name) {
472 vf->write(vf, set->name, strlen(set->name));
473 }
474 vf->write(vf, &lineEnd, 1);
475 size_t c;
476 for (c = 0; c < StringListSize(&set->lines); ++c) {
477 const char* line = *StringListGetPointer(&set->lines, c);
478 vf->write(vf, line, strlen(line));
479 vf->write(vf, &lineEnd, 1);
480 }
481 }
482 size_t d;
483 for (d = 0; d < StringListSize(&directives); ++d) {
484 free(*StringListGetPointer(&directives, d));
485 }
486 StringListClear(&directives);
487 StringListDeinit(&directives);
488 return true;
489}
490
491#if !defined(MINIMAL_CORE) || MINIMAL_CORE < 2
492void mCheatAutosave(struct mCheatDevice* device) {
493 if (!device->autosave) {
494 return;
495 }
496 struct VFile* vf = mDirectorySetOpenSuffix(&device->p->dirs, device->p->dirs.cheats, ".cheats", O_WRONLY | O_CREAT | O_TRUNC);
497 if (!vf) {
498 return;
499 }
500 mCheatSaveFile(device, vf);
501 vf->close(vf);
502}
503#endif
504
505void mCheatRefresh(struct mCheatDevice* device, struct mCheatSet* cheats) {
506 cheats->refresh(cheats, device);
507 if (!cheats->enabled) {
508 return;
509 }
510
511 size_t elseLoc = 0;
512 size_t endLoc = 0;
513 size_t nCodes = mCheatListSize(&cheats->list);
514 size_t i;
515 for (i = 0; i < nCodes; ++i) {
516 struct mCheat* cheat = mCheatListGetPointer(&cheats->list, i);
517 int32_t value = 0;
518 int32_t operand = cheat->operand;
519 uint32_t operationsRemaining = cheat->repeat;
520 uint32_t address = cheat->address;
521 bool performAssignment = false;
522 bool condition = true;
523 int conditionRemaining = 0;
524 int negativeConditionRemaining = 0;
525
526 for (; operationsRemaining; --operationsRemaining) {
527 switch (cheat->type) {
528 case CHEAT_ASSIGN:
529 value = operand;
530 performAssignment = true;
531 break;
532 case CHEAT_ASSIGN_INDIRECT:
533 value = operand;
534 address = _readMem(device->p, address + cheat->addressOffset, 4);
535 performAssignment = true;
536 break;
537 case CHEAT_AND:
538 value = _readMem(device->p, address, cheat->width) & operand;
539 performAssignment = true;
540 break;
541 case CHEAT_ADD:
542 value = _readMem(device->p, address, cheat->width) + operand;
543 performAssignment = true;
544 break;
545 case CHEAT_OR:
546 value = _readMem(device->p, address, cheat->width) | operand;
547 performAssignment = true;
548 break;
549 case CHEAT_IF_EQ:
550 condition = _readMem(device->p, address, cheat->width) == operand;
551 conditionRemaining = cheat->repeat;
552 negativeConditionRemaining = cheat->negativeRepeat;
553 operationsRemaining = 1;
554 break;
555 case CHEAT_IF_NE:
556 condition = _readMem(device->p, address, cheat->width) != operand;
557 conditionRemaining = cheat->repeat;
558 negativeConditionRemaining = cheat->negativeRepeat;
559 operationsRemaining = 1;
560 break;
561 case CHEAT_IF_LT:
562 condition = _readMem(device->p, address, cheat->width) < operand;
563 conditionRemaining = cheat->repeat;
564 negativeConditionRemaining = cheat->negativeRepeat;
565 operationsRemaining = 1;
566 break;
567 case CHEAT_IF_GT:
568 condition = _readMem(device->p, address, cheat->width) > operand;
569 conditionRemaining = cheat->repeat;
570 negativeConditionRemaining = cheat->negativeRepeat;
571 operationsRemaining = 1;
572 break;
573 case CHEAT_IF_ULT:
574 condition = (uint32_t) _readMem(device->p, address, cheat->width) < (uint32_t) operand;
575 conditionRemaining = cheat->repeat;
576 negativeConditionRemaining = cheat->negativeRepeat;
577 operationsRemaining = 1;
578 break;
579 case CHEAT_IF_UGT:
580 condition = (uint32_t) _readMem(device->p, address, cheat->width) > (uint32_t) operand;
581 conditionRemaining = cheat->repeat;
582 negativeConditionRemaining = cheat->negativeRepeat;
583 operationsRemaining = 1;
584 break;
585 case CHEAT_IF_AND:
586 condition = _readMem(device->p, address, cheat->width) & operand;
587 conditionRemaining = cheat->repeat;
588 negativeConditionRemaining = cheat->negativeRepeat;
589 operationsRemaining = 1;
590 break;
591 case CHEAT_IF_LAND:
592 condition = _readMem(device->p, address, cheat->width) && operand;
593 conditionRemaining = cheat->repeat;
594 negativeConditionRemaining = cheat->negativeRepeat;
595 operationsRemaining = 1;
596 break;
597 case CHEAT_IF_NAND:
598 condition = !(_readMem(device->p, address, cheat->width) & operand);
599 conditionRemaining = cheat->repeat;
600 negativeConditionRemaining = cheat->negativeRepeat;
601 operationsRemaining = 1;
602 break;
603 case CHEAT_IF_BUTTON:
604 condition = device->buttonDown;
605 conditionRemaining = cheat->repeat;
606 negativeConditionRemaining = cheat->negativeRepeat;
607 operationsRemaining = 1;
608 break;
609 }
610
611 if (performAssignment) {
612 _writeMem(device->p, address, cheat->width, value);
613 }
614
615 address += cheat->addressOffset;
616 operand += cheat->operandOffset;
617 }
618
619
620 if (elseLoc && i == elseLoc) {
621 i = endLoc;
622 endLoc = 0;
623 }
624 if (conditionRemaining > 0 && !condition) {
625 i += conditionRemaining;
626 } else if (negativeConditionRemaining > 0) {
627 elseLoc = i + conditionRemaining;
628 endLoc = elseLoc + negativeConditionRemaining;
629 }
630 }
631}
632
633void mCheatPressButton(struct mCheatDevice* device, bool down) {
634 device->buttonDown = down;
635}
636
637void mCheatDeviceInit(void* cpu, struct mCPUComponent* component) {
638 UNUSED(cpu);
639 struct mCheatDevice* device = (struct mCheatDevice*) component;
640 size_t i;
641 for (i = 0; i < mCheatSetsSize(&device->cheats); ++i) {
642 struct mCheatSet* cheats = *mCheatSetsGetPointer(&device->cheats, i);
643 cheats->add(cheats, device);
644 }
645}
646
647void mCheatDeviceDeinit(struct mCPUComponent* component) {
648 struct mCheatDevice* device = (struct mCheatDevice*) component;
649 size_t i;
650 for (i = mCheatSetsSize(&device->cheats); i--;) {
651 struct mCheatSet* cheats = *mCheatSetsGetPointer(&device->cheats, i);
652 cheats->remove(cheats, device);
653 }
654}