diff --git a/Engine/Game/Build/20260929/spatial_cull_ids.h b/Engine/Game/Build/20260929/spatial_cull_ids.h new file mode 100644 index 00000000..90d7714b --- /dev/null +++ b/Engine/Game/Build/20260929/spatial_cull_ids.h @@ -0,0 +1,31 @@ +// Reviewed native contract for supported_build::game_sha256 (September 29 2026). +// A spatial transaction queue (SpatialQueryManager's, and one in each world's client and render +// spatial managers) hands every object it tracks a cull ID from a free list. The list is made +// once, when the queue is, at SpatialQueryManagerSettings.InitialCullIdListSize entries (the +// game's config: 40,000; 80,000 in GameView.cfg), and never grows: an insert with the list empty +// reads entry 0xffffffff of it and crashes (0x489cfe9, loading a large custom map). +#pragma once +#include +#include +#include + +namespace dingosdk::game::build::v20260929::spatial_cull_ids { +// make_list(list, allocator): looks the settings up, sizes the list's two arrays (u32 next IDs, +// u64 per-ID entries) to InitialCullIdListSize and chains them into the free list. Its only +// callers are the two queue constructors (0x489b518, 0x489ba10). +inline constexpr std::uintptr_t make_list_rva = 0x0489b770; +inline constexpr std::array make_list_entry{ + 0x40, 0x53, 0x55, 0x56, 0x57, 0x41, 0x56, 0x48, 0x83, 0xec, 0x20, 0x45, 0x33, 0xf6, 0x48}; +// lea rdx, [settings type 0x77ae5e0] ... mov rcx, [settings manager 0x7482ce0]; +// call settings_lookup; test rax, rax; je; mov esi, [rax + 0x28]; jmp; mov esi, 1. +inline constexpr std::uintptr_t settings_type_load_rva = 0x0489b78d; +inline constexpr std::array settings_type_load{0x48, 0x8d, 0x15, 0x4c, 0x2e, 0xf1, 0x02}; +inline constexpr std::uintptr_t size_read_rva = 0x0489b7ae; +inline constexpr std::array size_read{ + 0x48, 0x8b, 0x0d, 0x2b, 0x75, 0xbe, 0x02, 0xe8, 0x36, 0xb7, 0x03, 0xfd, 0x48, 0x85, + 0xc0, 0x74, 0x05, 0x8b, 0x70, 0x28, 0xeb, 0x05, 0xbe, 0x01, 0x00, 0x00, 0x00}; +inline constexpr std::uintptr_t settings_manager = 0x07482ce0; +inline constexpr std::uintptr_t settings_lookup = 0x018d6ef0; +inline constexpr std::uintptr_t settings_type = 0x077ae5e0; +inline constexpr std::size_t initial_cull_id_list_size = 0x28; +} // namespace dingosdk::game::build::v20260929::spatial_cull_ids diff --git a/Extension/World/spatial_cull_ids.cpp b/Extension/World/spatial_cull_ids.cpp new file mode 100644 index 00000000..17224285 --- /dev/null +++ b/Extension/World/spatial_cull_ids.cpp @@ -0,0 +1,86 @@ +#include "spatial_cull_ids.h" + +#include "Engine/Core/Hooks/hooks.h" +#include "Engine/Core/Log/logging.h" +#include "Engine/Core/Platform/memory.h" +#include "Engine/Game/Build/addresses.h" +#include "Engine/Game/Build/20260929/entity_pages.h" +#include "Engine/Game/Build/20260929/spatial_cull_ids.h" + +#include +#include +#include +#include +#include + +namespace dingosdk { +namespace { +namespace contract = addr::spatial_cull_ids; +static_assert(spatial_cull_id_target == addr::entity_pages::capacity); + +using MakeList = void (*)(void* list, void* allocator); +using SettingsLookup = std::uintptr_t (*)(std::uintptr_t manager, std::uintptr_t type); +std::atomic original_make_list{}; +std::uintptr_t installed_base{}; +std::mutex install_mutex; + +bool matches(std::uintptr_t address, const auto& expected) noexcept { + std::array actual{}; + return expected.size() <= actual.size() && memory::read_bytes(address, actual.data(), expected.size()) && + std::equal(expected.begin(), expected.end(), actual.begin()); +} + +// The list reads the setting right after this, on the same thread; nothing else reads it. +void make_list(void* list, void* allocator) { + const auto incoming_error = GetLastError(); + std::uintptr_t manager{}; + if (memory::peek(installed_base + contract::settings_manager, manager) && manager) { + const auto settings = reinterpret_cast(installed_base + contract::settings_lookup)( + manager, installed_base + contract::settings_type); + if (settings) { + auto* size = reinterpret_cast(settings + contract::initial_cull_id_list_size); + if (*size < spatial_cull_id_target) { + logging::log(logging::Level::info, logging::Channel::world, + "Spatial cull IDs raised: {} -> {}", *size, spatial_cull_id_target); + *size = spatial_cull_id_target; + } + } + } + SetLastError(incoming_error); + original_make_list.load(std::memory_order_acquire)(list, allocator); +} +} // namespace + +bool start_spatial_cull_ids(std::uintptr_t base, std::string& error) { + std::lock_guard lock(install_mutex); + error.clear(); + if (installed_base) { + if (installed_base == base) return true; + error = "Spatial cull IDs are already raised on another image"; + return false; + } + if (!matches(base + contract::make_list_rva, contract::make_list_entry) || + !matches(base + contract::settings_type_load_rva, contract::settings_type_load) || + !matches(base + contract::size_read_rva, contract::size_read)) { + error = "the cull-ID list does not match the supported game"; + return false; + } + auto* target = reinterpret_cast(base + contract::make_list_rva); + MakeList original{}; + const auto prepared = hook_prepare(target, reinterpret_cast(&make_list), reinterpret_cast(&original)); + if (prepared != HookOk || !original) { + if (prepared == HookOk) hook_remove(target); + error = "its hook could not be prepared"; + return false; + } + original_make_list.store(original, std::memory_order_release); + installed_base = base; + if (hook_enable(target) != HookOk) { + hook_remove(target); + installed_base = 0; + error = "its hook could not be attached"; + return false; + } + return true; +} +} // namespace dingosdk diff --git a/Extension/World/spatial_cull_ids.h b/Extension/World/spatial_cull_ids.h new file mode 100644 index 00000000..f403d044 --- /dev/null +++ b/Extension/World/spatial_cull_ids.h @@ -0,0 +1,15 @@ +#pragma once + +#include +#include + +namespace dingosdk { +// Cull IDs per spatial transaction queue: one for every entity slot the entity pages hold +// (entity_pages.h), as each object a queue tracks is an entity. 12 bytes an ID per queue. +inline constexpr std::uint32_t spatial_cull_id_target = 262'144; + +// Install after Detours hook service init, before any level loads. Raises the game's +// InitialCullIdListSize to the target as each queue makes its list; a larger configured +// value is kept. +bool start_spatial_cull_ids(std::uintptr_t base, std::string& error); +} // namespace dingosdk diff --git a/Runtime/bootstrap.cpp b/Runtime/bootstrap.cpp index e5692d4c..20b3a995 100644 --- a/Runtime/bootstrap.cpp +++ b/Runtime/bootstrap.cpp @@ -22,6 +22,7 @@ #include "Extension/World/native_entity_pages.h" #include "Extension/World/world_pool_usage.h" #include "Extension/World/physics_world_size.h" +#include "Extension/World/spatial_cull_ids.h" #include namespace dingosdk::runtime { @@ -64,6 +65,8 @@ bool initialize_bootstrap(std::uintptr_t base) { if (!stage(Channel::assets, "Native render-resource pools", ready, error)) return false; error.clear(); ready = start_native_entity_pages(base, error); if (!stage(Channel::world, "Native entity pages (524288 entity/update slots, 1048576 render slots, 16384 spatial blocks/bucket, 128 MiB arena minimum)", ready, error)) return false; + error.clear(); ready = start_spatial_cull_ids(base, error); + if (!stage(Channel::world, "Spatial cull IDs (262144 a queue)", ready, error)) return false; // (Diagnostics only: how full those pools get. A mismatch leaves the game as it is.) error.clear(); ready = start_world_pool_usage(base, error); (void)stage(Channel::world, "World pool usage watch", ready, error); diff --git a/cmake/Runtime.cmake b/cmake/Runtime.cmake index 9b525ea4..27a069fc 100644 --- a/cmake/Runtime.cmake +++ b/cmake/Runtime.cmake @@ -122,6 +122,7 @@ add_library(dingosdk_runtime SHARED Extension/World/native_entity_pages.cpp Extension/World/world_pool_usage.cpp Extension/World/physics_world_size.cpp + Extension/World/spatial_cull_ids.cpp Extension/Rendering/display_startup.cpp Extension/Rendering/replay_export.cpp Engine/Game/World/world_model.cpp