Files
ReSkate/Extension/World/physics_wake_guard.cpp
T
AyeZeeBB b67fe93e55 Crashes: guards for 2.1.1's top crash sites, launcher checks, stuck-load reports, quieter logs
From 2.1.1's Sentry reports (60,000 in three days, 2026-10-21):
- Textures: a stale render texture handle takes the game's default texture instead of reading
  through null (Skate.exe+0x3e897fb, a third of all reports, every one with TheVlixOne-Nike_Pack 2.3.x).
- Physics: the wake pass's bitset is made as long as the world's owner list once indices pass
  20000, after long multiplayer sessions (+0x3d99e2f, +0x3d9ee43, +0x3d9ee47).
- Music: more than 1024 candidates are picked by ReSkate; the game's shuffle wrote past its stack table.
- Streaming: a staged read's piece that reaches past its 64 KB buffer is left unfilled (VCRUNTIME memcpy).
- Multiplayer: a remote skater or board whose parent bus link is damaged is left alive instead of
  destroyed (+0x2b94cd0).
- Mesh pool: on an integrated GPU its budget is capped by the RAM and page file free; the Memory
  line says what the PC has left.
Launcher:
- A folder with non-ASCII letters is a hard stop (the boot crash +0x18df4cf came after "Start anyway").
- The game's data files are checked against the supported build's sizes (from Steam's manifest);
  missing, cut-short or online-only files send the player to verifying.
- One game per folder: the launcher will not merge or start while one runs there, and ReSkate.dll
  holds the folder's mutex.
- Advice on the ready screen for PCs short of RAM and page file; reports carry pc.ram_gb and
  pc.commit_free_gb.
Stuck loads:
- A load in one stage for two minutes sends a stuck-load report (every thread's stack and the log,
  error.type stall, load.stage, load.seconds) while the game goes on (crash protocol 6).
- A load the player closes sends its timing with the stage it was in.
Logs: the PanelTrace diagnostics and the neighborhood unlock override's observation lines are gone.
2026-10-21 18:10:58 -05:00

78 lines
3.3 KiB
C++

#include "physics_wake_guard.h"
#include "Engine/Core/Hooks/hooks.h"
#include "Engine/Core/Log/logging.h"
#include "Engine/Core/Platform/memory.h"
#include "Engine/Game/Build/addresses.h"
#include "Engine/Game/Build/20260929/physics_wake.h"
#include <array>
#include <atomic>
#include <bit>
namespace dingosdk::physics_wake_guard {
namespace {
namespace pw = addr::physics_wake;
using Wake = void (*)(std::uintptr_t);
Wake original{};
std::atomic<bool> installed{};
std::atomic<std::uint32_t> most{};
std::atomic<std::uint64_t> widened{};
// Said the first few times, then each power of two: a session past the limit stays past it.
__declspec(noinline) void note(std::uint32_t owners, std::uint32_t limit) noexcept {
const auto count = widened.fetch_add(1, std::memory_order_relaxed) + 1;
if (count > 4 && !std::has_single_bit(count)) return;
try {
logging::log(logging::Level::info, logging::Channel::runtime,
"Physics: the world has {} physics owners, more than the {} the game's wake pass makes room for, where the game "
"would have crashed; the pass makes room for all of them ({} pass(es) since the game started).", owners, limit, count);
} catch (...) {}
}
// The world's owner count, 0 when it cannot be read (the game's own size is then kept).
std::uint32_t owners_of(std::uintptr_t system) noexcept {
std::uintptr_t world{}, begin{}, end{};
if (!memory::peek(system + pw::world_offset, world) || !world ||
!memory::peek(world + pw::owner_generations_begin, begin) ||
!memory::peek(world + pw::owner_generations_end, end) || end < begin || (end - begin) % 4)
return 0;
const auto count = (end - begin) / 4;
return count > 0x7fffffc0u ? 0 : static_cast<std::uint32_t>(count);
}
void guarded(std::uintptr_t system) {
auto* const limit = system ? reinterpret_cast<std::uint32_t*>(system + pw::owner_limit_offset) : nullptr;
const auto owners = system ? owners_of(system) : 0;
if (!limit || owners <= *limit) return original(system);
// The pass runs on the entity update's thread and is the one reader of the limit, which is
// put back as soon as it returns.
const auto stock = *limit;
*limit = owners;
if (owners > most.load(std::memory_order_relaxed)) most.store(owners, std::memory_order_relaxed);
note(owners, stock);
original(system);
*limit = stock;
}
} // namespace
bool install(std::uintptr_t base) noexcept {
if (installed.exchange(true)) return original != nullptr;
if (!base) return false;
std::array<unsigned char, 32> head{};
if (!memory::peek(base + pw::wake.rva, head) || head != pw::wake.bytes) {
logging::log(logging::Level::warning, logging::Channel::runtime,
"Physics wake guard: this build's physics code differs; it is left as it is.");
return false;
}
const auto target = reinterpret_cast<void*>(base + pw::wake.rva);
if (hook_prepare(target, reinterpret_cast<void*>(&guarded), reinterpret_cast<void**>(&original)) != HookOk ||
hook_enable(target) != HookOk) {
original = nullptr;
logging::log(logging::Level::warning, logging::Channel::runtime, "Physics wake guard: the game's function could not be hooked.");
return false;
}
return true;
}
std::uint32_t widest() noexcept { return most.load(std::memory_order_relaxed); }
} // namespace dingosdk::physics_wake_guard