Skip to content

Commit

Permalink
make ShowOff:OnPreActivate run before NVSE OnActivate, better hook er…
Browse files Browse the repository at this point in the history
…r msg
  • Loading branch information
Demorome committed Sep 24, 2023
1 parent 01a39d6 commit 1f56c99
Show file tree
Hide file tree
Showing 6 changed files with 95 additions and 37 deletions.
98 changes: 65 additions & 33 deletions SHOWOFF-NVSE/Events/ShowOffEvents.h
Original file line number Diff line number Diff line change
Expand Up @@ -264,7 +264,7 @@ namespace OnPreActivate
{
constexpr char eventName[] = "ShowOff:OnPreActivate";

bool __fastcall HandleEvent(TESObjectREFR* activated, Actor* activator)
UInt32 __fastcall HandleEvent(TESObjectREFR* activated, Actor* activator)
{
// Will be set if opening with a key.
auto* doorAboutToBeDoubleActivated = *reinterpret_cast<TESObjectREFR**>(0x11C9350);
Expand All @@ -276,6 +276,32 @@ namespace OnPreActivate
if (containerAboutToBeDoubleActivated && containerAboutToBeDoubleActivated == activated)
return true;

// Need to check a bunch of conditions to verify event would run as if hooking from 0x573347

// Check if activating ash/goo pile.
// Old OnPreActivate didn't run for them, only their sub-activation to get the actor's inventory.
if (activated->baseForm == *reinterpret_cast<TESForm**>(0x11CA27C)
|| activated->baseForm == *reinterpret_cast<TESForm**>(0x11CA280))
{
return true;
}

if (activator == g_thePlayer)
{
if (InterfaceManager::GetSingleton()->pipBoyMode == 2)
return 2; // sneakily patches NVSE's OnActivate to not run in this instance
// Magic number 2 = will be handled in my ASM as 1, but will still early-exit the func.

if (g_thePlayer->is3rdPersonVisible != g_thePlayer->bThirdPerson)
return false; // sneakily patches NVSE's OnActivate to not run in this instance

if (g_thePlayer->GetIsChildSize(0) && StdCall<bool>(0x8859E0, activated)) // TESObjectREFR::CanChildUse
return true; // whatever, let NVSE's OnActivate run here, as a treat

if (activated->IsDestroyed() && !activated->IsActor())
return true; // NVSE's OnActivate will run here, yes, and to change it now would probably break stuff.
}

auto constexpr resultCallback = [](NVSEArrayVarInterface::Element& result, void* shouldActivateAdrr) -> bool
{
if (UInt32 &shouldActivate = *static_cast<UInt32*>(shouldActivateAdrr))
Expand All @@ -287,67 +313,72 @@ namespace OnPreActivate
};
UInt32 shouldActivate = true;
g_eventInterface->DispatchEventAlt(eventName, resultCallback, &shouldActivate, activated, activator, &shouldActivate);

#if _DEBUG
Console_Print("OnActivate HOOK - Activated: [%08X] {%s} (%s), activator: [%08X]", activated ? activated->refID : 0,
activated ? activated->GetName() : "",
activated ? activated->GetTheName() : "",
activator ? activator->refID : 0);
#endif
return shouldActivate != 0;
} //result in AL
return shouldActivate;
} //result in EAX

__HOOK Hook()
{
static UInt32 const retnAddr = 0x57334C, getBaseForm = 0x7AF430,
retnFalse = 0x573396;
static UInt32 const NormalRetnAddr = 0x57318E,
EarlyEndAddr = 0x5737A3;
enum
{
activator = 8
};
_asm
{
//== Do regular code
mov [ebp - 0x12C], ecx
mov [ebp - 1], 0

//== Our code
// Check if this instance of TESObjectREFR::Activate was called by Activate func.
// (We don't want the event to run for that)
mov eax, dword ptr [ebp + 4] //rtn addr
cmp eax, 0x5B5B1D //one of the return addresses to Activate_Execute
je doNormal
je DoNormal
cmp eax, 0x5B5B4D
je doNormal
// Check if called by fade-in func (avoid running twice for load door activation)
je DoNormal
// Check if called by fade-in func (avoid running event twice for load door activation)
cmp eax, 0x8FEEB9
je doNormal
// Avoid running if opening locked ref - already had the opportunity to prevent activation before lockpick menu.
je DoNormal
// Avoid running event if opening locked ref - already had the opportunity to prevent activation before lockpick menu.
cmp eax, 0x78F95B
je doNormal
// Avoid running from StartConversation script func call
je DoNormal
// Avoid running event from StartConversation script func call
cmp eax, 0x5C8961
je doNormal
je DoNormal
cmp eax, 0x5C8A01
je doNormal
// Avoid running from SetOpenState script func call
je DoNormal
// Avoid running event from SetOpenState script func call
cmp eax, 0x5CED9B
je doNormal
je DoNormal
cmp eax, 0x5CEDCB
je doNormal
je DoNormal

pushad //unknown what the __fastcall function will preserve, so store everything.
mov edx, dword ptr [ebp + activator]
call HandleEvent
test al, al
popad
jnz doNormal
jmp retnFalse
cmp eax, 2
je EarlyExitButRetnTrue
test eax, eax
jnz DoNormal
// else, return false
XOR al, al
jmp EarlyEndAddr

doNormal:
call getBaseForm
jmp retnAddr
EarlyExitButRetnTrue:
mov al, 1
jmp EarlyEndAddr

DoNormal:
jmp NormalRetnAddr
}
}

