diff --git a/SHOWOFF-NVSE/Events/ShowOffEvents.h b/SHOWOFF-NVSE/Events/ShowOffEvents.h index 9a6faa4..77bfcb6 100644 --- a/SHOWOFF-NVSE/Events/ShowOffEvents.h +++ b/SHOWOFF-NVSE/Events/ShowOffEvents.h @@ -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(0x11C9350); @@ -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(0x11CA27C) + || activated->baseForm == *reinterpret_cast(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(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(shouldActivateAdrr)) @@ -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); } } @@ -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); } } diff --git a/SHOWOFF-NVSE/GameUI.cpp b/SHOWOFF-NVSE/GameUI.cpp index c7bf768..b6c5f8a 100644 --- a/SHOWOFF-NVSE/GameUI.cpp +++ b/SHOWOFF-NVSE/GameUI.cpp @@ -2,6 +2,11 @@ //All code in here copied from JIP +InterfaceManager* InterfaceManager::GetSingleton(void) +{ + return *reinterpret_cast(0x11D8A80); +} + __declspec(naked) UInt32 InterfaceManager::GetTopVisibleMenuID() { __asm diff --git a/SHOWOFF-NVSE/ShowOffNVSE.cpp b/SHOWOFF-NVSE/ShowOffNVSE.cpp index 246cd34..f11d95e 100644 --- a/SHOWOFF-NVSE/ShowOffNVSE.cpp +++ b/SHOWOFF-NVSE/ShowOffNVSE.cpp @@ -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 diff --git a/nvse/GameObjects.h b/nvse/GameObjects.h index 82c270a..4cef76e 100644 --- a/nvse/GameObjects.h +++ b/nvse/GameObjects.h @@ -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 diff --git a/nvse/SafeWrite.cpp b/nvse/SafeWrite.cpp index 31569ac..b278741 100644 --- a/nvse/SafeWrite.cpp +++ b/nvse/SafeWrite.cpp @@ -48,6 +48,17 @@ void __stdcall WriteRelJump(UInt32 jumpSrc, UInt32 jumpTgt) void __stdcall WriteRelCall(UInt32 jumpSrc, UInt32 jumpTgt) { + if (*reinterpret_cast(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); @@ -135,4 +146,6 @@ UInt8* GetParentBasePtr(void* addressOfReturnAddress, bool lambda) basePtr = *reinterpret_cast(basePtr); #endif return *reinterpret_cast(basePtr); -} \ No newline at end of file +} + +bool g_showedRuntimeHookConflictError = false; \ No newline at end of file diff --git a/nvse/SafeWrite.h b/nvse/SafeWrite.h index 66f4667..1ccdb37 100644 --- a/nvse/SafeWrite.h +++ b/nvse/SafeWrite.h @@ -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 { @@ -50,7 +52,13 @@ class CallDetour void WriteRelCall(UInt32 jumpSrc, UInt32 jumpTgt) { if (*reinterpret_cast(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);