Core: Improve support for ROM patch cheats, supporting disabling overlapping patches

This commit is contained in:
Vicki Pfau 2020-12-23 18:35:42 -08:00
parent a59ffbc9a6
commit 5781566717
9 changed files with 190 additions and 183 deletions

View File

@ -92,6 +92,7 @@ Misc:
- 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)

View File

@ -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 @@ struct mCheat {
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 @@ struct mCheatSet {
char* name;
bool enabled;
struct mCheatPatchList romPatches;
struct StringList lines;
};
@ -78,6 +91,7 @@ struct mCheatDevice {
struct mCheatSet* (*createSet)(struct mCheatDevice*, const char* name);
struct mCheatSets cheats;
struct Table unpatchedMemory;
bool autosave;
bool buttonDown;
};

View File

@ -11,7 +11,6 @@
CXX_GUARD_START
#include <mgba/core/cheats.h>
#include <mgba-util/vector.h>
enum GBCheatType {
GB_CHEAT_AUTODETECT,
@ -20,22 +19,6 @@ enum GBCheatType {
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);
CXX_GUARD_END

View File

@ -134,23 +134,14 @@ struct GBACheatHook {
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;

View File

@ -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 @@ static int32_t _readMem(struct mCore* core, uint32_t address, int width) {
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 @@ static void _writeMem(struct mCore* core, uint32_t address, int width, int32_t v
}
}
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 @@ void mCheatDeviceCreate(struct mCheatDevice* device) {
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 mCheatDeviceClear(struct mCheatDevice* device) {
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 @@ void mCheatSetDeinit(struct mCheatSet* set) {
free(set->name);
}
StringListDeinit(&set->lines);
mCheatPatchListDeinit(&set->romPatches);
if (set->deinit) {
set->deinit(set);
}
free(set);
}
@ -117,7 +239,9 @@ bool mCheatAddLine(struct mCheatSet* set, const char* line, int type) {
void mCheatAddSet(struct mCheatDevice* device, struct mCheatSet* cheats) {
*mCheatSetsAppend(&device->cheats) = cheats;
if (cheats->add) {
cheats->add(cheats, device);
}
}
void mCheatRemoveSet(struct mCheatDevice* device, struct mCheatSet* cheats) {
@ -131,7 +255,9 @@ void mCheatRemoveSet(struct mCheatDevice* device, struct mCheatSet* cheats) {
return;
}
mCheatSetsShift(&device->cheats, i, 1);
if (cheats->remove) {
cheats->remove(cheats, device);
}
}
bool mCheatParseFile(struct mCheatDevice* device, struct VFile* vf) {
@ -503,8 +629,14 @@ void mCheatAutosave(struct mCheatDevice* device) {
#endif
void mCheatRefresh(struct mCheatDevice* device, struct mCheatSet* cheats) {
if (cheats->enabled) {
_patchROM(device, cheats);
}
if (cheats->refresh) {
cheats->refresh(cheats, device);
}
if (!cheats->enabled) {
_unpatchROM(device, cheats);
return;
}

View File

@ -10,58 +10,6 @@
#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->parseDirectives = GBCheatParseDirectives;
set->dumpDirectives = GBCheatDumpDirectives;
set->d.parseDirectives = GBCheatParseDirectives;
set->d.dumpDirectives = GBCheatDumpDirectives;
set->d.refresh = GBCheatRefresh;
return &set->d;
set->refresh = NULL;
return set;
}
struct mCheatDevice* GBCheatDeviceCreate(void) {
@ -95,23 +41,8 @@ struct mCheatDevice* GBCheatDeviceCreate(void) {
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 @@ static bool GBCheatAddCodebreaker(struct GBCheatSet* cheats, uint16_t address, u
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 @@ static bool GBCheatAddGameSharkLine(struct GBCheatSet* cheats, const char* line)
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 @@ static bool GBCheatAddGameGenieLine(struct GBCheatSet* cheats, const char* line)
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 @@ static bool GBCheatAddVBALine(struct GBCheatSet* cheats, const char* line) {
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 @@ static bool GBCheatAddVBALine(struct GBCheatSet* cheats, const char* line) {
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;
@ -242,15 +174,6 @@ bool GBCheatAddLine(struct mCheatSet* set, const char* line, int type) {
}
}
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);
}
}
static void GBCheatSetCopyProperties(struct mCheatSet* set, struct mCheatSet* oldSet) {
UNUSED(set);
UNUSED(oldSet);

View File

@ -13,8 +13,6 @@
#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 @@ static void _removeBreakpoint(struct mCheatDevice* device, struct GBACheatSet* c
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 @@ static struct mCheatSet* GBACheatSetCreate(struct mCheatDevice* device, const ch
set->d.refresh = GBACheatRefresh;
GBACheatPatchListInit(&set->romPatches, 4);
return &set->d;
}
@ -120,18 +87,15 @@ static void GBACheatSetDeinit(struct mCheatSet* set) {
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,14 +240,9 @@ bool GBACheatAddLine(struct mCheatSet* set, const char* line, int type) {
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) {
if (cheats->enabled && gbaset->hook && !gbaset->hook->reentries) {
_addBreakpoint(device, gbaset);
}
} else {
_unpatchROM(device, gbaset);
}
}
static void GBACheatSetCopyProperties(struct mCheatSet* set, struct mCheatSet* oldSet) {

View File

@ -93,7 +93,7 @@ void GBACheatSetGameSharkVersion(struct GBACheatSet* cheats, enum GBACheatGameSh
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 @@ bool GBACheatAddGameSharkRaw(struct GBACheatSet* cheats, uint32_t op1, uint32_t
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) {

View File

@ -230,9 +230,11 @@ static bool _addPAR3Special(struct GBACheatSet* cheats, uint32_t op2) {
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 @@ static bool _addPAR3Special(struct GBACheatSet* cheats, uint32_t op2) {
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) {