void WriteHook()
{
WriteRelJump(0x573347, (UInt32)Hook);
// OnPreActivate hook used to be at 0x573347, but was moved higher to allow preventing NVSE's OnActivate from running.
// NVSE OnActivate hook = 0x57318E
WriteRelJump(0x573184, (UInt32)Hook);
}
}

Expand Down Expand Up @@ -679,6 +710,7 @@ namespace PreActivateInventoryItem
{
// New hook to handle special plugin/scripted inv item activation.
// Replace "call TESForm::GetTypeID"
// TODO: Fix conflit with Tweaks @
HandleSpecialActivation::EquipItem::g_Hook.WriteRelCall(0x88C65C, (UInt32)HandleSpecialActivation::EquipItem::Hook);
}
}
Expand Down
5 changes: 5 additions & 0 deletions SHOWOFF-NVSE/GameUI.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@

//All code in here copied from JIP

InterfaceManager* InterfaceManager::GetSingleton(void)
{
return *reinterpret_cast<InterfaceManager**>(0x11D8A80);
}

__declspec(naked) UInt32 InterfaceManager::GetTopVisibleMenuID()
{
__asm
Expand Down
2 changes: 1 addition & 1 deletion SHOWOFF-NVSE/ShowOffNVSE.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@
// Plugin Stuff
IDebugLog g_Log; // file will be open after NVSE plugin load
HMODULE g_ShowOffHandle;
constexpr UInt32 g_PluginVersion = 170;
constexpr UInt32 g_PluginVersion = 171;

// Allows modmakers to toggle ShowOff's debug messages for some of its functions.
#ifdef _DEBUG
Expand Down
2 changes: 1 addition & 1 deletion nvse/GameObjects.h
Original file line number Diff line number Diff line change
Expand Up @@ -860,7 +860,7 @@ class PlayerCharacter : public Character
UInt32 unk63C[3]; // 63C
UInt8 byte648; // 648
UInt8 byte649; // 649
bool byte64A; // 64A = not FirstPerson
bool is3rdPersonVisible; // 64A = not FirstPerson, credits to lStewieAl for the name.
bool is3rdPerson; // 64B
bool bThirdPerson; // 64C
UInt8 byte64D; // 64D
Expand Down
15 changes: 14 additions & 1 deletion nvse/SafeWrite.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,17 @@ void __stdcall WriteRelJump(UInt32 jumpSrc, UInt32 jumpTgt)

void __stdcall WriteRelCall(UInt32 jumpSrc, UInt32 jumpTgt)
{
if (*reinterpret_cast<UInt8*>(jumpSrc) != 0xE8) {
_ERROR("Cannot write call hook at address 0x%X; another hook made it no longer a function call.", jumpSrc);
if (!g_showedRuntimeHookConflictError)
{
MessageBoxA(nullptr, "Showoff xNVSE: Error detected while trying to hook the game; please report what you see in the log file.",
"ShowOff xNVSE", MB_ICONEXCLAMATION);
g_showedRuntimeHookConflictError = true;
}
return;
}

// ask to be able to modify the desired region of code (normally programs prevent code being modified by other code to prevent exploits)
UInt32 oldProtect;
VirtualProtect((void*)jumpSrc, 5, PAGE_EXECUTE_READWRITE, &oldProtect);
Expand Down Expand Up @@ -135,4 +146,6 @@ UInt8* GetParentBasePtr(void* addressOfReturnAddress, bool lambda)
basePtr = *reinterpret_cast<UInt8**>(basePtr);
#endif
return *reinterpret_cast<UInt8**>(basePtr);
}
}

bool g_showedRuntimeHookConflictError = false;
10 changes: 9 additions & 1 deletion nvse/SafeWrite.h
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ UInt32 GetRelJumpAddr(UInt32 jumpSrc);

UInt8* GetParentBasePtr(void* addressOfReturnAddress, bool lambda = false);

extern bool g_showedRuntimeHookConflictError;

// Stores the function-to-call before overwriting it, to allow calling the overwritten function after our hook is over.
class CallDetour
{
Expand All @@ -50,7 +52,13 @@ class CallDetour
void WriteRelCall(UInt32 jumpSrc, UInt32 jumpTgt)
{
if (*reinterpret_cast<UInt8*>(jumpSrc) != 0xE8) {
_ERROR("Cannot write detour at %X; jumpSrc is not a function call.", jumpSrc);
_ERROR("Cannot write detour hook at address 0x%X; another hook made it no longer a function call.", jumpSrc);
if (!g_showedRuntimeHookConflictError)
{
MessageBoxA(nullptr, "Showoff xNVSE: Error detected while trying to detour-hook the game; please report what you see in the log file.",
"ShowOff xNVSE", MB_ICONEXCLAMATION);
g_showedRuntimeHookConflictError = true;
}
return;
}
overwritten_addr = GetRelJumpAddr(jumpSrc);
Expand Down

0 comments on commit 1f56c99

Please sign in to comment.