Files
ReSkate/Extension/Rendering/local_graphics_controls.cpp
2026-10-02 14:40:41 -05:00

239 lines
13 KiB
C++

#include "Engine/Core/Log/logging.h"
#include "Extension/Profile/runtime_internal.h"
#include "local_graphics_controls.h"
#include "Extension/World/local_world_controls.h"
#include "Engine/Game/Build/20260929/visual_environment.h"
namespace dingosdk::profile_runtime {
// Renderer settings and native filmic property contracts: analysis/graphics-controls.md.
namespace {
bool writable(std::uintptr_t address) {
MEMORY_BASIC_INFORMATION m{};
return VirtualQuery(reinterpret_cast<void*>(address), &m, sizeof(m)) && m.State == MEM_COMMIT &&
!(m.Protect & (PAGE_GUARD | PAGE_NOACCESS)) &&
(m.Protect & (PAGE_READWRITE | PAGE_WRITECOPY | PAGE_EXECUTE_READWRITE | PAGE_EXECUTE_WRITECOPY)) != 0;
}
}
GraphicsRuntime& graphics_runtime() { static auto* r = new GraphicsRuntime; return *r; }
std::uint32_t graphics_video_mask(const GraphicsControls& choices) {
constexpr std::array<unsigned, 3> bits{2, 1, 0x10};
std::uint32_t mask{};
for (unsigned i = 0; i < bits.size(); ++i) if (choices.effects[i] == 0) mask |= bits[i];
return mask;
}
void graphics_video_setup(const std::uintptr_t* params, std::uintptr_t context, std::uintptr_t output) {
auto& r = graphics_runtime();
std::array<std::uintptr_t, 7> copy{};
std::uint32_t mask{};
bool changed = false;
{
PreserveError preserve;
// This pass is freshly constructed by DingoVideoFilterRenderPassModule.
// Its mask is owned by this invocation, rather than a shared VE asset.
std::uintptr_t pass{}, vt{}, module{}, module_vt{};
std::uint32_t cached_mask{};
if (r.active.load(std::memory_order_acquire) && output >= 0x860 &&
read(context + 0x18, pass) && pass == output - 0x860 &&
read(pass, vt) && vt == local_runtime().base + video_filter_pass_vtable &&
read(pass + 0x898, module) && read(module, module_vt) && module_vt == local_runtime().base + video_filter_module_vtable &&
read(reinterpret_cast<std::uintptr_t>(params), copy) && read(copy[5], mask) &&
read(pass + 0x890, cached_mask) && mask == cached_mask && (mask & ~0x117u) == 0) {
r.video_observed_at.store(GetTickCount64(), std::memory_order_release);
const auto filtered = mask & ~r.disabled_video_effects.load(std::memory_order_acquire);
if (filtered != mask) {
SIZE_T written{};
if (WriteProcessMemory(GetCurrentProcess(), reinterpret_cast<void*>(pass + 0x890), &filtered, sizeof(filtered), &written) && written == sizeof(filtered)) {
mask = filtered;
copy[5] = reinterpret_cast<std::uintptr_t>(&mask);
changed = true;
}
}
}
}
r.video_setup(changed ? copy.data() : params, context, output);
}
bool graphics_filmic_identity(const GraphicsFilmic& n) {
const auto base = local_runtime().base;
std::uintptr_t vt{}, data{}, type{}, collection{}, parent{}, parent_data{};
return read(n.component, vt) && vt == base + filmic_vtable &&
read(n.component + 8, data) && data == n.data && read(data + 8, type) && type == base + filmic_data_type &&
read(n.component + 0x18, collection) && read(collection, parent) && parent == n.parent &&
read(parent, vt) && vt == base + addr::visual_environment::entity_vtable && read(parent + 0x48, parent_data) &&
read(parent_data + 8, type) && type == base + addr::visual_environment::entity_data_type;
}
std::uintptr_t graphics_filmic_construct(std::uintptr_t factory, std::uintptr_t info) {
auto& r = graphics_runtime();
const auto result = r.construct(factory, info);
PreserveError preserve;
if (r.active.load(std::memory_order_acquire)) {
try {
GraphicsFilmic n; n.component = result;
std::uintptr_t collection{};
if (read(result + 8, n.data) && read(result + 0x18, collection) && read(collection, n.parent) && graphics_filmic_identity(n)) {
std::lock_guard lock(r.mutex);
if (r.nodes.size() < 4096) r.nodes[result] = n;
r.last_update = 0;
}
} catch (...) { dingosdk::logging::event(dingosdk::logging::Channel::graphics, "{\"event\":\"graphics_component_capture_failed\"}"); }
}
return result;
}
std::uintptr_t graphics_filmic_destroy(std::uintptr_t component, unsigned flags) {
auto& r = graphics_runtime();
{ PreserveError preserve; std::lock_guard lock(r.mutex); r.nodes.erase(component); }
return r.destroy(component, flags);
}
bool graphics_write_byte(std::uintptr_t address, std::uint8_t value) {
if (!writable(address)) return false;
SIZE_T written{}; std::uint8_t after{};
return WriteProcessMemory(GetCurrentProcess(), reinterpret_cast<void*>(address), &value, 1, &written) &&
written == 1 && read(address, after) && after == value;
}
bool graphics_settings_identity(std::uintptr_t object) {
const auto base = local_runtime().base;
std::uintptr_t vt{}, type{};
return read(object, vt) && vt >= base && vt < base + supported_build::game_image_size &&
read(object + 8, type) && type == base + post_process_settings_type;
}
bool graphics_settings_update() {
auto& r = graphics_runtime(); const auto base = local_runtime().base;
std::uintptr_t manager{}, buckets{}; unsigned count{};
if (!read(base + addr::engine::settings_manager, manager) || !read(manager + 0xc8, buckets) || !buckets ||
!read(manager + 0xd0, count) || !count || count > 0x100000) return false;
const auto object = r.lookup(manager, reinterpret_cast<const void*>(base + post_process_settings_type));
if (!graphics_settings_identity(object)) return false;
constexpr std::array<unsigned, 2> offsets{0x31e, 0x352};
bool ok = true, changed = false;
// The native renderer settings service caches PostProcess copies by
// DdcDirtyVersion. Changing the Boolean alone leaves those copies stale
// until a camera/pause transition rebuilds them.
const auto version = object + 0x1a8;
// The settings object only changes on a reload: its version word is checked once per object
// rather than with a VirtualQuery on every update.
if (object != r.settings) {
if (!writable(version)) return false;
r.settings = object; r.settings_fields = {};
}
for (unsigned i = 0; i < offsets.size(); ++i) {
auto& lease = r.settings_fields[i]; std::uint8_t current{};
if (!read(object + offsets[i], current) || current > 1) { ok = false; continue; }
if (lease.owned && current != lease.applied) lease = {};
if (!lease.owned) lease.original = current;
const int choice = r.model.choices.effects[i];
const auto wanted = choice < 0 ? lease.original : static_cast<std::uint8_t>(choice);
if (current != wanted && (!graphics_settings_identity(object) || !graphics_write_byte(object + offsets[i], wanted))) {
ok = false; continue;
}
changed |= current != wanted;
lease.applied = wanted; lease.owned = choice >= 0;
r.model.ready[i] = true; r.model.enabled[i] = wanted != 0;
}
if (changed) InterlockedIncrement(reinterpret_cast<volatile LONG*>(version));
return ok;
}
bool graphics_filmic_update(GraphicsFilmic& n, unsigned field) {
auto& r = graphics_runtime(); auto& lease = n.fields[field];
const unsigned index = field + 1;
const unsigned offset = field == 0 ? 0x97 : 0x95;
const unsigned bit = field == 0 ? 0x100 : 2;
const std::uint32_t hash = field == 0 ? 0x028a3397 : 0x2ae38bab;
std::uint8_t current{}; std::uint32_t mask{};
if (!read(n.component + offset, current) || current > 1 || !read(n.component + 0x5c, mask)) return false;
if (lease.owned && current != lease.applied) lease = {};
if (!lease.owned) { lease.original = current; lease.original_flag = (mask & bit) != 0; }
const int choice = r.model.choices.effects[index];
const auto desired = choice < 0 ? lease.original : static_cast<std::uint8_t>(choice);
if (choice >= 0 ? !lease.owned || current != desired : lease.owned) {
const EnvironmentChange change{hash, 0, &desired};
r.property(n.component, &change); // Includes the renderer ECS notification.
if (choice < 0) {
if (!read(n.component + 0x5c, mask)) return false;
mask = lease.original_flag ? mask | bit : mask & ~bit;
const EnvironmentChange flags{0xd421d5af, 0, &mask};
r.property(n.component, &flags);
}
if (!read(n.component + offset, current) || current != desired) return false;
lease.applied = desired; lease.owned = choice >= 0;
}
r.model.ready[index] = true;
r.model.enabled[index] = r.model.enabled[index] || current != 0;
return true;
}
void update_graphics_controls() {
auto& r = graphics_runtime(); std::lock_guard lock(r.mutex);
if (!r.active.load(std::memory_order_acquire)) return;
if (!r.thread) r.thread = GetCurrentThreadId();
if (r.thread != GetCurrentThreadId()) return;
const auto now = GetTickCount64();
if (r.last_update && now - r.last_update < 250) return;
r.last_update = now;
r.model.ready = {}; r.model.enabled = {}; r.model.filmic_components = 0;
bool ok = graphics_settings_update();
for (auto& [_, n] : r.nodes) {
std::uint16_t handle{};
if (!graphics_filmic_identity(n) || !read(n.component + 0x30, handle) || !handle) continue;
++r.model.filmic_components;
for (unsigned i = 0; i < 2; ++i) ok &= graphics_filmic_update(n, i);
}
const bool video = now - r.video_observed_at.load(std::memory_order_acquire) < 2000;
if (video) r.model.ready[2] = true;
r.model.status = !ok ? "Waiting for renderer settings." : !r.model.filmic_components && !video ?
"Graphics saved. Chromatic aberration will apply when a camera effect loads." : "Graphics controls active.";
}
void start_graphics_controls() noexcept {
auto& r = graphics_runtime(); const auto base = local_runtime().base;
std::vector<void*> created;
try {
r.model.choices = profile::graphics_controls(*local_runtime().store->shared_snapshot());
r.disabled_video_effects = graphics_video_mask(r.model.choices);
for (const auto& site : graphics_sites) {
std::array<unsigned char, 32> bytes{};
if (!read(base + site.rva, bytes) || bytes != site.bytes) throw std::runtime_error("Graphics fingerprint mismatch");
}
// Independent reflected offsets and Boolean types for the global renderer switches.
constexpr std::array<std::array<std::uintptr_t, 4>, 3> fields{{
{film_grain_enable_field, 0xa976a4ad, 0x31e, addr::engine::bool_type},
{vignette_enable_field, 0x3b5256dd, 0x352, addr::engine::bool_type},
{ddc_dirty_version_field, 0xe676bb9a, 0x1a8, addr::engine::uint32_type}}}; // DdcDirtyVersion: Uint32
for (const auto& f : fields) {
std::array<std::uintptr_t, 3> m{};
if (!read(base + f[0], m) || m[0] != f[1] || m[1] != f[2] || m[2] != base + f[3])
throw std::runtime_error("Graphics metadata mismatch");
}
std::uintptr_t destroy{}, property{};
if (!read(base + filmic_vtable + 8, destroy) || destroy != base + graphics_sites[1].rva ||
!read(base + filmic_vtable + 0x18, property) || property != base + graphics_sites[2].rva)
throw std::runtime_error("Graphics vtable mismatch");
r.property = reinterpret_cast<GraphicsRuntime::Property>(property);
r.lookup = reinterpret_cast<GraphicsRuntime::Lookup>(base + graphics_sites[3].rva);
const auto hook = [&](unsigned i, auto detour, auto& original) {
auto* target = reinterpret_cast<void*>(base + graphics_sites[i].rva);
if (hook_prepare(target, reinterpret_cast<void*>(detour), reinterpret_cast<void**>(&original)) != HookOk)
throw std::runtime_error("Cannot prepare graphics controls");
created.push_back(target);
};
hook(0, &graphics_filmic_construct, r.construct); hook(1, &graphics_filmic_destroy, r.destroy);
hook(4, &graphics_video_setup, r.video_setup);
for (auto* target : created) if (hook_enable(target) != HookOk) throw std::runtime_error("Cannot enable graphics controls");
r.active.store(true, std::memory_order_release);
dingosdk::logging::event(dingosdk::logging::Channel::graphics, "{\"event\":\"graphics_controls_initialized\",\"active\":true}");
} catch (...) {
for (auto* target : created) hook_disable(target);
r.model.status = "Graphics controls are unavailable in this build.";
dingosdk::logging::event(dingosdk::logging::Channel::graphics, "{\"event\":\"graphics_controls_initialized\",\"active\":false}");
}
}
}