all repos — mgba @ 3f044a5791166da2f05f1e72c8d6d06b6cd2d846

mGBA Game Boy Advance Emulator

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}