all repos — mgba @ e27e3333052238777f4acdb029f02f1fee90626c

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