diff --git a/Dalamud/Support/CurrentProcessModules.cs b/Dalamud/Support/CurrentProcessModules.cs
new file mode 100644
index 000000000..961c9828f
--- /dev/null
+++ b/Dalamud/Support/CurrentProcessModules.cs
@@ -0,0 +1,138 @@
+using System.Collections.Concurrent;
+using System.Diagnostics;
+using System.Runtime.InteropServices;
+using System.Threading;
+using System.Threading.Tasks;
+
+using Serilog;
+
+using TerraFX.Interop.Windows;
+
+namespace Dalamud.Support;
+
+/// Tracks the loaded process modules.
+[ServiceManager.EarlyLoadedService]
+internal sealed unsafe partial class CurrentProcessModules : IInternalDisposableService
+{
+ private static readonly ConcurrentQueue LogQueue = new();
+ private static readonly SemaphoreSlim LogSemaphore = new(0);
+
+ private static Process? process;
+ private static nint cookie;
+
+ private readonly CancellationTokenSource logTaskStop = new();
+ private readonly Task logTask;
+
+ [ServiceManager.ServiceConstructor]
+ private CurrentProcessModules()
+ {
+ var res = LdrRegisterDllNotification(0, &DllNotificationCallback, 0, out cookie);
+ if (res != STATUS.STATUS_SUCCESS)
+ {
+ Log.Error("{what}: LdrRegisterDllNotification failure: 0x{err}", nameof(CurrentProcessModules), res);
+ cookie = 0;
+ this.logTask = Task.CompletedTask;
+ return;
+ }
+
+ this.logTask = Task.Factory.StartNew(
+ () =>
+ {
+ while (!this.logTaskStop.IsCancellationRequested)
+ {
+ LogSemaphore.Wait();
+ while (LogQueue.TryDequeue(out var log))
+ Log.Verbose(log);
+ }
+ },
+ this.logTaskStop.Token,
+ TaskCreationOptions.LongRunning,
+ TaskScheduler.Default);
+ }
+
+ private enum LdrDllNotificationReason : uint
+ {
+ Loaded = 1,
+ Unloaded = 2,
+ }
+
+ /// Gets all the loaded modules, up to date.
+ public static ProcessModuleCollection ModuleCollection =>
+ (cookie == 0 ? Process.GetCurrentProcess() : process ??= Process.GetCurrentProcess()).Modules;
+
+ ///
+ void IInternalDisposableService.DisposeService()
+ {
+ if (Interlocked.Exchange(ref cookie, 0) is var copy and not 0)
+ LdrUnregisterDllNotification(copy);
+ if (!this.logTask.IsCompleted)
+ {
+ this.logTaskStop.Cancel();
+ LogSemaphore.Release();
+ this.logTask.Wait();
+ }
+ }
+
+ [UnmanagedCallersOnly]
+ private static void DllNotificationCallback(
+ LdrDllNotificationReason reason,
+ LdrDllNotificationData* data,
+ nint context)
+ {
+ process = null;
+ var name = new ReadOnlySpan(data->FullDllName->Buffer, data->FullDllName->Length / 2);
+ LogQueue.Enqueue(
+ $"[{nameof(CurrentProcessModules)}]: {reason}: {name} @ 0x{data->DllBase:X} ({data->SizeOfImage}:X bytes)");
+ LogSemaphore.Release();
+ }
+
+ ///
+ /// Registers for notification when a DLL is first loaded.
+ /// This notification occurs before dynamic linking takes place.
+ /// Docs.
+ ///
+ /// This parameter must be zero.
+ /// A pointer to a callback function to call when the DLL is loaded.
+ /// A pointer to context data for the callback function.
+ /// A pointer to a variable to receive an identifier for the callback function.
+ /// This identifier is used to unregister the notification callback function.
+ /// Returns an NTSTATUS or error code.
+ [LibraryImport("ntdll.dll", SetLastError = true)]
+ private static partial int LdrRegisterDllNotification(
+ uint flags,
+ delegate* unmanaged
+ notificationFunction,
+ nint context,
+ out nint cookie);
+
+ ///
+ /// Cancels DLL load notification previously registered by calling the LdrRegisterDllNotification function.
+ ///
+ /// Docs.
+ ///
+ /// A pointer to the callback identifier received from the LdrRegisterDllNotification call
+ /// that registered for notification.
+ ///
+ /// Returns an NTSTATUS or error code.
+ [LibraryImport("ntdll.dll", SetLastError = true)]
+ private static partial int LdrUnregisterDllNotification(nint cookie);
+
+ [StructLayout(LayoutKind.Sequential)]
+ private struct LdrDllNotificationData
+ {
+ /// Reserved.
+ public uint Flags;
+
+ /// The full path name of the DLL module.
+ public UNICODE_STRING* FullDllName;
+
+ /// The base file name of the DLL module.
+ public UNICODE_STRING* BaseDllName;
+
+ /// A pointer to the base address for the DLL in memory.
+ public nint DllBase;
+
+ /// The size of the DLL image, in bytes.
+ public uint SizeOfImage;
+ }
+}
diff --git a/Dalamud/Utility/Util.cs b/Dalamud/Utility/Util.cs
index 927f7b310..e6bf8c1f3 100644
--- a/Dalamud/Utility/Util.cs
+++ b/Dalamud/Utility/Util.cs
@@ -27,6 +27,8 @@ using Windows.Win32.Storage.FileSystem;
using Windows.Win32.System.Memory;
using Windows.Win32.System.Ole;
+using Dalamud.Support;
+
using static TerraFX.Interop.Windows.Windows;
using Win32_PInvoke = Windows.Win32.PInvoke;
@@ -191,7 +193,7 @@ public static class Util
public static unsafe string DescribeAddress(nint p)
{
Span namebuf = stackalloc char[9];
- var modules = Process.GetCurrentProcess().Modules;
+ var modules = CurrentProcessModules.ModuleCollection;
for (var i = 0; i < modules.Count; i++)
{
if (p < modules[i].BaseAddress) continue;