Fix startup crash and silent plugin loss on game version 1.5 (#15) - #16
Open
WizGery wants to merge 1 commit into
Open
Fix startup crash and silent plugin loss on game version 1.5 (#15)#16WizGery wants to merge 1 commit into
WizGery wants to merge 1 commit into
Conversation
Closes the failure reported in issue xiaoxiao921#15: with d3d12.dll present, the game crashes on startup on release_1_5 and LogOutput.log is created but stays empty. Verified working in-game against release_1_5_1164953_841. There are three separate bugs behind that symptom, each hidden by the one before it. Only the third is version-specific; the first two are latent and would resurface on any future patch that breaks a signature. 1. kcd2_address::rip() dereferenced without a null check scan() returns {0} when a pattern is not found, but rip() did *(int32_t*)(m_value + offset) unconditionally, so a missing signature was read as memory at address 1. That killed the process during init, before the logger flushed -- which is why the reported log is 0 bytes. offset(), rip() and get_call() now propagate zero, so a broken signature leaves a detectable null instead of a dead process. 2. A missing signature aborted the whole of kcd2_init The 35 hook-registration blocks handle a failed scan with if (!ptr) { LOG(ERROR) << "Failed to find X"; return; } and that return exits kcd2_init entirely, not just the block. On 1.5 the CPhysicalEntity_ctor signature no longer matches, so init aborted there and never reached the ExecuteBuffer hook ~500 lines below -- the hook that creates the Lua manager. The result was that no plugin loaded at all, with nothing in the log connecting the two. The script-system hook block is moved above the blocks that can abort. Making all 35 blocks independent (wrapping them in immediately-invoked lambdas) was tried first and rejected: it left the game hanging, because it installed hooks that had never run on this version and which index vtables that are null when their signatures are broken. Moving one block keeps the rest of the loader in the state it was tested in. 3. hook_CryScriptSystem_Init dereferenced a possibly-null unique_ptr It assumed g_lua_manager_instance already held the early-main VM manager. When that VM is never created the pointer is null, and the dereference crashes during CryScriptSystem::Init with the trace pointing into d3d12.dll and RAX=0. This only became reachable once init got far enough to register the hook. 4. Four Lua signatures no longer match on 1.5 These live in the unguarded scan block, so before fix 1 they were fatal: luaV_execute RVA 0x714cb8 lua_gettable RVA 0x718a6c lua_custom_alloc RVA 0x717620 lua_concat RVA 0x1323cd0 luaV_execute is representative of why these break. 1.3 expected 48 8B C4 48 89 58 ?? 89 50 ?? 55 56 57 41 54 ... and 1.5 has 48 8B C4 89 50 10 53 55 56 57 41 54 ... The compiler replaced a mov [rax+8],rbx with a later push rbx. One instruction, and the signature stops matching. New patterns mask their rel32 displacements so they survive the linker moving code around. luaV_execute was located via the string "'for' initial value", which in Lua 5.1 appears only inside that function; lua_concat by its disassembly (movsxd rbx,edx / cmp ebx,2 / test edx,edx, the exact shape of lua_concat(lua_State*, int)). Note for anyone redoing these on a future version: WHGame.dll is built with PGO, so MSVC splits functions into hot/cold fragments with separate .pdata entries and no prologue. Scanning backwards for int3 padding lands mid-instruction. Follow the UNW_FLAG_CHAININFO chain in UNWIND_INFO to get the real function start. Also adds a post-scan report listing by name any Lua pointer that failed to resolve, so the next breakage says which signature to redo instead of failing silently.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #15. Verified working in-game against
release_1_5_1164953_841.Symptom
With
d3d12.dllpresent the game crashes on startup, andLogOutput.logis created but stays empty.Cause
Four separate bugs, each hidden behind the previous one. Only two are version-specific — the other two are latent and will resurface on any future patch that breaks a signature.
1.
kcd2_address::rip()dereferenced without a null checkscan()returns{0}when a pattern isn't found, butrip()did*(int32_t*)(m_value + offset)unconditionally. A missing signature was therefore read as memory at address 1, killing the process during init before the logger flushed — which is exactly why the reported log is 0 bytes.offset(),rip()andget_call()now propagate zero, so a broken signature leaves a detectable null instead of a dead process.2. A missing signature aborted the whole of
kcd2_initThe 35 hook-registration blocks handle a failed scan with:
That
returnexitskcd2_initentirely, not just the block. On 1.5 theCPhysicalEntity_ctorsignature no longer matches, so init aborted there and never reached theExecuteBufferhook ~500 lines below — the hook that creates the Lua manager.The result was that no plugin loaded at all, with nothing in the log connecting a 3D-engine signature to the entire plugin system disappearing.
The script-system hook block is moved above the blocks that can abort.
3.
hook_CryScriptSystem_Initdereferenced a possibly-nullunique_ptrIt assumed
g_lua_manager_instancealready held the early-main VM manager. When that VM is never created the pointer is null, and the dereference crashes duringCryScriptSystem::Initwith the trace pointing intod3d12.dllandRAX=0. Only reachable once init got far enough to register the hook.4. Four Lua signatures no longer match on 1.5
These live in the unguarded scan block, so before fix 1 they were fatal:
luaV_execute0x714cb8lua_gettable0x718a6clua_custom_alloc0x717620lua_concat0x1323cd0luaV_executeshows why these break:The compiler replaced a
mov [rax+8],rbxwith a laterpush rbx. One instruction, and the signature stops matching.The new patterns mask their rel32 displacements so they survive the linker moving code.
Also added
A post-scan report listing by name any Lua pointer that failed to resolve:
So the next breakage says which signature needs redoing instead of failing silently.
Note for redoing signatures on a future version
WHGame.dllis built with PGO, so MSVC splits functions into hot/cold fragments with separate.pdataentries and no prologue. Scanning backwards forint3padding lands mid-instruction. Follow theUNW_FLAG_CHAININFOchain inUNWIND_INFOto reach the real function start — that is what turnedluaV_executefrom "pattern not found" into a concrete address.lua_concatwas identified from its disassembly (movsxd rbx,edx/cmp ebx,2/test edx,edx, the exact shape oflua_concat(lua_State*, int)), andluaV_executevia the string'for' initial value, which in Lua 5.1 appears only inside that function.Testing
Built with MSVC 2022 / CMake, installed as
d3d12.dllnext toKingdomCome.exe. Game reaches the main menu and loads saves normally;19/19Lua signatures resolve; plugins load and run;rom.memory.scan_pattern,rom.pointerreads,rom.gameaccess androm.memory.dynamic_hookall verified working from a test plugin.The only remaining error is
Failed to find CPhysicalEntity_ctor, which is one of the 3D-engine signatures and now degrades gracefully.