From 8e5fb7449886079542ecf57a0201de4610e321ce Mon Sep 17 00:00:00 2001 From: AyeZeeBB Date: Sat, 24 Oct 2026 22:47:02 -0500 Subject: [PATCH] Multiplayer: a remote skater whose parent link is one byte short is put right and destroyed, instead of crashing at map exit 2.1.2 left such an actor alive and forgotten. When the level went, the game released its parent through the same pointer, took one off the byte below the parent's vtable pointer and called through it (Skate.exe+0x2b8092f and +0x2b7fcfb, every report after the "link ... is damaged" warning with the same bus). A link exactly one less than the parent the bus also holds at +0x48 is now repaired before the destroy; other damage is still left alive. What shortens the link is not known yet. --- .../Remote/native_skater_spawn.cpp | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/Extension/Multiplayer/Remote/native_skater_spawn.cpp b/Extension/Multiplayer/Remote/native_skater_spawn.cpp index 6ec15b1c..1c88c70b 100644 --- a/Extension/Multiplayer/Remote/native_skater_spawn.cpp +++ b/Extension/Multiplayer/Remote/native_skater_spawn.cpp @@ -28,6 +28,22 @@ bool code(std::uintptr_t base, std::uintptr_t address) { // one less than the parent's address while the actor lived (the same parent is at bus + 0x48), // so the game read a vtable out of the middle of one and faulted; what writes it is not known // (2026-10-21). +// An actor left alive for that only crashed later: when the level went, the game released the +// parent through the same pointer, took one off the byte below the parent's vtable pointer, and +// called through it (Skate.exe+0x2b8092f, +0x2b7fcfb, every one after the warning below with the +// same bus; 2026-10-25). One less than the parent at bus + 0x48 is put right instead, and the actor +// destroyed as the game would. +void note_repaired_link(std::uintptr_t bus, std::uintptr_t parent) { + static std::atomic said{}; + if (said.fetch_add(1, std::memory_order_relaxed) >= 8) return; + try { + logging::log(logging::Level::warning, logging::Channel::network, + "Multiplayer: a remote actor's link to its parent in the game's entity tree was one byte short (bus {:#x}, " + "parent {:#x}); it is put right before the actor is destroyed.", + bus, parent); + } catch (...) { + } +} bool destroy_reaches_code(std::uintptr_t base, std::uintptr_t entity, std::uintptr_t owner, std::string &damage) { try { const auto bus = ptr(owner, 8); @@ -37,6 +53,11 @@ bool destroy_reaches_code(std::uintptr_t base, std::uintptr_t entity, std::uintp } if (ptr(bus, 0x28) != entity) return true; const auto up = ptr(bus, 0x20); + if (const auto parent = ptr(bus, 0x48); up % 8 == 7 && up + 1 == parent && code(base, ptr(ptr(parent), 0x18))) { + *reinterpret_cast(bus + 0x20) = parent; + note_repaired_link(bus, parent); + return true; + } if (up % 8 || !code(base, ptr(ptr(up), 0x18))) { damage = std::format("bus {:#x}, parent {:#x}, root bus {:#x}", bus, up, ptr(bus, 0x48)); return false;