GBA Cheats: Support for encrypted CodeBreaker cheats

This commit is contained in:
Jeffrey Pfau 2016-05-05 22:56:05 -07:00
parent 24c0893cf5
commit 20f790bb61
4 changed files with 195 additions and 3 deletions

View File

@ -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

View File

@ -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;

View File

@ -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;

View File

@ -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;