Remove most hand-authored native functions

This commit is contained in:
goaaats 2025-04-12 22:20:55 +02:00
parent 1bc216ccd6
commit fe562e8cf3
14 changed files with 204 additions and 1744 deletions

View file

@ -21,6 +21,7 @@ using Dalamud.Utility;
using Newtonsoft.Json;
using Serilog;
using Serilog.Events;
using Windows.Win32.UI.WindowsAndMessaging;
namespace Dalamud.Configuration.Internal;
@ -595,11 +596,15 @@ internal sealed class DalamudConfiguration : IInternalDisposableService
{
// https://source.chromium.org/chromium/chromium/src/+/main:ui/gfx/animation/animation_win.cc;l=29?q=ReducedMotion&ss=chromium
var winAnimEnabled = 0;
var success = NativeFunctions.SystemParametersInfo(
(uint)NativeFunctions.AccessibilityParameter.SPI_GETCLIENTAREAANIMATION,
0,
ref winAnimEnabled,
0);
var success = false;
unsafe
{
success = Windows.Win32.PInvoke.SystemParametersInfo(
SYSTEM_PARAMETERS_INFO_ACTION.SPI_GETCLIENTAREAANIMATION,
0,
&winAnimEnabled,
0);
}
if (!success)
{

View file

@ -15,6 +15,7 @@ using Dalamud.Utility;
using Dalamud.Utility.Timing;
using PInvoke;
using Serilog;
using Windows.Win32.Foundation;
#if DEBUG
[assembly: InternalsVisibleTo("Dalamud.CorePlugin")]
@ -29,13 +30,13 @@ namespace Dalamud;
/// The main Dalamud class containing all subsystems.
/// </summary>
[ServiceManager.ProvidedService]
internal sealed class Dalamud : IServiceType
internal sealed unsafe class Dalamud : IServiceType
{
#region Internals
private static int shownServiceError = 0;
private readonly ManualResetEvent unloadSignal;
#endregion
/// <summary>
@ -48,15 +49,15 @@ internal sealed class Dalamud : IServiceType
public Dalamud(DalamudStartInfo info, ReliableFileStorage fs, DalamudConfiguration configuration, IntPtr mainThreadContinueEvent)
{
this.StartInfo = info;
this.unloadSignal = new ManualResetEvent(false);
this.unloadSignal.Reset();
// Directory resolved signatures(CS, our own) will be cached in
var cacheDir = new DirectoryInfo(Path.Combine(this.StartInfo.WorkingDirectory!, "cachedSigs"));
if (!cacheDir.Exists)
cacheDir.Create();
// Set up the SigScanner for our target module
TargetSigScanner scanner;
using (Timings.Start("SigScanner Init"))
@ -71,21 +72,21 @@ internal sealed class Dalamud : IServiceType
configuration,
scanner,
Localization.FromAssets(info.AssetDirectory!, configuration.LanguageOverride));
// Set up FFXIVClientStructs
this.SetupClientStructsResolver(cacheDir);
void KickoffGameThread()
{
Log.Verbose("=============== GAME THREAD KICKOFF ===============");
Timings.Event("Game thread kickoff");
NativeFunctions.SetEvent(mainThreadContinueEvent);
Windows.Win32.PInvoke.SetEvent(new HANDLE(mainThreadContinueEvent));
}
void HandleServiceInitFailure(Task t)
{
Log.Error(t.Exception!, "Service initialization failure");
if (Interlocked.CompareExchange(ref shownServiceError, 1, 0) != 0)
return;
@ -116,15 +117,15 @@ internal sealed class Dalamud : IServiceType
HandleServiceInitFailure(t);
});
this.DefaultExceptionFilter = NativeFunctions.SetUnhandledExceptionFilter(nint.Zero);
NativeFunctions.SetUnhandledExceptionFilter(this.DefaultExceptionFilter);
Log.Debug($"SE default exception filter at {this.DefaultExceptionFilter.ToInt64():X}");
this.DefaultExceptionFilter = SetExceptionHandler(nint.Zero);
SetExceptionHandler(this.DefaultExceptionFilter);
Log.Debug($"SE default exception filter at {new IntPtr(this.DefaultExceptionFilter):X}");
var debugSig = "40 55 53 57 48 8D AC 24 70 AD FF FF";
this.DebugExceptionFilter = Service<TargetSigScanner>.Get().ScanText(debugSig);
Log.Debug($"SE debug exception filter at {this.DebugExceptionFilter.ToInt64():X}");
}
/// <summary>
/// Gets the start information for this Dalamud instance.
/// </summary>
@ -188,28 +189,30 @@ internal sealed class Dalamud : IServiceType
/// <summary>
/// Replace the current exception handler with the default one.
/// </summary>
internal void UseDefaultExceptionHandler() =>
this.SetExceptionHandler(this.DefaultExceptionFilter);
internal void UseDefaultExceptionHandler() =>
SetExceptionHandler(this.DefaultExceptionFilter);
/// <summary>
/// Replace the current exception handler with a debug one.
/// </summary>
internal void UseDebugExceptionHandler() =>
this.SetExceptionHandler(this.DebugExceptionFilter);
SetExceptionHandler(this.DebugExceptionFilter);
/// <summary>
/// Disable the current exception handler.
/// </summary>
internal void UseNoExceptionHandler() =>
this.SetExceptionHandler(nint.Zero);
SetExceptionHandler(nint.Zero);
/// <summary>
/// Helper function to set the exception handler.
/// </summary>
private void SetExceptionHandler(nint newFilter)
private static nint SetExceptionHandler(nint newFilter)
{
var oldFilter = NativeFunctions.SetUnhandledExceptionFilter(newFilter);
Log.Debug("Set ExceptionFilter to {0}, old: {1}", newFilter, oldFilter);
var oldFilter =
Windows.Win32.PInvoke.SetUnhandledExceptionFilter((delegate* unmanaged[Stdcall]<global::Windows.Win32.System.Diagnostics.Debug.EXCEPTION_POINTERS*, int>)newFilter);
Log.Debug("Set ExceptionFilter to {0}, old: {1}", newFilter, (nint)oldFilter);
return (nint)oldFilter;
}
private void SetupClientStructsResolver(DirectoryInfo cacheDir)

View file

@ -35,6 +35,10 @@
<Deterministic>true</Deterministic>
<Nullable>annotations</Nullable>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<!-- Enable when cswin32 properly supports implementing COM interfaces and we can
make IDropTarget work -->
<!-- <DisableRuntimeMarshalling>true</DisableRuntimeMarshalling> -->
</PropertyGroup>
<PropertyGroup Label="Configuration">

View file

@ -23,8 +23,6 @@ using Serilog.Events;
using Windows.Win32.Foundation;
using Windows.Win32.UI.WindowsAndMessaging;
using static Dalamud.NativeFunctions;
namespace Dalamud;
/// <summary>
@ -264,10 +262,12 @@ public sealed class EntryPoint
var symbolPath = Path.Combine(info.AssetDirectory, "UIRes", "pdb");
var searchPath = $".;{symbolPath}";
// Remove any existing Symbol Handler and Init a new one with our search path added
SymCleanup(GetCurrentProcess());
var currentProcess = Windows.Win32.PInvoke.GetCurrentProcess_SafeHandle();
if (!SymInitialize(GetCurrentProcess(), searchPath, true))
// Remove any existing Symbol Handler and Init a new one with our search path added
Windows.Win32.PInvoke.SymCleanup(currentProcess);
if (!Windows.Win32.PInvoke.SymInitialize(currentProcess, searchPath, true))
throw new Win32Exception();
}
catch (Exception ex)

