mirror of https://github.com/mgba-emu/mgba.git
GBA Cheats: Support for encrypted CodeBreaker cheats
This commit is contained in:
parent
24c0893cf5
commit
20f790bb61
1
CHANGES
1
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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Reference in New Issue