This commit is contained in:
AyeZeeBB committed 2026-10-04 16:07:51 -05:00
commit 30f3f84120
9 files changed
+282 -10

No files matched your search

@@ -0,0 +1,72 @@
// Reviewed native contract for supported_build::game_sha256 (September 29 2026).
// fb::FBPhysicsWorld creation copies the 0xa8-byte FBPhysicsWorldSize at the
// front of its descriptor into the job arguments (+0x2428) before any pool,
// free list or per-body array is sized from it. Both native callers build that
// descriptor on their own stack immediately before the call.
#pragma once
#include <array>
#include <cstddef>
#include <cstdint>
#include <span>
namespace dingosdk::game::build::v20260929::physics_world_size {
template<std::size_t N> consteval auto hex_bytes(const char (&hex)[N]) {
static_assert(N % 2 == 1);
std::array<std::uint8_t, (N - 1) / 2> result{};
const auto nibble = [](char c) -> unsigned {
if (c >= '0' && c <= '9') return static_cast<unsigned>(c - '0');
if (c >= 'a' && c <= 'f') return static_cast<unsigned>(c - 'a' + 10);
throw "Invalid physics world size contract hex";
};
for (std::size_t i = 0; i < result.size(); ++i)
result[i] = static_cast<std::uint8_t>((nibble(hex[i * 2]) << 4) | nibble(hex[i * 2 + 1]));
return result;
}
// World creation entry, through the stack cookie.
inline constexpr auto create_entry = hex_bytes(
"48895c241848897424205741544155415641574881eca0030000c5f829b42490030000488b05a6d3bb044833c4"
"4889842480030000");
// rbx = this->m_jobArguments; copy descriptor[0x00..0xa8) to rbx+0x2428.
inline constexpr auto size_copy = hex_bytes(
"488b5e28c4c17c1006c5fc118328240000c4c17c104e20c5fc118b48240000c4c17c104640c5fc118368240000"
"c4c17c104e60c5fc118b88240000c4c17c108680000000c5fc1183a8240000c4c17b108ea0000000c5fb118bc8"
"240000");
// The world reserves one internal body on top of BodyCount (data 8200 -> 8201 slots).
inline constexpr auto internal_body = hex_bytes("ff833024000083832824000003");
// Both callers: lea rdx,[stack descriptor]; mov rcx,rax; call create.
inline constexpr auto caller_a_call = hex_bytes("e851195200");
inline constexpr auto caller_b_call = hex_bytes("e8306f8dfe");
struct Contract {
std::uintptr_t rva;
std::span<const std::uint8_t> bytes;
};
inline constexpr Contract contracts[]{
{0x02606ff0, create_entry},
{0x02607fec, size_copy},
{0x026080b0, internal_body},
{0x020e569a, caller_a_call},
{0x03d300bb, caller_b_call},
};
inline constexpr std::size_t max_contract_size = [] {
std::size_t size = 0;
for (const auto& contract : contracts) size = contract.bytes.size() > size ? contract.bytes.size() : size;
return size;
}();
// unsigned-int fields of FBPhysicsWorldSize in reflected field order (index * 4).
inline constexpr std::size_t size_bytes = 0xa8;
inline constexpr std::size_t geometry_count = 0x00;
inline constexpr std::size_t owner_count = 0x04;
inline constexpr std::size_t body_count = 0x08;
inline constexpr std::size_t hull_and_mesh_count = 0x14;
inline constexpr std::size_t shape_count = 0x18;
inline constexpr std::size_t aggregate_shape_count = 0x1c;
inline constexpr std::size_t fixed_shape_count = 0x20;
inline constexpr std::size_t fixed_partition_count = 0x3c;
inline constexpr std::size_t persistent_spatial_index_count = 0xa0;
// fb::FBPhysicsWorld creation: (void* world, Descriptor* descriptor).
inline constexpr std::uintptr_t create_rva = 0x02606ff0;
} // namespace dingosdk::game::build::v20260929::physics_world_size
@@ -869,6 +869,17 @@ void dedicated_server_codec() {
check(row && row->dedicated && row->name == "Big friendly server" && row->map == ad.map && row->players == 3 &&
row->capacity == 16 && row->password_required && row->code == format_invite({server, ad.secret}),
"Server tags did not round trip");
// A whole last character is kept, and one the byte limit cuts through is dropped whole.
const auto accented = read_server_tags(dingosdk::server::server_tags({"Skate Caf\xC3\xA9", "Caf\xC3\xA9", 1, 8, false, true, 1}), server);
check(accented && accented->name == "Skate Caf\xC3\xA9" && accented->map == "Caf\xC3\xA9",
"A server name lost its non-ASCII last letter");
for (std::size_t lead = 0; lead < 3; ++lead) {
std::string euros(lead, 'a');
for (int i = 0; i < 50; ++i) euros += "\xE2\x82\xAC";
const auto cut = dingosdk::server::server_tags({euros, "Caf\xC3\xA9", 1, 8, false, true, 1});
const auto name = cut.substr(cut.find(",n") + 2);
check(cut.size() < 128 && name.size() > lead && (name.size() - lead) % 3 == 0, "A server name was cut inside a character");
}
check(!read_server_tags(tags, player), "A player's ID read as a server");
check(!read_server_tags("reskate,v1,k1,c4", server), "An incompatible server version listed");
}
+145
View File
@@ -0,0 +1,145 @@
#include "physics_world_size.h"
#include "Engine/Game/Build/supported_build.h"
#include "Engine/Game/Build/addresses.h"
#include "Engine/Game/Build/20260929/physics_world_size.h"
#include "Engine/Core/Log/logging.h"
#include <Windows.h>
#include "Engine/Core/Hooks/hooks.h"
#include <algorithm>
#include <array>
#include <atomic>
#include <cstddef>
#include <cstring>
#include <format>
#include <limits>
#include <mutex>
namespace dingosdk {
namespace {
namespace world_size = addr::physics_world_size;
using WorldCreate = void* (*)(void*, void*);
std::atomic<WorldCreate> original_create{};
std::uintptr_t installed_base{};
std::mutex install_mutex;
// Every pool a static triangle-mesh body is carved from. Contacts, joints,
// queries and culling tables track dynamic interactions and stay native.
constexpr std::size_t scaled_fields[]{
world_size::geometry_count,
world_size::owner_count,
world_size::body_count,
world_size::hull_and_mesh_count,
world_size::shape_count,
world_size::aggregate_shape_count,
world_size::fixed_shape_count,
world_size::fixed_partition_count,
world_size::persistent_spatial_index_count,
};
bool read_memory(const void* source, void* destination, std::size_t size) noexcept {
SIZE_T count{};
return ReadProcessMemory(GetCurrentProcess(), source, destination, size, &count) && count == size;
}
bool validate_contract(std::uintptr_t base) noexcept {
if (!base || base > std::numeric_limits<std::uintptr_t>::max() - supported_build::game_image_size)
return false;
IMAGE_DOS_HEADER dos{};
IMAGE_NT_HEADERS64 nt{};
if (!read_memory(reinterpret_cast<const void*>(base), &dos, sizeof(dos)) ||
dos.e_magic != IMAGE_DOS_SIGNATURE || dos.e_lfanew < static_cast<LONG>(sizeof(dos)) ||
dos.e_lfanew > 0x1000 ||
!read_memory(reinterpret_cast<const void*>(base + dos.e_lfanew), &nt, sizeof(nt)) ||
nt.Signature != IMAGE_NT_SIGNATURE || nt.FileHeader.Machine != IMAGE_FILE_MACHINE_AMD64 ||
nt.OptionalHeader.Magic != IMAGE_NT_OPTIONAL_HDR64_MAGIC ||
nt.OptionalHeader.SizeOfImage != supported_build::game_image_size) return false;
for (const auto& contract : world_size::contracts) {
std::array<std::uint8_t, world_size::max_contract_size> actual{};
if (!read_memory(reinterpret_cast<const void*>(base + contract.rva), actual.data(), contract.bytes.size()) ||
std::memcmp(actual.data(), contract.bytes.data(), contract.bytes.size()) != 0) return false;
}
return true;
}
std::uint32_t field(const std::uint8_t* size, std::size_t offset) noexcept {
std::uint32_t value{};
std::memcpy(&value, size + offset, sizeof(value));
return value;
}
void* create_world(void* world, void* descriptor) {
const auto incoming_error = GetLastError();
const auto original = original_create.load(std::memory_order_acquire);
alignas(16) std::array<std::uint8_t, world_size::size_bytes> size{};
// The descriptor is the caller's stack local, rebuilt for every creation, so
// raising it in place affects only this world. Absolute targets keep it idempotent.
if (descriptor && read_memory(descriptor, size.data(), size.size())) {
const auto native_size = size;
const auto bodies = field(size.data(), world_size::body_count);
if (bodies > 0 && bodies < physics_world_body_target) {
const auto scale = (physics_world_body_target + bodies - 1) / bodies;
for (const auto offset : scaled_fields) {
const auto native = field(size.data(), offset);
auto raised = offset == world_size::body_count
? physics_world_body_target
: static_cast<std::uint32_t>(std::min<std::uint64_t>(
std::uint64_t{native} * scale, physics_world_pool_ceiling));
raised = std::max(raised, native);
std::memcpy(size.data() + offset, &raised, sizeof(raised));
}
std::memcpy(descriptor, size.data(), size.size());
const auto pair = [&](std::size_t offset) {
return std::format("{} -> {}", field(native_size.data(), offset), field(size.data(), offset));
};
logging::log(logging::Level::info, logging::Channel::world,
"Physics world pools raised: bodies {}, shapes {}, fixed shapes {}, owners {}, "
"geometries {}, hull/mesh {}, partitions {}, spatial indices {}",
pair(world_size::body_count), pair(world_size::shape_count),
pair(world_size::fixed_shape_count), pair(world_size::owner_count),
pair(world_size::geometry_count), pair(world_size::hull_and_mesh_count),
pair(world_size::fixed_partition_count), pair(world_size::persistent_spatial_index_count));
}
}
SetLastError(incoming_error);
return original(world, descriptor);
}
} // namespace
bool start_physics_world_size(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 = "Physics world size support is already installed on another image";
return false;
}
if (!validate_contract(base)) {
error = "Physics world creation contract does not match the supported game";
return false;
}
auto* target = reinterpret_cast<void*>(base + world_size::create_rva);
WorldCreate original{};
const auto created = hook_prepare(target, reinterpret_cast<void*>(&create_world),
reinterpret_cast<void**>(&original));
if (created != HookOk) {
error = "Cannot create physics world hook (Detours hook service status " + std::to_string(created) + ")";
return false;
}
if (!original) {
hook_remove(target);
error = "Physics world trampoline is unavailable";
return false;
}
original_create.store(original, std::memory_order_release);
const auto enabled = hook_enable(target);
if (enabled != HookOk) {
hook_remove(target);
original_create.store(nullptr, std::memory_order_release);
error = "Cannot enable physics world hook (Detours hook service status " + std::to_string(enabled) + ")";
return false;
}
installed_base = base;
return true;
}
} // namespace dingosdk
+18
View File
@@ -0,0 +1,18 @@
#pragma once
#include <cstdint>
#include <string>
namespace dingosdk {
// Static collision slots per physics world. Stock worlds hold 8,200 bodies
// (+1 internal); a large custom map gives every collision part its own body.
// Kept below 65,536 so no 16-bit index elsewhere in the engine can wrap.
inline constexpr std::uint32_t physics_world_body_target = 65000;
// Pools that each static mesh body also draws from scale with BodyCount, up to this.
inline constexpr std::uint32_t physics_world_pool_ceiling = 1u << 20;
// Install after Detours hook service init, before any level loads. Raises the
// body pool and its companion static-collision pools at world creation only;
// queries, contacts, joints and per-query result limits keep their native size.
bool start_physics_world_size(std::uintptr_t base, std::string& error);
} // namespace dingosdk
+3
View File
@@ -15,6 +15,7 @@
#include "Extension/Rendering/display_startup.h"
#include "Extension/World/native_route_lookahead.h"
#include "Extension/World/native_entity_pages.h"
#include "Extension/World/physics_world_size.h"
#include <string>
namespace dingosdk::runtime {
@@ -51,6 +52,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 (1048576 entity/update/render slots, 16384 spatial blocks/bucket, 128 MiB arena minimum)", ready, error)) return false;
error.clear(); ready = start_physics_world_size(base, error);
if (!stage(Channel::world, "Physics world pools (65000 static bodies)", ready, error)) return false;
error.clear(); ready = start_native_route_lookahead(base, error);
if (!stage(Channel::world, "NPC route cycle and endpoint guards", ready, error)) return false;
if (!stage(Channel::graphics, "Display startup settings", start_display_settings(base), {})) return false;
+16
View File
@@ -68,6 +68,22 @@ int run() {
const auto odd = load_config(file);
check(odd.score_check == "warn" && odd.score_allow == std::vector<std::uint64_t>{0xabc}, "Bad scoring settings not cleaned");
// A port outside 1-65535 is refused, not wrapped to another port, and the file keeps the typo.
const auto refused = [&](const char *json) {
std::ofstream(file, std::ios::binary) << json;
try {
static_cast<void>(load_config(file));
} catch (const std::exception &) {
return text(file) == json;
}
return false;
};
check(refused(R"({"query_port": 70000})") && refused(R"({"port": 65536})") && refused(R"({"port": 0})"),
"An out-of-range port was accepted or the file rewritten");
std::ofstream(file, std::ios::binary) << R"({"port": 65535, "query_port": 1})";
const auto edges = load_config(file);
check(edges.port == 65535 && edges.query_port == 1, "Ports at the ends of the range refused");
std::filesystem::remove_all(folder);
if (failures) return 1;
std::cout << "server config: ok\n";
+9 -2
View File
@@ -128,8 +128,15 @@ ServerConfig load_config(const std::filesystem::path &file, std::vector<std::str
if (value.is_string())
if (const auto fingerprint = parse_scoring(value.string()))
c.score_allow.push_back(*fingerprint);
c.port = static_cast<std::uint16_t>(root.value("port", static_cast<unsigned>(c.port)));
c.query_port = static_cast<std::uint16_t>(root.value("query_port", static_cast<unsigned>(c.query_port)));
// Checked here, not in config_error: once narrowed, 70000 is just port 4464, and the
// next save would write that over the owner's typo.
const auto read_port = [&](const char *key, std::uint16_t fallback) {
const auto value = root.value(key, static_cast<unsigned>(fallback));
if (value < 1 || value > 65535) throw std::runtime_error(std::string(key) + " must be 1 to 65535.");
return static_cast<std::uint16_t>(value);
};
c.port = read_port("port", c.port);
c.query_port = read_port("query_port", c.query_port);
c.tps = root.value("tps", c.tps);
c.voice_chat = root.value("voice_chat", c.voice_chat);
c.voice_range = root.value("voice_range", c.voice_range);
+7 -8
View File
@@ -1,4 +1,5 @@
#include "steam_server.h"
#include "server_text.h"
#include "Extension/Multiplayer/Net/protocol.h"
#ifdef _WIN32
#include <Windows.h>
@@ -12,6 +13,7 @@
#include <thread>
#include <unistd.h>
#endif
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <optional>
@@ -45,14 +47,11 @@ struct SteamIP {
// Tags are comma separated, so a name loses its commas; the whole list must
// stay under Steam's 128 byte limit.
std::string tag_text(std::string_view text, std::size_t limit) {
std::string result;
for (const char c : text) {
if (result.size() >= limit) break;
result += c == ',' ? ' ' : c;
}
// Never end inside a UTF-8 sequence.
while (!result.empty() && (static_cast<unsigned char>(result.back()) & 0xC0) == 0x80) result.pop_back();
if (!result.empty() && (static_cast<unsigned char>(result.back()) & 0xC0) == 0xC0) result.pop_back();
std::string result(text);
std::replace(result.begin(), result.end(), ',', ' ');
// Cut at the limit only, and never through a character: a whole last character
// is kept ("Café" stays "Café"), one the limit splits is dropped.
cut_text(result, limit);
return result;
}
// Steam's library prints its own crash-reporting notes ("Setting breakpad
+1
View File
@@ -87,6 +87,7 @@ add_library(dingosdk_runtime SHARED
Extension/Assets/morph_memory_pool.cpp
Extension/Assets/native_render_resource_pool.cpp
Extension/World/native_entity_pages.cpp
Extension/World/physics_world_size.cpp
Extension/Rendering/display_startup.cpp
Extension/Rendering/replay_export.cpp
Engine/Game/World/world_model.cpp