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 Newtonsoft.Json;
using Serilog; using Serilog;
using Serilog.Events; using Serilog.Events;
using Windows.Win32.UI.WindowsAndMessaging;
namespace Dalamud.Configuration.Internal; 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 // https://source.chromium.org/chromium/chromium/src/+/main:ui/gfx/animation/animation_win.cc;l=29?q=ReducedMotion&ss=chromium
var winAnimEnabled = 0; var winAnimEnabled = 0;
var success = NativeFunctions.SystemParametersInfo( var success = false;
(uint)NativeFunctions.AccessibilityParameter.SPI_GETCLIENTAREAANIMATION, unsafe
0, {
ref winAnimEnabled, success = Windows.Win32.PInvoke.SystemParametersInfo(
0); SYSTEM_PARAMETERS_INFO_ACTION.SPI_GETCLIENTAREAANIMATION,
0,
&winAnimEnabled,
0);
}
if (!success) if (!success)
{ {

View file

@ -15,6 +15,7 @@ using Dalamud.Utility;
using Dalamud.Utility.Timing; using Dalamud.Utility.Timing;
using PInvoke; using PInvoke;
using Serilog; using Serilog;
using Windows.Win32.Foundation;
#if DEBUG #if DEBUG
[assembly: InternalsVisibleTo("Dalamud.CorePlugin")] [assembly: InternalsVisibleTo("Dalamud.CorePlugin")]
@ -29,7 +30,7 @@ namespace Dalamud;
/// The main Dalamud class containing all subsystems. /// The main Dalamud class containing all subsystems.
/// </summary> /// </summary>
[ServiceManager.ProvidedService] [ServiceManager.ProvidedService]
internal sealed class Dalamud : IServiceType internal sealed unsafe class Dalamud : IServiceType
{ {
#region Internals #region Internals
@ -79,7 +80,7 @@ internal sealed class Dalamud : IServiceType
{ {
Log.Verbose("=============== GAME THREAD KICKOFF ==============="); Log.Verbose("=============== GAME THREAD KICKOFF ===============");
Timings.Event("Game thread kickoff"); Timings.Event("Game thread kickoff");
NativeFunctions.SetEvent(mainThreadContinueEvent); Windows.Win32.PInvoke.SetEvent(new HANDLE(mainThreadContinueEvent));
} }
void HandleServiceInitFailure(Task t) void HandleServiceInitFailure(Task t)
@ -116,9 +117,9 @@ internal sealed class Dalamud : IServiceType
HandleServiceInitFailure(t); HandleServiceInitFailure(t);
}); });
this.DefaultExceptionFilter = NativeFunctions.SetUnhandledExceptionFilter(nint.Zero); this.DefaultExceptionFilter = SetExceptionHandler(nint.Zero);
NativeFunctions.SetUnhandledExceptionFilter(this.DefaultExceptionFilter); SetExceptionHandler(this.DefaultExceptionFilter);
Log.Debug($"SE default exception filter at {this.DefaultExceptionFilter.ToInt64():X}"); 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"; var debugSig = "40 55 53 57 48 8D AC 24 70 AD FF FF";
this.DebugExceptionFilter = Service<TargetSigScanner>.Get().ScanText(debugSig); this.DebugExceptionFilter = Service<TargetSigScanner>.Get().ScanText(debugSig);
@ -189,27 +190,29 @@ internal sealed class Dalamud : IServiceType
/// Replace the current exception handler with the default one. /// Replace the current exception handler with the default one.
/// </summary> /// </summary>
internal void UseDefaultExceptionHandler() => internal void UseDefaultExceptionHandler() =>
this.SetExceptionHandler(this.DefaultExceptionFilter); SetExceptionHandler(this.DefaultExceptionFilter);
/// <summary> /// <summary>
/// Replace the current exception handler with a debug one. /// Replace the current exception handler with a debug one.
/// </summary> /// </summary>
internal void UseDebugExceptionHandler() => internal void UseDebugExceptionHandler() =>
this.SetExceptionHandler(this.DebugExceptionFilter); SetExceptionHandler(this.DebugExceptionFilter);
/// <summary> /// <summary>
/// Disable the current exception handler. /// Disable the current exception handler.
/// </summary> /// </summary>
internal void UseNoExceptionHandler() => internal void UseNoExceptionHandler() =>
this.SetExceptionHandler(nint.Zero); SetExceptionHandler(nint.Zero);
/// <summary> /// <summary>
/// Helper function to set the exception handler. /// Helper function to set the exception handler.
/// </summary> /// </summary>
private void SetExceptionHandler(nint newFilter) private static nint SetExceptionHandler(nint newFilter)
{ {
var oldFilter = NativeFunctions.SetUnhandledExceptionFilter(newFilter); var oldFilter =
Log.Debug("Set ExceptionFilter to {0}, old: {1}", newFilter, 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) private void SetupClientStructsResolver(DirectoryInfo cacheDir)

View file

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

View file

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

View file

@ -55,4 +55,37 @@ internal sealed class WinSockHandlers : IInternalDisposableService
return socket; 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

@ -200,11 +200,11 @@ public abstract class Hook<T> : IDalamudHook where T : Delegate
if (EnvironmentConfiguration.DalamudForceMinHook) if (EnvironmentConfiguration.DalamudForceMinHook)
useMinHook = true; useMinHook = true;
var moduleHandle = NativeFunctions.GetModuleHandleW(moduleName); using var moduleHandle = Windows.Win32.PInvoke.GetModuleHandle(moduleName);
if (moduleHandle == IntPtr.Zero) if (moduleHandle.IsInvalid)
throw new Exception($"Could not get a handle to module {moduleName}"); 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) 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}");

