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