all repos — mgba @ a59ffbc9a698ceda535dcff7f5cfb9004a6c2ae8

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	free(device);
 64}
 65
 66void mCheatDeviceClear(struct mCheatDevice* device) {
 67	size_t i;
 68	for (i = 0; i < mCheatSetsSize(&device->cheats); ++i) {
 69		struct mCheatSet* set = *mCheatSetsGetPointer(&device->cheats, i);
 70		mCheatSetDeinit(set);
 71	}
 72	mCheatSetsClear(&device->cheats);
 73}
 74
 75void mCheatSetInit(struct mCheatSet* set, const char* name) {
 76	mCheatListInit(&set->list, 4);
 77	StringListInit(&set->lines, 4);
 78	if (name) {
 79		set->name = strdup(name);
 80	} else {
 81		set->name = 0;
 82	}
 83	set->enabled = true;
 84}
 85
 86void mCheatSetDeinit(struct mCheatSet* set) {
 87	size_t i;
 88	for (i = 0; i < StringListSize(&set->lines); ++i) {
 89		free(*StringListGetPointer(&set->lines, i));
 90	}
 91	mCheatListDeinit(&set->list);
 92	if (set->name) {
 93		free(set->name);
 94	}
 95	StringListDeinit(&set->lines);
 96	set->deinit(set);
 97	free(set);
 98}
 99
