From a83cb83bb4e814308a4b4f010f4538d03231407b Mon Sep 17 00:00:00 2001 From: AyeZeeBB Date: Sun, 11 Oct 2026 19:26:23 -0500 Subject: [PATCH] Raise the streamed texture limit to 32764, and warn when it is close The game can stream 20480 textures at once. When a map and its mods need one more, it takes a slot past the end of its table and waits for ever on a byte that is part of a counter: a hang, then a crash in the hang's own report (crash reports of 2026-10-11). The limit is raised to 32764, the most a texture's 16-bit handle can index, before the game makes its texture streaming manager: - The manager is allocated longer (6.0 MB, was 2.3 MB) and its slot table and per-slot floats moved behind its end. Each of the 226 instructions that hold one of those offsets or sizes gets another number; no code is added. Its four fixed lists are pointed at longer storage once the constructor has run. - The renderer keeps a block of floats per slot for each of four layers. Its object grows to match (27 instructions), and the two places that index a layer's block as layer * 20480 become a multiply by the new count, in the same twelve bytes. - Three functions keep 20480 entries on their own stack. Two list filters go through a longer list a stretch at a time, and the sort is given scratch room of its own past that many. Up to 20480 they run as the game wrote them. Every instruction is checked byte for byte first, and nothing is changed if any differs or the manager already exists. An empty texture_limit_stock.txt beside the game leaves the limit as it is. Players are told when they come close: a notice at 90% of the limit in force and an error at 100%, each once until use falls below 80%. The log records each new high in steps of 5%. Also: when a function of the game finds its stack written over, the game ends itself with exit code 0xc0000409 and no crash report. The log now names that function and its callers first. The site table (texture_streaming_sites.inc) is generated from a sweep of the supported build; texture_streaming.h describes the layout. Tested in game below 20480 textures only. Nothing has run above the old limit, where the three stack guards and the longer lists first matter. At 32764 the game still hangs; the notice is the protection. --- .../Game/Build/20260929/texture_streaming.h | 162 +++++++++++ .../20260929/texture_streaming_sites.inc | 259 +++++++++++++++++ Extension/World/stack_check_report.cpp | 62 +++++ Extension/World/stack_check_report.h | 10 + Extension/World/texture_handle_limit.cpp | 262 ++++++++++++++++++ Extension/World/texture_handle_limit.h | 22 ++ Runtime/client_tick.cpp | 2 + Runtime/runtime_init.cpp | 4 + cmake/Runtime.cmake | 2 + 9 files changed, 785 insertions(+) create mode 100644 Engine/Game/Build/20260929/texture_streaming.h create mode 100644 Engine/Game/Build/20260929/texture_streaming_sites.inc create mode 100644 Extension/World/stack_check_report.cpp create mode 100644 Extension/World/stack_check_report.h create mode 100644 Extension/World/texture_handle_limit.cpp create mode 100644 Extension/World/texture_handle_limit.h diff --git a/Engine/Game/Build/20260929/texture_streaming.h b/Engine/Game/Build/20260929/texture_streaming.h new file mode 100644 index 0000000..40c8000 --- /dev/null +++ b/Engine/Game/Build/20260929/texture_streaming.h @@ -0,0 +1,162 @@ +#pragma once +#include "Engine/Game/Build/fingerprint.h" +#include +#include + +// The game's TextureStreamingManager and its limit of streamed textures +// (Extension/World/texture_handle_limit.cpp). +// +// Every streamed texture holds a 16-bit handle: an index into the manager's table of slots +// (0x58 bytes each), 0xffff for none, and the code masks the index with 0x7fff. The table and +// what goes with it are sized for fb::MaxStreamingTextureCount, 0x5000 (20480), all inside the +// manager itself (one object of 0x24e780 bytes, FUN_143efd770 allocates it and keeps it at +// `manager`): +// +0x480 a list of pointers, its 0x5000 entries at +0x498 +// +0x28498 the slots +// +0x1e0498 how many slots are taken from the bottom (one kind of texture), +0x1e049c where +// those taken from the top begin (the other kind; 0x5000 when none), and a hint +// for each at +0x1e04a0 and +0x1e04a4 +// +0x1e0500 a float for each slot +// +0x1f4500 two lists of pointers, 0x5000 entries each: headers at +0x1f4500 and +0x21c518, +// entries behind each header +// +0x244530 a sorted list of handles, its 0x5000 entries at +0x244548 +// The lists are {begin, end, capacity end}; only the constructor names where their entries +// are. When the slots from the bottom and from the top meet, FUN_143e72660 writes a message +// ("Exceeded max streaming texture handle count") nobody reads and takes the next slot all the +// same: one past the table, which is the counter at +0x1e0498, whose low byte it then waits on +// as a lock for ever (a stall and then a crash in the stall's own report, 2026-10-11). +// +// The limit is raised by giving the manager a longer allocation and moving the slots, the +// floats and the lists' entries behind its end: every instruction that holds one of those +// offsets or sizes is in texture_streaming_sites.inc (a sweep of the build's code, checked two +// ways; nothing outside 0x3e5b350..0x3e77518 and the allocation holds one), and each is the +// same instruction with another number. The lists are pointed at their new entries once the +// constructor has run. The counters, the hints and everything else stay where they are. +// +// The renderer keeps floats of its own for every slot: an object for each view (0x500c0 bytes, +// made at 0x3f3d83a) with a block of 0x5000 floats for each of four layers from +0x40, a +// {pointer, count} for each block from +0x50040, and a pointer at +0x50080. It fills each +// block for the manager's count of slots (FUN_143e6c8e0 hands the view the manager's floats and +// that count), so the blocks have to grow with it: raising the manager alone ran the fill over +// the pointer behind the blocks (a crash at 0x3f31d2c on the first frame, 2026-10-11). Three +// functions go into the blocks: FUN_143f31aa0 (the fill) and FUN_143f319c0 (the merge into the +// manager's floats), both by layer * 0x5000 + 0x10 floats, written as lea [r + r*4], shl 12, +// add 0x10; and FUN_143f56560 (the jobs that write them), by the {pointer, count} of its +// layer, found as (layer + 0x5004) * 16. Nothing else in the code has that arithmetic. The +// blocks become raised_count floats each, the fields behind them move up, and the two +// lea/shl/add become an imul and the same add (renderer_rewrites). +namespace dingosdk::game::build::v20260929::texture_streaming { +inline constexpr std::uintptr_t manager = 0x76e92e0; // TextureStreamingManager*, null until made +inline constexpr Fingerprint constructor{0x3e5b350, { + 0x48,0x89,0x5c,0x24,0x18,0x55,0x56,0x57,0x41,0x54,0x41,0x55,0x41,0x56,0x41,0x57, + 0x48,0x8d,0xac,0x24,0x20,0xff,0xff,0xff,0x48,0x81,0xec,0xe0,0x01,0x00,0x00,0x48}}; +// __report_gsfailure: mov ecx, 2; int 0x29; ret. Every function's stack check jumps to it +// (FUN_14546ba30) when its cookie is not what it left (Extension/World/stack_check_report.cpp). +inline constexpr std::uintptr_t stack_check_failed = 0x546be9c; +inline constexpr std::array stack_check_failed_bytes{0xb9, 0x02, 0x00, 0x00, 0x00, 0xcd, 0x29, 0xc3}; +inline constexpr std::uint32_t stock_count = 0x5000; +// The most the 15-bit index allows, a multiple of four (the floats are reset sixteen bytes at +// a time) and short of 0x7fff, which with the index's high bit set would read as no handle. +inline constexpr std::uint32_t raised_count = 0x7ffc; +inline constexpr std::uint32_t slot_size = 0x58; +inline constexpr std::uint32_t taken_from_bottom = 0x1e0498, taken_from_top = 0x1e049c; + +inline constexpr std::uint32_t stock_size = 0x24e780; +inline constexpr std::uint32_t stock_slots = 0x28498, stock_floats = 0x1e0500; +// Behind the stock object, in this order. +inline constexpr std::uint32_t raised_slots = stock_size; +inline constexpr std::uint32_t raised_slot_bytes = raised_count * slot_size; +inline constexpr std::uint32_t raised_slots_end = raised_slots + raised_slot_bytes; +inline constexpr std::uint32_t raised_floats = raised_slots_end; +inline constexpr std::uint32_t raised_float_bytes = raised_count * 4; +struct List { + std::uint32_t header, stock_entries, raised_entries, entry_size; +}; +inline constexpr std::uint32_t lists_begin = raised_floats + raised_float_bytes; +inline constexpr List lists[]{ + {0x480, 0x498, lists_begin, 8}, + {0x1f4500, 0x1f4518, lists_begin + raised_count * 8, 8}, + {0x21c518, 0x21c530, lists_begin + raised_count * 16, 8}, + {0x244530, 0x244548, lists_begin + raised_count * 24, 2}, +}; +inline constexpr std::uint32_t raised_size = (lists_begin + raised_count * 26 + 0x7f) & ~0x7fu; +static_assert(raised_slots % 8 == 0 && raised_floats % 4 == 0 && lists_begin % 8 == 0 && raised_float_bytes % 16 == 0); + +// The renderer's object: four blocks of floats from +0x40, then its fields. +inline constexpr std::uint32_t renderer_blocks = 0x40; +inline constexpr std::uint32_t renderer_stock_fields = renderer_blocks + 4 * stock_count * 4; // 0x50040 +inline constexpr std::uint32_t renderer_raised_fields = renderer_blocks + 4 * raised_count * 4; // 0x80000 +static_assert(renderer_stock_fields == 0x50040 && renderer_raised_fields % 16 == 0); +// layer * count + 0x10, in floats: twelve bytes either way. +struct Rewrite { + std::uint32_t rva; + std::uint8_t stock[12], raised[12]; +}; +inline constexpr Rewrite renderer_rewrites[]{ + // lea r8, [r10 + r10*4]; shl r8, 12; add r8, 0x10 -> imul r8, r10, raised_count; add r8, 0x10; nop + {0x3f31a21, {0x4f, 0x8d, 0x04, 0x92, 0x49, 0xc1, 0xe0, 0x0c, 0x49, 0x83, 0xc0, 0x10}, + {0x4d, 0x69, 0xc2, raised_count & 0xff, raised_count >> 8, 0, 0, 0x49, 0x83, 0xc0, 0x10, 0x90}}, + // lea rdx, [rax + rax*4]; shl rdx, 12; add rdx, 0x10 -> imul rdx, rax, raised_count; add rdx, 0x10; nop + {0x3f31cfa, {0x48, 0x8d, 0x14, 0x80, 0x48, 0xc1, 0xe2, 0x0c, 0x48, 0x83, 0xc2, 0x10}, + {0x48, 0x69, 0xd0, raised_count & 0xff, raised_count >> 8, 0, 0, 0x48, 0x83, 0xc2, 0x10, 0x90}}, +}; + +// Three functions of the manager keep stock_count entries on their own stack, checked or not: +// - FUN_143e585a0 and FUN_143e58770 (manager, list*): each goes through one of the two lists of +// pointers, keeps the entries still to be done in a stack array and writes them back. Past +// the array they write to null (0xdeadc0de). +// - FUN_143e58950 (begin, end, scratch): a radix sort of the entries of the list at +0x480 +// (eight bytes each, a float and a handle), called only by FUN_143e753c0 with a stack array as +// its scratch room, unchecked. +inline constexpr Fingerprint filter_loading{0x3e585a0, { + 0x48,0x89,0x5c,0x24,0x18,0x48,0x89,0x6c,0x24,0x20,0x56,0x57,0x41,0x56,0xb8,0x40, + 0x80,0x02,0x00,0xe8,0xa8,0x34,0x61,0x01,0x48,0x2b,0xe0,0x48,0x8b,0x05,0xfe,0xbd}}; +inline constexpr Fingerprint filter_waiting{0x3e58770, { + 0x40,0x57,0xb8,0x50,0x80,0x02,0x00,0xe8,0xe4,0x32,0x61,0x01,0x48,0x2b,0xe0,0x48, + 0x8b,0x05,0x3a,0xbc,0x36,0x03,0x48,0x33,0xc4,0x48,0x89,0x84,0x24,0x38,0x80,0x02}}; +inline constexpr Fingerprint sort_wanted{0x3e58950, { + 0x4c,0x8b,0xdc,0x53,0x56,0x41,0x54,0x48,0x81,0xec,0x40,0x08,0x00,0x00,0x48,0x8b, + 0xc2,0x49,0x8b,0xf0,0x48,0x2b,0xc1,0x48,0x8b,0xda,0x48,0x83,0xe0,0xf8,0x4c,0x8b}}; + +// What the 32-bit value of a site is, and so what it becomes. +enum class Value : std::uint8_t { + slot, // an offset into the first slot: moved with the slots + slot_bytes, // the size of all the slots + slot_end, // the end of the slots (the destructor walks them backwards from it) + count, // the count of slots + floats, // the floats' offset + float_bytes, // the size of all the floats + alloc, // the manager's size, where it is allocated and where it is freed + r_alloc, // the renderer's object: its size + r_fill, // the floats of its four blocks together + r_block, // where its second, third and fourth block begin + r_field, // a field behind its blocks + r_spans, // those fields' offset in sixteens (the {pointer, count} of a layer) +}; +struct Site { + std::uint32_t rva; + std::uint8_t size, at; + Value value; + std::uint8_t bytes[15]; +}; +inline constexpr Site sites[]{ +#include "texture_streaming_sites.inc" +}; +constexpr std::uint32_t raised(Value value, std::uint32_t stock) { + switch (value) { + case Value::slot: return stock - stock_slots + raised_slots; + case Value::slot_bytes: return raised_slot_bytes; + case Value::slot_end: return raised_slots_end; + case Value::count: return raised_count; + case Value::floats: return stock - stock_floats + raised_floats; + case Value::float_bytes: return raised_float_bytes; + case Value::alloc: return raised_size; + case Value::r_alloc: return stock - renderer_stock_fields + renderer_raised_fields; + case Value::r_fill: return raised_count * 4; + case Value::r_block: return renderer_blocks + (stock - renderer_blocks) / (stock_count * 4) * (raised_count * 4); + case Value::r_field: return stock - renderer_stock_fields + renderer_raised_fields; + case Value::r_spans: return renderer_raised_fields / 16; + } + return stock; +} +} diff --git a/Engine/Game/Build/20260929/texture_streaming_sites.inc b/Engine/Game/Build/20260929/texture_streaming_sites.inc new file mode 100644 index 0000000..a553b1f --- /dev/null +++ b/Engine/Game/Build/20260929/texture_streaming_sites.inc @@ -0,0 +1,259 @@ +// Generated by a sweep of the supported build (see texture_streaming.h): do not edit by hand. +// {rva, length, offset of the 32-bit value, what the value is, the instruction's bytes} +{0x3e5b44a, 5, 1, Value::count, {0xb9,0x00,0x50,0x00,0x00}}, // mov ecx, 0x5000 +{0x3e5b46c, 7, 3, Value::slot, {0x48,0x8d,0x86,0x98,0x84,0x02,0x00}}, // lea rax, [rsi + 0x28498] +{0x3e5b49b, 7, 3, Value::floats, {0x48,0x8d,0x86,0x00,0x05,0x1e,0x00}}, // lea rax, [rsi + 0x1e0500] +{0x3e5b98f, 7, 3, Value::slot, {0x48,0x8d,0x8e,0x98,0x84,0x02,0x00}}, // lea rcx, [rsi + 0x28498] +{0x3e5b996, 6, 2, Value::slot_bytes, {0x41,0xb8,0x00,0x80,0x1b,0x00}}, // mov r8d, 0x1b8000 +{0x3e5d2f0, 5, 1, Value::count, {0xbf,0x00,0x50,0x00,0x00}}, // mov edi, 0x5000 +{0x3e5d2f5, 7, 3, Value::slot_end, {0x48,0x8d,0xb3,0x98,0x04,0x1e,0x00}}, // lea rsi, [rbx + 0x1e0498] +{0x3e5f2e9, 5, 1, Value::alloc, {0xba,0x80,0xe7,0x24,0x00}}, // mov edx, 0x24e780 +{0x3e620fb, 8, 3, Value::slot, {0x80,0xbc,0x30,0xa7,0x84,0x02,0x00,0x00}}, // cmp byte ptr [rax + rsi + 0x284a7], 0 +{0x3e62105, 8, 4, Value::slot, {0x48,0x8b,0x84,0x30,0xb0,0x84,0x02,0x00}}, // mov rax, qword ptr [rax + rsi + 0x284b0] +{0x3e6247f, 8, 3, Value::slot, {0x80,0xbc,0x31,0x9c,0x84,0x02,0x00,0x00}}, // cmp byte ptr [rcx + rsi + 0x2849c], 0 +{0x3e627de, 6, 2, Value::count, {0x81,0xfa,0x00,0x50,0x00,0x00}}, // cmp edx, 0x5000 +{0x3e627f6, 8, 4, Value::slot, {0x44,0x39,0x84,0x19,0xdc,0x84,0x02,0x00}}, // cmp dword ptr [rcx + rbx + 0x284dc], r8d +{0x3e62800, 9, 4, Value::slot, {0x48,0x83,0xbc,0x19,0xa8,0x84,0x02,0x00,0x00}}, // cmp qword ptr [rcx + rbx + 0x284a8], 0 +{0x3e62811, 6, 2, Value::count, {0x81,0xfa,0x00,0x50,0x00,0x00}}, // cmp edx, 0x5000 +{0x3e631ff, 8, 3, Value::slot, {0x80,0x8c,0x01,0xa7,0x84,0x02,0x00,0x01}}, // or byte ptr [rcx + rax + 0x284a7], 1 +{0x3e63246, 8, 4, Value::slot, {0x41,0x0f,0xb7,0x91,0xe4,0x84,0x02,0x00}}, // movzx edx, word ptr [r9 + 0x284e4] +{0x3e63286, 8, 4, Value::slot, {0x41,0x0f,0xb7,0x91,0xe6,0x84,0x02,0x00}}, // movzx edx, word ptr [r9 + 0x284e6] +{0x3e632d5, 9, 5, Value::slot, {0x43,0x0f,0xb7,0x94,0x3b,0xe4,0x84,0x02,0x00}}, // movzx edx, word ptr [r11 + r15 + 0x284e4] +{0x3e6331c, 9, 5, Value::slot, {0x43,0x0f,0xb7,0x94,0x3b,0xe6,0x84,0x02,0x00}}, // movzx edx, word ptr [r11 + r15 + 0x284e6] +{0x3e6336f, 9, 5, Value::slot, {0x43,0x0f,0xb6,0x94,0x39,0x99,0x84,0x02,0x00}}, // movzx edx, byte ptr [r9 + r15 + 0x28499] +{0x3e6337f, 8, 4, Value::slot, {0x43,0x2a,0x8c,0x39,0x9a,0x84,0x02,0x00}}, // sub cl, byte ptr [r9 + r15 + 0x2849a] +{0x3e6339e, 9, 5, Value::slot, {0x43,0x0f,0xb6,0x84,0x39,0x99,0x84,0x02,0x00}}, // movzx eax, byte ptr [r9 + r15 + 0x28499] +{0x3e633a7, 8, 4, Value::slot, {0x4b,0x8b,0xb4,0x39,0xa8,0x84,0x02,0x00}}, // mov rsi, qword ptr [r9 + r15 + 0x284a8] +{0x3e633d4, 8, 4, Value::slot, {0x42,0x86,0x84,0x3b,0x98,0x84,0x02,0x00}}, // xchg byte ptr [rbx + r15 + 0x28498], al +{0x3e633e7, 8, 4, Value::slot, {0x42,0x86,0x84,0x3b,0x98,0x84,0x02,0x00}}, // xchg byte ptr [rbx + r15 + 0x28498], al +{0x3e633f3, 8, 4, Value::slot, {0x4a,0x39,0xb4,0x3b,0xa8,0x84,0x02,0x00}}, // cmp qword ptr [rbx + r15 + 0x284a8], rsi +{0x3e633fd, 9, 5, Value::slot, {0x42,0x0f,0xb6,0x94,0x3b,0x9a,0x84,0x02,0x00}}, // movzx edx, byte ptr [rbx + r15 + 0x2849a] +{0x3e63406, 7, 3, Value::slot, {0x49,0x8d,0x8f,0xb0,0x84,0x02,0x00}}, // lea rcx, [r15 + 0x284b0] +{0x3e6341f, 9, 4, Value::slot, {0x42,0xc6,0x84,0x3b,0x98,0x84,0x02,0x00,0x00}}, // mov byte ptr [rbx + r15 + 0x28498], 0 +{0x3e6779b, 7, 3, Value::slot, {0x49,0x8b,0x8e,0xa8,0x84,0x02,0x00}}, // mov rcx, qword ptr [r14 + 0x284a8] +{0x3e677ab, 7, 3, Value::slot, {0x49,0x8b,0x96,0xa8,0x84,0x02,0x00}}, // mov rdx, qword ptr [r14 + 0x284a8] +{0x3e677b5, 8, 4, Value::slot, {0x45,0x0f,0xb6,0xbe,0x99,0x84,0x02,0x00}}, // movzx r15d, byte ptr [r14 + 0x28499] +{0x3e6780c, 7, 3, Value::slot, {0x49,0x8d,0x96,0xdc,0x84,0x02,0x00}}, // lea rdx, [r14 + 0x284dc] +{0x3e67837, 7, 3, Value::slot, {0x4d,0x8b,0x8e,0xa8,0x84,0x02,0x00}}, // mov r9, qword ptr [r14 + 0x284a8] +{0x3e67858, 8, 4, Value::slot, {0x45,0x0f,0xb6,0x86,0x99,0x84,0x02,0x00}}, // movzx r8d, byte ptr [r14 + 0x28499] +{0x3e67860, 7, 3, Value::slot, {0x49,0x8b,0x96,0xa8,0x84,0x02,0x00}}, // mov rdx, qword ptr [r14 + 0x284a8] +{0x3e678c7, 8, 4, Value::slot, {0x41,0x0f,0xb6,0x86,0x9c,0x84,0x02,0x00}}, // movzx eax, byte ptr [r14 + 0x2849c] +{0x3e68f46, 8, 4, Value::slot, {0x4c,0x39,0xa4,0x30,0xa8,0x84,0x02,0x00}}, // cmp qword ptr [rax + rsi + 0x284a8], r12 +{0x3e68f83, 7, 3, Value::slot, {0x86,0x84,0x37,0x98,0x84,0x02,0x00}}, // xchg byte ptr [rdi + rsi + 0x28498], al +{0x3e68f95, 7, 3, Value::slot, {0x86,0x84,0x37,0x98,0x84,0x02,0x00}}, // xchg byte ptr [rdi + rsi + 0x28498], al +{0x3e68fa4, 9, 5, Value::slot, {0xc5,0xfc,0x11,0x84,0x37,0x99,0x84,0x02,0x00}}, // vmovups ymmword ptr [rdi + rsi + 0x28499], ymm0 +{0x3e68fad, 9, 5, Value::slot, {0xc5,0xfc,0x11,0x84,0x37,0xb9,0x84,0x02,0x00}}, // vmovups ymmword ptr [rdi + rsi + 0x284b9], ymm0 +{0x3e68fb6, 9, 5, Value::slot, {0xc5,0xfc,0x11,0x84,0x37,0xd0,0x84,0x02,0x00}}, // vmovups ymmword ptr [rdi + rsi + 0x284d0], ymm0 +{0x3e68fbf, 10, 4, Value::slot, {0x66,0xc7,0x84,0x37,0xe8,0x84,0x02,0x00,0xff,0xff}}, // mov word ptr [rdi + rsi + 0x284e8], 0xffff +{0x3e68fcf, 7, 3, Value::slot, {0x89,0x84,0x37,0xdc,0x84,0x02,0x00}}, // mov dword ptr [rdi + rsi + 0x284dc], eax +{0x3e68fdb, 8, 4, Value::slot, {0x4c,0x89,0xb4,0x37,0xa8,0x84,0x02,0x00}}, // mov qword ptr [rdi + rsi + 0x284a8], r14 +{0x3e68ff6, 7, 3, Value::slot, {0x88,0x84,0x37,0x9a,0x84,0x02,0x00}}, // mov byte ptr [rdi + rsi + 0x2849a], al +{0x3e69001, 7, 3, Value::slot, {0x88,0x84,0x37,0x9b,0x84,0x02,0x00}}, // mov byte ptr [rdi + rsi + 0x2849b], al +{0x3e69014, 7, 3, Value::slot, {0x88,0x84,0x37,0x99,0x84,0x02,0x00}}, // mov byte ptr [rdi + rsi + 0x28499], al +{0x3e69027, 7, 3, Value::slot, {0x88,0x84,0x37,0x9d,0x84,0x02,0x00}}, // mov byte ptr [rdi + rsi + 0x2849d], al +{0x3e6903a, 7, 3, Value::slot, {0x88,0x84,0x37,0x9e,0x84,0x02,0x00}}, // mov byte ptr [rdi + rsi + 0x2849e], al +{0x3e6904d, 8, 3, Value::slot, {0x80,0xa4,0x37,0xa7,0x84,0x02,0x00,0xfe}}, // and byte ptr [rdi + rsi + 0x284a7], 0xfe +{0x3e69055, 8, 3, Value::slot, {0x80,0x8c,0x37,0xa7,0x84,0x02,0x00,0x90}}, // or byte ptr [rdi + rsi + 0x284a7], 0x90 +{0x3e6905d, 7, 3, Value::slot, {0x88,0x84,0x37,0x9f,0x84,0x02,0x00}}, // mov byte ptr [rdi + rsi + 0x2849f], al +{0x3e6906e, 8, 4, Value::slot, {0x48,0x89,0x84,0x37,0xb0,0x84,0x02,0x00}}, // mov qword ptr [rdi + rsi + 0x284b0], rax +{0x3e69077, 8, 4, Value::slot, {0x44,0x88,0xa4,0x37,0x98,0x84,0x02,0x00}}, // mov byte ptr [rdi + rsi + 0x28498], r12b +{0x3e69474, 8, 4, Value::slot, {0x46,0x39,0xb4,0x3e,0xdc,0x84,0x02,0x00}}, // cmp dword ptr [rsi + r15 + 0x284dc], r14d +{0x3e6948f, 9, 5, Value::slot, {0x42,0x0f,0xb6,0x84,0x3e,0xa7,0x84,0x02,0x00}}, // movzx eax, byte ptr [rsi + r15 + 0x284a7] +{0x3e69563, 7, 3, Value::slot, {0x49,0x8d,0xbf,0x98,0x84,0x02,0x00}}, // lea rdi, [r15 + 0x28498] +{0x3e6956d, 7, 3, Value::slot_bytes, {0x48,0x8d,0xb7,0x00,0x80,0x1b,0x00}}, // lea rsi, [rdi + 0x1b8000] +{0x3e69f3f, 8, 3, Value::slot, {0x80,0xa4,0x01,0xa7,0x84,0x02,0x00,0xfe}}, // and byte ptr [rcx + rax + 0x284a7], 0xfe +{0x3e69f47, 12, 4, Value::slot, {0x48,0xc7,0x84,0x01,0xc8,0x84,0x02,0x00,0x00,0x00,0x00,0x00}}, // mov qword ptr [rcx + rax + 0x284c8], 0 +{0x3e69fa5, 7, 3, Value::slot, {0x48,0x8d,0x96,0xdc,0x84,0x02,0x00}}, // lea rdx, [rsi + 0x284dc] +{0x3e69ffc, 7, 3, Value::slot, {0x48,0x89,0x86,0xb8,0x84,0x02,0x00}}, // mov qword ptr [rsi + 0x284b8], rax +{0x3e6a00d, 7, 3, Value::slot, {0x0f,0xb6,0x86,0x99,0x84,0x02,0x00}}, // movzx eax, byte ptr [rsi + 0x28499] +{0x3e6a016, 6, 2, Value::slot, {0x88,0x8e,0x9c,0x84,0x02,0x00}}, // mov byte ptr [rsi + 0x2849c], cl +{0x3e6a01c, 6, 2, Value::slot, {0x88,0x86,0x9d,0x84,0x02,0x00}}, // mov byte ptr [rsi + 0x2849d], al +{0x3e6a022, 6, 2, Value::slot, {0x89,0x9e,0xa0,0x84,0x02,0x00}}, // mov dword ptr [rsi + 0x284a0], ebx +{0x3e6a048, 7, 3, Value::slot, {0x48,0x8b,0x96,0xa8,0x84,0x02,0x00}}, // mov rdx, qword ptr [rsi + 0x284a8] +{0x3e6a076, 7, 3, Value::slot, {0x0f,0xb6,0x86,0xa7,0x84,0x02,0x00}}, // movzx eax, byte ptr [rsi + 0x284a7] +{0x3e6a0ce, 8, 4, Value::slot, {0x0f,0xb6,0x84,0x29,0x99,0x84,0x02,0x00}}, // movzx eax, byte ptr [rcx + rbp + 0x28499] +{0x3e6a0d6, 8, 4, Value::slot, {0x4c,0x8b,0xa4,0x29,0xa8,0x84,0x02,0x00}}, // mov r12, qword ptr [rcx + rbp + 0x284a8] +{0x3e6a108, 7, 3, Value::slot, {0x86,0x84,0x2b,0x98,0x84,0x02,0x00}}, // xchg byte ptr [rbx + rbp + 0x28498], al +{0x3e6a127, 7, 3, Value::slot, {0x86,0x84,0x2b,0x98,0x84,0x02,0x00}}, // xchg byte ptr [rbx + rbp + 0x28498], al +{0x3e6a132, 8, 4, Value::slot, {0x4c,0x39,0xa4,0x2b,0xa8,0x84,0x02,0x00}}, // cmp qword ptr [rbx + rbp + 0x284a8], r12 +{0x3e6a13c, 8, 4, Value::slot, {0x0f,0xb6,0x94,0x2b,0x9a,0x84,0x02,0x00}}, // movzx edx, byte ptr [rbx + rbp + 0x2849a] +{0x3e6a153, 7, 3, Value::slot, {0x48,0x8d,0x8d,0xb0,0x84,0x02,0x00}}, // lea rcx, [rbp + 0x284b0] +{0x3e6a166, 8, 3, Value::slot, {0xc6,0x84,0x2b,0x98,0x84,0x02,0x00,0x00}}, // mov byte ptr [rbx + rbp + 0x28498], 0 +{0x3e6a200, 7, 3, Value::slot, {0x48,0x8b,0x9e,0xd0,0x84,0x02,0x00}}, // mov rbx, qword ptr [rsi + 0x284d0] +{0x3e6a229, 6, 2, Value::slot, {0xfe,0x8e,0xa6,0x84,0x02,0x00}}, // dec byte ptr [rsi + 0x284a6] +{0x3e6a23c, 11, 3, Value::slot, {0x48,0xc7,0x86,0xd0,0x84,0x02,0x00,0x00,0x00,0x00,0x00}}, // mov qword ptr [rsi + 0x284d0], 0 +{0x3e6a247, 10, 2, Value::slot, {0xc7,0x86,0xd8,0x84,0x02,0x00,0x00,0x00,0x00,0x00}}, // mov dword ptr [rsi + 0x284d8], 0 +{0x3e6a7ef, 8, 3, Value::slot, {0xf6,0x84,0x38,0xa7,0x84,0x02,0x00,0x02}}, // test byte ptr [rax + rdi + 0x284a7], 2 +{0x3e6a7fd, 8, 3, Value::slot, {0x80,0xbc,0x38,0xa6,0x84,0x02,0x00,0x00}}, // cmp byte ptr [rax + rdi + 0x284a6], 0 +{0x3e6a83a, 6, 2, Value::slot, {0x8b,0x8b,0xa0,0x84,0x02,0x00}}, // mov ecx, dword ptr [rbx + 0x284a0] +{0x3e6a852, 7, 3, Value::slot, {0x48,0x8b,0x93,0xb8,0x84,0x02,0x00}}, // mov rdx, qword ptr [rbx + 0x284b8] +{0x3e6a868, 7, 3, Value::slot, {0x48,0x8b,0x8b,0xa8,0x84,0x02,0x00}}, // mov rcx, qword ptr [rbx + 0x284a8] +{0x3e6a874, 7, 3, Value::slot, {0x4c,0x89,0xa3,0xa8,0x84,0x02,0x00}}, // mov qword ptr [rbx + 0x284a8], r12 +{0x3e6a881, 7, 3, Value::slot, {0x4c,0x89,0xa3,0xb8,0x84,0x02,0x00}}, // mov qword ptr [rbx + 0x284b8], r12 +{0x3e6c682, 7, 3, Value::slot, {0x48,0x81,0xc1,0x98,0x84,0x02,0x00}}, // add rcx, 0x28498 +{0x3e6c8fd, 7, 3, Value::floats, {0x48,0x8d,0x81,0x00,0x05,0x1e,0x00}}, // lea rax, [rcx + 0x1e0500] +{0x3e6c904, 7, 3, Value::count, {0xc7,0x42,0x08,0x00,0x50,0x00,0x00}}, // mov dword ptr [rdx + 8], 0x5000 +{0x3e6d597, 8, 3, Value::slot, {0x80,0xbc,0x0a,0x9a,0x84,0x02,0x00,0x00}}, // cmp byte ptr [rdx + rcx + 0x2849a], 0 +{0x3e6d945, 7, 3, Value::slot, {0x48,0x8d,0xbe,0xc0,0x84,0x02,0x00}}, // lea rdi, [rsi + 0x284c0] +{0x3e6d98b, 7, 3, Value::slot, {0x48,0x8d,0x96,0xdc,0x84,0x02,0x00}}, // lea rdx, [rsi + 0x284dc] +{0x3e6d99d, 9, 5, Value::slot, {0x45,0x0f,0xb6,0x8c,0x34,0x99,0x84,0x02,0x00}}, // movzx r9d, byte ptr [r12 + rsi + 0x28499] +{0x3e6d9ab, 8, 4, Value::slot, {0x45,0x2a,0x8c,0x34,0x9b,0x84,0x02,0x00}}, // sub r9b, byte ptr [r12 + rsi + 0x2849b] +{0x3e6d9cd, 8, 4, Value::slot, {0x4d,0x8b,0x8c,0x34,0xb8,0x84,0x02,0x00}}, // mov r9, qword ptr [r12 + rsi + 0x284b8] +{0x3e6d9df, 7, 3, Value::slot, {0x48,0x8d,0xbe,0xc0,0x84,0x02,0x00}}, // lea rdi, [rsi + 0x284c0] +{0x3e6d9ee, 8, 4, Value::slot, {0x41,0x89,0x84,0x34,0xe0,0x84,0x02,0x00}}, // mov dword ptr [r12 + rsi + 0x284e0], eax +{0x3e6dac5, 9, 5, Value::slot, {0x45,0x0f,0xb6,0x84,0x36,0x99,0x84,0x02,0x00}}, // movzx r8d, byte ptr [r14 + rsi + 0x28499] +{0x3e6dad1, 8, 4, Value::slot, {0x45,0x2a,0x84,0x36,0x9b,0x84,0x02,0x00}}, // sub r8b, byte ptr [r14 + rsi + 0x2849b] +{0x3e6daeb, 8, 4, Value::slot, {0x44,0x38,0xac,0x30,0xa7,0x84,0x02,0x00}}, // cmp byte ptr [rax + rsi + 0x284a7], r13b +{0x3e6db01, 8, 4, Value::slot, {0x49,0x8b,0xbc,0x36,0xd0,0x84,0x02,0x00}}, // mov rdi, qword ptr [r14 + rsi + 0x284d0] +{0x3e6db2d, 9, 4, Value::slot, {0x41,0x80,0xa4,0x36,0xa7,0x84,0x02,0x00,0xfd}}, // and byte ptr [r14 + rsi + 0x284a7], 0xfd +{0x3e6e54f, 8, 4, Value::slot, {0x0f,0xb6,0x84,0x01,0xa7,0x84,0x02,0x00}}, // movzx eax, byte ptr [rcx + rax + 0x284a7] +{0x3e6e587, 9, 5, Value::slot, {0x46,0x0f,0xb6,0x84,0x12,0x9c,0x84,0x02,0x00}}, // movzx r8d, byte ptr [rdx + r10 + 0x2849c] +{0x3e6e590, 9, 5, Value::slot, {0x42,0x0f,0xb6,0x8c,0x12,0x9e,0x84,0x02,0x00}}, // movzx ecx, byte ptr [rdx + r10 + 0x2849e] +{0x3e6e6ac, 9, 5, Value::slot, {0x41,0x0f,0xb6,0x84,0x09,0xa5,0x84,0x02,0x00}}, // movzx eax, byte ptr [r9 + rcx + 0x284a5] +{0x3e6e6c0, 9, 5, Value::slot, {0x41,0x0f,0xb6,0x84,0x09,0xa4,0x84,0x02,0x00}}, // movzx eax, byte ptr [r9 + rcx + 0x284a4] +{0x3e6e6ce, 9, 5, Value::slot, {0x41,0x0f,0xb6,0x84,0x09,0xa4,0x84,0x02,0x00}}, // movzx eax, byte ptr [r9 + rcx + 0x284a4] +{0x3e6e6d9, 9, 5, Value::slot, {0x41,0x0f,0xb6,0x9c,0x09,0x99,0x84,0x02,0x00}}, // movzx ebx, byte ptr [r9 + rcx + 0x28499] +{0x3e6e6e2, 9, 5, Value::slot, {0x41,0x0f,0xb6,0xb4,0x09,0x9c,0x84,0x02,0x00}}, // movzx esi, byte ptr [r9 + rcx + 0x2849c] +{0x3e6e7a7, 9, 4, Value::slot, {0x48,0x83,0xbc,0x0a,0xa8,0x84,0x02,0x00,0x00}}, // cmp qword ptr [rdx + rcx + 0x284a8], 0 +{0x3e6e859, 7, 3, Value::slot, {0x48,0x81,0xc3,0x98,0x84,0x02,0x00}}, // add rbx, 0x28498 +{0x3e6ed5f, 8, 4, Value::slot, {0x48,0x89,0x94,0x01,0xb8,0x84,0x02,0x00}}, // mov qword ptr [rcx + rax + 0x284b8], rdx +{0x3e722a1, 8, 4, Value::slot, {0x4a,0x8b,0x84,0x26,0xb8,0x84,0x02,0x00}}, // mov rax, qword ptr [rsi + r12 + 0x284b8] +{0x3e72314, 8, 4, Value::slot, {0x4a,0x8b,0x84,0x26,0xb8,0x84,0x02,0x00}}, // mov rax, qword ptr [rsi + r12 + 0x284b8] +{0x3e72351, 8, 4, Value::slot, {0x42,0xfe,0x84,0x26,0xa6,0x84,0x02,0x00}}, // inc byte ptr [rsi + r12 + 0x284a6] +{0x3e72359, 8, 4, Value::slot, {0x4e,0x89,0xbc,0x26,0xc8,0x84,0x02,0x00}}, // mov qword ptr [rsi + r12 + 0x284c8], r15 +{0x3e72369, 8, 4, Value::slot, {0x42,0x2b,0x84,0x26,0xa0,0x84,0x02,0x00}}, // sub eax, dword ptr [rsi + r12 + 0x284a0] +{0x3e72b93, 7, 3, Value::slot, {0x48,0x81,0xc6,0x98,0x84,0x02,0x00}}, // add rsi, 0x28498 +{0x3e72bd6, 6, 2, Value::count, {0x41,0xbc,0x00,0x50,0x00,0x00}}, // mov r12d, 0x5000 +{0x3e72bfe, 8, 3, Value::slot, {0x48,0x83,0xbe,0xa8,0x84,0x02,0x00,0x00}}, // cmp qword ptr [rsi + 0x284a8], 0 +{0x3e72c20, 7, 3, Value::slot, {0x48,0x81,0xc6,0x98,0x84,0x02,0x00}}, // add rsi, 0x28498 +{0x3e72c7a, 5, 1, Value::count, {0xb9,0x00,0x50,0x00,0x00}}, // mov ecx, 0x5000 +{0x3e72ca3, 6, 2, Value::count, {0x81,0xc1,0x00,0x50,0x00,0x00}}, // add ecx, 0x5000 +{0x3e72d24, 7, 3, Value::slot, {0x48,0x81,0xc6,0x98,0x84,0x02,0x00}}, // add rsi, 0x28498 +{0x3e738df, 9, 4, Value::slot, {0x43,0x80,0xbc,0x37,0x9c,0x84,0x02,0x00,0x00}}, // cmp byte ptr [r15 + r14 + 0x2849c], 0 +{0x3e738e8, 9, 5, Value::slot, {0x66,0x43,0x89,0x9c,0x37,0xe6,0x84,0x02,0x00}}, // mov word ptr [r15 + r14 + 0x284e6], bx +{0x3e738f7, 9, 4, Value::slot, {0x43,0x80,0xbc,0x37,0xa6,0x84,0x02,0x00,0x00}}, // cmp byte ptr [r15 + r14 + 0x284a6], 0 +{0x3e73906, 9, 4, Value::slot, {0x43,0xf6,0x84,0x37,0xa7,0x84,0x02,0x00,0x02}}, // test byte ptr [r15 + r14 + 0x284a7], 2 +{0x3e7391f, 9, 5, Value::slot, {0x66,0x43,0x89,0x84,0x37,0xe4,0x84,0x02,0x00}}, // mov word ptr [r15 + r14 + 0x284e4], ax +{0x3e7394a, 9, 5, Value::slot, {0x43,0x0f,0xb6,0xbc,0x37,0x99,0x84,0x02,0x00}}, // movzx edi, byte ptr [r15 + r14 + 0x28499] +{0x3e73953, 9, 5, Value::slot, {0x66,0x43,0x89,0x84,0x37,0xe8,0x84,0x02,0x00}}, // mov word ptr [r15 + r14 + 0x284e8], ax +{0x3e7395c, 8, 4, Value::slot, {0x47,0x88,0xa4,0x37,0x9b,0x84,0x02,0x00}}, // mov byte ptr [r15 + r14 + 0x2849b], r12b +{0x3e73977, 8, 4, Value::slot, {0x47,0x88,0xa4,0x37,0x9a,0x84,0x02,0x00}}, // mov byte ptr [r15 + r14 + 0x2849a], r12b +{0x3e7399e, 8, 4, Value::slot, {0x43,0x88,0x94,0x37,0xa4,0x84,0x02,0x00}}, // mov byte ptr [r15 + r14 + 0x284a4], dl +{0x3e739c7, 8, 4, Value::slot, {0x47,0x88,0x84,0x37,0xa5,0x84,0x02,0x00}}, // mov byte ptr [r15 + r14 + 0x284a5], r8b +{0x3e739e5, 8, 4, Value::slot, {0x4b,0x8b,0x8c,0x37,0xa8,0x84,0x02,0x00}}, // mov rcx, qword ptr [r15 + r14 + 0x284a8] +{0x3e73a00, 7, 3, Value::slot, {0x49,0x8d,0x96,0xdc,0x84,0x02,0x00}}, // lea rdx, [r14 + 0x284dc] +{0x3e73a2a, 8, 4, Value::slot, {0x4f,0x8b,0x8c,0x37,0xa8,0x84,0x02,0x00}}, // mov r9, qword ptr [r15 + r14 + 0x284a8] +{0x3e73ab9, 9, 5, Value::slot, {0x43,0x0f,0xb6,0x84,0x37,0xa7,0x84,0x02,0x00}}, // movzx eax, byte ptr [r15 + r14 + 0x284a7] +{0x3e73acc, 8, 4, Value::slot, {0x43,0x88,0x8c,0x37,0xa7,0x84,0x02,0x00}}, // mov byte ptr [r15 + r14 + 0x284a7], cl +{0x3e74db3, 8, 4, Value::slot, {0x0f,0xb7,0x8c,0x31,0xe8,0x84,0x02,0x00}}, // movzx ecx, word ptr [rcx + rsi + 0x284e8] +{0x3e74f4d, 8, 4, Value::slot, {0x4a,0x8b,0x84,0x0e,0xc0,0x84,0x02,0x00}}, // mov rax, qword ptr [rsi + r9 + 0x284c0] +{0x3e74f5a, 8, 4, Value::slot, {0x4d,0x89,0xa4,0x31,0xc0,0x84,0x02,0x00}}, // mov qword ptr [r9 + rsi + 0x284c0], r12 +{0x3e74f6a, 8, 4, Value::slot, {0x4d,0x8b,0x8c,0x31,0xb8,0x84,0x02,0x00}}, // mov r9, qword ptr [r9 + rsi + 0x284b8] +{0x3e75143, 8, 4, Value::slot, {0x0f,0xb7,0x8c,0x31,0xe8,0x84,0x02,0x00}}, // movzx ecx, word ptr [rcx + rsi + 0x284e8] +{0x3e75273, 9, 5, Value::slot, {0x44,0x0f,0xb6,0x8c,0x33,0x9c,0x84,0x02,0x00}}, // movzx r9d, byte ptr [rbx + rsi + 0x2849c] +{0x3e752b7, 8, 4, Value::slot, {0x48,0x8b,0x8c,0x33,0xb8,0x84,0x02,0x00}}, // mov rcx, qword ptr [rbx + rsi + 0x284b8] +{0x3e752c3, 8, 4, Value::slot, {0x4c,0x89,0xbc,0x33,0xb8,0x84,0x02,0x00}}, // mov qword ptr [rbx + rsi + 0x284b8], r15 +{0x3e7572f, 7, 3, Value::slot, {0x49,0x8d,0x9e,0x98,0x84,0x02,0x00}}, // lea rbx, [r14 + 0x28498] +{0x3e7580a, 10, 6, Value::floats, {0xc4,0xc1,0x4a,0x5e,0x84,0x8e,0x00,0x05,0x1e,0x00}}, // vdivss xmm0, xmm6, dword ptr [r14 + rcx*4 + 0x1e0500] +{0x3e75b5d, 9, 4, Value::slot, {0x42,0xf6,0x84,0x31,0xa7,0x84,0x02,0x00,0x08}}, // test byte ptr [rcx + r14 + 0x284a7], 8 +{0x3e75b7f, 9, 4, Value::slot, {0x42,0xf6,0x84,0x31,0xa7,0x84,0x02,0x00,0x08}}, // test byte ptr [rcx + r14 + 0x284a7], 8 +{0x3e75bae, 9, 5, Value::slot, {0x42,0x0f,0xb6,0x94,0x31,0xa7,0x84,0x02,0x00}}, // movzx edx, byte ptr [rcx + r14 + 0x284a7] +{0x3e75bb7, 9, 5, Value::slot, {0x46,0x0f,0xb6,0x84,0x31,0x9e,0x84,0x02,0x00}}, // movzx r8d, byte ptr [rcx + r14 + 0x2849e] +{0x3e75bca, 9, 5, Value::slot, {0x42,0x0f,0xb6,0x84,0x31,0x9c,0x84,0x02,0x00}}, // movzx eax, byte ptr [rcx + r14 + 0x2849c] +{0x3e75c04, 9, 4, Value::slot, {0x43,0x80,0xbc,0x34,0xa7,0x84,0x02,0x00,0x00}}, // cmp byte ptr [r12 + r14 + 0x284a7], 0 +{0x3e75c13, 9, 5, Value::slot, {0x66,0x47,0x39,0x84,0x34,0xe8,0x84,0x02,0x00}}, // cmp word ptr [r12 + r14 + 0x284e8], r8w +{0x3e75c52, 9, 5, Value::slot, {0x42,0x0f,0xb7,0x8c,0x31,0xe8,0x84,0x02,0x00}}, // movzx ecx, word ptr [rcx + r14 + 0x284e8] +{0x3e75cb9, 8, 4, Value::slot, {0x49,0x8b,0x8c,0x36,0xa8,0x84,0x02,0x00}}, // mov rcx, qword ptr [r14 + rsi + 0x284a8] +{0x3e75cca, 7, 3, Value::slot, {0x49,0x8d,0x96,0xdc,0x84,0x02,0x00}}, // lea rdx, [r14 + 0x284dc] +{0x3e75cf3, 8, 4, Value::slot, {0x4d,0x8b,0x8c,0x36,0xa8,0x84,0x02,0x00}}, // mov r9, qword ptr [r14 + rsi + 0x284a8] +{0x3e75d4e, 8, 4, Value::slot, {0x4b,0x8b,0x8c,0x34,0xa8,0x84,0x02,0x00}}, // mov rcx, qword ptr [r12 + r14 + 0x284a8] +{0x3e75d5f, 7, 3, Value::slot, {0x49,0x8d,0x96,0xdc,0x84,0x02,0x00}}, // lea rdx, [r14 + 0x284dc] +{0x3e75d87, 8, 4, Value::slot, {0x4f,0x8b,0x8c,0x34,0xa8,0x84,0x02,0x00}}, // mov r9, qword ptr [r12 + r14 + 0x284a8] +{0x3e75dc3, 9, 4, Value::slot, {0x42,0x80,0xbc,0x31,0xa7,0x84,0x02,0x00,0x00}}, // cmp byte ptr [rcx + r14 + 0x284a7], 0 +{0x3e75dce, 9, 5, Value::slot, {0x66,0x46,0x39,0x84,0x31,0xe8,0x84,0x02,0x00}}, // cmp word ptr [rcx + r14 + 0x284e8], r8w +{0x3e75e03, 9, 5, Value::slot, {0x42,0x0f,0xb7,0x8c,0x31,0xe8,0x84,0x02,0x00}}, // movzx ecx, word ptr [rcx + r14 + 0x284e8] +{0x3e75e55, 8, 4, Value::slot, {0x4a,0x8b,0x8c,0x33,0xa8,0x84,0x02,0x00}}, // mov rcx, qword ptr [rbx + r14 + 0x284a8] +{0x3e75e6d, 7, 3, Value::slot, {0x48,0x8d,0x97,0xdc,0x84,0x02,0x00}}, // lea rdx, [rdi + 0x284dc] +{0x3e75e80, 9, 5, Value::slot, {0x42,0x0f,0xb6,0x8c,0x33,0x9b,0x84,0x02,0x00}}, // movzx ecx, byte ptr [rbx + r14 + 0x2849b] +{0x3e75e89, 9, 5, Value::slot, {0x42,0x0f,0xb6,0x94,0x33,0x99,0x84,0x02,0x00}}, // movzx edx, byte ptr [rbx + r14 + 0x28499] +{0x3e75e94, 9, 5, Value::slot, {0x42,0x0f,0xb6,0x8c,0x33,0x9c,0x84,0x02,0x00}}, // movzx ecx, byte ptr [rbx + r14 + 0x2849c] +{0x3e75ea5, 9, 4, Value::slot, {0x42,0x80,0xbc,0x33,0xa6,0x84,0x02,0x00,0x00}}, // cmp byte ptr [rbx + r14 + 0x284a6], 0 +{0x3e75eb4, 9, 4, Value::slot, {0x42,0xf6,0x84,0x33,0xa7,0x84,0x02,0x00,0x08}}, // test byte ptr [rbx + r14 + 0x284a7], 8 +{0x3e75ec3, 8, 4, Value::slot, {0x4a,0x8b,0x94,0x33,0xb8,0x84,0x02,0x00}}, // mov rdx, qword ptr [rbx + r14 + 0x284b8] +{0x3e75ee2, 7, 3, Value::slot, {0x48,0x8d,0x97,0xdc,0x84,0x02,0x00}}, // lea rdx, [rdi + 0x284dc] +{0x3e75ef6, 9, 5, Value::slot, {0x46,0x0f,0xb6,0x84,0x33,0x9c,0x84,0x02,0x00}}, // movzx r8d, byte ptr [rbx + r14 + 0x2849c] +{0x3e75f0f, 9, 5, Value::slot, {0x42,0x0f,0xb6,0x94,0x33,0x9e,0x84,0x02,0x00}}, // movzx edx, byte ptr [rbx + r14 + 0x2849e] +{0x3e75f1d, 9, 5, Value::slot, {0x42,0x0f,0xb6,0x8c,0x33,0x9c,0x84,0x02,0x00}}, // movzx ecx, byte ptr [rbx + r14 + 0x2849c] +{0x3e75f4f, 9, 4, Value::slot, {0x42,0x80,0xbc,0x37,0xa7,0x84,0x02,0x00,0x00}}, // cmp byte ptr [rdi + r14 + 0x284a7], 0 +{0x3e75f7f, 7, 3, Value::slot, {0x49,0x8d,0x96,0xdc,0x84,0x02,0x00}}, // lea rdx, [r14 + 0x284dc] +{0x3e75f95, 8, 4, Value::slot, {0x4a,0x8b,0x8c,0x37,0xa8,0x84,0x02,0x00}}, // mov rcx, qword ptr [rdi + r14 + 0x284a8] +{0x3e75fb1, 8, 4, Value::slot, {0x4a,0x8b,0x94,0x37,0xb8,0x84,0x02,0x00}}, // mov rdx, qword ptr [rdi + r14 + 0x284b8] +{0x3e75fbc, 8, 4, Value::slot, {0x42,0x8b,0x8c,0x37,0xa0,0x84,0x02,0x00}}, // mov ecx, dword ptr [rdi + r14 + 0x284a0] +{0x3e75fc4, 9, 5, Value::slot, {0x42,0x0f,0xb6,0x84,0x37,0x99,0x84,0x02,0x00}}, // movzx eax, byte ptr [rdi + r14 + 0x28499] +{0x3e75fcf, 8, 4, Value::slot, {0x42,0x88,0x84,0x37,0x9d,0x84,0x02,0x00}}, // mov byte ptr [rdi + r14 + 0x2849d], al +{0x3e75fd7, 8, 4, Value::slot, {0x42,0x88,0x84,0x37,0x9b,0x84,0x02,0x00}}, // mov byte ptr [rdi + r14 + 0x2849b], al +{0x3e75fdf, 12, 4, Value::slot, {0x4a,0xc7,0x84,0x37,0xb8,0x84,0x02,0x00,0x00,0x00,0x00,0x00}}, // mov qword ptr [rdi + r14 + 0x284b8], 0 +{0x3e75feb, 12, 4, Value::slot, {0x42,0xc7,0x84,0x37,0xa0,0x84,0x02,0x00,0x00,0x00,0x00,0x00}}, // mov dword ptr [rdi + r14 + 0x284a0], 0 +{0x3e75ff7, 9, 4, Value::slot, {0x42,0xc6,0x84,0x37,0x9c,0x84,0x02,0x00,0x00}}, // mov byte ptr [rdi + r14 + 0x2849c], 0 +{0x3e760bd, 9, 4, Value::slot, {0x42,0xf6,0x84,0x31,0xa7,0x84,0x02,0x00,0x08}}, // test byte ptr [rcx + r14 + 0x284a7], 8 +{0x3e76113, 9, 4, Value::slot, {0x42,0xf6,0x84,0x31,0xa7,0x84,0x02,0x00,0x08}}, // test byte ptr [rcx + r14 + 0x284a7], 8 +{0x3e7612e, 8, 4, Value::slot, {0x46,0x38,0x84,0x31,0x9c,0x84,0x02,0x00}}, // cmp byte ptr [rcx + r14 + 0x2849c], r8b +{0x3e76155, 9, 4, Value::slot, {0x42,0x80,0xbc,0x30,0xa7,0x84,0x02,0x00,0x00}}, // cmp byte ptr [rax + r14 + 0x284a7], 0 +{0x3e76203, 7, 3, Value::slot, {0x48,0x8d,0x93,0xdc,0x84,0x02,0x00}}, // lea rdx, [rbx + 0x284dc] +{0x3e76212, 7, 3, Value::slot, {0x0f,0xb6,0x83,0x9c,0x84,0x02,0x00}}, // movzx eax, byte ptr [rbx + 0x2849c] +{0x3e76223, 7, 2, Value::slot, {0x80,0xbb,0xa7,0x84,0x02,0x00,0x00}}, // cmp byte ptr [rbx + 0x284a7], 0 +{0x3e7623b, 7, 3, Value::slot, {0x48,0x8b,0xb3,0xc0,0x84,0x02,0x00}}, // mov rsi, qword ptr [rbx + 0x284c0] +{0x3e76251, 6, 2, Value::slot, {0x8b,0x83,0xe0,0x84,0x02,0x00}}, // mov eax, dword ptr [rbx + 0x284e0] +{0x3e7625d, 7, 3, Value::slot, {0x48,0x89,0xbb,0xc0,0x84,0x02,0x00}}, // mov qword ptr [rbx + 0x284c0], rdi +{0x3e76264, 7, 3, Value::slot, {0x4c,0x8d,0xab,0xb8,0x84,0x02,0x00}}, // lea r13, [rbx + 0x284b8] +{0x3e76279, 7, 3, Value::slot, {0x48,0x89,0xbb,0xc0,0x84,0x02,0x00}}, // mov qword ptr [rbx + 0x284c0], rdi +{0x3e76283, 7, 3, Value::slot, {0x4c,0x8d,0xab,0xb8,0x84,0x02,0x00}}, // lea r13, [rbx + 0x284b8] +{0x3e76311, 7, 3, Value::slot, {0x0f,0xb6,0x83,0x9c,0x84,0x02,0x00}}, // movzx eax, byte ptr [rbx + 0x2849c] +{0x3e76321, 7, 3, Value::slot, {0x0f,0xb6,0x83,0x99,0x84,0x02,0x00}}, // movzx eax, byte ptr [rbx + 0x28499] +{0x3e7632d, 7, 3, Value::slot, {0x0f,0xb6,0x83,0xa7,0x84,0x02,0x00}}, // movzx eax, byte ptr [rbx + 0x284a7] +{0x3e7633c, 7, 3, Value::slot, {0x44,0x8b,0x83,0xd8,0x84,0x02,0x00}}, // mov r8d, dword ptr [rbx + 0x284d8] +{0x3e763bc, 6, 2, Value::slot, {0xfe,0x83,0xa6,0x84,0x02,0x00}}, // inc byte ptr [rbx + 0x284a6] +{0x3e763ca, 7, 3, Value::slot, {0x48,0x89,0xb3,0xc8,0x84,0x02,0x00}}, // mov qword ptr [rbx + 0x284c8], rsi +{0x3e763da, 6, 2, Value::slot, {0x2b,0x83,0xa0,0x84,0x02,0x00}}, // sub eax, dword ptr [rbx + 0x284a0] +{0x3e76a49, 7, 3, Value::slot, {0x48,0x81,0xc3,0x98,0x84,0x02,0x00}}, // add rbx, 0x28498 +{0x3e77038, 10, 5, Value::slot, {0x66,0x41,0x83,0xbc,0x06,0xea,0x84,0x02,0x00,0x01}}, // cmp word ptr [r14 + rax + 0x284ea], 1 +{0x3e77044, 9, 4, Value::slot, {0x42,0x80,0xbc,0x30,0xa6,0x84,0x02,0x00,0x00}}, // cmp byte ptr [rax + r14 + 0x284a6], 0 +{0x3e7704f, 8, 4, Value::slot, {0x4a,0x8b,0x84,0x30,0xb0,0x84,0x02,0x00}}, // mov rax, qword ptr [rax + r14 + 0x284b0] +{0x3e7708c, 7, 3, Value::slot, {0x49,0x8d,0x8e,0x98,0x84,0x02,0x00}}, // lea rcx, [r14 + 0x28498] +{0x3e77518, 8, 4, Value::slot, {0x66,0x89,0x84,0x29,0xe6,0x84,0x02,0x00}}, // mov word ptr [rcx + rbp + 0x284e6], ax +{0x3e77af3, 9, 5, Value::floats, {0xc5,0xfa,0x10,0x8c,0x86,0x00,0x05,0x1e,0x00}}, // vmovss xmm1, dword ptr [rsi + rax*4 + 0x1e0500] +{0x3e77b16, 9, 5, Value::floats, {0xc5,0xfa,0x11,0x84,0x86,0x00,0x05,0x1e,0x00}}, // vmovss dword ptr [rsi + rax*4 + 0x1e0500], xmm0 +{0x3e77ec4, 7, 3, Value::floats, {0x48,0x8d,0x86,0x00,0x05,0x1e,0x00}}, // lea rax, [rsi + 0x1e0500] +{0x3e77ed0, 7, 3, Value::float_bytes, {0x48,0x8d,0x88,0x00,0x40,0x01,0x00}}, // lea rcx, [rax + 0x14000] +{0x3efd80a, 5, 1, Value::alloc, {0xba,0x80,0xe7,0x24,0x00}}, // mov edx, 0x24e780 +{0x3f31cc8, 7, 3, Value::r_field, {0x48,0x8b,0x86,0x80,0x00,0x05,0x00}}, // mov rax, qword ptr [rsi + 0x50080] +{0x3f31d22, 7, 3, Value::r_field, {0x48,0x8b,0x86,0x80,0x00,0x05,0x00}}, // mov rax, qword ptr [rsi + 0x50080] +{0x3f34a5b, 7, 3, Value::r_field, {0x48,0x8b,0x88,0x80,0x00,0x05,0x00}}, // mov rcx, qword ptr [rax + 0x50080] +{0x3f34a8b, 7, 3, Value::r_field, {0x48,0x8b,0x90,0x80,0x00,0x05,0x00}}, // mov rdx, qword ptr [rax + 0x50080] +{0x3f34e69, 7, 3, Value::r_field, {0x48,0x8b,0xa8,0x80,0x00,0x05,0x00}}, // mov rbp, qword ptr [rax + 0x50080] +{0x3f34ef3, 7, 3, Value::r_field, {0x48,0x8b,0x89,0x80,0x00,0x05,0x00}}, // mov rcx, qword ptr [rcx + 0x50080] +{0x3f3ed33, 7, 3, Value::r_field, {0x48,0x8b,0x88,0x80,0x00,0x05,0x00}}, // mov rcx, qword ptr [rax + 0x50080] +{0x3f3d83a, 5, 1, Value::r_alloc, {0xb9,0xc0,0x00,0x05,0x00}}, // mov ecx, 0x500c0 +{0x3f3d86b, 5, 1, Value::r_fill, {0xb9,0x00,0x40,0x01,0x00}}, // mov ecx, 0x14000 +{0x3f3d876, 7, 3, Value::r_block, {0x49,0x8d,0x80,0x40,0x40,0x01,0x00}}, // lea rax, [r8 + 0x14040] +{0x3f3d87d, 7, 3, Value::r_field, {0x49,0x89,0x90,0x40,0x00,0x05,0x00}}, // mov qword ptr [r8 + 0x50040], rdx +{0x3f3d884, 7, 3, Value::r_block, {0x49,0x8d,0x90,0x40,0x80,0x02,0x00}}, // lea rdx, [r8 + 0x28040] +{0x3f3d88b, 7, 3, Value::r_field, {0x41,0x89,0x88,0x4c,0x00,0x05,0x00}}, // mov dword ptr [r8 + 0x5004c], ecx +{0x3f3d892, 11, 3, Value::r_field, {0x41,0xc7,0x80,0x48,0x00,0x05,0x00,0x00,0x50,0x00,0x00}}, // mov dword ptr [r8 + 0x50048], 0x5000 +{0x3f3d892, 11, 7, Value::count, {0x41,0xc7,0x80,0x48,0x00,0x05,0x00,0x00,0x50,0x00,0x00}}, // mov dword ptr [r8 + 0x50048], 0x5000 +{0x3f3d89d, 7, 3, Value::r_field, {0x49,0x89,0x80,0x50,0x00,0x05,0x00}}, // mov qword ptr [r8 + 0x50050], rax +{0x3f3d8a7, 11, 3, Value::r_field, {0x41,0xc7,0x80,0x58,0x00,0x05,0x00,0x00,0x50,0x00,0x00}}, // mov dword ptr [r8 + 0x50058], 0x5000 +{0x3f3d8a7, 11, 7, Value::count, {0x41,0xc7,0x80,0x58,0x00,0x05,0x00,0x00,0x50,0x00,0x00}}, // mov dword ptr [r8 + 0x50058], 0x5000 +{0x3f3d8b2, 7, 3, Value::r_field, {0x41,0x89,0x88,0x5c,0x00,0x05,0x00}}, // mov dword ptr [r8 + 0x5005c], ecx +{0x3f3d8b9, 7, 3, Value::r_field, {0x49,0x89,0x90,0x60,0x00,0x05,0x00}}, // mov qword ptr [r8 + 0x50060], rdx +{0x3f3d8c0, 7, 3, Value::r_block, {0x49,0x8d,0x90,0x40,0xc0,0x03,0x00}}, // lea rdx, [r8 + 0x3c040] +{0x3f3d8c7, 11, 3, Value::r_field, {0x41,0xc7,0x80,0x68,0x00,0x05,0x00,0x00,0x50,0x00,0x00}}, // mov dword ptr [r8 + 0x50068], 0x5000 +{0x3f3d8c7, 11, 7, Value::count, {0x41,0xc7,0x80,0x68,0x00,0x05,0x00,0x00,0x50,0x00,0x00}}, // mov dword ptr [r8 + 0x50068], 0x5000 +{0x3f3d8d2, 7, 3, Value::r_field, {0x41,0x89,0x88,0x6c,0x00,0x05,0x00}}, // mov dword ptr [r8 + 0x5006c], ecx +{0x3f3d8d9, 7, 3, Value::r_field, {0x49,0x89,0x90,0x70,0x00,0x05,0x00}}, // mov qword ptr [r8 + 0x50070], rdx +{0x3f3d8e0, 11, 3, Value::r_field, {0x41,0xc7,0x80,0x78,0x00,0x05,0x00,0x00,0x50,0x00,0x00}}, // mov dword ptr [r8 + 0x50078], 0x5000 +{0x3f3d8e0, 11, 7, Value::count, {0x41,0xc7,0x80,0x78,0x00,0x05,0x00,0x00,0x50,0x00,0x00}}, // mov dword ptr [r8 + 0x50078], 0x5000 +{0x3f3d8eb, 7, 3, Value::r_field, {0x41,0x89,0x88,0x7c,0x00,0x05,0x00}}, // mov dword ptr [r8 + 0x5007c], ecx +{0x3f3d8f2, 7, 3, Value::r_field, {0x49,0x89,0x98,0x80,0x00,0x05,0x00}}, // mov qword ptr [r8 + 0x50080], rbx +{0x3f3d909, 7, 3, Value::r_field, {0x48,0x89,0x9e,0x80,0x00,0x05,0x00}}, // mov qword ptr [rsi + 0x50080], rbx +{0x3f56580, 7, 3, Value::r_spans, {0x48,0x8d,0x98,0x04,0x50,0x00,0x00}}, // lea rbx, [rax + 0x5004] diff --git a/Extension/World/stack_check_report.cpp b/Extension/World/stack_check_report.cpp new file mode 100644 index 0000000..a51a63b --- /dev/null +++ b/Extension/World/stack_check_report.cpp @@ -0,0 +1,62 @@ +#include "stack_check_report.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/texture_streaming.h" +#include +#include +#include +#include +#include + +namespace dingosdk::stack_check_report { +namespace { +namespace ts = addr::texture_streaming; +using Report = void (*)(std::uintptr_t cookie); +Report original{}; +std::uintptr_t image{}; +std::atomic installed{}; + +std::string place(std::uintptr_t address) { + char text[40]{}; + if (address >= image && address - image < 0x10000000) sprintf_s(text, "Skate.exe+0x%llx", static_cast(address - image)); + else sprintf_s(text, "0x%llx", static_cast(address)); + return text; +} +// The game is about to end itself with a fast fail, which no crash handler sees: say where +// first. The return address is in the function whose stack was written over; the frames behind +// it may be part of what was written over. +void reported(std::uintptr_t cookie) { + const auto from = reinterpret_cast(_ReturnAddress()); + try { + void* frames[24]{}; + const auto count = CaptureStackBackTrace(1, 24, frames, nullptr); + std::string trace; + for (unsigned i = 0; i < count; ++i) trace += (i ? " < " : "") + place(reinterpret_cast(frames[i])); + logging::log(logging::Level::error, logging::Channel::runtime, + "The game's stack check failed in the function that returns to {} (it ends the game at once, with no crash report). Callers: {}", + place(from), trace.empty() ? std::string("unknown") : trace); + logging::flush(); + } catch (...) {} + original(cookie); +} +} // namespace + +bool install(std::uintptr_t base) noexcept { + if (installed.exchange(true)) return original != nullptr; + if (!base) return false; + image = base; + std::array head{}; + if (!memory::peek(base + ts::stack_check_failed, head) || head != ts::stack_check_failed_bytes) return false; + const auto target = reinterpret_cast(base + ts::stack_check_failed); + if (hook_prepare(target, reinterpret_cast(&reported), reinterpret_cast(&original)) != HookOk || hook_enable(target) != HookOk) { + original = nullptr; + try { + logging::log(logging::Level::warning, logging::Channel::runtime, "Stack check report: the game's routine could not be hooked."); + } catch (...) {} + return false; + } + return true; +} +} // namespace dingosdk::stack_check_report diff --git a/Extension/World/stack_check_report.h b/Extension/World/stack_check_report.h new file mode 100644 index 0000000..17abf08 --- /dev/null +++ b/Extension/World/stack_check_report.h @@ -0,0 +1,10 @@ +#pragma once +#include + +// When a function of the game finds its stack written over, the game ends itself with a fast +// fail (exit code 0xc0000409): no exception, so neither crash reporter writes anything. This +// puts where it happened in the log first. A diagnostic for work on the game's fixed-size +// tables (texture_handle_limit); it changes nothing else. +namespace dingosdk::stack_check_report { +bool install(std::uintptr_t base) noexcept; +} diff --git a/Extension/World/texture_handle_limit.cpp b/Extension/World/texture_handle_limit.cpp new file mode 100644 index 0000000..edb9650 --- /dev/null +++ b/Extension/World/texture_handle_limit.cpp @@ -0,0 +1,262 @@ +#include "texture_handle_limit.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/texture_streaming.h" +#include "Extension/UI/Overlay/overlay.h" +#include +#include +#include +#include +#include +#include + +namespace dingosdk::texture_handle_limit { +namespace { +namespace ts = addr::texture_streaming; +using Constructor = void* (*)(void* manager, void* arena); +Constructor original{}; +std::uintptr_t image{}; +std::atomic installed{}, raised{}; +std::atomic lists_moved{}; + +std::uint32_t stock_value(const ts::Site& site) noexcept { + std::uint32_t value{}; + std::memcpy(&value, site.bytes + site.at, 4); + return value; +} +// Each list's entries go behind the manager's stock end, with what the constructor put in them. +// A list that is not where the constructor leaves it is left alone. +bool move_lists(std::uintptr_t manager) noexcept { + __try { + unsigned moved{}; + for (const auto& list : ts::lists) { + auto* header = reinterpret_cast(manager + list.header); + const auto stock = manager + list.stock_entries; + if (header[0] != stock || header[1] < stock || header[2] != stock + std::uintptr_t{ts::stock_count} * list.entry_size) continue; + const auto used = header[1] - header[0]; + const auto entries = manager + list.raised_entries; + std::memcpy(reinterpret_cast(entries), reinterpret_cast(stock), used); + header[0] = entries; + header[1] = entries + used; + header[2] = entries + std::uintptr_t{ts::raised_count} * list.entry_size; + ++moved; + } + lists_moved.store(moved); + return moved == std::size(ts::lists); + } __except (EXCEPTION_EXECUTE_HANDLER) { + return false; + } +} +__declspec(noinline) void say_made(bool moved) noexcept { + try { + if (moved) + logging::log(logging::Level::info, logging::Channel::runtime, + "Texture streaming: the game can hold {} streamed textures at once (its own limit is {}).", ts::raised_count, ts::stock_count); + else + logging::log(logging::Level::error, logging::Channel::runtime, + "Texture streaming: the limit was raised but {} of the manager's {} lists were not where expected; they keep {} entries.", + std::size(ts::lists) - lists_moved.load(), std::size(ts::lists), ts::stock_count); + } catch (...) {} +} +void* made(void* manager, void* arena) { + auto* result = original(manager, arena); + if (raised.load() && result) say_made(move_lists(reinterpret_cast(result))); + return result; +} + +// Three of the manager's functions keep 20480 entries on their own stack, which more slots can +// outgrow (texture_streaming.h). Each is hooked and left exactly as it is up to that many. +using Filter = void (*)(void* manager, std::uintptr_t* list); +using Sort = void (*)(std::uint32_t* begin, std::uint32_t* end, std::uint32_t* scratch); +Filter original_filters[2]{}; +Sort original_sort{}; + +// A longer list goes through the game's function a stretch at a time, each stretch presented as +// a list of its own ({begin, end, capacity end}), and what each keeps is closed up behind the +// last. The function only reads the list, empties entries in place and writes the kept ones back +// from `begin`, so a stretch is all it needs to see. +void filter_in_stretches(Filter filter, void* manager, std::uintptr_t* list) noexcept { + constexpr std::uintptr_t stretch = std::uintptr_t{ts::stock_count} * 8; + const auto begin = list[0], end = list[1]; + auto kept_end = begin; + for (auto at = begin; at < end;) { + const auto stop = std::min(end, at + stretch); + std::uintptr_t part[3]{at, stop, stop}; + filter(manager, part); + const auto kept = part[1] - part[0]; + if (kept && part[0] != kept_end) std::memmove(reinterpret_cast(kept_end), reinterpret_cast(part[0]), kept); + kept_end += kept; + at = stop; + } + list[1] = kept_end; +} +template void filtered(void* manager, std::uintptr_t* list) { + const auto filter = original_filters[Index]; + if (!list || list[1] < list[0] || (list[1] - list[0]) / 8 <= ts::stock_count) return filter(manager, list); + filter_in_stretches(filter, manager, list); +} +// The sort wants scratch room for as many entries as it sorts; its one caller gives it a +// stack array of 20480. More than that get room of their own for the call. +void sorted(std::uint32_t* begin, std::uint32_t* end, std::uint32_t* scratch) { + const auto bytes = reinterpret_cast(end) - reinterpret_cast(begin); + if (end < begin || bytes / 8 <= ts::stock_count) return original_sort(begin, end, scratch); + auto* room = static_cast(HeapAlloc(GetProcessHeap(), 0, bytes)); + if (!room) return; // (unsorted this once: the order only says which textures are dealt with first) + original_sort(begin, end, room); + HeapFree(GetProcessHeap(), 0, room); +} +bool hook(std::uintptr_t base, const game::build::Fingerprint& print, void* replacement, void** kept) noexcept { + std::array head{}; + const auto target = reinterpret_cast(base + print.rva); + if (!memory::peek(base + print.rva, head) || head != print.bytes || hook_prepare(target, replacement, kept) != HookOk || + hook_enable(target) != HookOk) { + *kept = nullptr; + return false; + } + return true; +} + +// "texture_limit_stock.txt" beside the game: the limit is left as the game has it (to compare). +bool stock_asked() noexcept { + wchar_t path[MAX_PATH]{}; + const auto size = GetModuleFileNameW(nullptr, path, MAX_PATH); + if (!size || size >= MAX_PATH) return false; + std::wstring file(path, size); + const auto slash = file.find_last_of(L"/\\"); + if (slash == std::wstring::npos) return false; + file.resize(slash + 1); + file += L"texture_limit_stock.txt"; + return GetFileAttributesW(file.c_str()) != INVALID_FILE_ATTRIBUTES; +} + +void say(logging::Level level, const char* text) noexcept { + try { + logging::log(level, logging::Channel::runtime, "Texture streaming: {}", text); + } catch (...) {} +} +} // namespace + +bool install(std::uintptr_t base) noexcept { + if (installed.exchange(true)) return raised.load(); + if (!base) return false; + image = base; + if (stock_asked()) { + say(logging::Level::warning, "texture_limit_stock.txt is there; the game keeps its own limit of streamed textures."); + return false; + } + // Everything is checked before anything is changed: the constructor, and each instruction + // byte for byte. + std::array head{}; + if (!memory::peek(base + ts::constructor.rva, head) || head != ts::constructor.bytes) { + say(logging::Level::warning, "this build's manager differs; the game keeps its own limit of streamed textures."); + return false; + } + std::uintptr_t first = ~std::uintptr_t{}, last{}; + for (const auto& site : ts::sites) { + std::array now{}; + if (site.size > now.size() || site.at + 4u > site.size || !memory::peek_bytes(base + site.rva, now.data(), site.size) || + std::memcmp(now.data(), site.bytes, site.size) != 0) { + say(logging::Level::warning, "this build's streaming code differs; the game keeps its own limit of streamed textures."); + return false; + } + first = std::min(first, base + site.rva); + last = std::max(last, base + site.rva + site.size); + } + for (const auto& rewrite : ts::renderer_rewrites) { + std::array now{}; + if (!memory::peek(base + rewrite.rva, now) || std::memcmp(now.data(), rewrite.stock, now.size()) != 0) { + say(logging::Level::warning, "this build's renderer differs; the game keeps its own limit of streamed textures."); + return false; + } + first = std::min(first, base + rewrite.rva); + last = std::max(last, base + rewrite.rva + now.size()); + } + std::uintptr_t manager{}; + if (!memory::peek(base + ts::manager, manager) || manager) { + say(logging::Level::warning, "the game had made its manager already; it keeps its own limit of streamed textures."); + return false; + } + const auto target = reinterpret_cast(base + ts::constructor.rva); + if (hook_prepare(target, reinterpret_cast(&made), reinterpret_cast(&original)) != HookOk) { + original = nullptr; + say(logging::Level::warning, "the manager's constructor could not be hooked; the game keeps its own limit of streamed textures."); + return false; + } + // One span of pages made writable for all of it, so no write can fail half way through. + DWORD before{}; + const auto span = last - first; + if (!VirtualProtect(reinterpret_cast(first), span, PAGE_EXECUTE_READWRITE, &before)) { + say(logging::Level::warning, "the streaming code could not be made writable; the game keeps its own limit of streamed textures."); + return false; + } + for (const auto& site : ts::sites) { + const auto value = ts::raised(site.value, stock_value(site)); + std::memcpy(reinterpret_cast(base + site.rva + site.at), &value, 4); + } + for (const auto& rewrite : ts::renderer_rewrites) + std::memcpy(reinterpret_cast(base + rewrite.rva), rewrite.raised, sizeof(rewrite.raised)); + DWORD ignored{}; + VirtualProtect(reinterpret_cast(first), span, before, &ignored); + FlushInstructionCache(GetCurrentProcess(), reinterpret_cast(first), span); + raised.store(true); + const bool guarded = hook(base, ts::filter_loading, reinterpret_cast(&filtered<0>), reinterpret_cast(&original_filters[0])) & + hook(base, ts::filter_waiting, reinterpret_cast(&filtered<1>), reinterpret_cast(&original_filters[1])) & + hook(base, ts::sort_wanted, reinterpret_cast(&sorted), reinterpret_cast(&original_sort)); + if (!guarded) + say(logging::Level::error, "the limit was raised but a function with room for 20480 on its stack could not be hooked; past 20480 the game may crash."); + if (hook_enable(target) != HookOk) + say(logging::Level::error, "the limit was raised but the constructor's hook did not start; the manager's lists keep 20480 entries."); + return true; +} + +Use use() noexcept { + Use result; + result.limit = raised.load() ? ts::raised_count : ts::stock_count; + std::uintptr_t manager{}; + std::uint32_t bottom{}, top{}; + if (!image || !memory::peek(image + ts::manager, manager) || !manager || !memory::peek(manager + ts::taken_from_bottom, bottom) || + !memory::peek(manager + ts::taken_from_top, top) || top > result.limit || bottom > result.limit) + return result; + result.known = true; + result.taken = std::min(result.limit, bottom + (result.limit - top)); + return result; +} + +void tick() noexcept { + static ULONGLONG next{}; + static bool warned{}, full{}; + static std::uint32_t most{}; + const auto now = GetTickCount64(); + if (now < next) return; + next = now + 2000; + const auto state = use(); + if (!state.known) return; + const auto percent = static_cast(std::uint64_t{state.taken} * 100 / state.limit); + try { + // (A new high, in steps: how close a session came is worth having in a log.) + if (state.taken >= most + state.limit / 20) { + most = state.taken; + logging::log(logging::Level::info, logging::Channel::runtime, "Texture streaming: {} of {} streamed textures in use ({}%).", + state.taken, state.limit, percent); + } + if (state.taken >= state.limit && !full) { + full = true; + logging::log(logging::Level::error, logging::Channel::runtime, + "Texture streaming: all {} streamed textures are in use; the game hangs when it needs one more.", state.limit); + overlay::notify(overlay::NoticeLevel::error, "Too many textures loaded", + "The game is out of room for textures and may freeze. Uninstall some mods to play safely."); + } else if (percent >= 90 && !warned) { + warned = true; + logging::log(logging::Level::warning, logging::Channel::runtime, + "Texture streaming: {} of {} streamed textures in use ({}%); the game hangs at the limit.", state.taken, state.limit, percent); + overlay::notify(overlay::NoticeLevel::warning, "Close to the texture limit", + std::to_string(state.taken) + " of " + std::to_string(state.limit) + + " textures are loaded, and the game freezes at the limit. Do not install any more mods, or uninstall some."); + } else if (percent < 80) { + warned = full = false; // told again the next time it comes close + } + } catch (...) {} +} +} // namespace dingosdk::texture_handle_limit diff --git a/Extension/World/texture_handle_limit.h b/Extension/World/texture_handle_limit.h new file mode 100644 index 0000000..9b39d81 --- /dev/null +++ b/Extension/World/texture_handle_limit.h @@ -0,0 +1,22 @@ +#pragma once +#include + +// The game can stream 20480 textures at once and hangs when a map and its mods need more +// (Engine/Game/Build//texture_streaming.h). This raises the limit to 32764, the most a +// texture's handle can hold, by moving the manager's tables into a longer allocation before the +// game makes the manager; and it tells the player when the limit, raised or not, is close. +namespace dingosdk::texture_handle_limit { +// Checks every instruction this build is known by and changes them; once, before the game has +// made its texture streaming manager. False when the build differs or the manager is already +// there: the game keeps its own limit. +bool install(std::uintptr_t base) noexcept; + +struct Use { + bool known{}; // the manager is there + std::uint32_t limit{}; // 20480, or 32764 when raised + std::uint32_t taken{}; // slots the manager has handed out at its two ends +}; +Use use() noexcept; +// Client thread, every tick: a notice when the textures loaded come close to the limit. +void tick() noexcept; +} diff --git a/Runtime/client_tick.cpp b/Runtime/client_tick.cpp index 1b473c5..7ab7b5d 100644 --- a/Runtime/client_tick.cpp +++ b/Runtime/client_tick.cpp @@ -4,6 +4,7 @@ #include "Extension/Assets/live_mods.h" #include "Extension/Assets/map_download.h" #include "Extension/Assets/map_icons.h" +#include "Extension/World/texture_handle_limit.h" #include "Engine/Core/Log/logging.h" #include "Engine/Core/Profiling/profiler.h" #include "Extension/Settings/job_spin.h" @@ -1214,6 +1215,7 @@ void tick(std::uintptr_t client, std::uintptr_t update) { if (const auto need = dingosdk::multiplayer::take_map_need()) dingosdk::map_download::offer(*need); dingosdk::map_download::tick(); dingosdk::map_icons::tick(); + dingosdk::texture_handle_limit::tick(); } dingosdk::multiplayer::refresh_identity_lists(); // How much memory the game holds, beside what a session adds to it: said when it has diff --git a/Runtime/runtime_init.cpp b/Runtime/runtime_init.cpp index 8e6e295..727dc44 100644 --- a/Runtime/runtime_init.cpp +++ b/Runtime/runtime_init.cpp @@ -8,6 +8,8 @@ #include "Extension/Settings/replay_guard.h" #include "Extension/World/load_error_guard.h" #include "Extension/World/mesh_unload_guard.h" +#include "Extension/World/stack_check_report.h" +#include "Extension/World/texture_handle_limit.h" #include "Extension/Console/commands.h" #include "Extension/UI/ui_pointer_skip.h" #include "Engine/Game/Build/addresses.h" @@ -319,6 +321,8 @@ extern "C" __declspec(dllexport) BOOL WINAPI DingoSDKDebugInitialize() { (void)dingosdk::replay_guard::install(r.base); (void)dingosdk::mesh_unload_guard::install(r.base); (void)dingosdk::load_error_guard::install(r.base); + (void)dingosdk::texture_handle_limit::install(r.base); + (void)dingosdk::stack_check_report::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); diff --git a/cmake/Runtime.cmake b/cmake/Runtime.cmake index b282683..14660fb 100644 --- a/cmake/Runtime.cmake +++ b/cmake/Runtime.cmake @@ -72,6 +72,8 @@ add_library(dingosdk_runtime SHARED Extension/Settings/replay_guard.cpp Extension/World/load_error_guard.cpp Extension/World/mesh_unload_guard.cpp + Extension/World/stack_check_report.cpp + Extension/World/texture_handle_limit.cpp Extension/Settings/replay_drafts.cpp Extension/UI/ui_pointer_skip.cpp Extension/Settings/named_settings.cpp