View file

@ -55,4 +55,37 @@ internal sealed class WinSockHandlers : IInternalDisposableService
return socket;
}
/// <summary>
/// Native ws2_32 functions.
/// </summary>
private static class NativeFunctions
{
/// <summary>
/// See https://docs.microsoft.com/en-us/windows/win32/api/winsock/nf-winsock-setsockopt.
/// The setsockopt function sets a socket option.
/// </summary>
/// <param name="socket">
/// A descriptor that identifies a socket.
/// </param>
/// <param name="level">
/// The level at which the option is defined (for example, SOL_SOCKET).
/// </param>
/// <param name="optName">
/// The socket option for which the value is to be set (for example, SO_BROADCAST). The optname parameter must be a
/// socket option defined within the specified level, or behavior is undefined.
/// </param>
/// <param name="optVal">
/// A pointer to the buffer in which the value for the requested option is specified.
/// </param>
/// <param name="optLen">
/// The size, in bytes, of the buffer pointed to by the optval parameter.
/// </param>
/// <returns>
/// If no error occurs, setsockopt returns zero. Otherwise, a value of SOCKET_ERROR is returned, and a specific error
/// code can be retrieved by calling WSAGetLastError.
/// </returns>
[DllImport("ws2_32.dll", CallingConvention = CallingConvention.Winapi, EntryPoint = "setsockopt")]
public static extern int SetSockOpt(IntPtr socket, SocketOptionLevel level, SocketOptionName optName, ref IntPtr optVal, int optLen);
}
}

