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