View file

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

View file

@ -41,6 +41,8 @@ using TerraFX.Interop.Windows;
using static TerraFX.Interop.Windows.Windows; using static TerraFX.Interop.Windows.Windows;
using DWMWINDOWATTRIBUTE = Windows.Win32.Graphics.Dwm.DWMWINDOWATTRIBUTE;
// general dev notes, here because it's easiest // general dev notes, here because it's easiest
/* /*
@ -487,12 +489,13 @@ internal partial class InterfaceManager : IInternalDisposableService
{ {
if (this.GameWindowHandle == 0) if (this.GameWindowHandle == 0)
throw new InvalidOperationException("Game window is not yet ready."); throw new InvalidOperationException("Game window is not yet ready.");
var value = enabled ? 1u : 0u; var value = enabled ? 1u : 0u;
DwmSetWindowAttribute( global::Windows.Win32.PInvoke.DwmSetWindowAttribute(
this.GameWindowHandle, new(this.GameWindowHandle.Value),
(uint)DWMWINDOWATTRIBUTE.DWMWA_USE_IMMERSIVE_DARK_MODE, DWMWINDOWATTRIBUTE.DWMWA_USE_IMMERSIVE_DARK_MODE,
&value, &value,
sizeof(int)).ThrowOnError(); sizeof(uint)).ThrowOnFailure();
} }
private static InterfaceManager WhenFontsReady() 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 /> /// 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>It WILL crash if you try to use a memory pointer allocated in some other way.</b><br />
/// <b> /// <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. /// been called, unless <paramref name="freeOnException"/> is set and the function has thrown an error.
/// </b> /// </b>
/// </summary> /// </summary>
/// <param name="dataPointer">Memory address for the data allocated using <see cref="ImGuiHelpers.AllocateMemory"/>.</param> /// <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="fontConfig">The font config.</param>
/// <param name="freeOnException">Free <paramref name="dataPointer"/> if an exception happens.</param> /// <param name="freeOnException">Free <paramref name="dataPointer"/> if an exception happens.</param>
/// <param name="debugTag">A debug tag.</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. /// used as the font size. Specify -1 to use the default font size.
/// </param> /// </param>
/// <param name="glyphRanges">The glyph ranges. Use <see cref="FontAtlasBuildToolkitUtilities"/>.ToGlyphRange to build.</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); ImFontPtr AddDalamudDefaultFont(float sizePx, ushort[]? glyphRanges = null);
/// <summary> /// <summary>

View file

@ -56,9 +56,9 @@ public interface IFontHandle : IDisposable
/// You may not access the font once you dispose this object. /// You may not access the font once you dispose this object.
/// </summary> /// </summary>
/// <returns>A disposable object that will pop the font on dispose.</returns> /// <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> /// <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. /// Use <see cref="IDisposable.Dispose"/> or <see cref="Pop"/> to undo this operation.
/// Do not use <see cref="ImGui.PopFont"/>.</para> /// Do not use <see cref="ImGui.PopFont"/>.</para>
/// </remarks> /// </remarks>

View file

@ -9,10 +9,9 @@ using Dalamud.Utility;
using FFXIVClientStructs.FFXIV.Client.System.Memory; using FFXIVClientStructs.FFXIV.Client.System.Memory;
using FFXIVClientStructs.FFXIV.Client.System.String; using FFXIVClientStructs.FFXIV.Client.System.String;
using Microsoft.Extensions.ObjectPool; using Microsoft.Extensions.ObjectPool;
using Windows.Win32.Foundation;
using Windows.Win32.System.Memory; using Windows.Win32.System.Memory;
using static Dalamud.NativeFunctions;
using LPayloadType = Lumina.Text.Payloads.PayloadType; using LPayloadType = Lumina.Text.Payloads.PayloadType;
using LSeString = Lumina.Text.SeString; using LSeString = Lumina.Text.SeString;
@ -28,6 +27,8 @@ public static unsafe class MemoryHelper
private static readonly ObjectPool<StringBuilder> StringBuilderPool = private static readonly ObjectPool<StringBuilder> StringBuilderPool =
ObjectPool.Create(new StringBuilderPooledObjectPolicy()); ObjectPool.Create(new StringBuilderPooledObjectPolicy());
private static readonly HANDLE ThisProcessPseudoHandle = new(unchecked((nint)0xFFFFFFFF));
#region Cast #region Cast
/// <summary>Casts the given memory address as the reference to the live object.</summary> /// <summary>Casts the given memory address as the reference to the live object.</summary>
@ -863,14 +864,22 @@ public static unsafe class MemoryHelper
unchecked unchecked
{ {
var length = value.Length; 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) if (!result)
throw new MemoryReadException($"Unable to read memory at {Util.DescribeAddress(memoryAddress)} of length {length} (result={result})"); throw new MemoryReadException($"Unable to read memory at {Util.DescribeAddress(memoryAddress)} of length {length} (result={result})");
var last = Marshal.GetLastWin32Error(); var last = Marshal.GetLastWin32Error();
if (last > 0) if (last > 0)
throw new MemoryReadException($"Unable to read memory at {Util.DescribeAddress(memoryAddress)} of length {length} (error={last})"); 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 unchecked
{ {
var length = data.Length; 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) if (!result)
throw new MemoryWriteException($"Unable to write memory at {Util.DescribeAddress(memoryAddress)} of length {length} (result={result})"); throw new MemoryWriteException($"Unable to write memory at {Util.DescribeAddress(memoryAddress)} of length {length} (result={result})");
var last = Marshal.GetLastWin32Error(); var last = Marshal.GetLastWin32Error();
if (last > 0) if (last > 0)
throw new MemoryWriteException($"Unable to write memory at {Util.DescribeAddress(memoryAddress)} of length {length} (error={last})"); 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 HWND_NOTOPMOST
SET_WINDOW_POS_FLAGS SET_WINDOW_POS_FLAGS
SetEvent
SymInitialize
SymCleanup
OpenClipboard OpenClipboard
SetClipboardData SetClipboardData
CloseClipboard CloseClipboard
@ -22,14 +27,34 @@ GlobalLock
GlobalUnlock GlobalUnlock
GLOBAL_ALLOC_FLAGS GLOBAL_ALLOC_FLAGS
MEM_ALLOCATION_TYPE
VirtualAlloc VirtualAlloc
VirtualProtect VirtualProtect
VirtualFree VirtualFree
VirtualQuery
SetUnhandledExceptionFilter
ReadProcessMemory ReadProcessMemory
WriteProcessMemory WriteProcessMemory
FlashWindowEx
GetProcAddress
GetModuleHandle
GetForegroundWindow
GetCurrentProcess GetCurrentProcess
GetWindowThreadProcessId
MessageBoxW 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.System.Ole;
using Windows.Win32.UI.WindowsAndMessaging; 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 HWND = Windows.Win32.Foundation.HWND;
using MEMORY_BASIC_INFORMATION = Windows.Win32.System.Memory.MEMORY_BASIC_INFORMATION;
using Win32_PInvoke = Windows.Win32.PInvoke; using Win32_PInvoke = Windows.Win32.PInvoke;
namespace Dalamud.Utility; namespace Dalamud.Utility;
@ -202,14 +202,14 @@ public static class Util
} }
MEMORY_BASIC_INFORMATION mbi; 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}(???)"; return $"0x{p:X}(???)";
var sb = new StringBuilder(); var sb = new StringBuilder();
sb.Append($"0x{p:X}("); sb.Append($"0x{p:X}(");
for (int i = 0, c = 0; i < PageProtectionFlagNames.Length; i++) for (int i = 0, c = 0; i < PageProtectionFlagNames.Length; i++)
{ {
if ((mbi.Protect & (1 << i)) == 0) if (((uint)mbi.Protect & (1 << i)) == 0)
continue; continue;
if (c++ != 0) if (c++ != 0)
sb.Append(" | "); 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> /// <summary>
/// Request that Windows flash the game window to grab the user's attention. /// Request that Windows flash the game window to grab the user's attention.
/// </summary> /// </summary>
/// <param name="flashIfOpen">Attempt to flash even if the game is currently focused.</param> /// <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; return;
var flashInfo = new NativeFunctions.FlashWindowInfo var flashInfo = new FLASHWINFO
{ {
Size = (uint)Marshal.SizeOf<NativeFunctions.FlashWindowInfo>(), cbSize = (uint)sizeof(FLASHWINFO),
Count = uint.MaxValue, uCount = uint.MaxValue,
Timeout = 0, dwTimeout = 0,
Flags = NativeFunctions.FlashWindow.All | NativeFunctions.FlashWindow.TimerNoFG, dwFlags = FLASHWINFO_FLAGS.FLASHW_ALL | FLASHWINFO_FLAGS.FLASHW_TIMERNOFG,
Hwnd = Process.GetCurrentProcess().MainWindowHandle, hwnd = new HWND(Process.GetCurrentProcess().MainWindowHandle.ToPointer()),
}; };
Win32_PInvoke.FlashWindowEx(flashInfo);
NativeFunctions.FlashWindowEx(ref flashInfo);
} }
/// <summary> /// <summary>