#pragma once // Fix crash in MovementAgentPathFollowerVirtual when [RDI+0x48] is NULL // Crash occurs when MOVSS tries to read from [RAX+0x8C] with RAX=NULL // Address Library ID: 92556 (MovementAgentPathFollowerVirtual::sub_1115090) // // Original crash flow: // MOV RAX, [RDI+0x48] ; RAX can be NULL // LEA RDX, [RAX+0x58] // MOVSS XMM2, [RAX+0x8C] ; CRASH if RAX is NULL // // Fix: Add null check after loading RAX, skip to safe exit if NULL namespace MovementPathFollowerCrashFix { // Address Library ID for MovementAgentPathFollowerVirtual::sub_1115090 constexpr REL::RelocationID FuncID(92556, 92556); // Offsets from function start to hook point (MOV RAX, [RDI+0x48]) // SE: 0x428, AE: 0x41F, VR: 0x428 constexpr REL::VariantOffset HookOffset(0x428, 0x41F, 0x428); // Offsets from function start to skip point (safe exit label) // SE: 0x518, AE: 0x50F, VR: 0x518 constexpr REL::VariantOffset SkipOffset(0x518, 0x50F, 0x518); // Hook size: MOV (4 bytes) + LEA (4 bytes) = 8 bytes constexpr std::size_t HookSize = 8; inline void Install() { const REL::Relocation funcBase{ FuncID }; const std::uintptr_t hookAddr = funcBase.address() + HookOffset.offset(); const std::uintptr_t skipAddr = funcBase.address() + SkipOffset.offset(); const std::uintptr_t returnAddr = hookAddr + HookSize; // Generate the patch code using xbyak struct PatchCode : Xbyak::CodeGenerator { PatchCode(std::uintptr_t a_skipAddr, std::uintptr_t a_returnAddr) { // Original: MOV RAX, [RDI+0x48] mov(rax, qword[rdi + 0x48]); // Add null check test(rax, rax); jz("skip_label"); // Original: LEA RDX, [RAX+0x58] lea(rdx, qword[rax + 0x58]); // Jump back to original code after LEA (continue with MOVSS) jmp(ptr[rip]); dq(a_returnAddr); // Skip label - jump to safe exit point L("skip_label"); jmp(ptr[rip]); dq(a_skipAddr); } }; PatchCode patchCode(skipAddr, returnAddr); patchCode.ready(); // Allocate trampoline space and copy our patch code SKSE::AllocTrampoline(patchCode.getSize() + 14); auto& trampoline = SKSE::GetTrampoline(); void* codeCave = trampoline.allocate(patchCode.getSize()); std::memcpy(codeCave, patchCode.getCode(), patchCode.getSize()); // Write jump from original location to our patch code trampoline.write_branch<5>(hookAddr, reinterpret_cast(codeCave)); // NOP remaining bytes if (HookSize > 5) { REL::safe_fill(hookAddr + 5, REL::NOP, HookSize - 5); } const char* version = REL::Module::IsVR() ? "VR" : (REL::Module::get().version() <= REL::Version(1, 5, 97, 0) ? "SE" : "AE"); logger::info("MovementPathFollowerCrashFix: Patched {} at 0x{:X}, code cave at 0x{:X}", version, hookAddr, reinterpret_cast(codeCave)); } }