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 mCheatListDeinit(&set->list);
87 size_t i;
88 for (i = 0; i < StringListSize(&set->lines); ++i) {
89 free(*StringListGetPointer(&set->lines, i));
90 }
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 set = device->createSet(device, NULL);
202 set->enabled = !nextDisabled;
203 nextDisabled = false;
204 }
205 mCheatAddLine(set, cheat, 0);
206 break;
207 }
208 }
209 if (set) {
210 mCheatAddSet(device, set);
211 }
212 size_t d;
213 for (d = 0; d < StringListSize(&directives); ++d) {
214 free(*StringListGetPointer(&directives, d));
215 }
216 StringListClear(&directives);
217 StringListDeinit(&directives);
218 return true;
219}
220
221bool mCheatParseLibretroFile(struct mCheatDevice* device, struct VFile* vf) {
222 char cheat[MAX_LINE_LENGTH];
223 char parsed[MAX_LINE_LENGTH];
224 struct mCheatSet* set = NULL;
225 unsigned long i = 0;
226 bool startFound = false;
227
228 while (true) {
229 ssize_t bytesRead = vf->readline(vf, cheat, sizeof(cheat));
230 if (bytesRead == 0) {
231 break;
232 }
233 if (bytesRead < 0) {
234 return false;
235 }
236 if (cheat[0] == '\n') {
237 continue;
238 }
239 if (strncmp(cheat, "cheat", 5) != 0) {
240 return false;
241 }
242 char* underscore = strchr(&cheat[5], '_');
243 if (!underscore) {
244 if (!startFound && cheat[5] == 's') {
245 startFound = true;
246 char* eq = strchr(&cheat[6], '=');
247 if (!eq) {
248 return false;
249 }
250 ++eq;
251 while (isspace((int) eq[0])) {
252 if (eq[0] == '\0') {
253 return false;
254 }
255 ++eq;
256 }
257
258 char* end;
259 unsigned long nCheats = strtoul(eq, &end, 10);
260 if (end[0] != '\0' && !isspace(end[0])) {
261 return false;
262 }
263
264 if (nCheats > MAX_CHEATS) {
265 return false;
266 }
267
268 while (nCheats > mCheatSetsSize(&device->cheats)) {
269 struct mCheatSet* newSet = device->createSet(device, NULL);
270 if (!newSet) {
271 return false;
272 }
273 mCheatAddSet(device, newSet);
274 }
275 continue;
276 }
277 return false;
278 }
279 char* underscore2;
280 i = strtoul(&cheat[5], &underscore2, 10);
281 if (underscore2 != underscore) {
282 return false;
283 }
284 ++underscore;
285 char* eq = strchr(underscore, '=');
286 if (!eq) {
287 return false;
288 }
289 ++eq;
290 while (isspace((int) eq[0])) {
291 if (eq[0] == '\0') {
292 return false;
293 }
294 ++eq;
295 }
296
297 if (i >= mCheatSetsSize(&device->cheats)) {
298 return false;
299 }
300 set = *mCheatSetsGetPointer(&device->cheats, i);
301
302 if (strncmp(underscore, "desc", 4) == 0) {
303 parseQuotedString(eq, strlen(eq), parsed, sizeof(parsed));
304 mCheatSetRename(set, parsed);
305 } else if (strncmp(underscore, "enable", 6) == 0) {
306 set->enabled = strncmp(eq, "true\n", 5) == 0;
307 } else if (strncmp(underscore, "code", 4) == 0) {
308 parseQuotedString(eq, strlen(eq), parsed, sizeof(parsed));
309 char* cur = parsed;
310 char* next;
311 while ((next = strchr(cur, '+'))) {
312 next[0] = '\0';
313 mCheatAddLine(set, cur, 0);
314 cur = &next[1];
315 }
316 mCheatAddLine(set, cur, 0);
317
318 for (++i; i < mCheatSetsSize(&device->cheats); ++i) {
319 struct mCheatSet* newSet = *mCheatSetsGetPointer(&device->cheats, i);
320 newSet->copyProperties(newSet, set);
321 }
322 }
323 }
324 return true;
325}
326
327bool mCheatSaveFile(struct mCheatDevice* device, struct VFile* vf) {
328 static const char lineStart[3] = "# ";
329 static const char lineEnd = '\n';
330 struct StringList directives;
331 StringListInit(&directives, 4);
332
333 size_t i;
334 for (i = 0; i < mCheatSetsSize(&device->cheats); ++i) {
335 struct mCheatSet* set = *mCheatSetsGetPointer(&device->cheats, i);
336 set->dumpDirectives(set, &directives);
337 if (!set->enabled) {
338 static const char* disabledDirective = "!disabled\n";
339 vf->write(vf, disabledDirective, strlen(disabledDirective));
340 }
341 size_t d;
342 for (d = 0; d < StringListSize(&directives); ++d) {
343 char directive[64];
344 ssize_t len = snprintf(directive, sizeof(directive) - 1, "!%s\n", *StringListGetPointer(&directives, d));
345 if (len > 1) {
346 vf->write(vf, directive, (size_t) len > sizeof(directive) ? sizeof(directive) : (size_t) len);
347 }
348 }
349
350 vf->write(vf, lineStart, 2);
351 if (set->name) {
352 vf->write(vf, set->name, strlen(set->name));
353 }
354 vf->write(vf, &lineEnd, 1);
355 size_t c;
356 for (c = 0; c < StringListSize(&set->lines); ++c) {
357 const char* line = *StringListGetPointer(&set->lines, c);
358 vf->write(vf, line, strlen(line));
359 vf->write(vf, &lineEnd, 1);
360 }
361 }
362 size_t d;
363 for (d = 0; d < StringListSize(&directives); ++d) {
364 free(*StringListGetPointer(&directives, d));
365 }
366 StringListClear(&directives);
367 StringListDeinit(&directives);
368 return true;
369}
370
371#if !defined(MINIMAL_CORE) || MINIMAL_CORE < 2
372void mCheatAutosave(struct mCheatDevice* device) {
373 if (!device->autosave) {
374 return;
375 }
376 struct VFile* vf = mDirectorySetOpenSuffix(&device->p->dirs, device->p->dirs.cheats, ".cheats", O_WRONLY | O_CREAT | O_TRUNC);
377 if (!vf) {
378 return;
379 }
380 mCheatSaveFile(device, vf);
381 vf->close(vf);
382}
383#endif
384
385void mCheatRefresh(struct mCheatDevice* device, struct mCheatSet* cheats) {
386 cheats->refresh(cheats, device);
387 if (!cheats->enabled) {
388 return;
389 }
390
391 size_t elseLoc = 0;
392 size_t endLoc = 0;
393 size_t nCodes = mCheatListSize(&cheats->list);
394 size_t i;
395 for (i = 0; i < nCodes; ++i) {
396 struct mCheat* cheat = mCheatListGetPointer(&cheats->list, i);
397 int32_t value = 0;
398 int32_t operand = cheat->operand;
399 uint32_t operationsRemaining = cheat->repeat;
400 uint32_t address = cheat->address;
401 bool performAssignment = false;
402 bool condition = true;
403 int conditionRemaining = 0;
404 int negativeConditionRemaining = 0;
405
406 for (; operationsRemaining; --operationsRemaining) {
407 switch (cheat->type) {
408 case CHEAT_ASSIGN:
409 value = operand;
410 performAssignment = true;
411 break;
412 case CHEAT_ASSIGN_INDIRECT:
413 value = operand;
414 address = _readMem(device->p, address + cheat->addressOffset, 4);
415 performAssignment = true;
416 break;
417 case CHEAT_AND:
418 value = _readMem(device->p, address, cheat->width) & operand;
419 performAssignment = true;
420 break;
421 case CHEAT_ADD:
422 value = _readMem(device->p, address, cheat->width) + operand;
423 performAssignment = true;
424 break;
425 case CHEAT_OR:
426 value = _readMem(device->p, address, cheat->width) | operand;
427 performAssignment = true;
428 break;
429 case CHEAT_IF_EQ:
430 condition = _readMem(device->p, address, cheat->width) == operand;
431 conditionRemaining = cheat->repeat;
432 negativeConditionRemaining = cheat->negativeRepeat;
433 operationsRemaining = 1;
434 break;
435 case CHEAT_IF_NE:
436 condition = _readMem(device->p, address, cheat->width) != operand;
437 conditionRemaining = cheat->repeat;
438 negativeConditionRemaining = cheat->negativeRepeat;
439 operationsRemaining = 1;
440 break;
441 case CHEAT_IF_LT:
442 condition = _readMem(device->p, address, cheat->width) < operand;
443 conditionRemaining = cheat->repeat;
444 negativeConditionRemaining = cheat->negativeRepeat;
445 operationsRemaining = 1;
446 break;
447 case CHEAT_IF_GT:
448 condition = _readMem(device->p, address, cheat->width) > operand;
449 conditionRemaining = cheat->repeat;
450 negativeConditionRemaining = cheat->negativeRepeat;
451 operationsRemaining = 1;
452 break;
453 case CHEAT_IF_ULT:
454 condition = (uint32_t) _readMem(device->p, address, cheat->width) < (uint32_t) operand;
455 conditionRemaining = cheat->repeat;
456 negativeConditionRemaining = cheat->negativeRepeat;
457 operationsRemaining = 1;
458 break;
459 case CHEAT_IF_UGT:
460 condition = (uint32_t) _readMem(device->p, address, cheat->width) > (uint32_t) operand;
461 conditionRemaining = cheat->repeat;
462 negativeConditionRemaining = cheat->negativeRepeat;
463 operationsRemaining = 1;
464 break;
465 case CHEAT_IF_AND:
466 condition = _readMem(device->p, address, cheat->width) & operand;
467 conditionRemaining = cheat->repeat;
468 negativeConditionRemaining = cheat->negativeRepeat;
469 operationsRemaining = 1;
470 break;
471 case CHEAT_IF_LAND:
472 condition = _readMem(device->p, address, cheat->width) && operand;
473 conditionRemaining = cheat->repeat;
474 negativeConditionRemaining = cheat->negativeRepeat;
475 operationsRemaining = 1;
476 break;
477 case CHEAT_IF_NAND:
478 condition = !(_readMem(device->p, address, cheat->width) & operand);
479 conditionRemaining = cheat->repeat;
480 negativeConditionRemaining = cheat->negativeRepeat;
481 operationsRemaining = 1;
482 break;
483 case CHEAT_IF_BUTTON:
484 condition = device->buttonDown;
485 conditionRemaining = cheat->repeat;
486 negativeConditionRemaining = cheat->negativeRepeat;
487 operationsRemaining = 1;
488 break;
489 }
490
491 if (performAssignment) {
492 _writeMem(device->p, address, cheat->width, value);
493 }
494
495 address += cheat->addressOffset;
496 operand += cheat->operandOffset;
497 }
498
499
500 if (elseLoc && i == elseLoc) {
501 i = endLoc;
502 endLoc = 0;
503 }
504 if (conditionRemaining > 0 && !condition) {
505 i += conditionRemaining;
506 } else if (negativeConditionRemaining > 0) {
507 elseLoc = i + conditionRemaining;
508 endLoc = elseLoc + negativeConditionRemaining;
509 }
510 }
511}
512
513void mCheatPressButton(struct mCheatDevice* device, bool down) {
514 device->buttonDown = down;
515}
516
517void mCheatDeviceInit(void* cpu, struct mCPUComponent* component) {
518 UNUSED(cpu);
519 struct mCheatDevice* device = (struct mCheatDevice*) component;
520 size_t i;
521 for (i = 0; i < mCheatSetsSize(&device->cheats); ++i) {
522 struct mCheatSet* cheats = *mCheatSetsGetPointer(&device->cheats, i);
523 cheats->add(cheats, device);
524 }
525}
526
527void mCheatDeviceDeinit(struct mCPUComponent* component) {
528 struct mCheatDevice* device = (struct mCheatDevice*) component;
529 size_t i;
530 for (i = mCheatSetsSize(&device->cheats); i--;) {
531 struct mCheatSet* cheats = *mCheatSetsGetPointer(&device->cheats, i);
532 cheats->remove(cheats, device);
533 }
534}