# Anti-cheat removed ## Request (2026-10-25) The Gladiator (Character03, level 106) "randomly exploded" fighting an underleveled dragon on Silber. The user: the cheater detection is not necessary, remove it. After a second explosion: "i want all that weird cheater detection code gone". ## Measured A temporary damage log showed the hero at full health (17200) going to 0 in one tick, with the game logging `cheat statBonus 0/3`, `cheat SkillLevel 0..7`, `Hase!` and `cObjectManager::destroy: tried to delete local hero #1!`; the hero came back with 1199 health and died to the next hit. - The game mirrors the local hero's fields (pointers `0x182cddc`, on a protected page, and `0x182cde0`, a separate heap block: `HeapAlloc` 0x1000 at `0x816541`): +0 hero +0x3b4, +4 hero +0x3ee (gold), +8 hero +0x3fe (u16), xor-ed with the hero's key byte (+0x38) repeated four times (the second copy: key << 1); +0xc a raw copy of hero +0x3b0..+0x49d (attributes, skill levels). Writer: `0x603d9c` and about 30 inline copies after legitimate changes, all starting with `VirtualProtect(page, 0x1000, PAGE_READWRITE)` and ending with `0x201`. - Checks: `cCreature::Think` about once in 101 ticks (`rand() % 101 == 10`, `0x5389b5`), `VerifyHeroMirror` `0x5905f0` (callers `0x57fc71`, `0x580001`, `0x580331`, `0x61ce22`), inline copies at `0x4659f6`, `0x564c6f`, `0x57e09c`, `0x585bd0`, `0x619d33`, and a trap in Think (`0x5385a6`: a hero whose level exceeds its experience's by more than 3 gets +0x3b4 / +0x3ee / +0x3fe raised behind the mirrors' back). A mismatch resets the field (attributes 1, skill levels 0); any mismatch, or no hero at all, kills the hero ("*** Cheater detected! ***", health 0, action dying) and sets the cheater flag `0x182cdd0`, saved in the hero file (record +0xf3, `0x604409`) and read back by loadHero (`0x6066eb`) and the character selection (`0x70da99`, from the record's mark +0x3ef). - Stream checks in the quest manager's hero data (`0x465980`): a marker `0xfea4fea4` (`0x467782`) and three stored values compared with the hero (`0x46a60b`, `0x46a64e`, `0x46a696`). - In the failing log every skill level mismatched: the mirror was stale (changes outside the game's mirror writers, e.g. the shared hero loaded into another world). ## History - First mod (confirmed): a `VirtualProtect` hook rewrote the mirror from the hero whenever a check opened its page, so checks compared the hero with itself. - The hero exploded again (`Hase!` twice, no `cheat statBonus` / `SkillLevel` line): the hook refreshed the second mirror only if it lay on the protected page, so never; the checks compare its three xor-ed fields after the first mirror (e.g. `0x585f83`) without a log line. Refreshing both fixed it (user: "i no longer explode"). - Then the checks were removed entirely (below), the hook is gone. ## Mod (`src/mods/anticheat.cpp`): checks removed Every check is skipped by a code patch (`re::ReplaceAll`): no field is reset, no hero killed, the cheater flag never set. | Check | Patch | |---|---| | Think trap `0x5385a6` | `0x5385ca` `jle` -> `jmp` | | Think, 1 tick in 101 (`0x5389be`) | `0x5389b8` `jne` -> `jmp` | | `0x564c6f`, `0x57e09c`, `0x585bd0` | the "not the local hero" `jne` before each (`0x564c50`, `0x57e07d`, `0x585bb0`) -> `jmp`: the game's own path past the check | | quest manager hero data `0x4659f6` | `0x4659dc` `jmp 0x46606b` (the code there writes esi / edi before reading them; the stream keeps its clean marker 0xfeedf00d) | | `0x619d33` | `mov ebp, [esp+0x34]; jmp 0x61a2de`, the registers as the check leaves them (edi already 0x88e874) | | `VerifyHeroMirror` `0x5905f0` (callers test al) | `mov al, 1; ret` | | stream checks `0x467782`, `0x46a60b`, `0x46a64e`, `0x46a696` | jumps past the punishment unconditional | | loadHero `0x6066eb` | writes 0 to the cheater flag | | character selection `0x70da8c` | `setne cl` -> `xor cl, cl` | All field resets (`mov [reg+0x3b4], 1` ...) lie inside these checks; all writes of the flag `0x182cdd0` (`0x465ebe`, `0x538e53`, `0x56508a`, `0x57e528`, `0x57fcfd`, `0x58008d`, `0x5803bd`, `0x586090`, `0x61a17e`, `0x61ceac`) are their punishments. The mirror writers stay: they only fill the now unread mirrors.