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