About this mod
Detects broken papyrus scripts stuck in recursion and prevents huge framerate lag
- Requirements
- Permissions and credits
- Changelogs
Install with your preferred mod manager like any other mod!
For VR users: make sure to download the latest version of VR address library (v0.53 and up) as this mod requires the changes made in that version.
The bug
Short and simple explanation
Since a function calling itself hundreds of times is usually related to a bug and not intended behavior, this mod breaks the recursion after a thousand calls. This way your framerate will not be affected, and the already broken mod that would've caused framerate lag will simply remain broken.
Super Technical Explanation
Every time the script code requires a function call, the stack for the currently running code has to push a new stackframe if the function call is valid. I haven't narrowed down exactly why or how, but this pushing of the new stackframe takes longer when there are more stackframes in the stack already. This all occurs in `AttemptFunctionCall` on the VirtualMachine. This method runs a bunch of checks on making sure the function call is valid (correct arg types, etc.), then allocates a new stackframe before actually calling the function.
To break the recursion, this mod hooks right before the initial check that checks if the stack is valid, the function call query is valid and the function call info returns a valid result. At that hook point, this mod adds an additional check for recursion:
static RE::BSFixedString* thunk(std::uint64_t unk0, RE::BSScript::Stack* a_stack, std::uint64_t* a_funcCallQuery)
{
if (a_stack != nullptr && a_stack->frames > 1000) { // checks if we are already 1000 frames (calls) deep
logger::info("Detected 1000+ recursive call! Will throw error in papyrus log"); // print error in custom log
*a_funcCallQuery = 0; // sets funcCallQuery to "nullptr" to trick skyrim into thinking the function call is invalid
}
return func(unk0, a_stack, a_funcCallQuery);
}
If this mod detects the 1000+ calls, it'll set "a_funcCallQuery" to "nullptr" which makes Skyrim think the call is invalid:
if ( !stack // decompiled skyrim code
|| !funcCallQuery->_ptr
|| !(funcCallQuery->_ptr->vftable_IFuncCallQuery_0->GetFunctionCallInfo_8)(
funcCallQuery->_ptr,
&a_callType,
&a_objectTypeInfo,
&functionName,
&a_variable,
&a_arg5) )
{
sub_141240A50(stack, "Unable to obtain function call information - returning None", 2u, vNoFuncStr, 1024);
v14 = vNoFuncStr;
goto LABEL_105;
}
then this mod hook the log function that normally would say "Unable to obtain function call information - returning None" to instead say "StackFrameOverFlow exception, function call exceeded 1000 call stack limit - returning None", so anyone seeing the logs will know it was this mod breaking the recursion
Want to witness the bug for yourself?
WARNING: Do not leave the demo enabled for actual gameplay! The demo WILL kill your framerate without this mod, and is intended for demonstration purposes only
CREDITS
Powerofthree for mentoring and their wonderful clean source code to reference
Fuzzles for being a facilitator, people pusher and tester to rule out any edge cases
Entire RE discord for helping me get my start in SKSE modding
VersuchDrei for helping with the initial write-up
Doodlezoid for the popup suggestion and modpage changes
Source code is available on my GitHub.