CrosshairPickData crash fix
This commit is contained in:
parent
c59ed9dcac
commit
d263cf9e84
@ -16,6 +16,7 @@
|
||||
#include "Patches/PluginsTxtPatch.h"
|
||||
#include "Patches/FormTypeCollisionDetector.h"
|
||||
#include "Patches/GogVramLeakFix.h"
|
||||
#include "Patches/CrosshairPickDataCrashFix.h"
|
||||
|
||||
using namespace SKSE;
|
||||
|
||||
@ -30,7 +31,8 @@ static std::map<std::string, bool> g_settings{
|
||||
{ "ForceBorderless", true },
|
||||
{ "AttachLightHitEffectCrashFix", true },
|
||||
{ "AutoScaleHeroMenu", true },
|
||||
{ "GogVramLeakFix", true }
|
||||
{ "GogVramLeakFix", true },
|
||||
{ "CrosshairPickDataCrashFix", true }
|
||||
};
|
||||
|
||||
namespace {
|
||||
@ -80,6 +82,10 @@ namespace {
|
||||
logger::info("Installing light attach crash fix...");
|
||||
AttachLightHitEffectCrash::Install();
|
||||
}
|
||||
if (g_settings.at("CrosshairPickDataCrashFix")) {
|
||||
logger::info("Installing crosshair pick data crash fix...");
|
||||
CrosshairPickDataCrashFix::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);
|
||||
|
||||
146
source/Enderal DLL/src/Patches/CrosshairPickDataCrashFix.h
Normal file
146
source/Enderal DLL/src/Patches/CrosshairPickDataCrashFix.h
Normal file
@ -0,0 +1,146 @@
|
||||
#pragma once
|
||||
|
||||
// Fix crash in CrosshairPickData::Pick collision result quicksort
|
||||
//
|
||||
// The vanilla quicksort has a backward partition loop that lacks bounds checking.
|
||||
// When collision data becomes corrupted or is modified during sorting (race condition),
|
||||
// the loop can iterate past array boundaries, causing an access violation.
|
||||
//
|
||||
// Crash signature: SkyrimSE.exe+73CA0A (SE) / +7D4C4B (AE) / +7675AA (VR)
|
||||
// Callstack: CrossHairPickData::Pick -> quicksort -> crash
|
||||
//
|
||||
// This fix adds bounds checking to the backward partition loop to prevent
|
||||
// the index from going below the left boundary.
|
||||
//
|
||||
// Address Library IDs:
|
||||
// SE 1.5.97: 42697 (function at 0x14073C960, patch at offset 0xA7)
|
||||
// AE 1.6.*: 43870 (function varies, patch at offset 0xA8)
|
||||
// VR 1.4.15: Raw address 0x140767500 (patch at offset 0xA7)
|
||||
|
||||
namespace CrosshairPickDataCrashFix
|
||||
{
|
||||
// The problematic loop in the quicksort function (SE example):
|
||||
//
|
||||
// LAB_14073ca01: ; Backward partition loop
|
||||
// DEC EDX ; Decrement right index
|
||||
// LEA RCX, [RCX + -0x40] ; Move pointer back by element size
|
||||
// DEC RSI ; Decrement current position
|
||||
// COMISS XMM4, dword ptr [RCX] ; Compare pivot with element <-- CRASH
|
||||
// JC LAB_14073ca01 ; Loop while pivot < element
|
||||
// MOV dword ptr [RSP + 0x20], EDX ; Store result
|
||||
//
|
||||
// The fix patches from DEC RSI to after JC with a jump to our safe loop
|
||||
|
||||
struct SafeBackwardLoop : Xbyak::CodeGenerator
|
||||
{
|
||||
SafeBackwardLoop(std::uintptr_t a_loopBody, std::uintptr_t a_loopExit)
|
||||
{
|
||||
Xbyak::Label loopBodyLabel;
|
||||
Xbyak::Label loopExitLabel;
|
||||
Xbyak::Label continueLoop;
|
||||
Xbyak::Label exitLoop;
|
||||
|
||||
// Recreate: DEC RSI
|
||||
dec(rsi);
|
||||
|
||||
// NEW: Bounds check - exit if RSI < RDI (current index < left boundary)
|
||||
cmp(rsi, rdi);
|
||||
jl(exitLoop);
|
||||
|
||||
// Recreate: COMISS XMM4, [RCX]
|
||||
comiss(xmm4, dword[rcx]);
|
||||
|
||||
// Recreate: JC back to loop body
|
||||
// JC = jump if carry = jump if XMM4 < [RCX]
|
||||
jc(continueLoop);
|
||||
|
||||
// Fall through to exit
|
||||
L(exitLoop);
|
||||
// Recreate: MOV [RSP + 0x20], EDX
|
||||
mov(dword[rsp + 0x20], edx);
|
||||
// Jump back to original code after the patched section
|
||||
jmp(ptr[rip + loopExitLabel]);
|
||||
|
||||
L(continueLoop);
|
||||
// Jump back to start of loop body
|
||||
jmp(ptr[rip + loopBodyLabel]);
|
||||
|
||||
L(loopBodyLabel);
|
||||
dq(a_loopBody);
|
||||
|
||||
L(loopExitLabel);
|
||||
dq(a_loopExit);
|
||||
}
|
||||
};
|
||||
|
||||
inline void InstallPatch(std::uintptr_t a_funcBase, std::size_t a_patchOffset)
|
||||
{
|
||||
// We patch from DEC RSI through JC (8 bytes total)
|
||||
// SE/VR: offset 0xA7, size 8 (A7-AE inclusive, then AF is MOV which we recreate)
|
||||
// AE: offset 0xA8, size 8 (A8-AF inclusive, then B0 is next instruction)
|
||||
constexpr std::size_t patchSize = 8;
|
||||
|
||||
std::uintptr_t patchAddr = a_funcBase + a_patchOffset;
|
||||
|
||||
// Calculate loop body and exit addresses
|
||||
// Loop body is 6 bytes before DEC RSI (at DEC EDX)
|
||||
std::uintptr_t loopBody = patchAddr - 6;
|
||||
// Loop exit is 6 bytes after patch start (after the JC instruction, at CMP RSI, RDI)
|
||||
std::uintptr_t loopExit = patchAddr + 6;
|
||||
|
||||
// Generate the safe loop code
|
||||
SafeBackwardLoop patch(loopBody, loopExit);
|
||||
patch.ready();
|
||||
|
||||
// Allocate trampoline space for our code cave
|
||||
auto& trampoline = SKSE::GetTrampoline();
|
||||
SKSE::AllocTrampoline(patch.getSize() + 14);
|
||||
|
||||
void* codeCave = trampoline.allocate(patch);
|
||||
|
||||
// Write a JMP from the patch location to our code cave
|
||||
// We have 8 bytes available, JMP rel32 uses 5, so we NOP the rest
|
||||
std::uint8_t jmpCode[8];
|
||||
jmpCode[0] = 0xE9; // JMP rel32
|
||||
|
||||
std::int32_t relOffset = static_cast<std::int32_t>(
|
||||
reinterpret_cast<std::uintptr_t>(codeCave) - (patchAddr + 5));
|
||||
std::memcpy(&jmpCode[1], &relOffset, 4);
|
||||
|
||||
// Fill remaining bytes with NOPs
|
||||
jmpCode[5] = 0x90; // NOP
|
||||
jmpCode[6] = 0x90; // NOP
|
||||
jmpCode[7] = 0x90; // NOP
|
||||
|
||||
REL::safe_write(patchAddr, jmpCode, patchSize);
|
||||
|
||||
logger::info("CrosshairPickData crash fix installed at {:X}", patchAddr);
|
||||
}
|
||||
|
||||
inline void Install()
|
||||
{
|
||||
if (REL::Module::IsVR()) {
|
||||
// VR 1.4.15: Raw address, no Address Library entry for this function
|
||||
// Function at 0x140767500, patch at offset 0xA7
|
||||
constexpr std::uintptr_t vrFuncAddr = 0x140767500;
|
||||
constexpr std::size_t vrPatchOffset = 0xA7;
|
||||
|
||||
// Verify we're at the expected VR version
|
||||
if (REL::Module::get().version() == REL::Version(1, 4, 15, 0)) {
|
||||
InstallPatch(vrFuncAddr, vrPatchOffset);
|
||||
} else {
|
||||
logger::info("CrosshairPickData crash fix: Unknown VR version, skipping");
|
||||
}
|
||||
} else if (REL::Module::get().version() >= REL::Version(1, 6, 0, 0)) {
|
||||
// AE versions: ID 43870, patch at offset 0xA8
|
||||
REL::Relocation<std::uintptr_t> funcBase{ REL::ID(43870) };
|
||||
constexpr std::size_t aePatchOffset = 0xA8;
|
||||
InstallPatch(funcBase.address(), aePatchOffset);
|
||||
} else {
|
||||
// SE 1.5.97: ID 42697, patch at offset 0xA7
|
||||
REL::Relocation<std::uintptr_t> funcBase{ REL::ID(42697) };
|
||||
constexpr std::size_t sePatchOffset = 0xA7;
|
||||
InstallPatch(funcBase.address(), sePatchOffset);
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user