diff --git a/CHANGES b/CHANGES index c3e7518a3..bed1296f9 100644 --- a/CHANGES +++ b/CHANGES @@ -1,6 +1,7 @@ 0.5.0: (Future) Features: - Game Boy support + - Support for encrypted CodeBreaker GBA cheats Bugfixes: - VFS: Fix reading 7z archives without rewinding first - Qt: Fix sending gameStopped twice diff --git a/src/gba/cheats.c b/src/gba/cheats.c index c738913d5..f7fc52749 100644 --- a/src/gba/cheats.c +++ b/src/gba/cheats.c @@ -133,6 +133,8 @@ void GBACheatSetInit(struct GBACheatSet* set, const char* name) { set->incompletePatch = 0; set->currentBlock = 0; set->gsaVersion = 0; + set->cbRngState = 0; + set->cbMaster = 0; set->remainingAddresses = 0; set->hook = 0; int i; @@ -548,6 +550,10 @@ void GBACheatRefresh(struct GBACheatDevice* device, struct GBACheatSet* cheats) void GBACheatSetCopyProperties(struct GBACheatSet* newSet, struct GBACheatSet* set) { newSet->gsaVersion = set->gsaVersion; memcpy(newSet->gsaSeeds, set->gsaSeeds, sizeof(newSet->gsaSeeds)); + newSet->cbRngState = set->cbRngState; + newSet->cbMaster = set->cbMaster; + memcpy(newSet->cbSeeds, set->cbSeeds, sizeof(newSet->cbSeeds)); + memcpy(newSet->cbTable, set->cbTable, sizeof(newSet->cbTable)); if (set->hook) { if (newSet->hook) { --newSet->hook->refs; diff --git a/src/gba/cheats.h b/src/gba/cheats.h index 977ee0a1d..976e35481 100644 --- a/src/gba/cheats.h +++ b/src/gba/cheats.h @@ -175,6 +175,10 @@ struct GBACheatSet { int gsaVersion; uint32_t gsaSeeds[4]; + uint32_t cbRngState; + uint32_t cbMaster; + uint8_t cbTable[0x30]; + uint32_t cbSeeds[4]; int remainingAddresses; char* name; diff --git a/src/gba/cheats/codebreaker.c b/src/gba/cheats/codebreaker.c index 34f49b297..0e0db47a1 100644 --- a/src/gba/cheats/codebreaker.c +++ b/src/gba/cheats/codebreaker.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2013-2015 Jeffrey Pfau +/* 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 @@ -10,11 +10,192 @@ #include "gba/io.h" #include "util/string.h" +static void _cbLoadByteswap(uint8_t* buffer, uint32_t op1, uint16_t op2) { + buffer[0] = op1 >> 24; + buffer[1] = op1 >> 16; + buffer[2] = op1 >> 8; + buffer[3] = op1; + buffer[4] = op2 >> 8; + buffer[5] = op2; +} + +static void _cbStoreByteswap(uint8_t* buffer, uint32_t* op1, uint16_t* op2) { + *op1 = (buffer[0] << 24) | (buffer[1] << 16) | (buffer[2] << 8) | buffer[3]; + *op2 = (buffer[4] << 8) | buffer[5]; +} + +static void _cbDecrypt(struct GBACheatSet* cheats, uint32_t* op1, uint16_t* op2) { + uint8_t buffer[6]; + int i; + + _cbLoadByteswap(buffer, *op1, *op2); + for (i = sizeof(cheats->cbTable) - 1; i >= 0; --i) { + size_t offsetX = i >> 3; + size_t offsetY = cheats->cbTable[i] >> 3; + int bitX = i & 7; + int bitY = cheats->cbTable[i] & 7; + + uint8_t x = (buffer[offsetX] >> bitX) & 1; + uint8_t y = (buffer[offsetY] >> bitY) & 1; + uint8_t x2 = buffer[offsetX] & ~(1 << bitX); + if (y) { + x2 |= 1 << bitX; + } + buffer[offsetX] = x2; + + // This can't be moved earlier due to pointer aliasing + uint8_t y2 = buffer[offsetY] & ~(1 << bitY); + if (x) { + y2 |= 1 << bitY; + } + buffer[offsetY] = y2; + } + _cbStoreByteswap(buffer, op1, op2); + + *op1 ^= cheats->cbSeeds[0]; + *op2 ^= cheats->cbSeeds[1]; + + _cbLoadByteswap(buffer, *op1, *op2); + uint32_t master = cheats->cbMaster; + for (i = 0; i < 5; ++i) { + buffer[i] ^= (master >> 8) ^ buffer[i + 1]; + } + buffer[5] ^= master >> 8; + + for (i = 5; i > 0; --i) { + buffer[i] ^= master ^ buffer[i - 1]; + } + buffer[0] ^= master; + _cbStoreByteswap(buffer, op1, op2); + + *op1 ^= cheats->cbSeeds[2]; + *op2 ^= cheats->cbSeeds[3]; +} + +static uint32_t _cbRand(struct GBACheatSet* cheats) { + // Roll LCG three times to get enough bits of entropy + uint32_t roll = cheats->cbRngState * 0x41C64E6D + 0x3039; + uint32_t roll2 = roll * 0x41C64E6D + 0x3039; + uint32_t roll3 = roll2 * 0x41C64E6D + 0x3039; + uint32_t mix = (roll << 14) & 0xC0000000; + mix |= (roll2 >> 1) & 0x3FFF8000; + mix |= (roll3 >> 16) & 0x7FFF; + cheats->cbRngState = roll3; + return mix; +} + +static size_t _cbSwapIndex(struct GBACheatSet* cheats) { + uint32_t roll = _cbRand(cheats); + uint32_t count = sizeof(cheats->cbTable); + + if (roll == count) { + roll = 0; + } + + if (roll < count) { + return roll; + } + + uint32_t bit = 1; + + while (count < 0x10000000 && count < roll) { + count <<= 4; + bit <<= 4; + } + + while (count < 0x80000000 && count < roll) { + count <<= 1; + bit <<= 1; + } + + uint32_t mask; + while (true) { + mask = 0; + if (roll >= count) { + roll -= count; + } + if (roll >= count >> 1) { + roll -= count >> 1; + mask |= ROR(bit, 1); + } + if (roll >= count >> 2) { + roll -= count >> 2; + mask |= ROR(bit, 2); + } + if (roll >= count >> 3) { + roll -= count >> 3; + mask |= ROR(bit, 3); + } + if (!roll || !(bit >> 4)) { + break; + } + bit >>= 4; + count >>= 4; + } + + mask &= 0xE0000000; + if (!mask || !(bit & 7)) { + return roll; + } + + if (mask & ROR(bit, 3)) { + roll += count >> 3; + } + if (mask & ROR(bit, 2)) { + roll += count >> 2; + } + if (mask & ROR(bit, 1)) { + roll += count >> 1; + } + + return roll; +} + +static void _cbReseed(struct GBACheatSet* cheats, uint32_t op1, uint16_t op2) { + cheats->cbRngState = (op2 & 0xFF) ^ 0x1111; + size_t i; + // Populate the initial seed table + for (i = 0; i < sizeof(cheats->cbTable); ++i) { + cheats->cbTable[i] = i; + } + // Swap pseudo-random table entries based on the input code + for (i = 0; i < 0x50; ++i) { + size_t x = _cbSwapIndex(cheats); + size_t y = _cbSwapIndex(cheats); + uint8_t swap = cheats->cbTable[x]; + cheats->cbTable[x] = cheats->cbTable[y]; + cheats->cbTable[y] = swap; + } + + // Spin the RNG some to make the initial seed + cheats->cbRngState = 0x4EFAD1C3; + for (i = 0; i < ((op1 >> 24) & 0xF); ++i) { + cheats->cbRngState = _cbRand(cheats); + } + + cheats->cbSeeds[2] = _cbRand(cheats); + cheats->cbSeeds[3] = _cbRand(cheats); + + cheats->cbRngState = (op2 >> 8) ^ 0xF254; + for (i = 0; i < (op2 >> 8); ++i) { + cheats->cbRngState = _cbRand(cheats); + } + + cheats->cbSeeds[0] = _cbRand(cheats); + cheats->cbSeeds[1] = _cbRand(cheats); + + cheats->cbMaster = op1; +} + bool GBACheatAddCodeBreaker(struct GBACheatSet* cheats, uint32_t op1, uint16_t op2) { char line[14] = "XXXXXXXX XXXX"; snprintf(line, sizeof(line), "%08X %04X", op1, op2); GBACheatRegisterLine(cheats, line); + if (cheats->cbMaster) { + _cbDecrypt(cheats, &op1, &op2); + } + enum GBACodeBreakerType type = op1 >> 28; struct GBACheat* cheat = 0; @@ -75,8 +256,8 @@ bool GBACheatAddCodeBreaker(struct GBACheatSet* cheats, uint32_t op1, uint16_t o cheat->width = 2; break; case CB_ENCRYPT: - mLOG(CHEATS, STUB, "CodeBreaker encryption not supported"); - return false; + _cbReseed(cheats, op1, op2); + return true; case CB_IF_NE: cheat = GBACheatListAppend(&cheats->list); cheat->type = CHEAT_IF_NE;