Skip to content

Patcher

BlueShank edited this page Sep 1, 2026 · 2 revisions

Garry's Mod has a few known bugs in its physics code that can crash or freeze the server.
The plugin ships with small fixes for these and applies them automatically when it loads, so there's nothing to set up.

The patch system is a compiled-in registry with byte verification and drift tracking: every fix is tied to the exact game code it repairs.
If a Garry's Mod update changes that code, the fix simply doesn't apply - the plugin never writes over code it doesn't recognize, so a fix that's no longer valid can't cause new problems.

Patches are compiled for both Linux x86 and x64 servers.
They only exist in server builds, never the client.

Tip

Every crash report's header shows how many fixes were applied versus skipped (e.g. 3 applied, 1 drifted), so you always know what's actually running. See Reading Reports.

Controlling patches from Lua

crashcapture.patches() -- list every fix and its state
crashcapture.patch("gm.phys.contact_stale_core", false) -- turn one off
crashcapture.patch("gm.phys.contact_stale_core") -- turn it back on

Your choice is saved to crashes/patches.txt and remembered on the next start.
CRASHCAPTURE_PATCHES=0 turns every fix off, regardless of the file.

crashcapture.patch() returns three values:

  • ok - whether the change was accepted.
  • status - what happens next:
    • "queued" - takes effect shortly, at the next frame boundary.
    • "restart" - applies on the next server start (most fixes, because rewriting code while it's running is unsafe).
    • "unknown" - an invalid id.
    • "blocked" - patches are turned off.
  • saved - whether your choice was written to disk.
    • false means it only lasts this session, usually because the reports folder isn't writable.

How a patch is applied

Each patch targets one code site, resolved symbol-first with an IDA-style signature fallback (see Signatures). Before anything is written:

  1. The site is located (by symbol if the build has one, otherwise by scanning for the expected bytes).
  2. The bytes at the site are verified against the expected pattern.
  3. Only on an exact match is the fix written.

Depending on the outcome, a patch ends up in one of these states:

State Meaning
disabled Off via config, the state file, or its default.
unresolved The site's symbol/signature found nothing in this build.
drifted The site was found, but the expected bytes differ - the engine code probably changed (often meaning it was fixed upstream).
failed The memory protection or the write itself was refused.
unsupported The patch kind is not implemented in this build.
applied Resolved, verified, written.

A drifted or unresolved fix is skipped, never forced, so an outdated fix can't corrupt a newer engine.

Defining a patch

Patches are compiled in as a CCPatch table, following fields:

  • id - stable, namespaced id used by crashcapture.patch(), e.g. "gm.phys.mindist_null_edge".
  • upstream - tracking note for when the bug gets fixed upstream.
  • note - one-line description of what the fix does.
  • kind - CC_PATCH_BYTES (replace bytes in place), CC_PATCH_DATA, CC_PATCH_DETOUR (redirect to a C++ handler, keeping the original reachable through a trampoline), or CC_PATCH_VFUNC.
  • site - a signature target: module, symbol (tried first), IDA pattern fallback, and resolve steps.
  • offset - the patched window is site + offset.
  • expect / mask / bytes / len - up to 24 bytes: what must be there (expect, mask marks which positions must match), and what replaces it.
  • detour / trampoline - for CC_PATCH_DETOUR: the C++ handler and the out-pointer receiving the original address.
  • default_on - whether the fix ships enabled.
  • hot_safe - the site is unreachable from non-game threads, so a live toggle cannot tear it.
    • Most physics fixes are not hot safe and therefore apply on restart.
  • keep_wildcards - leave mask-wildcard positions holding their original byte.

Example of a bytes patch, shortened from the real registry:

static const CCPatch kPatches[] = {
    {
        "gm.phys.oo_collision_hash_index",
        "gmod IVP OO-watcher collision-hash sign-extension",
        "read the 16-bit collision-hash slot with movzx, not movsx (an index >= 0x8000 went negative)",
        CC_PATCH_BYTES,
        {"patch.oo_hash_index", "vphysics", NULL,
            "0F BF 38 89 FE 66 83 FF FF",
            {{CC_STEP_END, 0, 0}}},
        0,
        {0x0F,0xBF,0x38,0x89,0xFE,0x66,0x83,0xFF,0xFF},
        {1,1,1,1,1,1,1,1,1},
        {0x0F,0xB7,0x38,0x89,0xFE,0x66,0x83,0xFF,0xFF},
        9,
        NULL, NULL,
        true, false, false,
    },
};

The registry is registered and applied at load:

CrashCapture::Patch::Register(kPatches, ARRAYSIZE(kPatches));
CrashCapture::Patch::Init();

The main entry points of the Patch namespace:

  • Patch::Register(const CCPatch* patches, int count): void
  • Patch::Init(): void
  • Patch::Queue(const char* id, bool on, bool* persisted): CCPatchToggle
  • Patch::Enabled(const char* id): bool
  • Patch::Count(): int
  • Patch::GetInfo(int index, CCPatchInfo* out): bool
  • Patch::LoadStateFile(): void
  • Patch::WriteStateFile(): bool

What's included

These fixes are compiled in and applied automatically. The list below is the current registry.

id default arch what it fixes
gm.phys.contact_stale_core on x86, x64 Stops a crash when physics objects are destroyed while still in use (skips reset_freeze_check_values when core->environment is null during teardown).
gm.phys.mindist_null_edge on x86, x64 Stops a crash when a physics contact record points at removed geometry or a NaN-position object.
gm.phys.mindist_stale_ff on x86, x64 Stops a crash when a retained mindist references a stale or NaN-position object (p_minimize_FF).
gm.phys.friction_stale_mindist on x86, x64 Stops a crash when managed friction is generated from a stale or NaN-position object.
gm.phys.oo_collision_hash_index on x86, x64 Stops a crash from a sign-extension bug reading the collision-hash slot index (movsx -> movzx; an index >= 0x8000 went negative).
gm.phys.oo_collision_hash_swap on x86, x64 Second movsx -> movzx site on the collision-hash swap-path probe.
gm.phys.watcher_stale_mindist on x86 only Stops a crash when a physics record is removed twice (x86 only: its x64 prologue can't be safely detoured).
gm.phys.ovtree_hash_remove on x86, x64 Stops a crash when an object is removed from a physics list twice.
gm.phys.vhash_remove_null on x86, x64 IVP_VHash::remove_elem returns early on an absent key instead of raising the not-found fatal.
gm.phys.minlist_walk_bound_a on x86, x64 Stops a freeze where physics scheduling gets stuck in a loop (out-of-range add() chain link treated as end-of-chain, first walk site).
gm.phys.minlist_walk_bound_b on x86, x64 Same fix, second insertion-walk link check.
gm.phys.minlist_skip_list off x86, x64 Disables the physics min-list skip-list (long-jump) optimization outright.
gm.phys.minlist_replace on x86, x64 Replaces IVP_U_Min_List::add with a corrected copy of the stock algorithm.
gm.phys.ctrl_remove_absent on x86, x64 Stops a crash when a constraint is removed from a physics object that has already been torn down.
gm.phys.coc_absent_bail on x86, x64 ctrl_remove_absent controller removal returns when the controller is absent from the sim unit's list instead of reading before the list array.
gm.phys.vhash_store_remove_bound opt-in x86 Bounds IVP_VHash_Store::remove_elem: its find loop has no null-slot break, so an absent key walks off the array.
gm.phys.friction_hash_init_size on x86 Creates the per-core friction hash with 16 initial slots instead of 2, cutting rehash+re-add churn as contacts accumulate.

When an engine update changes the code a fix is tied to, you'll get a notice in the console, and every crash report shows the applied/skipped counts so you always know what's actually running.

Clone this wiki locally