Add import hooks and ditch wndproc hooks (#902)

This commit is contained in:
kizer 2022-06-28 03:19:44 +09:00 committed by GitHub
parent 6ad647235c
commit ac7f3ea5d8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 1251 additions and 404 deletions

View file

@ -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();
}
} }
} }

View file

@ -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();
} }
} }
} }

View file

@ -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();
} }

View file

@ -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");
}
} }
} }

View 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 _);
}
}
}
}

View 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();
}
}
}
}

View 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;
}
}
}

View 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();
}
}
}

View file

@ -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

View file

@ -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