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