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 128
13
14const uint32_t M_CHEAT_DEVICE_ID = 0xABADC0DE;
15
16mLOG_DEFINE_CATEGORY(CHEATS, "Cheats", "core.cheats");
17
18DEFINE_VECTOR(mCheatList, struct mCheat);
19DEFINE_VECTOR(mCheatSets, struct mCheatSet*);
20
21static int32_t _readMem(struct mCore* core, uint32_t address, int width) {
22 switch (width) {
23 case 1:
24 return core->busRead8(core, address);
25 case 2:
26 return core->busRead16(core, address);
27 case 4:
28 return core->busRead32(core, address);
29 }
30 return 0;
31}
32
33static void _writeMem(struct mCore* core, uint32_t address, int width, int32_t value) {
34 switch (width) {
35 case 1:
36 core->busWrite8(core, address, value);
37 break;
38 case 2:
39 core->busWrite16(core, address, value);
40 break;
41 case 4:
42 core->busWrite32(core, address, value);
43 break;
44 }
45}
46
47static void mCheatDeviceInit(void*, struct mCPUComponent*);
48static void mCheatDeviceDeinit(struct mCPUComponent*);
49
50void mCheatDeviceCreate(struct mCheatDevice* device) {
51 device->d.id = M_CHEAT_DEVICE_ID;
52 device->d.init = mCheatDeviceInit;
53 device->d.deinit = mCheatDeviceDeinit;
54 device->autosave = false;
55 device->buttonDown = false;
56 mCheatSetsInit(&device->cheats, 4);
57}
58
59void mCheatDeviceDestroy(struct mCheatDevice* device) {
60 mCheatDeviceClear(device);
61 mCheatSetsDeinit(&device->cheats);
62}
63
64void mCheatDeviceClear(struct mCheatDevice* device) {
65 size_t i;
66 for (i = 0; i < mCheatSetsSize(&device->cheats); ++i) {
67 struct mCheatSet* set = *mCheatSetsGetPointer(&device->cheats, i);
68 mCheatSetDeinit(set);
69 }
70 mCheatSetsClear(&device->cheats);
71}
72
73void mCheatSetInit(struct mCheatSet* set, const char* name) {
74 mCheatListInit(&set->list, 4);
75 StringListInit(&set->lines, 4);
76 if (name) {
77 set->name = strdup(name);
78 } else {
79 set->name = 0;
80 }
81 set->enabled = true;
82}
83
84void mCheatSetDeinit(struct mCheatSet* set) {
85 mCheatListDeinit(&set->list);
86 size_t i;
87 for (i = 0; i < StringListSize(&set->lines); ++i) {
88 free(*StringListGetPointer(&set->lines, i));
89 }
90 if (set->name) {
91 free(set->name);
92 }
93 StringListDeinit(&set->lines);
94 set->deinit(set);
95 free(set);
96}
97
98void mCheatSetRename(struct mCheatSet* set, const char* name) {
99 if (set->name) {
100 free(set->name);
101 set->name = NULL;
102 }
103 if (name) {
104 set->name = strdup(name);
105 }
106}
107
108bool mCheatAddLine(struct mCheatSet* set, const char* line, int type) {
109 if (!set->addLine(set, line, type)) {
110 return false;
111 }
112 *StringListAppend(&set->lines) = strdup(line);
113 return true;
114}
115
116void mCheatAddSet(struct mCheatDevice* device, struct mCheatSet* cheats) {
117 *mCheatSetsAppend(&device->cheats) = cheats;
118 cheats->add(cheats, device);
119}
120
121void mCheatRemoveSet(struct mCheatDevice* device, struct mCheatSet* cheats) {
122 size_t i;
123 for (i = 0; i < mCheatSetsSize(&device->cheats); ++i) {
124 if (*mCheatSetsGetPointer(&device->cheats, i) == cheats) {
125 break;
126 }
127 }
128 if (i == mCheatSetsSize(&device->cheats)) {
129 return;
130 }
131 mCheatSetsShift(&device->cheats, i, 1);
132 cheats->remove(cheats, device);
133}
134
135bool mCheatParseFile(struct mCheatDevice* device, struct VFile* vf) {
136 char cheat[MAX_LINE_LENGTH];
137 struct mCheatSet* set = NULL;
138 struct mCheatSet* newSet;
139 bool nextDisabled = false;
140 struct StringList directives;
141 StringListInit(&directives, 4);
142
143 while (true) {
144 size_t i = 0;
145 ssize_t bytesRead = vf->readline(vf, cheat, sizeof(cheat));
146 rtrim(cheat);
147 if (bytesRead == 0) {
148 break;
149 }
150 if (bytesRead < 0) {
151 StringListDeinit(&directives);
152 return false;
153 }
154 while (isspace((int) cheat[i])) {
155 ++i;
156 }
157 switch (cheat[i]) {
158 case '#':
159 do {
160 ++i;
161 } while (isspace((int) cheat[i]));
162 newSet = device->createSet(device, &cheat[i]);
163 newSet->enabled = !nextDisabled;
164 nextDisabled = false;
165 if (set) {
166 mCheatAddSet(device, set);
167 }
168 if (set) {
169 newSet->copyProperties(newSet, set);
170 }
171 newSet->parseDirectives(newSet, &directives);
172 set = newSet;
173 break;
174 case '!':
175 do {
176 ++i;
177 } while (isspace((int) cheat[i]));
178 if (strcasecmp(&cheat[i], "disabled") == 0) {
179 nextDisabled = true;
180 break;
181 }
182 if (strcasecmp(&cheat[i], "reset") == 0) {
183 size_t d;
184 for (d = 0; d < StringListSize(&directives); ++d) {
185 free(*StringListGetPointer(&directives, d));
186 }
187 StringListClear(&directives);
188 break;
189 }
190 *StringListAppend(&directives) = strdup(&cheat[i]);
191 break;
192 default:
193 if (!set) {
194 set = device->createSet(device, NULL);
195 set->enabled = !nextDisabled;
196 nextDisabled = false;
197 }
198 mCheatAddLine(set, cheat, 0);
199 break;
200 }
201 }
202 if (set) {
203 mCheatAddSet(device, set);
204 }
205 size_t d;
206 for (d = 0; d < StringListSize(&directives); ++d) {
207 free(*StringListGetPointer(&directives, d));
208 }
209 StringListClear(&directives);
210 StringListDeinit(&directives);
211 return true;
212}
213
214bool mCheatSaveFile(struct mCheatDevice* device, struct VFile* vf) {
215 static const char lineStart[3] = "# ";
216 static const char lineEnd = '\n';
217 struct StringList directives;
218 StringListInit(&directives, 4);
219
220 size_t i;
221 for (i = 0; i < mCheatSetsSize(&device->cheats); ++i) {
222 struct mCheatSet* set = *mCheatSetsGetPointer(&device->cheats, i);
223 set->dumpDirectives(set, &directives);
224 if (!set->enabled) {
225 static const char* disabledDirective = "!disabled\n";
226 vf->write(vf, disabledDirective, strlen(disabledDirective));
227 }
228 size_t d;
229 for (d = 0; d < StringListSize(&directives); ++d) {
230 char directive[64];
231 ssize_t len = snprintf(directive, sizeof(directive) - 1, "!%s\n", *StringListGetPointer(&directives, d));
232 if (len > 1) {
233 vf->write(vf, directive, (size_t) len > sizeof(directive) ? sizeof(directive) : (size_t) len);
234 }
235 }
236
237 vf->write(vf, lineStart, 2);
238 if (set->name) {
239 vf->write(vf, set->name, strlen(set->name));
240 }
241 vf->write(vf, &lineEnd, 1);
242 size_t c;
243 for (c = 0; c < StringListSize(&set->lines); ++c) {
244 const char* line = *StringListGetPointer(&set->lines, c);
245 vf->write(vf, line, strlen(line));
246 vf->write(vf, &lineEnd, 1);
247 }
248 }
249 size_t d;
250 for (d = 0; d < StringListSize(&directives); ++d) {
251 free(*StringListGetPointer(&directives, d));
252 }
253 StringListClear(&directives);
254 StringListDeinit(&directives);
255 return true;
256}
257
258#if !defined(MINIMAL_CORE) || MINIMAL_CORE < 2
259void mCheatAutosave(struct mCheatDevice* device) {
260 if (!device->autosave) {
261 return;
262 }
263 struct VFile* vf = mDirectorySetOpenSuffix(&device->p->dirs, device->p->dirs.cheats, ".cheats", O_WRONLY | O_CREAT | O_TRUNC);
264 if (!vf) {
265 return;
266 }
267 mCheatSaveFile(device, vf);
268 vf->close(vf);
269}
270#endif
271
272void mCheatRefresh(struct mCheatDevice* device, struct mCheatSet* cheats) {
273 cheats->refresh(cheats, device);
274 if (!cheats->enabled) {
275 return;
276 }
277
278 size_t elseLoc = 0;
279 size_t endLoc = 0;
280 size_t nCodes = mCheatListSize(&cheats->list);
281 size_t i;
282 for (i = 0; i < nCodes; ++i) {
283 struct mCheat* cheat = mCheatListGetPointer(&cheats->list, i);
284 int32_t value = 0;
285 int32_t operand = cheat->operand;
286 uint32_t operationsRemaining = cheat->repeat;
287 uint32_t address = cheat->address;
288 bool performAssignment = false;
289 bool condition = true;
290 int conditionRemaining = 0;
291 int negativeConditionRemaining = 0;
292
293 for (; operationsRemaining; --operationsRemaining) {
294 switch (cheat->type) {
295 case CHEAT_ASSIGN:
296 value = operand;
297 performAssignment = true;
298 break;
299 case CHEAT_ASSIGN_INDIRECT:
300 value = operand;
301 address = _readMem(device->p, address + cheat->addressOffset, 4);
302 performAssignment = true;
303 break;
304 case CHEAT_AND:
305 value = _readMem(device->p, address, cheat->width) & operand;
306 performAssignment = true;
307 break;
308 case CHEAT_ADD:
309 value = _readMem(device->p, address, cheat->width) + operand;
310 performAssignment = true;
311 break;
312 case CHEAT_OR:
313 value = _readMem(device->p, address, cheat->width) | operand;
314 performAssignment = true;
315 break;
316 case CHEAT_IF_EQ:
317 condition = _readMem(device->p, address, cheat->width) == operand;
318 conditionRemaining = cheat->repeat;
319 negativeConditionRemaining = cheat->negativeRepeat;
320 operationsRemaining = 1;
321 break;
322 case CHEAT_IF_NE:
323 condition = _readMem(device->p, address, cheat->width) != operand;
324 conditionRemaining = cheat->repeat;
325 negativeConditionRemaining = cheat->negativeRepeat;
326 operationsRemaining = 1;
327 break;
328 case CHEAT_IF_LT:
329 condition = _readMem(device->p, address, cheat->width) < operand;
330 conditionRemaining = cheat->repeat;
331 negativeConditionRemaining = cheat->negativeRepeat;
332 operationsRemaining = 1;
333 break;
334 case CHEAT_IF_GT:
335 condition = _readMem(device->p, address, cheat->width) > operand;
336 conditionRemaining = cheat->repeat;
337 negativeConditionRemaining = cheat->negativeRepeat;
338 operationsRemaining = 1;
339 break;
340 case CHEAT_IF_ULT:
341 condition = (uint32_t) _readMem(device->p, address, cheat->width) < (uint32_t) operand;
342 conditionRemaining = cheat->repeat;
343 negativeConditionRemaining = cheat->negativeRepeat;
344 operationsRemaining = 1;
345 break;
346 case CHEAT_IF_UGT:
347 condition = (uint32_t) _readMem(device->p, address, cheat->width) > (uint32_t) operand;
348 conditionRemaining = cheat->repeat;
349 negativeConditionRemaining = cheat->negativeRepeat;
350 operationsRemaining = 1;
351 break;
352 case CHEAT_IF_AND:
353 condition = _readMem(device->p, address, cheat->width) & operand;
354 conditionRemaining = cheat->repeat;
355 negativeConditionRemaining = cheat->negativeRepeat;
356 operationsRemaining = 1;
357 break;
358 case CHEAT_IF_LAND:
359 condition = _readMem(device->p, address, cheat->width) && operand;
360 conditionRemaining = cheat->repeat;
361 negativeConditionRemaining = cheat->negativeRepeat;
362 operationsRemaining = 1;
363 break;
364 case CHEAT_IF_NAND:
365 condition = !(_readMem(device->p, address, cheat->width) & operand);
366 conditionRemaining = cheat->repeat;
367 negativeConditionRemaining = cheat->negativeRepeat;
368 operationsRemaining = 1;
369 break;
370 case CHEAT_IF_BUTTON:
371 condition = device->buttonDown;
372 conditionRemaining = cheat->repeat;
373 negativeConditionRemaining = cheat->negativeRepeat;
374 operationsRemaining = 1;
375 break;
376 }
377
378 if (performAssignment) {
379 _writeMem(device->p, address, cheat->width, value);
380 }
381
382 address += cheat->addressOffset;
383 operand += cheat->operandOffset;
384 }
385
386
387 if (elseLoc && i == elseLoc) {
388 i = endLoc;
389 endLoc = 0;
390 }
391 if (conditionRemaining > 0 && !condition) {
392 i += conditionRemaining;
393 } else if (negativeConditionRemaining > 0) {
394 elseLoc = i + conditionRemaining;
395 endLoc = elseLoc + negativeConditionRemaining;
396 }
397 }
398}
399
400void mCheatPressButton(struct mCheatDevice* device, bool down) {
401 device->buttonDown = down;
402}
403
404void mCheatDeviceInit(void* cpu, struct mCPUComponent* component) {
405 UNUSED(cpu);
406 struct mCheatDevice* device = (struct mCheatDevice*) component;
407 size_t i;
408 for (i = 0; i < mCheatSetsSize(&device->cheats); ++i) {
409 struct mCheatSet* cheats = *mCheatSetsGetPointer(&device->cheats, i);
410 cheats->add(cheats, device);
411 }
412}
413
414void mCheatDeviceDeinit(struct mCPUComponent* component) {
415 struct mCheatDevice* device = (struct mCheatDevice*) component;
416 size_t i;
417 for (i = mCheatSetsSize(&device->cheats); i--;) {
418 struct mCheatSet* cheats = *mCheatSetsGetPointer(&device->cheats, i);
419 cheats->remove(cheats, device);
420 }
421}