mirror of
https://github.com/goatcorp/Dalamud.git
synced 2025-12-12 18:27:23 +01:00
Add import hooks and ditch wndproc hooks (#902)
This commit is contained in:
parent
6ad647235c
commit
ac7f3ea5d8
11 changed files with 1251 additions and 404 deletions
|
|
@ -180,8 +180,23 @@ static TFnGetInputDeviceManager* GetGetInputDeviceManager(HWND hwnd) {
|
||||||
|
|
||||||
void xivfixes::prevent_devicechange_crashes(bool bApply) {
|
void xivfixes::prevent_devicechange_crashes(bool bApply) {
|
||||||
static const char* LogTag = "[xivfixes:prevent_devicechange_crashes]";
|
static const char* LogTag = "[xivfixes:prevent_devicechange_crashes]";
|
||||||
static std::optional<hooks::import_hook<decltype(CreateWindowExA)>> s_hookCreateWindowExA;
|
|
||||||
static std::optional<hooks::wndproc_hook> s_hookWndProc;
|
// We hook RegisterClassExA, since if the game has already launched (inject mode), the very crash we're trying to fix cannot happen at that point.
|
||||||
|
static std::optional<hooks::import_hook<decltype(RegisterClassExA)>> s_hookRegisterClassExA;
|
||||||
|
static WNDPROC s_pfnGameWndProc = nullptr;
|
||||||
|
|
||||||
|
// We're intentionally leaking memory for this one.
|
||||||
|
static const auto s_pfnBinder = static_cast<WNDPROC>(VirtualAlloc(nullptr, 64, MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE));
|
||||||
|
static const auto s_pfnAlternativeWndProc = static_cast<WNDPROC>([](HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) -> LRESULT {
|
||||||
|
if (uMsg == WM_DEVICECHANGE && wParam == DBT_DEVNODES_CHANGED) {
|
||||||
|
if (!GetGetInputDeviceManager(hWnd)()) {
|
||||||
|
logging::I("{} WndProc(0x{:X}, WM_DEVICECHANGE, DBT_DEVNODES_CHANGED, {}) called but the game does not have InputDeviceManager initialized; doing nothing.", LogTag, reinterpret_cast<size_t>(hWnd), lParam);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return s_pfnGameWndProc(hWnd, uMsg, wParam, lParam);
|
||||||
|
});
|
||||||
|
|
||||||
if (bApply) {
|
if (bApply) {
|
||||||
if (!g_startInfo.BootEnabledGameFixes.contains("prevent_devicechange_crashes")) {
|
if (!g_startInfo.BootEnabledGameFixes.contains("prevent_devicechange_crashes")) {
|
||||||
|
|
@ -189,47 +204,38 @@ void xivfixes::prevent_devicechange_crashes(bool bApply) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
s_hookCreateWindowExA.emplace("user32.dll!CreateWindowExA (prevent_devicechange_crashes)", "user32.dll", "CreateWindowExA", 0);
|
s_hookRegisterClassExA.emplace("user32.dll!RegisterClassExA (prevent_devicechange_crashes)", "user32.dll", "RegisterClassExA", 0);
|
||||||
s_hookCreateWindowExA->set_detour([](DWORD dwExStyle, LPCSTR lpClassName, LPCSTR lpWindowName, DWORD dwStyle, int X, int Y, int nWidth, int nHeight, HWND hWndParent, HMENU hMenu, HINSTANCE hInstance, LPVOID lpParam)->HWND {
|
s_hookRegisterClassExA->set_detour([](const WNDCLASSEXA* pWndClassExA)->ATOM {
|
||||||
const auto hWnd = s_hookCreateWindowExA->call_original(dwExStyle, lpClassName, lpWindowName, dwStyle, X, Y, nWidth, nHeight, hWndParent, hMenu, hInstance, lpParam);
|
// If this RegisterClassExA isn't initiated by the game executable, we do not handle it.
|
||||||
|
if (pWndClassExA->hInstance != GetModuleHandleW(nullptr))
|
||||||
|
return s_hookRegisterClassExA->call_original(pWndClassExA);
|
||||||
|
|
||||||
if (!hWnd
|
// If this RegisterClassExA isn't about FFXIVGAME, the game's main window, we do not handle it.
|
||||||
|| hInstance != g_hGameInstance
|
if (strncmp(pWndClassExA->lpszClassName, "FFXIVGAME", 10) != 0)
|
||||||
|| 0 != strcmp(lpClassName, "FFXIVGAME"))
|
return s_hookRegisterClassExA->call_original(pWndClassExA);
|
||||||
return hWnd;
|
|
||||||
|
|
||||||
logging::I(R"({} CreateWindow(0x{:08X}, "{}", "{}", 0x{:08X}, {}, {}, {}, {}, 0x{:X}, 0x{:X}, 0x{:X}, 0x{:X}) called; unhooking CreateWindowExA and hooking WndProc.)",
|
// push qword ptr [rip+1]
|
||||||
LogTag, dwExStyle, lpClassName, lpWindowName, dwStyle, X, Y, nWidth, nHeight, reinterpret_cast<size_t>(hWndParent), reinterpret_cast<size_t>(hMenu), reinterpret_cast<size_t>(hInstance), reinterpret_cast<size_t>(lpParam));
|
// ret
|
||||||
|
// <pointer to new wndproc>
|
||||||
|
memcpy(s_pfnBinder, "\xFF\x35\x01\x00\x00\x00\xC3", 7);
|
||||||
|
*reinterpret_cast<void**>(reinterpret_cast<char*>(s_pfnBinder) + 7) = s_pfnAlternativeWndProc;
|
||||||
|
|
||||||
s_hookWndProc.emplace("FFXIVGAME:WndProc (prevent_devicechange_crashes)", hWnd);
|
s_pfnGameWndProc = pWndClassExA->lpfnWndProc;
|
||||||
s_hookWndProc->set_detour([](HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) -> LRESULT {
|
|
||||||
|
|
||||||
if (uMsg == WM_DEVICECHANGE && wParam == DBT_DEVNODES_CHANGED) {
|
WNDCLASSEXA wndClassExA = *pWndClassExA;
|
||||||
if (!GetGetInputDeviceManager(hWnd)()) {
|
wndClassExA.lpfnWndProc = s_pfnBinder;
|
||||||
logging::I("{} WndProc(0x{:X}, WM_DEVICECHANGE, DBT_DEVNODES_CHANGED, {}) called but the game does not have InputDeviceManager initialized; doing nothing.", LogTag, reinterpret_cast<size_t>(hWnd), lParam);
|
return s_hookRegisterClassExA->call_original(&wndClassExA);
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return s_hookWndProc->call_original(hWnd, uMsg, wParam, lParam);
|
|
||||||
});
|
|
||||||
|
|
||||||
return hWnd;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
logging::I("{} Enable", LogTag);
|
logging::I("{} Enable", LogTag);
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
if (s_hookCreateWindowExA) {
|
if (s_hookRegisterClassExA) {
|
||||||
logging::I("{} Disable CreateWindowExA", LogTag);
|
logging::I("{} Disable RegisterClassExA", LogTag);
|
||||||
s_hookCreateWindowExA.reset();
|
s_hookRegisterClassExA.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
// This will effectively revert any other WndProc alterations, including Dalamud.
|
*reinterpret_cast<void**>(reinterpret_cast<char*>(s_pfnBinder) + 7) = s_pfnGameWndProc;
|
||||||
if (s_hookWndProc) {
|
|
||||||
logging::I("{} Disable WndProc", LogTag);
|
|
||||||
s_hookWndProc.reset();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ using Dalamud.Hooking;
|
||||||
using Dalamud.Interface.Internal;
|
using Dalamud.Interface.Internal;
|
||||||
using Dalamud.Logging.Internal;
|
using Dalamud.Logging.Internal;
|
||||||
using ImGuiNET;
|
using ImGuiNET;
|
||||||
|
using PInvoke;
|
||||||
using static Dalamud.NativeFunctions;
|
using static Dalamud.NativeFunctions;
|
||||||
|
|
||||||
namespace Dalamud.Game.Gui.Internal
|
namespace Dalamud.Game.Gui.Internal
|
||||||
|
|
@ -23,24 +23,14 @@ namespace Dalamud.Game.Gui.Internal
|
||||||
{
|
{
|
||||||
private static readonly ModuleLog Log = new("IME");
|
private static readonly ModuleLog Log = new("IME");
|
||||||
|
|
||||||
private IntPtr interfaceHandle;
|
|
||||||
private IntPtr wndProcPtr;
|
|
||||||
private IntPtr oldWndProcPtr;
|
|
||||||
private WndProcDelegate wndProcDelegate;
|
|
||||||
private AsmHook imguiTextInputCursorHook;
|
private AsmHook imguiTextInputCursorHook;
|
||||||
private Vector2* cursorPos;
|
private Vector2* cursorPos;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Initializes a new instance of the <see cref="DalamudIME"/> class.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="tag">Tag.</param>
|
|
||||||
[ServiceManager.ServiceConstructor]
|
[ServiceManager.ServiceConstructor]
|
||||||
private DalamudIME()
|
private DalamudIME()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
private delegate long WndProcDelegate(IntPtr hWnd, uint msg, ulong wParam, long lParam);
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets a value indicating whether the module is enabled.
|
/// Gets a value indicating whether the module is enabled.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -64,81 +54,23 @@ namespace Dalamud.Game.Gui.Internal
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
if (this.oldWndProcPtr != IntPtr.Zero)
|
|
||||||
{
|
|
||||||
SetWindowLongPtrW(this.interfaceHandle, WindowLongType.WndProc, this.oldWndProcPtr);
|
|
||||||
this.oldWndProcPtr = IntPtr.Zero;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.imguiTextInputCursorHook?.Dispose();
|
this.imguiTextInputCursorHook?.Dispose();
|
||||||
Marshal.FreeHGlobal((IntPtr)this.cursorPos);
|
Marshal.FreeHGlobal((IntPtr)this.cursorPos);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Get the position of the cursor.
|
/// Processes window messages.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns>The position of the cursor.</returns>
|
/// <param name="hWnd">Handle of the window.</param>
|
||||||
internal Vector2 GetCursorPos()
|
/// <param name="msg">Type of window message.</param>
|
||||||
{
|
/// <param name="wParam">wParam.</param>
|
||||||
return new Vector2(this.cursorPos->X, this.cursorPos->Y);
|
/// <param name="lParam">lParam.</param>
|
||||||
}
|
/// <returns>Return value, if not doing further processing.</returns>
|
||||||
|
public unsafe IntPtr? ProcessWndProcW(IntPtr hWnd, User32.WindowMessage msg, void* wParam, void* lParam)
|
||||||
[ServiceManager.CallWhenServicesReady]
|
|
||||||
private void ContinueConstruction(InterfaceManager.InterfaceManagerWithScene interfaceManagerWithScene)
|
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
this.wndProcDelegate = this.WndProcDetour;
|
if (ImGui.GetCurrentContext() != IntPtr.Zero && ImGui.GetIO().WantTextInput)
|
||||||
this.interfaceHandle = interfaceManagerWithScene.Manager.WindowHandlePtr;
|
|
||||||
this.wndProcPtr = Marshal.GetFunctionPointerForDelegate(this.wndProcDelegate);
|
|
||||||
this.oldWndProcPtr = SetWindowLongPtrW(this.interfaceHandle, WindowLongType.WndProc, this.wndProcPtr);
|
|
||||||
|
|
||||||
var module = Process.GetCurrentProcess().Modules.Cast<ProcessModule>().First(m => m.ModuleName == "cimgui.dll");
|
|
||||||
var scanner = new SigScanner(module);
|
|
||||||
var cursorDrawingPtr = scanner.ScanModule("F3 0F 11 75 ?? 0F 28 CF");
|
|
||||||
Log.Debug($"Found cursorDrawingPtr at {cursorDrawingPtr:X}");
|
|
||||||
|
|
||||||
this.cursorPos = (Vector2*)Marshal.AllocHGlobal(sizeof(Vector2));
|
|
||||||
this.cursorPos->X = 0f;
|
|
||||||
this.cursorPos->Y = 0f;
|
|
||||||
|
|
||||||
var asm = new[]
|
|
||||||
{
|
|
||||||
"use64",
|
|
||||||
$"push rax",
|
|
||||||
$"mov rax, {(IntPtr)this.cursorPos + sizeof(float)}",
|
|
||||||
$"movss [rax],xmm7",
|
|
||||||
$"mov rax, {(IntPtr)this.cursorPos}",
|
|
||||||
$"movss [rax],xmm6",
|
|
||||||
$"pop rax",
|
|
||||||
};
|
|
||||||
|
|
||||||
Log.Debug($"Asm Code:\n{string.Join("\n", asm)}");
|
|
||||||
this.imguiTextInputCursorHook = new AsmHook(cursorDrawingPtr, asm, "ImguiTextInputCursorHook");
|
|
||||||
this.imguiTextInputCursorHook?.Enable();
|
|
||||||
|
|
||||||
this.IsEnabled = true;
|
|
||||||
Log.Information("Enabled!");
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Log.Information(ex, "Enable failed");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ToggleWindow(bool visible)
|
|
||||||
{
|
|
||||||
if (visible)
|
|
||||||
Service<DalamudInterface>.Get().OpenImeWindow();
|
|
||||||
else
|
|
||||||
Service<DalamudInterface>.Get().CloseImeWindow();
|
|
||||||
}
|
|
||||||
|
|
||||||
private long WndProcDetour(IntPtr hWnd, uint msg, ulong wParam, long lParam)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (hWnd == this.interfaceHandle && ImGui.GetCurrentContext() != IntPtr.Zero && ImGui.GetIO().WantTextInput)
|
|
||||||
{
|
{
|
||||||
var io = ImGui.GetIO();
|
var io = ImGui.GetIO();
|
||||||
var wmsg = (WindowsMessage)msg;
|
var wmsg = (WindowsMessage)msg;
|
||||||
|
|
@ -146,17 +78,17 @@ namespace Dalamud.Game.Gui.Internal
|
||||||
switch (wmsg)
|
switch (wmsg)
|
||||||
{
|
{
|
||||||
case WindowsMessage.WM_IME_NOTIFY:
|
case WindowsMessage.WM_IME_NOTIFY:
|
||||||
switch ((IMECommand)wParam)
|
switch ((IMECommand)(IntPtr)wParam)
|
||||||
{
|
{
|
||||||
case IMECommand.ChangeCandidate:
|
case IMECommand.ChangeCandidate:
|
||||||
this.ToggleWindow(true);
|
this.ToggleWindow(true);
|
||||||
|
|
||||||
if (hWnd == IntPtr.Zero)
|
if (hWnd == IntPtr.Zero)
|
||||||
return 0;
|
return IntPtr.Zero;
|
||||||
|
|
||||||
var hIMC = ImmGetContext(hWnd);
|
var hIMC = ImmGetContext(hWnd);
|
||||||
if (hIMC == IntPtr.Zero)
|
if (hIMC == IntPtr.Zero)
|
||||||
return 0;
|
return IntPtr.Zero;
|
||||||
|
|
||||||
var size = ImmGetCandidateListW(hIMC, 0, IntPtr.Zero, 0);
|
var size = ImmGetCandidateListW(hIMC, 0, IntPtr.Zero, 0);
|
||||||
if (size == 0)
|
if (size == 0)
|
||||||
|
|
@ -225,11 +157,11 @@ namespace Dalamud.Game.Gui.Internal
|
||||||
|
|
||||||
break;
|
break;
|
||||||
case WindowsMessage.WM_IME_COMPOSITION:
|
case WindowsMessage.WM_IME_COMPOSITION:
|
||||||
if ((lParam & (long)IMEComposition.ResultStr) > 0)
|
if (((long)(IntPtr)lParam & (long)IMEComposition.ResultStr) > 0)
|
||||||
{
|
{
|
||||||
var hIMC = ImmGetContext(hWnd);
|
var hIMC = ImmGetContext(hWnd);
|
||||||
if (hIMC == IntPtr.Zero)
|
if (hIMC == IntPtr.Zero)
|
||||||
return 0;
|
return IntPtr.Zero;
|
||||||
|
|
||||||
var dwSize = ImmGetCompositionStringW(hIMC, IMEComposition.ResultStr, IntPtr.Zero, 0);
|
var dwSize = ImmGetCompositionStringW(hIMC, IMEComposition.ResultStr, IntPtr.Zero, 0);
|
||||||
var unmanagedPointer = Marshal.AllocHGlobal((int)dwSize);
|
var unmanagedPointer = Marshal.AllocHGlobal((int)dwSize);
|
||||||
|
|
@ -249,11 +181,11 @@ namespace Dalamud.Game.Gui.Internal
|
||||||
}
|
}
|
||||||
|
|
||||||
if (((long)(IMEComposition.CompStr | IMEComposition.CompAttr | IMEComposition.CompClause |
|
if (((long)(IMEComposition.CompStr | IMEComposition.CompAttr | IMEComposition.CompClause |
|
||||||
IMEComposition.CompReadAttr | IMEComposition.CompReadClause | IMEComposition.CompReadStr) & lParam) > 0)
|
IMEComposition.CompReadAttr | IMEComposition.CompReadClause | IMEComposition.CompReadStr) & (long)(IntPtr)lParam) > 0)
|
||||||
{
|
{
|
||||||
var hIMC = ImmGetContext(hWnd);
|
var hIMC = ImmGetContext(hWnd);
|
||||||
if (hIMC == IntPtr.Zero)
|
if (hIMC == IntPtr.Zero)
|
||||||
return 0;
|
return IntPtr.Zero;
|
||||||
|
|
||||||
var dwSize = ImmGetCompositionStringW(hIMC, IMEComposition.CompStr, IntPtr.Zero, 0);
|
var dwSize = ImmGetCompositionStringW(hIMC, IMEComposition.CompStr, IntPtr.Zero, 0);
|
||||||
var unmanagedPointer = Marshal.AllocHGlobal((int)dwSize);
|
var unmanagedPointer = Marshal.AllocHGlobal((int)dwSize);
|
||||||
|
|
@ -281,7 +213,62 @@ namespace Dalamud.Game.Gui.Internal
|
||||||
Log.Error(ex, "Prevented a crash in an IME hook");
|
Log.Error(ex, "Prevented a crash in an IME hook");
|
||||||
}
|
}
|
||||||
|
|
||||||
return CallWindowProcW(this.oldWndProcPtr, hWnd, msg, wParam, lParam);
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get the position of the cursor.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>The position of the cursor.</returns>
|
||||||
|
internal Vector2 GetCursorPos()
|
||||||
|
{
|
||||||
|
return new Vector2(this.cursorPos->X, this.cursorPos->Y);
|
||||||
|
}
|
||||||
|
|
||||||
|
[ServiceManager.CallWhenServicesReady]
|
||||||
|
private void ContinueConstruction(InterfaceManager.InterfaceManagerWithScene interfaceManagerWithScene)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var module = Process.GetCurrentProcess().Modules.Cast<ProcessModule>().First(m => m.ModuleName == "cimgui.dll");
|
||||||
|
var scanner = new SigScanner(module);
|
||||||
|
var cursorDrawingPtr = scanner.ScanModule("F3 0F 11 75 ?? 0F 28 CF");
|
||||||
|
Log.Debug($"Found cursorDrawingPtr at {cursorDrawingPtr:X}");
|
||||||
|
|
||||||
|
this.cursorPos = (Vector2*)Marshal.AllocHGlobal(sizeof(Vector2));
|
||||||
|
this.cursorPos->X = 0f;
|
||||||
|
this.cursorPos->Y = 0f;
|
||||||
|
|
||||||
|
var asm = new[]
|
||||||
|
{
|
||||||
|
"use64",
|
||||||
|
$"push rax",
|
||||||
|
$"mov rax, {(IntPtr)this.cursorPos + sizeof(float)}",
|
||||||
|
$"movss [rax],xmm7",
|
||||||
|
$"mov rax, {(IntPtr)this.cursorPos}",
|
||||||
|
$"movss [rax],xmm6",
|
||||||
|
$"pop rax",
|
||||||
|
};
|
||||||
|
|
||||||
|
Log.Debug($"Asm Code:\n{string.Join("\n", asm)}");
|
||||||
|
this.imguiTextInputCursorHook = new AsmHook(cursorDrawingPtr, asm, "ImguiTextInputCursorHook");
|
||||||
|
this.imguiTextInputCursorHook?.Enable();
|
||||||
|
|
||||||
|
this.IsEnabled = true;
|
||||||
|
Log.Information("Enabled!");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Log.Information(ex, "Enable failed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ToggleWindow(bool visible)
|
||||||
|
{
|
||||||
|
if (visible)
|
||||||
|
Service<DalamudInterface>.Get().OpenImeWindow();
|
||||||
|
else
|
||||||
|
Service<DalamudInterface>.Get().CloseImeWindow();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
using System;
|
using System;
|
||||||
|
using System.Diagnostics;
|
||||||
using System.Net.Sockets;
|
using System.Net.Sockets;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
|
@ -17,7 +18,7 @@ namespace Dalamud.Game.Network.Internal
|
||||||
[ServiceManager.ServiceConstructor]
|
[ServiceManager.ServiceConstructor]
|
||||||
private WinSockHandlers()
|
private WinSockHandlers()
|
||||||
{
|
{
|
||||||
this.ws2SocketHook = Hook<SocketDelegate>.FromSymbol("ws2_32.dll", "socket", this.OnSocket, true);
|
this.ws2SocketHook = Hook<SocketDelegate>.FromImport(Process.GetCurrentProcess().MainModule, "ws2_32.dll", "socket", 23, this.OnSocket);
|
||||||
this.ws2SocketHook?.Enable();
|
this.ws2SocketHook?.Enable();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
using System;
|
using System;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.IO;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
using Dalamud.Configuration.Internal;
|
using Dalamud.Configuration.Internal;
|
||||||
using Dalamud.Hooking.Internal;
|
using Dalamud.Hooking.Internal;
|
||||||
using Dalamud.Memory;
|
using Dalamud.Memory;
|
||||||
|
|
@ -13,12 +15,14 @@ namespace Dalamud.Hooking
|
||||||
/// This class is basically a thin wrapper around the LocalHook type to provide helper functions.
|
/// This class is basically a thin wrapper around the LocalHook type to provide helper functions.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <typeparam name="T">Delegate type to represents a function prototype. This must be the same prototype as original function do.</typeparam>
|
/// <typeparam name="T">Delegate type to represents a function prototype. This must be the same prototype as original function do.</typeparam>
|
||||||
public sealed class Hook<T> : IDisposable, IDalamudHook where T : Delegate
|
public class Hook<T> : IDisposable, IDalamudHook where T : Delegate
|
||||||
{
|
{
|
||||||
|
private const ulong IMAGE_ORDINAL_FLAG64 = 0x8000000000000000;
|
||||||
|
private const uint IMAGE_ORDINAL_FLAG32 = 0x80000000;
|
||||||
|
|
||||||
private readonly IntPtr address;
|
private readonly IntPtr address;
|
||||||
private readonly Reloaded.Hooks.Definitions.IHook<T> hookImpl;
|
|
||||||
private readonly MinSharp.Hook<T> minHookImpl;
|
private readonly Hook<T>? compatHookImpl;
|
||||||
private readonly bool isMinHook;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="Hook{T}"/> class.
|
/// Initializes a new instance of the <see cref="Hook{T}"/> class.
|
||||||
|
|
@ -26,6 +30,7 @@ namespace Dalamud.Hooking
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="address">A memory address to install a hook.</param>
|
/// <param name="address">A memory address to install a hook.</param>
|
||||||
/// <param name="detour">Callback function. Delegate must have a same original function prototype.</param>
|
/// <param name="detour">Callback function. Delegate must have a same original function prototype.</param>
|
||||||
|
[Obsolete("Use Hook<T>.FromAddress instead.")]
|
||||||
public Hook(IntPtr address, T detour)
|
public Hook(IntPtr address, T detour)
|
||||||
: this(address, detour, false, Assembly.GetCallingAssembly())
|
: this(address, detour, false, Assembly.GetCallingAssembly())
|
||||||
{
|
{
|
||||||
|
|
@ -39,42 +44,29 @@ namespace Dalamud.Hooking
|
||||||
/// <param name="address">A memory address to install a hook.</param>
|
/// <param name="address">A memory address to install a hook.</param>
|
||||||
/// <param name="detour">Callback function. Delegate must have a same original function prototype.</param>
|
/// <param name="detour">Callback function. Delegate must have a same original function prototype.</param>
|
||||||
/// <param name="useMinHook">Use the MinHook hooking library instead of Reloaded.</param>
|
/// <param name="useMinHook">Use the MinHook hooking library instead of Reloaded.</param>
|
||||||
|
[Obsolete("Use Hook<T>.FromAddress instead.")]
|
||||||
public Hook(IntPtr address, T detour, bool useMinHook)
|
public Hook(IntPtr address, T detour, bool useMinHook)
|
||||||
: this(address, detour, useMinHook, Assembly.GetCallingAssembly())
|
: this(address, detour, useMinHook, Assembly.GetCallingAssembly())
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="Hook{T}"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="address">A memory address to install a hook.</param>
|
||||||
|
internal Hook(IntPtr address)
|
||||||
|
{
|
||||||
|
this.address = address;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Obsolete("Use Hook<T>.FromAddress instead.")]
|
||||||
private Hook(IntPtr address, T detour, bool useMinHook, Assembly callingAssembly)
|
private Hook(IntPtr address, T detour, bool useMinHook, Assembly callingAssembly)
|
||||||
{
|
{
|
||||||
address = HookManager.FollowJmp(address);
|
address = HookManager.FollowJmp(address);
|
||||||
this.isMinHook = !EnvironmentConfiguration.DalamudForceReloaded && (EnvironmentConfiguration.DalamudForceMinHook || useMinHook);
|
if (useMinHook)
|
||||||
|
this.compatHookImpl = new MinHookHook<T>(address, detour, callingAssembly);
|
||||||
var hasOtherHooks = HookManager.Originals.ContainsKey(address);
|
|
||||||
if (!hasOtherHooks)
|
|
||||||
{
|
|
||||||
MemoryHelper.ReadRaw(address, 0x32, out var original);
|
|
||||||
HookManager.Originals[address] = original;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.address = address;
|
|
||||||
if (this.isMinHook)
|
|
||||||
{
|
|
||||||
if (!HookManager.MultiHookTracker.TryGetValue(address, out var indexList))
|
|
||||||
indexList = HookManager.MultiHookTracker[address] = new();
|
|
||||||
|
|
||||||
var index = (ulong)indexList.Count;
|
|
||||||
|
|
||||||
this.minHookImpl = new MinSharp.Hook<T>(address, detour, index);
|
|
||||||
|
|
||||||
// Add afterwards, so the hookIdent starts at 0.
|
|
||||||
indexList.Add(this);
|
|
||||||
}
|
|
||||||
else
|
else
|
||||||
{
|
this.compatHookImpl = new ReloadedHook<T>(address, detour, callingAssembly);
|
||||||
this.hookImpl = ReloadedHooks.Instance.CreateHook<T>(detour, address.ToInt64());
|
|
||||||
}
|
|
||||||
|
|
||||||
HookManager.TrackedHooks.TryAdd(Guid.NewGuid(), new HookInfo(this, detour, callingAssembly));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -94,40 +86,12 @@ namespace Dalamud.Hooking
|
||||||
/// Gets a delegate function that can be used to call the actual function as if function is not hooked yet.
|
/// Gets a delegate function that can be used to call the actual function as if function is not hooked yet.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <exception cref="ObjectDisposedException">Hook is already disposed.</exception>
|
/// <exception cref="ObjectDisposedException">Hook is already disposed.</exception>
|
||||||
public T Original
|
public virtual T Original => this.compatHookImpl != null ? this.compatHookImpl!.Original : throw new NotImplementedException();
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
this.CheckDisposed();
|
|
||||||
if (this.isMinHook)
|
|
||||||
{
|
|
||||||
return this.minHookImpl.Original;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return this.hookImpl.OriginalFunction;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets a value indicating whether or not the hook is enabled.
|
/// Gets a value indicating whether or not the hook is enabled.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool IsEnabled
|
public virtual bool IsEnabled => this.compatHookImpl != null ? this.compatHookImpl!.IsEnabled : throw new NotImplementedException();
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
this.CheckDisposed();
|
|
||||||
if (this.isMinHook)
|
|
||||||
{
|
|
||||||
return this.minHookImpl.Enabled;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return this.hookImpl.IsHookEnabled;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets a value indicating whether or not the hook has been disposed.
|
/// Gets a value indicating whether or not the hook has been disposed.
|
||||||
|
|
@ -135,15 +99,80 @@ namespace Dalamud.Hooking
|
||||||
public bool IsDisposed { get; private set; }
|
public bool IsDisposed { get; private set; }
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public string BackendName
|
public virtual string BackendName => this.compatHookImpl != null ? this.compatHookImpl!.BackendName : throw new NotImplementedException();
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
if (this.isMinHook)
|
|
||||||
return "MinHook";
|
|
||||||
|
|
||||||
return "Reloaded";
|
/// <summary>
|
||||||
|
/// Creates a hook by rewriting import table address.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="address">A memory address to install a hook.</param>
|
||||||
|
/// <param name="detour">Callback function. Delegate must have a same original function prototype.</param>
|
||||||
|
/// <returns>The hook with the supplied parameters.</returns>
|
||||||
|
public static unsafe Hook<T> FromFunctionPointerVariable(IntPtr address, T detour)
|
||||||
|
{
|
||||||
|
return new FunctionPointerVariableHook<T>(address, detour, Assembly.GetCallingAssembly());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a hook by rewriting import table address.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="module">Module to check for.</param>
|
||||||
|
/// <param name="moduleName">Name of the DLL, including the extension.</param>
|
||||||
|
/// <param name="functionName">Decorated name of the function.</param>
|
||||||
|
/// <param name="hintOrOrdinal">Hint or ordinal. 0 to unspecify.</param>
|
||||||
|
/// <param name="detour">Callback function. Delegate must have a same original function prototype.</param>
|
||||||
|
/// <returns>The hook with the supplied parameters.</returns>
|
||||||
|
public static unsafe Hook<T> FromImport(ProcessModule module, string moduleName, string functionName, uint hintOrOrdinal, T detour)
|
||||||
|
{
|
||||||
|
var pDos = (PeHeader.IMAGE_DOS_HEADER*)module.BaseAddress;
|
||||||
|
var pNt = (PeHeader.IMAGE_FILE_HEADER*)(module.BaseAddress + (int)pDos->e_lfanew + 4);
|
||||||
|
var isPe64 = pNt->SizeOfOptionalHeader == Marshal.SizeOf<PeHeader.IMAGE_OPTIONAL_HEADER64>();
|
||||||
|
PeHeader.IMAGE_DATA_DIRECTORY* pDataDirectory;
|
||||||
|
if (isPe64)
|
||||||
|
{
|
||||||
|
var pOpt = (PeHeader.IMAGE_OPTIONAL_HEADER64*)(module.BaseAddress + (int)pDos->e_lfanew + 4 + Marshal.SizeOf<PeHeader.IMAGE_FILE_HEADER>());
|
||||||
|
pDataDirectory = &pOpt->ImportTable;
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var pOpt = (PeHeader.IMAGE_OPTIONAL_HEADER32*)(module.BaseAddress + (int)pDos->e_lfanew + 4 + Marshal.SizeOf<PeHeader.IMAGE_FILE_HEADER>());
|
||||||
|
pDataDirectory = &pOpt->ImportTable;
|
||||||
|
}
|
||||||
|
|
||||||
|
var moduleNameLowerWithNullTerminator = (moduleName + "\0").ToLowerInvariant();
|
||||||
|
foreach (ref var importDescriptor in new Span<PeHeader.IMAGE_IMPORT_DESCRIPTOR>(
|
||||||
|
(PeHeader.IMAGE_IMPORT_DESCRIPTOR*)(module.BaseAddress + (int)pDataDirectory->VirtualAddress),
|
||||||
|
(int)(pDataDirectory->Size / Marshal.SizeOf<PeHeader.IMAGE_IMPORT_DESCRIPTOR>())))
|
||||||
|
{
|
||||||
|
// Having all zero values signals the end of the table. We didn't find anything.
|
||||||
|
if (importDescriptor.Characteristics == 0)
|
||||||
|
throw new MissingMethodException("Specified dll not found");
|
||||||
|
|
||||||
|
// Skip invalid entries, just in case.
|
||||||
|
if (importDescriptor.Name == 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// Name must be contained in this directory.
|
||||||
|
if (importDescriptor.Name < pDataDirectory->VirtualAddress)
|
||||||
|
continue;
|
||||||
|
var currentDllNameWithNullTerminator = Marshal.PtrToStringUTF8(
|
||||||
|
module.BaseAddress + (int)importDescriptor.Name,
|
||||||
|
(int)Math.Min(pDataDirectory->Size + pDataDirectory->VirtualAddress - importDescriptor.Name, moduleNameLowerWithNullTerminator.Length));
|
||||||
|
|
||||||
|
// Is this entry about the DLL that we're looking for? (Case insensitive)
|
||||||
|
if (currentDllNameWithNullTerminator.ToLowerInvariant() != moduleNameLowerWithNullTerminator)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (isPe64)
|
||||||
|
{
|
||||||
|
return new FunctionPointerVariableHook<T>(FromImportHelper64(module.BaseAddress, ref importDescriptor, ref *pDataDirectory, functionName, hintOrOrdinal), detour, Assembly.GetCallingAssembly());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return new FunctionPointerVariableHook<T>(FromImportHelper32(module.BaseAddress, ref importDescriptor, ref *pDataDirectory, functionName, hintOrOrdinal), detour, Assembly.GetCallingAssembly());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new MissingMethodException("Specified dll not found");
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -177,28 +206,40 @@ namespace Dalamud.Hooking
|
||||||
if (procAddress == IntPtr.Zero)
|
if (procAddress == IntPtr.Zero)
|
||||||
throw new Exception($"Could not get the address of {moduleName}::{exportName}");
|
throw new Exception($"Could not get the address of {moduleName}::{exportName}");
|
||||||
|
|
||||||
return new Hook<T>(procAddress, detour, useMinHook);
|
procAddress = HookManager.FollowJmp(procAddress);
|
||||||
|
if (useMinHook)
|
||||||
|
return new MinHookHook<T>(procAddress, detour, Assembly.GetCallingAssembly());
|
||||||
|
else
|
||||||
|
return new ReloadedHook<T>(procAddress, detour, Assembly.GetCallingAssembly());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a hook. Hooking address is inferred by calling to GetProcAddress() function.
|
||||||
|
/// The hook is not activated until Enable() method is called.
|
||||||
|
/// Please do not use MinHook unless you have thoroughly troubleshot why Reloaded does not work.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="procAddress">A memory address to install a hook.</param>
|
||||||
|
/// <param name="detour">Callback function. Delegate must have a same original function prototype.</param>
|
||||||
|
/// <param name="useMinHook">Use the MinHook hooking library instead of Reloaded.</param>
|
||||||
|
/// <returns>The hook with the supplied parameters.</returns>
|
||||||
|
public static Hook<T> FromAddress(IntPtr procAddress, T detour, bool useMinHook = false)
|
||||||
|
{
|
||||||
|
procAddress = HookManager.FollowJmp(procAddress);
|
||||||
|
if (useMinHook)
|
||||||
|
return new MinHookHook<T>(procAddress, detour, Assembly.GetCallingAssembly());
|
||||||
|
else
|
||||||
|
return new ReloadedHook<T>(procAddress, detour, Assembly.GetCallingAssembly());
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Remove a hook from the current process.
|
/// Remove a hook from the current process.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void Dispose()
|
public virtual void Dispose()
|
||||||
{
|
{
|
||||||
if (this.IsDisposed)
|
if (this.IsDisposed)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (this.isMinHook)
|
this.compatHookImpl?.Dispose();
|
||||||
{
|
|
||||||
this.minHookImpl.Dispose();
|
|
||||||
|
|
||||||
var index = HookManager.MultiHookTracker[this.address].IndexOf(this);
|
|
||||||
HookManager.MultiHookTracker[this.address][index] = null;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
this.Disable();
|
|
||||||
}
|
|
||||||
|
|
||||||
this.IsDisposed = true;
|
this.IsDisposed = true;
|
||||||
}
|
}
|
||||||
|
|
@ -206,60 +247,134 @@ namespace Dalamud.Hooking
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Starts intercepting a call to the function.
|
/// Starts intercepting a call to the function.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void Enable()
|
public virtual void Enable()
|
||||||
{
|
{
|
||||||
this.CheckDisposed();
|
if (this.compatHookImpl != null)
|
||||||
|
this.compatHookImpl.Enable();
|
||||||
if (this.isMinHook)
|
|
||||||
{
|
|
||||||
if (!this.minHookImpl.Enabled)
|
|
||||||
{
|
|
||||||
this.minHookImpl.Enable();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
else
|
||||||
{
|
throw new NotImplementedException();
|
||||||
if (!this.hookImpl.IsHookActivated)
|
|
||||||
this.hookImpl.Activate();
|
|
||||||
|
|
||||||
if (!this.hookImpl.IsHookEnabled)
|
|
||||||
this.hookImpl.Enable();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Stops intercepting a call to the function.
|
/// Stops intercepting a call to the function.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void Disable()
|
public virtual void Disable()
|
||||||
{
|
{
|
||||||
this.CheckDisposed();
|
if (this.compatHookImpl != null)
|
||||||
|
this.compatHookImpl.Disable();
|
||||||
if (this.isMinHook)
|
|
||||||
{
|
|
||||||
if (this.minHookImpl.Enabled)
|
|
||||||
{
|
|
||||||
this.minHookImpl.Disable();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
else
|
||||||
{
|
throw new NotImplementedException();
|
||||||
if (!this.hookImpl.IsHookActivated)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (this.hookImpl.IsHookEnabled)
|
|
||||||
this.hookImpl.Disable();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Check if this object has been disposed already.
|
/// Check if this object has been disposed already.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private void CheckDisposed()
|
protected void CheckDisposed()
|
||||||
{
|
{
|
||||||
if (this.IsDisposed)
|
if (this.IsDisposed)
|
||||||
{
|
{
|
||||||
throw new ObjectDisposedException(message: "Hook is already disposed", null);
|
throw new ObjectDisposedException(message: "Hook is already disposed", null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static unsafe IntPtr FromImportHelper32(IntPtr baseAddress, ref PeHeader.IMAGE_IMPORT_DESCRIPTOR desc, ref PeHeader.IMAGE_DATA_DIRECTORY dir, string functionName, uint hintOrOrdinal)
|
||||||
|
{
|
||||||
|
var importLookupsOversizedSpan = new Span<uint>((uint*)(baseAddress + (int)desc.OriginalFirstThunk), (int)((dir.Size - desc.OriginalFirstThunk) / Marshal.SizeOf<int>()));
|
||||||
|
var importAddressesOversizedSpan = new Span<uint>((uint*)(baseAddress + (int)desc.FirstThunk), (int)((dir.Size - desc.FirstThunk) / Marshal.SizeOf<int>()));
|
||||||
|
|
||||||
|
var functionNameWithNullTerminator = functionName + "\0";
|
||||||
|
for (int i = 0, i_ = Math.Min(importLookupsOversizedSpan.Length, importAddressesOversizedSpan.Length); i < i_ && importLookupsOversizedSpan[i] != 0 && importAddressesOversizedSpan[i] != 0; i++)
|
||||||
|
{
|
||||||
|
var importLookup = importLookupsOversizedSpan[i];
|
||||||
|
|
||||||
|
// Is this entry importing by ordinals? A lot of socket functions are the case.
|
||||||
|
if ((importLookup & IMAGE_ORDINAL_FLAG32) != 0)
|
||||||
|
{
|
||||||
|
var ordinal = importLookup & ~IMAGE_ORDINAL_FLAG32;
|
||||||
|
|
||||||
|
// Is this the entry?
|
||||||
|
if (hintOrOrdinal == 0 || ordinal != hintOrOrdinal)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// Is this entry not importing by ordinals, and are we using hint exclusively to find the entry?
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var hint = Marshal.ReadInt16(baseAddress + (int)importLookup);
|
||||||
|
|
||||||
|
if (functionName.Length > 0)
|
||||||
|
{
|
||||||
|
// Is this the entry?
|
||||||
|
if (hint != hintOrOrdinal)
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Name must be contained in this directory.
|
||||||
|
var currentFunctionNameWithNullTerminator = Marshal.PtrToStringUTF8(
|
||||||
|
baseAddress + (int)importLookup + 2,
|
||||||
|
(int)Math.Min(dir.VirtualAddress + dir.Size - (uint)baseAddress - importLookup - 2, (uint)functionNameWithNullTerminator.Length));
|
||||||
|
|
||||||
|
// Is this entry about the function that we're looking for?
|
||||||
|
if (currentFunctionNameWithNullTerminator != functionNameWithNullTerminator)
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return baseAddress + (int)desc.FirstThunk + (i * Marshal.SizeOf<int>());
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new MissingMethodException("Specified method not found");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static unsafe IntPtr FromImportHelper64(IntPtr baseAddress, ref PeHeader.IMAGE_IMPORT_DESCRIPTOR desc, ref PeHeader.IMAGE_DATA_DIRECTORY dir, string functionName, uint hintOrOrdinal)
|
||||||
|
{
|
||||||
|
var importLookupsOversizedSpan = new Span<ulong>((ulong*)(baseAddress + (int)desc.OriginalFirstThunk), (int)((dir.Size - desc.OriginalFirstThunk) / Marshal.SizeOf<ulong>()));
|
||||||
|
var importAddressesOversizedSpan = new Span<ulong>((ulong*)(baseAddress + (int)desc.FirstThunk), (int)((dir.Size - desc.FirstThunk) / Marshal.SizeOf<ulong>()));
|
||||||
|
|
||||||
|
var functionNameWithNullTerminator = functionName + "\0";
|
||||||
|
for (int i = 0, i_ = Math.Min(importLookupsOversizedSpan.Length, importAddressesOversizedSpan.Length); i < i_ && importLookupsOversizedSpan[i] != 0 && importAddressesOversizedSpan[i] != 0; i++)
|
||||||
|
{
|
||||||
|
var importLookup = importLookupsOversizedSpan[i];
|
||||||
|
|
||||||
|
// Is this entry importing by ordinals? A lot of socket functions are the case.
|
||||||
|
if ((importLookup & IMAGE_ORDINAL_FLAG64) != 0)
|
||||||
|
{
|
||||||
|
var ordinal = importLookup & ~IMAGE_ORDINAL_FLAG64;
|
||||||
|
|
||||||
|
// Is this the entry?
|
||||||
|
if (hintOrOrdinal == 0 || ordinal != hintOrOrdinal)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// Is this entry not importing by ordinals, and are we using hint exclusively to find the entry?
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var hint = Marshal.ReadInt16(baseAddress + (int)importLookup);
|
||||||
|
|
||||||
|
if (functionName.Length == 0)
|
||||||
|
{
|
||||||
|
// Is this the entry?
|
||||||
|
if (hint != hintOrOrdinal)
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Name must be contained in this directory.
|
||||||
|
var currentFunctionNameWithNullTerminator = Marshal.PtrToStringUTF8(
|
||||||
|
baseAddress + (int)importLookup + 2,
|
||||||
|
(int)Math.Min((ulong)dir.VirtualAddress + dir.Size - (ulong)baseAddress - importLookup - 2, (ulong)functionNameWithNullTerminator.Length));
|
||||||
|
|
||||||
|
// Is this entry about the function that we're looking for?
|
||||||
|
if (currentFunctionNameWithNullTerminator != functionNameWithNullTerminator)
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return baseAddress + (int)desc.FirstThunk + (i * Marshal.SizeOf<ulong>());
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new MissingMethodException("Specified method not found");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
117
Dalamud/Hooking/Internal/FunctionPointerVariableHook.cs
Normal file
117
Dalamud/Hooking/Internal/FunctionPointerVariableHook.cs
Normal file
|
|
@ -0,0 +1,117 @@
|
||||||
|
using System;
|
||||||
|
using System.ComponentModel;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using Dalamud.Memory;
|
||||||
|
|
||||||
|
namespace Dalamud.Hooking.Internal
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Manages a hook with MinHook.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">Delegate type to represents a function prototype. This must be the same prototype as original function do.</typeparam>
|
||||||
|
internal class FunctionPointerVariableHook<T> : Hook<T> where T : Delegate
|
||||||
|
{
|
||||||
|
private readonly IntPtr pfnOriginal;
|
||||||
|
private readonly T originalDelegate;
|
||||||
|
private readonly T detourDelegate;
|
||||||
|
|
||||||
|
private bool enabled = false;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="FunctionPointerVariableHook{T}"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="address">A memory address to install a hook.</param>
|
||||||
|
/// <param name="detour">Callback function. Delegate must have a same original function prototype.</param>
|
||||||
|
/// <param name="callingAssembly">Calling assembly.</param>
|
||||||
|
internal FunctionPointerVariableHook(IntPtr address, T detour, Assembly callingAssembly)
|
||||||
|
: base(address)
|
||||||
|
{
|
||||||
|
var hasOtherHooks = HookManager.Originals.ContainsKey(this.Address);
|
||||||
|
if (!hasOtherHooks)
|
||||||
|
{
|
||||||
|
MemoryHelper.ReadRaw(this.Address, 0x32, out var original);
|
||||||
|
HookManager.Originals[this.Address] = original;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!HookManager.MultiHookTracker.TryGetValue(this.Address, out var indexList))
|
||||||
|
indexList = HookManager.MultiHookTracker[this.Address] = new();
|
||||||
|
|
||||||
|
this.pfnOriginal = Marshal.ReadIntPtr(this.Address);
|
||||||
|
this.originalDelegate = Marshal.GetDelegateForFunctionPointer<T>(this.pfnOriginal);
|
||||||
|
this.detourDelegate = detour;
|
||||||
|
|
||||||
|
// Add afterwards, so the hookIdent starts at 0.
|
||||||
|
indexList.Add(this);
|
||||||
|
|
||||||
|
HookManager.TrackedHooks.TryAdd(Guid.NewGuid(), new HookInfo(this, detour, callingAssembly));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override T Original
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
this.CheckDisposed();
|
||||||
|
return this.originalDelegate;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override bool IsEnabled
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
this.CheckDisposed();
|
||||||
|
return this.enabled;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override string BackendName => "MinHook";
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override void Dispose()
|
||||||
|
{
|
||||||
|
if (this.IsDisposed)
|
||||||
|
return;
|
||||||
|
|
||||||
|
this.Disable();
|
||||||
|
|
||||||
|
var index = HookManager.MultiHookTracker[this.Address].IndexOf(this);
|
||||||
|
HookManager.MultiHookTracker[this.Address][index] = null;
|
||||||
|
|
||||||
|
base.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override void Enable()
|
||||||
|
{
|
||||||
|
this.CheckDisposed();
|
||||||
|
|
||||||
|
if (!this.enabled)
|
||||||
|
{
|
||||||
|
if (!NativeFunctions.VirtualProtect(this.Address, (UIntPtr)Marshal.SizeOf<IntPtr>(), MemoryProtection.ExecuteReadWrite, out var oldProtect))
|
||||||
|
throw new Win32Exception(Marshal.GetLastWin32Error());
|
||||||
|
|
||||||
|
Marshal.WriteIntPtr(this.Address, Marshal.GetFunctionPointerForDelegate(this.detourDelegate));
|
||||||
|
NativeFunctions.VirtualProtect(this.Address, (UIntPtr)Marshal.SizeOf<IntPtr>(), oldProtect, out _);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override void Disable()
|
||||||
|
{
|
||||||
|
this.CheckDisposed();
|
||||||
|
|
||||||
|
if (this.enabled)
|
||||||
|
{
|
||||||
|
if (!NativeFunctions.VirtualProtect(this.Address, (UIntPtr)Marshal.SizeOf<IntPtr>(), MemoryProtection.ExecuteReadWrite, out var oldProtect))
|
||||||
|
throw new Win32Exception(Marshal.GetLastWin32Error());
|
||||||
|
|
||||||
|
Marshal.WriteIntPtr(this.Address, this.pfnOriginal);
|
||||||
|
NativeFunctions.VirtualProtect(this.Address, (UIntPtr)Marshal.SizeOf<IntPtr>(), oldProtect, out _);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
104
Dalamud/Hooking/Internal/MinHookHook.cs
Normal file
104
Dalamud/Hooking/Internal/MinHookHook.cs
Normal file
|
|
@ -0,0 +1,104 @@
|
||||||
|
using System;
|
||||||
|
using System.Reflection;
|
||||||
|
|
||||||
|
using Dalamud.Memory;
|
||||||
|
|
||||||
|
namespace Dalamud.Hooking.Internal
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Manages a hook with MinHook.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">Delegate type to represents a function prototype. This must be the same prototype as original function do.</typeparam>
|
||||||
|
internal class MinHookHook<T> : Hook<T> where T : Delegate
|
||||||
|
{
|
||||||
|
private readonly MinSharp.Hook<T> minHookImpl;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="MinHookHook{T}"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="address">A memory address to install a hook.</param>
|
||||||
|
/// <param name="detour">Callback function. Delegate must have a same original function prototype.</param>
|
||||||
|
/// <param name="callingAssembly">Calling assembly.</param>
|
||||||
|
internal MinHookHook(IntPtr address, T detour, Assembly callingAssembly)
|
||||||
|
: base(address)
|
||||||
|
{
|
||||||
|
var hasOtherHooks = HookManager.Originals.ContainsKey(this.Address);
|
||||||
|
if (!hasOtherHooks)
|
||||||
|
{
|
||||||
|
MemoryHelper.ReadRaw(this.Address, 0x32, out var original);
|
||||||
|
HookManager.Originals[this.Address] = original;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!HookManager.MultiHookTracker.TryGetValue(this.Address, out var indexList))
|
||||||
|
indexList = HookManager.MultiHookTracker[this.Address] = new();
|
||||||
|
|
||||||
|
var index = (ulong)indexList.Count;
|
||||||
|
|
||||||
|
this.minHookImpl = new MinSharp.Hook<T>(this.Address, detour, index);
|
||||||
|
|
||||||
|
// Add afterwards, so the hookIdent starts at 0.
|
||||||
|
indexList.Add(this);
|
||||||
|
|
||||||
|
HookManager.TrackedHooks.TryAdd(Guid.NewGuid(), new HookInfo(this, detour, callingAssembly));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override T Original
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
this.CheckDisposed();
|
||||||
|
return this.minHookImpl.Original;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override bool IsEnabled
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
this.CheckDisposed();
|
||||||
|
return this.minHookImpl.Enabled;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override string BackendName => "MinHook";
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override void Dispose()
|
||||||
|
{
|
||||||
|
if (this.IsDisposed)
|
||||||
|
return;
|
||||||
|
|
||||||
|
this.minHookImpl.Dispose();
|
||||||
|
|
||||||
|
var index = HookManager.MultiHookTracker[this.Address].IndexOf(this);
|
||||||
|
HookManager.MultiHookTracker[this.Address][index] = null;
|
||||||
|
|
||||||
|
base.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override void Enable()
|
||||||
|
{
|
||||||
|
this.CheckDisposed();
|
||||||
|
|
||||||
|
if (!this.minHookImpl.Enabled)
|
||||||
|
{
|
||||||
|
this.minHookImpl.Enable();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override void Disable()
|
||||||
|
{
|
||||||
|
this.CheckDisposed();
|
||||||
|
|
||||||
|
if (this.minHookImpl.Enabled)
|
||||||
|
{
|
||||||
|
this.minHookImpl.Disable();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
396
Dalamud/Hooking/Internal/PeHeader.cs
Normal file
396
Dalamud/Hooking/Internal/PeHeader.cs
Normal file
|
|
@ -0,0 +1,396 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
#pragma warning disable
|
||||||
|
namespace Dalamud.Hooking.Internal
|
||||||
|
{
|
||||||
|
internal class PeHeader
|
||||||
|
{
|
||||||
|
public struct IMAGE_DOS_HEADER
|
||||||
|
{
|
||||||
|
public UInt16 e_magic;
|
||||||
|
public UInt16 e_cblp;
|
||||||
|
public UInt16 e_cp;
|
||||||
|
public UInt16 e_crlc;
|
||||||
|
public UInt16 e_cparhdr;
|
||||||
|
public UInt16 e_minalloc;
|
||||||
|
public UInt16 e_maxalloc;
|
||||||
|
public UInt16 e_ss;
|
||||||
|
public UInt16 e_sp;
|
||||||
|
public UInt16 e_csum;
|
||||||
|
public UInt16 e_ip;
|
||||||
|
public UInt16 e_cs;
|
||||||
|
public UInt16 e_lfarlc;
|
||||||
|
public UInt16 e_ovno;
|
||||||
|
public UInt16 e_res_0;
|
||||||
|
public UInt16 e_res_1;
|
||||||
|
public UInt16 e_res_2;
|
||||||
|
public UInt16 e_res_3;
|
||||||
|
public UInt16 e_oemid;
|
||||||
|
public UInt16 e_oeminfo;
|
||||||
|
public UInt16 e_res2_0;
|
||||||
|
public UInt16 e_res2_1;
|
||||||
|
public UInt16 e_res2_2;
|
||||||
|
public UInt16 e_res2_3;
|
||||||
|
public UInt16 e_res2_4;
|
||||||
|
public UInt16 e_res2_5;
|
||||||
|
public UInt16 e_res2_6;
|
||||||
|
public UInt16 e_res2_7;
|
||||||
|
public UInt16 e_res2_8;
|
||||||
|
public UInt16 e_res2_9;
|
||||||
|
public UInt32 e_lfanew;
|
||||||
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
|
public struct IMAGE_DATA_DIRECTORY
|
||||||
|
{
|
||||||
|
public UInt32 VirtualAddress;
|
||||||
|
public UInt32 Size;
|
||||||
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||||
|
public struct IMAGE_OPTIONAL_HEADER32
|
||||||
|
{
|
||||||
|
public UInt16 Magic;
|
||||||
|
public Byte MajorLinkerVersion;
|
||||||
|
public Byte MinorLinkerVersion;
|
||||||
|
public UInt32 SizeOfCode;
|
||||||
|
public UInt32 SizeOfInitializedData;
|
||||||
|
public UInt32 SizeOfUninitializedData;
|
||||||
|
public UInt32 AddressOfEntryPoint;
|
||||||
|
public UInt32 BaseOfCode;
|
||||||
|
public UInt32 BaseOfData;
|
||||||
|
public UInt32 ImageBase;
|
||||||
|
public UInt32 SectionAlignment;
|
||||||
|
public UInt32 FileAlignment;
|
||||||
|
public UInt16 MajorOperatingSystemVersion;
|
||||||
|
public UInt16 MinorOperatingSystemVersion;
|
||||||
|
public UInt16 MajorImageVersion;
|
||||||
|
public UInt16 MinorImageVersion;
|
||||||
|
public UInt16 MajorSubsystemVersion;
|
||||||
|
public UInt16 MinorSubsystemVersion;
|
||||||
|
public UInt32 Win32VersionValue;
|
||||||
|
public UInt32 SizeOfImage;
|
||||||
|
public UInt32 SizeOfHeaders;
|
||||||
|
public UInt32 CheckSum;
|
||||||
|
public UInt16 Subsystem;
|
||||||
|
public UInt16 DllCharacteristics;
|
||||||
|
public UInt32 SizeOfStackReserve;
|
||||||
|
public UInt32 SizeOfStackCommit;
|
||||||
|
public UInt32 SizeOfHeapReserve;
|
||||||
|
public UInt32 SizeOfHeapCommit;
|
||||||
|
public UInt32 LoaderFlags;
|
||||||
|
public UInt32 NumberOfRvaAndSizes;
|
||||||
|
|
||||||
|
public IMAGE_DATA_DIRECTORY ExportTable;
|
||||||
|
public IMAGE_DATA_DIRECTORY ImportTable;
|
||||||
|
public IMAGE_DATA_DIRECTORY ResourceTable;
|
||||||
|
public IMAGE_DATA_DIRECTORY ExceptionTable;
|
||||||
|
public IMAGE_DATA_DIRECTORY CertificateTable;
|
||||||
|
public IMAGE_DATA_DIRECTORY BaseRelocationTable;
|
||||||
|
public IMAGE_DATA_DIRECTORY Debug;
|
||||||
|
public IMAGE_DATA_DIRECTORY Architecture;
|
||||||
|
public IMAGE_DATA_DIRECTORY GlobalPtr;
|
||||||
|
public IMAGE_DATA_DIRECTORY TLSTable;
|
||||||
|
public IMAGE_DATA_DIRECTORY LoadConfigTable;
|
||||||
|
public IMAGE_DATA_DIRECTORY BoundImport;
|
||||||
|
public IMAGE_DATA_DIRECTORY IAT;
|
||||||
|
public IMAGE_DATA_DIRECTORY DelayImportDescriptor;
|
||||||
|
public IMAGE_DATA_DIRECTORY CLRRuntimeHeader;
|
||||||
|
public IMAGE_DATA_DIRECTORY Reserved;
|
||||||
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||||
|
public struct IMAGE_OPTIONAL_HEADER64
|
||||||
|
{
|
||||||
|
public UInt16 Magic;
|
||||||
|
public Byte MajorLinkerVersion;
|
||||||
|
public Byte MinorLinkerVersion;
|
||||||
|
public UInt32 SizeOfCode;
|
||||||
|
public UInt32 SizeOfInitializedData;
|
||||||
|
public UInt32 SizeOfUninitializedData;
|
||||||
|
public UInt32 AddressOfEntryPoint;
|
||||||
|
public UInt32 BaseOfCode;
|
||||||
|
public UInt64 ImageBase;
|
||||||
|
public UInt32 SectionAlignment;
|
||||||
|
public UInt32 FileAlignment;
|
||||||
|
public UInt16 MajorOperatingSystemVersion;
|
||||||
|
public UInt16 MinorOperatingSystemVersion;
|
||||||
|
public UInt16 MajorImageVersion;
|
||||||
|
public UInt16 MinorImageVersion;
|
||||||
|
public UInt16 MajorSubsystemVersion;
|
||||||
|
public UInt16 MinorSubsystemVersion;
|
||||||
|
public UInt32 Win32VersionValue;
|
||||||
|
public UInt32 SizeOfImage;
|
||||||
|
public UInt32 SizeOfHeaders;
|
||||||
|
public UInt32 CheckSum;
|
||||||
|
public UInt16 Subsystem;
|
||||||
|
public UInt16 DllCharacteristics;
|
||||||
|
public UInt64 SizeOfStackReserve;
|
||||||
|
public UInt64 SizeOfStackCommit;
|
||||||
|
public UInt64 SizeOfHeapReserve;
|
||||||
|
public UInt64 SizeOfHeapCommit;
|
||||||
|
public UInt32 LoaderFlags;
|
||||||
|
public UInt32 NumberOfRvaAndSizes;
|
||||||
|
|
||||||
|
public IMAGE_DATA_DIRECTORY ExportTable;
|
||||||
|
public IMAGE_DATA_DIRECTORY ImportTable;
|
||||||
|
public IMAGE_DATA_DIRECTORY ResourceTable;
|
||||||
|
public IMAGE_DATA_DIRECTORY ExceptionTable;
|
||||||
|
public IMAGE_DATA_DIRECTORY CertificateTable;
|
||||||
|
public IMAGE_DATA_DIRECTORY BaseRelocationTable;
|
||||||
|
public IMAGE_DATA_DIRECTORY Debug;
|
||||||
|
public IMAGE_DATA_DIRECTORY Architecture;
|
||||||
|
public IMAGE_DATA_DIRECTORY GlobalPtr;
|
||||||
|
public IMAGE_DATA_DIRECTORY TLSTable;
|
||||||
|
public IMAGE_DATA_DIRECTORY LoadConfigTable;
|
||||||
|
public IMAGE_DATA_DIRECTORY BoundImport;
|
||||||
|
public IMAGE_DATA_DIRECTORY IAT;
|
||||||
|
public IMAGE_DATA_DIRECTORY DelayImportDescriptor;
|
||||||
|
public IMAGE_DATA_DIRECTORY CLRRuntimeHeader;
|
||||||
|
public IMAGE_DATA_DIRECTORY Reserved;
|
||||||
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||||
|
public struct IMAGE_FILE_HEADER
|
||||||
|
{
|
||||||
|
public UInt16 Machine;
|
||||||
|
public UInt16 NumberOfSections;
|
||||||
|
public UInt32 TimeDateStamp;
|
||||||
|
public UInt32 PointerToSymbolTable;
|
||||||
|
public UInt32 NumberOfSymbols;
|
||||||
|
public UInt16 SizeOfOptionalHeader;
|
||||||
|
public UInt16 Characteristics;
|
||||||
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Explicit)]
|
||||||
|
public struct IMAGE_SECTION_HEADER
|
||||||
|
{
|
||||||
|
[FieldOffset(0)]
|
||||||
|
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)]
|
||||||
|
public char[] Name;
|
||||||
|
[FieldOffset(8)]
|
||||||
|
public UInt32 VirtualSize;
|
||||||
|
[FieldOffset(12)]
|
||||||
|
public UInt32 VirtualAddress;
|
||||||
|
[FieldOffset(16)]
|
||||||
|
public UInt32 SizeOfRawData;
|
||||||
|
[FieldOffset(20)]
|
||||||
|
public UInt32 PointerToRawData;
|
||||||
|
[FieldOffset(24)]
|
||||||
|
public UInt32 PointerToRelocations;
|
||||||
|
[FieldOffset(28)]
|
||||||
|
public UInt32 PointerToLinenumbers;
|
||||||
|
[FieldOffset(32)]
|
||||||
|
public UInt16 NumberOfRelocations;
|
||||||
|
[FieldOffset(34)]
|
||||||
|
public UInt16 NumberOfLinenumbers;
|
||||||
|
[FieldOffset(36)]
|
||||||
|
public DataSectionFlags Characteristics;
|
||||||
|
|
||||||
|
public string Section
|
||||||
|
{
|
||||||
|
get { return new string(Name); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Flags]
|
||||||
|
public enum DataSectionFlags : uint
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Reserved for future use.
|
||||||
|
/// </summary>
|
||||||
|
TypeReg = 0x00000000,
|
||||||
|
/// <summary>
|
||||||
|
/// Reserved for future use.
|
||||||
|
/// </summary>
|
||||||
|
TypeDsect = 0x00000001,
|
||||||
|
/// <summary>
|
||||||
|
/// Reserved for future use.
|
||||||
|
/// </summary>
|
||||||
|
TypeNoLoad = 0x00000002,
|
||||||
|
/// <summary>
|
||||||
|
/// Reserved for future use.
|
||||||
|
/// </summary>
|
||||||
|
TypeGroup = 0x00000004,
|
||||||
|
/// <summary>
|
||||||
|
/// The section should not be padded to the next boundary. This flag is obsolete and is replaced by IMAGE_SCN_ALIGN_1BYTES. This is valid only for object files.
|
||||||
|
/// </summary>
|
||||||
|
TypeNoPadded = 0x00000008,
|
||||||
|
/// <summary>
|
||||||
|
/// Reserved for future use.
|
||||||
|
/// </summary>
|
||||||
|
TypeCopy = 0x00000010,
|
||||||
|
/// <summary>
|
||||||
|
/// The section contains executable code.
|
||||||
|
/// </summary>
|
||||||
|
ContentCode = 0x00000020,
|
||||||
|
/// <summary>
|
||||||
|
/// The section contains initialized data.
|
||||||
|
/// </summary>
|
||||||
|
ContentInitializedData = 0x00000040,
|
||||||
|
/// <summary>
|
||||||
|
/// The section contains uninitialized data.
|
||||||
|
/// </summary>
|
||||||
|
ContentUninitializedData = 0x00000080,
|
||||||
|
/// <summary>
|
||||||
|
/// Reserved for future use.
|
||||||
|
/// </summary>
|
||||||
|
LinkOther = 0x00000100,
|
||||||
|
/// <summary>
|
||||||
|
/// The section contains comments or other information. The .drectve section has this type. This is valid for object files only.
|
||||||
|
/// </summary>
|
||||||
|
LinkInfo = 0x00000200,
|
||||||
|
/// <summary>
|
||||||
|
/// Reserved for future use.
|
||||||
|
/// </summary>
|
||||||
|
TypeOver = 0x00000400,
|
||||||
|
/// <summary>
|
||||||
|
/// The section will not become part of the image. This is valid only for object files.
|
||||||
|
/// </summary>
|
||||||
|
LinkRemove = 0x00000800,
|
||||||
|
/// <summary>
|
||||||
|
/// The section contains COMDAT data. For more information, see section 5.5.6, COMDAT Sections (Object Only). This is valid only for object files.
|
||||||
|
/// </summary>
|
||||||
|
LinkComDat = 0x00001000,
|
||||||
|
/// <summary>
|
||||||
|
/// Reset speculative exceptions handling bits in the TLB entries for this section.
|
||||||
|
/// </summary>
|
||||||
|
NoDeferSpecExceptions = 0x00004000,
|
||||||
|
/// <summary>
|
||||||
|
/// The section contains data referenced through the global pointer (GP).
|
||||||
|
/// </summary>
|
||||||
|
RelativeGP = 0x00008000,
|
||||||
|
/// <summary>
|
||||||
|
/// Reserved for future use.
|
||||||
|
/// </summary>
|
||||||
|
MemPurgeable = 0x00020000,
|
||||||
|
/// <summary>
|
||||||
|
/// Reserved for future use.
|
||||||
|
/// </summary>
|
||||||
|
Memory16Bit = 0x00020000,
|
||||||
|
/// <summary>
|
||||||
|
/// Reserved for future use.
|
||||||
|
/// </summary>
|
||||||
|
MemoryLocked = 0x00040000,
|
||||||
|
/// <summary>
|
||||||
|
/// Reserved for future use.
|
||||||
|
/// </summary>
|
||||||
|
MemoryPreload = 0x00080000,
|
||||||
|
/// <summary>
|
||||||
|
/// Align data on a 1-byte boundary. Valid only for object files.
|
||||||
|
/// </summary>
|
||||||
|
Align1Bytes = 0x00100000,
|
||||||
|
/// <summary>
|
||||||
|
/// Align data on a 2-byte boundary. Valid only for object files.
|
||||||
|
/// </summary>
|
||||||
|
Align2Bytes = 0x00200000,
|
||||||
|
/// <summary>
|
||||||
|
/// Align data on a 4-byte boundary. Valid only for object files.
|
||||||
|
/// </summary>
|
||||||
|
Align4Bytes = 0x00300000,
|
||||||
|
/// <summary>
|
||||||
|
/// Align data on an 8-byte boundary. Valid only for object files.
|
||||||
|
/// </summary>
|
||||||
|
Align8Bytes = 0x00400000,
|
||||||
|
/// <summary>
|
||||||
|
/// Align data on a 16-byte boundary. Valid only for object files.
|
||||||
|
/// </summary>
|
||||||
|
Align16Bytes = 0x00500000,
|
||||||
|
/// <summary>
|
||||||
|
/// Align data on a 32-byte boundary. Valid only for object files.
|
||||||
|
/// </summary>
|
||||||
|
Align32Bytes = 0x00600000,
|
||||||
|
/// <summary>
|
||||||
|
/// Align data on a 64-byte boundary. Valid only for object files.
|
||||||
|
/// </summary>
|
||||||
|
Align64Bytes = 0x00700000,
|
||||||
|
/// <summary>
|
||||||
|
/// Align data on a 128-byte boundary. Valid only for object files.
|
||||||
|
/// </summary>
|
||||||
|
Align128Bytes = 0x00800000,
|
||||||
|
/// <summary>
|
||||||
|
/// Align data on a 256-byte boundary. Valid only for object files.
|
||||||
|
/// </summary>
|
||||||
|
Align256Bytes = 0x00900000,
|
||||||
|
/// <summary>
|
||||||
|
/// Align data on a 512-byte boundary. Valid only for object files.
|
||||||
|
/// </summary>
|
||||||
|
Align512Bytes = 0x00A00000,
|
||||||
|
/// <summary>
|
||||||
|
/// Align data on a 1024-byte boundary. Valid only for object files.
|
||||||
|
/// </summary>
|
||||||
|
Align1024Bytes = 0x00B00000,
|
||||||
|
/// <summary>
|
||||||
|
/// Align data on a 2048-byte boundary. Valid only for object files.
|
||||||
|
/// </summary>
|
||||||
|
Align2048Bytes = 0x00C00000,
|
||||||
|
/// <summary>
|
||||||
|
/// Align data on a 4096-byte boundary. Valid only for object files.
|
||||||
|
/// </summary>
|
||||||
|
Align4096Bytes = 0x00D00000,
|
||||||
|
/// <summary>
|
||||||
|
/// Align data on an 8192-byte boundary. Valid only for object files.
|
||||||
|
/// </summary>
|
||||||
|
Align8192Bytes = 0x00E00000,
|
||||||
|
/// <summary>
|
||||||
|
/// The section contains extended relocations.
|
||||||
|
/// </summary>
|
||||||
|
LinkExtendedRelocationOverflow = 0x01000000,
|
||||||
|
/// <summary>
|
||||||
|
/// The section can be discarded as needed.
|
||||||
|
/// </summary>
|
||||||
|
MemoryDiscardable = 0x02000000,
|
||||||
|
/// <summary>
|
||||||
|
/// The section cannot be cached.
|
||||||
|
/// </summary>
|
||||||
|
MemoryNotCached = 0x04000000,
|
||||||
|
/// <summary>
|
||||||
|
/// The section is not pageable.
|
||||||
|
/// </summary>
|
||||||
|
MemoryNotPaged = 0x08000000,
|
||||||
|
/// <summary>
|
||||||
|
/// The section can be shared in memory.
|
||||||
|
/// </summary>
|
||||||
|
MemoryShared = 0x10000000,
|
||||||
|
/// <summary>
|
||||||
|
/// The section can be executed as code.
|
||||||
|
/// </summary>
|
||||||
|
MemoryExecute = 0x20000000,
|
||||||
|
/// <summary>
|
||||||
|
/// The section can be read.
|
||||||
|
/// </summary>
|
||||||
|
MemoryRead = 0x40000000,
|
||||||
|
/// <summary>
|
||||||
|
/// The section can be written to.
|
||||||
|
/// </summary>
|
||||||
|
MemoryWrite = 0x80000000
|
||||||
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Explicit)]
|
||||||
|
public struct IMAGE_IMPORT_DESCRIPTOR
|
||||||
|
{
|
||||||
|
[FieldOffset(0)]
|
||||||
|
public uint Characteristics;
|
||||||
|
|
||||||
|
[FieldOffset(0)]
|
||||||
|
public uint OriginalFirstThunk;
|
||||||
|
|
||||||
|
[FieldOffset(4)]
|
||||||
|
public uint TimeDateStamp;
|
||||||
|
|
||||||
|
[FieldOffset(8)]
|
||||||
|
public uint ForwarderChain;
|
||||||
|
|
||||||
|
[FieldOffset(12)]
|
||||||
|
public uint Name;
|
||||||
|
|
||||||
|
[FieldOffset(16)]
|
||||||
|
public uint FirstThunk;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
92
Dalamud/Hooking/Internal/ReloadedHook.cs
Normal file
92
Dalamud/Hooking/Internal/ReloadedHook.cs
Normal file
|
|
@ -0,0 +1,92 @@
|
||||||
|
using System;
|
||||||
|
using System.Reflection;
|
||||||
|
|
||||||
|
using Dalamud.Memory;
|
||||||
|
using Reloaded.Hooks;
|
||||||
|
|
||||||
|
namespace Dalamud.Hooking.Internal
|
||||||
|
{
|
||||||
|
internal class ReloadedHook<T> : Hook<T> where T : Delegate
|
||||||
|
{
|
||||||
|
private readonly Reloaded.Hooks.Definitions.IHook<T> hookImpl;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="ReloadedHook{T}"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="address">A memory address to install a hook.</param>
|
||||||
|
/// <param name="detour">Callback function. Delegate must have a same original function prototype.</param>
|
||||||
|
/// <param name="callingAssembly">Calling assembly.</param>
|
||||||
|
internal ReloadedHook(IntPtr address, T detour, Assembly callingAssembly)
|
||||||
|
: base(address)
|
||||||
|
{
|
||||||
|
var hasOtherHooks = HookManager.Originals.ContainsKey(this.Address);
|
||||||
|
if (!hasOtherHooks)
|
||||||
|
{
|
||||||
|
MemoryHelper.ReadRaw(this.Address, 0x32, out var original);
|
||||||
|
HookManager.Originals[this.Address] = original;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.hookImpl = ReloadedHooks.Instance.CreateHook<T>(detour, address.ToInt64());
|
||||||
|
|
||||||
|
HookManager.TrackedHooks.TryAdd(Guid.NewGuid(), new HookInfo(this, detour, callingAssembly));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override T Original
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
this.CheckDisposed();
|
||||||
|
return this.hookImpl.OriginalFunction;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override bool IsEnabled
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
this.CheckDisposed();
|
||||||
|
return this.hookImpl.IsHookEnabled;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override string BackendName => "Reloaded";
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override void Dispose()
|
||||||
|
{
|
||||||
|
if (this.IsDisposed)
|
||||||
|
return;
|
||||||
|
|
||||||
|
this.Disable();
|
||||||
|
|
||||||
|
base.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override void Enable()
|
||||||
|
{
|
||||||
|
this.CheckDisposed();
|
||||||
|
|
||||||
|
if (!this.hookImpl.IsHookActivated)
|
||||||
|
this.hookImpl.Activate();
|
||||||
|
|
||||||
|
if (!this.hookImpl.IsHookEnabled)
|
||||||
|
this.hookImpl.Enable();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override void Disable()
|
||||||
|
{
|
||||||
|
this.CheckDisposed();
|
||||||
|
|
||||||
|
if (!this.hookImpl.IsHookActivated)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (this.hookImpl.IsHookEnabled)
|
||||||
|
this.hookImpl.Disable();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -11,6 +11,7 @@ using Dalamud.Configuration.Internal;
|
||||||
using Dalamud.Game;
|
using Dalamud.Game;
|
||||||
using Dalamud.Game.ClientState.GamePad;
|
using Dalamud.Game.ClientState.GamePad;
|
||||||
using Dalamud.Game.ClientState.Keys;
|
using Dalamud.Game.ClientState.Keys;
|
||||||
|
using Dalamud.Game.Gui.Internal;
|
||||||
using Dalamud.Game.Internal.DXGI;
|
using Dalamud.Game.Internal.DXGI;
|
||||||
using Dalamud.Hooking;
|
using Dalamud.Hooking;
|
||||||
using Dalamud.Interface.GameFonts;
|
using Dalamud.Interface.GameFonts;
|
||||||
|
|
@ -27,6 +28,7 @@ using Serilog;
|
||||||
using SharpDX.Direct3D11;
|
using SharpDX.Direct3D11;
|
||||||
|
|
||||||
// general dev notes, here because it's easiest
|
// general dev notes, here because it's easiest
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* - Hooking ResizeBuffers seemed to be unnecessary, though I'm not sure why. Left out for now since it seems to work without it.
|
* - Hooking ResizeBuffers seemed to be unnecessary, though I'm not sure why. Left out for now since it seems to work without it.
|
||||||
* - We may want to build our ImGui command list in a thread to keep it divorced from present. We'd still have to block in present to
|
* - We may want to build our ImGui command list in a thread to keep it divorced from present. We'd still have to block in present to
|
||||||
|
|
@ -52,18 +54,17 @@ namespace Dalamud.Interface.Internal
|
||||||
private const ushort Fallback1Codepoint = 0x3013; // Geta mark; FFXIV uses this to indicate that a glyph is missing.
|
private const ushort Fallback1Codepoint = 0x3013; // Geta mark; FFXIV uses this to indicate that a glyph is missing.
|
||||||
private const ushort Fallback2Codepoint = '-'; // FFXIV uses dash if Geta mark is unavailable.
|
private const ushort Fallback2Codepoint = '-'; // FFXIV uses dash if Geta mark is unavailable.
|
||||||
|
|
||||||
private readonly string rtssPath;
|
|
||||||
|
|
||||||
private readonly HashSet<SpecialGlyphRequest> glyphRequests = new();
|
private readonly HashSet<SpecialGlyphRequest> glyphRequests = new();
|
||||||
private readonly Dictionary<ImFontPtr, TargetFontModification> loadedFontInfo = new();
|
private readonly Dictionary<ImFontPtr, TargetFontModification> loadedFontInfo = new();
|
||||||
|
|
||||||
private readonly ManualResetEvent fontBuildSignal;
|
private readonly ManualResetEvent fontBuildSignal;
|
||||||
private readonly SwapChainVtableResolver address;
|
private readonly SwapChainVtableResolver address;
|
||||||
|
private readonly Hook<DispatchMessageWDelegate> dispatchMessageWHook;
|
||||||
|
private readonly Hook<SetCursorDelegate> setCursorHook;
|
||||||
private RawDX11Scene? scene;
|
private RawDX11Scene? scene;
|
||||||
|
|
||||||
private Hook<PresentDelegate>? presentHook;
|
private Hook<PresentDelegate>? presentHook;
|
||||||
private Hook<ResizeBuffersDelegate>? resizeBuffersHook;
|
private Hook<ResizeBuffersDelegate>? resizeBuffersHook;
|
||||||
private Hook<SetCursorDelegate>? setCursorHook;
|
|
||||||
|
|
||||||
// can't access imgui IO before first present call
|
// can't access imgui IO before first present call
|
||||||
private bool lastWantCapture = false;
|
private bool lastWantCapture = false;
|
||||||
|
|
@ -72,31 +73,16 @@ namespace Dalamud.Interface.Internal
|
||||||
private bool isOverrideGameCursor = true;
|
private bool isOverrideGameCursor = true;
|
||||||
|
|
||||||
[ServiceManager.ServiceConstructor]
|
[ServiceManager.ServiceConstructor]
|
||||||
private InterfaceManager()
|
private InterfaceManager(SigScanner sigScanner)
|
||||||
{
|
{
|
||||||
|
this.dispatchMessageWHook = Hook<DispatchMessageWDelegate>.FromImport(
|
||||||
|
Process.GetCurrentProcess().MainModule, "user32.dll", "DispatchMessageW", 0, this.DispatchMessageWDetour);
|
||||||
|
this.setCursorHook = Hook<SetCursorDelegate>.FromImport(
|
||||||
|
Process.GetCurrentProcess().MainModule, "user32.dll", "SetCursor", 0, this.SetCursorDetour);
|
||||||
|
|
||||||
this.fontBuildSignal = new ManualResetEvent(false);
|
this.fontBuildSignal = new ManualResetEvent(false);
|
||||||
|
|
||||||
this.address = new SwapChainVtableResolver();
|
this.address = new SwapChainVtableResolver();
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var rtss = NativeFunctions.GetModuleHandleW("RTSSHooks64.dll");
|
|
||||||
|
|
||||||
if (rtss != IntPtr.Zero)
|
|
||||||
{
|
|
||||||
var fileName = new StringBuilder(255);
|
|
||||||
_ = NativeFunctions.GetModuleFileNameW(rtss, fileName, fileName.Capacity);
|
|
||||||
this.rtssPath = fileName.ToString();
|
|
||||||
Log.Verbose($"RTSS at {this.rtssPath}");
|
|
||||||
|
|
||||||
if (!NativeFunctions.FreeLibrary(rtss))
|
|
||||||
throw new Win32Exception();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
Log.Error(e, "RTSS Free failed");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
|
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
|
||||||
|
|
@ -105,10 +91,17 @@ namespace Dalamud.Interface.Internal
|
||||||
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
|
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
|
||||||
private delegate IntPtr ResizeBuffersDelegate(IntPtr swapChain, uint bufferCount, uint width, uint height, uint newFormat, uint swapChainFlags);
|
private delegate IntPtr ResizeBuffersDelegate(IntPtr swapChain, uint bufferCount, uint width, uint height, uint newFormat, uint swapChainFlags);
|
||||||
|
|
||||||
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
|
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
|
||||||
|
private delegate int CreateDXGIFactoryDelegate(Guid riid, out IntPtr ppFactory);
|
||||||
|
|
||||||
|
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
|
||||||
|
private delegate int IDXGIFactory_CreateSwapChainDelegate(IntPtr pFactory, IntPtr pDevice, IntPtr pDesc, out IntPtr ppSwapChain);
|
||||||
|
|
||||||
|
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
|
||||||
private delegate IntPtr SetCursorDelegate(IntPtr hCursor);
|
private delegate IntPtr SetCursorDelegate(IntPtr hCursor);
|
||||||
|
|
||||||
private delegate void InstallRTSSHook();
|
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
|
||||||
|
private delegate IntPtr DispatchMessageWDelegate(ref User32.MSG msg);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// This event gets called each frame to facilitate ImGui drawing.
|
/// This event gets called each frame to facilitate ImGui drawing.
|
||||||
|
|
@ -245,6 +238,11 @@ namespace Dalamud.Interface.Internal
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool IsBuildingFontsBeforeAtlasBuild => this.isRebuildingFonts && !this.fontBuildSignal.WaitOne(0);
|
public bool IsBuildingFontsBeforeAtlasBuild => this.isRebuildingFonts && !this.fontBuildSignal.WaitOne(0);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a value indicating the native handle of the game main window.
|
||||||
|
/// </summary>
|
||||||
|
public IntPtr GameWindowHandle { get; private set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Dispose of managed and unmanaged resources.
|
/// Dispose of managed and unmanaged resources.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -260,9 +258,10 @@ namespace Dalamud.Interface.Internal
|
||||||
Thread.Sleep(500);
|
Thread.Sleep(500);
|
||||||
|
|
||||||
this.scene?.Dispose();
|
this.scene?.Dispose();
|
||||||
this.setCursorHook?.Dispose();
|
this.setCursorHook.Dispose();
|
||||||
this.presentHook?.Dispose();
|
this.presentHook?.Dispose();
|
||||||
this.resizeBuffersHook?.Dispose();
|
this.resizeBuffersHook?.Dispose();
|
||||||
|
this.dispatchMessageWHook.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
#nullable enable
|
#nullable enable
|
||||||
|
|
@ -428,6 +427,132 @@ namespace Dalamud.Interface.Internal
|
||||||
Util.Fatal($"One or more files required by XIVLauncher were not found.\nPlease restart and report this error if it occurs again.\n\n{path}", "Error");
|
Util.Fatal($"One or more files required by XIVLauncher were not found.\nPlease restart and report this error if it occurs again.\n\n{path}", "Error");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void InitScene(IntPtr swapChain)
|
||||||
|
{
|
||||||
|
RawDX11Scene newScene;
|
||||||
|
using (Timings.Start("IM Scene Init"))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
newScene = new RawDX11Scene(swapChain);
|
||||||
|
}
|
||||||
|
catch (DllNotFoundException ex)
|
||||||
|
{
|
||||||
|
Service<InterfaceManagerWithScene>.ProvideException(ex);
|
||||||
|
Log.Error(ex, "Could not load ImGui dependencies.");
|
||||||
|
|
||||||
|
var res = PInvoke.User32.MessageBox(
|
||||||
|
IntPtr.Zero,
|
||||||
|
"Dalamud plugins require the Microsoft Visual C++ Redistributable to be installed.\nPlease install the runtime from the official Microsoft website or disable Dalamud.\n\nDo you want to download the redistributable now?",
|
||||||
|
"Dalamud Error",
|
||||||
|
User32.MessageBoxOptions.MB_YESNO | User32.MessageBoxOptions.MB_TOPMOST | User32.MessageBoxOptions.MB_ICONERROR);
|
||||||
|
|
||||||
|
if (res == User32.MessageBoxResult.IDYES)
|
||||||
|
{
|
||||||
|
var psi = new ProcessStartInfo
|
||||||
|
{
|
||||||
|
FileName = "https://aka.ms/vs/16/release/vc_redist.x64.exe",
|
||||||
|
UseShellExecute = true,
|
||||||
|
};
|
||||||
|
Process.Start(psi);
|
||||||
|
}
|
||||||
|
|
||||||
|
Environment.Exit(-1);
|
||||||
|
|
||||||
|
// Doesn't reach here, but to make the compiler not complain
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var startInfo = Service<DalamudStartInfo>.Get();
|
||||||
|
var configuration = Service<DalamudConfiguration>.Get();
|
||||||
|
|
||||||
|
var iniFileInfo = new FileInfo(Path.Combine(Path.GetDirectoryName(startInfo.ConfigurationPath), "dalamudUI.ini"));
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (iniFileInfo.Length > 1200000)
|
||||||
|
{
|
||||||
|
Log.Warning("dalamudUI.ini was over 1mb, deleting");
|
||||||
|
iniFileInfo.CopyTo(Path.Combine(iniFileInfo.DirectoryName, $"dalamudUI-{DateTimeOffset.Now.ToUnixTimeSeconds()}.ini"));
|
||||||
|
iniFileInfo.Delete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Log.Error(ex, "Could not delete dalamudUI.ini");
|
||||||
|
}
|
||||||
|
|
||||||
|
newScene.UpdateCursor = this.isOverrideGameCursor;
|
||||||
|
newScene.ImGuiIniPath = iniFileInfo.FullName;
|
||||||
|
newScene.OnBuildUI += this.Display;
|
||||||
|
newScene.OnNewInputFrame += this.OnNewInputFrame;
|
||||||
|
|
||||||
|
StyleModel.TransferOldModels();
|
||||||
|
|
||||||
|
if (configuration.SavedStyles == null || configuration.SavedStyles.All(x => x.Name != StyleModelV1.DalamudStandard.Name))
|
||||||
|
{
|
||||||
|
configuration.SavedStyles = new List<StyleModel> { StyleModelV1.DalamudStandard, StyleModelV1.DalamudClassic };
|
||||||
|
configuration.ChosenStyle = StyleModelV1.DalamudStandard.Name;
|
||||||
|
}
|
||||||
|
else if (configuration.SavedStyles.Count == 1)
|
||||||
|
{
|
||||||
|
configuration.SavedStyles.Add(StyleModelV1.DalamudClassic);
|
||||||
|
}
|
||||||
|
else if (configuration.SavedStyles[1].Name != StyleModelV1.DalamudClassic.Name)
|
||||||
|
{
|
||||||
|
configuration.SavedStyles.Insert(1, StyleModelV1.DalamudClassic);
|
||||||
|
}
|
||||||
|
|
||||||
|
configuration.SavedStyles[0] = StyleModelV1.DalamudStandard;
|
||||||
|
configuration.SavedStyles[1] = StyleModelV1.DalamudClassic;
|
||||||
|
|
||||||
|
var style = configuration.SavedStyles.FirstOrDefault(x => x.Name == configuration.ChosenStyle);
|
||||||
|
if (style == null)
|
||||||
|
{
|
||||||
|
style = StyleModelV1.DalamudStandard;
|
||||||
|
configuration.ChosenStyle = style.Name;
|
||||||
|
configuration.Save();
|
||||||
|
}
|
||||||
|
|
||||||
|
style.Apply();
|
||||||
|
|
||||||
|
ImGui.GetIO().FontGlobalScale = configuration.GlobalUiScale;
|
||||||
|
|
||||||
|
this.SetupFonts();
|
||||||
|
|
||||||
|
if (!configuration.IsDocking)
|
||||||
|
{
|
||||||
|
ImGui.GetIO().ConfigFlags &= ~ImGuiConfigFlags.DockingEnable;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ImGui.GetIO().ConfigFlags |= ImGuiConfigFlags.DockingEnable;
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTE (Chiv) Toggle gamepad navigation via setting
|
||||||
|
if (!configuration.IsGamepadNavigationEnabled)
|
||||||
|
{
|
||||||
|
ImGui.GetIO().BackendFlags &= ~ImGuiBackendFlags.HasGamepad;
|
||||||
|
ImGui.GetIO().ConfigFlags &= ~ImGuiConfigFlags.NavEnableSetMousePos;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ImGui.GetIO().BackendFlags |= ImGuiBackendFlags.HasGamepad;
|
||||||
|
ImGui.GetIO().ConfigFlags |= ImGuiConfigFlags.NavEnableSetMousePos;
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTE (Chiv) Explicitly deactivate on dalamud boot
|
||||||
|
ImGui.GetIO().ConfigFlags &= ~ImGuiConfigFlags.NavEnableGamepad;
|
||||||
|
|
||||||
|
ImGuiHelpers.MainViewport = ImGui.GetMainViewport();
|
||||||
|
|
||||||
|
Log.Information("[IM] Scene & ImGui setup OK!");
|
||||||
|
}
|
||||||
|
|
||||||
|
this.scene = newScene;
|
||||||
|
Service<InterfaceManagerWithScene>.Provide(new(this));
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* NOTE(goat): When hooking ReShade DXGISwapChain::runtime_present, this is missing the syncInterval arg.
|
* NOTE(goat): When hooking ReShade DXGISwapChain::runtime_present, this is missing the syncInterval arg.
|
||||||
* Seems to work fine regardless, I guess, so whatever.
|
* Seems to work fine regardless, I guess, so whatever.
|
||||||
|
|
@ -438,125 +563,7 @@ namespace Dalamud.Interface.Internal
|
||||||
return this.presentHook!.Original(swapChain, syncInterval, presentFlags);
|
return this.presentHook!.Original(swapChain, syncInterval, presentFlags);
|
||||||
|
|
||||||
if (this.scene == null)
|
if (this.scene == null)
|
||||||
{
|
this.InitScene(swapChain);
|
||||||
using (Timings.Start("IM Scene Init"))
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
this.scene = new RawDX11Scene(swapChain);
|
|
||||||
}
|
|
||||||
catch (DllNotFoundException ex)
|
|
||||||
{
|
|
||||||
Service<InterfaceManagerWithScene>.ProvideException(ex);
|
|
||||||
Log.Error(ex, "Could not load ImGui dependencies.");
|
|
||||||
|
|
||||||
var res = PInvoke.User32.MessageBox(
|
|
||||||
IntPtr.Zero,
|
|
||||||
"Dalamud plugins require the Microsoft Visual C++ Redistributable to be installed.\nPlease install the runtime from the official Microsoft website or disable Dalamud.\n\nDo you want to download the redistributable now?",
|
|
||||||
"Dalamud Error",
|
|
||||||
User32.MessageBoxOptions.MB_YESNO | User32.MessageBoxOptions.MB_TOPMOST | User32.MessageBoxOptions.MB_ICONERROR);
|
|
||||||
|
|
||||||
if (res == User32.MessageBoxResult.IDYES)
|
|
||||||
{
|
|
||||||
var psi = new ProcessStartInfo
|
|
||||||
{
|
|
||||||
FileName = "https://aka.ms/vs/16/release/vc_redist.x64.exe",
|
|
||||||
UseShellExecute = true,
|
|
||||||
};
|
|
||||||
Process.Start(psi);
|
|
||||||
}
|
|
||||||
|
|
||||||
Environment.Exit(-1);
|
|
||||||
}
|
|
||||||
|
|
||||||
var startInfo = Service<DalamudStartInfo>.Get();
|
|
||||||
var configuration = Service<DalamudConfiguration>.Get();
|
|
||||||
|
|
||||||
var iniFileInfo = new FileInfo(Path.Combine(Path.GetDirectoryName(startInfo.ConfigurationPath), "dalamudUI.ini"));
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (iniFileInfo.Length > 1200000)
|
|
||||||
{
|
|
||||||
Log.Warning("dalamudUI.ini was over 1mb, deleting");
|
|
||||||
iniFileInfo.CopyTo(Path.Combine(iniFileInfo.DirectoryName, $"dalamudUI-{DateTimeOffset.Now.ToUnixTimeSeconds()}.ini"));
|
|
||||||
iniFileInfo.Delete();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Log.Error(ex, "Could not delete dalamudUI.ini");
|
|
||||||
}
|
|
||||||
|
|
||||||
this.scene.UpdateCursor = this.isOverrideGameCursor;
|
|
||||||
this.scene.ImGuiIniPath = iniFileInfo.FullName;
|
|
||||||
this.scene.OnBuildUI += this.Display;
|
|
||||||
this.scene.OnNewInputFrame += this.OnNewInputFrame;
|
|
||||||
|
|
||||||
StyleModel.TransferOldModels();
|
|
||||||
|
|
||||||
if (configuration.SavedStyles == null || configuration.SavedStyles.All(x => x.Name != StyleModelV1.DalamudStandard.Name))
|
|
||||||
{
|
|
||||||
configuration.SavedStyles = new List<StyleModel> { StyleModelV1.DalamudStandard, StyleModelV1.DalamudClassic };
|
|
||||||
configuration.ChosenStyle = StyleModelV1.DalamudStandard.Name;
|
|
||||||
}
|
|
||||||
else if (configuration.SavedStyles.Count == 1)
|
|
||||||
{
|
|
||||||
configuration.SavedStyles.Add(StyleModelV1.DalamudClassic);
|
|
||||||
}
|
|
||||||
else if (configuration.SavedStyles[1].Name != StyleModelV1.DalamudClassic.Name)
|
|
||||||
{
|
|
||||||
configuration.SavedStyles.Insert(1, StyleModelV1.DalamudClassic);
|
|
||||||
}
|
|
||||||
|
|
||||||
configuration.SavedStyles[0] = StyleModelV1.DalamudStandard;
|
|
||||||
configuration.SavedStyles[1] = StyleModelV1.DalamudClassic;
|
|
||||||
|
|
||||||
var style = configuration.SavedStyles.FirstOrDefault(x => x.Name == configuration.ChosenStyle);
|
|
||||||
if (style == null)
|
|
||||||
{
|
|
||||||
style = StyleModelV1.DalamudStandard;
|
|
||||||
configuration.ChosenStyle = style.Name;
|
|
||||||
configuration.Save();
|
|
||||||
}
|
|
||||||
|
|
||||||
style.Apply();
|
|
||||||
|
|
||||||
ImGui.GetIO().FontGlobalScale = configuration.GlobalUiScale;
|
|
||||||
|
|
||||||
this.SetupFonts();
|
|
||||||
|
|
||||||
if (!configuration.IsDocking)
|
|
||||||
{
|
|
||||||
ImGui.GetIO().ConfigFlags &= ~ImGuiConfigFlags.DockingEnable;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
ImGui.GetIO().ConfigFlags |= ImGuiConfigFlags.DockingEnable;
|
|
||||||
}
|
|
||||||
|
|
||||||
// NOTE (Chiv) Toggle gamepad navigation via setting
|
|
||||||
if (!configuration.IsGamepadNavigationEnabled)
|
|
||||||
{
|
|
||||||
ImGui.GetIO().BackendFlags &= ~ImGuiBackendFlags.HasGamepad;
|
|
||||||
ImGui.GetIO().ConfigFlags &= ~ImGuiConfigFlags.NavEnableSetMousePos;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
ImGui.GetIO().BackendFlags |= ImGuiBackendFlags.HasGamepad;
|
|
||||||
ImGui.GetIO().ConfigFlags |= ImGuiConfigFlags.NavEnableSetMousePos;
|
|
||||||
}
|
|
||||||
|
|
||||||
// NOTE (Chiv) Explicitly deactivate on dalamud boot
|
|
||||||
ImGui.GetIO().ConfigFlags &= ~ImGuiConfigFlags.NavEnableGamepad;
|
|
||||||
|
|
||||||
ImGuiHelpers.MainViewport = ImGui.GetMainViewport();
|
|
||||||
|
|
||||||
Log.Information("[IM] Scene & ImGui setup OK!");
|
|
||||||
|
|
||||||
Service<Framework>.Get().RunOnFrameworkThread(() => Service<InterfaceManagerWithScene>.Provide(new(this)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.address.IsReshade)
|
if (this.address.IsReshade)
|
||||||
{
|
{
|
||||||
|
|
@ -975,44 +982,34 @@ namespace Dalamud.Interface.Internal
|
||||||
this.address.Setup(sigScanner);
|
this.address.Setup(sigScanner);
|
||||||
framework.RunOnFrameworkThread(() =>
|
framework.RunOnFrameworkThread(() =>
|
||||||
{
|
{
|
||||||
this.setCursorHook = Hook<SetCursorDelegate>.FromSymbol("user32.dll", "SetCursor", this.SetCursorDetour, true)!;
|
while ((this.GameWindowHandle = NativeFunctions.FindWindowEx(IntPtr.Zero, this.GameWindowHandle, "FFXIVGAME", IntPtr.Zero)) != IntPtr.Zero)
|
||||||
|
{
|
||||||
|
_ = User32.GetWindowThreadProcessId(this.GameWindowHandle, out var pid);
|
||||||
|
|
||||||
|
if (pid == Environment.ProcessId && User32.IsWindowVisible(this.GameWindowHandle))
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
this.presentHook = new Hook<PresentDelegate>(this.address.Present, this.PresentDetour);
|
this.presentHook = new Hook<PresentDelegate>(this.address.Present, this.PresentDetour);
|
||||||
this.resizeBuffersHook = new Hook<ResizeBuffersDelegate>(this.address.ResizeBuffers, this.ResizeBuffersDetour);
|
this.resizeBuffersHook = new Hook<ResizeBuffersDelegate>(this.address.ResizeBuffers, this.ResizeBuffersDetour);
|
||||||
|
|
||||||
Log.Verbose("===== S W A P C H A I N =====");
|
Log.Verbose("===== S W A P C H A I N =====");
|
||||||
Log.Verbose($"SetCursor address 0x{this.setCursorHook!.Address.ToInt64():X}");
|
|
||||||
Log.Verbose($"Present address 0x{this.presentHook!.Address.ToInt64():X}");
|
Log.Verbose($"Present address 0x{this.presentHook!.Address.ToInt64():X}");
|
||||||
Log.Verbose($"ResizeBuffers address 0x{this.resizeBuffersHook!.Address.ToInt64():X}");
|
Log.Verbose($"ResizeBuffers address 0x{this.resizeBuffersHook!.Address.ToInt64():X}");
|
||||||
|
|
||||||
this.setCursorHook.Enable();
|
this.setCursorHook.Enable();
|
||||||
this.presentHook.Enable();
|
this.presentHook.Enable();
|
||||||
this.resizeBuffersHook.Enable();
|
this.resizeBuffersHook.Enable();
|
||||||
|
this.dispatchMessageWHook.Enable();
|
||||||
try
|
|
||||||
{
|
|
||||||
if (!string.IsNullOrEmpty(this.rtssPath))
|
|
||||||
{
|
|
||||||
NativeFunctions.LoadLibraryW(this.rtssPath);
|
|
||||||
var rtssModule = NativeFunctions.GetModuleHandleW("RTSSHooks64.dll");
|
|
||||||
var installAddr = NativeFunctions.GetProcAddress(rtssModule, "InstallRTSSHook");
|
|
||||||
|
|
||||||
Log.Debug("Installing RTSS hook");
|
|
||||||
Marshal.GetDelegateForFunctionPointer<InstallRTSSHook>(installAddr).Invoke();
|
|
||||||
Log.Debug("RTSS hook OK!");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Log.Error(ex, "Could not reload RTSS");
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Disable()
|
private void Disable()
|
||||||
{
|
{
|
||||||
this.setCursorHook?.Disable();
|
this.setCursorHook.Disable();
|
||||||
this.presentHook?.Disable();
|
this.presentHook?.Disable();
|
||||||
this.resizeBuffersHook?.Disable();
|
this.resizeBuffersHook?.Disable();
|
||||||
|
this.dispatchMessageWHook.Disable();
|
||||||
}
|
}
|
||||||
|
|
||||||
// This is intended to only be called as a handler attached to scene.OnNewRenderFrame
|
// This is intended to only be called as a handler attached to scene.OnNewRenderFrame
|
||||||
|
|
@ -1059,6 +1056,23 @@ namespace Dalamud.Interface.Internal
|
||||||
this.isRebuildingFonts = false;
|
this.isRebuildingFonts = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private unsafe IntPtr DispatchMessageWDetour(ref User32.MSG msg)
|
||||||
|
{
|
||||||
|
if (msg.hwnd == this.GameWindowHandle && this.scene != null)
|
||||||
|
{
|
||||||
|
var ime = Service<DalamudIME>.GetNullable();
|
||||||
|
var res = ime?.ProcessWndProcW(msg.hwnd, msg.message, (void*)msg.wParam, (void*)msg.lParam);
|
||||||
|
if (res != null)
|
||||||
|
return res.Value;
|
||||||
|
|
||||||
|
res = this.scene.ProcessWndProcW(msg.hwnd, msg.message, (void*)msg.wParam, (void*)msg.lParam);
|
||||||
|
if (res != null)
|
||||||
|
return res.Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.dispatchMessageWHook.Original(ref msg);
|
||||||
|
}
|
||||||
|
|
||||||
private IntPtr ResizeBuffersDetour(IntPtr swapChain, uint bufferCount, uint width, uint height, uint newFormat, uint swapChainFlags)
|
private IntPtr ResizeBuffersDetour(IntPtr swapChain, uint bufferCount, uint width, uint height, uint newFormat, uint swapChainFlags)
|
||||||
{
|
{
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
|
|
|
||||||
|
|
@ -1906,6 +1906,21 @@ namespace Dalamud
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int ClientPointers;
|
public int ClientPointers;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Finds window according to conditions.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="parentHandle">Handle to parent window.</param>
|
||||||
|
/// <param name="childAfter">Window to search after.</param>
|
||||||
|
/// <param name="className">Name of class.</param>
|
||||||
|
/// <param name="windowTitle">Name of window.</param>
|
||||||
|
/// <returns>Found window, or null.</returns>
|
||||||
|
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
|
||||||
|
public static extern IntPtr FindWindowEx(
|
||||||
|
IntPtr parentHandle,
|
||||||
|
IntPtr childAfter,
|
||||||
|
string className,
|
||||||
|
IntPtr windowTitle);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
Subproject commit de1a512bc77eed279f9909a3a233068a51a57b21
|
Subproject commit 5da5fb742375aba55026f3beeef05dfe876a21bd
|
||||||
Loading…
Add table
Add a link
Reference in a new issue