all repos — mgba @ b137e459e0cb6d467721874c0aa8ffd7e7aef288

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