all repos — mgba @ 6154ed91cb48a8ad25b9986ea03553f1e27351c1

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*);
 21DEFINE_VECTOR(mCheatPatchList, struct mCheatPatch);
 22
 23struct mCheatPatchedMem {
 24	uint32_t originalValue;
 25	int refs;
 26	bool dirty;
 27};
 28
 29static uint32_t _patchMakeKey(struct mCheatPatch* patch) {
 30	// NB: This assumes patches have only one valid size per platform
 31	uint32_t patchKey = patch->address;
 32	switch (patch->width) {
 33	case 2:
 34		patchKey >>= 1;
 35		break;
 36	case 4:
 37		patchKey >>= 2;
 38		break;
 39	default:
 40		break;	
 41	}
 42	// TODO: More than one segment
 43	if (patch->segment > 0) {
 44		patchKey |= patch->segment << 16;
 45	}
 46	return patchKey;
 47}
 48
 49static int32_t _readMem(struct mCore* core, uint32_t address, int width) {
 50	switch (width) {
 51	case 1:
 52		return core->busRead8(core, address);
 53	case 2:
 54		return core->busRead16(core, address);
 55	case 4:
 56		return core->busRead32(core, address);
 57	}
 58	return 0;
 59}
 60
 61static int32_t _readMemSegment(struct mCore* core, uint32_t address, int segment, int width) {
 62	switch (width) {
 63	case 1:
 64		return core->rawRead8(core, address, segment);
 65	case 2:
 66		return core->rawRead16(core, address, segment);
 67	case 4:
 68		return core->rawRead32(core, address, segment);
 69	}
 70	return 0;
 71}
 72
 73static void _writeMem(struct mCore* core, uint32_t address, int width, int32_t value) {
 74	switch (width) {
 75	case 1:
 76		core->busWrite8(core, address, value);
 77		break;
 78	case 2:
 79		core->busWrite16(core, address, value);
 80		break;
 81	case 4:
 82		core->busWrite32(core, address, value);
 83		break;
 84	}
 85}
 86
 87static void _patchMem(struct mCore* core, uint32_t address, int segment, int width, int32_t value) {
 88	switch (width) {
 89	case 1:
 90		core->rawWrite8(core, address, segment, value);
 91		break;
 92	case 2:
 93		core->rawWrite16(core, address, segment, value);
 94		break;
 95	case 4:
 96		core->rawWrite32(core, address, segment, value);
 97		break;
 98	}
 99}
