all repos — mgba @ 77ead17e290ce40fa7ef4f1d7a859e20d18fc3eb

mGBA Game Boy Advance Emulator

src/gba/cheats.c (view raw)

  1/* Copyright (c) 2013-2015 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 "cheats.h"
  7
  8#include "gba/cheats/gameshark.h"
  9#include "gba/cheats/parv3.h"
 10#include "gba/gba.h"
 11#include "util/string.h"
 12#include "util/vfs.h"
 13
 14#define MAX_LINE_LENGTH 128
 15
 16const uint32_t GBA_CHEAT_DEVICE_ID = 0xABADC0DE;
 17
 18DEFINE_VECTOR(GBACheatList, struct GBACheat);
 19DEFINE_VECTOR(GBACheatSets, struct GBACheatSet*);
 20DEFINE_VECTOR(StringList, char*);
 21
 22static int32_t _readMem(struct ARMCore* cpu, uint32_t address, int width) {
 23	switch (width) {
 24	case 1:
 25		return cpu->memory.load8(cpu, address, 0);
 26	case 2:
 27		return cpu->memory.load16(cpu, address, 0);
 28	case 4:
 29		return cpu->memory.load32(cpu, address, 0);
 30	}
 31	return 0;
 32}
 33
 34static void _writeMem(struct ARMCore* cpu, uint32_t address, int width, int32_t value) {
 35	switch (width) {
 36	case 1:
 37		cpu->memory.store8(cpu, address, value, 0);
 38		break;
 39	case 2:
 40		cpu->memory.store16(cpu, address, value, 0);
 41		break;
 42	case 4:
 43		cpu->memory.store32(cpu, address, value, 0);
 44		break;
 45	}
 46}
 47
 48void GBACheatRegisterLine(struct GBACheatSet* cheats, const char* line) {
 49	*StringListAppend(&cheats->lines) = strdup(line);
 50}
 51
 52static void _addBreakpoint(struct GBACheatDevice* device, struct GBACheatSet* cheats) {
 53	if (!device->p || !cheats->hook) {
 54		return;
 55	}
 56	++cheats->hook->reentries;
 57	if (cheats->hook->reentries > 1) {
 58		return;
 59	}
 60	GBASetBreakpoint(device->p, &device->d, cheats->hook->address, cheats->hook->mode, &cheats->hook->patchedOpcode);
 61}
 62
 63static void _removeBreakpoint(struct GBACheatDevice* device, struct GBACheatSet* cheats) {
 64	if (!device->p || !cheats->hook) {
 65		return;
 66	}
 67	--cheats->hook->reentries;
 68	if (cheats->hook->reentries > 0) {
 69		return;
 70	}
 71	GBAClearBreakpoint(device->p, cheats->hook->address, cheats->hook->mode, cheats->hook->patchedOpcode);
 72}
 73
 74static void _patchROM(struct GBACheatDevice* device, struct GBACheatSet* cheats) {
 75	if (!device->p) {
 76		return;
 77	}
 78	int i;
 79	for (i = 0; i < MAX_ROM_PATCHES; ++i) {
 80		if (!cheats->romPatches[i].exists || cheats->romPatches[i].applied) {
 81			continue;
 82		}
 83		GBAPatch16(device->p->cpu, cheats->romPatches[i].address, cheats->romPatches[i].newValue, &cheats->romPatches[i].oldValue);
 84		cheats->romPatches[i].applied = true;
 85	}
 86}
 87
 88static void _unpatchROM(struct GBACheatDevice* device, struct GBACheatSet* cheats) {
 89	if (!device->p) {
 90		return;
 91	}
 92	int i;
 93	for (i = 0; i < MAX_ROM_PATCHES; ++i) {
 94		if (!cheats->romPatches[i].exists || !cheats->romPatches[i].applied) {
 95			continue;
 96		}
 97		GBAPatch16(device->p->cpu, cheats->romPatches[i].address, cheats->romPatches[i].oldValue, 0);
 98		cheats->romPatches[i].applied = false;
 99	}
100}
101
102static void GBACheatDeviceInit(struct ARMCore*, struct ARMComponent*);
103static void GBACheatDeviceDeinit(struct ARMComponent*);
104
105void GBACheatDeviceCreate(struct GBACheatDevice* device) {
106	device->d.id = GBA_CHEAT_DEVICE_ID;
107	device->d.init = GBACheatDeviceInit;
108	device->d.deinit = GBACheatDeviceDeinit;
109	GBACheatSetsInit(&device->cheats, 4);
110}
111
112void GBACheatDeviceDestroy(struct GBACheatDevice* device) {
113	size_t i;
114	for (i = 0; i < GBACheatSetsSize(&device->cheats); ++i) {
115		struct GBACheatSet* set = *GBACheatSetsGetPointer(&device->cheats, i);
116		GBACheatSetDeinit(set);
117		free(set);
118	}
119	GBACheatSetsDeinit(&device->cheats);
120}
121
122void GBACheatSetInit(struct GBACheatSet* set, const char* name) {
123	GBACheatListInit(&set->list, 4);
124	StringListInit(&set->lines, 4);
125	set->incompleteCheat = 0;
126	set->incompletePatch = 0;
127	set->currentBlock = 0;
128	set->gsaVersion = 0;
129	set->remainingAddresses = 0;
130	set->hook = 0;
131	int i;
132	for (i = 0; i < MAX_ROM_PATCHES; ++i) {
133		set->romPatches[i].exists = false;
134	}
135	if (name) {
136		set->name = strdup(name);
137	} else {
138		set->name = 0;
139	}
140	set->enabled = true;
141}
142
143void GBACheatSetDeinit(struct GBACheatSet* set) {
144	GBACheatListDeinit(&set->list);
145	size_t i;
146	for (i = 0; i < StringListSize(&set->lines); ++i) {
147		free(*StringListGetPointer(&set->lines, i));
148	}
149	if (set->name) {
150		free(set->name);
151	}
152	if (set->hook) {
153		--set->hook->refs;
154		if (set->hook->refs == 0) {
155			free(set->hook);
156		}
157	}
158}
159
160void GBACheatAttachDevice(struct GBA* gba, struct GBACheatDevice* device) {
161	if (gba->cpu->components[GBA_COMPONENT_CHEAT_DEVICE]) {
162		ARMHotplugDetach(gba->cpu, GBA_COMPONENT_CHEAT_DEVICE);
163	}
164	gba->cpu->components[GBA_COMPONENT_CHEAT_DEVICE] = &device->d;
165	ARMHotplugAttach(gba->cpu, GBA_COMPONENT_CHEAT_DEVICE);
166}
167
168void GBACheatAddSet(struct GBACheatDevice* device, struct GBACheatSet* cheats) {
169	*GBACheatSetsAppend(&device->cheats) = cheats;
170	_addBreakpoint(device, cheats);
171	_patchROM(device, cheats);
172}
173
174void GBACheatRemoveSet(struct GBACheatDevice* device, struct GBACheatSet* cheats) {
175	size_t i;
176	for (i = 0; i < GBACheatSetsSize(&device->cheats); ++i) {
177		if (*GBACheatSetsGetPointer(&device->cheats, i) == cheats) {
178			break;
179		}
180	}
181	if (i == GBACheatSetsSize(&device->cheats)) {
182		return;
183	}
184	GBACheatSetsShift(&device->cheats, i, 1);
185	_unpatchROM(device, cheats);
186	_removeBreakpoint(device, cheats);
187}
188
189bool GBACheatAddAutodetect(struct GBACheatSet* set, uint32_t op1, uint32_t op2) {
190	uint32_t o1 = op1;
191	uint32_t o2 = op2;
192	char line[18] = "XXXXXXXX XXXXXXXX";
193	snprintf(line, sizeof(line), "%08X %08X", op1, op2);
194	GBACheatRegisterLine(set, line);
195
196	switch (set->gsaVersion) {
197	case 0:
198		// Try to detect GameShark version
199		GBACheatDecryptGameShark(&o1, &o2, GBACheatGameSharkSeeds);
200		if ((o1 & 0xF0000000) == 0xF0000000 && !(o2 & 0xFFFFFCFE)) {
201			GBACheatSetGameSharkVersion(set, 1);
202			return GBACheatAddGameSharkRaw(set, o1, o2);
203		}
204		o1 = op1;
205		o2 = op2;
206		GBACheatDecryptGameShark(&o1, &o2, GBACheatProActionReplaySeeds);
207		if ((o1 & 0xFE000000) == 0xC4000000 && !(o2 & 0xFFFF0000)) {
208			GBACheatSetGameSharkVersion(set, 3);
209			return GBACheatAddProActionReplayRaw(set, o1, o2);
210		}
211		break;
212	case 1:
213		GBACheatDecryptGameShark(&o1, &o2, set->gsaSeeds);
214		return GBACheatAddGameSharkRaw(set, o1, o2);
215	case 3:
216		GBACheatDecryptGameShark(&o1, &o2, set->gsaSeeds);
217		return GBACheatAddProActionReplayRaw(set, o1, o2);
218	}
219	return false;
220}
221
222bool GBACheatAutodetectLine(struct GBACheatSet* cheats, const char* line) {
223	uint32_t op1;
224	uint32_t op2;
225	line = hex32(line, &op1);
226	if (!line) {
227		return false;
228	}
229	while (*line == ' ') {
230		++line;
231	}
232	line = hex32(line, &op2);
233	if (!line) {
234		return false;
235	}
236	return GBACheatAddAutodetect(cheats, op1, op2);
237}
238
239bool GBACheatParseFile(struct GBACheatDevice* device, struct VFile* vf) {
240	char cheat[MAX_LINE_LENGTH];
241	struct GBACheatSet* set = 0;
242	struct GBACheatSet* newSet;
243	int gsaVersion = 0;
244	bool nextDisabled = false;
245	bool reset = false;
246	while (true) {
247		size_t i = 0;
248		ssize_t bytesRead = vf->readline(vf, cheat, sizeof(cheat));
249		if (bytesRead == 0) {
250			break;
251		}
252		if (bytesRead < 0) {
253			return false;
254		}
255		while (isspace(cheat[i])) {
256			++i;
257		}
258		switch (cheat[i]) {
259		case '#':
260			do {
261				++i;
262			} while (isspace(cheat[i]));
263			newSet = malloc(sizeof(*set));
264			GBACheatSetInit(newSet, &cheat[i]);
265			newSet->enabled = !nextDisabled;
266			nextDisabled = false;
267			if (set) {
268				GBACheatAddSet(device, set);
269			}
270			if (set && !reset) {
271				GBACheatSetCopyProperties(newSet, set);
272			} else {
273				GBACheatSetGameSharkVersion(newSet, gsaVersion);
274			}
275			reset = false;
276			set = newSet;
277			break;
278		case '!':
279			do {
280				++i;
281			} while (isspace(cheat[i]));
282			if (strncasecmp(&cheat[i], "GSAv", 4) == 0 || strncasecmp(&cheat[i], "PARv", 4) == 0) {
283				i += 4;
284				gsaVersion = atoi(&cheat[i]);
285				break;
286			}
287			if (strcasecmp(&cheat[i], "disabled") == 0) {
288				nextDisabled = true;
289				break;
290			}
291			if (strcasecmp(&cheat[i], "reset") == 0) {
292				reset = true;
293				break;
294			}
295			break;
296		default:
297			if (!set) {
298				set = malloc(sizeof(*set));
299				GBACheatSetInit(set, 0);
300				set->enabled = !nextDisabled;
301				nextDisabled = false;
302				GBACheatSetGameSharkVersion(set, gsaVersion);
303			}
304			GBACheatAddLine(set, cheat);
305			break;
306		}
307	}
308	if (set) {
309		GBACheatAddSet(device, set);
310	}
311	return true;
312}
313
314bool GBACheatSaveFile(struct GBACheatDevice* device, struct VFile* vf) {
315	static const char lineStart[3] = "# ";
316	static const char lineEnd = '\n';
317
318	struct GBACheatHook* lastHook = 0;
319
320	size_t i;
321	for (i = 0; i < GBACheatSetsSize(&device->cheats); ++i) {
322		struct GBACheatSet* set = *GBACheatSetsGetPointer(&device->cheats, i);
323		if (lastHook && set->hook != lastHook) {
324			static const char* resetDirective = "!reset\n";
325			vf->write(vf, resetDirective, strlen(resetDirective));
326		}
327		switch (set->gsaVersion) {
328		case 1: {
329			static const char* versionDirective = "!GSAv1\n";
330			vf->write(vf, versionDirective, strlen(versionDirective));
331			break;
332		}
333		case 3: {
334			static const char* versionDirective = "!PARv3\n";
335			vf->write(vf, versionDirective, strlen(versionDirective));
336			break;
337		}
338		default:
339			break;
340		}
341		lastHook = set->hook;
342		if (!set->enabled) {
343			static const char* disabledDirective = "!disabled\n";
344			vf->write(vf, disabledDirective, strlen(disabledDirective));
345		}
346
347		vf->write(vf, lineStart, 2);
348		if (set->name) {
349			vf->write(vf, set->name, strlen(set->name));
350		}
351		vf->write(vf, &lineEnd, 1);
352		size_t c;
353		for (c = 0; c < StringListSize(&set->lines); ++c) {
354			const char* line = *StringListGetPointer(&set->lines, c);
355			vf->write(vf, line, strlen(line));
356			vf->write(vf, &lineEnd, 1);
357		}
358	}
359	return true;
360}
361
362bool GBACheatAddLine(struct GBACheatSet* cheats, const char* line) {
363	uint32_t op1;
364	uint16_t op2;
365	uint16_t op3;
366	line = hex32(line, &op1);
367	if (!line) {
368		return false;
369	}
370	while (isspace(line[0])) {
371		++line;
372	}
373	line = hex16(line, &op2);
374	if (!line) {
375		return false;
376	}
377	if (!line[0] || isspace(line[0])) {
378		return GBACheatAddCodeBreaker(cheats, op1, op2);
379	}
380	line = hex16(line, &op3);
381	if (!line) {
382		return false;
383	}
384	uint32_t realOp2 = op2;
385	realOp2 <<= 16;
386	realOp2 |= op3;
387	return GBACheatAddAutodetect(cheats, op1, realOp2);
388}
389
390void GBACheatRefresh(struct GBACheatDevice* device, struct GBACheatSet* cheats) {
391	if (!cheats->enabled) {
392		return;
393	}
394	bool condition = true;
395	int conditionRemaining = 0;
396	int negativeConditionRemaining = 0;
397	_patchROM(device, cheats);
398
399	size_t nCodes = GBACheatListSize(&cheats->list);
400	size_t i;
401	for (i = 0; i < nCodes; ++i) {
402		if (conditionRemaining > 0) {
403			--conditionRemaining;
404			if (!condition) {
405				continue;
406			}
407		} else if (negativeConditionRemaining > 0) {
408			conditionRemaining = negativeConditionRemaining - 1;
409			negativeConditionRemaining = 0;
410			condition = !condition;
411			if (!condition) {
412				continue;
413			}
414		} else {
415			condition = true;
416		}
417		struct GBACheat* cheat = GBACheatListGetPointer(&cheats->list, i);
418		int32_t value = 0;
419		int32_t operand = cheat->operand;
420		uint32_t operationsRemaining = cheat->repeat;
421		uint32_t address = cheat->address;
422		bool performAssignment = false;
423		for (; operationsRemaining; --operationsRemaining) {
424			switch (cheat->type) {
425			case CHEAT_ASSIGN:
426				value = operand;
427				performAssignment = true;
428				break;
429			case CHEAT_ASSIGN_INDIRECT:
430				value = operand;
431				address = _readMem(device->p->cpu, address + cheat->addressOffset, 4);
432				performAssignment = true;
433				break;
434			case CHEAT_AND:
435				value = _readMem(device->p->cpu, address, cheat->width) & operand;
436				performAssignment = true;
437				break;
438			case CHEAT_ADD:
439				value = _readMem(device->p->cpu, address, cheat->width) + operand;
440				performAssignment = true;
441				break;
442			case CHEAT_OR:
443				value = _readMem(device->p->cpu, address, cheat->width) | operand;
444				performAssignment = true;
445				break;
446			case CHEAT_IF_EQ:
447				condition = _readMem(device->p->cpu, address, cheat->width) == operand;
448				conditionRemaining = cheat->repeat;
449				negativeConditionRemaining = cheat->negativeRepeat;
450				break;
451			case CHEAT_IF_NE:
452				condition = _readMem(device->p->cpu, address, cheat->width) != operand;
453				conditionRemaining = cheat->repeat;
454				negativeConditionRemaining = cheat->negativeRepeat;
455				break;
456			case CHEAT_IF_LT:
457				condition = _readMem(device->p->cpu, address, cheat->width) < operand;
458				conditionRemaining = cheat->repeat;
459				negativeConditionRemaining = cheat->negativeRepeat;
460				break;
461			case CHEAT_IF_GT:
462				condition = _readMem(device->p->cpu, address, cheat->width) > operand;
463				conditionRemaining = cheat->repeat;
464				negativeConditionRemaining = cheat->negativeRepeat;
465				break;
466			case CHEAT_IF_ULT:
467				condition = (uint32_t) _readMem(device->p->cpu, address, cheat->width) < (uint32_t) operand;
468				conditionRemaining = cheat->repeat;
469				negativeConditionRemaining = cheat->negativeRepeat;
470				break;
471			case CHEAT_IF_UGT:
472				condition = (uint32_t) _readMem(device->p->cpu, address, cheat->width) > (uint32_t) operand;
473				conditionRemaining = cheat->repeat;
474				negativeConditionRemaining = cheat->negativeRepeat;
475				break;
476			case CHEAT_IF_AND:
477				condition = _readMem(device->p->cpu, address, cheat->width) & operand;
478				conditionRemaining = cheat->repeat;
479				negativeConditionRemaining = cheat->negativeRepeat;
480				break;
481			case CHEAT_IF_LAND:
482				condition = _readMem(device->p->cpu, address, cheat->width) && operand;
483				conditionRemaining = cheat->repeat;
484				negativeConditionRemaining = cheat->negativeRepeat;
485				break;
486			}
487
488			if (performAssignment) {
489				_writeMem(device->p->cpu, address, cheat->width, value);
490			}
491
492			address += cheat->addressOffset;
493			operand += cheat->operandOffset;
494		}
495	}
496}
497
498void GBACheatSetCopyProperties(struct GBACheatSet* newSet, struct GBACheatSet* set) {
499	newSet->gsaVersion = set->gsaVersion;
500	memcpy(newSet->gsaSeeds, set->gsaSeeds, sizeof(newSet->gsaSeeds));
501	if (set->hook) {
502		if (newSet->hook) {
503			--newSet->hook->refs;
504			if (newSet->hook->refs == 0) {
505				free(newSet->hook);
506			}
507		}
508		newSet->hook = set->hook;
509		++newSet->hook->refs;
510	}
511}
512
513void GBACheatDeviceInit(struct ARMCore* cpu, struct ARMComponent* component) {
514	struct GBACheatDevice* device = (struct GBACheatDevice*) component;
515	device->p = (struct GBA*) cpu->master;
516	size_t i;
517	for (i = 0; i < GBACheatSetsSize(&device->cheats); ++i) {
518		struct GBACheatSet* cheats = *GBACheatSetsGetPointer(&device->cheats, i);
519		_addBreakpoint(device, cheats);
520		_patchROM(device, cheats);
521	}
522}
523
524void GBACheatDeviceDeinit(struct ARMComponent* component) {
525	struct GBACheatDevice* device = (struct GBACheatDevice*) component;
526	size_t i;
527	for (i = GBACheatSetsSize(&device->cheats); i--;) {
528		struct GBACheatSet* cheats = *GBACheatSetsGetPointer(&device->cheats, i);
529		_unpatchROM(device, cheats);
530		_removeBreakpoint(device, cheats);
531	}
532}