From f289666c2cec57ee7f9adc2668c4fe91c70773c8 Mon Sep 17 00:00:00 2001 From: AyeZeeBB Date: Sat, 10 Oct 2026 16:15:59 -0500 Subject: [PATCH] Entity limit 4x, replay crash guard, -map launch option, memory tools; pause-menu fixes Memory - The entity and pending-update pages are four times the game's own size (524,288 slots), down from eight times. At eight, the game kept a 3.5 GB block for them that was fully in RAM on custom maps; the largest map load seen needs about 150,000 slots. The render-instance pool and spatial buckets stay at eight times. - The log has a "Memory:" line when the game's memory, the players in the session or the skaters built change: committed, in RAM, players, skaters built, player distance and map. - Console: `perf memory` lists the game's memory by block size. `perf memory mark` remembers it, and the next `perf memory` says what was added since. `perf memory trace` also records who asked for each block until `perf memory stop`. `perf memory big` lists the largest blocks with how much of each is in RAM and a sample of what is in them. RESKATE_MEMORY_TRACE= starts the trace with the game. Replay crash guard - The game's replay recorder crashes when its stream has no room for the next chunk: it writes to the chunk it was not given (Skate.exe+0x48efa34, crash reports of 2026-10-10). The function now runs under a handler that, for that one fault alone, returns as if no chunk was ended, and says so in the log. Any other fault still goes to the crash reporter. The skip path has not been triggered in testing. Launcher - `-map ` (also --map, -map=) loads that map as soon as the game is up, named as the console's `load` names one. With --no-gui and no other options the launch still uses the saved launcher settings. Pause menu - The Multiplayer and Mod Options pages step aside while the game's side menu, or a screen opened from it such as Settings, is over them. They drew over both before. - A button still held when a page comes back is not a press on it: the Back that closed the side menu no longer dismissed the page as well. Multiplayer cosmetics - A player whose mods add or remove cosmetic slots (stickers, say) no longer has the whole outfit refused. Their items go to this game's slots by slot: a slot they lack stays empty, an item in a slot this game lacks is left out, and the log says how many of each. --- Engine/Core/Platform/launcher_support.cpp | 42 ++ Engine/Core/Platform/launcher_support.h | 7 + Engine/Game/Build/20260929/entity_pages.h | 606 +++++++++--------- Engine/Game/Build/20260929/pending_updates.h | 274 ++++---- Engine/Game/Build/20260929/replay_recorder.h | 19 + Engine/Game/Build/20260929/spatial_buckets.h | 3 +- Engine/Vfs/Test/store_copies_tests.cpp | 7 +- Engine/Vfs/mod_store_copies.cpp | 12 + Engine/Vfs/mod_store_copies.h | 18 + Extension/Console/commands.h | 2 + Extension/Console/perf_commands.cpp | 315 +++++++++ .../Multiplayer/Remote/native_cosmetics.cpp | 28 + .../Remote/native_cosmetics_layout.h | 40 ++ .../Multiplayer/Test/native_layout_tests.cpp | 17 + Extension/Settings/replay_guard.cpp | 69 ++ Extension/Settings/replay_guard.h | 14 + Extension/UI/NativeMenu/native_menu.cpp | 18 +- Extension/UI/Overlay/hub_page.cpp | 5 + Launcher/gui_launcher.cpp | 3 + Launcher/launch.cpp | 9 + Launcher/main.cpp | 7 +- README.md | 1 + Runtime/bootstrap.cpp | 2 +- Runtime/client_tick.cpp | 30 +- Runtime/runtime_init.cpp | 4 + cmake/Runtime.cmake | 1 + 26 files changed, 1107 insertions(+), 446 deletions(-) create mode 100644 Engine/Game/Build/20260929/replay_recorder.h create mode 100644 Extension/Settings/replay_guard.cpp create mode 100644 Extension/Settings/replay_guard.h diff --git a/Engine/Core/Platform/launcher_support.cpp b/Engine/Core/Platform/launcher_support.cpp index 7e2d632..818afb9 100644 --- a/Engine/Core/Platform/launcher_support.cpp +++ b/Engine/Core/Platform/launcher_support.cpp @@ -144,6 +144,39 @@ std::uint64_t offline_steam_id() noexcept { } #ifdef _WIN32 +namespace { +bool map_option(std::wstring_view argument) { + return argument == L"-map" || argument == L"--map" || argument.starts_with(L"-map=") || argument.starts_with(L"--map="); +} +// The name goes into a console command the game runs: one line of plain text. +std::wstring map_name(std::wstring_view value) { + while (!value.empty() && value.front() == L' ') value.remove_prefix(1); + while (!value.empty() && value.back() == L' ') value.remove_suffix(1); + if (value.empty() || value.size() > 96) fail("-map requires a map's name, at most 96 characters"); + for (const auto character : value) + if (character < 0x20 || character > 0x7e || character == L';' || character == L'"') + fail("-map takes a map's name in plain letters, digits and spaces"); + return std::wstring(value); +} +} // namespace + +std::wstring take_map_option(std::vector& arguments) { + std::wstring map; + for (std::size_t index = 0; index < arguments.size();) { + const std::wstring_view argument(arguments[index]); + if (!map_option(argument)) { ++index; continue; } + const auto equals = argument.find(L'='); + if (equals != std::wstring_view::npos) map = map_name(argument.substr(equals + 1)); + else { + if (index + 1 == arguments.size()) fail("-map requires a map's name"); + map = map_name(arguments[index + 1]); + arguments.erase(arguments.begin() + static_cast(index) + 1); + } + arguments.erase(arguments.begin() + static_cast(index)); + } + return map; +} + LaunchOptions parse_launch_options(const std::vector& arguments) { LaunchOptions options; bool log_level_explicit = false; @@ -181,6 +214,15 @@ LaunchOptions parse_launch_options(const std::vector& arguments) { if (!logging::parse_level(level)) fail("--log-level requires trace, debug, info, warning, error, critical, or off"); options.log_level = std::move(level); log_level_explicit = true; continue; } + if (map_option(argument)) { + const auto equals = argument.find(L'='); + if (equals != std::wstring_view::npos) options.map = map_name(argument.substr(equals + 1)); + else { + if (++index == arguments.size()) fail("-map requires a map's name"); + options.map = map_name(arguments[index]); + } + continue; + } if (argument == L"--gpu-diagnostics") { options.gpu_diagnostics = true; continue; } if (argument == L"--no-discord") { options.discord = false; continue; } const bool menu_key = argument.starts_with(L"--menu-key="); diff --git a/Engine/Core/Platform/launcher_support.h b/Engine/Core/Platform/launcher_support.h index 3e096a7..1e15604 100644 --- a/Engine/Core/Platform/launcher_support.h +++ b/Engine/Core/Platform/launcher_support.h @@ -52,12 +52,19 @@ struct LaunchOptions { unsigned menu_key{0x2D}; // VK_INSERT unsigned console_key{0xC0}; // VK_OEM_3, the key left of 1 std::string log_level{"info"}; + // -map : the map the game loads once it is up, named as the console's "load" names + // one (a map's name, short name or asset path, or the start of one). Empty: none. + std::wstring map; std::vector game_arguments; }; // Arguments exclude argv[0]. Saved game display settings apply by default. // --windowed or an explicit width/height requests the windowed override. LaunchOptions parse_launch_options(const std::vector& arguments); +// Takes "-map " (--map, -map=) out of `arguments` and returns the name it gave, +// checked as parse_launch_options checks it; empty when there is none. For a launch whose +// other options come from somewhere else (the launcher's saved settings). +std::wstring take_map_option(std::vector& arguments); std::wstring windowed_arguments(const LaunchOptions& options); // Detect mods beside Skate.exe. Explicit game data paths take precedence. std::wstring mod_data_arguments(const std::filesystem::path& game_directory, diff --git a/Engine/Game/Build/20260929/entity_pages.h b/Engine/Game/Build/20260929/entity_pages.h index 1052a85..fa4068c 100644 --- a/Engine/Game/Build/20260929/entity_pages.h +++ b/Engine/Game/Build/20260929/entity_pages.h @@ -8,8 +8,11 @@ namespace dingosdk::game::build::v20260929::entity_pages { // EntityBus TransformSpace and property-space snapshots have 128 page pointers. // Slot 131072 formerly indexed pointer 128, overwriting snapshot bookkeeping. // Enlarge the leaf pages without changing owners, snapshots, IDs or their ABI. +// Four times the game's 131,072 slots (it was eight until 2026-10-10): the largest map load +// seen needed 150,152, and every page set the game keeps is this many times its own size, +// whether a map uses it or not. inline constexpr std::size_t page_count = 128; -inline constexpr unsigned page_shift = 13; +inline constexpr unsigned page_shift = 12; inline constexpr std::size_t page_entries = std::size_t{1} << page_shift; inline constexpr std::size_t page_bytes = page_entries * sizeof(std::uint64_t); inline constexpr std::size_t capacity = page_count * page_entries; @@ -17,13 +20,14 @@ inline constexpr std::size_t capacity = page_count * page_entries; // object addresses. Grow its 464-byte-record pages without changing that ABI. inline constexpr unsigned render_instance_page_shift = 11; inline constexpr std::size_t render_instance_page_bytes = (std::size_t{1} << render_instance_page_shift) * 0x1d0; -static_assert((512u << render_instance_page_shift) == capacity); +// (Left at eight times the game's: its pages are its own, by its own shift.) +static_assert((512u << render_instance_page_shift) >= capacity); static_assert(render_instance_page_bytes == 0xe8000); // The cull-ID list and this separate monotonic bounds arena must both fit the // city. Retail config can override GridSize after Game.cfg has been processed. // Preserve native allocation/reset ownership and any larger configured value. inline constexpr std::size_t spatial_arena_min_bytes = 128 * 1024 * 1024; -static_assert(capacity == 1'048'576 && page_bytes == 65'536); +static_assert(capacity == 524'288 && page_bytes == 32'768); enum class Kind { index_shift, page_bytes, page_entries, spatial_arena_bytes, @@ -67,303 +71,303 @@ inline constexpr Patch patches[]{ patch(0x050e244d, Kind::render_instance_pages, "41b800a00300", "41b800800e00"), patch(0x050e245b, Kind::render_instance_pages, "c1e309", "c1e30b"), patch(0x0489ee29, Kind::spatial_arena_bytes, "bb00001000", "bb00000008"), - patch(0x048a5e2c, Kind::index_shift, "c1e80a", "c1e80d"), - patch(0x048a5e31, Kind::index_shift, "c1e20a", "c1e20d"), - patch(0x048a5fec, Kind::index_shift, "c1e80a", "c1e80d"), - patch(0x048a6015, Kind::index_shift, "c1e20a", "c1e20d"), - patch(0x048b2713, Kind::index_shift, "c1e90a", "c1e90d"), - patch(0x048b271c, Kind::index_shift, "41c1e00a", "41c1e00d"), - patch(0x048b27b6, Kind::index_shift, "c1e80a", "c1e80d"), - patch(0x048b27c4, Kind::index_shift, "c1e10a", "c1e10d"), - patch(0x048b292a, Kind::index_shift, "41c1ea0a", "41c1ea0d"), - patch(0x048b2935, Kind::index_shift, "c1e10a", "c1e10d"), - patch(0x048b3816, Kind::index_shift, "c1e90a", "c1e90d"), - patch(0x048b3823, Kind::index_shift, "c1e20a", "c1e20d"), - patch(0x048b5112, Kind::index_shift, "41c1e80a", "41c1e80d"), - patch(0x048b5122, Kind::index_shift, "c1e00a", "c1e00d"), - patch(0x048b51a2, Kind::index_shift, "41c1e80a", "41c1e80d"), - patch(0x048b51b2, Kind::index_shift, "c1e00a", "c1e00d"), - patch(0x048b52d6, Kind::index_shift, "c1e80a", "c1e80d"), - patch(0x048b52dd, Kind::index_shift, "c1e10a", "c1e10d"), - patch(0x048b53cc, Kind::index_shift, "c1e80a", "c1e80d"), - patch(0x048b53d3, Kind::index_shift, "c1e10a", "c1e10d"), - patch(0x048b5692, Kind::index_shift, "41c1e80a", "41c1e80d"), - patch(0x048b56a2, Kind::index_shift, "c1e00a", "c1e00d"), - patch(0x048b57a9, Kind::index_shift, "c1e80a", "c1e80d"), - patch(0x048b57b0, Kind::index_shift, "c1e10a", "c1e10d"), - patch(0x048b58b6, Kind::index_shift, "c1e80a", "c1e80d"), - patch(0x048b58bd, Kind::index_shift, "c1e10a", "c1e10d"), - patch(0x048b7ed4, Kind::index_shift, "c1e90a", "c1e90d"), - patch(0x048b7ef5, Kind::index_shift, "41c1e00a", "41c1e00d"), - patch(0x048b8530, Kind::index_shift, "c1e80a", "c1e80d"), - patch(0x048b8556, Kind::index_shift, "41c1e20a", "41c1e20d"), - patch(0x048b9879, Kind::page_entries, "81c600040000", "81c600200000"), - patch(0x048b9881, Kind::page_bytes, "488daf00200000", "488daf00000100"), - patch(0x048b99d8, Kind::page_entries, "4181c400040000", "4181c400200000"), - patch(0x048b99e4, Kind::page_bytes, "498dbe00200000", "498dbe00000100"), - patch(0x048bb417, Kind::index_shift, "c1e90a", "c1e90d"), - patch(0x048bb41f, Kind::index_shift, "c1e20a", "c1e20d"), - patch(0x048bb76a, Kind::index_shift, "c1e80a", "c1e80d"), - patch(0x048bb771, Kind::index_shift, "c1e10a", "c1e10d"), - patch(0x048bb83a, Kind::index_shift, "c1e80a", "c1e80d"), - patch(0x048bb841, Kind::index_shift, "c1e10a", "c1e10d"), - patch(0x048bb8fa, Kind::index_shift, "c1e80a", "c1e80d"), - patch(0x048bb901, Kind::index_shift, "c1e10a", "c1e10d"), - patch(0x048bb9b9, Kind::index_shift, "c1e80a", "c1e80d"), - patch(0x048bb9c0, Kind::index_shift, "c1e10a", "c1e10d"), - patch(0x048bba89, Kind::index_shift, "c1e80a", "c1e80d"), - patch(0x048bba90, Kind::index_shift, "c1e10a", "c1e10d"), - patch(0x048bbb89, Kind::index_shift, "c1e80a", "c1e80d"), - patch(0x048bbb90, Kind::index_shift, "c1e10a", "c1e10d"), - patch(0x048bbc4b, Kind::index_shift, "c1e80a", "c1e80d"), - patch(0x048bbc52, Kind::index_shift, "c1e10a", "c1e10d"), - patch(0x048bbd39, Kind::index_shift, "c1e80a", "c1e80d"), - patch(0x048bbd40, Kind::index_shift, "c1e10a", "c1e10d"), - patch(0x048bbf26, Kind::index_shift, "c1e80a", "c1e80d"), - patch(0x048bbf2d, Kind::index_shift, "c1e10a", "c1e10d"), - patch(0x048bd05e, Kind::page_bytes, "b900200000", "b900000100"), - patch(0x048bd06d, Kind::page_bytes, "41b800200000", "41b800000100"), - patch(0x048bd42b, Kind::index_shift, "c1e80a", "c1e80d"), - patch(0x048bd432, Kind::index_shift, "c1e10a", "c1e10d"), - patch(0x048bd516, Kind::index_shift, "c1e80a", "c1e80d"), - patch(0x048bd51d, Kind::index_shift, "c1e10a", "c1e10d"), - patch(0x048c019e, Kind::page_entries, "4181c600040000", "4181c600200000"), - patch(0x048c01a7, Kind::page_bytes, "4c8da500200000", "4c8da500000100"), - patch(0x048c024e, Kind::page_entries, "4181c400040000", "4181c400200000"), - patch(0x048c025a, Kind::page_bytes, "4c8dad00200000", "4c8dad00000100"), - patch(0x048c069c, Kind::page_entries, "81c500040000", "81c500200000"), - patch(0x048c06a4, Kind::page_bytes, "4c8db600200000", "4c8db600000100"), - patch(0x048c07f8, Kind::page_entries, "4181c400040000", "4181c400200000"), - patch(0x048c0804, Kind::page_bytes, "498dbe00200000", "498dbe00000100"), - patch(0x048c3942, Kind::page_bytes, "b900200000", "b900000100"), - patch(0x048c394f, Kind::page_bytes, "41b800200000", "41b800000100"), - patch(0x048c39bb, Kind::index_shift, "c1e90a", "c1e90d"), - patch(0x048c39c0, Kind::index_shift, "c1e20a", "c1e20d"), - patch(0x048c4fbd, Kind::index_shift, "c1e90a", "c1e90d"), - patch(0x048c4fc5, Kind::index_shift, "c1e20a", "c1e20d"), - patch(0x048c50cf, Kind::index_shift, "c1e80a", "c1e80d"), - patch(0x048c50d6, Kind::index_shift, "c1e10a", "c1e10d"), - patch(0x048c5d01, Kind::index_shift, "c1e90a", "c1e90d"), - patch(0x048c5d06, Kind::index_shift, "c1e20a", "c1e20d"), - patch(0x048c5e1c, Kind::page_bytes, "41b800200000", "41b800000100"), - patch(0x048c5e53, Kind::page_bytes, "41b800200000", "41b800000100"), - patch(0x048c5e8d, Kind::page_bytes, "41b800200000", "41b800000100"), - patch(0x048c5ec7, Kind::page_bytes, "41b800200000", "41b800000100"), - patch(0x048c5f01, Kind::page_bytes, "41b800200000", "41b800000100"), - patch(0x048c5f3b, Kind::page_bytes, "41b800200000", "41b800000100"), - patch(0x048c5f75, Kind::page_bytes, "41b800200000", "41b800000100"), - patch(0x048c5faf, Kind::page_bytes, "41b800200000", "41b800000100"), - patch(0x048c703c, Kind::index_shift, "c1ea0a", "c1ea0d"), - patch(0x048c7049, Kind::index_shift, "c1e20a", "c1e20d"), - patch(0x04ea3670, Kind::index_shift, "c1e80a", "c1e80d"), - patch(0x04ea3692, Kind::index_shift, "c1e20a", "c1e20d"), - patch(0x04ea3b1d, Kind::index_shift, "c1e90a", "c1e90d"), - patch(0x04ea3b42, Kind::index_shift, "c1e00a", "c1e00d"), - patch(0x04ea3bd0, Kind::index_shift, "c1e90a", "c1e90d"), - patch(0x04ea3bf8, Kind::index_shift, "c1e00a", "c1e00d"), - patch(0x04ec12e3, Kind::index_shift, "c1e90a", "c1e90d"), - patch(0x04ec12eb, Kind::index_shift, "c1e20a", "c1e20d"), - patch(0x04ec13c7, Kind::index_shift, "c1e80a", "c1e80d"), - patch(0x04ec13d5, Kind::index_shift, "c1e10a", "c1e10d"), - patch(0x04ec2b13, Kind::index_shift, "c1e90a", "c1e90d"), - patch(0x04ec2b1b, Kind::index_shift, "c1e20a", "c1e20d"), - patch(0x04ec2bf7, Kind::index_shift, "c1e80a", "c1e80d"), - patch(0x04ec2c05, Kind::index_shift, "c1e10a", "c1e10d"), - patch(0x04ec2dc6, Kind::index_shift, "c1e90a", "c1e90d"), - patch(0x04ec2dce, Kind::index_shift, "c1e20a", "c1e20d"), - patch(0x04ec2eb7, Kind::index_shift, "c1e80a", "c1e80d"), - patch(0x04ec2ec5, Kind::index_shift, "c1e10a", "c1e10d"), - patch(0x04ed56e9, Kind::index_shift, "c1e80a", "c1e80d"), - patch(0x04ed56ef, Kind::index_shift, "41c1e20a", "41c1e20d"), - patch(0x04ed7296, Kind::index_shift, "c1e80a", "c1e80d"), - patch(0x04ed72bb, Kind::index_shift, "41c1e20a", "41c1e20d"), - patch(0x04ed78d3, Kind::index_shift, "c1e80a", "c1e80d"), - patch(0x04ed78f8, Kind::index_shift, "41c1e20a", "41c1e20d"), - patch(0x04edde6d, Kind::page_entries, "81c500040000", "81c500200000"), - patch(0x04edde78, Kind::page_bytes, "4c8dbf00200000", "4c8dbf00000100"), - patch(0x04eddff8, Kind::page_entries, "4181c400040000", "4181c400200000"), - patch(0x04ede004, Kind::page_bytes, "498dbe00200000", "498dbe00000100"), - patch(0x04edf33d, Kind::page_entries, "81c500040000", "81c500200000"), - patch(0x04edf348, Kind::page_bytes, "4c8dbf00200000", "4c8dbf00000100"), - patch(0x04edf508, Kind::page_entries, "4181c400040000", "4181c400200000"), - patch(0x04edf514, Kind::page_bytes, "498dbe00200000", "498dbe00000100"), - patch(0x04edf719, Kind::page_entries, "81c600040000", "81c600200000"), - patch(0x04edf724, Kind::page_bytes, "488daf00200000", "488daf00000100"), - patch(0x04edf888, Kind::page_entries, "4181c400040000", "4181c400200000"), - patch(0x04edf894, Kind::page_bytes, "498dbe00200000", "498dbe00000100"), - patch(0x04ee3b47, Kind::index_shift, "c1e90a", "c1e90d"), - patch(0x04ee3b4f, Kind::index_shift, "c1e20a", "c1e20d"), - patch(0x04ee4c77, Kind::index_shift, "c1e90a", "c1e90d"), - patch(0x04ee4c7f, Kind::index_shift, "c1e20a", "c1e20d"), - patch(0x04ee4f47, Kind::index_shift, "c1e90a", "c1e90d"), - patch(0x04ee4f4f, Kind::index_shift, "c1e20a", "c1e20d"), - patch(0x04ee7daa, Kind::index_shift, "c1e90a", "c1e90d"), - patch(0x04ee7db9, Kind::index_shift, "c1e20a", "c1e20d"), - patch(0x04eeb19e, Kind::page_bytes, "b900200000", "b900000100"), - patch(0x04eeb1ad, Kind::page_bytes, "41b800200000", "41b800000100"), - patch(0x04eeba26, Kind::index_shift, "c1e80a", "c1e80d"), - patch(0x04eeba2d, Kind::index_shift, "c1e10a", "c1e10d"), - patch(0x04eec74e, Kind::index_shift, "c1e80a", "c1e80d"), - patch(0x04eec755, Kind::index_shift, "c1e10a", "c1e10d"), - patch(0x04eec8ac, Kind::index_shift, "c1e80a", "c1e80d"), - patch(0x04eec8b3, Kind::index_shift, "c1e10a", "c1e10d"), - patch(0x04eecd45, Kind::index_shift, "41c1e80a", "41c1e80d"), - patch(0x04eecd58, Kind::index_shift, "c1e00a", "c1e00d"), - patch(0x04eece4d, Kind::index_shift, "c1e90a", "c1e90d"), - patch(0x04eece6e, Kind::index_shift, "41c1e00a", "41c1e00d"), - patch(0x04eecf6d, Kind::index_shift, "c1e80a", "c1e80d"), - patch(0x04eecf74, Kind::index_shift, "c1e10a", "c1e10d"), - patch(0x04eed0a0, Kind::index_shift, "c1e80a", "c1e80d"), - patch(0x04eed0a7, Kind::index_shift, "c1e10a", "c1e10d"), - patch(0x04eed221, Kind::index_shift, "c1e80a", "c1e80d"), - patch(0x04eed228, Kind::index_shift, "c1e10a", "c1e10d"), - patch(0x04eed31c, Kind::index_shift, "c1e80a", "c1e80d"), - patch(0x04eed323, Kind::index_shift, "c1e10a", "c1e10d"), - patch(0x04eed468, Kind::index_shift, "c1e90a", "c1e90d"), - patch(0x04eed474, Kind::index_shift, "c1e10a", "c1e10d"), - patch(0x04eed6a0, Kind::index_shift, "c1e80a", "c1e80d"), - patch(0x04eed6a7, Kind::index_shift, "c1e10a", "c1e10d"), - patch(0x04eed869, Kind::index_shift, "c1e80a", "c1e80d"), - patch(0x04eed870, Kind::index_shift, "c1e10a", "c1e10d"), - patch(0x04eed9f4, Kind::index_shift, "c1e80a", "c1e80d"), - patch(0x04eed9fb, Kind::index_shift, "c1e10a", "c1e10d"), - patch(0x04eee208, Kind::index_shift, "41c1e80a", "41c1e80d"), - patch(0x04eee21b, Kind::index_shift, "c1e00a", "c1e00d"), - patch(0x04eee292, Kind::index_shift, "41c1e80a", "41c1e80d"), - patch(0x04eee2a5, Kind::index_shift, "c1e00a", "c1e00d"), - patch(0x04eee328, Kind::index_shift, "41c1e80a", "41c1e80d"), - patch(0x04eee33b, Kind::index_shift, "c1e00a", "c1e00d"), - patch(0x04eee3b2, Kind::index_shift, "41c1e80a", "41c1e80d"), - patch(0x04eee3c5, Kind::index_shift, "c1e00a", "c1e00d"), - patch(0x04eee432, Kind::index_shift, "41c1e80a", "41c1e80d"), - patch(0x04eee445, Kind::index_shift, "c1e00a", "c1e00d"), - patch(0x04eee4a2, Kind::index_shift, "41c1e80a", "41c1e80d"), - patch(0x04eee4b5, Kind::index_shift, "c1e00a", "c1e00d"), - patch(0x04eee60a, Kind::index_shift, "c1e90a", "c1e90d"), - patch(0x04eee62b, Kind::index_shift, "41c1e00a", "41c1e00d"), - patch(0x04eee72f, Kind::index_shift, "c1e80a", "c1e80d"), - patch(0x04eee736, Kind::index_shift, "c1e10a", "c1e10d"), - patch(0x04eee89f, Kind::index_shift, "c1e80a", "c1e80d"), - patch(0x04eee8a6, Kind::index_shift, "c1e10a", "c1e10d"), - patch(0x04eeea0a, Kind::index_shift, "c1e80a", "c1e80d"), - patch(0x04eeea11, Kind::index_shift, "c1e10a", "c1e10d"), - patch(0x04eeecbe, Kind::index_shift, "c1e80a", "c1e80d"), - patch(0x04eeecc5, Kind::index_shift, "c1e10a", "c1e10d"), - patch(0x04eeede9, Kind::index_shift, "c1e80a", "c1e80d"), - patch(0x04eeedf0, Kind::index_shift, "c1e10a", "c1e10d"), - patch(0x04eef06f, Kind::index_shift, "c1e80a", "c1e80d"), - patch(0x04eef076, Kind::index_shift, "c1e10a", "c1e10d"), - patch(0x04eef15d, Kind::index_shift, "c1e80a", "c1e80d"), - patch(0x04eef164, Kind::index_shift, "c1e10a", "c1e10d"), - patch(0x04eef214, Kind::index_shift, "c1e80a", "c1e80d"), - patch(0x04eef21b, Kind::index_shift, "c1e10a", "c1e10d"), - patch(0x04ef0113, Kind::index_shift, "c1e80a", "c1e80d"), - patch(0x04ef011a, Kind::index_shift, "c1e10a", "c1e10d"), - patch(0x04ef5deb, Kind::page_entries, "4181c700040000", "4181c700200000"), - patch(0x04ef5df7, Kind::page_bytes, "498db600200000", "498db600000100"), - patch(0x04ef5f89, Kind::page_entries, "4181c700040000", "4181c700200000"), - patch(0x04ef5f95, Kind::page_bytes, "498db600200000", "498db600000100"), - patch(0x04ef60ae, Kind::page_entries, "4181c500040000", "4181c500200000"), - patch(0x04ef60ba, Kind::page_bytes, "4d8dbe00200000", "4d8dbe00000100"), - patch(0x04efc57b, Kind::page_entries, "4181c700040000", "4181c700200000"), - patch(0x04efc587, Kind::page_bytes, "498db600200000", "498db600000100"), - patch(0x04efc729, Kind::page_entries, "4181c700040000", "4181c700200000"), - patch(0x04efc735, Kind::page_bytes, "498db600200000", "498db600000100"), - patch(0x04efc858, Kind::page_entries, "4181c500040000", "4181c500200000"), - patch(0x04efc864, Kind::page_bytes, "4d8dbe00200000", "4d8dbe00000100"), - patch(0x04f097dc, Kind::page_entries, "4181c600040000", "4181c600200000"), - patch(0x04f097e5, Kind::page_bytes, "4c8da500200000", "4c8da500000100"), - patch(0x04f0988f, Kind::page_entries, "4181c400040000", "4181c400200000"), - patch(0x04f0989b, Kind::page_bytes, "4c8dad00200000", "4c8dad00000100"), - patch(0x04f0b69c, Kind::page_entries, "81c500040000", "81c500200000"), - patch(0x04f0b6a7, Kind::page_bytes, "4c8db600200000", "4c8db600000100"), - patch(0x04f0b828, Kind::page_entries, "4181c400040000", "4181c400200000"), - patch(0x04f0b834, Kind::page_bytes, "498dbe00200000", "498dbe00000100"), - patch(0x04f16e2d, Kind::index_shift, "c1e80a", "c1e80d"), - patch(0x04f16e34, Kind::index_shift, "c1e10a", "c1e10d"), - patch(0x04f16ee4, Kind::index_shift, "c1e80a", "c1e80d"), - patch(0x04f16eeb, Kind::index_shift, "c1e10a", "c1e10d"), - patch(0x04f1700c, Kind::index_shift, "c1e80a", "c1e80d"), - patch(0x04f17013, Kind::index_shift, "c1e10a", "c1e10d"), - patch(0x04f172d4, Kind::index_shift, "c1e80a", "c1e80d"), - patch(0x04f172db, Kind::index_shift, "c1e10a", "c1e10d"), - patch(0x04f17618, Kind::index_shift, "c1e80a", "c1e80d"), - patch(0x04f1761f, Kind::index_shift, "c1e10a", "c1e10d"), - patch(0x04f19462, Kind::page_bytes, "b900200000", "b900000100"), - patch(0x04f1946f, Kind::page_bytes, "41b800200000", "41b800000100"), - patch(0x04f194db, Kind::index_shift, "c1e90a", "c1e90d"), - patch(0x04f194e0, Kind::index_shift, "c1e20a", "c1e20d"), - patch(0x04f19ca2, Kind::page_bytes, "b900200000", "b900000100"), - patch(0x04f19caf, Kind::page_bytes, "41b800200000", "41b800000100"), - patch(0x04f19d1b, Kind::index_shift, "c1e90a", "c1e90d"), - patch(0x04f19d20, Kind::index_shift, "c1e20a", "c1e20d"), - patch(0x04f19f22, Kind::page_bytes, "b900200000", "b900000100"), - patch(0x04f19f2f, Kind::page_bytes, "41b800200000", "41b800000100"), - patch(0x04f19f9b, Kind::index_shift, "c1e90a", "c1e90d"), - patch(0x04f19fa0, Kind::index_shift, "c1e20a", "c1e20d"), - patch(0x04f1e856, Kind::index_shift, "c1e90a", "c1e90d"), - patch(0x04f1e863, Kind::index_shift, "c1e20a", "c1e20d"), - patch(0x04f1e8f0, Kind::index_shift, "c1e90a", "c1e90d"), - patch(0x04f1e8f8, Kind::index_shift, "c1e20a", "c1e20d"), - patch(0x04f1ea31, Kind::index_shift, "c1e80a", "c1e80d"), - patch(0x04f1ea39, Kind::index_shift, "c1e10a", "c1e10d"), - patch(0x04f216fd, Kind::index_shift, "c1e90a", "c1e90d"), - patch(0x04f2170a, Kind::index_shift, "c1e20a", "c1e20d"), - patch(0x04f21796, Kind::index_shift, "c1e90a", "c1e90d"), - patch(0x04f2179e, Kind::index_shift, "c1e20a", "c1e20d"), - patch(0x04f218d6, Kind::index_shift, "c1e80a", "c1e80d"), - patch(0x04f218de, Kind::index_shift, "c1e10a", "c1e10d"), - patch(0x04f21f09, Kind::index_shift, "c1e90a", "c1e90d"), - patch(0x04f21f16, Kind::index_shift, "c1e20a", "c1e20d"), - patch(0x04f21fa2, Kind::index_shift, "c1e90a", "c1e90d"), - patch(0x04f21faa, Kind::index_shift, "c1e20a", "c1e20d"), - patch(0x04f220e8, Kind::index_shift, "c1e80a", "c1e80d"), - patch(0x04f220f0, Kind::index_shift, "c1e10a", "c1e10d"), - patch(0x04f2b166, Kind::index_shift, "c1e80a", "c1e80d"), - patch(0x04f2b16b, Kind::index_shift, "c1e10a", "c1e10d"), - patch(0x04f2b2e4, Kind::index_shift, "c1e90a", "c1e90d"), - patch(0x04f2b2ec, Kind::index_shift, "c1e10a", "c1e10d"), - patch(0x04f2b38c, Kind::page_bytes, "41b800200000", "41b800000100"), - patch(0x04f2b3c3, Kind::page_bytes, "41b800200000", "41b800000100"), - patch(0x04f2b3fd, Kind::page_bytes, "41b800200000", "41b800000100"), - patch(0x04f2b437, Kind::page_bytes, "41b800200000", "41b800000100"), - patch(0x04f2b471, Kind::page_bytes, "41b800200000", "41b800000100"), - patch(0x04f2b4ab, Kind::page_bytes, "41b800200000", "41b800000100"), - patch(0x04f2b4e5, Kind::page_bytes, "41b800200000", "41b800000100"), - patch(0x04f2b51f, Kind::page_bytes, "41b800200000", "41b800000100"), - patch(0x04f2e6e6, Kind::index_shift, "c1e80a", "c1e80d"), - patch(0x04f2e6eb, Kind::index_shift, "c1e10a", "c1e10d"), - patch(0x04f2e877, Kind::index_shift, "c1e90a", "c1e90d"), - patch(0x04f2e87f, Kind::index_shift, "c1e10a", "c1e10d"), - patch(0x04f2e92c, Kind::page_bytes, "41b800200000", "41b800000100"), - patch(0x04f2e963, Kind::page_bytes, "41b800200000", "41b800000100"), - patch(0x04f2e99d, Kind::page_bytes, "41b800200000", "41b800000100"), - patch(0x04f2e9d7, Kind::page_bytes, "41b800200000", "41b800000100"), - patch(0x04f2ea11, Kind::page_bytes, "41b800200000", "41b800000100"), - patch(0x04f2ea4b, Kind::page_bytes, "41b800200000", "41b800000100"), - patch(0x04f2ea85, Kind::page_bytes, "41b800200000", "41b800000100"), - patch(0x04f2eabf, Kind::page_bytes, "41b800200000", "41b800000100"), - patch(0x04f2ec86, Kind::index_shift, "c1e80a", "c1e80d"), - patch(0x04f2ec8b, Kind::index_shift, "c1e10a", "c1e10d"), - patch(0x04f2ee27, Kind::index_shift, "c1e90a", "c1e90d"), - patch(0x04f2ee2f, Kind::index_shift, "c1e10a", "c1e10d"), - patch(0x04f2eedc, Kind::page_bytes, "41b800200000", "41b800000100"), - patch(0x04f2ef13, Kind::page_bytes, "41b800200000", "41b800000100"), - patch(0x04f2ef4d, Kind::page_bytes, "41b800200000", "41b800000100"), - patch(0x04f2ef87, Kind::page_bytes, "41b800200000", "41b800000100"), - patch(0x04f2efc1, Kind::page_bytes, "41b800200000", "41b800000100"), - patch(0x04f2effb, Kind::page_bytes, "41b800200000", "41b800000100"), - patch(0x04f2f035, Kind::page_bytes, "41b800200000", "41b800000100"), - patch(0x04f2f06f, Kind::page_bytes, "41b800200000", "41b800000100"), - patch(0x04f34fea, Kind::index_shift, "c1e80a", "c1e80d"), - patch(0x04f34ff1, Kind::index_shift, "c1e10a", "c1e10d"), - patch(0x04f35b60, Kind::index_shift, "c1e80a", "c1e80d"), - patch(0x04f35b66, Kind::index_shift, "41c1e00a", "41c1e00d"), - patch(0x04f38418, Kind::index_shift, "c1e80a", "c1e80d"), - patch(0x04f3841f, Kind::index_shift, "c1e10a", "c1e10d"), - patch(0x04f38958, Kind::index_shift, "c1e80a", "c1e80d"), - patch(0x04f3895f, Kind::index_shift, "c1e10a", "c1e10d"), - patch(0x04f38a78, Kind::index_shift, "c1e80a", "c1e80d"), - patch(0x04f38a7f, Kind::index_shift, "c1e10a", "c1e10d"), + patch(0x048a5e2c, Kind::index_shift, "c1e80a", "c1e80c"), + patch(0x048a5e31, Kind::index_shift, "c1e20a", "c1e20c"), + patch(0x048a5fec, Kind::index_shift, "c1e80a", "c1e80c"), + patch(0x048a6015, Kind::index_shift, "c1e20a", "c1e20c"), + patch(0x048b2713, Kind::index_shift, "c1e90a", "c1e90c"), + patch(0x048b271c, Kind::index_shift, "41c1e00a", "41c1e00c"), + patch(0x048b27b6, Kind::index_shift, "c1e80a", "c1e80c"), + patch(0x048b27c4, Kind::index_shift, "c1e10a", "c1e10c"), + patch(0x048b292a, Kind::index_shift, "41c1ea0a", "41c1ea0c"), + patch(0x048b2935, Kind::index_shift, "c1e10a", "c1e10c"), + patch(0x048b3816, Kind::index_shift, "c1e90a", "c1e90c"), + patch(0x048b3823, Kind::index_shift, "c1e20a", "c1e20c"), + patch(0x048b5112, Kind::index_shift, "41c1e80a", "41c1e80c"), + patch(0x048b5122, Kind::index_shift, "c1e00a", "c1e00c"), + patch(0x048b51a2, Kind::index_shift, "41c1e80a", "41c1e80c"), + patch(0x048b51b2, Kind::index_shift, "c1e00a", "c1e00c"), + patch(0x048b52d6, Kind::index_shift, "c1e80a", "c1e80c"), + patch(0x048b52dd, Kind::index_shift, "c1e10a", "c1e10c"), + patch(0x048b53cc, Kind::index_shift, "c1e80a", "c1e80c"), + patch(0x048b53d3, Kind::index_shift, "c1e10a", "c1e10c"), + patch(0x048b5692, Kind::index_shift, "41c1e80a", "41c1e80c"), + patch(0x048b56a2, Kind::index_shift, "c1e00a", "c1e00c"), + patch(0x048b57a9, Kind::index_shift, "c1e80a", "c1e80c"), + patch(0x048b57b0, Kind::index_shift, "c1e10a", "c1e10c"), + patch(0x048b58b6, Kind::index_shift, "c1e80a", "c1e80c"), + patch(0x048b58bd, Kind::index_shift, "c1e10a", "c1e10c"), + patch(0x048b7ed4, Kind::index_shift, "c1e90a", "c1e90c"), + patch(0x048b7ef5, Kind::index_shift, "41c1e00a", "41c1e00c"), + patch(0x048b8530, Kind::index_shift, "c1e80a", "c1e80c"), + patch(0x048b8556, Kind::index_shift, "41c1e20a", "41c1e20c"), + patch(0x048b9879, Kind::page_entries, "81c600040000", "81c600100000"), + patch(0x048b9881, Kind::page_bytes, "488daf00200000", "488daf00800000"), + patch(0x048b99d8, Kind::page_entries, "4181c400040000", "4181c400100000"), + patch(0x048b99e4, Kind::page_bytes, "498dbe00200000", "498dbe00800000"), + patch(0x048bb417, Kind::index_shift, "c1e90a", "c1e90c"), + patch(0x048bb41f, Kind::index_shift, "c1e20a", "c1e20c"), + patch(0x048bb76a, Kind::index_shift, "c1e80a", "c1e80c"), + patch(0x048bb771, Kind::index_shift, "c1e10a", "c1e10c"), + patch(0x048bb83a, Kind::index_shift, "c1e80a", "c1e80c"), + patch(0x048bb841, Kind::index_shift, "c1e10a", "c1e10c"), + patch(0x048bb8fa, Kind::index_shift, "c1e80a", "c1e80c"), + patch(0x048bb901, Kind::index_shift, "c1e10a", "c1e10c"), + patch(0x048bb9b9, Kind::index_shift, "c1e80a", "c1e80c"), + patch(0x048bb9c0, Kind::index_shift, "c1e10a", "c1e10c"), + patch(0x048bba89, Kind::index_shift, "c1e80a", "c1e80c"), + patch(0x048bba90, Kind::index_shift, "c1e10a", "c1e10c"), + patch(0x048bbb89, Kind::index_shift, "c1e80a", "c1e80c"), + patch(0x048bbb90, Kind::index_shift, "c1e10a", "c1e10c"), + patch(0x048bbc4b, Kind::index_shift, "c1e80a", "c1e80c"), + patch(0x048bbc52, Kind::index_shift, "c1e10a", "c1e10c"), + patch(0x048bbd39, Kind::index_shift, "c1e80a", "c1e80c"), + patch(0x048bbd40, Kind::index_shift, "c1e10a", "c1e10c"), + patch(0x048bbf26, Kind::index_shift, "c1e80a", "c1e80c"), + patch(0x048bbf2d, Kind::index_shift, "c1e10a", "c1e10c"), + patch(0x048bd05e, Kind::page_bytes, "b900200000", "b900800000"), + patch(0x048bd06d, Kind::page_bytes, "41b800200000", "41b800800000"), + patch(0x048bd42b, Kind::index_shift, "c1e80a", "c1e80c"), + patch(0x048bd432, Kind::index_shift, "c1e10a", "c1e10c"), + patch(0x048bd516, Kind::index_shift, "c1e80a", "c1e80c"), + patch(0x048bd51d, Kind::index_shift, "c1e10a", "c1e10c"), + patch(0x048c019e, Kind::page_entries, "4181c600040000", "4181c600100000"), + patch(0x048c01a7, Kind::page_bytes, "4c8da500200000", "4c8da500800000"), + patch(0x048c024e, Kind::page_entries, "4181c400040000", "4181c400100000"), + patch(0x048c025a, Kind::page_bytes, "4c8dad00200000", "4c8dad00800000"), + patch(0x048c069c, Kind::page_entries, "81c500040000", "81c500100000"), + patch(0x048c06a4, Kind::page_bytes, "4c8db600200000", "4c8db600800000"), + patch(0x048c07f8, Kind::page_entries, "4181c400040000", "4181c400100000"), + patch(0x048c0804, Kind::page_bytes, "498dbe00200000", "498dbe00800000"), + patch(0x048c3942, Kind::page_bytes, "b900200000", "b900800000"), + patch(0x048c394f, Kind::page_bytes, "41b800200000", "41b800800000"), + patch(0x048c39bb, Kind::index_shift, "c1e90a", "c1e90c"), + patch(0x048c39c0, Kind::index_shift, "c1e20a", "c1e20c"), + patch(0x048c4fbd, Kind::index_shift, "c1e90a", "c1e90c"), + patch(0x048c4fc5, Kind::index_shift, "c1e20a", "c1e20c"), + patch(0x048c50cf, Kind::index_shift, "c1e80a", "c1e80c"), + patch(0x048c50d6, Kind::index_shift, "c1e10a", "c1e10c"), + patch(0x048c5d01, Kind::index_shift, "c1e90a", "c1e90c"), + patch(0x048c5d06, Kind::index_shift, "c1e20a", "c1e20c"), + patch(0x048c5e1c, Kind::page_bytes, "41b800200000", "41b800800000"), + patch(0x048c5e53, Kind::page_bytes, "41b800200000", "41b800800000"), + patch(0x048c5e8d, Kind::page_bytes, "41b800200000", "41b800800000"), + patch(0x048c5ec7, Kind::page_bytes, "41b800200000", "41b800800000"), + patch(0x048c5f01, Kind::page_bytes, "41b800200000", "41b800800000"), + patch(0x048c5f3b, Kind::page_bytes, "41b800200000", "41b800800000"), + patch(0x048c5f75, Kind::page_bytes, "41b800200000", "41b800800000"), + patch(0x048c5faf, Kind::page_bytes, "41b800200000", "41b800800000"), + patch(0x048c703c, Kind::index_shift, "c1ea0a", "c1ea0c"), + patch(0x048c7049, Kind::index_shift, "c1e20a", "c1e20c"), + patch(0x04ea3670, Kind::index_shift, "c1e80a", "c1e80c"), + patch(0x04ea3692, Kind::index_shift, "c1e20a", "c1e20c"), + patch(0x04ea3b1d, Kind::index_shift, "c1e90a", "c1e90c"), + patch(0x04ea3b42, Kind::index_shift, "c1e00a", "c1e00c"), + patch(0x04ea3bd0, Kind::index_shift, "c1e90a", "c1e90c"), + patch(0x04ea3bf8, Kind::index_shift, "c1e00a", "c1e00c"), + patch(0x04ec12e3, Kind::index_shift, "c1e90a", "c1e90c"), + patch(0x04ec12eb, Kind::index_shift, "c1e20a", "c1e20c"), + patch(0x04ec13c7, Kind::index_shift, "c1e80a", "c1e80c"), + patch(0x04ec13d5, Kind::index_shift, "c1e10a", "c1e10c"), + patch(0x04ec2b13, Kind::index_shift, "c1e90a", "c1e90c"), + patch(0x04ec2b1b, Kind::index_shift, "c1e20a", "c1e20c"), + patch(0x04ec2bf7, Kind::index_shift, "c1e80a", "c1e80c"), + patch(0x04ec2c05, Kind::index_shift, "c1e10a", "c1e10c"), + patch(0x04ec2dc6, Kind::index_shift, "c1e90a", "c1e90c"), + patch(0x04ec2dce, Kind::index_shift, "c1e20a", "c1e20c"), + patch(0x04ec2eb7, Kind::index_shift, "c1e80a", "c1e80c"), + patch(0x04ec2ec5, Kind::index_shift, "c1e10a", "c1e10c"), + patch(0x04ed56e9, Kind::index_shift, "c1e80a", "c1e80c"), + patch(0x04ed56ef, Kind::index_shift, "41c1e20a", "41c1e20c"), + patch(0x04ed7296, Kind::index_shift, "c1e80a", "c1e80c"), + patch(0x04ed72bb, Kind::index_shift, "41c1e20a", "41c1e20c"), + patch(0x04ed78d3, Kind::index_shift, "c1e80a", "c1e80c"), + patch(0x04ed78f8, Kind::index_shift, "41c1e20a", "41c1e20c"), + patch(0x04edde6d, Kind::page_entries, "81c500040000", "81c500100000"), + patch(0x04edde78, Kind::page_bytes, "4c8dbf00200000", "4c8dbf00800000"), + patch(0x04eddff8, Kind::page_entries, "4181c400040000", "4181c400100000"), + patch(0x04ede004, Kind::page_bytes, "498dbe00200000", "498dbe00800000"), + patch(0x04edf33d, Kind::page_entries, "81c500040000", "81c500100000"), + patch(0x04edf348, Kind::page_bytes, "4c8dbf00200000", "4c8dbf00800000"), + patch(0x04edf508, Kind::page_entries, "4181c400040000", "4181c400100000"), + patch(0x04edf514, Kind::page_bytes, "498dbe00200000", "498dbe00800000"), + patch(0x04edf719, Kind::page_entries, "81c600040000", "81c600100000"), + patch(0x04edf724, Kind::page_bytes, "488daf00200000", "488daf00800000"), + patch(0x04edf888, Kind::page_entries, "4181c400040000", "4181c400100000"), + patch(0x04edf894, Kind::page_bytes, "498dbe00200000", "498dbe00800000"), + patch(0x04ee3b47, Kind::index_shift, "c1e90a", "c1e90c"), + patch(0x04ee3b4f, Kind::index_shift, "c1e20a", "c1e20c"), + patch(0x04ee4c77, Kind::index_shift, "c1e90a", "c1e90c"), + patch(0x04ee4c7f, Kind::index_shift, "c1e20a", "c1e20c"), + patch(0x04ee4f47, Kind::index_shift, "c1e90a", "c1e90c"), + patch(0x04ee4f4f, Kind::index_shift, "c1e20a", "c1e20c"), + patch(0x04ee7daa, Kind::index_shift, "c1e90a", "c1e90c"), + patch(0x04ee7db9, Kind::index_shift, "c1e20a", "c1e20c"), + patch(0x04eeb19e, Kind::page_bytes, "b900200000", "b900800000"), + patch(0x04eeb1ad, Kind::page_bytes, "41b800200000", "41b800800000"), + patch(0x04eeba26, Kind::index_shift, "c1e80a", "c1e80c"), + patch(0x04eeba2d, Kind::index_shift, "c1e10a", "c1e10c"), + patch(0x04eec74e, Kind::index_shift, "c1e80a", "c1e80c"), + patch(0x04eec755, Kind::index_shift, "c1e10a", "c1e10c"), + patch(0x04eec8ac, Kind::index_shift, "c1e80a", "c1e80c"), + patch(0x04eec8b3, Kind::index_shift, "c1e10a", "c1e10c"), + patch(0x04eecd45, Kind::index_shift, "41c1e80a", "41c1e80c"), + patch(0x04eecd58, Kind::index_shift, "c1e00a", "c1e00c"), + patch(0x04eece4d, Kind::index_shift, "c1e90a", "c1e90c"), + patch(0x04eece6e, Kind::index_shift, "41c1e00a", "41c1e00c"), + patch(0x04eecf6d, Kind::index_shift, "c1e80a", "c1e80c"), + patch(0x04eecf74, Kind::index_shift, "c1e10a", "c1e10c"), + patch(0x04eed0a0, Kind::index_shift, "c1e80a", "c1e80c"), + patch(0x04eed0a7, Kind::index_shift, "c1e10a", "c1e10c"), + patch(0x04eed221, Kind::index_shift, "c1e80a", "c1e80c"), + patch(0x04eed228, Kind::index_shift, "c1e10a", "c1e10c"), + patch(0x04eed31c, Kind::index_shift, "c1e80a", "c1e80c"), + patch(0x04eed323, Kind::index_shift, "c1e10a", "c1e10c"), + patch(0x04eed468, Kind::index_shift, "c1e90a", "c1e90c"), + patch(0x04eed474, Kind::index_shift, "c1e10a", "c1e10c"), + patch(0x04eed6a0, Kind::index_shift, "c1e80a", "c1e80c"), + patch(0x04eed6a7, Kind::index_shift, "c1e10a", "c1e10c"), + patch(0x04eed869, Kind::index_shift, "c1e80a", "c1e80c"), + patch(0x04eed870, Kind::index_shift, "c1e10a", "c1e10c"), + patch(0x04eed9f4, Kind::index_shift, "c1e80a", "c1e80c"), + patch(0x04eed9fb, Kind::index_shift, "c1e10a", "c1e10c"), + patch(0x04eee208, Kind::index_shift, "41c1e80a", "41c1e80c"), + patch(0x04eee21b, Kind::index_shift, "c1e00a", "c1e00c"), + patch(0x04eee292, Kind::index_shift, "41c1e80a", "41c1e80c"), + patch(0x04eee2a5, Kind::index_shift, "c1e00a", "c1e00c"), + patch(0x04eee328, Kind::index_shift, "41c1e80a", "41c1e80c"), + patch(0x04eee33b, Kind::index_shift, "c1e00a", "c1e00c"), + patch(0x04eee3b2, Kind::index_shift, "41c1e80a", "41c1e80c"), + patch(0x04eee3c5, Kind::index_shift, "c1e00a", "c1e00c"), + patch(0x04eee432, Kind::index_shift, "41c1e80a", "41c1e80c"), + patch(0x04eee445, Kind::index_shift, "c1e00a", "c1e00c"), + patch(0x04eee4a2, Kind::index_shift, "41c1e80a", "41c1e80c"), + patch(0x04eee4b5, Kind::index_shift, "c1e00a", "c1e00c"), + patch(0x04eee60a, Kind::index_shift, "c1e90a", "c1e90c"), + patch(0x04eee62b, Kind::index_shift, "41c1e00a", "41c1e00c"), + patch(0x04eee72f, Kind::index_shift, "c1e80a", "c1e80c"), + patch(0x04eee736, Kind::index_shift, "c1e10a", "c1e10c"), + patch(0x04eee89f, Kind::index_shift, "c1e80a", "c1e80c"), + patch(0x04eee8a6, Kind::index_shift, "c1e10a", "c1e10c"), + patch(0x04eeea0a, Kind::index_shift, "c1e80a", "c1e80c"), + patch(0x04eeea11, Kind::index_shift, "c1e10a", "c1e10c"), + patch(0x04eeecbe, Kind::index_shift, "c1e80a", "c1e80c"), + patch(0x04eeecc5, Kind::index_shift, "c1e10a", "c1e10c"), + patch(0x04eeede9, Kind::index_shift, "c1e80a", "c1e80c"), + patch(0x04eeedf0, Kind::index_shift, "c1e10a", "c1e10c"), + patch(0x04eef06f, Kind::index_shift, "c1e80a", "c1e80c"), + patch(0x04eef076, Kind::index_shift, "c1e10a", "c1e10c"), + patch(0x04eef15d, Kind::index_shift, "c1e80a", "c1e80c"), + patch(0x04eef164, Kind::index_shift, "c1e10a", "c1e10c"), + patch(0x04eef214, Kind::index_shift, "c1e80a", "c1e80c"), + patch(0x04eef21b, Kind::index_shift, "c1e10a", "c1e10c"), + patch(0x04ef0113, Kind::index_shift, "c1e80a", "c1e80c"), + patch(0x04ef011a, Kind::index_shift, "c1e10a", "c1e10c"), + patch(0x04ef5deb, Kind::page_entries, "4181c700040000", "4181c700100000"), + patch(0x04ef5df7, Kind::page_bytes, "498db600200000", "498db600800000"), + patch(0x04ef5f89, Kind::page_entries, "4181c700040000", "4181c700100000"), + patch(0x04ef5f95, Kind::page_bytes, "498db600200000", "498db600800000"), + patch(0x04ef60ae, Kind::page_entries, "4181c500040000", "4181c500100000"), + patch(0x04ef60ba, Kind::page_bytes, "4d8dbe00200000", "4d8dbe00800000"), + patch(0x04efc57b, Kind::page_entries, "4181c700040000", "4181c700100000"), + patch(0x04efc587, Kind::page_bytes, "498db600200000", "498db600800000"), + patch(0x04efc729, Kind::page_entries, "4181c700040000", "4181c700100000"), + patch(0x04efc735, Kind::page_bytes, "498db600200000", "498db600800000"), + patch(0x04efc858, Kind::page_entries, "4181c500040000", "4181c500100000"), + patch(0x04efc864, Kind::page_bytes, "4d8dbe00200000", "4d8dbe00800000"), + patch(0x04f097dc, Kind::page_entries, "4181c600040000", "4181c600100000"), + patch(0x04f097e5, Kind::page_bytes, "4c8da500200000", "4c8da500800000"), + patch(0x04f0988f, Kind::page_entries, "4181c400040000", "4181c400100000"), + patch(0x04f0989b, Kind::page_bytes, "4c8dad00200000", "4c8dad00800000"), + patch(0x04f0b69c, Kind::page_entries, "81c500040000", "81c500100000"), + patch(0x04f0b6a7, Kind::page_bytes, "4c8db600200000", "4c8db600800000"), + patch(0x04f0b828, Kind::page_entries, "4181c400040000", "4181c400100000"), + patch(0x04f0b834, Kind::page_bytes, "498dbe00200000", "498dbe00800000"), + patch(0x04f16e2d, Kind::index_shift, "c1e80a", "c1e80c"), + patch(0x04f16e34, Kind::index_shift, "c1e10a", "c1e10c"), + patch(0x04f16ee4, Kind::index_shift, "c1e80a", "c1e80c"), + patch(0x04f16eeb, Kind::index_shift, "c1e10a", "c1e10c"), + patch(0x04f1700c, Kind::index_shift, "c1e80a", "c1e80c"), + patch(0x04f17013, Kind::index_shift, "c1e10a", "c1e10c"), + patch(0x04f172d4, Kind::index_shift, "c1e80a", "c1e80c"), + patch(0x04f172db, Kind::index_shift, "c1e10a", "c1e10c"), + patch(0x04f17618, Kind::index_shift, "c1e80a", "c1e80c"), + patch(0x04f1761f, Kind::index_shift, "c1e10a", "c1e10c"), + patch(0x04f19462, Kind::page_bytes, "b900200000", "b900800000"), + patch(0x04f1946f, Kind::page_bytes, "41b800200000", "41b800800000"), + patch(0x04f194db, Kind::index_shift, "c1e90a", "c1e90c"), + patch(0x04f194e0, Kind::index_shift, "c1e20a", "c1e20c"), + patch(0x04f19ca2, Kind::page_bytes, "b900200000", "b900800000"), + patch(0x04f19caf, Kind::page_bytes, "41b800200000", "41b800800000"), + patch(0x04f19d1b, Kind::index_shift, "c1e90a", "c1e90c"), + patch(0x04f19d20, Kind::index_shift, "c1e20a", "c1e20c"), + patch(0x04f19f22, Kind::page_bytes, "b900200000", "b900800000"), + patch(0x04f19f2f, Kind::page_bytes, "41b800200000", "41b800800000"), + patch(0x04f19f9b, Kind::index_shift, "c1e90a", "c1e90c"), + patch(0x04f19fa0, Kind::index_shift, "c1e20a", "c1e20c"), + patch(0x04f1e856, Kind::index_shift, "c1e90a", "c1e90c"), + patch(0x04f1e863, Kind::index_shift, "c1e20a", "c1e20c"), + patch(0x04f1e8f0, Kind::index_shift, "c1e90a", "c1e90c"), + patch(0x04f1e8f8, Kind::index_shift, "c1e20a", "c1e20c"), + patch(0x04f1ea31, Kind::index_shift, "c1e80a", "c1e80c"), + patch(0x04f1ea39, Kind::index_shift, "c1e10a", "c1e10c"), + patch(0x04f216fd, Kind::index_shift, "c1e90a", "c1e90c"), + patch(0x04f2170a, Kind::index_shift, "c1e20a", "c1e20c"), + patch(0x04f21796, Kind::index_shift, "c1e90a", "c1e90c"), + patch(0x04f2179e, Kind::index_shift, "c1e20a", "c1e20c"), + patch(0x04f218d6, Kind::index_shift, "c1e80a", "c1e80c"), + patch(0x04f218de, Kind::index_shift, "c1e10a", "c1e10c"), + patch(0x04f21f09, Kind::index_shift, "c1e90a", "c1e90c"), + patch(0x04f21f16, Kind::index_shift, "c1e20a", "c1e20c"), + patch(0x04f21fa2, Kind::index_shift, "c1e90a", "c1e90c"), + patch(0x04f21faa, Kind::index_shift, "c1e20a", "c1e20c"), + patch(0x04f220e8, Kind::index_shift, "c1e80a", "c1e80c"), + patch(0x04f220f0, Kind::index_shift, "c1e10a", "c1e10c"), + patch(0x04f2b166, Kind::index_shift, "c1e80a", "c1e80c"), + patch(0x04f2b16b, Kind::index_shift, "c1e10a", "c1e10c"), + patch(0x04f2b2e4, Kind::index_shift, "c1e90a", "c1e90c"), + patch(0x04f2b2ec, Kind::index_shift, "c1e10a", "c1e10c"), + patch(0x04f2b38c, Kind::page_bytes, "41b800200000", "41b800800000"), + patch(0x04f2b3c3, Kind::page_bytes, "41b800200000", "41b800800000"), + patch(0x04f2b3fd, Kind::page_bytes, "41b800200000", "41b800800000"), + patch(0x04f2b437, Kind::page_bytes, "41b800200000", "41b800800000"), + patch(0x04f2b471, Kind::page_bytes, "41b800200000", "41b800800000"), + patch(0x04f2b4ab, Kind::page_bytes, "41b800200000", "41b800800000"), + patch(0x04f2b4e5, Kind::page_bytes, "41b800200000", "41b800800000"), + patch(0x04f2b51f, Kind::page_bytes, "41b800200000", "41b800800000"), + patch(0x04f2e6e6, Kind::index_shift, "c1e80a", "c1e80c"), + patch(0x04f2e6eb, Kind::index_shift, "c1e10a", "c1e10c"), + patch(0x04f2e877, Kind::index_shift, "c1e90a", "c1e90c"), + patch(0x04f2e87f, Kind::index_shift, "c1e10a", "c1e10c"), + patch(0x04f2e92c, Kind::page_bytes, "41b800200000", "41b800800000"), + patch(0x04f2e963, Kind::page_bytes, "41b800200000", "41b800800000"), + patch(0x04f2e99d, Kind::page_bytes, "41b800200000", "41b800800000"), + patch(0x04f2e9d7, Kind::page_bytes, "41b800200000", "41b800800000"), + patch(0x04f2ea11, Kind::page_bytes, "41b800200000", "41b800800000"), + patch(0x04f2ea4b, Kind::page_bytes, "41b800200000", "41b800800000"), + patch(0x04f2ea85, Kind::page_bytes, "41b800200000", "41b800800000"), + patch(0x04f2eabf, Kind::page_bytes, "41b800200000", "41b800800000"), + patch(0x04f2ec86, Kind::index_shift, "c1e80a", "c1e80c"), + patch(0x04f2ec8b, Kind::index_shift, "c1e10a", "c1e10c"), + patch(0x04f2ee27, Kind::index_shift, "c1e90a", "c1e90c"), + patch(0x04f2ee2f, Kind::index_shift, "c1e10a", "c1e10c"), + patch(0x04f2eedc, Kind::page_bytes, "41b800200000", "41b800800000"), + patch(0x04f2ef13, Kind::page_bytes, "41b800200000", "41b800800000"), + patch(0x04f2ef4d, Kind::page_bytes, "41b800200000", "41b800800000"), + patch(0x04f2ef87, Kind::page_bytes, "41b800200000", "41b800800000"), + patch(0x04f2efc1, Kind::page_bytes, "41b800200000", "41b800800000"), + patch(0x04f2effb, Kind::page_bytes, "41b800200000", "41b800800000"), + patch(0x04f2f035, Kind::page_bytes, "41b800200000", "41b800800000"), + patch(0x04f2f06f, Kind::page_bytes, "41b800200000", "41b800800000"), + patch(0x04f34fea, Kind::index_shift, "c1e80a", "c1e80c"), + patch(0x04f34ff1, Kind::index_shift, "c1e10a", "c1e10c"), + patch(0x04f35b60, Kind::index_shift, "c1e80a", "c1e80c"), + patch(0x04f35b66, Kind::index_shift, "41c1e00a", "41c1e00c"), + patch(0x04f38418, Kind::index_shift, "c1e80a", "c1e80c"), + patch(0x04f3841f, Kind::index_shift, "c1e10a", "c1e10c"), + patch(0x04f38958, Kind::index_shift, "c1e80a", "c1e80c"), + patch(0x04f3895f, Kind::index_shift, "c1e10a", "c1e10c"), + patch(0x04f38a78, Kind::index_shift, "c1e80a", "c1e80c"), + patch(0x04f38a7f, Kind::index_shift, "c1e10a", "c1e10c"), }; } // namespace dingosdk::game::build::v20260929::entity_pages diff --git a/Engine/Game/Build/20260929/pending_updates.h b/Engine/Game/Build/20260929/pending_updates.h index 0699a24..6f3f266 100644 --- a/Engine/Game/Build/20260929/pending_updates.h +++ b/Engine/Game/Build/20260929/pending_updates.h @@ -12,7 +12,7 @@ using entity_pages::patch; // treating its allocator pointer and metadata as additional page pointers. inline constexpr std::size_t entry_bytes = 24; inline constexpr std::size_t page_bytes = entity_pages::page_entries * entry_bytes; -static_assert(page_bytes == 0x30000); +static_assert(page_bytes == 0x18000); // Keep owner offsets and stack-frame layouts intact. Disable only the two // undersized inline pages; the existing lazy allocator/destructor owns every @@ -21,141 +21,141 @@ static_assert(page_bytes == 0x30000); inline constexpr Patch patches[]{ patch(0x048a7450, Kind::pending_update_pages, "41c7800c04000002000000", "41c7800c04000000000000"), patch(0x048a745b, Kind::pending_update_pages, "833db6cf910206", "e9300000009090"), - patch(0x048a944c, Kind::pending_update_pages, "41b800600000", "41b800000300"), - patch(0x048abf3e, Kind::pending_update_pages, "c1e90a", "c1e90d"), - patch(0x048abf44, Kind::pending_update_pages, "c1e10a", "c1e10d"), - patch(0x048afad8, Kind::pending_update_pages, "c1e80a", "c1e80d"), - patch(0x048afae2, Kind::pending_update_pages, "c1e10a", "c1e10d"), - patch(0x048bdd9b, Kind::pending_update_pages, "c1e80a", "c1e80d"), - patch(0x048bdda9, Kind::pending_update_pages, "41c1e20a", "41c1e20d"), - patch(0x048bddcb, Kind::pending_update_pages, "c1e80a", "c1e80d"), - patch(0x048bddd2, Kind::pending_update_pages, "c1e10a", "c1e10d"), - patch(0x048be423, Kind::pending_update_pages, "c1e80a", "c1e80d"), - patch(0x048be437, Kind::pending_update_pages, "ba00600000", "ba00000300"), - patch(0x048be45b, Kind::pending_update_pages, "c1e70a", "c1e70d"), - patch(0x048bffb9, Kind::pending_update_pages, "c1e80a", "c1e80d"), - patch(0x048bffbe, Kind::pending_update_pages, "c1e00a", "c1e00d"), - patch(0x048bfff2, Kind::pending_update_pages, "c1e80a", "c1e80d"), - patch(0x048bfff9, Kind::pending_update_pages, "c1e10a", "c1e10d"), - patch(0x048c0012, Kind::pending_update_pages, "c1e80a", "c1e80d"), - patch(0x048c0017, Kind::pending_update_pages, "c1e00a", "c1e00d"), - patch(0x048c04c2, Kind::pending_update_pages, "c1e90a", "c1e90d"), - patch(0x048c04c8, Kind::pending_update_pages, "c1e10a", "c1e10d"), - patch(0x048c0e14, Kind::pending_update_pages, "c1e80a", "c1e80d"), - patch(0x048c0e1c, Kind::pending_update_pages, "c1e10a", "c1e10d"), - patch(0x04eb2fce, Kind::pending_update_pages, "c1e90a", "c1e90d"), - patch(0x04eb2fd4, Kind::pending_update_pages, "c1e10a", "c1e10d"), - patch(0x04eb30ae, Kind::pending_update_pages, "c1e90a", "c1e90d"), - patch(0x04eb30b4, Kind::pending_update_pages, "c1e10a", "c1e10d"), - patch(0x04eb318e, Kind::pending_update_pages, "c1e90a", "c1e90d"), - patch(0x04eb3194, Kind::pending_update_pages, "c1e10a", "c1e10d"), - patch(0x04eef54b, Kind::pending_update_pages, "c1e80a", "c1e80d"), - patch(0x04eef559, Kind::pending_update_pages, "41c1e20a", "41c1e20d"), - patch(0x04eef57b, Kind::pending_update_pages, "c1e80a", "c1e80d"), - patch(0x04eef582, Kind::pending_update_pages, "c1e10a", "c1e10d"), - patch(0x04eef61b, Kind::pending_update_pages, "c1e80a", "c1e80d"), - patch(0x04eef629, Kind::pending_update_pages, "41c1e20a", "41c1e20d"), - patch(0x04eef64b, Kind::pending_update_pages, "c1e80a", "c1e80d"), - patch(0x04eef652, Kind::pending_update_pages, "c1e10a", "c1e10d"), - patch(0x04eef6fb, Kind::pending_update_pages, "c1e80a", "c1e80d"), - patch(0x04eef709, Kind::pending_update_pages, "41c1e20a", "41c1e20d"), - patch(0x04eef72b, Kind::pending_update_pages, "c1e80a", "c1e80d"), - patch(0x04eef732, Kind::pending_update_pages, "c1e10a", "c1e10d"), - patch(0x04ef55da, Kind::pending_update_pages, "c1e90a", "c1e90d"), - patch(0x04ef5601, Kind::pending_update_pages, "ba00600000", "ba00000300"), - patch(0x04ef5628, Kind::pending_update_pages, "c1e10a", "c1e10d"), - patch(0x04ef566f, Kind::pending_update_pages, "ba00600000", "ba00000300"), - patch(0x04ef569d, Kind::pending_update_pages, "c1e10a", "c1e10d"), - patch(0x04ef56a5, Kind::pending_update_pages, "c1e80a", "c1e80d"), - patch(0x04ef56b4, Kind::pending_update_pages, "c1e00a", "c1e00d"), - patch(0x04ef574d, Kind::pending_update_pages, "c1e80a", "c1e80d"), - patch(0x04ef576f, Kind::pending_update_pages, "ba00600000", "ba00000300"), - patch(0x04ef579b, Kind::pending_update_pages, "c1e10a", "c1e10d"), - patch(0x04ef5813, Kind::pending_update_pages, "c1e80a", "c1e80d"), - patch(0x04ef583c, Kind::pending_update_pages, "ba00600000", "ba00000300"), - patch(0x04ef5863, Kind::pending_update_pages, "c1e10a", "c1e10d"), - patch(0x04ef58aa, Kind::pending_update_pages, "ba00600000", "ba00000300"), - patch(0x04ef58d8, Kind::pending_update_pages, "c1e10a", "c1e10d"), - patch(0x04ef58e0, Kind::pending_update_pages, "c1e80a", "c1e80d"), - patch(0x04ef58ef, Kind::pending_update_pages, "c1e00a", "c1e00d"), - patch(0x04ef5971, Kind::pending_update_pages, "c1e80a", "c1e80d"), - patch(0x04ef5978, Kind::pending_update_pages, "c1e10a", "c1e10d"), - patch(0x04ef59a4, Kind::pending_update_pages, "c1e80a", "c1e80d"), - patch(0x04ef59ab, Kind::pending_update_pages, "c1e10a", "c1e10d"), - patch(0x04ef5a72, Kind::pending_update_pages, "c1e80a", "c1e80d"), - patch(0x04ef5a91, Kind::pending_update_pages, "ba00600000", "ba00000300"), - patch(0x04ef5ab8, Kind::pending_update_pages, "c1e10a", "c1e10d"), - patch(0x04ef5b01, Kind::pending_update_pages, "c1e80a", "c1e80d"), - patch(0x04ef5b06, Kind::pending_update_pages, "c1e00a", "c1e00d"), - patch(0x04ef5b4f, Kind::pending_update_pages, "c1e80a", "c1e80d"), - patch(0x04ef5b6e, Kind::pending_update_pages, "ba00600000", "ba00000300"), - patch(0x04ef5b95, Kind::pending_update_pages, "c1e10a", "c1e10d"), - patch(0x04ef5bdd, Kind::pending_update_pages, "c1e80a", "c1e80d"), - patch(0x04ef5be2, Kind::pending_update_pages, "c1e00a", "c1e00d"), - patch(0x04ef5c1f, Kind::pending_update_pages, "c1e80a", "c1e80d"), - patch(0x04ef5c26, Kind::pending_update_pages, "c1e10a", "c1e10d"), - patch(0x04ef5c41, Kind::pending_update_pages, "c1e80a", "c1e80d"), - patch(0x04ef5c46, Kind::pending_update_pages, "c1e00a", "c1e00d"), - patch(0x04ef5e59, Kind::pending_update_pages, "c1e90a", "c1e90d"), - patch(0x04ef5e78, Kind::pending_update_pages, "ba00600000", "ba00000300"), - patch(0x04ef5e97, Kind::pending_update_pages, "c1e60a", "c1e60d"), - patch(0x04ef5feb, Kind::pending_update_pages, "c1e90a", "c1e90d"), - patch(0x04ef600a, Kind::pending_update_pages, "ba00600000", "ba00000300"), - patch(0x04ef6029, Kind::pending_update_pages, "c1e60a", "c1e60d"), - patch(0x04ef61af, Kind::pending_update_pages, "418d8dff030000", "418d8dff1f0000"), - patch(0x04ef61b6, Kind::pending_update_pages, "48c1e90a", "48c1e90d"), - patch(0x04ef61f3, Kind::pending_update_pages, "b800040000", "b800200000"), - patch(0x04ef628b, Kind::pending_update_pages, "4181c500fcffff", "4181c500e0ffff"), - patch(0x04ef630d, Kind::pending_update_pages, "41b800600000", "41b800000300"), - patch(0x04efbdf3, Kind::pending_update_pages, "c1e90a", "c1e90d"), - patch(0x04efbe1a, Kind::pending_update_pages, "ba00600000", "ba00000300"), - patch(0x04efbe41, Kind::pending_update_pages, "c1e10a", "c1e10d"), - patch(0x04efbe88, Kind::pending_update_pages, "ba00600000", "ba00000300"), - patch(0x04efbeb6, Kind::pending_update_pages, "c1e10a", "c1e10d"), - patch(0x04efbebe, Kind::pending_update_pages, "c1e80a", "c1e80d"), - patch(0x04efbecd, Kind::pending_update_pages, "c1e00a", "c1e00d"), - patch(0x04efbf6c, Kind::pending_update_pages, "c1e80a", "c1e80d"), - patch(0x04efbf8e, Kind::pending_update_pages, "ba00600000", "ba00000300"), - patch(0x04efbfba, Kind::pending_update_pages, "c1e10a", "c1e10d"), - patch(0x04efc035, Kind::pending_update_pages, "c1e80a", "c1e80d"), - patch(0x04efc062, Kind::pending_update_pages, "ba00600000", "ba00000300"), - patch(0x04efc08e, Kind::pending_update_pages, "c1e10a", "c1e10d"), - patch(0x04efc0e9, Kind::pending_update_pages, "ba00600000", "ba00000300"), - patch(0x04efc117, Kind::pending_update_pages, "c1e10a", "c1e10d"), - patch(0x04efc11f, Kind::pending_update_pages, "c1e80a", "c1e80d"), - patch(0x04efc12e, Kind::pending_update_pages, "c1e00a", "c1e00d"), - patch(0x04efc202, Kind::pending_update_pages, "c1e80a", "c1e80d"), - patch(0x04efc221, Kind::pending_update_pages, "ba00600000", "ba00000300"), - patch(0x04efc248, Kind::pending_update_pages, "c1e10a", "c1e10d"), - patch(0x04efc2c2, Kind::pending_update_pages, "c1e80a", "c1e80d"), - patch(0x04efc2e1, Kind::pending_update_pages, "ba00600000", "ba00000300"), - patch(0x04efc308, Kind::pending_update_pages, "c1e10a", "c1e10d"), - patch(0x04efc353, Kind::pending_update_pages, "c1e80a", "c1e80d"), - patch(0x04efc358, Kind::pending_update_pages, "c1e00a", "c1e00d"), - patch(0x04efc399, Kind::pending_update_pages, "c1e80a", "c1e80d"), - patch(0x04efc3a0, Kind::pending_update_pages, "c1e10a", "c1e10d"), - patch(0x04efc3bb, Kind::pending_update_pages, "c1e80a", "c1e80d"), - patch(0x04efc3c0, Kind::pending_update_pages, "c1e00a", "c1e00d"), - patch(0x04efc5ef, Kind::pending_update_pages, "c1e90a", "c1e90d"), - patch(0x04efc60e, Kind::pending_update_pages, "ba00600000", "ba00000300"), - patch(0x04efc62d, Kind::pending_update_pages, "c1e60a", "c1e60d"), - patch(0x04efc78e, Kind::pending_update_pages, "c1e90a", "c1e90d"), - patch(0x04efc7ad, Kind::pending_update_pages, "ba00600000", "ba00000300"), - patch(0x04efc7cc, Kind::pending_update_pages, "c1e60a", "c1e60d"), - patch(0x04efc96c, Kind::pending_update_pages, "418d8dff030000", "418d8dff1f0000"), - patch(0x04efc973, Kind::pending_update_pages, "48c1e90a", "48c1e90d"), - patch(0x04efc9b3, Kind::pending_update_pages, "b800040000", "b800200000"), - patch(0x04efca54, Kind::pending_update_pages, "4181c500fcffff", "4181c500e0ffff"), - patch(0x04efcad9, Kind::pending_update_pages, "41b800600000", "41b800000300"), - patch(0x04f095de, Kind::pending_update_pages, "c1e80a", "c1e80d"), - patch(0x04f095e3, Kind::pending_update_pages, "c1e00a", "c1e00d"), - patch(0x04f0961e, Kind::pending_update_pages, "c1e80a", "c1e80d"), - patch(0x04f09625, Kind::pending_update_pages, "c1e10a", "c1e10d"), - patch(0x04f0963e, Kind::pending_update_pages, "c1e80a", "c1e80d"), - patch(0x04f09643, Kind::pending_update_pages, "c1e00a", "c1e00d"), - patch(0x04f09af2, Kind::pending_update_pages, "c1e90a", "c1e90d"), - patch(0x04f09af8, Kind::pending_update_pages, "c1e10a", "c1e10d"), - patch(0x04f0db17, Kind::pending_update_pages, "c1e80a", "c1e80d"), - patch(0x04f0db1f, Kind::pending_update_pages, "c1e10a", "c1e10d"), + patch(0x048a944c, Kind::pending_update_pages, "41b800600000", "41b800800100"), + patch(0x048abf3e, Kind::pending_update_pages, "c1e90a", "c1e90c"), + patch(0x048abf44, Kind::pending_update_pages, "c1e10a", "c1e10c"), + patch(0x048afad8, Kind::pending_update_pages, "c1e80a", "c1e80c"), + patch(0x048afae2, Kind::pending_update_pages, "c1e10a", "c1e10c"), + patch(0x048bdd9b, Kind::pending_update_pages, "c1e80a", "c1e80c"), + patch(0x048bdda9, Kind::pending_update_pages, "41c1e20a", "41c1e20c"), + patch(0x048bddcb, Kind::pending_update_pages, "c1e80a", "c1e80c"), + patch(0x048bddd2, Kind::pending_update_pages, "c1e10a", "c1e10c"), + patch(0x048be423, Kind::pending_update_pages, "c1e80a", "c1e80c"), + patch(0x048be437, Kind::pending_update_pages, "ba00600000", "ba00800100"), + patch(0x048be45b, Kind::pending_update_pages, "c1e70a", "c1e70c"), + patch(0x048bffb9, Kind::pending_update_pages, "c1e80a", "c1e80c"), + patch(0x048bffbe, Kind::pending_update_pages, "c1e00a", "c1e00c"), + patch(0x048bfff2, Kind::pending_update_pages, "c1e80a", "c1e80c"), + patch(0x048bfff9, Kind::pending_update_pages, "c1e10a", "c1e10c"), + patch(0x048c0012, Kind::pending_update_pages, "c1e80a", "c1e80c"), + patch(0x048c0017, Kind::pending_update_pages, "c1e00a", "c1e00c"), + patch(0x048c04c2, Kind::pending_update_pages, "c1e90a", "c1e90c"), + patch(0x048c04c8, Kind::pending_update_pages, "c1e10a", "c1e10c"), + patch(0x048c0e14, Kind::pending_update_pages, "c1e80a", "c1e80c"), + patch(0x048c0e1c, Kind::pending_update_pages, "c1e10a", "c1e10c"), + patch(0x04eb2fce, Kind::pending_update_pages, "c1e90a", "c1e90c"), + patch(0x04eb2fd4, Kind::pending_update_pages, "c1e10a", "c1e10c"), + patch(0x04eb30ae, Kind::pending_update_pages, "c1e90a", "c1e90c"), + patch(0x04eb30b4, Kind::pending_update_pages, "c1e10a", "c1e10c"), + patch(0x04eb318e, Kind::pending_update_pages, "c1e90a", "c1e90c"), + patch(0x04eb3194, Kind::pending_update_pages, "c1e10a", "c1e10c"), + patch(0x04eef54b, Kind::pending_update_pages, "c1e80a", "c1e80c"), + patch(0x04eef559, Kind::pending_update_pages, "41c1e20a", "41c1e20c"), + patch(0x04eef57b, Kind::pending_update_pages, "c1e80a", "c1e80c"), + patch(0x04eef582, Kind::pending_update_pages, "c1e10a", "c1e10c"), + patch(0x04eef61b, Kind::pending_update_pages, "c1e80a", "c1e80c"), + patch(0x04eef629, Kind::pending_update_pages, "41c1e20a", "41c1e20c"), + patch(0x04eef64b, Kind::pending_update_pages, "c1e80a", "c1e80c"), + patch(0x04eef652, Kind::pending_update_pages, "c1e10a", "c1e10c"), + patch(0x04eef6fb, Kind::pending_update_pages, "c1e80a", "c1e80c"), + patch(0x04eef709, Kind::pending_update_pages, "41c1e20a", "41c1e20c"), + patch(0x04eef72b, Kind::pending_update_pages, "c1e80a", "c1e80c"), + patch(0x04eef732, Kind::pending_update_pages, "c1e10a", "c1e10c"), + patch(0x04ef55da, Kind::pending_update_pages, "c1e90a", "c1e90c"), + patch(0x04ef5601, Kind::pending_update_pages, "ba00600000", "ba00800100"), + patch(0x04ef5628, Kind::pending_update_pages, "c1e10a", "c1e10c"), + patch(0x04ef566f, Kind::pending_update_pages, "ba00600000", "ba00800100"), + patch(0x04ef569d, Kind::pending_update_pages, "c1e10a", "c1e10c"), + patch(0x04ef56a5, Kind::pending_update_pages, "c1e80a", "c1e80c"), + patch(0x04ef56b4, Kind::pending_update_pages, "c1e00a", "c1e00c"), + patch(0x04ef574d, Kind::pending_update_pages, "c1e80a", "c1e80c"), + patch(0x04ef576f, Kind::pending_update_pages, "ba00600000", "ba00800100"), + patch(0x04ef579b, Kind::pending_update_pages, "c1e10a", "c1e10c"), + patch(0x04ef5813, Kind::pending_update_pages, "c1e80a", "c1e80c"), + patch(0x04ef583c, Kind::pending_update_pages, "ba00600000", "ba00800100"), + patch(0x04ef5863, Kind::pending_update_pages, "c1e10a", "c1e10c"), + patch(0x04ef58aa, Kind::pending_update_pages, "ba00600000", "ba00800100"), + patch(0x04ef58d8, Kind::pending_update_pages, "c1e10a", "c1e10c"), + patch(0x04ef58e0, Kind::pending_update_pages, "c1e80a", "c1e80c"), + patch(0x04ef58ef, Kind::pending_update_pages, "c1e00a", "c1e00c"), + patch(0x04ef5971, Kind::pending_update_pages, "c1e80a", "c1e80c"), + patch(0x04ef5978, Kind::pending_update_pages, "c1e10a", "c1e10c"), + patch(0x04ef59a4, Kind::pending_update_pages, "c1e80a", "c1e80c"), + patch(0x04ef59ab, Kind::pending_update_pages, "c1e10a", "c1e10c"), + patch(0x04ef5a72, Kind::pending_update_pages, "c1e80a", "c1e80c"), + patch(0x04ef5a91, Kind::pending_update_pages, "ba00600000", "ba00800100"), + patch(0x04ef5ab8, Kind::pending_update_pages, "c1e10a", "c1e10c"), + patch(0x04ef5b01, Kind::pending_update_pages, "c1e80a", "c1e80c"), + patch(0x04ef5b06, Kind::pending_update_pages, "c1e00a", "c1e00c"), + patch(0x04ef5b4f, Kind::pending_update_pages, "c1e80a", "c1e80c"), + patch(0x04ef5b6e, Kind::pending_update_pages, "ba00600000", "ba00800100"), + patch(0x04ef5b95, Kind::pending_update_pages, "c1e10a", "c1e10c"), + patch(0x04ef5bdd, Kind::pending_update_pages, "c1e80a", "c1e80c"), + patch(0x04ef5be2, Kind::pending_update_pages, "c1e00a", "c1e00c"), + patch(0x04ef5c1f, Kind::pending_update_pages, "c1e80a", "c1e80c"), + patch(0x04ef5c26, Kind::pending_update_pages, "c1e10a", "c1e10c"), + patch(0x04ef5c41, Kind::pending_update_pages, "c1e80a", "c1e80c"), + patch(0x04ef5c46, Kind::pending_update_pages, "c1e00a", "c1e00c"), + patch(0x04ef5e59, Kind::pending_update_pages, "c1e90a", "c1e90c"), + patch(0x04ef5e78, Kind::pending_update_pages, "ba00600000", "ba00800100"), + patch(0x04ef5e97, Kind::pending_update_pages, "c1e60a", "c1e60c"), + patch(0x04ef5feb, Kind::pending_update_pages, "c1e90a", "c1e90c"), + patch(0x04ef600a, Kind::pending_update_pages, "ba00600000", "ba00800100"), + patch(0x04ef6029, Kind::pending_update_pages, "c1e60a", "c1e60c"), + patch(0x04ef61af, Kind::pending_update_pages, "418d8dff030000", "418d8dff0f0000"), + patch(0x04ef61b6, Kind::pending_update_pages, "48c1e90a", "48c1e90c"), + patch(0x04ef61f3, Kind::pending_update_pages, "b800040000", "b800100000"), + patch(0x04ef628b, Kind::pending_update_pages, "4181c500fcffff", "4181c500f0ffff"), + patch(0x04ef630d, Kind::pending_update_pages, "41b800600000", "41b800800100"), + patch(0x04efbdf3, Kind::pending_update_pages, "c1e90a", "c1e90c"), + patch(0x04efbe1a, Kind::pending_update_pages, "ba00600000", "ba00800100"), + patch(0x04efbe41, Kind::pending_update_pages, "c1e10a", "c1e10c"), + patch(0x04efbe88, Kind::pending_update_pages, "ba00600000", "ba00800100"), + patch(0x04efbeb6, Kind::pending_update_pages, "c1e10a", "c1e10c"), + patch(0x04efbebe, Kind::pending_update_pages, "c1e80a", "c1e80c"), + patch(0x04efbecd, Kind::pending_update_pages, "c1e00a", "c1e00c"), + patch(0x04efbf6c, Kind::pending_update_pages, "c1e80a", "c1e80c"), + patch(0x04efbf8e, Kind::pending_update_pages, "ba00600000", "ba00800100"), + patch(0x04efbfba, Kind::pending_update_pages, "c1e10a", "c1e10c"), + patch(0x04efc035, Kind::pending_update_pages, "c1e80a", "c1e80c"), + patch(0x04efc062, Kind::pending_update_pages, "ba00600000", "ba00800100"), + patch(0x04efc08e, Kind::pending_update_pages, "c1e10a", "c1e10c"), + patch(0x04efc0e9, Kind::pending_update_pages, "ba00600000", "ba00800100"), + patch(0x04efc117, Kind::pending_update_pages, "c1e10a", "c1e10c"), + patch(0x04efc11f, Kind::pending_update_pages, "c1e80a", "c1e80c"), + patch(0x04efc12e, Kind::pending_update_pages, "c1e00a", "c1e00c"), + patch(0x04efc202, Kind::pending_update_pages, "c1e80a", "c1e80c"), + patch(0x04efc221, Kind::pending_update_pages, "ba00600000", "ba00800100"), + patch(0x04efc248, Kind::pending_update_pages, "c1e10a", "c1e10c"), + patch(0x04efc2c2, Kind::pending_update_pages, "c1e80a", "c1e80c"), + patch(0x04efc2e1, Kind::pending_update_pages, "ba00600000", "ba00800100"), + patch(0x04efc308, Kind::pending_update_pages, "c1e10a", "c1e10c"), + patch(0x04efc353, Kind::pending_update_pages, "c1e80a", "c1e80c"), + patch(0x04efc358, Kind::pending_update_pages, "c1e00a", "c1e00c"), + patch(0x04efc399, Kind::pending_update_pages, "c1e80a", "c1e80c"), + patch(0x04efc3a0, Kind::pending_update_pages, "c1e10a", "c1e10c"), + patch(0x04efc3bb, Kind::pending_update_pages, "c1e80a", "c1e80c"), + patch(0x04efc3c0, Kind::pending_update_pages, "c1e00a", "c1e00c"), + patch(0x04efc5ef, Kind::pending_update_pages, "c1e90a", "c1e90c"), + patch(0x04efc60e, Kind::pending_update_pages, "ba00600000", "ba00800100"), + patch(0x04efc62d, Kind::pending_update_pages, "c1e60a", "c1e60c"), + patch(0x04efc78e, Kind::pending_update_pages, "c1e90a", "c1e90c"), + patch(0x04efc7ad, Kind::pending_update_pages, "ba00600000", "ba00800100"), + patch(0x04efc7cc, Kind::pending_update_pages, "c1e60a", "c1e60c"), + patch(0x04efc96c, Kind::pending_update_pages, "418d8dff030000", "418d8dff0f0000"), + patch(0x04efc973, Kind::pending_update_pages, "48c1e90a", "48c1e90c"), + patch(0x04efc9b3, Kind::pending_update_pages, "b800040000", "b800100000"), + patch(0x04efca54, Kind::pending_update_pages, "4181c500fcffff", "4181c500f0ffff"), + patch(0x04efcad9, Kind::pending_update_pages, "41b800600000", "41b800800100"), + patch(0x04f095de, Kind::pending_update_pages, "c1e80a", "c1e80c"), + patch(0x04f095e3, Kind::pending_update_pages, "c1e00a", "c1e00c"), + patch(0x04f0961e, Kind::pending_update_pages, "c1e80a", "c1e80c"), + patch(0x04f09625, Kind::pending_update_pages, "c1e10a", "c1e10c"), + patch(0x04f0963e, Kind::pending_update_pages, "c1e80a", "c1e80c"), + patch(0x04f09643, Kind::pending_update_pages, "c1e00a", "c1e00c"), + patch(0x04f09af2, Kind::pending_update_pages, "c1e90a", "c1e90c"), + patch(0x04f09af8, Kind::pending_update_pages, "c1e10a", "c1e10c"), + patch(0x04f0db17, Kind::pending_update_pages, "c1e80a", "c1e80c"), + patch(0x04f0db1f, Kind::pending_update_pages, "c1e10a", "c1e10c"), }; } // namespace dingosdk::game::build::v20260929::pending_updates diff --git a/Engine/Game/Build/20260929/replay_recorder.h b/Engine/Game/Build/20260929/replay_recorder.h new file mode 100644 index 0000000..7f05cff --- /dev/null +++ b/Engine/Game/Build/20260929/replay_recorder.h @@ -0,0 +1,19 @@ +#pragma once +#include "Engine/Game/Build/fingerprint.h" +#include + +// The game's rolling replay recording (Extension/Settings/replay_guard.cpp). +namespace dingosdk::game::build::v20260929::replay_recorder { +// end_chunk(recorder, force): run on a job worker as the game plays. Every recorder + 0x110 +// frames, or when the stream (recorder + 0x950) is as full as recorder + 0x30 allows, it ends +// the chunk being recorded (recorder + 0xa80) with a "Tail" marker and starts the next: it has +// the stream drop old chunks (0x48f0e30), then asks it for the new chunk's head (0x48fb5a0, an +// allocation tagged "rplyFreezeClip"). It returns whether a chunk was ended (al). +inline constexpr Fingerprint end_chunk{0x48ef8a0, { + 0x40,0x53,0x55,0x57,0x48,0x81,0xec,0xc0,0x01,0x00,0x00,0x48,0x8b,0x05,0x0e,0x4b, + 0x8d,0x02,0x48,0x33,0xc4,0x48,0x89,0x84,0x24,0x90,0x01,0x00,0x00,0xc5,0xfb,0x10}}; +// The store of the new head's first field, right after that allocation, with no check that the +// stream had room for one: an access violation writing 0x24 when it had none (crash reports of +// 2026-10-10, v2.0.3, skating on a server). +inline constexpr std::uintptr_t end_chunk_head_store = 0x48efa34; +} diff --git a/Engine/Game/Build/20260929/spatial_buckets.h b/Engine/Game/Build/20260929/spatial_buckets.h index 75d4826..db44b00 100644 --- a/Engine/Game/Build/20260929/spatial_buckets.h +++ b/Engine/Game/Build/20260929/spatial_buckets.h @@ -26,7 +26,8 @@ struct alignas(16) RootLayout { BucketLayout nodes[nodes_per_root]; std::uint32_t node_count; }; -static_assert(blocks_per_bucket * entries_per_block == entity_pages::capacity); +// (Left at eight times the game's while the entity pages are at four: room to spare.) +static_assert(blocks_per_bucket * entries_per_block >= entity_pages::capacity); static_assert(blocks_per_bucket < 65'536); static_assert(offsetof(BucketLayout, block_pointers) == 0x10000); static_assert(offsetof(BucketLayout, block_count) == 0x30000); diff --git a/Engine/Vfs/Test/store_copies_tests.cpp b/Engine/Vfs/Test/store_copies_tests.cpp index 72c79a3..fad00c1 100644 --- a/Engine/Vfs/Test/store_copies_tests.cpp +++ b/Engine/Vfs/Test/store_copies_tests.cpp @@ -391,7 +391,12 @@ void installed_mods(const fs::path& game, const fs::path& folder, const StoreIte << found.game_costumes << " of the game's costumes, " << found.items.size() << " copy(ies) of store items in " << found.mods.size() << " of them\n"; for (const auto& source : found.mods) - std::cout << " " << source.mod << ": " << source.count << ", e.g. " << source.example << " is " << source.original << '\n'; + std::cout << " " << source.mod << ": " << source.count << ", e.g. " << source.example << " is " << source.original + << (source.by_mesh ? " (by its mesh)" : " (by what its item is fitted on)") << '\n'; + for (const auto& mesh : found.meshes_seen) + std::cout << " mesh " << mesh.mod << ": " << mesh.mesh << ", " << mesh.points << " points, " << mesh.closest.shared + << " of them only in " << (mesh.closest.key.empty() ? std::string("no store costume") : mesh.closest.key + " (" + + std::to_string(mesh.closest.costume_points) + " points)") << '\n'; for (const auto& note : notes) std::cout << " note: " << note << '\n'; } } // namespace diff --git a/Engine/Vfs/mod_store_copies.cpp b/Engine/Vfs/mod_store_copies.cpp index dd631b4..7a436bc 100644 --- a/Engine/Vfs/mod_store_copies.cpp +++ b/Engine/Vfs/mod_store_copies.cpp @@ -615,6 +615,16 @@ std::string StoreMeshes::original(const std::vector& points) cons return most ? *most : std::string(); } +StoreMeshes::Closest StoreMeshes::closest(const std::vector& points) const { + std::vector shared(owners_.size()); + for (const auto point : points) + if (const auto found = points_.find(point); found != points_.end() && found->second != nobody) ++shared[found->second]; + Closest most; + for (std::size_t owner = 0; owner < owners_.size(); ++owner) + if (owners_[owner].sold && shared[owner] > most.shared) most = {owners_[owner].key, shared[owner], owners_[owner].largest}; + return most; +} + StoreCopies check_store_copies(const Catalog& catalog, const StoreItem& sold, std::vector* notes, std::size_t threads, bool background) noexcept { StoreCopies result; @@ -680,11 +690,13 @@ StoreCopies check_store_copies(const Catalog& catalog, const StoreItem& sold, st for (const auto& [mod, items, meshes] : added) { StoreCopies::Source source{mod->name}; for (const auto& mesh : meshes) { + result.meshes_seen.push_back({mod->name, mesh.name, mesh.points.size(), costumes.closest(mesh.points)}); const auto original = costumes.original(mesh.points); if (original.empty()) continue; if (!source.count++) { source.example = mesh.name; source.original = original; + source.by_mesh = true; } result.items.insert_or_assign(lower(mesh.name), lower(original)); } diff --git a/Engine/Vfs/mod_store_copies.h b/Engine/Vfs/mod_store_copies.h index ce7a96e..694356a 100644 --- a/Engine/Vfs/mod_store_copies.h +++ b/Engine/Vfs/mod_store_copies.h @@ -98,6 +98,13 @@ public: // an item anyone has. [[nodiscard]] std::string original(const std::vector& points) const; static constexpr std::size_t shared_least = 300; + // For a report: the store costume most of these points are only in, and how many are, whether + // or not that is enough to call the mesh taken from it (empty key: none are any costume's). + struct Closest { + std::string key; + std::size_t shared{}, costume_points{}; + }; + [[nodiscard]] Closest closest(const std::vector& points) const; private: static constexpr std::uint32_t nobody = 0xffffffffU; @@ -119,8 +126,19 @@ struct StoreCopies { std::string mod; std::size_t count{}; std::string example, original; // one of its copies and what that copies, as written + // How that one was found: its mesh has the costume's geometry, or its item is fitted on + // what the costume is (its shape, or a store item's looks or data). + bool by_mesh{}; }; std::vector mods; + // Every mesh level a mod ships and how much of a store costume's geometry is in it, for a + // report of the check (the Paid Cosmetics Detector, the test tool): a copy or not. + struct MeshSeen { + std::string mod, mesh; + std::size_t points{}; + StoreMeshes::Closest closest; + }; + std::vector meshes_seen; // How many of the game's own items the mods' were held against; none when no mod adds an item. std::size_t game_items{}; // And how many of the mods' meshes against how many of the game's costumes. diff --git a/Extension/Console/commands.h b/Extension/Console/commands.h index 2d554f0..9ff6d7f 100644 --- a/Extension/Console/commands.h +++ b/Extension/Console/commands.h @@ -22,6 +22,8 @@ void register_object_commands(Commands &); void register_park_editor_commands(Commands &); void register_multiplayer_commands(Commands &); void register_perf_commands(Commands &); +// perf_commands.cpp: starts `perf memory`'s trace with the game when RESKATE_MEMORY_TRACE asks for it. +void start_memory_trace_from_environment() noexcept; void register_item_commands(Commands &); void register_trainer_commands(Commands &); void register_hall_of_meat_commands(Commands &); diff --git a/Extension/Console/perf_commands.cpp b/Extension/Console/perf_commands.cpp index 644fe3f..c676d03 100644 --- a/Extension/Console/perf_commands.cpp +++ b/Extension/Console/perf_commands.cpp @@ -3,7 +3,16 @@ #include "Extension/Settings/engine_tweaks.h" #include "Extension/Settings/job_spin.h" #include "Extension/UI/ui_pointer_skip.h" +#include "Engine/Core/Hooks/hooks.h" +#include +#include +#include +#include +#include #include +#include +#include +#include // The performance profiler's console commands (Engine/Core/Profiling/profiler.h). Everything but // the saved HUD switch runs on the overlay's thread: the profiler is thread-safe. @@ -64,6 +73,202 @@ void print_report(const Output& out) { } } +// `perf memory`: what the game's memory is made of, by allocation, and what was added since a +// mark: for finding what a full server's memory goes on. An allocation is one VirtualAlloc +// reservation (the engine's arenas and pools are each one or a few); its size here is what of +// it is committed, which is what counts against the PC's memory. +struct MemoryMap { + std::map allocations; // private memory: base -> committed bytes + std::uint64_t committed{}, mapped{}, image{}; +}; +MemoryMap read_memory_map() { + MemoryMap map; + SYSTEM_INFO system{}; + GetSystemInfo(&system); + auto at = reinterpret_cast(system.lpMinimumApplicationAddress); + const auto end = reinterpret_cast(system.lpMaximumApplicationAddress); + MEMORY_BASIC_INFORMATION info{}; + while (at < end && VirtualQuery(reinterpret_cast(at), &info, sizeof(info)) == sizeof(info)) { + if (info.State == MEM_COMMIT) { + if (info.Type == MEM_PRIVATE) { + map.allocations[reinterpret_cast(info.AllocationBase)] += info.RegionSize; + map.committed += info.RegionSize; + } else (info.Type == MEM_IMAGE ? map.image : map.mapped) += info.RegionSize; + } + at = reinterpret_cast(info.BaseAddress) + info.RegionSize; + } + return map; +} +std::string megabytes(std::uint64_t bytes) { + return bytes >= (1ull << 30) ? std::format("{:.2f} GB", static_cast(bytes) / (1ull << 30)) + : std::format("{:.1f} MB", static_cast(bytes) / (1ull << 20)); +} +// How much of [base, base + bytes) is in RAM now: the pages the game has written to and +// Windows has not moved out. The rest of a committed block is promised and unused. +std::uint64_t resident_bytes(std::uintptr_t base, std::uint64_t bytes) { + constexpr std::size_t batch = 16384; + std::vector pages(batch); + std::uint64_t resident{}; + for (std::uint64_t done = 0; done < bytes;) { + const auto count = static_cast(std::min(batch, (bytes - done + 4095) / 4096)); + for (std::size_t i = 0; i < count; ++i) pages[i].VirtualAddress = reinterpret_cast(base + done + i * 4096); + if (!QueryWorkingSetEx(GetCurrentProcess(), pages.data(), static_cast(count * sizeof(pages[0])))) break; + for (std::size_t i = 0; i < count; ++i) resident += pages[i].VirtualAttributes.Valid ? 4096 : 0; + done += count * 4096ull; + } + return resident; +} +// Allocations by size, the sizes that add up to most first: "126 x 32.0 MB = 3.94 GB". +void print_by_size(const std::vector& sizes, const Output& out, std::size_t most) { + std::map> groups; // size rounded to 64 KB -> count, bytes + for (const auto size : sizes) { + auto& group = groups[(size + 0xffff) & ~0xffffull]; + ++group.first; + group.second += size; + } + std::vector>> order(groups.begin(), groups.end()); + std::sort(order.begin(), order.end(), [](const auto& a, const auto& b) { return a.second.second > b.second.second; }); + for (std::size_t i = 0; i < order.size() && i < most; ++i) + out(std::format(" {} x {} = {}", order[i].second.first, megabytes(order[i].first), megabytes(order[i].second.second))); +} + +// `perf memory trace`: who asks Windows for the middle-sized blocks (2 to 64 MB) while it is on, +// by the code that called for each: where a block of a size `perf memory` showed many of comes +// from. Every commit of memory in the process passes the hook while it is on, so it is a +// research aid and off until asked for. +namespace trace { +constexpr std::size_t callers = 8192, depth = 14; +struct Caller { + std::uint64_t hash{}, count{}, bytes{}, size{}, largest{}; // size: the smallest such block; largest: the largest + std::array frames{}; + unsigned frame_count{}; +}; +std::mutex mutex; +std::vector table(callers); // found by hash: a commit of any size can be traced +std::atomic on{}; +// The sizes traced: 2 to 64 MB from the console; the largest blocks alone from the start of +// the game (RESKATE_MEMORY_TRACE), where nearly everything is being allocated. +std::atomic least{2ull << 20}, most{64ull << 20}; +bool prepared{}; +using Allocate = LONG (NTAPI*)(HANDLE, PVOID*, ULONG_PTR, PSIZE_T, ULONG, ULONG); +Allocate original{}; + +// The newer call for the same (VirtualAlloc2, which graphics drivers and the D3D12 runtime use). +using AllocateEx = LONG (NTAPI*)(HANDLE, PVOID*, PSIZE_T, ULONG, ULONG, void*, ULONG); +AllocateEx original_ex{}; +void note(HANDLE process, SIZE_T asked, ULONG type) noexcept; + +LONG NTAPI hooked(HANDLE process, PVOID* base, ULONG_PTR zero_bits, PSIZE_T size, ULONG type, ULONG protect) { + const auto asked = size ? *size : 0; + const auto status = original(process, base, zero_bits, size, type, protect); + if (status >= 0) note(process, asked, type); + return status; +} +LONG NTAPI hooked_ex(HANDLE process, PVOID* base, PSIZE_T size, ULONG type, ULONG protect, void* parameters, ULONG parameter_count) { + const auto asked = size ? *size : 0; + const auto status = original_ex(process, base, size, type, protect, parameters, parameter_count); + if (status >= 0) note(process, asked, type); + return status; +} +void note(HANDLE process, SIZE_T asked, ULONG type) noexcept { + if (!on.load(std::memory_order_relaxed) || !(type & (MEM_COMMIT | MEM_RESERVE)) || process != GetCurrentProcess() || + asked < least.load(std::memory_order_relaxed) || asked > most.load(std::memory_order_relaxed)) return; + // (Reservations count too: a block reserved whole and committed a piece at a time is only + // this large when it is reserved.) + Caller seen; + seen.frame_count = CaptureStackBackTrace(2, static_cast(depth), seen.frames.data(), nullptr); + // By who asked alone: one caller's blocks of every size add up together. + std::uint64_t hash = 1469598103934665603ull; + for (unsigned i = 0; i < seen.frame_count; ++i) hash = hash * 1099511628211ull ^ reinterpret_cast(seen.frames[i]); + if (!hash) hash = 1; + std::lock_guard lock(mutex); + for (std::size_t probe = 0; probe < 128; ++probe) { + auto& caller = table[(hash + probe) % callers]; + if (caller.count && caller.hash != hash) continue; + if (!caller.count) { caller = seen; caller.hash = hash; caller.size = asked; } + ++caller.count; + caller.bytes += asked; + caller.size = std::min(caller.size, asked); + caller.largest = std::max(caller.largest, asked); + break; + } +} +bool start(std::string& error) { + if (!prepared) { + const auto target = reinterpret_cast(GetProcAddress(GetModuleHandleW(L"ntdll.dll"), "NtAllocateVirtualMemory")); + if (!target || hook_prepare(target, reinterpret_cast(&hooked), reinterpret_cast(&original)) != HookOk) { + error = "the allocation hook could not be prepared"; + return false; + } + // (Missing on an older Windows: then only the first is traced.) + if (const auto ex = reinterpret_cast(GetProcAddress(GetModuleHandleW(L"ntdll.dll"), "NtAllocateVirtualMemoryEx")); + ex && hook_prepare(ex, reinterpret_cast(&hooked_ex), reinterpret_cast(&original_ex)) != HookOk) + original_ex = nullptr; + prepared = true; + } + { std::lock_guard lock(mutex); std::fill(table.begin(), table.end(), Caller{}); } + const auto target = reinterpret_cast(GetProcAddress(GetModuleHandleW(L"ntdll.dll"), "NtAllocateVirtualMemory")); + if (!on.load() && hook_enable(target) != HookOk) { error = "the allocation hook could not be attached"; return false; } + if (!on.load() && original_ex) hook_enable(reinterpret_cast(GetProcAddress(GetModuleHandleW(L"ntdll.dll"), "NtAllocateVirtualMemoryEx"))); + on.store(true); + return true; +} +void stop() { + if (!on.exchange(false)) return; + hook_disable(reinterpret_cast(GetProcAddress(GetModuleHandleW(L"ntdll.dll"), "NtAllocateVirtualMemory"))); + if (original_ex) hook_disable(reinterpret_cast(GetProcAddress(GetModuleHandleW(L"ntdll.dll"), "NtAllocateVirtualMemoryEx"))); +} +std::string place(void* address) { + HMODULE module{}; + if (!GetModuleHandleExW(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, + reinterpret_cast(address), &module) || !module) + return std::format("{:#x}", reinterpret_cast(address)); + wchar_t path[MAX_PATH]{}; + GetModuleFileNameW(module, path, MAX_PATH); + const wchar_t* name = path; + for (const wchar_t* c = path; *c; ++c) if (*c == L'\\') name = c + 1; + std::string text; + for (; *name; ++name) text += static_cast(*name); + return std::format("{}+{:#x}", text, reinterpret_cast(address) - reinterpret_cast(module)); +} +void report(const Output& out) { + std::vector seen; + { + std::lock_guard lock(mutex); + for (const auto& caller : table) if (caller.count) seen.push_back(caller); + } + std::sort(seen.begin(), seen.end(), [](const Caller& a, const Caller& b) { return a.bytes > b.bytes; }); + out(std::format("Memory asked for while tracing (blocks of {} and up), by who asked ({} callers), the most first:", + megabytes(least.load()), seen.size())); + for (std::size_t i = 0; i < seen.size() && i < 16; ++i) { + out(seen[i].size == seen[i].largest + ? std::format(" {} x {} bytes = {}", seen[i].count, seen[i].size, megabytes(seen[i].bytes)) + : std::format(" {} blocks of {} to {} bytes = {}", seen[i].count, seen[i].size, seen[i].largest, megabytes(seen[i].bytes))); + std::string line = " "; + for (unsigned frame = 0; frame < seen[i].frame_count; ++frame) line += " < " + place(seen[i].frames[frame]); + out(line); + } +} +} // namespace trace + +// RESKATE_MEMORY_TRACE=: from the start of the game, who asks for each block of at +// least that size (`perf memory` then lists them). A research aid for what a map's load commits. +void start_memory_trace_from_environment() noexcept { + wchar_t value[16]{}; + const auto length = GetEnvironmentVariableW(L"RESKATE_MEMORY_TRACE", value, 16); + if (!length || length >= 16) return; + // (In megabytes; "all" for every block, however small.) + const bool all = value[0] == L'a'; + const auto megabytes_least = std::wcstoul(value, nullptr, 10); + if (!megabytes_least && !all) return; + try { + trace::least.store(all ? 1 : static_cast(megabytes_least) << 20); + trace::most.store(UINT64_MAX); + std::string error; + (void)trace::start(error); + } catch (...) {} +} + void register_perf_commands(Commands& registry) { // The HUD starts off every launch; these turn it on for the session. auto toggle = action("perf", "Show or hide the performance HUD", Group::console); @@ -234,6 +439,116 @@ void register_perf_commands(Commands& registry) { }; registry.add(std::move(tweaks)); + auto what = argument("mark", Type::text, true); + what.complete = [](const Model&, auto) { return std::vector{"mark", "trace", "stop", "big"}; }; + auto memory = action("perf memory", "The game's memory by allocation; `perf memory mark` remembers it and the next `perf memory` says what was added since; " + "`perf memory trace` then also says who asked for the middle-sized blocks, until `perf memory stop`; `perf memory big` lists the largest blocks and how much of each is in use", + Group::console, {std::move(what)}); + memory.execution = Execution::local; + memory.run = [](const Model&, const Values& args, const Output& out) { + static std::mutex mutex; + static MemoryMap marked; + static bool has_mark{}; + std::lock_guard lock(mutex); + const auto now = read_memory_map(); + out(std::format("Memory: {} committed in {} allocations, {} of files mapped in, {} of programs.", megabytes(now.committed), + now.allocations.size(), megabytes(now.mapped), megabytes(now.image))); + if (!args.empty() && equal(std::get(args[0]), "big")) { + // The largest blocks one by one: where each is, its exact size, and how much of it is used. + std::vector> order; + for (const auto& [base, size] : now.allocations) order.emplace_back(size, base); + std::sort(order.rbegin(), order.rend()); + out("The largest blocks (committed, of which in RAM now):"); + for (std::size_t i = 0; i < order.size() && i < 24; ++i) { + // (The whole reservation is walked: what is not committed in it is not in RAM either.) + MEMORY_BASIC_INFORMATION info{}; + std::uint64_t span = order[i].first; + for (auto at = order[i].second; VirtualQuery(reinterpret_cast(at), &info, sizeof(info)) == sizeof(info) && + reinterpret_cast(info.AllocationBase) == order[i].second; + at = reinterpret_cast(info.BaseAddress) + info.RegionSize) + span = reinterpret_cast(info.BaseAddress) + info.RegionSize - order[i].second; + out(std::format(" {:#x}: {} bytes ({}), {} in RAM", order[i].second, order[i].first, megabytes(order[i].first), + megabytes(resident_bytes(order[i].second, span)))); + if (i >= 3) continue; + // What kind of memory the three largest are, and a look at what is in them: how + // they are protected and held, and the bytes at a few places that are in RAM. + VirtualQuery(reinterpret_cast(order[i].second), &info, sizeof(info)); + PSAPI_WORKING_SET_EX_INFORMATION page{reinterpret_cast(order[i].second)}; + QueryWorkingSetEx(GetCurrentProcess(), &page, sizeof(page)); + out(std::format(" reserved {:#x} bytes, protection {:#x} (as reserved {:#x}), first region {:#x} bytes; first page: {}{}{}{}", + span, info.Protect, info.AllocationProtect, static_cast(info.RegionSize), + page.VirtualAttributes.Valid ? "in RAM" : "not in RAM", page.VirtualAttributes.Shared ? ", shared" : "", + page.VirtualAttributes.Locked ? ", locked" : "", page.VirtualAttributes.LargePage ? ", large pages" : "")); + std::size_t looked{}, zero_pages{}; + for (std::uint64_t part = 0; part < 64; ++part) { + const auto at = order[i].second + ((span / 64) * part & ~0xfffull); + PSAPI_WORKING_SET_EX_INFORMATION here{reinterpret_cast(at)}; + if (!QueryWorkingSetEx(GetCurrentProcess(), &here, sizeof(here)) || !here.VirtualAttributes.Valid) continue; + std::array bytes{}; + SIZE_T got{}; + if (!ReadProcessMemory(GetCurrentProcess(), reinterpret_cast(at), bytes.data(), bytes.size(), &got) || got != bytes.size()) continue; + ++looked; + const bool zero = std::all_of(bytes.begin(), bytes.end(), [](unsigned char byte) { return byte == 0; }); + zero_pages += zero; + if (zero || looked - zero_pages > 4) continue; + std::string hex; + for (std::size_t byte = 0; byte < 48; ++byte) hex += std::format("{:02x}{}", bytes[byte], byte % 8 == 7 ? " " : ""); + out(std::format(" +{:#x}: {}", at - order[i].second, hex)); + } + out(std::format(" of {} sampled pages in RAM, {} are all zero", looked, zero_pages)); + } + return; + } + const bool tracing = !args.empty() && equal(std::get(args[0]), "trace"); + if (!args.empty() && equal(std::get(args[0]), "stop")) { + trace::stop(); + out("Tracing stopped."); + return; + } + if (tracing) { + std::string error; + // Every size: a large block is often set aside whole and filled a page at a time. + trace::least.store(1); + trace::most.store(UINT64_MAX); + if (!trace::start(error)) { out("error: " + error); return; } + } + if (tracing || (!args.empty() && equal(std::get(args[0]), "mark"))) { + marked = now; + has_mark = true; + out(tracing ? "Marked, and tracing who asks for memory. Change what you want to measure, then run `perf memory`; `perf memory stop` ends the tracing." + : "Marked. Change what you want to measure, then run `perf memory`."); + return; + } + if (!has_mark) { + std::vector sizes; + for (const auto& [base, size] : now.allocations) sizes.push_back(size); + out("The allocation sizes that add up to most:"); + print_by_size(sizes, out, 20); + if (trace::on.load()) trace::report(out); + return; + } + std::vector added, grown; + std::uint64_t added_bytes{}, grown_bytes{}, freed_bytes{}; + for (const auto& [base, size] : now.allocations) { + const auto before = marked.allocations.find(base); + if (before == marked.allocations.end()) added.push_back(size), added_bytes += size; + else if (size > before->second) grown.push_back(size - before->second), grown_bytes += size - before->second; + else freed_bytes += before->second - size; + } + for (const auto& [base, size] : marked.allocations) + if (!now.allocations.contains(base)) freed_bytes += size; + out(std::format("Since the mark: {} to {} ({}{}).", megabytes(marked.committed), megabytes(now.committed), + now.committed >= marked.committed ? "+" : "-", + megabytes(now.committed >= marked.committed ? now.committed - marked.committed : marked.committed - now.committed))); + out(std::format("New allocations: {} in {}. By size:", megabytes(added_bytes), added.size())); + print_by_size(added, out, 20); + out(std::format("Growth of allocations that were there: {} in {}. By how much each grew:", megabytes(grown_bytes), grown.size())); + print_by_size(grown, out, 12); + out(std::format("Given back: {}.", megabytes(freed_bytes))); + if (trace::on.load()) trace::report(out); + }; + registry.add(std::move(memory)); + auto status = action("perf status", "Client update and frame timing, ReSkate's zones, CPU per thread", Group::console); status.execution = Execution::local; status.run = [](const Model&, const Values&, const Output& out) { print_summary(out); }; diff --git a/Extension/Multiplayer/Remote/native_cosmetics.cpp b/Extension/Multiplayer/Remote/native_cosmetics.cpp index bc5c14d..98fdcd9 100644 --- a/Extension/Multiplayer/Remote/native_cosmetics.cpp +++ b/Extension/Multiplayer/Remote/native_cosmetics.cpp @@ -10,6 +10,7 @@ #include #include #include +#include namespace dingosdk::multiplayer { namespace { @@ -71,6 +72,33 @@ void apply_cosmetic_recipe(std::uintptr_t base, std::uintptr_t entity, std::uint // Items this PC does not have (a player's cosmetics mod) would reject the // whole outfit: they become the matching default, or an empty slot. auto usable = recipe; + // A slot the peer's game does not have takes this player's own parameters for it: they + // are the right shape for the slot, and it is shown empty. + const auto own = [&](std::uint32_t slot, std::size_t size) { + std::vector out; + try { + for (const auto &item : memory.array(memory.ptr(local, 0x158), max_cosmetic_slots)) + if (item.slot == slot) { + out = memory.array(reinterpret_cast(item.parameters), + max_cosmetic_parameters); + break; + } + } catch (const std::exception &) { + out.clear(); + } + if (out.size() != size) out.assign(size, 0); + return out; + }; + if (const auto [empty, left_out] = memory.fit_slots(resource, usable, own); empty || left_out) { + static std::mutex logged_mutex; + static std::set> logged; + std::lock_guard lock(logged_mutex); + if (logged.size() < 32 && logged.emplace(recipe.key, empty, left_out).second) + logging::log(logging::Level::info, logging::Channel::runtime, + "Multiplayer: a player's {} has different cosmetic slots from this game's (a mod adds or " + "removes some): {} of this game's slots left empty, {} of their items left out.", + recipe.key == board_recipe_key ? "board" : "skater", empty, left_out); + } const auto reserved = [](const std::string &asset) { return profile_runtime::reserved_cosmetic(asset); }; for (const auto &[missing, replacement] : memory.substitute_missing(resource, usable, default_cosmetic_items, reserved)) { diff --git a/Extension/Multiplayer/Remote/native_cosmetics_layout.h b/Extension/Multiplayer/Remote/native_cosmetics_layout.h index e38a35a..beda695 100644 --- a/Extension/Multiplayer/Remote/native_cosmetics_layout.h +++ b/Extension/Multiplayer/Remote/native_cosmetics_layout.h @@ -200,6 +200,46 @@ template struct CosmeticMemory { } return swaps; } + // A mod can add slots to the skater or the board (stickers, say), and then two players' + // templates hold different slots. Puts a peer's items in this game's slots by slot hash: + // a slot the peer does not have stays empty, and an item in a slot this game does not + // have is left out. `own(slot, count)` gives the parameters for a slot the peer sent none + // for (or a different number of). Returns {slots left empty, items left out}; a recipe + // that already has this game's slots in order is not touched. + template + std::pair fit_slots(std::uintptr_t res, CosmeticRecipe &r, const Parameters &own) const { + const auto slots = ptr(res, 0x28); + const auto wanted = count(slots, 24, max_cosmetic_slots); + std::vector ids(wanted); + std::vector sizes(wanted); + bool same = r.items.size() == wanted; + for (std::size_t i = 0; i < wanted; ++i) { + const auto slot = ptr(slots, i * 24 + 8) & ~std::uintptr_t{4}; + ids[i] = get(slot, 0x4c); + sizes[i] = count(ptr(slot, 0x18), 4, max_cosmetic_parameters); + same = same && r.items[i].slot == ids[i] && r.items[i].parameters.size() == sizes[i]; + } + if (same) return {}; + std::vector used(r.items.size()); + std::vector fitted; + fitted.reserve(wanted); + std::size_t empty{}; + for (std::size_t i = 0; i < wanted; ++i) { + std::size_t from = 0; + while (from < r.items.size() && (used[from] || r.items[from].slot != ids[i])) ++from; + if (from == r.items.size()) { + ++empty; + fitted.push_back({ids[i], {}, own(ids[i], sizes[i])}); + continue; + } + used[from] = true; + fitted.push_back(std::move(r.items[from])); + if (fitted.back().parameters.size() != sizes[i]) fitted.back().parameters = own(ids[i], sizes[i]); + } + const auto left_out = static_cast(std::count(used.begin(), used.end(), false)); + r.items = std::move(fitted); + return {empty, left_out}; + } void validate(std::uintptr_t res, const CosmeticRecipe &r) const { check(r.key == get(res, 0x38) && r.version == get(res, 0x3c), "Peer cosmetic template differs from this game."); diff --git a/Extension/Multiplayer/Test/native_layout_tests.cpp b/Extension/Multiplayer/Test/native_layout_tests.cpp index d64da78..dbb8d30 100644 --- a/Extension/Multiplayer/Test/native_layout_tests.cpp +++ b/Extension/Multiplayer/Test/native_layout_tests.cpp @@ -618,6 +618,23 @@ void cosmetic_layout_regression() { check(modded.items[0].asset.empty() && !reject(m, modded), "A missing peer item without a default must become empty"); modded = one; check(fake.substitute_missing(res, modded, fallbacks).empty() && modded == one, "Installed peer items must stay"); + // A peer whose mods add slots (or who lacks this game's): items go to this game's slots by slot hash. + const auto own = [](std::uint32_t, std::size_t size) { return std::vector(size, 7); }; + modded = one; + check(fake.fit_slots(res, modded, own) == std::pair{} && modded == one, + "A recipe with this game's slots must not be touched"); + modded.items = {{99, "Own_Sticker", {1, 2}}, one.items[0]}; + check(fake.fit_slots(res, modded, own) == std::pair{0, 1} && modded == one && !reject(m, modded), + "An item in a slot this game lacks must be left out, and the rest kept"); + modded.items = {{99, "Own_Sticker", {1, 2}}}; + check(fake.fit_slots(res, modded, own) == std::pair{1, 1} && modded.items.size() == 1 && + modded.items[0] == CosmeticSlot{11, "", {7}} && !reject(m, modded), + "A slot the peer lacks must stay empty with this player's parameters"); + modded = one; + modded.items[0].parameters = {1, 2, 3}; + check(fake.fit_slots(res, modded, own) == std::pair{} && + modded.items[0] == CosmeticSlot{11, "Own_Shirt", {7}} && !reject(m, modded), + "An item whose slot takes other parameters here must keep its place"); auto bad = m; bad.put(categories, category + 0x100); bad.put(category + 0x138, std::uint32_t{88}); diff --git a/Extension/Settings/replay_guard.cpp b/Extension/Settings/replay_guard.cpp new file mode 100644 index 0000000..7fa666d --- /dev/null +++ b/Extension/Settings/replay_guard.cpp @@ -0,0 +1,69 @@ +#include "replay_guard.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/replay_recorder.h" +#include +#include +#include + +namespace dingosdk::replay_guard { +namespace { +namespace rr = addr::replay_recorder; +// (Two arguments are read; the others are passed on as they came.) +using EndChunk = std::uint64_t (*)(void*, std::uint64_t, std::uint64_t, std::uint64_t); +EndChunk original{}; +std::uintptr_t fault_at{}; +std::atomic installed{}; +std::atomic skips{}; + +// Only the write to the chunk head the stream did not give: nothing else is this guard's. +int ours(const EXCEPTION_POINTERS* info) noexcept { + const auto& record = *info->ExceptionRecord; + return record.ExceptionCode == EXCEPTION_ACCESS_VIOLATION && + reinterpret_cast(record.ExceptionAddress) == fault_at + ? EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH; +} +// Said the first time and then now and again: it can happen every few frames once it starts. +__declspec(noinline) void note_skip() noexcept { + const auto count = skips.fetch_add(1, std::memory_order_relaxed) + 1; + if (count != 1 && count % 1000) return; + try { + logging::log(logging::Level::warning, logging::Channel::runtime, + "Replay recording: the game had no room for its next chunk and would have crashed; the chunk was skipped " + "({} so far). Replays of this stretch may have gaps.", count); + } catch (...) {} +} +std::uint64_t guarded(void* recorder, std::uint64_t force, std::uint64_t third, std::uint64_t fourth) { + __try { + return original(recorder, force, third, fourth); + } __except (ours(GetExceptionInformation())) { + note_skip(); + return 0; // no chunk was ended + } +} +} // namespace + +bool install(std::uintptr_t base) noexcept { + if (installed.exchange(true)) return original != nullptr; + if (!base) return false; + std::array bytes{}; + if (!memory::peek(base + rr::end_chunk.rva, bytes) || bytes != rr::end_chunk.bytes) { + logging::log(logging::Level::warning, logging::Channel::runtime, + "Replay guard: this build's replay recorder differs; it is left as it is."); + return false; + } + fault_at = base + rr::end_chunk_head_store; + const auto target = reinterpret_cast(base + rr::end_chunk.rva); + if (hook_prepare(target, reinterpret_cast(&guarded), reinterpret_cast(&original)) != HookOk || + hook_enable(target) != HookOk) { + original = nullptr; + logging::log(logging::Level::warning, logging::Channel::runtime, "Replay guard: the replay recorder could not be hooked."); + return false; + } + return true; +} + +std::uint64_t skipped() noexcept { return skips.load(std::memory_order_relaxed); } +} // namespace dingosdk::replay_guard diff --git a/Extension/Settings/replay_guard.h b/Extension/Settings/replay_guard.h new file mode 100644 index 0000000..0611a52 --- /dev/null +++ b/Extension/Settings/replay_guard.h @@ -0,0 +1,14 @@ +#pragma once +#include + +// The game's replay recording crashes when its stream has no room for the next chunk: it writes +// to the chunk it was not given (Engine/Game/Build//replay_recorder.h). The guard runs +// that one function under an exception handler and, for that one fault alone, has it return as +// if no chunk was ended: the game goes on, with a gap or a glitch in the replay where it would +// have closed. Every other fault is left to the crash reporter. +namespace dingosdk::replay_guard { +// Checks this build's function and hooks it; once. False when the build differs (no guard). +bool install(std::uintptr_t base) noexcept; +// How many chunks were skipped since the game started. +std::uint64_t skipped() noexcept; +} diff --git a/Extension/UI/NativeMenu/native_menu.cpp b/Extension/UI/NativeMenu/native_menu.cpp index e11650a..8c0dd7b 100644 --- a/Extension/UI/NativeMenu/native_menu.cpp +++ b/Extension/UI/NativeMenu/native_menu.cpp @@ -565,8 +565,22 @@ void tick_page(std::uintptr_t base, bool loading) noexcept { // Tell the overlay while this page is the one on screen: it draws the body. const bool ours = shown_tab == our_key || shown_tab == our_key + tools_page * 0x100; const auto& shown_page = page_state(shown_tab == our_key ? 0 : tools_page); - const bool on_screen = ours && game_in_menu.load(std::memory_order_relaxed) && shown_page.page.handle && - context.type_of(shown_page.page.handle) == shown_page.page.type; + bool on_screen = ours && game_in_menu.load(std::memory_order_relaxed) && shown_page.page.handle && + context.type_of(shown_page.page.handle) == shown_page.page.type; + // The game's side menu (pause pressed again) and what opens from it (Settings...) go + // over the page without closing it: the page's focused index is -1 for that long, + // and 0 or more while the page has the player. Not ours to draw over then. + if (on_screen) { + try { + // (The page's screen record, the part every screen of the game has, and in it the index.) + on_screen = read(context.address(context.path(shown_page.page, {0xfafda1a6, 0x20a39d92, 0x6b01fd01}))) >= 0; + } catch (const std::exception& e) { + // A page laid out another way: as before, and said once. + static bool said{}; + if (!std::exchange(said, true)) logging::write(logging::Level::warning, logging::Channel::ui, + std::string("Native menu: the page's focus could not be read (") + e.what() + "); it stays up under the game's side menu."); + } catch (...) {} + } // (Said at once when another page takes its place, not left to go stale.) page_shown.store(shown_tab == our_key ? 0 : 1, std::memory_order_relaxed); page_seen.store(on_screen ? now : 0, std::memory_order_relaxed); diff --git a/Extension/UI/Overlay/hub_page.cpp b/Extension/UI/Overlay/hub_page.cpp index 29106b1..fab624e 100644 --- a/Extension/UI/Overlay/hub_page.cpp +++ b/Extension/UI/Overlay/hub_page.cpp @@ -1345,6 +1345,11 @@ bool hub_page_pending() { set_typing(h, 0); wipe(h.password), wipe(h.code_password), wipe(h.host_password); h.notice.clear(); + } else { + // What is held as the page comes up is not a press on it: the Back that closed the + // game's side menu over it would dismiss the page as well. + h.keys_before.fill(true); + h.pad_before = ~0U; } } // With the overlay's own menu or console open the page stays where it is, behind them, and diff --git a/Launcher/gui_launcher.cpp b/Launcher/gui_launcher.cpp index 4719952..dbb924a 100644 --- a/Launcher/gui_launcher.cpp +++ b/Launcher/gui_launcher.cpp @@ -470,6 +470,9 @@ void Launcher::run_play() { if (std::find(arguments_.begin(), arguments_.end(), L"-offline") != arguments_.end() || std::find(arguments_.begin(), arguments_.end(), L"--offline") != arguments_.end()) options.offline = true; + // And a map to start on, over one in the saved launch options. + if (auto arguments = arguments_; true) + if (auto map = launcher::take_map_option(arguments); !map.empty()) options.map = std::move(map); launched_ = false; launcher_app::start_game(session_, options, [this](DWORD id) { game_ = id; }); launched_ = true; diff --git a/Launcher/launch.cpp b/Launcher/launch.cpp index 8e84d48..b7c83ca 100644 --- a/Launcher/launch.cpp +++ b/Launcher/launch.cpp @@ -711,6 +711,15 @@ DWORD start_game(const Session& session, const launcher::LaunchOptions& options, command += quote_argument(argument); } set_environment(L"RESKATE_GPU_DIAGNOSTICS", options.gpu_diagnostics ? L"1" : L"0"); + // -map: the game loads it once it is up, as the console's "load" would (client_tick.cpp), + // ahead of any startup commands already asked for. + if (!options.map.empty()) { + std::wstring commands = L"load " + options.map; + std::wstring already(4096, L'\0'); + const auto length = GetEnvironmentVariableW(L"RESKATE_STARTUP_COMMANDS", already.data(), 4096); + if (length && length < 4096) commands += L";" + already.substr(0, length); + set_environment(L"RESKATE_STARTUP_COMMANDS", commands.c_str()); + } set_environment(L"RESKATE_DISCORD", options.discord ? L"1" : L"0"); if (options.menu_key == options.console_key) throw std::runtime_error("The menu and console need different keys. Change one in Settings."); diff --git a/Launcher/main.cpp b/Launcher/main.cpp index 0ce8acd..b766ca9 100644 --- a/Launcher/main.cpp +++ b/Launcher/main.cpp @@ -72,8 +72,11 @@ void run_headless(const app::Session& session, std::vector argumen const bool relaunched = take_flag(arguments, L"--reskate-updated"); const bool updates = !take_flag(arguments, L"--no-update") && dingosdk::launcher_gui::updates_enabled(session); // With no options of its own, the launch is the one the launcher window's Play would make. - const auto options = arguments.empty() ? dingosdk::launcher_gui::saved_launch_options(session) - : dingosdk::launcher::parse_launch_options(arguments); + // A map to start on is not one of those: "--no-gui -map " is that launch, on that map. + auto map = dingosdk::launcher::take_map_option(arguments); + auto options = arguments.empty() ? dingosdk::launcher_gui::saved_launch_options(session) + : dingosdk::launcher::parse_launch_options(arguments); + if (!map.empty()) options.map = std::move(map); update::remove_previous_launcher(session.self); if (updates) { const auto config = update::fetch_config(); diff --git a/README.md b/README.md index c417b84..fd4c5d0 100644 --- a/README.md +++ b/README.md @@ -215,6 +215,7 @@ Useful launcher flags: | `--log-trace` | the same as `--log-level=trace`; an explicit `--log-level` wins | | `-wconsole` | also show the log live in a console window while the game runs | | `--menu-key=0x2D`, `--console-key=0xC0` | menu and console keys (virtual-key codes) | +| `-map ` | load that map as soon as the game is up, e.g. `-map "San Vansterdam"`; the name is a map's name, short name or asset path, or the start of one | | `--no-loose-files` | ignore loose Lua and config files beside the game | | `--gpu-diagnostics` | record extra detail when the graphics driver crashes (DRED) | diff --git a/Runtime/bootstrap.cpp b/Runtime/bootstrap.cpp index b62b5a9..1fe558a 100644 --- a/Runtime/bootstrap.cpp +++ b/Runtime/bootstrap.cpp @@ -52,7 +52,7 @@ bool initialize_bootstrap(std::uintptr_t base) { error.clear(); ready = start_native_render_resource_pool(base, error); 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; + 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_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); diff --git a/Runtime/client_tick.cpp b/Runtime/client_tick.cpp index d02d180..09ca0c9 100644 --- a/Runtime/client_tick.cpp +++ b/Runtime/client_tick.cpp @@ -35,6 +35,7 @@ #include "Extension/World/level_loading.h" #include "Extension/World/loading_screen.h" #include +#include #include #include #include @@ -294,7 +295,8 @@ void update_model(std::uintptr_t client, TickState& frame) { r.next_named_settings_publish = settings_now + 250; publish_named_settings(r); } - // RESKATE_STARTUP_COMMANDS="load skate1map;noclip 1" (development): console commands + // RESKATE_STARTUP_COMMANDS="load skate1map;noclip 1" (development, and the launcher's + // -map , which is a "load" at the front of it): console commands // queued in order once the world is up and can accept them, so a build can be exercised // without anyone driving the overlay. A load waits for its destination to be listed. { @@ -1186,6 +1188,32 @@ void tick(std::uintptr_t client, std::uintptr_t update) { dingosdk::map_download::tick(); } dingosdk::multiplayer::refresh_identity_lists(); + // How much memory the game holds, beside what a session adds to it: said when it has + // moved by a quarter of a gigabyte or the players or skaters have changed much, at most + // every ten seconds. For telling what a full server's memory goes on (the map, every + // player, or only the skaters that are built). + if (static std::uint64_t next_memory{}; GetTickCount64() >= next_memory) { + next_memory = GetTickCount64() + 10000; + PROCESS_MEMORY_COUNTERS_EX counters{}; + counters.cb = sizeof(counters); + if (GetProcessMemoryInfo(GetCurrentProcess(), reinterpret_cast(&counters), sizeof(counters))) { + const auto mp = dingosdk::multiplayer::model(); + const bool session = mp.active && !mp.echo && (mp.hosting || mp.connected); + const int players = session ? static_cast(mp.roster.size()) : 0; + const int skaters = session ? static_cast(std::ranges::count_if(mp.roster, [](const auto& player) { return player.visible; })) : 0; + static std::uint64_t said_private{}; + static int said_players = -1, said_skaters = -1; + const auto moved = counters.PrivateUsage > said_private ? counters.PrivateUsage - said_private : said_private - counters.PrivateUsage; + if (moved >= (256ull << 20) || std::abs(players - said_players) >= 5 || std::abs(skaters - said_skaters) >= 5) { + said_private = counters.PrivateUsage; said_players = players; said_skaters = skaters; + const auto gigabytes = [](std::uint64_t bytes) { return static_cast(bytes) / (1024.0 * 1024.0 * 1024.0); }; + dingosdk::logging::printf(dingosdk::logging::Level::info, dingosdk::logging::Channel::runtime, + "Memory: %.2f GB committed, %.2f GB in RAM; %d player(s) in the session, %d built as skaters (player distance %.0f m); map %s.", + gigabytes(counters.PrivateUsage), gigabytes(counters.WorkingSetSize), players, skaters, mp.player_distance, + (session && !mp.map.empty() ? mp.map : r.multiplayer_map).c_str()); + } + } + } // The player's Discord status (discord_presence.h): where they skate and with whom, // looked at every couple of seconds. if (static std::uint64_t next_presence{}; dingosdk::discord_presence::available() && GetTickCount64() >= next_presence) { diff --git a/Runtime/runtime_init.cpp b/Runtime/runtime_init.cpp index a1aad9c..1250f56 100644 --- a/Runtime/runtime_init.cpp +++ b/Runtime/runtime_init.cpp @@ -5,6 +5,8 @@ #include "Engine/Core/Log/logging.h" #include "Engine/Core/Profiling/profiler.h" #include "Extension/Settings/engine_tweaks.h" +#include "Extension/Settings/replay_guard.h" +#include "Extension/Console/commands.h" #include "Extension/UI/ui_pointer_skip.h" #include "Engine/Game/Build/addresses.h" #include "Engine/Game/Build/20260929/profiler_labels.h" @@ -310,6 +312,8 @@ extern "C" __declspec(dllexport) BOOL WINAPI DingoSDKDebugInitialize() { start_frame_timing(); (void)dingosdk::ui_pointer::install(r.base); (void)dingosdk::engine_tweaks::install(r.base); + (void)dingosdk::replay_guard::install(r.base); + dingosdk::console::start_memory_trace_from_environment(); const bool fixed_stop_provider = (global_offline_hook && progression_guard_armed) && dingosdk::prepare_fixed_stop_entitlement_provider(r.base); record("{\"event\":\"fixed_stop_entitlement_provider_initialized\",\"prepared\":" + diff --git a/cmake/Runtime.cmake b/cmake/Runtime.cmake index ae49022..16ab5d9 100644 --- a/cmake/Runtime.cmake +++ b/cmake/Runtime.cmake @@ -69,6 +69,7 @@ add_library(dingosdk_runtime SHARED Extension/Settings/console_commands.cpp Extension/Settings/job_spin.cpp Extension/Settings/engine_tweaks.cpp + Extension/Settings/replay_guard.cpp Extension/UI/ui_pointer_skip.cpp Extension/Settings/named_settings.cpp Extension/World/console_commands.cpp