Use same interop as Dalamud, forcibly hackfix mouse coords
This commit is contained in:
parent
056953c26e
commit
4a2096835e
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
.vs/
|
||||
obj/
|
||||
bin/
|
||||
*.user
|
30
CustomResolution.sln
Normal file
30
CustomResolution.sln
Normal file
@ -0,0 +1,30 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.3.32505.426
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CustomResolution", "CustomResolution\CustomResolution.csproj", "{13C812E9-0D42-4B95-8646-40EEBF30636F}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{5ECEDCE5-D60F-4A8A-AB33-4131F5C7371C}"
|
||||
ProjectSection(SolutionItems) = preProject
|
||||
Directory.Build.props = Directory.Build.props
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|x64 = Debug|x64
|
||||
Release|x64 = Release|x64
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{13C812E9-0D42-4B95-8646-40EEBF30636F}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{13C812E9-0D42-4B95-8646-40EEBF30636F}.Debug|x64.Build.0 = Debug|x64
|
||||
{13C812E9-0D42-4B95-8646-40EEBF30636F}.Release|x64.ActiveCfg = Release|x64
|
||||
{13C812E9-0D42-4B95-8646-40EEBF30636F}.Release|x64.Build.0 = Release|x64
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {B17E85B1-5F60-4440-9F9A-3DDE877E8CDF}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
@ -26,9 +26,7 @@
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="DalamudPackager" Version="2.1.12" />
|
||||
<PackageReference Include="Microsoft.Windows.CsWin32" Version="0.3.49-beta">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="TerraFX.Interop.Windows" Version="10.0.22621.2" />
|
||||
<Reference Include="FFXIVClientStructs">
|
||||
<HintPath>$(DalamudLibPath)FFXIVClientStructs.dll</HintPath>
|
||||
<Private>false</Private>
|
||||
|
133
CustomResolution/FastReflectionHelper.cs
Normal file
133
CustomResolution/FastReflectionHelper.cs
Normal file
@ -0,0 +1,133 @@
|
||||
using System;
|
||||
using System.Reflection;
|
||||
using System.Reflection.Emit;
|
||||
|
||||
namespace MonoMod.Utils;
|
||||
|
||||
public delegate object? FastReflectionDelegate(object? target, params object?[] args);
|
||||
|
||||
public static class FastReflectionHelper
|
||||
{
|
||||
private static readonly Type[] _dynamicMethodDelegateArgs = { typeof(object), typeof(object?[]) };
|
||||
|
||||
public static FastReflectionDelegate CreateFastDelegate(MethodBase method, bool directBoxValueAccess = true)
|
||||
{
|
||||
var dm = new DynamicMethod($"FastReflection<{method}>", typeof(object), _dynamicMethodDelegateArgs);
|
||||
var il = dm.GetILGenerator();
|
||||
|
||||
var args = method.GetParameters();
|
||||
|
||||
var generateLocalBoxValuePtr = true;
|
||||
|
||||
if (!method.IsStatic)
|
||||
{
|
||||
il.Emit(OpCodes.Ldarg_0);
|
||||
|
||||
if (method.DeclaringType?.IsValueType ?? false)
|
||||
{
|
||||
il.Emit(OpCodes.Unbox_Any, method.DeclaringType);
|
||||
}
|
||||
}
|
||||
|
||||
for (var i = 0; i < args.Length; i++)
|
||||
{
|
||||
var argType = args[i].ParameterType;
|
||||
var argIsByRef = argType.IsByRef;
|
||||
|
||||
if (argIsByRef)
|
||||
{
|
||||
argType = argType.GetElementType()!;
|
||||
}
|
||||
|
||||
var argIsValueType = argType.IsValueType;
|
||||
|
||||
if (argIsByRef && argIsValueType && !directBoxValueAccess)
|
||||
{
|
||||
// Used later when storing back the reference to the new box in the array.
|
||||
il.Emit(OpCodes.Ldarg_1);
|
||||
il.Emit(OpCodes.Ldc_I4, i);
|
||||
}
|
||||
|
||||
il.Emit(OpCodes.Ldarg_1);
|
||||
il.Emit(OpCodes.Ldc_I4, i);
|
||||
|
||||
if (argIsByRef && !argIsValueType)
|
||||
{
|
||||
il.Emit(OpCodes.Ldelema, typeof(object));
|
||||
}
|
||||
else
|
||||
{
|
||||
il.Emit(OpCodes.Ldelem_Ref);
|
||||
|
||||
if (argIsValueType)
|
||||
{
|
||||
if (!argIsByRef || !directBoxValueAccess)
|
||||
{
|
||||
// if !directBoxValueAccess, create a new box if required
|
||||
il.Emit(OpCodes.Unbox_Any, argType);
|
||||
|
||||
if (argIsByRef)
|
||||
{
|
||||
// box back
|
||||
il.Emit(OpCodes.Box, argType);
|
||||
|
||||
// store new box value address to local 0
|
||||
il.Emit(OpCodes.Dup);
|
||||
il.Emit(OpCodes.Unbox, argType);
|
||||
|
||||
if (generateLocalBoxValuePtr)
|
||||
{
|
||||
generateLocalBoxValuePtr = false;
|
||||
il.DeclareLocal(typeof(void*));
|
||||
}
|
||||
|
||||
il.Emit(OpCodes.Stloc_0);
|
||||
|
||||
// arr and index set up already
|
||||
il.Emit(OpCodes.Stelem_Ref);
|
||||
|
||||
// load address back to stack
|
||||
il.Emit(OpCodes.Ldloc_0);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// if directBoxValueAccess, emit unbox (get value address)
|
||||
il.Emit(OpCodes.Unbox, argType);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (method.IsConstructor)
|
||||
{
|
||||
il.Emit(OpCodes.Newobj, (ConstructorInfo) method);
|
||||
}
|
||||
else if (method.IsFinal || !method.IsVirtual)
|
||||
{
|
||||
il.Emit(OpCodes.Call, (MethodInfo) method);
|
||||
}
|
||||
else
|
||||
{
|
||||
il.Emit(OpCodes.Callvirt, (MethodInfo) method);
|
||||
}
|
||||
|
||||
var returnType = method.IsConstructor ? method.DeclaringType! : ((MethodInfo) method).ReturnType;
|
||||
|
||||
if (returnType != typeof(void))
|
||||
{
|
||||
if (returnType.IsValueType)
|
||||
{
|
||||
il.Emit(OpCodes.Box, returnType);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
il.Emit(OpCodes.Ldnull);
|
||||
}
|
||||
|
||||
il.Emit(OpCodes.Ret);
|
||||
|
||||
return (FastReflectionDelegate) dm.CreateDelegate(typeof(FastReflectionDelegate));
|
||||
}
|
||||
}
|
58
CustomResolution/Hooks/CursorPosHooks.cs
Normal file
58
CustomResolution/Hooks/CursorPosHooks.cs
Normal file
@ -0,0 +1,58 @@
|
||||
using CustomResolution.WndProcHookManagerProxyApi;
|
||||
using Dalamud.Hooking;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Reflection.Emit;
|
||||
using System.Runtime.InteropServices;
|
||||
using TerraFX.Interop.Windows;
|
||||
|
||||
|
||||
namespace CustomResolution.Hooks;
|
||||
|
||||
// THIS. IS. UGLY.
|
||||
public sealed unsafe class CursorPosHooks : IDisposable
|
||||
{
|
||||
private readonly Hook<GetCursorPosDelegate> _getCursorPosHook;
|
||||
private readonly Hook<SetCursorPosDelegate> _setCursorPosHook;
|
||||
|
||||
public CursorPosHooks()
|
||||
{
|
||||
_getCursorPosHook = Service.GameInteropProvider.HookFromSymbol<GetCursorPosDelegate>("user32.dll", "GetCursorPos", GetCursorPosDetour);
|
||||
_getCursorPosHook.Enable();
|
||||
|
||||
_setCursorPosHook = Service.GameInteropProvider.HookFromSymbol<SetCursorPosDelegate>("user32.dll", "SetCursorPos", SetCursorPosDetour);
|
||||
_setCursorPosHook.Enable();
|
||||
}
|
||||
|
||||
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
|
||||
private unsafe delegate bool GetCursorPosDelegate(POINT* lpPoint);
|
||||
|
||||
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
|
||||
private unsafe delegate bool SetCursorPosDelegate(int x, int y);
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_getCursorPosHook.Dispose();
|
||||
}
|
||||
|
||||
private bool GetCursorPosDetour(POINT* lpPoint)
|
||||
{
|
||||
var rv = _getCursorPosHook.Original(lpPoint);
|
||||
|
||||
if (!rv)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
Service.Plugin.ConvertCoordsWinToGame(ref lpPoint->x, ref lpPoint->y);
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
private bool SetCursorPosDetour(int x, int y)
|
||||
{
|
||||
Service.Plugin.ConvertCoordsGameToWin(ref x, ref y);
|
||||
|
||||
return _setCursorPosHook.Original(x, y);
|
||||
}
|
||||
}
|
132
CustomResolution/Hooks/WndProcHook.cs
Normal file
132
CustomResolution/Hooks/WndProcHook.cs
Normal file
@ -0,0 +1,132 @@
|
||||
using CustomResolution.WndProcHookManagerProxyApi;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Reflection.Emit;
|
||||
using TerraFX.Interop.Windows;
|
||||
|
||||
|
||||
namespace CustomResolution.Hooks;
|
||||
|
||||
// THIS. IS. UGLY.
|
||||
public sealed class WndProcHook : IDisposable
|
||||
{
|
||||
private const uint WM_MOUSEFIRST = 0x0200;
|
||||
private const uint WM_MOUSELAST = 0x0209;
|
||||
|
||||
private static WndProcHook? _instance;
|
||||
|
||||
private readonly WndProcHookManager _manager;
|
||||
|
||||
private bool _applied = false;
|
||||
private DynamicMethod? _genMethod;
|
||||
private Delegate? _genDelegate;
|
||||
|
||||
public WndProcHook()
|
||||
{
|
||||
_instance = this;
|
||||
|
||||
_manager = new();
|
||||
|
||||
Apply();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (!_applied)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (_manager.PreWndProc is not { } preWndProcProp)
|
||||
{
|
||||
Service.PluginLog.Information("CustomResolution couldn't obtain the PreWndProc event.");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
preWndProcProp.RemoveEventHandler(_manager.ProxiedValue, _genDelegate);
|
||||
|
||||
_applied = false;
|
||||
}
|
||||
|
||||
public static void InvokeStatic(object args)
|
||||
{
|
||||
if (_instance is { } instance)
|
||||
{
|
||||
instance.Invoke(new WndProcEventArgs(instance._manager, args));
|
||||
}
|
||||
}
|
||||
|
||||
private static void ParamToCoords(LPARAM param, out int x, out int y)
|
||||
{
|
||||
x = (int)(param.Value & 0xFFFF);
|
||||
y = (int)(param.Value >> 16 & 0xFFFF);
|
||||
}
|
||||
|
||||
private static LPARAM CoordsToParam(int x, int y)
|
||||
{
|
||||
nint value = 0;
|
||||
value |= x & 0xFFFF;
|
||||
value |= y >> 16 & 0xFFFF;
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
private void Invoke(WndProcEventArgs args)
|
||||
{
|
||||
if (!(WM_MOUSEFIRST <= args.Message && args.Message <= WM_MOUSELAST))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ParamToCoords(args.LParam, out int x, out int y);
|
||||
|
||||
Service.Plugin.ConvertCoordsWinToGame(ref x, ref y);
|
||||
|
||||
args.LParam = CoordsToParam(x, y);
|
||||
}
|
||||
|
||||
private void Apply()
|
||||
{
|
||||
if (!_manager.Refresh())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (_applied)
|
||||
{
|
||||
Dispose();
|
||||
}
|
||||
|
||||
if (_manager.PreWndProc is not { } preWndProcProp)
|
||||
{
|
||||
Service.PluginLog.Information("CustomResolution couldn't obtain the PreWndProc event.");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (_genDelegate is null)
|
||||
{
|
||||
var delegateType = preWndProcProp.EventHandlerType!;
|
||||
var delegateInvoke = delegateType.GetMethod("Invoke")!;
|
||||
|
||||
_genMethod = new DynamicMethod(
|
||||
"CustomResolution_PreWndProc",
|
||||
delegateInvoke.ReturnType,
|
||||
delegateInvoke.GetParameters().Select(p => p.ParameterType).ToArray()
|
||||
);
|
||||
|
||||
var il = _genMethod.GetILGenerator();
|
||||
|
||||
il.Emit(OpCodes.Ldarg_0);
|
||||
il.Emit(OpCodes.Call, typeof(WndProcHook).GetMethod("InvokeStatic")!);
|
||||
il.Emit(OpCodes.Ret);
|
||||
|
||||
_genDelegate = _genMethod.CreateDelegate(delegateType);
|
||||
}
|
||||
|
||||
preWndProcProp.AddEventHandler(_manager.ProxiedValue, _genDelegate);
|
||||
|
||||
_applied = true;
|
||||
}
|
||||
}
|
@ -1 +0,0 @@
|
||||
GetClientRect
|
@ -2,11 +2,12 @@
|
||||
using Dalamud.Plugin;
|
||||
using Dalamud.Plugin.Services;
|
||||
using FFXIVClientStructs.FFXIV.Client.Graphics.Kernel;
|
||||
using FFXIVClientStructs.FFXIV.Client.UI;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Windows.Win32;
|
||||
using Windows.Win32.Foundation;
|
||||
using TerraFX.Interop.Windows;
|
||||
using static FFXIVClientStructs.FFXIV.Client.UI.Misc.GroupPoseModule;
|
||||
|
||||
namespace CustomResolution;
|
||||
|
||||
@ -27,6 +28,9 @@ public sealed unsafe class Plugin : IDalamudPlugin
|
||||
|
||||
Service.PluginUI = new();
|
||||
|
||||
Service.WndProcHook = new();
|
||||
Service.CursorPosHooks = new();
|
||||
|
||||
_cmds = typeof(Plugin).Assembly.GetTypes()
|
||||
.Where(t => !t.IsAbstract && typeof(Cmd).IsAssignableFrom(t))
|
||||
.Select(t => (Cmd) Activator.CreateInstance(t)!)
|
||||
@ -61,26 +65,66 @@ public sealed unsafe class Plugin : IDalamudPlugin
|
||||
cmd.Dispose();
|
||||
}
|
||||
|
||||
Service.CursorPosHooks.Dispose();
|
||||
Service.WndProcHook.Dispose();
|
||||
|
||||
Service.PluginUI.Dispose();
|
||||
|
||||
Service.Plugin = null!;
|
||||
}
|
||||
|
||||
public void ConvertCoordsWinToGame(ref int x, ref int y)
|
||||
{
|
||||
if (CurrentWidth == CurrentWindowWidth && CurrentHeight == CurrentWindowHeight)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
float scaleX = CurrentWidth / (float) CurrentWindowWidth;
|
||||
float scaleY = CurrentHeight / (float) CurrentWindowHeight;
|
||||
|
||||
x = (int) Math.Round(x * scaleX);
|
||||
y = (int) Math.Round(y * scaleY);
|
||||
}
|
||||
|
||||
public void ConvertCoordsGameToWin(ref int x, ref int y)
|
||||
{
|
||||
if (CurrentWidth == CurrentWindowWidth && CurrentHeight == CurrentWindowHeight)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
float scaleX = CurrentWindowWidth / (float) CurrentWidth;
|
||||
float scaleY = CurrentWindowHeight / (float) CurrentHeight;
|
||||
|
||||
x = (int) Math.Round(x * scaleX);
|
||||
y = (int) Math.Round(y * scaleY);
|
||||
}
|
||||
|
||||
public void Update()
|
||||
{
|
||||
var dev = Device.Instance();
|
||||
|
||||
var width = dev->Width;
|
||||
var height = dev->Height;
|
||||
RECT rect = default;
|
||||
|
||||
PInvoke.GetClientRect((HWND) (IntPtr) dev->hWnd, out var rect);
|
||||
TerraFX.Interop.Windows.Windows.GetClientRect((HWND) (IntPtr) dev->hWnd, &rect);
|
||||
|
||||
int rectWidth = rect.right - rect.left;
|
||||
int rectHeight = rect.bottom - rect.top;
|
||||
|
||||
if ((rectWidth <= 0 || rectHeight <= 0) && !_unloading)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
uint width, height;
|
||||
|
||||
if (Service.Config.IsScale || _unloading)
|
||||
{
|
||||
var scale = _unloading ? 1f : Service.Config.Scale;
|
||||
|
||||
width = (uint) Math.Round(rect.Width * scale);
|
||||
height = (uint) Math.Round(rect.Height * scale);
|
||||
width = (uint) Math.Round(rectWidth * scale);
|
||||
height = (uint) Math.Round(rectHeight * scale);
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -90,6 +134,8 @@ public sealed unsafe class Plugin : IDalamudPlugin
|
||||
|
||||
if (width != dev->Width || height != dev->Height)
|
||||
{
|
||||
Service.PluginLog.Info($"Changing resolution to {width} x {height}");
|
||||
|
||||
if (width < 256)
|
||||
{
|
||||
width = 256;
|
||||
@ -107,8 +153,8 @@ public sealed unsafe class Plugin : IDalamudPlugin
|
||||
|
||||
CurrentWidth = width;
|
||||
CurrentHeight = height;
|
||||
CurrentWindowWidth = (uint) rect.Width;
|
||||
CurrentWindowHeight = (uint) rect.Height;
|
||||
CurrentWindowWidth = (uint) rectWidth;
|
||||
CurrentWindowHeight = (uint) rectHeight;
|
||||
}
|
||||
|
||||
private void OnFrameworkUpdate(IFramework framework)
|
||||
|
@ -41,6 +41,6 @@ public sealed class PluginUI : IDisposable
|
||||
|
||||
private void ShowConfigWindow()
|
||||
{
|
||||
|
||||
SettingsVisible = true;
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
using Dalamud.IoC;
|
||||
using CustomResolution.Hooks;
|
||||
using Dalamud.IoC;
|
||||
using Dalamud.Plugin;
|
||||
using System;
|
||||
using Dalamud.Plugin.Services;
|
||||
|
||||
namespace CustomResolution;
|
||||
@ -13,6 +13,9 @@ public sealed class Service
|
||||
|
||||
public static PluginUI PluginUI { get; internal set; } = null!;
|
||||
|
||||
public static WndProcHook WndProcHook { get; internal set; } = null!;
|
||||
public static CursorPosHooks CursorPosHooks { get; internal set; } = null!;
|
||||
|
||||
[PluginService]
|
||||
public static DalamudPluginInterface PluginInterface { get; private set; } = null!;
|
||||
|
||||
@ -26,7 +29,8 @@ public sealed class Service
|
||||
public static IClientState ClientState { get; private set; } = null!;
|
||||
|
||||
[PluginService]
|
||||
public static IPluginLog PluginLog { get; private set; } = null!;
|
||||
public static IGameInteropProvider GameInteropProvider { get; private set; } = null!;
|
||||
|
||||
public static Func<string, object?>? GetPluginInstance { get; private set; }
|
||||
[PluginService]
|
||||
public static IPluginLog PluginLog { get; private set; } = null!;
|
||||
}
|
||||
|
@ -0,0 +1,8 @@
|
||||
namespace CustomResolution.WndProcHookManagerProxyApi;
|
||||
|
||||
public interface IWndProcHookManagerProxy
|
||||
{
|
||||
WndProcHookManager WndProcHookManager { get; }
|
||||
object ProxiedValue { get; }
|
||||
bool IsValid => WndProcHookManager is not null && ProxiedValue is not null;
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
using TerraFX.Interop.Windows;
|
||||
|
||||
namespace CustomResolution.WndProcHookManagerProxyApi;
|
||||
|
||||
// This could be optimized in a thousand different ways (better dynamic methods, source-gen this class), but eh.
|
||||
|
||||
public readonly record struct WndProcEventArgs(WndProcHookManager WndProcHookManager, object ProxiedValue) : IWndProcHookManagerProxy
|
||||
{
|
||||
public HWND Hwnd => (HWND) WndProcHookManager.GetValue(ProxiedValue, nameof(Hwnd))!;
|
||||
public int ViewportId => (int) WndProcHookManager.GetValue(ProxiedValue, nameof(ViewportId))!;
|
||||
|
||||
public uint Message
|
||||
{
|
||||
get => (uint) WndProcHookManager.GetValue(ProxiedValue, nameof(Message))!;
|
||||
set => WndProcHookManager.SetValue(ProxiedValue, nameof(Message), value);
|
||||
}
|
||||
|
||||
public WPARAM WParam
|
||||
{
|
||||
get => (WPARAM) WndProcHookManager.GetValue(ProxiedValue, nameof(WParam))!;
|
||||
set => WndProcHookManager.SetValue(ProxiedValue, nameof(WParam), value);
|
||||
}
|
||||
|
||||
public LPARAM LParam
|
||||
{
|
||||
get => (LPARAM) WndProcHookManager.GetValue(ProxiedValue, nameof(LParam))!;
|
||||
set => WndProcHookManager.SetValue(ProxiedValue, nameof(LParam), value);
|
||||
}
|
||||
|
||||
public bool SuppressCall
|
||||
{
|
||||
get => (bool) WndProcHookManager.GetValue(ProxiedValue, nameof(SuppressCall))!;
|
||||
set => WndProcHookManager.SetValue(ProxiedValue, nameof(SuppressCall), value);
|
||||
}
|
||||
}
|
@ -0,0 +1,289 @@
|
||||
using Dalamud.IoC;
|
||||
using MonoMod.Utils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace CustomResolution.WndProcHookManagerProxyApi;
|
||||
|
||||
public class WndProcHookManager
|
||||
{
|
||||
internal const BindingFlags _BindingFlags = BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static;
|
||||
|
||||
private object? _realManager;
|
||||
private Type? _realManagerType;
|
||||
|
||||
private WeakReference? _lastManager;
|
||||
private readonly Dictionary<(Type, string), FastReflectionDelegate> _methodCache = new();
|
||||
private readonly Dictionary<(Type, string), MemberInfo> _memberCache = new();
|
||||
|
||||
public WndProcHookManager()
|
||||
{
|
||||
Refresh();
|
||||
}
|
||||
|
||||
public bool IsAvailable => _realManager is not null;
|
||||
public object? ProxiedValue => _realManager;
|
||||
|
||||
public EventInfo? PreWndProc { get; private set; }
|
||||
|
||||
public bool Refresh()
|
||||
{
|
||||
if (IsAvailable)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
Service.PluginLog.Information("Refreshing WndProcHookManagerProxyApi");
|
||||
|
||||
try
|
||||
{
|
||||
if (typeof(PluginServiceAttribute).Assembly.GetType("Dalamud.Service`1") is not { } serviceContainerContainer)
|
||||
{
|
||||
Service.PluginLog.Warning("CustomResolution couldn't find the service container types.");
|
||||
|
||||
Unset();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (typeof(PluginServiceAttribute).Assembly.GetType("Dalamud.Hooking.WndProcHook.WndProcHookManager") is not { } wndProcHookManagerType)
|
||||
{
|
||||
Service.PluginLog.Warning("CustomResolution couldn't find the WndProcHookManager type.");
|
||||
|
||||
Unset();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
_realManagerType = wndProcHookManagerType;
|
||||
|
||||
serviceContainerContainer = serviceContainerContainer.MakeGenericType(wndProcHookManagerType);
|
||||
|
||||
if (serviceContainerContainer.GetMethod("Get")?.Invoke(null, Array.Empty<object>()) is not object manager)
|
||||
{
|
||||
Service.PluginLog.Warning("CustomResolution couldn't obtain the WndProcHookManager instance.");
|
||||
|
||||
Unset();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
_realManager = manager;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Service.PluginLog.Warning($"CustomResolution couldn't obtain the WndProcHookManager service: {e}");
|
||||
|
||||
Unset();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
Service.PluginLog.Information($"WndProcHookManager found: {_realManagerType.Assembly.FullName}");
|
||||
|
||||
if (_lastManager?.Target != _realManager)
|
||||
{
|
||||
Service.PluginLog.Information($"Different WndProcHookManager, clearing cache");
|
||||
|
||||
ClearCache();
|
||||
|
||||
_lastManager = new WeakReference(_realManager);
|
||||
}
|
||||
|
||||
PreWndProc = _realManagerType.GetEvent("PreWndProc");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
internal FastReflectionDelegate GetMethod(Type target, string name, Func<Type, MethodBase>? getMethod = null)
|
||||
{
|
||||
if (_methodCache.TryGetValue((target, name), out var fun))
|
||||
{
|
||||
return fun;
|
||||
}
|
||||
|
||||
return _methodCache[(target, name)] = FastReflectionHelper.CreateFastDelegate(getMethod?.Invoke(target) ?? target.GetMethods().FirstOrDefault(m => m.Name == name) ?? target.GetMethod(name, _BindingFlags)!);
|
||||
}
|
||||
|
||||
internal object? InvokeStatic(string typeName, string name, params object?[] args)
|
||||
{
|
||||
if (_realManagerType?.Assembly.GetType(typeName) is not { } type)
|
||||
{
|
||||
Service.PluginLog.Information($"GetStaticValue failed, type \"{typeName}\" not found");
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
return GetMethod(type, name).Invoke(null, args);
|
||||
}
|
||||
|
||||
internal object? Invoke(object proxiedValue, string name, params object?[] args)
|
||||
{
|
||||
return GetMethod(proxiedValue.GetType(), name).Invoke(proxiedValue, args);
|
||||
}
|
||||
|
||||
internal object? GetStaticValue(string typeName, string name, params object?[] args)
|
||||
{
|
||||
if (_realManagerType?.Assembly.GetType(typeName) is not { } type)
|
||||
{
|
||||
Service.PluginLog.Information($"GetStaticValue failed, type \"{typeName}\" not found");
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!_memberCache.TryGetValue((type, name), out var member))
|
||||
{
|
||||
member = _memberCache[(type, name)] = type.GetMember(name, _BindingFlags).First();
|
||||
}
|
||||
|
||||
if (member is FieldInfo field)
|
||||
{
|
||||
return field.GetValue(null);
|
||||
}
|
||||
|
||||
if (member is PropertyInfo property)
|
||||
{
|
||||
return property.GetValue(null, args);
|
||||
}
|
||||
|
||||
return InvokeStatic(typeName, name, args);
|
||||
}
|
||||
|
||||
internal object? GetValue(object proxiedValue, string name, params object?[] args)
|
||||
{
|
||||
if (!_memberCache.TryGetValue((proxiedValue.GetType(), name), out var member))
|
||||
{
|
||||
member = _memberCache[(proxiedValue.GetType(), name)] = proxiedValue.GetType().GetMember(name, _BindingFlags).First();
|
||||
}
|
||||
|
||||
if (member is FieldInfo field)
|
||||
{
|
||||
return field.GetValue(proxiedValue);
|
||||
}
|
||||
|
||||
if (member is PropertyInfo property)
|
||||
{
|
||||
return property.GetValue(proxiedValue, args);
|
||||
}
|
||||
|
||||
return Invoke(proxiedValue, name, args);
|
||||
}
|
||||
|
||||
internal void SetStaticValue(string typeName, string name, params object?[] args)
|
||||
{
|
||||
if (_realManagerType?.Assembly.GetType(typeName) is not { } type)
|
||||
{
|
||||
Service.PluginLog.Information($"GetStaticValue failed, type \"{typeName}\" not found");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_memberCache.TryGetValue((type, name), out var member))
|
||||
{
|
||||
member = _memberCache[(type, name)] = type.GetMember(name, _BindingFlags).First();
|
||||
}
|
||||
|
||||
if (member is FieldInfo field)
|
||||
{
|
||||
field.SetValue(null, args[0]);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (member is PropertyInfo property)
|
||||
{
|
||||
property.SetValue(null, args.First(), args.Length > 1 ? args.Skip(1).ToArray() : null);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
InvokeStatic(typeName, name, args);
|
||||
}
|
||||
|
||||
internal void SetValue(object proxiedValue, string name, params object?[] args)
|
||||
{
|
||||
if (!_memberCache.TryGetValue((proxiedValue.GetType(), name), out var member))
|
||||
{
|
||||
member = _memberCache[(proxiedValue.GetType(), name)] = proxiedValue.GetType().GetMember(name, _BindingFlags).First();
|
||||
}
|
||||
|
||||
if (member is FieldInfo field)
|
||||
{
|
||||
field.SetValue(proxiedValue, args[0]);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (member is PropertyInfo property)
|
||||
{
|
||||
property.SetValue(proxiedValue, args.First(), args.Length > 1 ? args.Skip(1).ToArray() : null);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
Invoke(proxiedValue, name, args);
|
||||
}
|
||||
|
||||
internal ITuple ConvertValueTuple(Type targetType, object proxiedValue)
|
||||
{
|
||||
var rawTuple = (ITuple) proxiedValue;
|
||||
var dstItems = new object?[rawTuple.Length];
|
||||
var rawTypes = proxiedValue.GetType().GenericTypeArguments;
|
||||
var dstTypes = targetType.GenericTypeArguments;
|
||||
|
||||
for (var i = 0; i < rawTuple.Length; i++)
|
||||
{
|
||||
var raw = rawTuple[i];
|
||||
|
||||
if (dstTypes[i].IsAssignableFrom(rawTypes[i]))
|
||||
{
|
||||
dstItems[i] = raw;
|
||||
}
|
||||
else if (raw is not null)
|
||||
{
|
||||
var dstType = dstTypes[i];
|
||||
|
||||
var isNullable = dstType.FullName?.StartsWith("System.Nullable`1[[") ?? false;
|
||||
|
||||
if (isNullable)
|
||||
{
|
||||
dstType = dstType.GenericTypeArguments[0];
|
||||
}
|
||||
|
||||
dstItems[i] = Activator.CreateInstance(dstType, new object[] { this, raw });
|
||||
|
||||
if (isNullable)
|
||||
{
|
||||
dstItems[i] = Activator.CreateInstance(dstTypes[i], new object[] { dstItems[i]! });
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
dstItems[i] = null;
|
||||
}
|
||||
}
|
||||
|
||||
return (ITuple) Activator.CreateInstance(targetType, dstItems)!;
|
||||
}
|
||||
|
||||
private void Unset()
|
||||
{
|
||||
_realManager = default;
|
||||
_realManagerType = default;
|
||||
|
||||
PreWndProc = default;
|
||||
|
||||
ClearCache();
|
||||
}
|
||||
|
||||
private void ClearCache()
|
||||
{
|
||||
_lastManager = null;
|
||||
_methodCache.Clear();
|
||||
_memberCache.Clear();
|
||||
}
|
||||
}
|
@ -8,46 +8,11 @@
|
||||
"resolved": "2.1.12",
|
||||
"contentHash": "Sc0PVxvgg4NQjcI8n10/VfUQBAS4O+Fw2pZrAqBdRMbthYGeogzu5+xmIGCGmsEZ/ukMOBuAqiNiB5qA3MRalg=="
|
||||
},
|
||||
"Microsoft.Windows.CsWin32": {
|
||||
"TerraFX.Interop.Windows": {
|
||||
"type": "Direct",
|
||||
"requested": "[0.3.49-beta, )",
|
||||
"resolved": "0.3.49-beta",
|
||||
"contentHash": "/iAXplVDKESFyLz/MShuVYVx62YFvFePpq3qrzREl8KScQqG6Z2kV7r9UJTjQS1g+hfy3V+e4m1x9lsu24YNSg==",
|
||||
"dependencies": {
|
||||
"Microsoft.Windows.SDK.Win32Docs": "0.1.42-alpha",
|
||||
"Microsoft.Windows.SDK.Win32Metadata": "55.0.45-preview",
|
||||
"Microsoft.Windows.WDK.Win32Metadata": "0.9.9-experimental",
|
||||
"System.Memory": "4.5.5",
|
||||
"System.Runtime.CompilerServices.Unsafe": "6.0.0"
|
||||
}
|
||||
},
|
||||
"Microsoft.Windows.SDK.Win32Docs": {
|
||||
"type": "Transitive",
|
||||
"resolved": "0.1.42-alpha",
|
||||
"contentHash": "Z/9po23gUA9aoukirh2ItMU2ZS9++Js9Gdds9fu5yuMojDrmArvY2y+tq9985tR3cxFxpZO1O35Wjfo0khj5HA=="
|
||||
},
|
||||
"Microsoft.Windows.SDK.Win32Metadata": {
|
||||
"type": "Transitive",
|
||||
"resolved": "55.0.45-preview",
|
||||
"contentHash": "2i1kVIk8rMuMCYtoeYeWbmCRLqNgb63Berac3Gv+J/pjp0oucopasKgLQNslryhsgBl4xex9ENhJX2MXEW5vdA=="
|
||||
},
|
||||
"Microsoft.Windows.WDK.Win32Metadata": {
|
||||
"type": "Transitive",
|
||||
"resolved": "0.9.9-experimental",
|
||||
"contentHash": "cncPlxLqR25mFOuvpxZPgXgJqRmXyV5eTULj/9w8AQvO/c1xJDEapohRsWkYaia7hCR/EwEzBHr7RU9u93T03w==",
|
||||
"dependencies": {
|
||||
"Microsoft.Windows.SDK.Win32Metadata": "55.0.45-preview"
|
||||
}
|
||||
},
|
||||
"System.Memory": {
|
||||
"type": "Transitive",
|
||||
"resolved": "4.5.5",
|
||||
"contentHash": "XIWiDvKPXaTveaB7HVganDlOCRoj03l+jrwNvcge/t8vhGYKvqV+dMv6G4SAX2NoNmN0wZfVPTAlFwZcZvVOUw=="
|
||||
},
|
||||
"System.Runtime.CompilerServices.Unsafe": {
|
||||
"type": "Transitive",
|
||||
"resolved": "6.0.0",
|
||||
"contentHash": "/iUeP3tq1S0XdNNoMz5C9twLSrM/TH+qElHkXWaPvuNOt+99G75NrV0OS2EqHx5wMN7popYjpc8oTjC1y16DLg=="
|
||||
"requested": "[10.0.22621.2, )",
|
||||
"resolved": "10.0.22621.2",
|
||||
"contentHash": "lORoYCoURS33Vi7PDvubRugLF2+l5v3rX2oVEqNhpBLjs3aZpqapRvTHPKVwsC+dGrsGuqJ/3yXuxVUb0vl3vg=="
|
||||
}
|
||||
}
|
||||
}
|
||||
|
5
README.md
Normal file
5
README.md
Normal file
@ -0,0 +1,5 @@
|
||||
# CustomResolution
|
||||
### Gluing PatMe and Ffxiv2Mqtt together
|
||||
Basic plugin that pokes Ffxiv2Mqtt every time PatMe detects a pat.
|
||||
|
||||
![preview of MQTTExplorer showing the ffxiv/patme/cid/emote topics](./preview.png)
|
10
release.bat
Normal file
10
release.bat
Normal file
@ -0,0 +1,10 @@
|
||||
@set src=.\CustomResolution\bin\x64\Release\CustomResolution
|
||||
@set dst=O:\home\ade\wwwext\xiv\CustomResolution
|
||||
|
||||
dotnet build -c Release
|
||||
|
||||
rmdir /s /q %dst%
|
||||
xcopy %src%\ %dst%\ /E
|
||||
xcopy .\CustomResolution\images\icon.png %dst%\
|
||||
|
||||
pause
|
Loading…
Reference in New Issue
Block a user