crash fix

This commit is contained in:
AyeZeeBB committed 2026-10-16 17:34:06 -05:00
1 parent 8f92b41a78
commit 290089e16d
9 files changed
+170 -9

No files matched your search

@@ -0,0 +1,22 @@
#pragma once
#include "Engine/Game/Build/fingerprint.h"
#include <cstdint>
// A mesh's render settings (Extension/World/mesh_settings_guard.cpp).
namespace dingosdk::game::build::v20260929::mesh_settings {
// apply_settings(mesh, settings): as a level's meshes load (0x3f59610, for each mesh whose flag
// at + 0x3b0 is clear), it builds the settings object of the mesh's asset in place at mesh + 0x340
// (0x70 bytes, a reflected instance of 0x76f26f0) when the asset has one; settings is that
// object's source, null when it has none. Its caller sets the flag either way.
inline constexpr Fingerprint apply_settings{0x3f5beb0, {
0x48,0x89,0x5c,0x24,0x10,0x57,0x48,0x83,0xec,0x60,0x80,0xb9,0xb0,0x03,0x00,0x00,
0x00,0x48,0x8b,0xfa,0x48,0x8b,0xd9,0x74,0x20,0x48,0x81,0xc1,0x40,0x03,0x00,0x00}};
// The mesh (0x3c0 bytes, made at 0x3f33770) leaves + 0x340 to + 0x3af as the allocation had it.
inline constexpr std::ptrdiff_t settings_offset = 0x340;
// The settings object's string the mesh frees as it unloads (0x3f5fa20, through 0x1584c50,
// whenever the flag is set): with no settings object it is whatever the allocation left there.
inline constexpr std::ptrdiff_t unload_string_offset = 0x360;
inline constexpr std::ptrdiff_t applied_flag_offset = 0x3b0;
// The game's empty string: a string that holds it frees nothing (0x1584c50 compares with it).
inline constexpr std::uintptr_t empty_string = 0x72fe704;
}
+69
View File
@@ -0,0 +1,69 @@
#include "mesh_settings_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/mesh_settings.h"
#include <array>
#include <atomic>
#include <bit>
#include <cstring>
namespace dingosdk::mesh_settings_guard {
namespace {
namespace ms = addr::mesh_settings;
using ApplySettings = void (*)(std::byte*, void*);
ApplySettings original{};
std::uintptr_t empty{};
std::atomic<bool> installed{};
std::atomic<std::uint64_t> missing{}, stale{};
// (A count worth a line in the log: the first few, then each power of two.)
__declspec(noinline) void note(std::uint64_t count, std::uint64_t held, std::uintptr_t value) noexcept {
if (count > 4 && !std::has_single_bit(count)) return;
try {
logging::log(logging::Level::info, logging::Channel::runtime,
"Mesh settings: {} mesh(es) have none ({} held something the game would have freed on unload; this one {:#x}); they are "
"given an empty string.", count, held, value);
} catch (...) {}
}
void guarded(std::byte* mesh, void* settings) {
// Only a mesh about to be marked as having settings it will not get: the string its unload
// frees is made the empty one. A mesh that gets its settings has the string made with them.
if (mesh && !settings && mesh[ms::applied_flag_offset] == std::byte{0}) {
std::uintptr_t held{};
std::memcpy(&held, mesh + ms::unload_string_offset, sizeof(held));
std::memcpy(mesh + ms::unload_string_offset, &empty, sizeof(empty));
const bool would_free = held != 0 && held != empty;
const auto count = missing.fetch_add(1, std::memory_order_relaxed) + 1;
const auto freed = stale.fetch_add(would_free ? 1 : 0, std::memory_order_relaxed) + (would_free ? 1 : 0);
note(count, freed, held);
}
original(mesh, settings);
}
} // 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 + ms::apply_settings.rva, head) || head != ms::apply_settings.bytes) {
logging::log(logging::Level::warning, logging::Channel::runtime,
"Mesh settings guard: this build's mesh settings code differs; it is left as it is.");
return false;
}
empty = base + ms::empty_string;
const auto target = reinterpret_cast<void*>(base + ms::apply_settings.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, "Mesh settings guard: the game's function could not be hooked.");
return false;
}
return true;
}
std::uint64_t without_settings() noexcept { return missing.load(std::memory_order_relaxed); }
std::uint64_t would_have_freed() noexcept { return stale.load(std::memory_order_relaxed); }
} // namespace dingosdk::mesh_settings_guard
+17
View File
@@ -0,0 +1,17 @@
#pragma once
#include <cstdint>
// Leaving a level crashed the game in its allocator (Skate.exe+0x3d9ed9d, +0x3d9ee43, +0x3d9a856)
// as it freed a string of each unloading mesh (Engine/Game/Build/<build>/mesh_settings.h). The
// string belongs to the settings object the game builds inside the mesh; a mesh whose asset has no
// settings object is marked as having one anyway, and its string is whatever that memory held
// before (often a pointer freed long ago). The guard gives such a mesh the game's empty string
// first, so its unload frees nothing.
namespace dingosdk::mesh_settings_guard {
// Checks this build's function and hooks it; once. False when the build differs (no guard).
bool install(std::uintptr_t base) noexcept;
// Meshes without a settings object since the game started, and how many of them held something
// the game would have freed.
std::uint64_t without_settings() noexcept;
std::uint64_t would_have_freed() noexcept;
}
+40 -9
View File
@@ -1,10 +1,13 @@
#include "gui_internal.h"
#include "gui_renderer.h"
#include "mod_manager.h"
#include "problem.h"
#include <cmath>
#include <fstream>
#include <iterator>
#include <format>
// The main screen: background, name plate, tiles and the STATUS tile.
@@ -120,21 +123,47 @@ void progress_bar(ImDrawList* draw, ImVec2 a, ImVec2 b, float fill, float time)
skate_theme::striped_bar(draw, a, b, fill, time, g_scale);
}
// Loads the signed-in account's Steam picture for the plate, or drops it when Steam is closed or
// signed out. Only re-decoded when the cached file changes.
void refresh_steam_avatar(Ui& ui) {
const auto path = launcher_app::steam_avatar_path();
std::error_code error;
const auto time = path.empty() ? fs::file_time_type{} : fs::last_write_time(path, error);
if (path == ui.steam_avatar_path && time == ui.steam_avatar_time) return;
if (ui.steam_avatar && g_renderer) g_renderer->release_texture(ui.steam_avatar);
ui.steam_avatar = {};
ui.steam_avatar_path = path;
ui.steam_avatar_time = time;
if (path.empty() || !g_renderer) return;
std::ifstream input(path, std::ios::binary);
const std::vector<unsigned char> bytes{std::istreambuf_iterator<char>(input), std::istreambuf_iterator<char>()};
std::vector<unsigned char> pixels;
UINT width{}, height{};
if (!bytes.empty() && decode_image(bytes, ImVec2(S(46), S(46)), pixels, width, height))
ui.steam_avatar = g_renderer->upload_texture(pixels, width, height);
}
// Name plate in the top-left corner, like the HUB's player card.
void draw_plate(ImDrawList* draw, const Fonts& fonts, ImVec2 position, const std::string& title, const std::string& version) {
void draw_plate(ImDrawList* draw, const Fonts& fonts, ImVec2 position, const std::string& title, const std::string& version,
ImTextureID avatar) {
const float h = S(46);
const float text = std::max(fonts.bold->CalcTextSizeA(fonts.bold->FontSize, FLT_MAX, 0, title.c_str()).x,
fonts.body->CalcTextSizeA(fonts.body->FontSize, FLT_MAX, 0, version.c_str()).x);
const float width = std::max(S(300), h + S(28) + text);
draw->AddRectFilled(ImVec2(position.x + h, position.y + S(3)), ImVec2(position.x + width, position.y + h - S(3)),
rgba(44, 47, 49, 0.94f));
draw->AddRectFilled(position, ImVec2(position.x + h, position.y + h), color::avatar);
// A small deck on the avatar tile.
const ImVec2 c(position.x + h * 0.5f, position.y + h * 0.5f);
const int start = draw->VtxBuffer.Size;
draw->AddRectFilled(ImVec2(c.x - S(15), c.y - S(5.5f)), ImVec2(c.x + S(15), c.y + S(5.5f)), color::text, S(5.5f));
for (const float x : {-9.0f, 9.0f}) draw->AddCircleFilled(ImVec2(c.x + S(x), c.y), S(1.8f), color::avatar);
rotate_since(draw, start, -40.0f, c);
if (avatar) {
// The player's Steam picture in the tile.
draw->AddImage(avatar, position, ImVec2(position.x + h, position.y + h));
} else {
draw->AddRectFilled(position, ImVec2(position.x + h, position.y + h), color::avatar);
// A small deck on the avatar tile.
const ImVec2 c(position.x + h * 0.5f, position.y + h * 0.5f);
const int start = draw->VtxBuffer.Size;
draw->AddRectFilled(ImVec2(c.x - S(15), c.y - S(5.5f)), ImVec2(c.x + S(15), c.y + S(5.5f)), color::text, S(5.5f));
for (const float x : {-9.0f, 9.0f}) draw->AddCircleFilled(ImVec2(c.x + S(x), c.y), S(1.8f), color::avatar);
rotate_since(draw, start, -40.0f, c);
}
draw->AddCircleFilled(ImVec2(position.x + S(3), position.y + S(3)), S(5), color::good);
draw->AddText(fonts.bold, fonts.bold->FontSize, ImVec2(position.x + h + S(12), position.y + S(5)), color::text, title.c_str());
draw->AddText(fonts.body, fonts.body->FontSize, ImVec2(position.x + h + S(12), position.y + S(24)), color::muted, version.c_str());
@@ -393,6 +422,7 @@ void frame(Launcher& launcher, const Fonts& fonts, HWND window, Ui& ui, ModsPane
// from the first Steam check; steam_offline_seen keeps it to one decision.
if (time - ui.steam_checked > 5) {
ui.steam_name = launcher_app::steam_persona_name();
refresh_steam_avatar(ui);
ui.steam_checked = time;
if (!ui.steam_offline_seen) {
ui.steam_offline_seen = true;
@@ -417,7 +447,8 @@ void frame(Launcher& launcher, const Fonts& fonts, HWND window, Ui& ui, ModsPane
if (update::binary_updates_enabled() && state.config && !state.config->launcher.version.empty())
version = "Launcher " + state.config->launcher.version;
const bool greet = !launcher.settings().offline && !ui.steam_name.empty();
draw_plate(draw, fonts, ImVec2(S(50), S(40)), greet ? "Welcome, " + ui.steam_name : std::string("ReSkate"), version);
draw_plate(draw, fonts, ImVec2(S(50), S(40)), greet ? "Welcome, " + ui.steam_name : std::string("ReSkate"), version,
launcher.settings().offline ? ImTextureID{} : ui.steam_avatar);
page_title(draw, fonts, ImVec2(S(52), S(88)), "RESKATE");
window_buttons(draw, window, size);
+5
View File
@@ -377,6 +377,11 @@ struct Ui {
// Steam display name for the name plate, re-read every few seconds.
std::string steam_name;
double steam_checked{-100};
// The account's Steam picture on the plate in place of the drawn logo; reloaded
// when Steam caches a new one or another account signs in.
ImTextureID steam_avatar{};
fs::path steam_avatar_path;
fs::file_time_type steam_avatar_time{};
// One-time notice that Skate will start offline because Steam is not
// running or not signed in. Decided on the first Steam check.
bool steam_offline{};
+11
View File
@@ -635,6 +635,17 @@ std::string steam_persona_name() {
return name;
}
std::filesystem::path steam_avatar_path() {
if (!steam_signed_in()) return {};
const auto user = active_steam_id();
const auto steam = steam_directory();
if (!user || steam.empty()) return {};
// Steam keeps each account's picture it has shown as config/avatarcache/<SteamID64>.png.
auto path = steam / L"config" / L"avatarcache" / (std::to_wstring(*user) + L".png");
std::error_code error;
return fs::is_regular_file(path, error) ? path : std::filesystem::path{};
}
// Skate.exe hard-imports MiniDumpWriteDump from the Windows API set
// api-ms-win-core-debug-minidump-l1-1-0.dll. Windows resolves that set through
// its API set schema, so no such file exists on disk; Wine/Proton does not map
+3
View File
@@ -41,6 +41,9 @@ std::wstring steam_offline_reason();
// Display name of the signed-in Steam account (UTF-8), or empty when Steam is
// not running, signed out, or the name cannot be read.
std::string steam_persona_name();
// The signed-in account's profile picture in Steam's avatar cache, or empty
// when Steam is not running, signed out, or has not cached it.
std::filesystem::path steam_avatar_path();
// Validates, starts, injects and resumes Skate; returns its process id.
// `created` receives the id as soon as the process exists (ReSkate's startup
// splash can open before this returns). Throws a user-facing message.
+2
View File
@@ -8,6 +8,7 @@
#include "Extension/Settings/replay_guard.h"
#include "Extension/World/load_error_guard.h"
#include "Extension/World/mesh_unload_guard.h"
#include "Extension/World/mesh_settings_guard.h"
#include "Extension/World/shader_permutation_guard.h"
#include "Extension/World/light_table_guard.h"
#include "Extension/World/stack_check_report.h"
@@ -329,6 +330,7 @@ extern "C" __declspec(dllexport) BOOL WINAPI DingoSDKDebugInitialize() {
(void)dingosdk::engine_tweaks::install(r.base);
(void)dingosdk::replay_guard::install(r.base);
(void)dingosdk::mesh_unload_guard::install(r.base);
(void)dingosdk::mesh_settings_guard::install(r.base);
(void)dingosdk::shader_permutation_guard::install(r.base);
(void)dingosdk::light_table_guard::install(r.base);
(void)dingosdk::load_error_guard::install(r.base);
+1
View File
@@ -88,6 +88,7 @@ add_library(dingosdk_runtime SHARED
Extension/Music/app_audio_capture.cpp
Extension/Music/music_inject.cpp
Extension/World/mesh_unload_guard.cpp
Extension/World/mesh_settings_guard.cpp
Extension/World/shader_permutation_guard.cpp
Extension/World/light_table_guard.cpp
Extension/World/stack_check_report.cpp