// The game's anti-cheat is off (request of 2026-10-25, after the Gladiator // "randomly exploded" fighting a dragon on Silber). // // Measured: the game keeps a mirror of the local hero's attribute / skill // fields on a protected page (pointers 0x182cddc and 0x182cde0, see // RefreshMirror). Checks compare the hero with it - cCreature::Think about once // in 101 ticks (0x5389be), VerifyHeroMirror 0x5905f0 (4 callers) and inline // copies at 0x4659f6, 0x564c6f, 0x57e09c, 0x585bd0, 0x619d33 - reset every // mismatching field (attributes to 1, skill levels to 0), and on any mismatch // kill the hero ("*** Cheater detected! *** Hase!") and set the cheater flag // 0x182cdd0, saved in the hero file (record +0xf3). The log showed the hero at // full health dying with every skill level mismatching: the mirror was stale // (any change the game does not route through its own mirror writers, e.g. // loading the shared hero into another world, leaves it behind). // // The mod: every check and every mirror writer starts with // VirtualProtect(mirror, 0x1000, PAGE_READWRITE); at that moment the mirror is // refreshed from the hero, so checks compare the hero with itself and writers // overwrite it as before. The stream checks of the quest manager's hero data // (a cheat marker 0xfea4fea4 and three stored values) never punish, and loading // a hero file no longer sets the cheater flag. #include #include #include #include "game/engine/objects.h" #include "re/hook.h" #include "re/log.h" #include "re/patch.h" namespace mods::anticheat { namespace { // Mirror layout (writer 0x603d9c): +0 hero +0x3b4, +4 hero +0x3ee (gold), // +8 hero +0x3fe (u16), each xor-ed with the hero's key byte (+0x38) repeated // four times; +0xc a copy of hero +0x3b0..+0x49d. The second mirror uses the // key shifted left by one. constexpr uintptr_t kMirrorPointer = 0x0182cddc; constexpr uintptr_t kSecondMirrorPointer = 0x0182cde0; constexpr size_t kHeroKey = 0x38; constexpr size_t kField0 = 0x3b4; constexpr size_t kGold = 0x3ee; constexpr size_t kField2 = 0x3fe; // u16 constexpr size_t kCopyStart = 0x3b0; constexpr size_t kCopySize = 0x3b * 4 + 2; // rep movsd 0x3b + movsw constexpr size_t kMirrorCopy = 0xc; constexpr size_t kPageSize = 0x1000; uint32_t U32(const uint8_t* p) { uint32_t v; std::memcpy(&v, p, 4); return v; } void WriteMirror(uint8_t* mirror, const uint8_t* hero, uint32_t key) { const uint32_t values[3] = {U32(hero + kField0) ^ key, U32(hero + kGold) ^ key, (U32(hero + kField2) & 0xffff) ^ key}; std::memcpy(mirror, values, sizeof values); std::memcpy(mirror + kMirrorCopy, hero + kCopyStart, kCopySize); } // The page is writable when this runs (inside VirtualProtect below). void RefreshMirror(uint8_t* page) { const auto* hero = static_cast(game::GetPlayerHero()); if (!hero) { return; } const uint32_t keyByte = hero[kHeroKey]; const uint32_t key = keyByte | keyByte << 8 | keyByte << 16 | keyByte << 24; WriteMirror(page, hero, key); auto* second = *reinterpret_cast(kSecondMirrorPointer); if (second >= page && second + kMirrorCopy + kCopySize <= page + kPageSize) { WriteMirror(second, hero, key << 1); } } constexpr re::CodeReplacement kNoStreamPunishment[] = { // cheat marker 0xfea4fea4 read: `jne` past the punishment -> `jmp` {0x00467782, {0x0f, 0x85, 0xbd, 0x01, 0x00, 0x00}, {0xe9, 0xbe, 0x01, 0x00, 0x00, 0x90}, 6}, // stored values compared with the hero: `je` past the punishment -> `jmp` {0x0046a60b, {0x74, 0x18}, {0xeb, 0x18}, 2}, {0x0046a64e, {0x74, 0x18}, {0xeb, 0x18}, 2}, {0x0046a696, {0x74, 0x18}, {0xeb, 0x18}, 2}, // loadHero: the hero file's cheater flag (`mov byte [0x182cdd0], 1` -> 0) {0x006066eb, {0xc6, 0x05, 0xd0, 0xcd, 0x82, 0x01, 0x01}, {0xc6, 0x05, 0xd0, 0xcd, 0x82, 0x01, 0x00}, 7}, }; void Startup() { const int failed = re::ReplaceAll(kNoStreamPunishment); re::Log("anticheat: off (%d patches failed)", failed); } RE_STARTUP(AntiCheatStartup, Startup); } // namespace } // namespace mods::anticheat BOOL WINAPI VirtualProtectMirrorFresh(LPVOID address, SIZE_T size, DWORD protect, PDWORD old); RE_HOOK_API(VirtualProtect, VirtualProtectMirrorFresh); BOOL WINAPI VirtualProtectMirrorFresh(LPVOID address, SIZE_T size, DWORD protect, PDWORD old) { const BOOL result = VirtualProtectMirrorFresh_original(address, size, protect, old); auto* mirror = *reinterpret_cast(mods::anticheat::kMirrorPointer); if (result && protect == PAGE_READWRITE && mirror && address == mirror) { mods::anticheat::RefreshMirror(mirror); } return result; }