diff --git a/src/gba/cheats.c b/src/gba/cheats.c index 9fd006a75..440233243 100644 --- a/src/gba/cheats.c +++ b/src/gba/cheats.c @@ -12,6 +12,9 @@ const uint32_t GBA_CHEAT_DEVICE_ID = 0xABADC0DE; DEFINE_VECTOR(GBACheatList, struct GBACheat); +static const uint32_t _gsa1S[4] = { 0x09F4FBBD, 0x9681884A, 0x352027E9, 0xF3DEE5A7 }; +static const uint32_t _gsa3S[4] = { 0x7AA9648F, 0x7FAE6994, 0xC0EFAAD5, 0x42712C57 }; + static int32_t _readMem(struct ARMCore* cpu, uint32_t address, int width) { switch (width) { case 1: @@ -105,6 +108,126 @@ static const char* _hex16(const char* line, uint16_t* out) { return line; } +// http://en.wikipedia.org/wiki/Tiny_Encryption_Algorithm +static void _decryptGameShark(uint32_t* op1, uint32_t* op2, const uint32_t* seeds) { + uint32_t sum = 0xC6EF3720; + int i; + for (i = 0; i < 32; ++i) { + *op2 -= ((*op1 << 4) + seeds[2]) ^ (*op1 + sum) ^ ((*op1 >> 5) + seeds[3]); + *op1 -= ((*op2 << 4) + seeds[0]) ^ (*op2 + sum) ^ ((*op2 >> 5) + seeds[1]); + sum -= 0x9E3779B9; + } +} + +static bool _addGSA1(struct GBACheatSet* cheats, uint32_t op1, uint32_t op2) { + enum GBAGameSharkType type = op1 >> 28; + struct GBACheat* cheat = 0; + + if (cheats->incompleteCheat) { + if (cheats->remainingAddresses > 0) { + cheat = GBACheatListAppend(&cheats->list); + cheat->type = CHEAT_ASSIGN; + cheat->width = 4; + cheat->address = op1; + cheat->operand = cheats->incompleteCheat->operand; + cheat->repeat = 1; + --cheats->remainingAddresses; + } + if (cheats->remainingAddresses > 0) { + cheat = GBACheatListAppend(&cheats->list); + cheat->type = CHEAT_ASSIGN; + cheat->width = 4; + cheat->address = op2; + cheat->operand = cheats->incompleteCheat->operand; + cheat->repeat = 1; + --cheats->remainingAddresses; + } + if (cheats->remainingAddresses == 0) { + cheats->incompleteCheat = 0; + } + return true; + } + + switch (type) { + case GSA_ASSIGN_1: + cheat = GBACheatListAppend(&cheats->list); + cheat->type = CHEAT_ASSIGN; + cheat->width = 1; + cheat->address = op1 & 0x0FFFFFFF; + break; + case GSA_ASSIGN_2: + cheat = GBACheatListAppend(&cheats->list); + cheat->type = CHEAT_ASSIGN; + cheat->width = 2; + cheat->address = op1 & 0x0FFFFFFF; + break; + case GSA_ASSIGN_4: + cheat = GBACheatListAppend(&cheats->list); + cheat->type = CHEAT_ASSIGN; + cheat->width = 4; + cheat->address = op1 & 0x0FFFFFFF; + break; + case GSA_ASSIGN_LIST: + cheats->remainingAddresses = (op1 & 0xFFFF) - 1; + cheat = GBACheatListAppend(&cheats->list); + cheat->type = CHEAT_ASSIGN; + cheat->width = 4; + cheat->address = op2; + cheats->incompleteCheat = cheat; + break; + case GSA_PATCH: + if (cheats->nRomPatches >= MAX_ROM_PATCHES) { + return false; + } + cheats->romPatches[cheats->nRomPatches].address = (op1 & 0xFFFFFF) << 1; + cheats->romPatches[cheats->nRomPatches].newValue = op2; + cheats->romPatches[cheats->nRomPatches].applied = false; + ++cheats->nRomPatches; + return true; + case GSA_BUTTON: + // TODO: Implement button + return false; + case GSA_IF_EQ: + if (op1 == 0xDEADFACE) { + // TODO: Change seeds + return false; + } + cheat = GBACheatListAppend(&cheats->list); + cheat->type = CHEAT_IF_EQ; + cheat->width = 2; + cheat->address = op1 & 0x0FFFFFFF; + break; + case GSA_IF_EQ_RANGE: + cheat = GBACheatListAppend(&cheats->list); + cheat->type = CHEAT_IF_EQ; + cheat->width = 2; + cheat->address = op2 & 0x0FFFFFFF; + cheat->operand = op1 & 0xFFFF; + cheat->repeat = (op1 >> 16) & 0xFF; + return true; + case GSA_HOOK: + if (cheats->hookAddress) { + return false; + } + cheats->hookAddress = BASE_CART0 | (op1 & (SIZE_CART0 - 1)); + cheats->hookMode = MODE_THUMB; + return true; + default: + return false; + } + cheat->operand = op2; + cheat->repeat = 1; + return false; +} + +static bool _addGSA3(struct GBACheatSet* cheats, uint32_t op1, uint32_t op2) { + // TODO + UNUSED(cheats); + UNUSED(op1); + UNUSED(op2); + return false; +} + static void _addBreakpoint(struct GBACheatDevice* device) { if (!device->cheats || !device->p) { return; @@ -119,6 +242,34 @@ static void _removeBreakpoint(struct GBACheatDevice* device) { GBAClearBreakpoint(device->p, device->cheats->hookAddress, device->cheats->hookMode, device->cheats->patchedOpcode); } +static void _patchROM(struct GBACheatDevice* device) { + if (!device->cheats || !device->p) { + return; + } + int i; + for (i = 0; i < device->cheats->nRomPatches; ++i) { + if (device->cheats->romPatches[i].applied) { + continue; + } + GBAPatch16(device->p->cpu, device->cheats->romPatches[i].address, device->cheats->romPatches[i].newValue, &device->cheats->romPatches[i].oldValue); + device->cheats->romPatches[i].applied = true; + } +} + +static void _unpatchROM(struct GBACheatDevice* device) { + if (!device->cheats || !device->p) { + return; + } + int i; + for (i = 0; i < device->cheats->nRomPatches; ++i) { + if (!device->cheats->romPatches[i].applied) { + continue; + } + GBAPatch16(device->p->cpu, device->cheats->romPatches[i].address, device->cheats->romPatches[i].oldValue, 0); + device->cheats->romPatches[i].applied = false; + } +} + static void GBACheatDeviceInit(struct ARMCore*, struct ARMComponent*); static void GBACheatDeviceDeinit(struct ARMComponent*); @@ -134,6 +285,8 @@ void GBACheatSetInit(struct GBACheatSet* set) { GBACheatListInit(&set->list, 4); set->incompleteCheat = 0; set->patchedOpcode = 0; + set->gsaVersion = 0; + set->remainingAddresses = 0; } void GBACheatSetDeinit(struct GBACheatSet* set) { @@ -150,8 +303,10 @@ void GBACheatAttachDevice(struct GBA* gba, struct GBACheatDevice* device) { void GBACheatInstallSet(struct GBACheatDevice* device, struct GBACheatSet* cheats) { _removeBreakpoint(device); + _unpatchROM(device); device->cheats = cheats; _addBreakpoint(device); + _patchROM(device); } bool GBACheatAddCodeBreaker(struct GBACheatSet* cheats, uint32_t op1, uint16_t op2) { @@ -278,9 +433,86 @@ bool GBACheatAddCodeBreakerLine(struct GBACheatSet* cheats, const char* line) { return GBACheatAddCodeBreaker(cheats, op1, op2); } +bool GBACheatAddGameShark(struct GBACheatSet* set, uint32_t op1, uint32_t op2) { + uint32_t o1 = op1; + uint32_t o2 = op2; + switch (set->gsaVersion) { + case 0: + // Try to detect GameShark version + _decryptGameShark(&o1, &o2, _gsa1S); + if ((o1 & 0xF0000000) == 0xF0000000 && !(o2 & 0xFFFFFCFE)) { + set->gsaVersion = 1; + memcpy(set->gsaSeeds, _gsa1S, 4 * sizeof(uint32_t)); + return _addGSA1(set, o1, o2); + } + o1 = op1; + o2 = op2; + _decryptGameShark(&o1, &o2, _gsa3S); + if ((o1 & 0xFE000000) == 0xC4000000 && !(o2 & 0xFFFF0000)) { + set->gsaVersion = 3; + memcpy(set->gsaSeeds, _gsa3S, 4 * sizeof(uint32_t)); + return _addGSA3(set, o1, o2); + } + break; + case 1: + _decryptGameShark(&o1, &o2, set->gsaSeeds); + return _addGSA1(set, o1, o2); + case 3: + _decryptGameShark(&o1, &o2, set->gsaSeeds); + return _addGSA3(set, o1, o2); + } + return false; +} + +bool GBACheatAddGameSharkLine(struct GBACheatSet* cheats, const char* line) { + uint32_t op1; + uint32_t op2; + line = _hex32(line, &op1); + if (!line) { + return false; + } + while (*line == ' ') { + ++line; + } + line = _hex32(line, &op2); + if (!line) { + return false; + } + return GBACheatAddGameShark(cheats, op1, op2); +} + +bool GBACheatAddLine(struct GBACheatSet* cheats, const char* line) { + uint32_t op1; + uint16_t op2; + uint16_t op3; + line = _hex32(line, &op1); + if (!line) { + return false; + } + while (isspace(line[0])) { + ++line; + } + line = _hex16(line, &op2); + if (!line) { + return false; + } + if (isspace(line[0])) { + return GBACheatAddCodeBreaker(cheats, op1, op2); + } + line = _hex16(line, &op3); + if (!line) { + return false; + } + uint32_t realOp2 = op2; + realOp2 <<= 16; + realOp2 |= op3; + return GBACheatAddGameShark(cheats, op1, realOp2); +} + void GBACheatRefresh(struct GBACheatDevice* device) { bool condition = true; int conditionRemaining = 0; + _patchROM(device); size_t nCodes = GBACheatListSize(&device->cheats->list); size_t i; @@ -358,4 +590,4 @@ void GBACheatDeviceInit(struct ARMCore* cpu, struct ARMComponent* component) { void GBACheatDeviceDeinit(struct ARMComponent* component) { struct GBACheatDevice* device = (struct GBACheatDevice*) component; _removeBreakpoint(device); -} \ No newline at end of file +} diff --git a/src/gba/cheats.h b/src/gba/cheats.h index 633565d7c..24559d009 100644 --- a/src/gba/cheats.h +++ b/src/gba/cheats.h @@ -11,6 +11,8 @@ #include "arm.h" #include "util/vector.h" +#define MAX_ROM_PATCHES 4 + enum GBACheatType { CHEAT_ASSIGN, CHEAT_AND, @@ -42,6 +44,18 @@ enum GBACodeBreakerType { CB_IF_AND = 0xF, }; +enum GBAGameSharkType { + GSA_ASSIGN_1 = 0x0, + GSA_ASSIGN_2 = 0x1, + GSA_ASSIGN_4 = 0x2, + GSA_ASSIGN_LIST = 0x3, + GSA_PATCH = 0x6, + GSA_BUTTON = 0x8, + GSA_IF_EQ = 0xD, + GSA_IF_EQ_RANGE = 0xE, + GSA_HOOK = 0xF +}; + struct GBACheat { enum GBACheatType type; int width; @@ -62,6 +76,18 @@ struct GBACheatSet { struct GBACheat* incompleteCheat; uint32_t patchedOpcode; + + struct GBACheatPatch { + uint32_t address; + int16_t newValue; + int16_t oldValue; + bool applied; + } romPatches[MAX_ROM_PATCHES]; + int nRomPatches; + + int gsaVersion; + uint32_t gsaSeeds[4]; + int remainingAddresses; }; struct GBACheatDevice { @@ -82,6 +108,11 @@ void GBACheatInstallSet(struct GBACheatDevice*, struct GBACheatSet*); bool GBACheatAddCodeBreaker(struct GBACheatSet*, uint32_t op1, uint16_t op2); bool GBACheatAddCodeBreakerLine(struct GBACheatSet*, const char* line); +bool GBACheatAddGameShark(struct GBACheatSet*, uint32_t op1, uint32_t op2); +bool GBACheatAddGameSharkLine(struct GBACheatSet*, const char* line); + +bool GBACheatAddLine(struct GBACheatSet*, const char* line); + void GBACheatRefresh(struct GBACheatDevice*); #endif \ No newline at end of file diff --git a/src/gba/supervisor/thread.c b/src/gba/supervisor/thread.c index 7fdf4b390..5026fe62e 100644 --- a/src/gba/supervisor/thread.c +++ b/src/gba/supervisor/thread.c @@ -179,7 +179,7 @@ static THREAD_ENTRY _GBAThreadRun(void* context) { if (!bytesRead) { break; } - GBACheatAddCodeBreakerLine(threadContext->cheats, cheat); + GBACheatAddLine(threadContext->cheats, cheat); } } GBACheatInstallSet(&cheatDevice, threadContext->cheats);