Jump to content

NVSE - Patching instructions earlier than SafeWrite8()?


FiftyTifty

Recommended Posts

Initially, I just wanted to patch the values for the GetHit imagespace modifier. Which I'm able to do, so there's no screen blurring when getting hit. Now, I want to skip the instructions that actually call that imagespace modifier. I found them in IDA, but NVSE won't let me patch them early enough in execution, so by the time they're changed, the engine has already done something with the instructions, so changing them this late in execution does nothing.

 

Looked at iStewieAI's tweaks plugin, and I noticed that he uses several things. One, is that his early execution functions have the prefix __fastcall. Another, is that he uses a custom function called addDeferredInitHandler(). Lastly, he uses two other functions instead of SafeWrite(), which are WriteRelCall() and WriteRelJump(). But there's no documentation on how to use those.

 

Here's the NVSEPlugin_Load() function:

 

 

bool NVSEPlugin_Load(const NVSEInterface * nvse)
{
    _MESSAGE("load");

    g_pluginHandle = nvse->GetPluginHandle();

    // save the NVSEinterface in cas we need it later
    SaveNVSE = (NVSEInterface *)nvse;

    // register to receive messages from NVSE
    NVSEMessagingInterface* msgIntfc = (NVSEMessagingInterface*)nvse->QueryInterface(kInterface_Messaging);
    msgIntfc->RegisterListener(g_pluginHandle, "NVSE", MessageHandler);
    g_msg = msgIntfc;

    g_script = (NVSEScriptInterface*)nvse->QueryInterface(kInterface_Script);

    //Begin
    addDeferredInitHandler();
    PatchGetHitIMAD();
    //End

    return true;
}

 

Here's my plugin's function:

 

 

void __fastcall PatchGetHitIMAD() {
    //This function must be called at PreLoadGame()
    _MESSAGE("Starting");

    addDeferredInitHandler();
    //Address of near JNZ = 0046D7A4
    SafeWrite8(0x46D7A4, 0xE9);
    SafeWrite8(0x46D7A5, 0xB6);
    SafeWrite8(0x46D7A6, 0x00);
    SafeWrite8(0x46D7A9, 0x90);
}

 

Original bytes: 0F 85 B5 00 00 00

New bytes: E9 B6 00 00 90

 

What those new instructions do, is they change the JNZ (a two byte opcode) into JMP (one byte opcode) by replacing the first opcode, and changing the second byte into the offset. The third byte, the original offset, is turned into 00. And the last byte is changed into a NOP for good measure.

 

While the instructions are patched in memory, after the engine's been started, they're no longer used so changing them does nothing to the game itself. Even with __fastcall and addDeferredInitHandler() being called in NVSEPlugin_Load(). The bytes changed, but the instructions are no longer referenced by the game engine.

 

So how do I go about doing this?

Link to comment
Share on other sites

Oh my, I forgot to report back.

 

So I patched the instructions of FalloutNV.exe directly, but that didn't make any difference whatsoever. So the instructions that actually apply the GetHit IMAD aren't readily available with IDA nor CheatEngine. I settled for just setting the duration of the imagespace modifier to 0, but I'd much rather have it never be called in the first place. That's a possible performance gain that we're missing out on.

Link to comment
Share on other sites

__fastcall is a calling convention in which the first two arguments to the function are passed in ECX and EDX registers. It has nothing to do with performance.

The game uses the __thiscall convention in many places, in which the first argument is passed in ECX- in order to replace that call, you can create a class and have a member method as __thiscall, but itâs easier to just declare the function as fastcall and give a dummy argument for EDX.

 

If youâre doing your patching in NVSEPlugin_Load, it is already being executed before any of the game is loaded.

 

You likely wonât need a deferred initialisation. I use one because there are some things that I need to patch after the game has created all the singletons.

 

Check that the code you are patching is definitely called (breakpoint it).

 

WriteRelJump(x, y) and WriteRelCall(x, y) write in a long jmp or call instruction jumping/calling from address x to address y in memory.

Edited by lStewieAl
Link to comment
Share on other sites

__fastcall is a calling convention in which the first two arguments to the function are passed in ECX and EDX registers. It has nothing to do with performance.

The game uses the __thiscall convention in many places, in which the first argument is passed in ECX- in order to replace that call, you can create a class and have a member method as __thiscall, but itâs easier to just declare the function as fastcall and give a dummy argument for EDX.

 

If youâre doing your patching in NVSEPlugin_Load, it is already being executed before any of the game is loaded.

 

You likely wonât need a deferred initialisation. I use one because there are some things that I need to patch after the game has created all the singletons.

 

Check that the code you are patching is definitely called (breakpoint it).

 

In performance, I meant that having the instructions which call the imagespace modifier skipped could bring about noticeable performance improvements in combat. And even after patching the executable directly, rather than waiting for any NVSE hooks, the instructions are completely ignored. Used IDA Pro 5.0 to find them. Loaded up FalloutNV.exe, found GetHit in the string list, found the instructions that use it, patched them, but no change in behaviour. Screenshot to demonstrate: https://i.imgur.com/NSNP04Y.png

 

That's the only result I find for it. So the code that handles applying that imagespace modifier when the player takes damage must be elsewhere, and not as easy to locate.

 

Good to know about the deferred initialization though.

Link to comment
Share on other sites

  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...