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