View file

@ -70,12 +70,12 @@ public abstract class Hook<T> : IDalamudHook where T : Delegate
/// <inheritdoc/>
public virtual string BackendName => throw new NotImplementedException();
/// <summary>
/// Gets the unique GUID for this hook.
/// </summary>
protected Guid HookId { get; } = Guid.NewGuid();
/// <summary>
/// Remove a hook from the current process.
/// </summary>
@ -200,11 +200,11 @@ public abstract class Hook<T> : IDalamudHook where T : Delegate
if (EnvironmentConfiguration.DalamudForceMinHook)
useMinHook = true;
var moduleHandle = NativeFunctions.GetModuleHandleW(moduleName);
if (moduleHandle == IntPtr.Zero)
using var moduleHandle = Windows.Win32.PInvoke.GetModuleHandle(moduleName);
if (moduleHandle.IsInvalid)
throw new Exception($"Could not get a handle to module {moduleName}");
var procAddress = NativeFunctions.GetProcAddress(moduleHandle, exportName);
var procAddress = (nint)Windows.Win32.PInvoke.GetProcAddress(moduleHandle, exportName);
if (procAddress == IntPtr.Zero)
throw new Exception($"Could not get the address of {moduleName}::{exportName}");

View file

@ -29,14 +29,14 @@ internal class AssertHandler : IDisposable
/// <summary>
/// Initializes a new instance of the <see cref="AssertHandler"/> class.
/// </summary>
public AssertHandler()
public unsafe AssertHandler()
{
this.callback = (expr, file, line) => this.OnImGuiAssert(expr, file, line);
this.callback = this.OnImGuiAssert;
}
private delegate void AssertCallbackDelegate(
[MarshalAs(UnmanagedType.LPStr)] string expr,
[MarshalAs(UnmanagedType.LPStr)] string file,
private unsafe delegate void AssertCallbackDelegate(
void* expr,
void* file,
int line);
/// <summary>
@ -53,15 +53,15 @@ internal class AssertHandler : IDisposable
/// <summary>
/// Register the cimgui assert handler with the native library.
/// </summary>
public void Setup()
public unsafe void Setup()
{
CustomNativeFunctions.igCustom_SetAssertCallback(this.callback);
CustomNativeFunctions.igCustom_SetAssertCallback(Marshal.GetFunctionPointerForDelegate(this.callback).ToPointer());
}
/// <summary>
/// Unregister the cimgui assert handler with the native library.
/// </summary>
public void Shutdown()
public unsafe void Shutdown()
{
CustomNativeFunctions.igCustom_SetAssertCallback(null);
}
@ -72,8 +72,19 @@ internal class AssertHandler : IDisposable
this.Shutdown();
}
private void OnImGuiAssert(string expr, string file, int line)
private unsafe void OnImGuiAssert(void* pExpr, void* pFile, int line)
{
var expr = Marshal.PtrToStringAnsi(new IntPtr(pExpr));
var file = Marshal.PtrToStringAnsi(new IntPtr(pFile));
if (expr == null || file == null)
{
Log.Warning("ImGui assertion failed: {Expr} at {File}:{Line} (failed to parse)",
expr,
file,
line);
return;
}
var key = $"{file}:{line}";
if (this.ignoredAsserts.Contains(key))
return;
@ -218,11 +229,11 @@ internal class AssertHandler : IDisposable
}
}
private static class CustomNativeFunctions
private static unsafe class CustomNativeFunctions
{
[DllImport("cimgui", CallingConvention = CallingConvention.Cdecl)]
#pragma warning disable SA1300
public static extern void igCustom_SetAssertCallback(AssertCallbackDelegate? callback);
public static extern void igCustom_SetAssertCallback(void* cb);
#pragma warning restore SA1300
}
}

