Summary
hkbClipGenerator_Generate is missing a null/sentinel guard after ActiveClip::GetLastBlendingClipGenerator. When the blending-clip list is non-empty but the stored deque element is stale/recycled, the returned hkbClipGenerator* is garbage and is passed straight to the game trampoline, causing an EXCEPTION_ACCESS_VIOLATION.
Affected versions
- 3.0.2 and 3.1.5 confirmed (both ship the byte-identical function at
0x180148CB0 in their respective DLLs).
Crash signature
EXCEPTION_ACCESS_VIOLATION at SkyrimSE.exe+0A0C98A — movaps [rdx+rax*1], xmm2
OpenAnimationReplacer.dll+0148D5C in frame [1]
Root cause (decompile)
// hkbClipGenerator_Generate @ 0x180148CB0 (from PDB)
if ((this != NULL) && (this->_blendingClipGenerators.size() != 0) && (param_1->_padding_ != 0xc)) {
phVar1 = ActiveClip::GetLastBlendingClipGenerator(this);
// NO NULL/SENTINEL GUARD HERE
(*(code *)_hkbClipGenerator_Generate._impl)(phVar1, param_2, param_3, param_4);
return;
}
GetLastBlendingClipGenerator reads from a std::deque backing array. If the stored hkbClipGenerator* is a dangling/recycled pointer (deque inconsistency or use-after-free on a recycled clip object), the returned value is garbage. It is passed directly to the game trampoline → the game does a clip-ID lookup → returns the Havok -1 sentinel → that is used as a buffer write offset → AV.
Suggested fix
phVar1 = ActiveClip::GetLastBlendingClipGenerator(this);
if (phVar1 == nullptr) {
// fall through to the normal (non-blending) path below
} else {
(*(code *)_hkbClipGenerator_Generate._impl)(phVar1, param_2, param_3, param_4);
return;
}
(A stronger guard would additionally validate the returned pointer's vtable against the known hkbClipGenerator vtable before dispatch, to also reject a recycled object that still holds a committed-but-freed vtable.)
Context
- Observed on Skyrim SE 1.6.1170 (Apostasy 3.2.2 modlist).
- Repro NPC: a DragonCultDraugr playing
AlcoveLayingIdle in Snow Veil Sanctum.
- Behavior-graph mods present on the affected NPC type: MCO Draugr Boss Addon and TDC - Draugr MCO (both modify the draugr behavior graph).
- A downstream consumer (a MinHook-based
GetLast interceptor that validates the returned pointer before dispatch) has been confirmed to eliminate the crash in this configuration, which corroborates the null/stale-pointer diagnosis.
Summary
hkbClipGenerator_Generateis missing a null/sentinel guard afterActiveClip::GetLastBlendingClipGenerator. When the blending-clip list is non-empty but the stored deque element is stale/recycled, the returnedhkbClipGenerator*is garbage and is passed straight to the game trampoline, causing anEXCEPTION_ACCESS_VIOLATION.Affected versions
0x180148CB0in their respective DLLs).Crash signature
EXCEPTION_ACCESS_VIOLATIONatSkyrimSE.exe+0A0C98A—movaps [rdx+rax*1], xmm2OpenAnimationReplacer.dll+0148D5Cin frame [1]Root cause (decompile)
GetLastBlendingClipGeneratorreads from astd::dequebacking array. If the storedhkbClipGenerator*is a dangling/recycled pointer (deque inconsistency or use-after-free on a recycled clip object), the returned value is garbage. It is passed directly to the game trampoline → the game does a clip-ID lookup → returns the Havok-1sentinel → that is used as a buffer write offset → AV.Suggested fix
(A stronger guard would additionally validate the returned pointer's vtable against the known
hkbClipGeneratorvtable before dispatch, to also reject a recycled object that still holds a committed-but-freed vtable.)Context
AlcoveLayingIdlein Snow Veil Sanctum.GetLastinterceptor that validates the returned pointer before dispatch) has been confirmed to eliminate the crash in this configuration, which corroborates the null/stale-pointer diagnosis.