mirror of
https://github.com/Dingo-Shenanigans/ReSkate.git
synced 2026-10-28 00:17:08 +01:00
Mesh streaming pool (fixes loading crashes on older GPUs) - 1.0.0 forced MeshStreaming.PoolSize to 3.5 GiB on every card. An RX 580 crashed while loading with DXGI_ERROR_DEVICE_RESET, and the oversized pool is the likely cause. The pool is now a quarter of the largest hardware adapter's dedicated video memory, capped at 3.5 GiB; cards of 6 GB or less keep the game's own 520 MB. The card, its memory and the chosen size are logged once at startup. Remote players (busy servers) - Transform interpolate is inline plain arithmetic: 22 -> 5.8 ns per joint. It was 4% of a busy server's client frame, since every player within 200 m has the whole skeleton interpolated every frame. - Players out of the camera's view and more than 8 m away are sampled at 20 Hz, like far players. Their position still updates every frame for collision, sound and nametags, and a player coming into view is sampled that frame. Expression hooks (run for every expression the game executes) - No profiler zones in the expression hooks: taking a sample switched them on, inflating the very samples that measured the hooks. - The throwdown lab pump does one sorted lookup of the running graph and skips everything else for graphs it doesn't watch (graph_throttle gets throttled_graph for this). Settings and memory reads - Script gameplay-setting and native-setting gets are served from small per-thread caches, keyed on the asset/key address, its first 8 name bytes and a new profile::Store::changes() counter, instead of a store lookup each call. - Per-frame reads in the client tick, native settings and population controls use memory::peek instead of ReadProcessMemory. - Script error logging probes each argument pointer once.
173 lines
8.2 KiB
C++
173 lines
8.2 KiB
C++
#include "Engine/Core/Log/logging.h"
|
|
#include "Extension/Profile/runtime_internal.h"
|
|
#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
|
|
|
|
// service does not exist. Hook the applied cache, never the pending UI edits.
|
|
|
|
// See analysis/native-game-settings.md for the two distinct native boundaries.
|
|
|
|
GameplaySettingFunctions& gameplay_settings() { static GameplaySettingFunctions f; return f; }
|
|
|
|
// UI edits and repeated backend initialization share the same setters. Only
|
|
|
|
// the reviewed menu actions authorize a durable edit; initialization restores.
|
|
|
|
unsigned gameplay_expression_kind(std::uintptr_t vm) {
|
|
std::uintptr_t instance{}, resource{}, current{}; std::uint32_t key{};
|
|
std::array<std::uint32_t, 10> layout{};
|
|
if (!vm || !read(vm + 0x30, instance) || !read(instance, resource) ||
|
|
!read(vm + 0x38, current) || current != resource || !read(resource + 0x10, key) ||
|
|
!read(resource + 0x20, layout)) return 0;
|
|
if ((key == 0x6777763e && layout == std::array<std::uint32_t, 10>{112,2696,1010,2329,11,0,35,1638514,6881314,66563}) ||
|
|
(key == 0xff4a234e && layout == std::array<std::uint32_t, 10>{80,416,184,359,6,0,6,327698,2949131,1}) ||
|
|
(key == 0xff2662d5 && layout == std::array<std::uint32_t, 10>{96,2000,661,1338,6,0,23,983108,5046294,2}) ||
|
|
(key == 0x82c1331d && layout == std::array<std::uint32_t, 10>{64,496,202,472,9,0,5,28,1507339,66305}) ||
|
|
(key == 0x6e9f5108 && layout == std::array<std::uint32_t, 10>{96,1928,746,1444,11,0,14,1638495,7208982,17041665})) return 1;
|
|
if ((key == 0x7a55b1d7 && layout == std::array<std::uint32_t, 10>{224,5624,721,2953,19,0,17,2293843,6488140,66561}) ||
|
|
(key == 0x375c4ce9 && layout == std::array<std::uint32_t, 10>{112,1552,1014,2506,33,0,7,983209,8454179,264449})) return 2;
|
|
return 0;
|
|
}
|
|
|
|
bool gameplay_user_edit() {
|
|
if (gameplay_expression_kind(executing_expression) == 1) return true;
|
|
auto* scope = expression_scope;
|
|
for (unsigned depth = 0; scope && depth < 32; ++depth, scope = scope->parent)
|
|
if (scope->vm != executing_expression && gameplay_expression_kind(scope->vm) == 1) return true;
|
|
return false;
|
|
}
|
|
|
|
bool gameplay_setting_key(const void* reference, std::string& key) {
|
|
std::uintptr_t asset{};
|
|
if (!memory::peek(reinterpret_cast<std::uintptr_t>(reference), asset)) return false; // every script settings get
|
|
asset &= ~std::uintptr_t{4};
|
|
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;
|
|
PreserveError preserve;
|
|
try {
|
|
std::string key;
|
|
if (!gameplay_setting_key(reference, key)) return;
|
|
if (s.store->user_value(key) == value) return;
|
|
s.store->set_user_value(key, value);
|
|
dingosdk::logging::event(dingosdk::logging::Channel::settings, dingosdk::Json{{"event", "local_gameplay_setting_saved"}, {"key", key}}.dump().c_str());
|
|
} catch (...) { dingosdk::logging::event(dingosdk::logging::Channel::settings, "{\"event\":\"local_gameplay_setting_save_failed\"}"); }
|
|
}
|
|
|
|
bool gameplay_get_bool(std::uintptr_t m, const void* a) {
|
|
if (const auto value = saved_gameplay_setting<bool>(a)) return *value;
|
|
return gameplay_settings().get_bool(m, a);
|
|
}
|
|
|
|
float gameplay_get_number(std::uintptr_t m, const void* a) {
|
|
if (const auto value = saved_gameplay_setting<float>(a)) return *value;
|
|
return gameplay_settings().get_number(m, a);
|
|
}
|
|
|
|
std::int32_t gameplay_get_integer(std::uintptr_t m, const void* a) {
|
|
if (const auto value = saved_gameplay_setting<std::int32_t>(a)) return *value;
|
|
return gameplay_settings().get_integer(m, a);
|
|
}
|
|
|
|
void* gameplay_get_string(std::uintptr_t m, void* out, const void* a) {
|
|
// The original constructs the output CString; assignment may then replace it.
|
|
auto* result = gameplay_settings().get_string(m, out, a);
|
|
if (const auto value = saved_gameplay_setting<std::string>(a))
|
|
user_values().assign_string(out, value->c_str(), static_cast<std::uint32_t>(value->size()));
|
|
return result;
|
|
}
|
|
|
|
void gameplay_set_bool(std::uintptr_t m, const void* a, bool v) {
|
|
if (local_runtime().active.load(std::memory_order_acquire)) {
|
|
PreserveError preserve; std::string key;
|
|
}
|
|
if (!gameplay_user_edit()) {
|
|
gameplay_settings().set_bool(m, a, saved_gameplay_setting<bool>(a).value_or(v)); return;
|
|
}
|
|
gameplay_settings().set_bool(m, a, v);
|
|
save_gameplay_setting(a, v);
|
|
}
|
|
|
|
void gameplay_set_number(const void* a, float v) {
|
|
if (!gameplay_user_edit()) {
|
|
gameplay_settings().set_number(a, saved_gameplay_setting<float>(a).value_or(v)); return;
|
|
}
|
|
gameplay_settings().set_number(a, v);
|
|
save_gameplay_setting(a, v);
|
|
}
|
|
|
|
void gameplay_set_integer(std::uintptr_t m, const void* a, std::int32_t v) {
|
|
if (!gameplay_user_edit()) {
|
|
gameplay_settings().set_integer(m, a, saved_gameplay_setting<std::int32_t>(a).value_or(v)); return;
|
|
}
|
|
gameplay_settings().set_integer(m, a, v);
|
|
save_gameplay_setting(a, v);
|
|
}
|
|
|
|
void gameplay_set_string(std::uintptr_t m, const void* a, const void* v) {
|
|
const bool edit = gameplay_user_edit();
|
|
if (!edit) {
|
|
if (const auto saved = saved_gameplay_setting<std::string>(a)) {
|
|
const char* text = saved->c_str(); gameplay_settings().set_string(m, a, &text); return;
|
|
}
|
|
}
|
|
gameplay_settings().set_string(m, a, v);
|
|
if (!edit || !local_runtime().active.load(std::memory_order_acquire)) return;
|
|
PreserveError preserve;
|
|
try {
|
|
std::uintptr_t text{}; std::string value;
|
|
if (!read(reinterpret_cast<std::uintptr_t>(v), text)) return;
|
|
for (std::size_t i = 0; i <= 4096; ++i) {
|
|
char c{}; if (!read(text + i, c)) return;
|
|
if (!c) { save_gameplay_setting(a, value); return; }
|
|
value += c;
|
|
}
|
|
} catch (...) { dingosdk::logging::event(dingosdk::logging::Channel::settings, "{\"event\":\"local_gameplay_setting_save_failed\"}"); }
|
|
}
|
|
} |