Core: Improve support for ROM patch cheats, supporting disabling overlapping patches
@@ -92,6 +92,7 @@ - 3DS: Batch directory reads
- Core: Add savedataUpdated callback - Core: Add shutdown callback - Core: Rework thread state synchronization + - Core: Improve support for ROM patch cheats, supporting disabling overlapping patches - GB: Allow pausing event loop while CPU is blocked - GB: Add support for sleep and shutdown callbacks - GB I/O: Implement preliminary support for PCM12/PCM34 (closes mgba.io/i/1468)
@@ -12,6 +12,7 @@ CXX_GUARD_START
#include <mgba/core/cpu.h> #include <mgba/core/log.h> +#include <mgba-util/table.h> #include <mgba-util/vector.h> enum mCheatType {@@ -44,9 +45,20 @@ int32_t addressOffset;
int32_t operandOffset; }; +struct mCheatPatch { + uint32_t address; + int segment; + uint32_t value; + int width; + bool applied; + uint32_t checkValue; + bool check; +}; + mLOG_DECLARE_CATEGORY(CHEATS); DECLARE_VECTOR(mCheatList, struct mCheat); +DECLARE_VECTOR(mCheatPatchList, struct mCheatPatch); struct mCheatDevice; struct mCheatSet {@@ -66,6 +78,7 @@ void (*refresh)(struct mCheatSet* set, struct mCheatDevice* device);
char* name; bool enabled; + struct mCheatPatchList romPatches; struct StringList lines; };@@ -78,6 +91,7 @@
struct mCheatSet* (*createSet)(struct mCheatDevice*, const char* name); struct mCheatSets cheats; + struct Table unpatchedMemory; bool autosave; bool buttonDown; };
@@ -11,29 +11,12 @@
CXX_GUARD_START #include <mgba/core/cheats.h> -#include <mgba-util/vector.h> enum GBCheatType { GB_CHEAT_AUTODETECT, GB_CHEAT_GAMESHARK, GB_CHEAT_GAME_GENIE, GB_CHEAT_VBA -}; - -struct GBCheatPatch { - uint16_t address; - int8_t newValue; - int8_t oldValue; - int segment; - bool applied; - bool checkByte; -}; - -DECLARE_VECTOR(GBCheatPatchList, struct GBCheatPatch); - -struct GBCheatSet { - struct mCheatSet d; - struct GBCheatPatchList romPatches; }; struct mCheatDevice* GBCheatDeviceCreate(void);
@@ -134,23 +134,14 @@ size_t refs;
size_t reentries; }; -struct GBACheatPatch { - uint32_t address; - int16_t newValue; - int16_t oldValue; - bool applied; -}; - DECLARE_VECTOR(GBACheatPatchList, struct GBACheatPatch); struct GBACheatSet { struct mCheatSet d; struct GBACheatHook* hook; - struct GBACheatPatchList romPatches; - size_t incompleteCheat; - struct GBACheatPatch* incompletePatch; + struct mCheatPatch* incompletePatch; size_t currentBlock; int gsaVersion;
@@ -18,6 +18,33 @@ mLOG_DEFINE_CATEGORY(CHEATS, "Cheats", "core.cheats");
DEFINE_VECTOR(mCheatList, struct mCheat); DEFINE_VECTOR(mCheatSets, struct mCheatSet*); +DEFINE_VECTOR(mCheatPatchList, struct mCheatPatch); + +struct mCheatPatchedMem { + uint32_t originalValue; + int refs; + bool dirty; +}; + +static uint32_t _patchMakeKey(struct mCheatPatch* patch) { + // NB: This assumes patches have only one valid size per platform + uint32_t patchKey = patch->address; + switch (patch->width) { + case 2: + patchKey >>= 1; + break; + case 4: + patchKey >>= 2; + break; + default: + break; + } + // TODO: More than one segment + if (patch->segment > 0) { + patchKey |= patch->segment << 16; + } + return patchKey; +} static int32_t _readMem(struct mCore* core, uint32_t address, int width) { switch (width) {@@ -31,6 +58,18 @@ }
return 0; } +static int32_t _readMemSegment(struct mCore* core, uint32_t address, int segment, int width) { + switch (width) { + case 1: + return core->rawRead8(core, address, segment); + case 2: + return core->rawRead16(core, address, segment); + case 4: + return core->rawRead32(core, address, segment); + } + return 0; +} + static void _writeMem(struct mCore* core, uint32_t address, int width, int32_t value) { switch (width) { case 1:@@ -45,6 +84,83 @@ break;
} } +static void _patchMem(struct mCore* core, uint32_t address, int segment, int width, int32_t value) { + switch (width) { + case 1: + core->rawWrite8(core, address, segment, value); + break; + case 2: + core->rawWrite16(core, address, segment, value); + break; + case 4: + core->rawWrite32(core, address, segment, value); + break; + } +} + +static void _patchROM(struct mCheatDevice* device, struct mCheatSet* cheats) { + if (!device->p) { + return; + } + size_t i; + for (i = 0; i < mCheatPatchListSize(&cheats->romPatches); ++i) { + struct mCheatPatch* patch = mCheatPatchListGetPointer(&cheats->romPatches, i); + int segment = -1; + if (patch->check && patch->segment < 0) { + int maxSegment = 0; + for (segment = 0; segment < maxSegment; ++segment) { + uint32_t value = _readMemSegment(device->p, patch->address, segment, patch->width); + if (value == patch->checkValue) { + break; + } + } + if (segment == maxSegment) { + continue; + } + } + patch->segment = segment; + + uint32_t patchKey = _patchMakeKey(patch); + struct mCheatPatchedMem* patchData = TableLookup(&device->unpatchedMemory, patchKey); + if (!patchData) { + patchData = malloc(sizeof(*patchData)); + patchData->originalValue = _readMemSegment(device->p, patch->address, segment, patch->width); + patchData->refs = 1; + patchData->dirty = false; + TableInsert(&device->unpatchedMemory, patchKey, patchData); + } else if (!patch->applied) { + ++patchData->refs; + patchData->dirty = true; + } else if (!patchData->dirty) { + continue; + } + _patchMem(device->p, patch->address, segment, patch->width, patch->value); + patch->applied = true; + } +} + +static void _unpatchROM(struct mCheatDevice* device, struct mCheatSet* cheats) { + if (!device->p) { + return; + } + size_t i; + for (i = 0; i < mCheatPatchListSize(&cheats->romPatches); ++i) { + struct mCheatPatch* patch = mCheatPatchListGetPointer(&cheats->romPatches, i); + if (!patch->applied) { + continue; + } + uint32_t patchKey = _patchMakeKey(patch); + struct mCheatPatchedMem* patchData = TableLookup(&device->unpatchedMemory, patchKey); + --patchData->refs; + patchData->dirty = true; + if (patchData->refs <= 0) { + _patchMem(device->p, patch->address, patch->segment, patch->width, patchData->originalValue); + TableRemove(&device->unpatchedMemory, patchKey); + } + patch->applied = false; + } +} + static void mCheatDeviceInit(void*, struct mCPUComponent*); static void mCheatDeviceDeinit(struct mCPUComponent*);@@ -55,11 +171,13 @@ device->d.deinit = mCheatDeviceDeinit;
device->autosave = false; device->buttonDown = false; mCheatSetsInit(&device->cheats, 4); + TableInit(&device->unpatchedMemory, 4, free); } void mCheatDeviceDestroy(struct mCheatDevice* device) { mCheatDeviceClear(device); mCheatSetsDeinit(&device->cheats); + TableDeinit(&device->unpatchedMemory); free(device); }@@ -75,6 +193,7 @@
void mCheatSetInit(struct mCheatSet* set, const char* name) { mCheatListInit(&set->list, 4); StringListInit(&set->lines, 4); + mCheatPatchListInit(&set->romPatches, 4); if (name) { set->name = strdup(name); } else {@@ -93,7 +212,10 @@ if (set->name) {
free(set->name); } StringListDeinit(&set->lines); - set->deinit(set); + mCheatPatchListDeinit(&set->romPatches); + if (set->deinit) { + set->deinit(set); + } free(set); }@@ -117,7 +239,9 @@ }
void mCheatAddSet(struct mCheatDevice* device, struct mCheatSet* cheats) { *mCheatSetsAppend(&device->cheats) = cheats; - cheats->add(cheats, device); + if (cheats->add) { + cheats->add(cheats, device); + } } void mCheatRemoveSet(struct mCheatDevice* device, struct mCheatSet* cheats) {@@ -131,7 +255,9 @@ if (i == mCheatSetsSize(&device->cheats)) {
return; } mCheatSetsShift(&device->cheats, i, 1); - cheats->remove(cheats, device); + if (cheats->remove) { + cheats->remove(cheats, device); + } } bool mCheatParseFile(struct mCheatDevice* device, struct VFile* vf) {@@ -503,8 +629,14 @@ }
#endif void mCheatRefresh(struct mCheatDevice* device, struct mCheatSet* cheats) { - cheats->refresh(cheats, device); + if (cheats->enabled) { + _patchROM(device, cheats); + } + if (cheats->refresh) { + cheats->refresh(cheats, device); + } if (!cheats->enabled) { + _unpatchROM(device, cheats); return; }
@@ -10,58 +10,6 @@ #include <mgba/internal/gb/gb.h>
#include <mgba/internal/gb/memory.h> #include <mgba-util/string.h> -DEFINE_VECTOR(GBCheatPatchList, struct GBCheatPatch); - -static void _patchROM(struct mCheatDevice* device, struct GBCheatSet* cheats) { - if (!device->p) { - return; - } - size_t i; - for (i = 0; i < GBCheatPatchListSize(&cheats->romPatches); ++i) { - struct GBCheatPatch* patch = GBCheatPatchListGetPointer(&cheats->romPatches, i); - if (patch->applied) { - continue; - } - int segment = 0; - if (patch->checkByte) { - struct GB* gb = device->p->board; - int maxSegment = (gb->memory.romSize + GB_SIZE_CART_BANK0 - 1) / GB_SIZE_CART_BANK0; - for (; segment < maxSegment; ++segment) { - int8_t value = GBView8(device->p->cpu, patch->address, segment); - if (value == patch->oldValue) { - break; - } - } - if (segment == maxSegment) { - continue; - } - } - // TODO: More than one segment - GBPatch8(device->p->cpu, patch->address, patch->newValue, &patch->oldValue, segment); - patch->applied = true; - patch->segment = segment; - } -} - -static void _unpatchROM(struct mCheatDevice* device, struct GBCheatSet* cheats) { - if (!device->p) { - return; - } - size_t i; - for (i = 0; i < GBCheatPatchListSize(&cheats->romPatches); ++i) { - struct GBCheatPatch* patch = GBCheatPatchListGetPointer(&cheats->romPatches, i); - if (!patch->applied) { - continue; - } - GBPatch8(device->p->cpu, patch->address, patch->oldValue, &patch->newValue, patch->segment); - patch->applied = false; - } -} - -static void GBCheatSetDeinit(struct mCheatSet* set); -static void GBCheatAddSet(struct mCheatSet* cheats, struct mCheatDevice* device); -static void GBCheatRemoveSet(struct mCheatSet* cheats, struct mCheatDevice* device); -static void GBCheatRefresh(struct mCheatSet* cheats, struct mCheatDevice* device); static void GBCheatSetCopyProperties(struct mCheatSet* set, struct mCheatSet* oldSet); static void GBCheatParseDirectives(struct mCheatSet* set, const struct StringList* directives); static void GBCheatDumpDirectives(struct mCheatSet* set, struct StringList* directives);@@ -69,23 +17,21 @@ static bool GBCheatAddLine(struct mCheatSet*, const char* line, int type);
static struct mCheatSet* GBCheatSetCreate(struct mCheatDevice* device, const char* name) { UNUSED(device); - struct GBCheatSet* set = malloc(sizeof(*set)); - mCheatSetInit(&set->d, name); + struct mCheatSet* set = malloc(sizeof(*set)); + mCheatSetInit(set, name); - GBCheatPatchListInit(&set->romPatches, 0); + set->deinit = NULL; + set->add = NULL; + set->remove = NULL; - set->d.deinit = GBCheatSetDeinit; - set->d.add = GBCheatAddSet; - set->d.remove = GBCheatRemoveSet; + set->addLine = GBCheatAddLine; + set->copyProperties = GBCheatSetCopyProperties; - set->d.addLine = GBCheatAddLine; - set->d.copyProperties = GBCheatSetCopyProperties; - - set->d.parseDirectives = GBCheatParseDirectives; - set->d.dumpDirectives = GBCheatDumpDirectives; + set->parseDirectives = GBCheatParseDirectives; + set->dumpDirectives = GBCheatDumpDirectives; - set->d.refresh = GBCheatRefresh; - return &set->d; + set->refresh = NULL; + return set; } struct mCheatDevice* GBCheatDeviceCreate(void) {@@ -95,23 +41,8 @@ device->createSet = GBCheatSetCreate;
return device; } -static void GBCheatSetDeinit(struct mCheatSet* set) { - struct GBCheatSet* gbset = (struct GBCheatSet*) set; - GBCheatPatchListDeinit(&gbset->romPatches); -} - -static void GBCheatAddSet(struct mCheatSet* cheats, struct mCheatDevice* device) { - struct GBCheatSet* gbset = (struct GBCheatSet*) cheats; - _patchROM(device, gbset); -} - -static void GBCheatRemoveSet(struct mCheatSet* cheats, struct mCheatDevice* device) { - struct GBCheatSet* gbset = (struct GBCheatSet*) cheats; - _unpatchROM(device, gbset); -} - -static bool GBCheatAddCodebreaker(struct GBCheatSet* cheats, uint16_t address, uint8_t data) { - struct mCheat* cheat = mCheatListAppend(&cheats->d.list); +static bool GBCheatAddCodebreaker(struct mCheatSet* cheats, uint16_t address, uint8_t data) { + struct mCheat* cheat = mCheatListAppend(&cheats->list); cheat->type = CHEAT_ASSIGN; cheat->width = 1; cheat->address = address;@@ -121,11 +52,11 @@ cheat->negativeRepeat = 0;
return true; } -static bool GBCheatAddGameShark(struct GBCheatSet* cheats, uint32_t op) { +static bool GBCheatAddGameShark(struct mCheatSet* cheats, uint32_t op) { return GBCheatAddCodebreaker(cheats, ((op & 0xFF) << 8) | ((op >> 8) & 0xFF), (op >> 16) & 0xFF); } -static bool GBCheatAddGameSharkLine(struct GBCheatSet* cheats, const char* line) { +static bool GBCheatAddGameSharkLine(struct mCheatSet* cheats, const char* line) { uint32_t op; if (!hex32(line, &op)) { return false;@@ -133,7 +64,7 @@ }
return GBCheatAddGameShark(cheats, op); } -static bool GBCheatAddGameGenieLine(struct GBCheatSet* cheats, const char* line) { +static bool GBCheatAddGameGenieLine(struct mCheatSet* cheats, const char* line) { uint16_t op1; uint16_t op2; uint16_t op3 = 0x1000;@@ -156,24 +87,26 @@ }
uint16_t address = (op1 & 0xF) << 8; address |= (op2 >> 4) & 0xFF; address |= ((op2 & 0xF) ^ 0xF) << 12; - struct GBCheatPatch* patch = GBCheatPatchListAppend(&cheats->romPatches); + struct mCheatPatch* patch = mCheatPatchListAppend(&cheats->romPatches); patch->address = address; - patch->newValue = op1 >> 4; + patch->value = op1 >> 4; patch->applied = false; + patch->width = 1; + patch->segment = -1; if (op3 < 0x1000) { uint32_t value = ((op3 & 0xF00) << 20) | (op3 & 0xF); value = ROR(value, 2); value |= value >> 24; value ^= 0xBA; - patch->oldValue = value; - patch->checkByte = true; + patch->checkValue = value; + patch->check = true; } else { - patch->checkByte = false; + patch->check = false; } return true; } -static bool GBCheatAddVBALine(struct GBCheatSet* cheats, const char* line) { +static bool GBCheatAddVBALine(struct mCheatSet* cheats, const char* line) { uint16_t address; uint8_t value; const char* lineNext = hex16(line, &address);@@ -183,7 +116,7 @@ }
if (!hex8(line, &value)) { return false; } - struct mCheat* cheat = mCheatListAppend(&cheats->d.list); + struct mCheat* cheat = mCheatListAppend(&cheats->list); cheat->type = CHEAT_ASSIGN; cheat->width = 1; cheat->address = address;@@ -193,8 +126,7 @@ cheat->negativeRepeat = 0;
return true; } -bool GBCheatAddLine(struct mCheatSet* set, const char* line, int type) { - struct GBCheatSet* cheats = (struct GBCheatSet*) set; +bool GBCheatAddLine(struct mCheatSet* cheats, const char* line, int type) { switch (type) { case GB_CHEAT_AUTODETECT: break;@@ -239,15 +171,6 @@ uint32_t realOp = op1 << 16;
realOp |= op2 << 8; realOp |= op3; return GBCheatAddGameShark(cheats, realOp); - } -} - -static void GBCheatRefresh(struct mCheatSet* cheats, struct mCheatDevice* device) { - struct GBCheatSet* gbset = (struct GBCheatSet*) cheats; - if (cheats->enabled) { - _patchROM(device, gbset); - } else { - _unpatchROM(device, gbset); } }
@@ -13,8 +13,6 @@ #include "gba/cheats/parv3.h"
#define MAX_LINE_LENGTH 128 -DEFINE_VECTOR(GBACheatPatchList, struct GBACheatPatch); - static void _addBreakpoint(struct mCheatDevice* device, struct GBACheatSet* cheats) { if (!device->p || !cheats->hook) { return;@@ -37,36 +35,6 @@ }
GBAClearBreakpoint(device->p->board, cheats->hook->address, cheats->hook->mode, cheats->hook->patchedOpcode); } -static void _patchROM(struct mCheatDevice* device, struct GBACheatSet* cheats) { - if (!device->p) { - return; - } - size_t i; - for (i = 0; i < GBACheatPatchListSize(&cheats->romPatches); ++i) { - struct GBACheatPatch* patch = GBACheatPatchListGetPointer(&cheats->romPatches, i); - if (patch->applied) { - continue; - } - GBAPatch16(device->p->cpu, patch->address, patch->newValue, &patch->oldValue); - patch->applied = true; - } -} - -static void _unpatchROM(struct mCheatDevice* device, struct GBACheatSet* cheats) { - if (!device->p) { - return; - } - size_t i; - for (i = 0; i < GBACheatPatchListSize(&cheats->romPatches); ++i) { - struct GBACheatPatch* patch = GBACheatPatchListGetPointer(&cheats->romPatches, i); - if (!patch->applied) { - continue; - } - GBAPatch16(device->p->cpu, patch->address, patch->oldValue, NULL); - patch->applied = false; - } -} - static void GBACheatSetDeinit(struct mCheatSet* set); static void GBACheatAddSet(struct mCheatSet* cheats, struct mCheatDevice* device); static void GBACheatRemoveSet(struct mCheatSet* cheats, struct mCheatDevice* device);@@ -101,7 +69,6 @@ set->d.dumpDirectives = GBACheatDumpDirectives;
set->d.refresh = GBACheatRefresh; - GBACheatPatchListInit(&set->romPatches, 4); return &set->d; }@@ -120,18 +87,15 @@ if (gbaset->hook->refs == 0) {
free(gbaset->hook); } } - GBACheatPatchListDeinit(&gbaset->romPatches); } static void GBACheatAddSet(struct mCheatSet* cheats, struct mCheatDevice* device) { struct GBACheatSet* gbaset = (struct GBACheatSet*) cheats; _addBreakpoint(device, gbaset); - _patchROM(device, gbaset); } static void GBACheatRemoveSet(struct mCheatSet* cheats, struct mCheatDevice* device) { struct GBACheatSet* gbaset = (struct GBACheatSet*) cheats; - _unpatchROM(device, gbaset); _removeBreakpoint(device, gbaset); }@@ -276,13 +240,8 @@ }
static void GBACheatRefresh(struct mCheatSet* cheats, struct mCheatDevice* device) { struct GBACheatSet* gbaset = (struct GBACheatSet*) cheats; - if (cheats->enabled) { - _patchROM(device, gbaset); - if (gbaset->hook && !gbaset->hook->reentries) { - _addBreakpoint(device, gbaset); - } - } else { - _unpatchROM(device, gbaset); + if (cheats->enabled && gbaset->hook && !gbaset->hook->reentries) { + _addBreakpoint(device, gbaset); } }
@@ -93,7 +93,7 @@
bool GBACheatAddGameSharkRaw(struct GBACheatSet* cheats, uint32_t op1, uint32_t op2) { enum GBAGameSharkType type = op1 >> 28; struct mCheat* cheat = 0; - struct GBACheatPatch* romPatch; + struct mCheatPatch* romPatch; if (cheats->incompleteCheat != COMPLETE) { struct mCheat* incompleteCheat = mCheatListGetPointer(&cheats->d.list, cheats->incompleteCheat);@@ -149,10 +149,12 @@ cheat->address = op2;
cheats->incompleteCheat = mCheatListIndex(&cheats->d.list, cheat); break; case GSA_PATCH: - romPatch = GBACheatPatchListAppend(&cheats->romPatches); + romPatch = mCheatPatchListAppend(&cheats->d.romPatches); romPatch->address = BASE_CART0 | ((op1 & 0xFFFFFF) << 1); - romPatch->newValue = op2; + romPatch->value = op2; romPatch->applied = false; + romPatch->width = 2; + romPatch->check = false; return true; case GSA_BUTTON: switch (op1 & 0x00F00000) {
@@ -230,9 +230,11 @@ cheats->incompleteCheat = mCheatListIndex(&cheats->d.list, cheat);
break; } if (romPatch >= 0) { - struct GBACheatPatch* patch = GBACheatPatchListAppend(&cheats->romPatches); + struct mCheatPatch* patch = mCheatPatchListAppend(&cheats->d.romPatches); patch->address = BASE_CART0 | ((op2 & 0xFFFFFF) << 1); patch->applied = false; + patch->check = false; + patch->width = 2; cheats->incompletePatch = patch; } return true;@@ -240,8 +242,8 @@ }
bool GBACheatAddProActionReplayRaw(struct GBACheatSet* cheats, uint32_t op1, uint32_t op2) { if (cheats->incompletePatch) { - cheats->incompletePatch->newValue = op1; - cheats->incompletePatch = 0; + cheats->incompletePatch->value = op1; + cheats->incompletePatch = NULL; return true; } if (cheats->incompleteCheat != COMPLETE) {