Added MovementPathFollowerCrashFix

This commit is contained in:
Eddoursul 2026-01-27 05:37:40 +01:00
commit 13ff172e61
2 changed files with 88 additions and 0 deletions

View File

@ -17,6 +17,7 @@
#include "Patches/FormTypeCollisionDetector.h"
#include "Patches/GogVramLeakFix.h"
#include "Patches/CrosshairPickDataCrashFix.h"
#include "Patches/MovementPathFollowerCrashFix.h"
using namespace SKSE;
@ -86,6 +87,8 @@ namespace {
logger::info("Installing crosshair pick data crash fix...");
CrosshairPickDataCrashFix::Install();
}
logger::info("Installing movement path follower crash fix...");
MovementPathFollowerCrashFix::Install();
if (g_settings.at("StayAtSystemPage")) {
if (const auto pluginInfo = GetLoadInterface()->GetPluginInfo("StayAtSystemPage"); pluginInfo) {
MessageBoxW(NULL, L"Stay At The System Page is already included in Enderal, please, disable it.", L"Enderal SE Error", MB_OK | MB_ICONERROR);

View File

@ -0,0 +1,85 @@
#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<std::uintptr_t> 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<std::uintptr_t>(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<std::uintptr_t>(codeCave));
}
}