Skip to content

fix(linux): compute cave-pair displacements at load time; apply pairs atomically - #6

Open
crombieman wants to merge 2 commits into
ed0ard:mainfrom
crombieman:fix/computed-displacements
Open

fix(linux): compute cave-pair displacements at load time; apply pairs atomically#6
crombieman wants to merge 2 commits into
ed0ard:mainfrom
crombieman:fix/computed-displacements

Conversation

@crombieman

Copy link
Copy Markdown

What this does

Two related fixes for the Linux cave-pair patches, building directly on the excellent build-14172 revalidation in #5:

  1. Atomic pair application (ports the fix accepted for testing in CS2-Bot-Improver PR #106): caves are applied before their partners, a partner is skipped cleanly if its cave's signature no longer matches, and an orphaned cave is rolled back. Previously a half-applied pair left a jmp into unpatched bytes — that was the segfault in CCSBot::UpdateLookAround under bot combat (~3–5 min MTTF on my server before the fix).

  2. Load-time computed displacements: the rel32 fields inside all three cave pairs (and OnBombPlanted_AllBotsLearnSite) are computed from the resolved signature addresses instead of hardcoded. Loading is now three-phase — resolve every signature first (a cave patch overwrites its own CC-padding signature), compute, then write.

Why

The cave↔site distance spans function boundaries and shifts on most game updates. The failure mode is nastier than a dead signature: on the pre-14172 build, Vision_AlwaysWatchApproachPoints_LoopEntry's hardcoded displacement was exactly 0x20 short of where the cave actually resolved — both signatures still matched, the pair "applied" cleanly, and the jump executed a neighboring function's bytes. No crash, just silently corrupted bot vision behavior (bot-vs-bot kill rate on my DM server dropped ~3.5× with the pair applied). #5's comments document the same 0x20 shift from the other direction. With this change, that entire failure class is gone: whatever the new geometry is, the computed jump lands on the cave.

Validation

  • Build 14172: 42/42 Linux patches apply; the computed rel32 values reproduce fix(linux): revalidate patches + m_gameState 0x5100 for build 14172 #5's hand-revalidated displacements exactly (e.g. LoopEntry cave at site+0x1D34A, WatchApproachPoints exits at the original jz target and fall-through). Two independent derivations agreeing was a nice confirmation of both.
  • Pre-14172 build: boot-verified the atomic path — WatchApproachPoints_Cave's signature was dead there, and the pair skipped/rolled back cleanly instead of half-applying.
  • Production: multi-day continuous runtime on a Linux CS2 dedicated server (22-bot DM + human sessions), zero crashes.

Also included

  • Cave signatures no longer pin the build-specific tail jmp rel32 — generalizes fix(linux): revalidate patches + m_gameState 0x5100 for build 14172 #5's revival fix for WatchApproachPoints_Cave to all three caves (the other two carry the same landmine and have survived by luck).
  • AttackState_RetreatOnSniper_Disable / BombPickup_CT_GlobalHearRange now rewrite only the opcode byte (je/jnejmp), matching the Windows definitions' approach, so their rel8 can't go stale.
  • LoopEntry_Cave's signature pins all 32 padding bytes it writes (was 30 — the last two were only covered by expectedOriginal).
  • TBot_BombsiteSearch_UseKnownPlantedSite is kept exactly as revalidated in fix(linux): revalidate patches + m_gameState 0x5100 for build 14172 #5 — its call target can't be derived from the patch site, and the hardcoded expectedOriginal is precisely what makes it fail safe on drift. The comment sketches a possible future signature-based route if you ever want it drift-proof too.

Caveats / notes for review

  • Windows is untested. The three-phase loader is shared code (the fixup step is Linux-only and the Windows definitions are untouched), so risk should be low, but I'd appreciate a smoke test from someone with a Windows server before merge.
  • I left ModuleVersion at 1.8.7 — version bump is your call.
  • Happy to split or adjust anything; structured it as one commit for reviewability since the pieces are interdependent.

Thanks to @mrc4tt for the 14172 revalidation that both revived the third pair and gave me the reference values to verify the computation against, and to @AmagiReina for testing the atomicity fix on Linux over in Bot-Improver #106.

🤖 Generated with Claude Code

crombieman and others added 2 commits August 1, 2026 11:20
… atomically

Two related fixes for the Linux cave-pair patches:

1. Atomic pair application (ports the fix from CS2-Bot-Improver PR #106):
   a "<name>_Cave" and its "<name>" partner are now applied cave-first,
   with the partner skipped if the cave's signature no longer matches and
   the cave rolled back if the partner fails. Previously a half-applied
   pair left a jmp into unpatched bytes (segfault in
   CCSBot::UpdateLookAround under bot combat).

2. Computed displacements: the rel32 fields inside all three cave pairs
   (and OnBombPlanted_AllBotsLearnSite) are now computed at load time
   from the resolved addresses instead of hardcoded. Loading is
   three-phase - resolve every signature first (a cave patch overwrites
   its own CC-padding signature), compute, then write. The cross-function
   cave<->site distance changes on most game updates; on the pre-14172
   build the hardcoded LoopEntry displacement was exactly 0x20 short of
   the relocated cave, so a sig-matched pair jumped into a neighboring
   function's bytes - no crash, just silently corrupted bot vision.

Also:
- Cave signatures no longer pin the build-specific tail jmp rel32
  (generalizes the ed0ard#5 revival fix to all three caves).
- AttackState_RetreatOnSniper_Disable / BombPickup_CT_GlobalHearRange
  rewrite only the opcode byte; the rel8 displacement can't go stale.
- Vision_AlwaysWatchApproachPoints_LoopEntry_Cave's signature now pins
  all 32 padding bytes it writes (was 30).
- TBot_BombsiteSearch_UseKnownPlantedSite kept exactly as revalidated in
  ed0ard#5 (its call target can't be derived from the patch site; the
  hardcoded expectedOriginal is what makes it fail safe on drift).

Validated on build 14172: 42/42 patches apply, and the computed rel32
values reproduce the hand-revalidated displacements from ed0ard#5 exactly.
Multi-day runtime on a production Linux DM server with no crashes.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@crombieman

Copy link
Copy Markdown
Author

Thanks for the fix. Windows was the side I couldn't test, so good to have that covered. The Linux side has been applying cleanly across the last few game updates on my server. Let me know if you want anything else tested before merge.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants