Revert "Keep launcher alive through Skate startup; trim per-frame client work"

This reverts commit b7932535e5.
This commit is contained in:
AyeZeeBB committed 2026-10-02 20:19:51 -05:00
1 parent b7932535e5
commit f7fc909ca7
17 files changed
+60 -177

No files matched your search

@@ -9,10 +9,9 @@ inline constexpr std::array<std::uint8_t, 2> 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<std::uint8_t, 6> exit_instruction{
0xff, 0x25, 0x7f, 0x43, 0x5e, 0xfd};
}
+18
View File
@@ -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<float, 16> 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];
+1 -24
View File
@@ -11,7 +11,6 @@
#include "Engine/Game/World/park_rotation.h"
#include "Engine/Game/World/world_layers.h"
#include <array>
#include <cmath>
#include <cstdint>
#include <deque>
#include <optional>
@@ -214,29 +213,7 @@ std::optional<Packet> decode(std::span<const std::uint8_t>) 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<float, 3> &);
std::array<float, 16> to_matrix(const Transform &);
Transform from_matrix(const std::array<float, 16> &);
-7
View File
@@ -8,7 +8,6 @@
#include <algorithm>
#include <Windows.h>
#include <array>
#include <atomic>
#include <cmath>
#include <fstream>
#include <limits>
@@ -257,9 +256,6 @@ std::string with_cached_entitlements(std::string text) {
std::string_view embedded_defaults() noexcept {
return {reinterpret_cast<const char*>(embedded_profile_defaults), sizeof(embedded_profile_defaults)};
}
namespace {
std::atomic<std::uint64_t> 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<storage::SaveDatabase>(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<const Snapshot> 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);
}
}
-3
View File
@@ -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<const MissionState> mission_state() const;
+1 -5
View File
@@ -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<std::string> seen; static std::set<std::uintptr_t> looked_at, arguments_seen;
static std::mutex mutex; static std::set<std::string> seen; static std::set<std::uintptr_t> 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<std::uintptr_t>(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);
}
@@ -3,9 +3,6 @@
#include "Extension/Progression/local_entitlement_trigger_runtime.h"
#include "local_gameplay_settings.h"
#include "local_user_settings.h"
#include <optional>
#include <string_view>
#include <array>
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<const void*>(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<std::uintptr_t>(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<dingosdk::Json> value; };
thread_local std::array<Entry, 256> 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<unsigned char>(text[i]);
if (c < 32 || c == 127) return nullptr;
}
entry.changes = 0;
entry.value = s.store->user_value(std::string_view(text, static_cast<std::size_t>(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;
+5 -5
View File
@@ -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<class T> std::optional<T> 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<T, bool>) { if (value->is_boolean()) return value->get<bool>(); }
if constexpr (std::is_same_v<T, float>) {
+15 -54
View File
@@ -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 <optional>
#include <string_view>
#include <array>
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<dingosdk::Json> native_setting_json(const NativeSettingValue* input) {
NativeSettingValue value{};
if (!memory::peek(reinterpret_cast<std::uintptr_t>(input), value) || !value.data) return {};
if (!read(reinterpret_cast<std::uintptr_t>(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<std::uintptr_t>(value.data), byte) && byte <= 1) return byte != 0;
if (read(reinterpret_cast<std::uintptr_t>(value.data), byte) && byte <= 1) return byte != 0;
return {};
}
case native_types::native_uint32: return native_scalar<std::uint32_t>(value.data);
case native_types::native_int32: return native_scalar<std::int32_t>(value.data);
case native_types::native_float32: return native_scalar<float>(value.data);
case native_types::native_cstring: {
std::uintptr_t text{};
if (!memory::peek(reinterpret_cast<std::uintptr_t>(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<std::size_t>(length));
std::uintptr_t text{}; std::string result;
if (!read(reinterpret_cast<std::uintptr_t>(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<std::uintptr_t>(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<dingosdk::Json> value; };
thread_local std::array<Entry, 256> 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<unsigned char>(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<std::size_t>(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<std::uintptr_t>(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<std::uintptr_t>(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;
}
+1 -1
View File
@@ -17,7 +17,7 @@ unsigned native_settings_location(std::uintptr_t group);
template<class T> std::optional<dingosdk::Json> native_scalar(const void* data) {
T value{};
if (!memory::peek(reinterpret_cast<std::uintptr_t>(data), value)) return {};
if (!read(reinterpret_cast<std::uintptr_t>(data), value)) return {};
if constexpr (std::is_floating_point_v<T>) if (!std::isfinite(value)) return {};
if constexpr (std::is_same_v<T, std::uint64_t>) if (value > INT64_MAX) return {};
return dingosdk::Json(value);
@@ -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<void*>(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<float>(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<float>(v); return true;
}
bool population_write(std::uintptr_t config, unsigned i, float value) {
+2 -2
View File
@@ -68,7 +68,7 @@ template<unsigned Kind> 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<unsigned Kind> 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<unsigned>((end - begin) / 8);
}
}
+3 -8
View File
@@ -275,14 +275,11 @@ int run(const launcher_app::Session& session, const std::vector<std::wstring>& 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<std::wstring>& 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);
+3 -12
View File
@@ -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<Row> 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();
+1 -3
View File
@@ -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};
};
+1 -2
View File
@@ -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. "
+4 -5
View File
@@ -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,