mirror of
https://github.com/Dingo-Shenanigans/ReSkate.git
synced 2026-10-17 17:27:19 +02:00
Revert "Keep launcher alive through Skate startup; trim per-frame client work"
This reverts commit b7932535e5.
This commit is contained in:
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};
|
||||
}
|
||||
@@ -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];
|
||||
|
||||
@@ -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> &);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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>) {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
@@ -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
@@ -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();
|
||||
|
||||
@@ -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};
|
||||
};
|
||||
|
||||
|
||||
@@ -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. "
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in new issue
Block a user