CrosshairPickData crash fix
This commit is contained in:
parent
c59ed9dcac
commit
d263cf9e84
@ -16,6 +16,7 @@
|
|||||||
#include "Patches/PluginsTxtPatch.h"
|
#include "Patches/PluginsTxtPatch.h"
|
||||||
#include "Patches/FormTypeCollisionDetector.h"
|
#include "Patches/FormTypeCollisionDetector.h"
|
||||||
#include "Patches/GogVramLeakFix.h"
|
#include "Patches/GogVramLeakFix.h"
|
||||||
|
#include "Patches/CrosshairPickDataCrashFix.h"
|
||||||
|
|
||||||
using namespace SKSE;
|
using namespace SKSE;
|
||||||
|
|
||||||
@ -30,7 +31,8 @@ static std::map<std::string, bool> g_settings{
|
|||||||
{ "ForceBorderless", true },
|
{ "ForceBorderless", true },
|
||||||
{ "AttachLightHitEffectCrashFix", true },
|
{ "AttachLightHitEffectCrashFix", true },
|
||||||
{ "AutoScaleHeroMenu", true },
|
{ "AutoScaleHeroMenu", true },
|
||||||
{ "GogVramLeakFix", true }
|
{ "GogVramLeakFix", true },
|
||||||
|
{ "CrosshairPickDataCrashFix", true }
|
||||||
};
|
};
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
@ -80,6 +82,10 @@ namespace {
|
|||||||
logger::info("Installing light attach crash fix...");
|
logger::info("Installing light attach crash fix...");
|
||||||
AttachLightHitEffectCrash::Install();
|
AttachLightHitEffectCrash::Install();
|
||||||
}
|
}
|
||||||
|
if (g_settings.at("CrosshairPickDataCrashFix")) {
|
||||||
|
logger::info("Installing crosshair pick data crash fix...");
|
||||||
|
CrosshairPickDataCrashFix::Install();
|
||||||
|
}
|
||||||
if (g_settings.at("StayAtSystemPage")) {
|
if (g_settings.at("StayAtSystemPage")) {
|
||||||
if (const auto pluginInfo = GetLoadInterface()->GetPluginInfo("StayAtSystemPage"); pluginInfo) {
|
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);
|
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