From f7fc909ca71e213da0ee37aa2fc840cdd33134c8 Mon Sep 17 00:00:00 2001 From: AyeZeeBB Date: Fri, 2 Oct 2026 20:19:51 -0500 Subject: [PATCH] Revert "Keep launcher alive through Skate startup; trim per-frame client work" This reverts commit b7932535e560ecc4390f5fa33937ebeeb550a70e. --- .../Build/20260929/startup_interventions.h | 5 +- Extension/Multiplayer/Net/protocol.cpp | 18 +++++ Extension/Multiplayer/Net/protocol.h | 25 +------ Extension/Profile/local_profile.cpp | 7 -- Extension/Profile/local_profile.h | 3 - Extension/Progression/script_error_log.cpp | 6 +- .../Settings/local_gameplay_settings.cpp | 39 ----------- Extension/Settings/local_gameplay_settings.h | 10 +-- Extension/Settings/local_native_settings.cpp | 69 ++++--------------- Extension/Settings/local_native_settings.h | 2 +- Extension/World/local_population_controls.cpp | 7 +- Extension/World/local_population_controls.h | 4 +- Launcher/gui.cpp | 11 +-- Launcher/gui_home.cpp | 15 +--- Launcher/gui_internal.h | 4 +- Launcher/gui_settings.cpp | 3 +- Runtime/client_tick.cpp | 9 ++- 17 files changed, 60 insertions(+), 177 deletions(-) diff --git a/Engine/Game/Build/20260929/startup_interventions.h b/Engine/Game/Build/20260929/startup_interventions.h index 5cb7f94..11f7164 100644 --- a/Engine/Game/Build/20260929/startup_interventions.h +++ b/Engine/Game/Build/20260929/startup_interventions.h @@ -9,10 +9,9 @@ inline constexpr std::array frame_instruction{0xf3, 0xaa}; // Handler and data expected in R9/R14 at the frame clear. inline constexpr std::uintptr_t frame_handler = 0x0880d710; inline constexpr std::uintptr_t frame_data = 0x086772fc; -// Exit thunk (JMP [rip+...]) and the return address it is expected to be called from: -// the last LEA RSP,[RSP]; JMP stub before the PUSHFQ / LOCK CMPXCHG routine, as in 20260908. +// Exit thunk (JMP [rip+...]) and the return address it is expected to be called from. inline constexpr std::uintptr_t exit_thunk = 0x08a06ff3; -inline constexpr std::uintptr_t exit_return = 0x087fe7fb; +inline constexpr std::uintptr_t exit_return = 0x087755f2; inline constexpr std::array exit_instruction{ 0xff, 0x25, 0x7f, 0x43, 0x5e, 0xfd}; } diff --git a/Extension/Multiplayer/Net/protocol.cpp b/Extension/Multiplayer/Net/protocol.cpp index fc8eada..8be095b 100644 --- a/Extension/Multiplayer/Net/protocol.cpp +++ b/Extension/Multiplayer/Net/protocol.cpp @@ -965,6 +965,24 @@ std::uint64_t map_hash(std::string_view value) noexcept { } return result; } +Transform interpolate(const Transform &a, const Transform &b, float t) { + Transform out; + t = std::clamp(t, 0.0f, 1.0f); + for (unsigned i = 0; i < 3; ++i) { + out.position[i] = std::lerp(a.position[i], b.position[i], t); + out.scale[i] = std::lerp(a.scale[i], b.scale[i], t); + } + float dot{}, norm{}; + for (unsigned i = 0; i < 4; ++i) + dot += a.rotation[i] * b.rotation[i]; + for (unsigned i = 0; i < 4; ++i) { + out.rotation[i] = std::lerp(a.rotation[i], dot < 0 ? -b.rotation[i] : b.rotation[i], t); + norm += out.rotation[i] * out.rotation[i]; + } + for (float &v : out.rotation) + v /= std::sqrt(norm); + return out; +} std::array to_matrix(const Transform &t) { const auto q = interpolate(t, t, 0).rotation; const float x = q[0], y = q[1], z = q[2], w = q[3]; diff --git a/Extension/Multiplayer/Net/protocol.h b/Extension/Multiplayer/Net/protocol.h index 5973f57..913b8fb 100644 --- a/Extension/Multiplayer/Net/protocol.h +++ b/Extension/Multiplayer/Net/protocol.h @@ -11,7 +11,6 @@ #include "Engine/Game/World/park_rotation.h" #include "Engine/Game/World/world_layers.h" #include -#include #include #include #include @@ -214,29 +213,7 @@ std::optional decode(std::span) noexcept; bool newer_sequence(std::uint32_t candidate, std::uint32_t previous) noexcept; std::uint64_t map_hash(std::string_view) noexcept; bool valid_map_destination(std::string_view) noexcept; -// Normalized lerp between two transforms. Runs for every joint of every remote player each -// frame, so it is inline and plain arithmetic (std::lerp's edge-case branches and a divide -// per component were ~1% of a multiplayer client frame, profiled 2026-10-02). -inline Transform interpolate(const Transform &a, const Transform &b, float t) { - Transform out; - t = t < 0.0f ? 0.0f : t > 1.0f ? 1.0f : t; - for (unsigned i = 0; i < 3; ++i) { - out.position[i] = a.position[i] + (b.position[i] - a.position[i]) * t; - out.scale[i] = a.scale[i] + (b.scale[i] - a.scale[i]) * t; - } - const float dot = a.rotation[0] * b.rotation[0] + a.rotation[1] * b.rotation[1] + - a.rotation[2] * b.rotation[2] + a.rotation[3] * b.rotation[3]; - const float sign = dot < 0 ? -1.0f : 1.0f; - float norm{}; - for (unsigned i = 0; i < 4; ++i) { - out.rotation[i] = a.rotation[i] + (sign * b.rotation[i] - a.rotation[i]) * t; - norm += out.rotation[i] * out.rotation[i]; - } - const float inverse = 1.0f / std::sqrt(norm); - for (float &v : out.rotation) - v *= inverse; - return out; -} +Transform interpolate(const Transform &, const Transform &, float); void offset_pose(Pose &, const std::array &); std::array to_matrix(const Transform &); Transform from_matrix(const std::array &); diff --git a/Extension/Profile/local_profile.cpp b/Extension/Profile/local_profile.cpp index 13683fb..d35dd0b 100644 --- a/Extension/Profile/local_profile.cpp +++ b/Extension/Profile/local_profile.cpp @@ -8,7 +8,6 @@ #include #include #include -#include #include #include #include @@ -257,9 +256,6 @@ std::string with_cached_entitlements(std::string text) { std::string_view embedded_defaults() noexcept { return {reinterpret_cast(embedded_profile_defaults), sizeof(embedded_profile_defaults)}; } -namespace { -std::atomic store_changes{1}; -} Store::Store(std::filesystem::path path, const std::filesystem::path& defaults) : path_(storage::database_path(std::move(path))), database_(std::make_unique(path_, database::profile_schema())) { const auto seed = decode(with_cached_entitlements(defaults.empty() || !std::filesystem::exists(defaults) ? @@ -287,10 +283,8 @@ Store::Store(std::filesystem::path path, const std::filesystem::path& defaults) if (next.extensions.contains("location_travel") && next.extensions.at("location_travel").is_object()) for (const auto* key : {"destinations", "access_points"}) next.extensions.at("location_travel").erase(key); if (!database_->exists() || next != value_) commit(std::move(next)); - store_changes.fetch_add(1, std::memory_order_release); } Store::~Store() = default; -std::uint64_t Store::changes() noexcept { return store_changes.load(std::memory_order_acquire); } Snapshot Store::snapshot() const { std::lock_guard lock(mutex_); return value_; } std::shared_ptr Store::shared_snapshot() const { std::lock_guard lock(mutex_); @@ -458,7 +452,6 @@ void Store::commit(Snapshot next) { if (database_->exists()) database_->commit(std::move(tables)); else database_->initialize(std::move(tables)); value_ = std::move(next); - store_changes.fetch_add(1, std::memory_order_release); } } diff --git a/Extension/Profile/local_profile.h b/Extension/Profile/local_profile.h index c7b3577..8361776 100644 --- a/Extension/Profile/local_profile.h +++ b/Extension/Profile/local_profile.h @@ -137,9 +137,6 @@ public: std::string export_json() const; void import_json(std::string_view); std::uint64_t revision() const; - // Bumped whenever any Store's saved values may have changed (each commit, each Store made); - // read without the lock, so caches on hot paths can tell whether a lookup is still current. - static std::uint64_t changes() noexcept; // Same immutable object until a successful commit changes this Store's // revision. Retained readers remain valid after writes or Store destruction. std::shared_ptr mission_state() const; diff --git a/Extension/Progression/script_error_log.cpp b/Extension/Progression/script_error_log.cpp index 2c5b749..e6af050 100644 --- a/Extension/Progression/script_error_log.cpp +++ b/Extension/Progression/script_error_log.cpp @@ -14,7 +14,7 @@ void script_error(std::uintptr_t, std::uintptr_t*, std::uintptr_t* arguments) { PreserveError preserve; // Authored graphs raise the same error every frame, from the same constant text: a text // pointer already looked at is not read again (each character was a system call). - static std::mutex mutex; static std::set seen; static std::set looked_at, arguments_seen; + static std::mutex mutex; static std::set seen; static std::set looked_at; std::lock_guard lock(mutex); try { // The message position is not known from the binding alone; take the @@ -35,10 +35,6 @@ void script_error(std::uintptr_t, std::uintptr_t*, std::uintptr_t* arguments) { for (std::size_t index = 0; arguments && index < 4 && message.empty(); ++index) { std::uintptr_t argument{}, inner{}; if (!memory::peek(reinterpret_cast(arguments) + index * 8, argument) || argument < 0x10000) break; - // The same arguments come back every frame: each is probed once (read, not peeked: - // it is often not a pointer, and a failed peek costs an exception). - if (arguments_seen.contains(argument)) continue; - if (arguments_seen.size() < 4096) arguments_seen.insert(argument); if (read(argument, inner) && inner >= 0x10000) message = printable(inner); if (message.empty()) message = printable(argument); } diff --git a/Extension/Settings/local_gameplay_settings.cpp b/Extension/Settings/local_gameplay_settings.cpp index 0794e29..d80c906 100644 --- a/Extension/Settings/local_gameplay_settings.cpp +++ b/Extension/Settings/local_gameplay_settings.cpp @@ -3,9 +3,6 @@ #include "Extension/Progression/local_entitlement_trigger_runtime.h" #include "local_gameplay_settings.h" #include "local_user_settings.h" -#include -#include -#include namespace dingosdk::profile_runtime { // Applied gameplay values live in an ECS cache even when the user-value @@ -51,42 +48,6 @@ bool gameplay_setting_key(const void* reference, std::string& key) { return asset && identifier(reinterpret_cast(asset + 0x60), key); } -// Every script settings get the game makes (several a frame from its expressions) asks this. One -// store lookup per setting asset and profile change, cached on the asking thread in a small -// direct-mapped table: a hit is three peeks and compares, with no heap string, store lock or map -// walk (~0.5% of a multiplayer client frame before, profiled 2026-10-02). A hit needs the same -// asset, name address and first 8 name bytes, so a reused address cannot answer for another setting. -const dingosdk::Json* saved_gameplay_json(const void* reference) { - auto& s = local_runtime(); - if (!s.active.load(std::memory_order_acquire)) return nullptr; - std::uintptr_t asset{}, name{}; - if (!memory::peek(reinterpret_cast(reference), asset)) return nullptr; - asset &= ~std::uintptr_t{4}; - if (!asset || !memory::peek(asset + 0x60, name) || !name) return nullptr; - std::uint64_t prefix{}; - (void)memory::peek(name, prefix); // a short name at the end of a page stays 0: a miss refills - struct Entry { std::uintptr_t asset{}, name{}; std::uint64_t prefix{}, changes{}; std::optional value; }; - thread_local std::array cache; - const auto changes = profile::Store::changes(); - auto& entry = cache[((asset >> 4) ^ (asset >> 12)) & 255]; - if (entry.asset != asset || entry.name != name || entry.prefix != prefix || entry.changes != changes) { - char text[256]; - const auto length = memory::peek_cstring(name, text, sizeof(text)); - if (length <= 0) return nullptr; - for (std::ptrdiff_t i = 0; i < length; ++i) { - const auto c = static_cast(text[i]); - if (c < 32 || c == 127) return nullptr; - } - entry.changes = 0; - entry.value = s.store->user_value(std::string_view(text, static_cast(length))); - entry.asset = asset; - entry.name = name; - entry.prefix = prefix; - entry.changes = changes; - } - return entry.value ? &*entry.value : nullptr; -} - void save_gameplay_setting(const void* reference, const dingosdk::Json& value) { auto& s = local_runtime(); if (!s.active.load(std::memory_order_acquire)) return; diff --git a/Extension/Settings/local_gameplay_settings.h b/Extension/Settings/local_gameplay_settings.h index 9bd8cd8..09ab860 100644 --- a/Extension/Settings/local_gameplay_settings.h +++ b/Extension/Settings/local_gameplay_settings.h @@ -24,14 +24,14 @@ bool gameplay_user_edit(); bool gameplay_setting_key(const void* reference, std::string& key); -// The saved value for a script setting reference, or null: cached per setting asset and profile -// change on the asking thread (valid until this thread's next call). May throw. -const dingosdk::Json* saved_gameplay_json(const void* reference); - template std::optional saved_gameplay_setting(const void* reference) { + auto& s = local_runtime(); + if (!s.active.load(std::memory_order_acquire)) return {}; PreserveError preserve; try { - const auto* value = saved_gameplay_json(reference); + std::string key; + if (!gameplay_setting_key(reference, key)) return {}; + const auto value = s.store->user_value(key); if (!value) return {}; if constexpr (std::is_same_v) { if (value->is_boolean()) return value->get(); } if constexpr (std::is_same_v) { diff --git a/Extension/Settings/local_native_settings.cpp b/Extension/Settings/local_native_settings.cpp index 1334163..881e6b9 100644 --- a/Extension/Settings/local_native_settings.cpp +++ b/Extension/Settings/local_native_settings.cpp @@ -6,9 +6,6 @@ #include "Engine/Game/Build/20260929/local_native_settings.h" #include "Engine/Game/Build/20260929/named_settings.h" #include "local_user_settings.h" -#include -#include -#include namespace dingosdk::profile_runtime { namespace native_types = addr::named_settings; @@ -33,65 +30,32 @@ unsigned native_settings_location(std::uintptr_t group) { return 0; } -// Runs on every settings get the game's scripts make: peeked, not read (one ReadProcessMemory -// call per read, ~0.15% of a multiplayer client frame, profiled 2026-10-02). std::optional native_setting_json(const NativeSettingValue* input) { NativeSettingValue value{}; - if (!memory::peek(reinterpret_cast(input), value) || !value.data) return {}; + if (!read(reinterpret_cast(input), value) || !value.data) return {}; const auto type = value.type - local_runtime().base; switch (type) { case native_types::native_bool: { std::uint8_t byte{}; - if (memory::peek(reinterpret_cast(value.data), byte) && byte <= 1) return byte != 0; + if (read(reinterpret_cast(value.data), byte) && byte <= 1) return byte != 0; return {}; } case native_types::native_uint32: return native_scalar(value.data); case native_types::native_int32: return native_scalar(value.data); case native_types::native_float32: return native_scalar(value.data); case native_types::native_cstring: { - std::uintptr_t text{}; - if (!memory::peek(reinterpret_cast(value.data), text)) return {}; - char buffer[4097]; - const auto length = memory::peek_cstring(text, buffer, sizeof(buffer)); - if (length < 0) return {}; - return std::string(buffer, static_cast(length)); + std::uintptr_t text{}; std::string result; + if (!read(reinterpret_cast(value.data), text)) return {}; + for (std::size_t i = 0; i <= 4096; ++i) { + char c{}; if (!read(text + i, c)) return {}; + if (!c) return result; + result += c; + } } } return {}; } -namespace { -// The saved option for a native setting key, or null: one store lookup per key and profile change, -// cached on the asking thread in a small direct-mapped table (valid until this thread's next call). -// A hit needs the same key address, location and first 8 key bytes; the key text is read only on -// a miss. May throw. -const dingosdk::Json* saved_native_option(unsigned location, const char* key) { - const auto address = reinterpret_cast(key); - std::uint64_t prefix{}; - (void)memory::peek(address, prefix); // a short key at the end of a page stays 0: a miss refills - struct Entry { std::uintptr_t key{}; unsigned location{}; std::uint64_t prefix{}, changes{}; std::optional value; }; - thread_local std::array cache; - const auto changes = profile::Store::changes(); - auto& entry = cache[((address >> 3) ^ (address >> 11) ^ location) & 255]; - if (entry.key != address || entry.location != location || entry.prefix != prefix || entry.changes != changes) { - char text[256]; - const auto length = memory::peek_cstring(address, text, sizeof(text)); - if (length <= 0) return nullptr; - for (std::ptrdiff_t i = 0; i < length; ++i) { - const auto c = static_cast(text[i]); - if (c < 32 || c == 127) return nullptr; - } - entry.changes = 0; - entry.value = local_runtime().store->native_profile_option(location, std::string_view(text, static_cast(length))); - entry.key = address; - entry.location = location; - entry.prefix = prefix; - entry.changes = changes; - } - return entry.value ? &*entry.value : nullptr; -} -} - bool restore_native_setting(std::uintptr_t group, const char* key, std::uintptr_t type, const dingosdk::Json& saved) { const auto base = local_runtime().base; @@ -118,18 +82,15 @@ const NativeSettingValue* native_setting_get(std::uintptr_t group, const char* k auto* result = native_settings().get(group, key); PreserveError preserve; try { - const auto location = native_settings_location(group); - const auto* saved = location ? saved_native_option(location, key) : nullptr; + const auto location = native_settings_location(group); std::string id; + if (!location || !identifier(&key, id)) return result; + const auto saved = local_runtime().store->native_profile_option(location, id); if (!saved) return result; const auto current = native_setting_json(result); - if (!current || *current == *saved) return result; - // Copied before the restore: a get it causes on this thread reuses the cache. - const auto wanted = *saved; - std::string name; - (void)identifier(&key, name); NativeSettingValue native{}; - if (memory::peek(reinterpret_cast(result), native) && restore_native_setting(group, key, native.type, wanted)) - dingosdk::logging::event(dingosdk::logging::Channel::settings, dingosdk::Json{{"event","local_native_setting_restored"},{"location",location},{"key",name}}.dump().c_str()); + if (current && *current != *saved && read(reinterpret_cast(result), native) && + restore_native_setting(group, key, native.type, *saved)) + dingosdk::logging::event(dingosdk::logging::Channel::settings, dingosdk::Json{{"event","local_native_setting_restored"},{"location",location},{"key",id}}.dump().c_str()); } catch (...) { dingosdk::logging::event(dingosdk::logging::Channel::settings, "{\"event\":\"local_native_setting_restore_failed\"}"); } return result; } diff --git a/Extension/Settings/local_native_settings.h b/Extension/Settings/local_native_settings.h index 8054895..8575baf 100644 --- a/Extension/Settings/local_native_settings.h +++ b/Extension/Settings/local_native_settings.h @@ -17,7 +17,7 @@ unsigned native_settings_location(std::uintptr_t group); template std::optional native_scalar(const void* data) { T value{}; - if (!memory::peek(reinterpret_cast(data), value)) return {}; + if (!read(reinterpret_cast(data), value)) return {}; if constexpr (std::is_floating_point_v) if (!std::isfinite(value)) return {}; if constexpr (std::is_same_v) if (value > INT64_MAX) return {}; return dingosdk::Json(value); diff --git a/Extension/World/local_population_controls.cpp b/Extension/World/local_population_controls.cpp index fc3c032..0ad9156 100644 --- a/Extension/World/local_population_controls.cpp +++ b/Extension/World/local_population_controls.cpp @@ -21,14 +21,13 @@ bool population_config_identity(std::uintptr_t config) { // The validated manager supplies a TrafficSystemConfig, but the asset name // varies by map (BAM uses Gameplay/AI/BAM_TrafficSystemConfig). The exact // native class identifies the field layout; the default asset name does not. - return memory::peek(config + 8, type) && type == local_runtime().base + addr::population::traffic_system_config_type && + return read(config + 8, type) && type == local_runtime().base + addr::population::traffic_system_config_type && identifier(reinterpret_cast(config + 0x18), name); } -// Every population tick, on the validated live config: peeked. bool population_value(std::uintptr_t config, unsigned i, float& out) { - if (i == 0 || i == 3) return memory::peek(config + population_offsets[i], out) && std::isfinite(out) && out >= 0 && out <= 100; - std::uint32_t v{}; if (!memory::peek(config + population_offsets[i], v) || v > 1000) return false; out = static_cast(v); return true; + if (i == 0 || i == 3) return read(config + population_offsets[i], out) && std::isfinite(out) && out >= 0 && out <= 100; + std::uint32_t v{}; if (!read(config + population_offsets[i], v) || v > 1000) return false; out = static_cast(v); return true; } bool population_write(std::uintptr_t config, unsigned i, float value) { diff --git a/Extension/World/local_population_controls.h b/Extension/World/local_population_controls.h index aa57bb3..cf20202 100644 --- a/Extension/World/local_population_controls.h +++ b/Extension/World/local_population_controls.h @@ -68,7 +68,7 @@ template void population_tick(std::uintptr_t manager) { const auto found = p.nodes.find(manager); if (found != p.nodes.end() && population_identity(manager, found->second.realm)) { auto& node = found->second; std::uintptr_t config{}; std::uint8_t running{}; - if (memory::peek(manager + 0x108, config) && memory::peek(manager + 0x152, running) && running == 1) { + if (read(manager + 0x108, config) && read(manager + 0x152, running) && running == 1) { config &= ~std::uintptr_t{4}; if (config != node.config) { population_release_config(manager); node.config = config; } if (population_config_identity(config)) { @@ -90,7 +90,7 @@ template void population_tick(std::uintptr_t manager) { p.observed_at[Kind] = GetTickCount64(); r.model.population_ready[Kind] = ok; std::uintptr_t begin{}, end{}; const unsigned offset = Kind == 0 ? 0xa8 : 0xe8; - if (memory::peek(manager + offset, begin) && memory::peek(manager + offset + 8, end) && end >= begin && end - begin <= 8000) + if (read(manager + offset, begin) && read(manager + offset + 8, end) && end >= begin && end - begin <= 8000) r.model.population[Kind] = static_cast((end - begin) / 8); } } diff --git a/Launcher/gui.cpp b/Launcher/gui.cpp index dd7e99e..d16d102 100644 --- a/Launcher/gui.cpp +++ b/Launcher/gui.cpp @@ -275,14 +275,11 @@ int run(const launcher_app::Session& session, const std::vector& a auto& ui = *ui_storage; ModsPanel mods_panel; bool running = true; - // The game this launcher started: hide when its splash opens, then wait for it - // to close and return, or exit (Close the launcher when Skate starts). - // Staying alive until then matters: outside a Steam library, Skate's protection - // closed the game (TerminateProcess 0x32) when the launcher exited during startup. + // The game this launcher started: hide when its splash opens, then exit + // (Close the launcher when Skate starts) or wait and return when it closes. HANDLE game{}; DWORD game_id{}; bool hidden{}; - bool exit_with_game{}; while (running) { MSG message; while (PeekMessageW(&message, nullptr, 0, 0, PM_REMOVE)) { @@ -305,17 +302,15 @@ int run(const launcher_app::Session& session, const std::vector& a hidden = true; } // Exit only after injection succeeded, so a failed launch can still report. - if (hidden && launcher.launched() && launcher.settings().close_on_launch) exit_with_game = true; + if (hidden && launcher.launched() && launcher.settings().close_on_launch) break; if (hidden && !launcher.busy() && launcher.snapshot().phase == Phase::failed) { ShowWindow(window, SW_SHOWNORMAL); SetForegroundWindow(window); hidden = false; - exit_with_game = false; } if (WaitForSingleObject(game, 0) == WAIT_OBJECT_0) { CloseHandle(game); game = nullptr; - if (exit_with_game) break; launcher.game_exited(hidden); if (hidden) { ShowWindow(window, SW_SHOWNORMAL); diff --git a/Launcher/gui_home.cpp b/Launcher/gui_home.cpp index ef62801..7f94c78 100644 --- a/Launcher/gui_home.cpp +++ b/Launcher/gui_home.cpp @@ -239,9 +239,7 @@ bool mods_tile(ImDrawList* draw, const Fonts& fonts, ImVec2 position, ImVec2 siz } // The STATUS tile, laid out like the HUB's BOUNTIES tile: a header and icon rows. -// `steam_offline`: Steam isn't running or signed in, so PLAY starts offline. -void status_tile(ImDrawList* draw, const Fonts& fonts, const State& state, ImVec2 position, float width, float time, - bool steam_offline) { +void status_tile(ImDrawList* draw, const Fonts& fonts, const State& state, ImVec2 position, float width, float time) { struct Row { Icon icon; std::string text; std::string detail; }; std::vector rows; Icon now = Icon::busy; @@ -254,11 +252,6 @@ void status_tile(ImDrawList* draw, const Fonts& fonts, const State& state, ImVec default: break; } rows.push_back({now, state.status, state.detail}); - // While launching, the status detail already says the game is starting offline. - if (steam_offline && state.phase != Phase::launching) - rows.push_back({Icon::warning, "Steam isn't open or signed in", - "PLAY starts skate. in offline mode as Unknown Player; multiplayer is hidden. " - "Open Steam and sign in to play online."}); if (state.config) { const auto build = state.config->game.build_id.empty() ? state.config->game.manifest_id : state.config->game.build_id; const bool game_ok = state.phase != Phase::game_missing && state.phase != Phase::game_outdated && @@ -337,8 +330,7 @@ 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; if (time - ui.steam_checked > 5) { - ui.steam_signed_in = launcher_app::steam_signed_in(); - ui.steam_name = ui.steam_signed_in ? launcher_app::steam_persona_name() : std::string(); + ui.steam_name = launcher_app::steam_persona_name(); ui.steam_checked = time; } const bool greet = !launcher.settings().offline && !ui.steam_name.empty(); @@ -414,8 +406,7 @@ void frame(Launcher& launcher, const Fonts& fonts, HWND window, Ui& ui, ModsPane (ImGui::IsKeyPressed(ImGuiKey_Enter, false) || ImGui::IsKeyPressed(ImGuiKey_KeypadEnter, false))) act(); ImGui::EndDisabled(); - status_tile(draw, fonts, state, ImVec2(S(60), S(200)), S(360), time, - !ui.steam_signed_in && !launcher.settings().offline); + status_tile(draw, fonts, state, ImVec2(S(60), S(200)), S(360), time); if (modal) draw->AddRectFilled(ImVec2(0, 0), size, rgba(4, 6, 9, 0.72f)); g_drag_allowed = !modal && !ImGui::IsAnyItemHovered(); diff --git a/Launcher/gui_internal.h b/Launcher/gui_internal.h index 8d8118b..5363e1a 100644 --- a/Launcher/gui_internal.h +++ b/Launcher/gui_internal.h @@ -233,10 +233,8 @@ struct Ui { // The MODS tile's "2 of 3 enabled", re-read every few seconds. std::string mods_detail; double mods_checked{-100}; - // Steam display name for the name plate, and whether Steam is running and - // signed in (PLAY starts offline otherwise), re-read every few seconds. + // Steam display name for the name plate, re-read every few seconds. std::string steam_name; - bool steam_signed_in{true}; double steam_checked{-100}; }; diff --git a/Launcher/gui_settings.cpp b/Launcher/gui_settings.cpp index 4aabf8f..9071d73 100644 --- a/Launcher/gui_settings.cpp +++ b/Launcher/gui_settings.cpp @@ -184,8 +184,7 @@ void settings_window(Launcher& launcher, const Fonts& fonts, ImVec2 size, Ui& ui default: section_caption(fonts, "LAUNCHER"); setting_check("Close the launcher when Skate starts", - "The launcher hides while you play and exits when the game closes. " - "Turn off to have it come back instead.", + "Otherwise the launcher hides while you play and comes back when the game closes.", settings.close_on_launch); setting_check("Install ReSkate updates", "Turn off to keep the ReSkate.dll and launcher you have, such as a test build someone gave you. " diff --git a/Runtime/client_tick.cpp b/Runtime/client_tick.cpp index a735fc5..d0dc147 100644 --- a/Runtime/client_tick.cpp +++ b/Runtime/client_tick.cpp @@ -192,9 +192,8 @@ void update_model(std::uintptr_t client, TickState& frame) { auto& r = runtime(); std::uintptr_t vtable{}; DWORD state{}, game_type{}; - // Every client tick: peeked, not read (three ReadProcessMemory calls a tick, profiled 2026-10-02). - if (!memory::peek(client, vtable) || vtable != r.base + engine::client_vtable || client > highest - 0x2a0 || - !memory::peek(client + 0xc4, state) || state > 26 || !memory::peek(client + 0xc0, game_type) || game_type > 3) return; + if (!read(client, vtable) || vtable != r.base + engine::client_vtable || client > highest - 0x2a0 || + !read(client + 0xc4, state) || state > 26 || !read(client + 0xc0, game_type) || game_type > 3) return; DWORD no_thread{}; r.engine_thread.compare_exchange_strong(no_thread, GetCurrentThreadId()); if (r.engine_thread.load() != GetCurrentThreadId()) return; @@ -928,7 +927,7 @@ void tick(std::uintptr_t client, std::uintptr_t update) { ? *tick_state.context : native_context(r.base, client, game_type); }; const bool multiplayer_ready=!r.observer_failed && !loading && - memory::peek(client+0xc4,state) && (state==13 || state==21) && memory::peek(client+0xc0,game_type) && + read(client+0xc4,state) && (state==13 || state==21) && read(client+0xc0,game_type) && context_ready(); { DINGO_PROFILE_ZONE("tick/multiplayer"); @@ -940,7 +939,7 @@ void tick(std::uintptr_t client, std::uintptr_t update) { // session placed the remote skaters for this frame (and also out of play, to give up). std::uintptr_t camera_context{}; const bool camera_phase = tick_state.camera_issue ? *tick_state.camera_issue == nullptr : - memory::peek(client + 8, camera_context) && !dingosdk::camera_probe_unavailable_reason(camera_context); + read(client + 8, camera_context) && !dingosdk::camera_probe_unavailable_reason(camera_context); { DINGO_PROFILE_ZONE("tick/native party"); dingosdk::multiplayer::tick_native_party_actions(r.base, client,