View file

@ -41,6 +41,8 @@ using TerraFX.Interop.Windows;
using static TerraFX.Interop.Windows.Windows;
using DWMWINDOWATTRIBUTE = Windows.Win32.Graphics.Dwm.DWMWINDOWATTRIBUTE;
// general dev notes, here because it's easiest
/*
@ -487,12 +489,13 @@ internal partial class InterfaceManager : IInternalDisposableService
{
if (this.GameWindowHandle == 0)
throw new InvalidOperationException("Game window is not yet ready.");
var value = enabled ? 1u : 0u;
DwmSetWindowAttribute(
this.GameWindowHandle,
(uint)DWMWINDOWATTRIBUTE.DWMWA_USE_IMMERSIVE_DARK_MODE,
global::Windows.Win32.PInvoke.DwmSetWindowAttribute(
new(this.GameWindowHandle.Value),
DWMWINDOWATTRIBUTE.DWMWA_USE_IMMERSIVE_DARK_MODE,
&value,
sizeof(int)).ThrowOnError();
sizeof(uint)).ThrowOnFailure();
}
private static InterfaceManager WhenFontsReady()

View file

@ -70,12 +70,12 @@ public interface IFontAtlasBuildToolkitPreBuild : IFontAtlasBuildToolkit
/// Adds a font from memory region allocated using <see cref="ImGuiHelpers.AllocateMemory"/>.<br />
/// <b>It WILL crash if you try to use a memory pointer allocated in some other way.</b><br />
/// <b>
/// Do NOT call <see cref="ImGuiNative.igMemFree"/> on the <paramref name="dataPointer"/> once this function has
/// Do NOT call <see cref="ImGui.MemFree"/> on the <paramref name="dataPointer"/> once this function has
/// been called, unless <paramref name="freeOnException"/> is set and the function has thrown an error.
/// </b>
/// </summary>
/// <param name="dataPointer">Memory address for the data allocated using <see cref="ImGuiHelpers.AllocateMemory"/>.</param>
/// <param name="dataSize">The size of the font file..</param>
/// <param name="dataSize">The size of the font file.</param>
/// <param name="fontConfig">The font config.</param>
/// <param name="freeOnException">Free <paramref name="dataPointer"/> if an exception happens.</param>
/// <param name="debugTag">A debug tag.</param>
@ -155,7 +155,7 @@ public interface IFontAtlasBuildToolkitPreBuild : IFontAtlasBuildToolkit
/// used as the font size. Specify -1 to use the default font size.
/// </param>
/// <param name="glyphRanges">The glyph ranges. Use <see cref="FontAtlasBuildToolkitUtilities"/>.ToGlyphRange to build.</param>
/// <returns>A font returned from <see cref="ImFontAtlasPtr.AddFont"/>.</returns>
/// <returns>A font returned from <see cref="ImFontAtlasPtr.AddFont(ImFontConfig*)"/>.</returns>
ImFontPtr AddDalamudDefaultFont(float sizePx, ushort[]? glyphRanges = null);
/// <summary>

View file

@ -56,9 +56,9 @@ public interface IFontHandle : IDisposable
/// You may not access the font once you dispose this object.
/// </summary>
/// <returns>A disposable object that will pop the font on dispose.</returns>
/// <exception cref="InvalidOperationException">If called outside of the main thread.</exception>
/// <exception cref="InvalidOperationException">If called outside the main thread.</exception>
/// <remarks>
/// <para>This function uses <see cref="ImGui.PushFont"/>, and may do extra things.
/// <para>This function uses <see cref="ImGui.PushFont(ImFontPtr)"/>, and may do extra things.
/// Use <see cref="IDisposable.Dispose"/> or <see cref="Pop"/> to undo this operation.
/// Do not use <see cref="ImGui.PopFont"/>.</para>
/// </remarks>

View file

@ -9,10 +9,9 @@ using Dalamud.Utility;
using FFXIVClientStructs.FFXIV.Client.System.Memory;
using FFXIVClientStructs.FFXIV.Client.System.String;
using Microsoft.Extensions.ObjectPool;
using Windows.Win32.Foundation;
using Windows.Win32.System.Memory;
using static Dalamud.NativeFunctions;
using LPayloadType = Lumina.Text.Payloads.PayloadType;
using LSeString = Lumina.Text.SeString;
@ -28,6 +27,8 @@ public static unsafe class MemoryHelper
private static readonly ObjectPool<StringBuilder> StringBuilderPool =
ObjectPool.Create(new StringBuilderPooledObjectPolicy());
private static readonly HANDLE ThisProcessPseudoHandle = new(unchecked((nint)0xFFFFFFFF));
#region Cast
/// <summary>Casts the given memory address as the reference to the live object.</summary>
@ -863,14 +864,22 @@ public static unsafe class MemoryHelper
unchecked
{
var length = value.Length;
var result = NativeFunctions.ReadProcessMemory((nint)0xFFFFFFFF, memoryAddress, value, length, out _);
fixed (byte* pVal = value)
{
var result = Windows.Win32.PInvoke.ReadProcessMemory(
ThisProcessPseudoHandle,
memoryAddress.ToPointer(),
pVal,
(nuint)length,
null);
if (!result)
throw new MemoryReadException($"Unable to read memory at {Util.DescribeAddress(memoryAddress)} of length {length} (result={result})");
if (!result)
throw new MemoryReadException($"Unable to read memory at {Util.DescribeAddress(memoryAddress)} of length {length} (result={result})");
var last = Marshal.GetLastWin32Error();
if (last > 0)
throw new MemoryReadException($"Unable to read memory at {Util.DescribeAddress(memoryAddress)} of length {length} (error={last})");
var last = Marshal.GetLastWin32Error();
if (last > 0)
throw new MemoryReadException($"Unable to read memory at {Util.DescribeAddress(memoryAddress)} of length {length} (error={last})");
}
}
}
@ -885,14 +894,22 @@ public static unsafe class MemoryHelper
unchecked
{
var length = data.Length;
var result = NativeFunctions.WriteProcessMemory((nint)0xFFFFFFFF, memoryAddress, data, length, out _);
fixed (byte* pData = data)
{
var result = Windows.Win32.PInvoke.WriteProcessMemory(
ThisProcessPseudoHandle,
memoryAddress.ToPointer(),
pData,
(nuint)length,
null);
if (!result)
throw new MemoryWriteException($"Unable to write memory at {Util.DescribeAddress(memoryAddress)} of length {length} (result={result})");
if (!result)
throw new MemoryWriteException($"Unable to write memory at {Util.DescribeAddress(memoryAddress)} of length {length} (result={result})");
var last = Marshal.GetLastWin32Error();
if (last > 0)
throw new MemoryWriteException($"Unable to write memory at {Util.DescribeAddress(memoryAddress)} of length {length} (error={last})");
var last = Marshal.GetLastWin32Error();
if (last > 0)
throw new MemoryWriteException($"Unable to write memory at {Util.DescribeAddress(memoryAddress)} of length {length} (error={last})");
}
}
}

File diff suppressed because it is too large Load diff

View file

@ -12,6 +12,11 @@ HWND_TOPMOST
HWND_NOTOPMOST
SET_WINDOW_POS_FLAGS
SetEvent
SymInitialize
SymCleanup
OpenClipboard
SetClipboardData
CloseClipboard
@ -22,14 +27,34 @@ GlobalLock
GlobalUnlock
GLOBAL_ALLOC_FLAGS
MEM_ALLOCATION_TYPE
VirtualAlloc
VirtualProtect
VirtualFree
VirtualQuery
SetUnhandledExceptionFilter
ReadProcessMemory
WriteProcessMemory
FlashWindowEx
GetProcAddress
GetModuleHandle
GetForegroundWindow
GetCurrentProcess
GetWindowThreadProcessId
MessageBoxW
SystemParametersInfo
SystemParametersInfo
DwmSetWindowAttribute
setsockopt
RegisterDragDrop
RevokeDragDrop
DragQueryFileW

View file

@ -27,9 +27,9 @@ using Windows.Win32.System.Memory;
using Windows.Win32.System.Ole;
using Windows.Win32.UI.WindowsAndMessaging;
using static TerraFX.Interop.Windows.Windows;
using FLASHWINFO = Windows.Win32.UI.WindowsAndMessaging.FLASHWINFO;
using HWND = Windows.Win32.Foundation.HWND;
using MEMORY_BASIC_INFORMATION = Windows.Win32.System.Memory.MEMORY_BASIC_INFORMATION;
using Win32_PInvoke = Windows.Win32.PInvoke;
namespace Dalamud.Utility;
@ -202,14 +202,14 @@ public static class Util
}
MEMORY_BASIC_INFORMATION mbi;
if (VirtualQuery((void*)p, &mbi, (nuint)sizeof(MEMORY_BASIC_INFORMATION)) == 0)
if (Win32_PInvoke.VirtualQuery((void*)p, &mbi, (nuint)sizeof(MEMORY_BASIC_INFORMATION)) == 0)
return $"0x{p:X}(???)";
var sb = new StringBuilder();
sb.Append($"0x{p:X}(");
for (int i = 0, c = 0; i < PageProtectionFlagNames.Length; i++)
{
if ((mbi.Protect & (1 << i)) == 0)
if (((uint)mbi.Protect & (1 << i)) == 0)
continue;
if (c++ != 0)
sb.Append(" | ");
@ -577,25 +577,44 @@ public static class Util
}
}
/// <summary>
/// Returns true if the current application has focus, false otherwise.
/// </summary>
/// <returns>
/// If the current application is focused.
/// </returns>
public static unsafe bool ApplicationIsActivated()
{
var activatedHandle = Win32_PInvoke.GetForegroundWindow();
if (activatedHandle == IntPtr.Zero)
return false; // No window is currently activated
uint pid;
_ = Win32_PInvoke.GetWindowThreadProcessId(activatedHandle, &pid);
if (Marshal.GetLastWin32Error() != 0)
return false;
return pid == Environment.ProcessId;
}
/// <summary>
/// Request that Windows flash the game window to grab the user's attention.
/// </summary>
/// <param name="flashIfOpen">Attempt to flash even if the game is currently focused.</param>
public static void FlashWindow(bool flashIfOpen = false)
public static unsafe void FlashWindow(bool flashIfOpen = false)
{
if (NativeFunctions.ApplicationIsActivated() && !flashIfOpen)
if (ApplicationIsActivated() && !flashIfOpen)
return;
var flashInfo = new NativeFunctions.FlashWindowInfo
var flashInfo = new FLASHWINFO
{
Size = (uint)Marshal.SizeOf<NativeFunctions.FlashWindowInfo>(),
Count = uint.MaxValue,
Timeout = 0,
Flags = NativeFunctions.FlashWindow.All | NativeFunctions.FlashWindow.TimerNoFG,
Hwnd = Process.GetCurrentProcess().MainWindowHandle,
cbSize = (uint)sizeof(FLASHWINFO),
uCount = uint.MaxValue,
dwTimeout = 0,
dwFlags = FLASHWINFO_FLAGS.FLASHW_ALL | FLASHWINFO_FLAGS.FLASHW_TIMERNOFG,
hwnd = new HWND(Process.GetCurrentProcess().MainWindowHandle.ToPointer()),
};
NativeFunctions.FlashWindowEx(ref flashInfo);
Win32_PInvoke.FlashWindowEx(flashInfo);
}
/// <summary>