100void mCheatSetRename(struct mCheatSet* set, const char* name) {
101	if (set->name) {
102		free(set->name);
103		set->name = NULL;
104	}
105	if (name) {
106		set->name = strdup(name);
107	}
108}
109
110bool mCheatAddLine(struct mCheatSet* set, const char* line, int type) {
111	if (!set->addLine(set, line, type)) {
112		return false;
113	}
114	*StringListAppend(&set->lines) = strdup(line);
115	return true;
116}
117
118void mCheatAddSet(struct mCheatDevice* device, struct mCheatSet* cheats) {
119	*mCheatSetsAppend(&device->cheats) = cheats;
120	cheats->add(cheats, device);
121}
122
123void mCheatRemoveSet(struct mCheatDevice* device, struct mCheatSet* cheats) {
124	size_t i;
125	for (i = 0; i < mCheatSetsSize(&device->cheats); ++i) {
126		if (*mCheatSetsGetPointer(&device->cheats, i) == cheats) {
127			break;
128		}
129	}
130	if (i == mCheatSetsSize(&device->cheats)) {
131		return;
132	}
133	mCheatSetsShift(&device->cheats, i, 1);
134	cheats->remove(cheats, device);
135}
136
137bool mCheatParseFile(struct mCheatDevice* device, struct VFile* vf) {
138	char cheat[MAX_LINE_LENGTH];
139	struct mCheatSet* set = NULL;
140	struct mCheatSet* newSet;
141	bool nextDisabled = false;
142	struct StringList directives;
143	StringListInit(&directives, 4);
144
145	while (true) {
146		size_t i = 0;
147		ssize_t bytesRead = vf->readline(vf, cheat, sizeof(cheat));
148		rtrim(cheat);
149		if (bytesRead == 0) {
150			break;
151		}
152		if (bytesRead < 0) {
153			StringListDeinit(&directives);
154			return false;
155		}
156		while (isspace((int) cheat[i])) {
157			++i;
158		}
159		switch (cheat[i]) {
160		case '#':
161			do {
162				++i;
163			} while (isspace((int) cheat[i]));
164			newSet = device->createSet(device, &cheat[i]);
165			newSet->enabled = !nextDisabled;
166			nextDisabled = false;
167			if (set) {
168				mCheatAddSet(device, set);
169			}
170			if (set) {
171				newSet->copyProperties(newSet, set);
172			}
173			newSet->parseDirectives(newSet, &directives);
174			set = newSet;
175			break;
176		case '!':
177			do {
178				++i;
179			} while (isspace((int) cheat[i]));
180			if (strcasecmp(&cheat[i], "disabled") == 0) {
181				nextDisabled = true;
182				break;
183			}
184			if (strcasecmp(&cheat[i], "reset") == 0) {
185				size_t d;
186				for (d = 0; d < StringListSize(&directives); ++d) {
187					free(*StringListGetPointer(&directives, d));
188				}
189				StringListClear(&directives);
190				break;
191			}
192			*StringListAppend(&directives) = strdup(&cheat[i]);
193			break;
194		default:
195			if (!set) {
196				if (strncmp(cheat, "cheats = ", 9) == 0) {
197					// This is in libretro format, switch over to that parser
198					vf->seek(vf, 0, SEEK_SET);
199					StringListDeinit(&directives);
200					return mCheatParseLibretroFile(device, vf);
201				}
202				if (cheat[0] == '[') {
203					// This is in EZ Flash CHT format, switch over to that parser
204					vf->seek(vf, 0, SEEK_SET);
205					StringListDeinit(&directives);
206					return mCheatParseEZFChtFile(device, vf);
207				}
208				set = device->createSet(device, NULL);
209				set->enabled = !nextDisabled;
210				nextDisabled = false;
211			}
212			mCheatAddLine(set, cheat, 0);
213			break;
214		}
215	}
216	if (set) {
217		mCheatAddSet(device, set);
218	}
219	size_t d;
220	for (d = 0; d < StringListSize(&directives); ++d) {
221		free(*StringListGetPointer(&directives, d));
222	}
223	StringListClear(&directives);
224	StringListDeinit(&directives);
225	return true;
226}
227
228bool mCheatParseLibretroFile(struct mCheatDevice* device, struct VFile* vf) {
229	char cheat[MAX_LINE_LENGTH];
230	char parsed[MAX_LINE_LENGTH];
231	struct mCheatSet* set = NULL;
232	unsigned long i = 0;
233	bool startFound = false;
234
235	while (true) {
236		ssize_t bytesRead = vf->readline(vf, cheat, sizeof(cheat));
237		if (bytesRead == 0) {
238			break;
239		}
240		if (bytesRead < 0) {
241			return false;
242		}
243		if (cheat[0] == '\n') {
244			continue;
245		}
246		if (strncmp(cheat, "cheat", 5) != 0) {
247			return false;
248		}
249		char* underscore = strchr(&cheat[5], '_');
250		if (!underscore) {
251			if (!startFound && cheat[5] == 's') {
252				startFound = true;
253				char* eq = strchr(&cheat[6], '=');
254				if (!eq) {
255					return false;
256				}
257				++eq;
258				while (isspace((int) eq[0])) {
259					if (eq[0] == '\0') {
260						return false;
261					}
262					++eq;
263				}
264
265				char* end;
266				unsigned long nCheats = strtoul(eq, &end, 10);
267				if (end[0] != '\0' && !isspace(end[0])) {
268					return false;
269				}
270
271				if (nCheats > MAX_CHEATS) {
272					return false;
273				}
274
275				while (nCheats > mCheatSetsSize(&device->cheats)) {
276					struct mCheatSet* newSet = device->createSet(device, NULL);
277					if (!newSet) {
278						return false;
279					}
280					mCheatAddSet(device, newSet);
281				}
282				continue;
283			}
284			return false;
285		}
286		char* underscore2;
287		i = strtoul(&cheat[5], &underscore2, 10);
288		if (underscore2 != underscore) {
289			return false;
290		}
291		++underscore;
292		char* eq = strchr(underscore, '=');
293		if (!eq) {
294			return false;
295		}
296		++eq;
297		while (isspace((int) eq[0])) {
298			if (eq[0] == '\0') {
299				return false;
300			}
301			++eq;
302		}
303
304		if (i >= mCheatSetsSize(&device->cheats)) {
305			return false;
306		}
307		set = *mCheatSetsGetPointer(&device->cheats, i);
308
309		if (strncmp(underscore, "desc", 4) == 0) {
310			parseQuotedString(eq, strlen(eq), parsed, sizeof(parsed));
311			mCheatSetRename(set, parsed);
312		} else if (strncmp(underscore, "enable", 6) == 0) {
313			set->enabled = strncmp(eq, "true\n", 5) == 0;
314		} else if (strncmp(underscore, "code", 4) == 0) {
315			parseQuotedString(eq, strlen(eq), parsed, sizeof(parsed));
316			char* cur = parsed;
317			char* next;
318			while ((next = strchr(cur, '+'))) {
319				next[0] = '\0';
320				mCheatAddLine(set, cur, 0);
321				cur = &next[1];
322			}
323			mCheatAddLine(set, cur, 0);
324
325			for (++i; i < mCheatSetsSize(&device->cheats); ++i) {
326				struct mCheatSet* newSet = *mCheatSetsGetPointer(&device->cheats, i);
327				newSet->copyProperties(newSet, set);
328			}
329		}
330	}
331	return true;
332}
333
334bool mCheatParseEZFChtFile(struct mCheatDevice* device, struct VFile* vf) {
335	char cheat[MAX_LINE_LENGTH];
336	char cheatName[MAX_LINE_LENGTH];
337	char miniline[32];
338	size_t cheatNameLength = 0;
339	struct mCheatSet* set = NULL;
340
341	cheatName[MAX_LINE_LENGTH - 1] = '\0';
342	while (true) {
343		ssize_t bytesRead = vf->readline(vf, cheat, sizeof(cheat));
344		if (bytesRead == 0) {
345			break;
346		}
347		if (bytesRead < 0) {
348			return false;
349		}
350		if (cheat[0] == '\n' || (bytesRead >= 2 && cheat[0] == '\r' && cheat[1] == '\n')) {
351			continue;
352		}
353
354		if (cheat[0] == '[') {
355			if (strncmp(cheat, "[GameInfo]", 10) == 0) {
356				break;
357			}
358			char* end = strchr(cheat, ']');
359			if (!end) {
360				return false;
361			}
362			char* name = gbkToUtf8(&cheat[1], end - cheat - 1);
363			strncpy(cheatName, name, sizeof(cheatName) - 1);
364			free(name);
365			cheatNameLength = strlen(cheatName);
366			continue;
367		}
368
369		char* eq = strchr(cheat, '=');
370		if (!eq) {
371			continue;
372		}
373		if (strncmp(cheat, "ON", eq - cheat) != 0) {
374			char* subname = gbkToUtf8(cheat, eq - cheat);
375			snprintf(&cheatName[cheatNameLength], sizeof(cheatName) - cheatNameLength - 1, ": %s", subname);
376		}
377		set = device->createSet(device, cheatName);
378		set->enabled = false;
379		mCheatAddSet(device, set);
380		cheatName[cheatNameLength] = '\0';
381		++eq;
382
383		uint32_t gameptr = 0;
384		uint32_t hexval = 0;
385		int digit;
386		while (eq[0] != '\r' && eq[1] != '\n') {
387			if (cheat + bytesRead == eq || eq[0] == '\0') {
388				bytesRead = vf->readline(vf, cheat, sizeof(cheat));
389				eq = cheat;
390				if (bytesRead == 0) {
391					break;
392				}
393				if (bytesRead < 0) {
394					return false;
395				}
396			}
397			switch (eq[0]) {
398			case ',':
399				if (!gameptr) {
400					gameptr = hexval;
401					if (hexval < 0x40000) {
402						gameptr += 0x02000000;
403					} else {
404						gameptr += 0x03000000 - 0x40000;
405					}
406				} else {
407					if (hexval > 0xFF) {
408						return false;
409					}
410					snprintf(miniline, sizeof(miniline) - 1, "%08X:%02X", gameptr, hexval);
411					mCheatAddLine(set, miniline, 0);
412					++gameptr;
413				}
414				hexval = 0;
415				break;
416			case ';':
417				if (hexval > 0xFF) {
418					return false;
419				}
420				snprintf(miniline, sizeof(miniline) - 1, "%08X:%02X", gameptr, hexval);
421				mCheatAddLine(set, miniline, 0);
422				hexval = 0;
423				gameptr = 0;
424				break;
425			default:
426				digit = hexDigit(eq[0]);
427				if (digit < 0) {
428					return false;
429				}
430				hexval <<= 4;
431				hexval |= digit;
432				break;
433			}
434			++eq;
435		}
436		if (gameptr) {
437			if (hexval > 0xFF) {
438				return false;
439			}
440			snprintf(miniline, sizeof(miniline) - 1, "%08X:%02X", gameptr, hexval);
441			mCheatAddLine(set, miniline, 0);
442		}
443	}
444	return true;
445}
446
447bool mCheatSaveFile(struct mCheatDevice* device, struct VFile* vf) {
448	static const char lineStart[3] = "# ";
449	static const char lineEnd = '\n';
450	struct StringList directives;
451	StringListInit(&directives, 4);
452
453	size_t i;
454	for (i = 0; i < mCheatSetsSize(&device->cheats); ++i) {
455		struct mCheatSet* set = *mCheatSetsGetPointer(&device->cheats, i);
456		set->dumpDirectives(set, &directives);
457		if (!set->enabled) {
458			static const char* disabledDirective = "!disabled\n";
459			vf->write(vf, disabledDirective, strlen(disabledDirective));
460		}
461		size_t d;
462		for (d = 0; d < StringListSize(&directives); ++d) {
463			char directive[64];
464			ssize_t len = snprintf(directive, sizeof(directive) - 1, "!%s\n", *StringListGetPointer(&directives, d));
465			if (len > 1) {
466				vf->write(vf, directive, (size_t) len > sizeof(directive) ? sizeof(directive) : (size_t) len);
467			}
468		}
469
470		vf->write(vf, lineStart, 2);
471		if (set->name) {
472			vf->write(vf, set->name, strlen(set->name));
473		}
474		vf->write(vf, &lineEnd, 1);
475		size_t c;
476		for (c = 0; c < StringListSize(&set->lines); ++c) {
477			const char* line = *StringListGetPointer(&set->lines, c);
478			vf->write(vf, line, strlen(line));
479			vf->write(vf, &lineEnd, 1);
480		}
481	}
482	size_t d;
483	for (d = 0; d < StringListSize(&directives); ++d) {
484		free(*StringListGetPointer(&directives, d));
485	}
486	StringListClear(&directives);
487	StringListDeinit(&directives);
488	return true;
489}
490
491#if !defined(MINIMAL_CORE) || MINIMAL_CORE < 2
492void mCheatAutosave(struct mCheatDevice* device) {
493	if (!device->autosave) {
494		return;
495	}
496	struct VFile* vf = mDirectorySetOpenSuffix(&device->p->dirs, device->p->dirs.cheats, ".cheats", O_WRONLY | O_CREAT | O_TRUNC);
497	if (!vf) {
498		return;
499	}
500	mCheatSaveFile(device, vf);
501	vf->close(vf);
502}
503#endif
504
505void mCheatRefresh(struct mCheatDevice* device, struct mCheatSet* cheats) {
506	cheats->refresh(cheats, device);
507	if (!cheats->enabled) {
508		return;
509	}
510
511	size_t elseLoc = 0;
512	size_t endLoc = 0;
513	size_t nCodes = mCheatListSize(&cheats->list);
514	size_t i;
515	for (i = 0; i < nCodes; ++i) {
516		struct mCheat* cheat = mCheatListGetPointer(&cheats->list, i);
517		int32_t value = 0;
518		int32_t operand = cheat->operand;
519		uint32_t operationsRemaining = cheat->repeat;
520		uint32_t address = cheat->address;
521		bool performAssignment = false;
522		bool condition = true;
523		int conditionRemaining = 0;
524		int negativeConditionRemaining = 0;
525
526		for (; operationsRemaining; --operationsRemaining) {
527			switch (cheat->type) {
528			case CHEAT_ASSIGN:
529				value = operand;
530				performAssignment = true;
531				break;
532			case CHEAT_ASSIGN_INDIRECT:
533				value = operand;
534				address = _readMem(device->p, address + cheat->addressOffset, 4);
535				performAssignment = true;
536				break;
537			case CHEAT_AND:
538				value = _readMem(device->p, address, cheat->width) & operand;
539				performAssignment = true;
540				break;
541			case CHEAT_ADD:
542				value = _readMem(device->p, address, cheat->width) + operand;
543				performAssignment = true;
544				break;
545			case CHEAT_OR:
546				value = _readMem(device->p, address, cheat->width) | operand;
547				performAssignment = true;
548				break;
549			case CHEAT_IF_EQ:
550				condition = _readMem(device->p, address, cheat->width) == operand;
551				conditionRemaining = cheat->repeat;
552				negativeConditionRemaining = cheat->negativeRepeat;
553				operationsRemaining = 1;
554				break;
555			case CHEAT_IF_NE:
556				condition = _readMem(device->p, address, cheat->width) != operand;
557				conditionRemaining = cheat->repeat;
558				negativeConditionRemaining = cheat->negativeRepeat;
559				operationsRemaining = 1;
560				break;
561			case CHEAT_IF_LT:
562				condition = _readMem(device->p, address, cheat->width) < operand;
563				conditionRemaining = cheat->repeat;
564				negativeConditionRemaining = cheat->negativeRepeat;
565				operationsRemaining = 1;
566				break;
567			case CHEAT_IF_GT:
568				condition = _readMem(device->p, address, cheat->width) > operand;
569				conditionRemaining = cheat->repeat;
570				negativeConditionRemaining = cheat->negativeRepeat;
571				operationsRemaining = 1;
572				break;
573			case CHEAT_IF_ULT:
574				condition = (uint32_t) _readMem(device->p, address, cheat->width) < (uint32_t) operand;
575				conditionRemaining = cheat->repeat;
576				negativeConditionRemaining = cheat->negativeRepeat;
577				operationsRemaining = 1;
578				break;
579			case CHEAT_IF_UGT:
580				condition = (uint32_t) _readMem(device->p, address, cheat->width) > (uint32_t) operand;
581				conditionRemaining = cheat->repeat;
582				negativeConditionRemaining = cheat->negativeRepeat;
583				operationsRemaining = 1;
584				break;
585			case CHEAT_IF_AND:
586				condition = _readMem(device->p, address, cheat->width) & operand;
587				conditionRemaining = cheat->repeat;
588				negativeConditionRemaining = cheat->negativeRepeat;
589				operationsRemaining = 1;
590				break;
591			case CHEAT_IF_LAND:
592				condition = _readMem(device->p, address, cheat->width) && operand;
593				conditionRemaining = cheat->repeat;
594				negativeConditionRemaining = cheat->negativeRepeat;
595				operationsRemaining = 1;
596				break;
597			case CHEAT_IF_NAND:
598				condition = !(_readMem(device->p, address, cheat->width) & operand);
599				conditionRemaining = cheat->repeat;
600				negativeConditionRemaining = cheat->negativeRepeat;
601				operationsRemaining = 1;
602				break;
603			case CHEAT_IF_BUTTON:
604				condition = device->buttonDown;
605				conditionRemaining = cheat->repeat;
606				negativeConditionRemaining = cheat->negativeRepeat;
607				operationsRemaining = 1;
608				break;
609			}
610
611			if (performAssignment) {
612				_writeMem(device->p, address, cheat->width, value);
613			}
614
615			address += cheat->addressOffset;
616			operand += cheat->operandOffset;
617		}
618
619
620		if (elseLoc && i == elseLoc) {
621			i = endLoc;
622			endLoc = 0;
623		}
624		if (conditionRemaining > 0 && !condition) {
625			i += conditionRemaining;
626		} else if (negativeConditionRemaining > 0) {
627			elseLoc = i + conditionRemaining;
628			endLoc = elseLoc + negativeConditionRemaining;
629		}
630	}
631}
632
633void mCheatPressButton(struct mCheatDevice* device, bool down) {
634	device->buttonDown = down;
635}
636
637void mCheatDeviceInit(void* cpu, struct mCPUComponent* component) {
638	UNUSED(cpu);
639	struct mCheatDevice* device = (struct mCheatDevice*) component;
640	size_t i;
641	for (i = 0; i < mCheatSetsSize(&device->cheats); ++i) {
642		struct mCheatSet* cheats = *mCheatSetsGetPointer(&device->cheats, i);
643		cheats->add(cheats, device);
644	}
645}
646
647void mCheatDeviceDeinit(struct mCPUComponent* component) {
648	struct mCheatDevice* device = (struct mCheatDevice*) component;
649	size_t i;
650	for (i = mCheatSetsSize(&device->cheats); i--;) {
651		struct mCheatSet* cheats = *mCheatSetsGetPointer(&device->cheats, i);
652		cheats->remove(cheats, device);
653	}
654}