mirror of https://github.com/mgba-emu/mgba.git
654 lines
17 KiB
C
654 lines
17 KiB
C
/* Copyright (c) 2013-2016 Jeffrey Pfau
|
|
*
|
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
#include <mgba/core/cheats.h>
|
|
|
|
#include <mgba/core/core.h>
|
|
#include <mgba-util/string.h>
|
|
#include <mgba-util/vfs.h>
|
|
|
|
#define MAX_LINE_LENGTH 512
|
|
#define MAX_CHEATS 1000
|
|
|
|
const uint32_t M_CHEAT_DEVICE_ID = 0xABADC0DE;
|
|
|
|
mLOG_DEFINE_CATEGORY(CHEATS, "Cheats", "core.cheats");
|
|
|
|
DEFINE_VECTOR(mCheatList, struct mCheat);
|
|
DEFINE_VECTOR(mCheatSets, struct mCheatSet*);
|
|
|
|
static int32_t _readMem(struct mCore* core, uint32_t address, int width) {
|
|
switch (width) {
|
|
case 1:
|
|
return core->busRead8(core, address);
|
|
case 2:
|
|
return core->busRead16(core, address);
|
|
case 4:
|
|
return core->busRead32(core, address);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static void _writeMem(struct mCore* core, uint32_t address, int width, int32_t value) {
|
|
switch (width) {
|
|
case 1:
|
|
core->busWrite8(core, address, value);
|
|
break;
|
|
case 2:
|
|
core->busWrite16(core, address, value);
|
|
break;
|
|
case 4:
|
|
core->busWrite32(core, address, value);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void mCheatDeviceInit(void*, struct mCPUComponent*);
|
|
static void mCheatDeviceDeinit(struct mCPUComponent*);
|
|
|
|
void mCheatDeviceCreate(struct mCheatDevice* device) {
|
|
device->d.id = M_CHEAT_DEVICE_ID;
|
|
device->d.init = mCheatDeviceInit;
|
|
device->d.deinit = mCheatDeviceDeinit;
|
|
device->autosave = false;
|
|
device->buttonDown = false;
|
|
mCheatSetsInit(&device->cheats, 4);
|
|
}
|
|
|
|
void mCheatDeviceDestroy(struct mCheatDevice* device) {
|
|
mCheatDeviceClear(device);
|
|
mCheatSetsDeinit(&device->cheats);
|
|
}
|
|
|
|
void mCheatDeviceClear(struct mCheatDevice* device) {
|
|
size_t i;
|
|
for (i = 0; i < mCheatSetsSize(&device->cheats); ++i) {
|
|
struct mCheatSet* set = *mCheatSetsGetPointer(&device->cheats, i);
|
|
mCheatSetDeinit(set);
|
|
}
|
|
mCheatSetsClear(&device->cheats);
|
|
}
|
|
|
|
void mCheatSetInit(struct mCheatSet* set, const char* name) {
|
|
mCheatListInit(&set->list, 4);
|
|
StringListInit(&set->lines, 4);
|
|
if (name) {
|
|
set->name = strdup(name);
|
|
} else {
|
|
set->name = 0;
|
|
}
|
|
set->enabled = true;
|
|
}
|
|
|
|
void mCheatSetDeinit(struct mCheatSet* set) {
|
|
size_t i;
|
|
for (i = 0; i < StringListSize(&set->lines); ++i) {
|
|
free(*StringListGetPointer(&set->lines, i));
|
|
}
|
|
mCheatListDeinit(&set->list);
|
|
if (set->name) {
|
|
free(set->name);
|
|
}
|
|
StringListDeinit(&set->lines);
|
|
set->deinit(set);
|
|
free(set);
|
|
}
|
|
|
|
void mCheatSetRename(struct mCheatSet* set, const char* name) {
|
|
if (set->name) {
|
|
free(set->name);
|
|
set->name = NULL;
|
|
}
|
|
if (name) {
|
|
set->name = strdup(name);
|
|
}
|
|
}
|
|
|
|
bool mCheatAddLine(struct mCheatSet* set, const char* line, int type) {
|
|
if (!set->addLine(set, line, type)) {
|
|
return false;
|
|
}
|
|
*StringListAppend(&set->lines) = strdup(line);
|
|
return true;
|
|
}
|
|
|
|
void mCheatAddSet(struct mCheatDevice* device, struct mCheatSet* cheats) {
|
|
*mCheatSetsAppend(&device->cheats) = cheats;
|
|
cheats->add(cheats, device);
|
|
}
|
|
|
|
void mCheatRemoveSet(struct mCheatDevice* device, struct mCheatSet* cheats) {
|
|
size_t i;
|
|
for (i = 0; i < mCheatSetsSize(&device->cheats); ++i) {
|
|
if (*mCheatSetsGetPointer(&device->cheats, i) == cheats) {
|
|
break;
|
|
}
|
|
}
|
|
if (i == mCheatSetsSize(&device->cheats)) {
|
|
return;
|
|
}
|
|
mCheatSetsShift(&device->cheats, i, 1);
|
|
cheats->remove(cheats, device);
|
|
}
|
|
|
|
bool mCheatParseFile(struct mCheatDevice* device, struct VFile* vf) {
|
|
char cheat[MAX_LINE_LENGTH];
|
|
struct mCheatSet* set = NULL;
|
|
struct mCheatSet* newSet;
|
|
bool nextDisabled = false;
|
|
struct StringList directives;
|
|
StringListInit(&directives, 4);
|
|
|
|
while (true) {
|
|
size_t i = 0;
|
|
ssize_t bytesRead = vf->readline(vf, cheat, sizeof(cheat));
|
|
rtrim(cheat);
|
|
if (bytesRead == 0) {
|
|
break;
|
|
}
|
|
if (bytesRead < 0) {
|
|
StringListDeinit(&directives);
|
|
return false;
|
|
}
|
|
while (isspace((int) cheat[i])) {
|
|
++i;
|
|
}
|
|
switch (cheat[i]) {
|
|
case '#':
|
|
do {
|
|
++i;
|
|
} while (isspace((int) cheat[i]));
|
|
newSet = device->createSet(device, &cheat[i]);
|
|
newSet->enabled = !nextDisabled;
|
|
nextDisabled = false;
|
|
if (set) {
|
|
mCheatAddSet(device, set);
|
|
}
|
|
if (set) {
|
|
newSet->copyProperties(newSet, set);
|
|
}
|
|
newSet->parseDirectives(newSet, &directives);
|
|
set = newSet;
|
|
break;
|
|
case '!':
|
|
do {
|
|
++i;
|
|
} while (isspace((int) cheat[i]));
|
|
if (strcasecmp(&cheat[i], "disabled") == 0) {
|
|
nextDisabled = true;
|
|
break;
|
|
}
|
|
if (strcasecmp(&cheat[i], "reset") == 0) {
|
|
size_t d;
|
|
for (d = 0; d < StringListSize(&directives); ++d) {
|
|
free(*StringListGetPointer(&directives, d));
|
|
}
|
|
StringListClear(&directives);
|
|
break;
|
|
}
|
|
*StringListAppend(&directives) = strdup(&cheat[i]);
|
|
break;
|
|
default:
|
|
if (!set) {
|
|
if (strncmp(cheat, "cheats = ", 9) == 0) {
|
|
// This is in libretro format, switch over to that parser
|
|
vf->seek(vf, 0, SEEK_SET);
|
|
StringListDeinit(&directives);
|
|
return mCheatParseLibretroFile(device, vf);
|
|
}
|
|
if (cheat[0] == '[') {
|
|
// This is in EZ Flash CHT format, switch over to that parser
|
|
vf->seek(vf, 0, SEEK_SET);
|
|
StringListDeinit(&directives);
|
|
return mCheatParseEZFChtFile(device, vf);
|
|
}
|
|
set = device->createSet(device, NULL);
|
|
set->enabled = !nextDisabled;
|
|
nextDisabled = false;
|
|
}
|
|
mCheatAddLine(set, cheat, 0);
|
|
break;
|
|
}
|
|
}
|
|
if (set) {
|
|
mCheatAddSet(device, set);
|
|
}
|
|
size_t d;
|
|
for (d = 0; d < StringListSize(&directives); ++d) {
|
|
free(*StringListGetPointer(&directives, d));
|
|
}
|
|
StringListClear(&directives);
|
|
StringListDeinit(&directives);
|
|
return true;
|
|
}
|
|
|
|
bool mCheatParseLibretroFile(struct mCheatDevice* device, struct VFile* vf) {
|
|
char cheat[MAX_LINE_LENGTH];
|
|
char parsed[MAX_LINE_LENGTH];
|
|
struct mCheatSet* set = NULL;
|
|
unsigned long i = 0;
|
|
bool startFound = false;
|
|
|
|
while (true) {
|
|
ssize_t bytesRead = vf->readline(vf, cheat, sizeof(cheat));
|
|
if (bytesRead == 0) {
|
|
break;
|
|
}
|
|
if (bytesRead < 0) {
|
|
return false;
|
|
}
|
|
if (cheat[0] == '\n') {
|
|
continue;
|
|
}
|
|
if (strncmp(cheat, "cheat", 5) != 0) {
|
|
return false;
|
|
}
|
|
char* underscore = strchr(&cheat[5], '_');
|
|
if (!underscore) {
|
|
if (!startFound && cheat[5] == 's') {
|
|
startFound = true;
|
|
char* eq = strchr(&cheat[6], '=');
|
|
if (!eq) {
|
|
return false;
|
|
}
|
|
++eq;
|
|
while (isspace((int) eq[0])) {
|
|
if (eq[0] == '\0') {
|
|
return false;
|
|
}
|
|
++eq;
|
|
}
|
|
|
|
char* end;
|
|
unsigned long nCheats = strtoul(eq, &end, 10);
|
|
if (end[0] != '\0' && !isspace(end[0])) {
|
|
return false;
|
|
}
|
|
|
|
if (nCheats > MAX_CHEATS) {
|
|
return false;
|
|
}
|
|
|
|
while (nCheats > mCheatSetsSize(&device->cheats)) {
|
|
struct mCheatSet* newSet = device->createSet(device, NULL);
|
|
if (!newSet) {
|
|
return false;
|
|
}
|
|
mCheatAddSet(device, newSet);
|
|
}
|
|
continue;
|
|
}
|
|
return false;
|
|
}
|
|
char* underscore2;
|
|
i = strtoul(&cheat[5], &underscore2, 10);
|
|
if (underscore2 != underscore) {
|
|
return false;
|
|
}
|
|
++underscore;
|
|
char* eq = strchr(underscore, '=');
|
|
if (!eq) {
|
|
return false;
|
|
}
|
|
++eq;
|
|
while (isspace((int) eq[0])) {
|
|
if (eq[0] == '\0') {
|
|
return false;
|
|
}
|
|
++eq;
|
|
}
|
|
|
|
if (i >= mCheatSetsSize(&device->cheats)) {
|
|
return false;
|
|
}
|
|
set = *mCheatSetsGetPointer(&device->cheats, i);
|
|
|
|
if (strncmp(underscore, "desc", 4) == 0) {
|
|
parseQuotedString(eq, strlen(eq), parsed, sizeof(parsed));
|
|
mCheatSetRename(set, parsed);
|
|
} else if (strncmp(underscore, "enable", 6) == 0) {
|
|
set->enabled = strncmp(eq, "true\n", 5) == 0;
|
|
} else if (strncmp(underscore, "code", 4) == 0) {
|
|
parseQuotedString(eq, strlen(eq), parsed, sizeof(parsed));
|
|
char* cur = parsed;
|
|
char* next;
|
|
while ((next = strchr(cur, '+'))) {
|
|
next[0] = '\0';
|
|
mCheatAddLine(set, cur, 0);
|
|
cur = &next[1];
|
|
}
|
|
mCheatAddLine(set, cur, 0);
|
|
|
|
for (++i; i < mCheatSetsSize(&device->cheats); ++i) {
|
|
struct mCheatSet* newSet = *mCheatSetsGetPointer(&device->cheats, i);
|
|
newSet->copyProperties(newSet, set);
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool mCheatParseEZFChtFile(struct mCheatDevice* device, struct VFile* vf) {
|
|
char cheat[MAX_LINE_LENGTH];
|
|
char cheatName[MAX_LINE_LENGTH];
|
|
char miniline[32];
|
|
size_t cheatNameLength;
|
|
struct mCheatSet* set = NULL;
|
|
|
|
cheatName[MAX_LINE_LENGTH - 1] = '\0';
|
|
while (true) {
|
|
ssize_t bytesRead = vf->readline(vf, cheat, sizeof(cheat));
|
|
if (bytesRead == 0) {
|
|
break;
|
|
}
|
|
if (bytesRead < 0) {
|
|
return false;
|
|
}
|
|
if (cheat[0] == '\n' || (bytesRead >= 2 && cheat[0] == '\r' && cheat[1] == '\n')) {
|
|
continue;
|
|
}
|
|
|
|
if (cheat[0] == '[') {
|
|
if (strncmp(cheat, "[GameInfo]", 10) == 0) {
|
|
break;
|
|
}
|
|
char* end = strchr(cheat, ']');
|
|
if (!end) {
|
|
return false;
|
|
}
|
|
char* name = gbkToUtf8(&cheat[1], end - cheat - 1);
|
|
strncpy(cheatName, name, sizeof(cheatName) - 1);
|
|
free(name);
|
|
cheatNameLength = strlen(cheatName);
|
|
continue;
|
|
}
|
|
|
|
char* eq = strchr(cheat, '=');
|
|
if (!eq) {
|
|
continue;
|
|
}
|
|
if (strncmp(cheat, "ON", eq - cheat) != 0) {
|
|
char* subname = gbkToUtf8(cheat, eq - cheat);
|
|
snprintf(&cheatName[cheatNameLength], sizeof(cheatName) - cheatNameLength - 1, ": %s", subname);
|
|
}
|
|
set = device->createSet(device, cheatName);
|
|
set->enabled = false;
|
|
mCheatAddSet(device, set);
|
|
cheatName[cheatNameLength] = '\0';
|
|
++eq;
|
|
|
|
uint32_t gameptr = 0;
|
|
uint32_t hexval = 0;
|
|
int digit;
|
|
while (eq[0] != '\r' && eq[1] != '\n') {
|
|
if (cheat + bytesRead == eq || eq[0] == '\0') {
|
|
bytesRead = vf->readline(vf, cheat, sizeof(cheat));
|
|
eq = cheat;
|
|
if (bytesRead == 0) {
|
|
break;
|
|
}
|
|
if (bytesRead < 0) {
|
|
return false;
|
|
}
|
|
}
|
|
switch (eq[0]) {
|
|
case ',':
|
|
if (!gameptr) {
|
|
gameptr = hexval;
|
|
if (hexval < 0x40000) {
|
|
gameptr += 0x02000000;
|
|
} else {
|
|
gameptr += 0x03000000 - 0x40000;
|
|
}
|
|
} else {
|
|
if (hexval > 0xFF) {
|
|
return false;
|
|
}
|
|
snprintf(miniline, sizeof(miniline) - 1, "%08X:%02X", gameptr, hexval);
|
|
mCheatAddLine(set, miniline, 0);
|
|
++gameptr;
|
|
}
|
|
hexval = 0;
|
|
break;
|
|
case ';':
|
|
if (hexval > 0xFF) {
|
|
return false;
|
|
}
|
|
snprintf(miniline, sizeof(miniline) - 1, "%08X:%02X", gameptr, hexval);
|
|
mCheatAddLine(set, miniline, 0);
|
|
hexval = 0;
|
|
gameptr = 0;
|
|
break;
|
|
default:
|
|
digit = hexDigit(eq[0]);
|
|
if (digit < 0) {
|
|
return false;
|
|
}
|
|
hexval <<= 4;
|
|
hexval |= digit;
|
|
break;
|
|
}
|
|
++eq;
|
|
}
|
|
if (gameptr) {
|
|
if (hexval > 0xFF) {
|
|
return false;
|
|
}
|
|
snprintf(miniline, sizeof(miniline) - 1, "%08X:%02X", gameptr, hexval);
|
|
mCheatAddLine(set, miniline, 0);
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool mCheatSaveFile(struct mCheatDevice* device, struct VFile* vf) {
|
|
static const char lineStart[3] = "# ";
|
|
static const char lineEnd = '\n';
|
|
struct StringList directives;
|
|
StringListInit(&directives, 4);
|
|
|
|
size_t i;
|
|
for (i = 0; i < mCheatSetsSize(&device->cheats); ++i) {
|
|
struct mCheatSet* set = *mCheatSetsGetPointer(&device->cheats, i);
|
|
set->dumpDirectives(set, &directives);
|
|
if (!set->enabled) {
|
|
static const char* disabledDirective = "!disabled\n";
|
|
vf->write(vf, disabledDirective, strlen(disabledDirective));
|
|
}
|
|
size_t d;
|
|
for (d = 0; d < StringListSize(&directives); ++d) {
|
|
char directive[64];
|
|
ssize_t len = snprintf(directive, sizeof(directive) - 1, "!%s\n", *StringListGetPointer(&directives, d));
|
|
if (len > 1) {
|
|
vf->write(vf, directive, (size_t) len > sizeof(directive) ? sizeof(directive) : (size_t) len);
|
|
}
|
|
}
|
|
|
|
vf->write(vf, lineStart, 2);
|
|
if (set->name) {
|
|
vf->write(vf, set->name, strlen(set->name));
|
|
}
|
|
vf->write(vf, &lineEnd, 1);
|
|
size_t c;
|
|
for (c = 0; c < StringListSize(&set->lines); ++c) {
|
|
const char* line = *StringListGetPointer(&set->lines, c);
|
|
vf->write(vf, line, strlen(line));
|
|
vf->write(vf, &lineEnd, 1);
|
|
}
|
|
}
|
|
size_t d;
|
|
for (d = 0; d < StringListSize(&directives); ++d) {
|
|
free(*StringListGetPointer(&directives, d));
|
|
}
|
|
StringListClear(&directives);
|
|
StringListDeinit(&directives);
|
|
return true;
|
|
}
|
|
|
|
#if !defined(MINIMAL_CORE) || MINIMAL_CORE < 2
|
|
void mCheatAutosave(struct mCheatDevice* device) {
|
|
if (!device->autosave) {
|
|
return;
|
|
}
|
|
struct VFile* vf = mDirectorySetOpenSuffix(&device->p->dirs, device->p->dirs.cheats, ".cheats", O_WRONLY | O_CREAT | O_TRUNC);
|
|
if (!vf) {
|
|
return;
|
|
}
|
|
mCheatSaveFile(device, vf);
|
|
vf->close(vf);
|
|
}
|
|
#endif
|
|
|
|
void mCheatRefresh(struct mCheatDevice* device, struct mCheatSet* cheats) {
|
|
cheats->refresh(cheats, device);
|
|
if (!cheats->enabled) {
|
|
return;
|
|
}
|
|
|
|
size_t elseLoc = 0;
|
|
size_t endLoc = 0;
|
|
size_t nCodes = mCheatListSize(&cheats->list);
|
|
size_t i;
|
|
for (i = 0; i < nCodes; ++i) {
|
|
struct mCheat* cheat = mCheatListGetPointer(&cheats->list, i);
|
|
int32_t value = 0;
|
|
int32_t operand = cheat->operand;
|
|
uint32_t operationsRemaining = cheat->repeat;
|
|
uint32_t address = cheat->address;
|
|
bool performAssignment = false;
|
|
bool condition = true;
|
|
int conditionRemaining = 0;
|
|
int negativeConditionRemaining = 0;
|
|
|
|
for (; operationsRemaining; --operationsRemaining) {
|
|
switch (cheat->type) {
|
|
case CHEAT_ASSIGN:
|
|
value = operand;
|
|
performAssignment = true;
|
|
break;
|
|
case CHEAT_ASSIGN_INDIRECT:
|
|
value = operand;
|
|
address = _readMem(device->p, address + cheat->addressOffset, 4);
|
|
performAssignment = true;
|
|
break;
|
|
case CHEAT_AND:
|
|
value = _readMem(device->p, address, cheat->width) & operand;
|
|
performAssignment = true;
|
|
break;
|
|
case CHEAT_ADD:
|
|
value = _readMem(device->p, address, cheat->width) + operand;
|
|
performAssignment = true;
|
|
break;
|
|
case CHEAT_OR:
|
|
value = _readMem(device->p, address, cheat->width) | operand;
|
|
performAssignment = true;
|
|
break;
|
|
case CHEAT_IF_EQ:
|
|
condition = _readMem(device->p, address, cheat->width) == operand;
|
|
conditionRemaining = cheat->repeat;
|
|
negativeConditionRemaining = cheat->negativeRepeat;
|
|
operationsRemaining = 1;
|
|
break;
|
|
case CHEAT_IF_NE:
|
|
condition = _readMem(device->p, address, cheat->width) != operand;
|
|
conditionRemaining = cheat->repeat;
|
|
negativeConditionRemaining = cheat->negativeRepeat;
|
|
operationsRemaining = 1;
|
|
break;
|
|
case CHEAT_IF_LT:
|
|
condition = _readMem(device->p, address, cheat->width) < operand;
|
|
conditionRemaining = cheat->repeat;
|
|
negativeConditionRemaining = cheat->negativeRepeat;
|
|
operationsRemaining = 1;
|
|
break;
|
|
case CHEAT_IF_GT:
|
|
condition = _readMem(device->p, address, cheat->width) > operand;
|
|
conditionRemaining = cheat->repeat;
|
|
negativeConditionRemaining = cheat->negativeRepeat;
|
|
operationsRemaining = 1;
|
|
break;
|
|
case CHEAT_IF_ULT:
|
|
condition = (uint32_t) _readMem(device->p, address, cheat->width) < (uint32_t) operand;
|
|
conditionRemaining = cheat->repeat;
|
|
negativeConditionRemaining = cheat->negativeRepeat;
|
|
operationsRemaining = 1;
|
|
break;
|
|
case CHEAT_IF_UGT:
|
|
condition = (uint32_t) _readMem(device->p, address, cheat->width) > (uint32_t) operand;
|
|
conditionRemaining = cheat->repeat;
|
|
negativeConditionRemaining = cheat->negativeRepeat;
|
|
operationsRemaining = 1;
|
|
break;
|
|
case CHEAT_IF_AND:
|
|
condition = _readMem(device->p, address, cheat->width) & operand;
|
|
conditionRemaining = cheat->repeat;
|
|
negativeConditionRemaining = cheat->negativeRepeat;
|
|
operationsRemaining = 1;
|
|
break;
|
|
case CHEAT_IF_LAND:
|
|
condition = _readMem(device->p, address, cheat->width) && operand;
|
|
conditionRemaining = cheat->repeat;
|
|
negativeConditionRemaining = cheat->negativeRepeat;
|
|
operationsRemaining = 1;
|
|
break;
|
|
case CHEAT_IF_NAND:
|
|
condition = !(_readMem(device->p, address, cheat->width) & operand);
|
|
conditionRemaining = cheat->repeat;
|
|
negativeConditionRemaining = cheat->negativeRepeat;
|
|
operationsRemaining = 1;
|
|
break;
|
|
case CHEAT_IF_BUTTON:
|
|
condition = device->buttonDown;
|
|
conditionRemaining = cheat->repeat;
|
|
negativeConditionRemaining = cheat->negativeRepeat;
|
|
operationsRemaining = 1;
|
|
break;
|
|
}
|
|
|
|
if (performAssignment) {
|
|
_writeMem(device->p, address, cheat->width, value);
|
|
}
|
|
|
|
address += cheat->addressOffset;
|
|
operand += cheat->operandOffset;
|
|
}
|
|
|
|
|
|
if (elseLoc && i == elseLoc) {
|
|
i = endLoc;
|
|
endLoc = 0;
|
|
}
|
|
if (conditionRemaining > 0 && !condition) {
|
|
i += conditionRemaining;
|
|
} else if (negativeConditionRemaining > 0) {
|
|
elseLoc = i + conditionRemaining;
|
|
endLoc = elseLoc + negativeConditionRemaining;
|
|
}
|
|
}
|
|
}
|
|
|
|
void mCheatPressButton(struct mCheatDevice* device, bool down) {
|
|
device->buttonDown = down;
|
|
}
|
|
|
|
void mCheatDeviceInit(void* cpu, struct mCPUComponent* component) {
|
|
UNUSED(cpu);
|
|
struct mCheatDevice* device = (struct mCheatDevice*) component;
|
|
size_t i;
|
|
for (i = 0; i < mCheatSetsSize(&device->cheats); ++i) {
|
|
struct mCheatSet* cheats = *mCheatSetsGetPointer(&device->cheats, i);
|
|
cheats->add(cheats, device);
|
|
}
|
|
}
|
|
|
|
void mCheatDeviceDeinit(struct mCPUComponent* component) {
|
|
struct mCheatDevice* device = (struct mCheatDevice*) component;
|
|
size_t i;
|
|
for (i = mCheatSetsSize(&device->cheats); i--;) {
|
|
struct mCheatSet* cheats = *mCheatSetsGetPointer(&device->cheats, i);
|
|
cheats->remove(cheats, device);
|
|
}
|
|
}
|