100
101static void _patchROM(struct mCheatDevice* device, struct mCheatSet* cheats) {
102	if (!device->p) {
103		return;
104	}
105	size_t i;
106	for (i = 0; i < mCheatPatchListSize(&cheats->romPatches); ++i) {
107		struct mCheatPatch* patch = mCheatPatchListGetPointer(&cheats->romPatches, i);
108		int segment = -1;
109		if (patch->check && patch->segment < 0) {
110			const struct mCoreMemoryBlock* block = mCoreGetMemoryBlockInfo(device->p, patch->address);
111			if (!block) {
112				continue;
113			}
114			for (segment = 0; segment < block->maxSegment; ++segment) {
115				uint32_t value = _readMemSegment(device->p, patch->address, segment, patch->width);
116				if (value == patch->checkValue) {
117					break;
118				}
119			}
120			if (segment == block->maxSegment) {
121				continue;
122			}
123		}
124		patch->segment = segment;
125
126		uint32_t patchKey = _patchMakeKey(patch);
127		struct mCheatPatchedMem* patchData = TableLookup(&device->unpatchedMemory, patchKey);
128		if (!patchData) {
129			patchData = malloc(sizeof(*patchData));
130			patchData->originalValue = _readMemSegment(device->p, patch->address, segment, patch->width);
131			patchData->refs = 1;
132			patchData->dirty = false;
133			TableInsert(&device->unpatchedMemory, patchKey, patchData);
134		} else if (!patch->applied) {
135			++patchData->refs;
136			patchData->dirty = true;
137		} else if (!patchData->dirty) {
138			continue;
139		}
140		_patchMem(device->p, patch->address, segment, patch->width, patch->value);
141		patch->applied = true;
142	}
143}
144
145static void _unpatchROM(struct mCheatDevice* device, struct mCheatSet* cheats) {
146	if (!device->p) {
147		return;
148	}
149	size_t i;
150	for (i = 0; i < mCheatPatchListSize(&cheats->romPatches); ++i) {
151		struct mCheatPatch* patch = mCheatPatchListGetPointer(&cheats->romPatches, i);
152		if (!patch->applied) {
153			continue;
154		}
155		uint32_t patchKey = _patchMakeKey(patch);
156		struct mCheatPatchedMem* patchData = TableLookup(&device->unpatchedMemory, patchKey);
157		--patchData->refs;
158		patchData->dirty = true;
159		if (patchData->refs <= 0) {
160			_patchMem(device->p, patch->address, patch->segment, patch->width, patchData->originalValue);
161			TableRemove(&device->unpatchedMemory, patchKey);
162		}
163		patch->applied = false;
164	}
165}
166
167static void mCheatDeviceInit(void*, struct mCPUComponent*);
168static void mCheatDeviceDeinit(struct mCPUComponent*);
169
170void mCheatDeviceCreate(struct mCheatDevice* device) {
171	device->d.id = M_CHEAT_DEVICE_ID;
172	device->d.init = mCheatDeviceInit;
173	device->d.deinit = mCheatDeviceDeinit;
174	device->autosave = false;
175	device->buttonDown = false;
176	mCheatSetsInit(&device->cheats, 4);
177	TableInit(&device->unpatchedMemory, 4, free);
178}
179
180void mCheatDeviceDestroy(struct mCheatDevice* device) {
181	mCheatDeviceClear(device);
182	mCheatSetsDeinit(&device->cheats);
183	TableDeinit(&device->unpatchedMemory);
184	free(device);
185}
186
187void mCheatDeviceClear(struct mCheatDevice* device) {
188	size_t i;
189	for (i = 0; i < mCheatSetsSize(&device->cheats); ++i) {
190		struct mCheatSet* set = *mCheatSetsGetPointer(&device->cheats, i);
191		mCheatSetDeinit(set);
192	}
193	mCheatSetsClear(&device->cheats);
194}
195
196void mCheatSetInit(struct mCheatSet* set, const char* name) {
197	mCheatListInit(&set->list, 4);
198	StringListInit(&set->lines, 4);
199	mCheatPatchListInit(&set->romPatches, 4);
200	if (name) {
201		set->name = strdup(name);
202	} else {
203		set->name = 0;
204	}
205	set->enabled = true;
206}
207
208void mCheatSetDeinit(struct mCheatSet* set) {
209	size_t i;
210	for (i = 0; i < StringListSize(&set->lines); ++i) {
211		free(*StringListGetPointer(&set->lines, i));
212	}
213	mCheatListDeinit(&set->list);
214	if (set->name) {
215		free(set->name);
216	}
217	StringListDeinit(&set->lines);
218	mCheatPatchListDeinit(&set->romPatches);
219	if (set->deinit) {
220		set->deinit(set);
221	}
222	free(set);
223}
224
225void mCheatSetRename(struct mCheatSet* set, const char* name) {
226	if (set->name) {
227		free(set->name);
228		set->name = NULL;
229	}
230	if (name) {
231		set->name = strdup(name);
232	}
233}
234
235bool mCheatAddLine(struct mCheatSet* set, const char* line, int type) {
236	if (!set->addLine(set, line, type)) {
237		return false;
238	}
239	*StringListAppend(&set->lines) = strdup(line);
240	return true;
241}
242
243void mCheatAddSet(struct mCheatDevice* device, struct mCheatSet* cheats) {
244	*mCheatSetsAppend(&device->cheats) = cheats;
245	if (cheats->add) {
246		cheats->add(cheats, device);
247	}
248}
249
250void mCheatRemoveSet(struct mCheatDevice* device, struct mCheatSet* cheats) {
251	size_t i;
252	for (i = 0; i < mCheatSetsSize(&device->cheats); ++i) {
253		if (*mCheatSetsGetPointer(&device->cheats, i) == cheats) {
254			break;
255		}
256	}
257	if (i == mCheatSetsSize(&device->cheats)) {
258		return;
259	}
260	mCheatSetsShift(&device->cheats, i, 1);
261	if (cheats->remove) {
262		cheats->remove(cheats, device);
263	}
264}
265
266bool mCheatParseFile(struct mCheatDevice* device, struct VFile* vf) {
267	char cheat[MAX_LINE_LENGTH];
268	struct mCheatSet* set = NULL;
269	struct mCheatSet* newSet;
270	bool nextDisabled = false;
271	struct StringList directives;
272	StringListInit(&directives, 4);
273
274	while (true) {
275		size_t i = 0;
276		ssize_t bytesRead = vf->readline(vf, cheat, sizeof(cheat));
277		rtrim(cheat);
278		if (bytesRead == 0) {
279			break;
280		}
281		if (bytesRead < 0) {
282			StringListDeinit(&directives);
283			return false;
284		}
285		while (isspace((int) cheat[i])) {
286			++i;
287		}
288		switch (cheat[i]) {
289		case '#':
290			do {
291				++i;
292			} while (isspace((int) cheat[i]));
293			newSet = device->createSet(device, &cheat[i]);
294			newSet->enabled = !nextDisabled;
295			nextDisabled = false;
296			if (set) {
297				mCheatAddSet(device, set);
298			}
299			if (set) {
300				newSet->copyProperties(newSet, set);
301			}
302			newSet->parseDirectives(newSet, &directives);
303			set = newSet;
304			break;
305		case '!':
306			do {
307				++i;
308			} while (isspace((int) cheat[i]));
309			if (strcasecmp(&cheat[i], "disabled") == 0) {
310				nextDisabled = true;
311				break;
312			}
313			if (strcasecmp(&cheat[i], "reset") == 0) {
314				size_t d;
315				for (d = 0; d < StringListSize(&directives); ++d) {
316					free(*StringListGetPointer(&directives, d));
317				}
318				StringListClear(&directives);
319				break;
320			}
321			*StringListAppend(&directives) = strdup(&cheat[i]);
322			break;
323		default:
324			if (!set) {
325				if (strncmp(cheat, "cheats = ", 9) == 0) {
326					// This is in libretro format, switch over to that parser
327					vf->seek(vf, 0, SEEK_SET);
328					StringListDeinit(&directives);
329					return mCheatParseLibretroFile(device, vf);
330				}
331				if (cheat[0] == '[') {
332					// This is in EZ Flash CHT format, switch over to that parser
333					vf->seek(vf, 0, SEEK_SET);
334					StringListDeinit(&directives);
335					return mCheatParseEZFChtFile(device, vf);
336				}
337				set = device->createSet(device, NULL);
338				set->enabled = !nextDisabled;
339				nextDisabled = false;
340			}
341			mCheatAddLine(set, cheat, 0);
342			break;
343		}
344	}
345	if (set) {
346		mCheatAddSet(device, set);
347	}
348	size_t d;
349	for (d = 0; d < StringListSize(&directives); ++d) {
350		free(*StringListGetPointer(&directives, d));
351	}
352	StringListClear(&directives);
353	StringListDeinit(&directives);
354	return true;
355}
356
357bool mCheatParseLibretroFile(struct mCheatDevice* device, struct VFile* vf) {
358	char cheat[MAX_LINE_LENGTH];
359	char parsed[MAX_LINE_LENGTH];
360	struct mCheatSet* set = NULL;
361	unsigned long i = 0;
362	bool startFound = false;
363
364	while (true) {
365		ssize_t bytesRead = vf->readline(vf, cheat, sizeof(cheat));
366		if (bytesRead == 0) {
367			break;
368		}
369		if (bytesRead < 0) {
370			return false;
371		}
372		if (cheat[0] == '\n') {
373			continue;
374		}
375		if (strncmp(cheat, "cheat", 5) != 0) {
376			return false;
377		}
378		char* underscore = strchr(&cheat[5], '_');
379		if (!underscore) {
380			if (!startFound && cheat[5] == 's') {
381				startFound = true;
382				char* eq = strchr(&cheat[6], '=');
383				if (!eq) {
384					return false;
385				}
386				++eq;
387				while (isspace((int) eq[0])) {
388					if (eq[0] == '\0') {
389						return false;
390					}
391					++eq;
392				}
393
394				char* end;
395				unsigned long nCheats = strtoul(eq, &end, 10);
396				if (end[0] != '\0' && !isspace(end[0])) {
397					return false;
398				}
399
400				if (nCheats > MAX_CHEATS) {
401					return false;
402				}
403
404				while (nCheats > mCheatSetsSize(&device->cheats)) {
405					struct mCheatSet* newSet = device->createSet(device, NULL);
406					if (!newSet) {
407						return false;
408					}
409					mCheatAddSet(device, newSet);
410				}
411				continue;
412			}
413			return false;
414		}
415		char* underscore2;
416		i = strtoul(&cheat[5], &underscore2, 10);
417		if (underscore2 != underscore) {
418			return false;
419		}
420		++underscore;
421		char* eq = strchr(underscore, '=');
422		if (!eq) {
423			return false;
424		}
425		++eq;
426		while (isspace((int) eq[0])) {
427			if (eq[0] == '\0') {
428				return false;
429			}
430			++eq;
431		}
432
433		if (i >= mCheatSetsSize(&device->cheats)) {
434			return false;
435		}
436		set = *mCheatSetsGetPointer(&device->cheats, i);
437
438		if (strncmp(underscore, "desc", 4) == 0) {
439			parseQuotedString(eq, strlen(eq), parsed, sizeof(parsed));
440			mCheatSetRename(set, parsed);
441		} else if (strncmp(underscore, "enable", 6) == 0) {
442			set->enabled = strncmp(eq, "true\n", 5) == 0;
443		} else if (strncmp(underscore, "code", 4) == 0) {
444			parseQuotedString(eq, strlen(eq), parsed, sizeof(parsed));
445			char* cur = parsed;
446			char* next;
447			while ((next = strchr(cur, '+'))) {
448				next[0] = '\0';
449				mCheatAddLine(set, cur, 0);
450				cur = &next[1];
451			}
452			mCheatAddLine(set, cur, 0);
453
454			for (++i; i < mCheatSetsSize(&device->cheats); ++i) {
455				struct mCheatSet* newSet = *mCheatSetsGetPointer(&device->cheats, i);
456				newSet->copyProperties(newSet, set);
457			}
458		}
459	}
460	return true;
461}
462
463bool mCheatParseEZFChtFile(struct mCheatDevice* device, struct VFile* vf) {
464	char cheat[MAX_LINE_LENGTH];
465	char cheatName[MAX_LINE_LENGTH];
466	char miniline[32];
467	size_t cheatNameLength = 0;
468	struct mCheatSet* set = NULL;
469
470	cheatName[MAX_LINE_LENGTH - 1] = '\0';
471	while (true) {
472		ssize_t bytesRead = vf->readline(vf, cheat, sizeof(cheat));
473		if (bytesRead == 0) {
474			break;
475		}
476		if (bytesRead < 0) {
477			return false;
478		}
479		if (cheat[0] == '\n' || (bytesRead >= 2 && cheat[0] == '\r' && cheat[1] == '\n')) {
480			continue;
481		}
482
483		if (cheat[0] == '[') {
484			if (strncmp(cheat, "[GameInfo]", 10) == 0) {
485				break;
486			}
487			char* end = strchr(cheat, ']');
488			if (!end) {
489				return false;
490			}
491			char* name = gbkToUtf8(&cheat[1], end - cheat - 1);
492			strncpy(cheatName, name, sizeof(cheatName) - 1);
493			free(name);
494			cheatNameLength = strlen(cheatName);
495			continue;
496		}
497
498		char* eq = strchr(cheat, '=');
499		if (!eq) {
500			continue;
501		}
502		if (strncmp(cheat, "ON", eq - cheat) != 0) {
503			char* subname = gbkToUtf8(cheat, eq - cheat);
504			snprintf(&cheatName[cheatNameLength], sizeof(cheatName) - cheatNameLength - 1, ": %s", subname);
505		}
506		set = device->createSet(device, cheatName);
507		set->enabled = false;
508		mCheatAddSet(device, set);
509		cheatName[cheatNameLength] = '\0';
510		++eq;
511
512		uint32_t gameptr = 0;
513		uint32_t hexval = 0;
514		int digit;
515		while (eq[0] != '\r' && eq[1] != '\n') {
516			if (cheat + bytesRead == eq || eq[0] == '\0') {
517				bytesRead = vf->readline(vf, cheat, sizeof(cheat));
518				eq = cheat;
519				if (bytesRead == 0) {
520					break;
521				}
522				if (bytesRead < 0) {
523					return false;
524				}
525			}
526			switch (eq[0]) {
527			case ',':
528				if (!gameptr) {
529					gameptr = hexval;
530					if (hexval < 0x40000) {
531						gameptr += 0x02000000;
532					} else {
533						gameptr += 0x03000000 - 0x40000;
534					}
535				} else {
536					if (hexval > 0xFF) {
537						return false;
538					}
539					snprintf(miniline, sizeof(miniline) - 1, "%08X:%02X", gameptr, hexval);
540					mCheatAddLine(set, miniline, 0);
541					++gameptr;
542				}
543				hexval = 0;
544				break;
545			case ';':
546				if (hexval > 0xFF) {
547					return false;
548				}
549				snprintf(miniline, sizeof(miniline) - 1, "%08X:%02X", gameptr, hexval);
550				mCheatAddLine(set, miniline, 0);
551				hexval = 0;
552				gameptr = 0;
553				break;
554			default:
555				digit = hexDigit(eq[0]);
556				if (digit < 0) {
557					return false;
558				}
559				hexval <<= 4;
560				hexval |= digit;
561				break;
562			}
563			++eq;
564		}
565		if (gameptr) {
566			if (hexval > 0xFF) {
567				return false;
568			}
569			snprintf(miniline, sizeof(miniline) - 1, "%08X:%02X", gameptr, hexval);
570			mCheatAddLine(set, miniline, 0);
571		}
572	}
573	return true;
574}
575
576bool mCheatSaveFile(struct mCheatDevice* device, struct VFile* vf) {
577	static const char lineStart[3] = "# ";
578	static const char lineEnd = '\n';
579	struct StringList directives;
580	StringListInit(&directives, 4);
581
582	size_t i;
583	for (i = 0; i < mCheatSetsSize(&device->cheats); ++i) {
584		struct mCheatSet* set = *mCheatSetsGetPointer(&device->cheats, i);
585		set->dumpDirectives(set, &directives);
586		if (!set->enabled) {
587			static const char* disabledDirective = "!disabled\n";
588			vf->write(vf, disabledDirective, strlen(disabledDirective));
589		}
590		size_t d;
591		for (d = 0; d < StringListSize(&directives); ++d) {
592			char directive[64];
593			ssize_t len = snprintf(directive, sizeof(directive) - 1, "!%s\n", *StringListGetPointer(&directives, d));
594			if (len > 1) {
595				vf->write(vf, directive, (size_t) len > sizeof(directive) ? sizeof(directive) : (size_t) len);
596			}
597		}
598
599		vf->write(vf, lineStart, 2);
600		if (set->name) {
601			vf->write(vf, set->name, strlen(set->name));
602		}
603		vf->write(vf, &lineEnd, 1);
604		size_t c;
605		for (c = 0; c < StringListSize(&set->lines); ++c) {
606			const char* line = *StringListGetPointer(&set->lines, c);
607			vf->write(vf, line, strlen(line));
608			vf->write(vf, &lineEnd, 1);
609		}
610	}
611	size_t d;
612	for (d = 0; d < StringListSize(&directives); ++d) {
613		free(*StringListGetPointer(&directives, d));
614	}
615	StringListClear(&directives);
616	StringListDeinit(&directives);
617	return true;
618}
619
620#if !defined(MINIMAL_CORE) || MINIMAL_CORE < 2
621void mCheatAutosave(struct mCheatDevice* device) {
622	if (!device->autosave) {
623		return;
624	}
625	struct VFile* vf = mDirectorySetOpenSuffix(&device->p->dirs, device->p->dirs.cheats, ".cheats", O_WRONLY | O_CREAT | O_TRUNC);
626	if (!vf) {
627		return;
628	}
629	mCheatSaveFile(device, vf);
630	vf->close(vf);
631}
632#endif
633
634void mCheatRefresh(struct mCheatDevice* device, struct mCheatSet* cheats) {
635	if (cheats->enabled) {
636		_patchROM(device, cheats);
637	}
638	if (cheats->refresh) {
639		cheats->refresh(cheats, device);
640	}
641	if (!cheats->enabled) {
642		_unpatchROM(device, cheats);
643		return;
644	}
645
646	size_t elseLoc = 0;
647	size_t endLoc = 0;
648	size_t nCodes = mCheatListSize(&cheats->list);
649	size_t i;
650	for (i = 0; i < nCodes; ++i) {
651		struct mCheat* cheat = mCheatListGetPointer(&cheats->list, i);
652		int32_t value = 0;
653		int32_t operand = cheat->operand;
654		uint32_t operationsRemaining = cheat->repeat;
655		uint32_t address = cheat->address;
656		bool performAssignment = false;
657		bool condition = true;
658		int conditionRemaining = 0;
659		int negativeConditionRemaining = 0;
660
661		for (; operationsRemaining; --operationsRemaining) {
662			switch (cheat->type) {
663			case CHEAT_ASSIGN:
664				value = operand;
665				performAssignment = true;
666				break;
667			case CHEAT_ASSIGN_INDIRECT:
668				value = operand;
669				address = _readMem(device->p, address + cheat->addressOffset, 4);
670				performAssignment = true;
671				break;
672			case CHEAT_AND:
673				value = _readMem(device->p, address, cheat->width) & operand;
674				performAssignment = true;
675				break;
676			case CHEAT_ADD:
677				value = _readMem(device->p, address, cheat->width) + operand;
678				performAssignment = true;
679				break;
680			case CHEAT_OR:
681				value = _readMem(device->p, address, cheat->width) | operand;
682				performAssignment = true;
683				break;
684			case CHEAT_IF_EQ:
685				condition = _readMem(device->p, address, cheat->width) == operand;
686				conditionRemaining = cheat->repeat;
687				negativeConditionRemaining = cheat->negativeRepeat;
688				operationsRemaining = 1;
689				break;
690			case CHEAT_IF_NE:
691				condition = _readMem(device->p, address, cheat->width) != operand;
692				conditionRemaining = cheat->repeat;
693				negativeConditionRemaining = cheat->negativeRepeat;
694				operationsRemaining = 1;
695				break;
696			case CHEAT_IF_LT:
697				condition = _readMem(device->p, address, cheat->width) < operand;
698				conditionRemaining = cheat->repeat;
699				negativeConditionRemaining = cheat->negativeRepeat;
700				operationsRemaining = 1;
701				break;
702			case CHEAT_IF_GT:
703				condition = _readMem(device->p, address, cheat->width) > operand;
704				conditionRemaining = cheat->repeat;
705				negativeConditionRemaining = cheat->negativeRepeat;
706				operationsRemaining = 1;
707				break;
708			case CHEAT_IF_ULT:
709				condition = (uint32_t) _readMem(device->p, address, cheat->width) < (uint32_t) operand;
710				conditionRemaining = cheat->repeat;
711				negativeConditionRemaining = cheat->negativeRepeat;
712				operationsRemaining = 1;
713				break;
714			case CHEAT_IF_UGT:
715				condition = (uint32_t) _readMem(device->p, address, cheat->width) > (uint32_t) operand;
716				conditionRemaining = cheat->repeat;
717				negativeConditionRemaining = cheat->negativeRepeat;
718				operationsRemaining = 1;
719				break;
720			case CHEAT_IF_AND:
721				condition = _readMem(device->p, address, cheat->width) & operand;
722				conditionRemaining = cheat->repeat;
723				negativeConditionRemaining = cheat->negativeRepeat;
724				operationsRemaining = 1;
725				break;
726			case CHEAT_IF_LAND:
727				condition = _readMem(device->p, address, cheat->width) && operand;
728				conditionRemaining = cheat->repeat;
729				negativeConditionRemaining = cheat->negativeRepeat;
730				operationsRemaining = 1;
731				break;
732			case CHEAT_IF_NAND:
733				condition = !(_readMem(device->p, address, cheat->width) & operand);
734				conditionRemaining = cheat->repeat;
735				negativeConditionRemaining = cheat->negativeRepeat;
736				operationsRemaining = 1;
737				break;
738			case CHEAT_IF_BUTTON:
739				condition = device->buttonDown;
740				conditionRemaining = cheat->repeat;
741				negativeConditionRemaining = cheat->negativeRepeat;
742				operationsRemaining = 1;
743				break;
744			}
745
746			if (performAssignment) {
747				_writeMem(device->p, address, cheat->width, value);
748			}
749
750			address += cheat->addressOffset;
751			operand += cheat->operandOffset;
752		}
753
754
755		if (elseLoc && i == elseLoc) {
756			i = endLoc;
757			endLoc = 0;
758		}
759		if (conditionRemaining > 0 && !condition) {
760			i += conditionRemaining;
761		} else if (negativeConditionRemaining > 0) {
762			elseLoc = i + conditionRemaining;
763			endLoc = elseLoc + negativeConditionRemaining;
764		}
765	}
766}
767
768void mCheatPressButton(struct mCheatDevice* device, bool down) {
769	device->buttonDown = down;
770}
771
772void mCheatDeviceInit(void* cpu, struct mCPUComponent* component) {
773	UNUSED(cpu);
774	struct mCheatDevice* device = (struct mCheatDevice*) component;
775	size_t i;
776	for (i = 0; i < mCheatSetsSize(&device->cheats); ++i) {
777		struct mCheatSet* cheats = *mCheatSetsGetPointer(&device->cheats, i);
778		cheats->add(cheats, device);
779	}
780}
781
782void mCheatDeviceDeinit(struct mCPUComponent* component) {
783	struct mCheatDevice* device = (struct mCheatDevice*) component;
784	size_t i;
785	for (i = mCheatSetsSize(&device->cheats); i--;) {
786		struct mCheatSet* cheats = *mCheatSetsGetPointer(&device->cheats, i);
787		cheats->remove(cheats, device);
788	}
789}