diff --git a/Dalamud/Game/ClientState/Aetherytes/AetheryteList.cs b/Dalamud/Game/ClientState/Aetherytes/AetheryteList.cs index 3eadefd0f..a3d44d423 100644 --- a/Dalamud/Game/ClientState/Aetherytes/AetheryteList.cs +++ b/Dalamud/Game/ClientState/Aetherytes/AetheryteList.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using Dalamud.IoC; using Dalamud.IoC.Internal; using Dalamud.Plugin.Services; +using Dalamud.Utility; using FFXIVClientStructs.FFXIV.Client.Game.UI; using Serilog; @@ -28,7 +29,7 @@ internal sealed unsafe partial class AetheryteList : IServiceType, IAetheryteLis [ServiceManager.ServiceConstructor] private AetheryteList() { - Log.Verbose($"Teleport address 0x{((nint)this.telepoInstance).ToInt64():X}"); + Log.Verbose($"Teleport address {Util.DescribeAddress(this.telepoInstance)}"); } /// diff --git a/Dalamud/Game/ClientState/Buddy/BuddyList.cs b/Dalamud/Game/ClientState/Buddy/BuddyList.cs index bafe9ded6..db9b8e562 100644 --- a/Dalamud/Game/ClientState/Buddy/BuddyList.cs +++ b/Dalamud/Game/ClientState/Buddy/BuddyList.cs @@ -6,6 +6,7 @@ using System.Runtime.InteropServices; using Dalamud.IoC; using Dalamud.IoC.Internal; using Dalamud.Plugin.Services; +using Dalamud.Utility; using Serilog; @@ -34,7 +35,7 @@ internal sealed partial class BuddyList : IServiceType, IBuddyList { this.address = this.clientState.AddressResolver; - Log.Verbose($"Buddy list address 0x{this.address.BuddyList.ToInt64():X}"); + Log.Verbose($"Buddy list address {Util.DescribeAddress(this.address.BuddyList)}"); } /// diff --git a/Dalamud/Game/ClientState/ClientState.cs b/Dalamud/Game/ClientState/ClientState.cs index d6fd8f37b..b31bcf780 100644 --- a/Dalamud/Game/ClientState/ClientState.cs +++ b/Dalamud/Game/ClientState/ClientState.cs @@ -54,7 +54,7 @@ internal sealed class ClientState : IInternalDisposableService, IClientState this.ClientLanguage = (ClientLanguage)dalamud.StartInfo.Language; - Log.Verbose($"SetupTerritoryType address 0x{this.address.SetupTerritoryType.ToInt64():X}"); + Log.Verbose($"SetupTerritoryType address {Util.DescribeAddress(this.address.SetupTerritoryType)}"); this.setupTerritoryTypeHook = Hook.FromAddress(this.address.SetupTerritoryType, this.SetupTerritoryTypeDetour); diff --git a/Dalamud/Game/ClientState/Fates/FateTable.cs b/Dalamud/Game/ClientState/Fates/FateTable.cs index e28a8d113..abd5bac41 100644 --- a/Dalamud/Game/ClientState/Fates/FateTable.cs +++ b/Dalamud/Game/ClientState/Fates/FateTable.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using Dalamud.IoC; using Dalamud.IoC.Internal; using Dalamud.Plugin.Services; +using Dalamud.Utility; using Serilog; @@ -26,7 +27,7 @@ internal sealed partial class FateTable : IServiceType, IFateTable { this.address = clientState.AddressResolver; - Log.Verbose($"Fate table address 0x{this.address.FateTablePtr.ToInt64():X}"); + Log.Verbose($"Fate table address {Util.DescribeAddress(this.address.FateTablePtr)}"); } /// diff --git a/Dalamud/Game/ClientState/GamePad/GamepadState.cs b/Dalamud/Game/ClientState/GamePad/GamepadState.cs index 79698d2cc..05d691823 100644 --- a/Dalamud/Game/ClientState/GamePad/GamepadState.cs +++ b/Dalamud/Game/ClientState/GamePad/GamepadState.cs @@ -4,6 +4,7 @@ using Dalamud.Hooking; using Dalamud.IoC; using Dalamud.IoC.Internal; using Dalamud.Plugin.Services; +using Dalamud.Utility; using ImGuiNET; using Serilog; @@ -35,7 +36,7 @@ internal unsafe class GamepadState : IInternalDisposableService, IGamepadState private GamepadState(ClientState clientState) { var resolver = clientState.AddressResolver; - Log.Verbose($"GamepadPoll address 0x{resolver.GamepadPoll.ToInt64():X}"); + Log.Verbose($"GamepadPoll address {Util.DescribeAddress(resolver.GamepadPoll)}"); this.gamepadPoll = Hook.FromAddress(resolver.GamepadPoll, this.GamepadPollDetour); this.gamepadPoll?.Enable(); } diff --git a/Dalamud/Game/ClientState/JobGauge/JobGauges.cs b/Dalamud/Game/ClientState/JobGauge/JobGauges.cs index 4afdd8130..e0ae986c5 100644 --- a/Dalamud/Game/ClientState/JobGauge/JobGauges.cs +++ b/Dalamud/Game/ClientState/JobGauge/JobGauges.cs @@ -5,6 +5,7 @@ using Dalamud.Game.ClientState.JobGauge.Types; using Dalamud.IoC; using Dalamud.IoC.Internal; using Dalamud.Plugin.Services; +using Dalamud.Utility; using Serilog; @@ -27,7 +28,7 @@ internal class JobGauges : IServiceType, IJobGauges { this.Address = clientState.AddressResolver.JobGaugeData; - Log.Verbose($"JobGaugeData address 0x{this.Address.ToInt64():X}"); + Log.Verbose($"JobGaugeData address {Util.DescribeAddress(this.Address)}"); } /// diff --git a/Dalamud/Game/ClientState/Keys/KeyState.cs b/Dalamud/Game/ClientState/Keys/KeyState.cs index 7e4292598..35f0c2161 100644 --- a/Dalamud/Game/ClientState/Keys/KeyState.cs +++ b/Dalamud/Game/ClientState/Keys/KeyState.cs @@ -5,6 +5,7 @@ using System.Runtime.InteropServices; using Dalamud.IoC; using Dalamud.IoC.Internal; using Dalamud.Plugin.Services; +using Dalamud.Utility; using Serilog; @@ -45,7 +46,7 @@ internal class KeyState : IServiceType, IKeyState this.bufferBase = moduleBaseAddress + Marshal.ReadInt32(addressResolver.KeyboardState); this.indexBase = moduleBaseAddress + Marshal.ReadInt32(addressResolver.KeyboardStateIndexArray); - Log.Verbose($"Keyboard state buffer address 0x{this.bufferBase.ToInt64():X}"); + Log.Verbose($"Keyboard state buffer address {Util.DescribeAddress(this.bufferBase)}"); } /// diff --git a/Dalamud/Game/ClientState/Objects/ObjectTable.cs b/Dalamud/Game/ClientState/Objects/ObjectTable.cs index d88383c22..50f4e81ce 100644 --- a/Dalamud/Game/ClientState/Objects/ObjectTable.cs +++ b/Dalamud/Game/ClientState/Objects/ObjectTable.cs @@ -53,7 +53,7 @@ internal sealed partial class ObjectTable : IServiceType, IObjectTable for (var i = 0; i < this.frameworkThreadEnumerators.Length; i++) this.frameworkThreadEnumerators[i] = new(this, i); - Log.Verbose($"Object table address 0x{this.clientState.AddressResolver.ObjectTable.ToInt64():X}"); + Log.Verbose($"Object table address {Util.DescribeAddress(this.clientState.AddressResolver.ObjectTable)}"); } /// diff --git a/Dalamud/Game/ClientState/Party/PartyList.cs b/Dalamud/Game/ClientState/Party/PartyList.cs index d6e235d30..4fbd8fdfb 100644 --- a/Dalamud/Game/ClientState/Party/PartyList.cs +++ b/Dalamud/Game/ClientState/Party/PartyList.cs @@ -6,6 +6,7 @@ using System.Runtime.InteropServices; using Dalamud.IoC; using Dalamud.IoC.Internal; using Dalamud.Plugin.Services; +using Dalamud.Utility; using Serilog; @@ -34,7 +35,7 @@ internal sealed unsafe partial class PartyList : IServiceType, IPartyList { this.address = this.clientState.AddressResolver; - Log.Verbose($"Group manager address 0x{this.address.GroupManager.ToInt64():X}"); + Log.Verbose($"Group manager address {Util.DescribeAddress(this.address.GroupManager)}"); } /// diff --git a/Dalamud/Game/Gui/GameGui.cs b/Dalamud/Game/Gui/GameGui.cs index e92473539..80463a119 100644 --- a/Dalamud/Game/Gui/GameGui.cs +++ b/Dalamud/Game/Gui/GameGui.cs @@ -55,11 +55,11 @@ internal sealed unsafe class GameGui : IInternalDisposableService, IGameGui this.address.Setup(sigScanner); Log.Verbose("===== G A M E G U I ====="); - Log.Verbose($"GameGuiManager address 0x{this.address.BaseAddress.ToInt64():X}"); - Log.Verbose($"SetGlobalBgm address 0x{this.address.SetGlobalBgm.ToInt64():X}"); - Log.Verbose($"HandleItemHover address 0x{this.address.HandleItemHover.ToInt64():X}"); - Log.Verbose($"HandleItemOut address 0x{this.address.HandleItemOut.ToInt64():X}"); - Log.Verbose($"HandleImm address 0x{this.address.HandleImm.ToInt64():X}"); + Log.Verbose($"GameGuiManager address {Util.DescribeAddress(this.address.BaseAddress)}"); + Log.Verbose($"SetGlobalBgm address {Util.DescribeAddress(this.address.SetGlobalBgm)}"); + Log.Verbose($"HandleItemHover address {Util.DescribeAddress(this.address.HandleItemHover)}"); + Log.Verbose($"HandleItemOut address {Util.DescribeAddress(this.address.HandleItemOut)}"); + Log.Verbose($"HandleImm address {Util.DescribeAddress(this.address.HandleImm)}"); this.setGlobalBgmHook = Hook.FromAddress(this.address.SetGlobalBgm, this.HandleSetGlobalBgmDetour); diff --git a/Dalamud/Game/Internal/AntiDebug.cs b/Dalamud/Game/Internal/AntiDebug.cs index dfc6bde90..48b8688a1 100644 --- a/Dalamud/Game/Internal/AntiDebug.cs +++ b/Dalamud/Game/Internal/AntiDebug.cs @@ -1,5 +1,7 @@ using System.Collections.Generic; +using Dalamud.Utility; + #if !DEBUG using Dalamud.Configuration.Internal; #endif @@ -30,7 +32,7 @@ internal sealed class AntiDebug : IInternalDisposableService this.debugCheckAddress = IntPtr.Zero; } - Log.Verbose($"Debug check address 0x{this.debugCheckAddress.ToInt64():X}"); + Log.Verbose($"Debug check address {Util.DescribeAddress(this.debugCheckAddress)}"); if (!this.IsEnabled) { @@ -65,7 +67,7 @@ internal sealed class AntiDebug : IInternalDisposableService this.original = new byte[this.nop.Length]; if (this.debugCheckAddress != IntPtr.Zero && !this.IsEnabled) { - Log.Information($"Overwriting debug check at 0x{this.debugCheckAddress.ToInt64():X}"); + Log.Information($"Overwriting debug check at {Util.DescribeAddress(this.debugCheckAddress)}"); SafeMemory.ReadBytes(this.debugCheckAddress, this.nop.Length, out this.original); SafeMemory.WriteBytes(this.debugCheckAddress, this.nop); } @@ -87,7 +89,7 @@ internal sealed class AntiDebug : IInternalDisposableService if (this.debugCheckAddress != IntPtr.Zero && this.original != null) { - Log.Information($"Reverting debug check at 0x{this.debugCheckAddress.ToInt64():X}"); + Log.Information($"Reverting debug check at {Util.DescribeAddress(this.debugCheckAddress)}"); SafeMemory.WriteBytes(this.debugCheckAddress, this.original); } else diff --git a/Dalamud/Game/Internal/DXGI/Definitions/ID3D11DeviceVtbl.cs b/Dalamud/Game/Internal/DXGI/Definitions/ID3D11DeviceVtbl.cs deleted file mode 100644 index 4bd369dd3..000000000 --- a/Dalamud/Game/Internal/DXGI/Definitions/ID3D11DeviceVtbl.cs +++ /dev/null @@ -1,226 +0,0 @@ -namespace Dalamud.Game.Internal.DXGI.Definitions; - -/// -/// Contains a full list of ID3D11Device functions to be used as an indexer into the DirectX Virtual Function Table entries. -/// -internal enum ID3D11DeviceVtbl -{ - // IUnknown - - /// - /// IUnknown::QueryInterface method (unknwn.h). - /// - QueryInterface = 0, - - /// - /// IUnknown::AddRef method (unknwn.h). - /// - AddRef = 1, - - /// - /// IUnknown::Release method (unknwn.h). - /// - Release = 2, - - // ID3D11Device - - /// - /// ID3D11Device::CreateBuffer method (d3d11.h). - /// - CreateBuffer = 3, - - /// - /// ID3D11Device::CreateTexture1D method (d3d11.h). - /// - CreateTexture1D = 4, - - /// - /// ID3D11Device::CreateTexture2D method (d3d11.h). - /// - CreateTexture2D = 5, - - /// - /// ID3D11Device::CreateTexture3D method (d3d11.h). - /// - CreateTexture3D = 6, - - /// - /// ID3D11Device::CreateShaderResourceView method (d3d11.h). - /// - CreateShaderResourceView = 7, - - /// - /// ID3D11Device::CreateUnorderedAccessView method (d3d11.h). - /// - CreateUnorderedAccessView = 8, - - /// - /// ID3D11Device::CreateRenderTargetView method (d3d11.h). - /// - CreateRenderTargetView = 9, - - /// - /// ID3D11Device::CreateDepthStencilView method (d3d11.h). - /// - CreateDepthStencilView = 10, - - /// - /// ID3D11Device::CreateInputLayout method (d3d11.h). - /// - CreateInputLayout = 11, - - /// - /// ID3D11Device::CreateVertexShader method (d3d11.h). - /// - CreateVertexShader = 12, - - /// - /// ID3D11Device::CreateGeometryShader method (d3d11.h). - /// - CreateGeometryShader = 13, - - /// - /// ID3D11Device::CreateGeometryShaderWithStreamOutput method (d3d11.h). - /// - CreateGeometryShaderWithStreamOutput = 14, - - /// - /// ID3D11Device::CreatePixelShader method (d3d11.h). - /// - CreatePixelShader = 15, - - /// - /// ID3D11Device::CreateHullShader method (d3d11.h). - /// - CreateHullShader = 16, - - /// - /// ID3D11Device::CreateDomainShader method (d3d11.h). - /// - CreateDomainShader = 17, - - /// - /// ID3D11Device::CreateComputeShader method (d3d11.h). - /// - CreateComputeShader = 18, - - /// - /// ID3D11Device::CreateClassLinkage method (d3d11.h). - /// - CreateClassLinkage = 19, - - /// - /// ID3D11Device::CreateBlendState method (d3d11.h). - /// - CreateBlendState = 20, - - /// - /// ID3D11Device::CreateDepthStencilState method (d3d11.h). - /// - CreateDepthStencilState = 21, - - /// - /// ID3D11Device::CreateRasterizerState method (d3d11.h). - /// - CreateRasterizerState = 22, - - /// - /// ID3D11Device::CreateSamplerState method (d3d11.h). - /// - CreateSamplerState = 23, - - /// - /// ID3D11Device::CreateQuery method (d3d11.h). - /// - CreateQuery = 24, - - /// - /// ID3D11Device::CreatePredicate method (d3d11.h). - /// - CreatePredicate = 25, - - /// - /// ID3D11Device::CreateCounter method (d3d11.h). - /// - CreateCounter = 26, - - /// - /// ID3D11Device::CreateDeferredContext method (d3d11.h). - /// - CreateDeferredContext = 27, - - /// - /// ID3D11Device::OpenSharedResource method (d3d11.h). - /// - OpenSharedResource = 28, - - /// - /// ID3D11Device::CheckFormatSupport method (d3d11.h). - /// - CheckFormatSupport = 29, - - /// - /// ID3D11Device::CheckMultisampleQualityLevels method (d3d11.h). - /// - CheckMultisampleQualityLevels = 30, - - /// - /// ID3D11Device::CheckCounterInfo method (d3d11.h). - /// - CheckCounterInfo = 31, - - /// - /// ID3D11Device::CheckCounter method (d3d11.h). - /// - CheckCounter = 32, - - /// - /// ID3D11Device::CheckFeatureSupport method (d3d11.h). - /// - CheckFeatureSupport = 33, - - /// - /// ID3D11Device::GetPrivateData method (d3d11.h). - /// - GetPrivateData = 34, - - /// - /// ID3D11Device::SetPrivateData method (d3d11.h). - /// - SetPrivateData = 35, - - /// - /// ID3D11Device::SetPrivateDataInterface method (d3d11.h). - /// - SetPrivateDataInterface = 36, - - /// - /// ID3D11Device::GetFeatureLevel method (d3d11.h). - /// - GetFeatureLevel = 37, - - /// - /// ID3D11Device::GetCreationFlags method (d3d11.h). - /// - GetCreationFlags = 38, - - /// - /// ID3D11Device::GetDeviceRemovedReason method (d3d11.h). - /// - GetDeviceRemovedReason = 39, - - /// - /// ID3D11Device::GetImmediateContext method (d3d11.h). - /// - GetImmediateContext = 40, - - /// - /// ID3D11Device::SetExceptionMode method (d3d11.h). - /// - SetExceptionMode = 41, - - /// - /// ID3D11Device::GetExceptionMode method (d3d11.h). - /// - GetExceptionMode = 42, -} diff --git a/Dalamud/Game/Internal/DXGI/Definitions/IDXGISwapChainVtbl.cs b/Dalamud/Game/Internal/DXGI/Definitions/IDXGISwapChainVtbl.cs deleted file mode 100644 index 4bcb6fb72..000000000 --- a/Dalamud/Game/Internal/DXGI/Definitions/IDXGISwapChainVtbl.cs +++ /dev/null @@ -1,106 +0,0 @@ -namespace Dalamud.Game.Internal.DXGI.Definitions; - -/// -/// Contains a full list of IDXGISwapChain functions to be used as an indexer into the SwapChain Virtual Function Table -/// entries. -/// -internal enum IDXGISwapChainVtbl -{ - // IUnknown - - /// - /// IUnknown::QueryInterface method (unknwn.h). - /// - QueryInterface = 0, - - /// - /// IUnknown::AddRef method (unknwn.h). - /// - AddRef = 1, - - /// - /// IUnknown::Release method (unknwn.h). - /// - Release = 2, - - // IDXGIObject - - /// - /// IDXGIObject::SetPrivateData method (dxgi.h). - /// - SetPrivateData = 3, - - /// - /// IDXGIObject::SetPrivateDataInterface method (dxgi.h). - /// - SetPrivateDataInterface = 4, - - /// - /// IDXGIObject::GetPrivateData method (dxgi.h). - /// - GetPrivateData = 5, - - /// - /// IDXGIObject::GetParent method (dxgi.h). - /// - GetParent = 6, - - // IDXGIDeviceSubObject - - /// - /// IDXGIDeviceSubObject::GetDevice method (dxgi.h). - /// - GetDevice = 7, - - // IDXGISwapChain - - /// - /// IDXGISwapChain::Present method (dxgi.h). - /// - Present = 8, - - /// - /// IUnknIDXGISwapChainown::GetBuffer method (dxgi.h). - /// - GetBuffer = 9, - - /// - /// IDXGISwapChain::SetFullscreenState method (dxgi.h). - /// - SetFullscreenState = 10, - - /// - /// IDXGISwapChain::GetFullscreenState method (dxgi.h). - /// - GetFullscreenState = 11, - - /// - /// IDXGISwapChain::GetDesc method (dxgi.h). - /// - GetDesc = 12, - - /// - /// IDXGISwapChain::ResizeBuffers method (dxgi.h). - /// - ResizeBuffers = 13, - - /// - /// IDXGISwapChain::ResizeTarget method (dxgi.h). - /// - ResizeTarget = 14, - - /// - /// IDXGISwapChain::GetContainingOutput method (dxgi.h). - /// - GetContainingOutput = 15, - - /// - /// IDXGISwapChain::GetFrameStatistics method (dxgi.h). - /// - GetFrameStatistics = 16, - - /// - /// IDXGISwapChain::GetLastPresentCount method (dxgi.h). - /// - GetLastPresentCount = 17, -} diff --git a/Dalamud/Game/Internal/DXGI/ISwapChainAddressResolver.cs b/Dalamud/Game/Internal/DXGI/ISwapChainAddressResolver.cs deleted file mode 100644 index daccb8d14..000000000 --- a/Dalamud/Game/Internal/DXGI/ISwapChainAddressResolver.cs +++ /dev/null @@ -1,17 +0,0 @@ -namespace Dalamud.Game.Internal.DXGI; - -/// -/// An interface binding for the address resolvers that attempt to find native D3D11 methods. -/// -public interface ISwapChainAddressResolver -{ - /// - /// Gets or sets the address of the native D3D11.Present method. - /// - IntPtr Present { get; set; } - - /// - /// Gets or sets the address of the native D3D11.ResizeBuffers method. - /// - IntPtr ResizeBuffers { get; set; } -} diff --git a/Dalamud/Game/Internal/DXGI/SwapChainVtableResolver.cs b/Dalamud/Game/Internal/DXGI/SwapChainVtableResolver.cs deleted file mode 100644 index ba880d0e5..000000000 --- a/Dalamud/Game/Internal/DXGI/SwapChainVtableResolver.cs +++ /dev/null @@ -1,169 +0,0 @@ -using System.Collections.Generic; -using System.Diagnostics; -using System.Runtime.InteropServices; - -using Dalamud.Game.Internal.DXGI.Definitions; -using FFXIVClientStructs.FFXIV.Client.Graphics.Kernel; -using Serilog; - -namespace Dalamud.Game.Internal.DXGI; - -/// -/// This class attempts to determine the D3D11 SwapChain vtable addresses via instantiating a new form and inspecting it. -/// -/// -/// If the normal signature based method of resolution fails, this is the backup. -/// -internal class SwapChainVtableResolver : BaseAddressResolver, ISwapChainAddressResolver -{ - /// - public IntPtr Present { get; set; } - - /// - public IntPtr ResizeBuffers { get; set; } - - /// - /// Gets a value indicating whether or not ReShade is loaded/used. - /// - public bool IsReshade { get; private set; } - - /// - protected override unsafe void Setup64Bit(ISigScanner sig) - { - Device* kernelDev; - SwapChain* swapChain; - void* dxgiSwapChain; - - while (true) - { - kernelDev = Device.Instance(); - if (kernelDev == null) - continue; - - swapChain = kernelDev->SwapChain; - if (swapChain == null) - continue; - - dxgiSwapChain = swapChain->DXGISwapChain; - if (dxgiSwapChain == null) - continue; - - break; - } - - var scVtbl = GetVTblAddresses(new IntPtr(dxgiSwapChain), Enum.GetValues(typeof(IDXGISwapChainVtbl)).Length); - - this.Present = scVtbl[(int)IDXGISwapChainVtbl.Present]; - - var modules = Process.GetCurrentProcess().Modules; - foreach (ProcessModule processModule in modules) - { - if (processModule.FileName == null || !processModule.FileName.EndsWith("game\\dxgi.dll")) - continue; - - try - { - var fileInfo = FileVersionInfo.GetVersionInfo(processModule.FileName); - - if (fileInfo.FileDescription == null) - break; - - if (!fileInfo.FileDescription.Contains("GShade") && !fileInfo.FileDescription.Contains("ReShade")) - break; - - // warning: these comments may no longer be accurate. - // reshade master@4232872 RVA - // var p = processModule.BaseAddress + 0x82C7E0; // DXGISwapChain::Present - // var p = processModule.BaseAddress + 0x82FAC0; // DXGISwapChain::runtime_present - // DXGISwapChain::handle_device_loss =>df DXGISwapChain::Present => DXGISwapChain::runtime_present - // 5.2+ - F6 C2 01 0F 85 - // 6.0+ - F6 C2 01 0F 85 88 - - var scanner = new SigScanner(processModule); - var reShadeDxgiPresent = IntPtr.Zero; - - if (fileInfo.FileVersion?.StartsWith("6.") == true) - { - // No Addon - if (scanner.TryScanText("F6 C2 01 0F 85 A8", out reShadeDxgiPresent)) - { - Log.Information("Hooking present for ReShade 6 No-Addon"); - } - - // Addon - else if (scanner.TryScanText("F6 C2 01 0F 85 88", out reShadeDxgiPresent)) - { - Log.Information("Hooking present for ReShade 6 Addon"); - } - - // Fallback - else - { - Log.Error("Failed to get ReShade 6 DXGISwapChain::on_present offset!"); - } - } - - // Looks like this sig only works for GShade 4 - if (reShadeDxgiPresent == IntPtr.Zero && fileInfo.FileDescription?.Contains("GShade 4.") == true) - { - if (scanner.TryScanText("E8 ?? ?? ?? ?? 45 0F B6 5E ??", out reShadeDxgiPresent)) - { - Log.Information("Hooking present for GShade 4"); - } - else - { - Log.Error("Failed to find GShade 4 DXGISwapChain::on_present offset!"); - } - } - - if (reShadeDxgiPresent == IntPtr.Zero) - { - if (scanner.TryScanText("F6 C2 01 0F 85", out reShadeDxgiPresent)) - { - Log.Information("Hooking present for ReShade with fallback 5.X sig"); - } - else - { - Log.Error("Failed to find ReShade DXGISwapChain::on_present offset with fallback sig!"); - } - } - - Log.Information("ReShade DLL: {FileName} ({Info} - {Version}) with DXGISwapChain::on_present at {Address}", - processModule.FileName, - fileInfo.FileDescription ?? "Unknown", - fileInfo.FileVersion ?? "Unknown", - reShadeDxgiPresent.ToString("X")); - - if (reShadeDxgiPresent != IntPtr.Zero) - { - this.Present = reShadeDxgiPresent; - this.IsReshade = true; - } - - break; - } - catch (Exception e) - { - Log.Error(e, "Failed to get ReShade version info"); - break; - } - } - - this.ResizeBuffers = scVtbl[(int)IDXGISwapChainVtbl.ResizeBuffers]; - } - - private static List GetVTblAddresses(IntPtr pointer, int numberOfMethods) - { - return GetVTblAddresses(pointer, 0, numberOfMethods); - } - - private static List GetVTblAddresses(IntPtr pointer, int startIndex, int numberOfMethods) - { - var vtblAddresses = new List(); - var vTable = Marshal.ReadIntPtr(pointer); - for (var i = startIndex; i < startIndex + numberOfMethods; i++) - vtblAddresses.Add(Marshal.ReadIntPtr(vTable, i * IntPtr.Size)); // using IntPtr.Size allows us to support both 32 and 64-bit processes - - return vtblAddresses; - } -} diff --git a/Dalamud/Game/Network/GameNetwork.cs b/Dalamud/Game/Network/GameNetwork.cs index ad08f3684..b51232ece 100644 --- a/Dalamud/Game/Network/GameNetwork.cs +++ b/Dalamud/Game/Network/GameNetwork.cs @@ -38,8 +38,8 @@ internal sealed class GameNetwork : IInternalDisposableService, IGameNetwork this.address.Setup(sigScanner); Log.Verbose("===== G A M E N E T W O R K ====="); - Log.Verbose($"ProcessZonePacketDown address 0x{this.address.ProcessZonePacketDown.ToInt64():X}"); - Log.Verbose($"ProcessZonePacketUp address 0x{this.address.ProcessZonePacketUp.ToInt64():X}"); + Log.Verbose($"ProcessZonePacketDown address {Util.DescribeAddress(this.address.ProcessZonePacketDown)}"); + Log.Verbose($"ProcessZonePacketUp address {Util.DescribeAddress(this.address.ProcessZonePacketUp)}"); this.processZonePacketDownHook = Hook.FromAddress(this.address.ProcessZonePacketDown, this.ProcessZonePacketDownDetour); this.processZonePacketUpHook = Hook.FromAddress(this.address.ProcessZonePacketUp, this.ProcessZonePacketUpDetour); diff --git a/Dalamud/Hooking/Internal/HookManager.cs b/Dalamud/Hooking/Internal/HookManager.cs index 44c1e6735..8f5dba583 100644 --- a/Dalamud/Hooking/Internal/HookManager.cs +++ b/Dalamud/Hooking/Internal/HookManager.cs @@ -5,6 +5,7 @@ using System.Runtime.InteropServices; using Dalamud.Logging.Internal; using Dalamud.Memory; +using Dalamud.Utility; using Iced.Intel; @@ -69,7 +70,7 @@ internal class HookManager : IInternalDisposableService /// A new Unhooker instance. public static Unhooker RegisterUnhooker(IntPtr address, int minBytes, int maxBytes) { - Log.Verbose($"Registering hook at 0x{address.ToInt64():X} (minBytes=0x{minBytes:X}, maxBytes=0x{maxBytes:X})"); + Log.Verbose($"Registering hook at {Util.DescribeAddress(address)} (minBytes=0x{minBytes:X}, maxBytes=0x{maxBytes:X})"); return Unhookers.GetOrAdd(address, _ => new Unhooker(address, minBytes, maxBytes)); } diff --git a/Dalamud/Hooking/Internal/Unhooker.cs b/Dalamud/Hooking/Internal/Unhooker.cs index 79ee59b66..33e4640f4 100644 --- a/Dalamud/Hooking/Internal/Unhooker.cs +++ b/Dalamud/Hooking/Internal/Unhooker.cs @@ -1,4 +1,5 @@ using Dalamud.Memory; +using Dalamud.Utility; namespace Dalamud.Hooking.Internal; @@ -63,7 +64,7 @@ public class Unhooker var len = this.trimmed ? this.originalBytes.Length : int.Max(this.GetNaiveHookLength(), this.minBytes); if (len > 0) { - HookManager.Log.Verbose($"Reverting hook at 0x{this.address.ToInt64():X} ({len} bytes, trimmed={this.trimmed})"); + HookManager.Log.Verbose($"Reverting hook at {Util.DescribeAddress(this.address)} ({len} bytes, trimmed={this.trimmed})"); MemoryHelper.ChangePermission(this.address, len, MemoryProtection.ExecuteReadWrite, out var oldPermissions); MemoryHelper.WriteRaw(this.address, this.originalBytes[..len]); MemoryHelper.ChangePermission(this.address, len, oldPermissions); diff --git a/Dalamud/Interface/Internal/InterfaceManager.cs b/Dalamud/Interface/Internal/InterfaceManager.cs index 26b5c8ce2..06a93e453 100644 --- a/Dalamud/Interface/Internal/InterfaceManager.cs +++ b/Dalamud/Interface/Internal/InterfaceManager.cs @@ -12,7 +12,6 @@ using Dalamud.Configuration.Internal; using Dalamud.Game; using Dalamud.Game.ClientState.GamePad; using Dalamud.Game.ClientState.Keys; -using Dalamud.Game.Internal.DXGI; using Dalamud.Hooking; using Dalamud.Hooking.WndProcHook; using Dalamud.Interface.ImGuiNotification.Internal; @@ -79,11 +78,11 @@ internal class InterfaceManager : IInternalDisposableService private readonly ConcurrentQueue runBeforeImGuiRender = new(); private readonly ConcurrentQueue runAfterImGuiRender = new(); - private readonly SwapChainVtableResolver address = new(); private RawDX11Scene? scene; private Hook? setCursorHook; - private Hook? presentHook; + private Hook? dxgiPresentHook; + private Hook? reshadeOnPresentHook; private Hook? resizeBuffersHook; private IFontAtlas? dalamudAtlas; @@ -100,7 +99,10 @@ internal class InterfaceManager : IInternalDisposableService } [UnmanagedFunctionPointer(CallingConvention.ThisCall)] - private delegate IntPtr PresentDelegate(IntPtr swapChain, uint syncInterval, uint presentFlags); + private delegate IntPtr DxgiPresentDelegate(IntPtr swapChain, uint syncInterval, uint presentFlags); + + [UnmanagedFunctionPointer(CallingConvention.ThisCall)] + private delegate void ReshadeOnPresentDelegate(nint swapChain, uint flags, nint presentParams); [UnmanagedFunctionPointer(CallingConvention.ThisCall)] private delegate IntPtr ResizeBuffersDelegate(IntPtr swapChain, uint bufferCount, uint width, uint height, uint newFormat, uint swapChainFlags); @@ -296,7 +298,8 @@ internal class InterfaceManager : IInternalDisposableService { this.wndProcHookManager.PreWndProc -= this.WndProcHookManagerOnPreWndProc; Interlocked.Exchange(ref this.setCursorHook, null)?.Dispose(); - Interlocked.Exchange(ref this.presentHook, null)?.Dispose(); + Interlocked.Exchange(ref this.dxgiPresentHook, null)?.Dispose(); + Interlocked.Exchange(ref this.reshadeOnPresentHook, null)?.Dispose(); Interlocked.Exchange(ref this.resizeBuffersHook, null)?.Dispose(); } } @@ -629,17 +632,16 @@ internal class InterfaceManager : IInternalDisposableService args.SuppressWithValue(r.Value); } - /* - * NOTE(goat): When hooking ReShade DXGISwapChain::runtime_present, this is missing the syncInterval arg. - * Seems to work fine regardless, I guess, so whatever. - */ - private IntPtr PresentDetour(IntPtr swapChain, uint syncInterval, uint presentFlags) + private void ReshadeOnPresentDetour(nint swapChain, uint flags, nint presentParams) { - Debug.Assert(this.presentHook is not null, "How did PresentDetour get called when presentHook is null?"); - Debug.Assert(this.dalamudAtlas is not null, "dalamudAtlas should have been set already"); + if (!SwapChainHelper.IsGameDeviceSwapChain(swapChain)) + { + this.reshadeOnPresentHook!.Original(swapChain, flags, presentParams); + return; + } - if (this.scene != null && swapChain != this.scene.SwapChain.NativePointer) - return this.presentHook!.Original(swapChain, syncInterval, presentFlags); + Debug.Assert(this.reshadeOnPresentHook is not null, "this.reshadeOnPresentHook is not null"); + Debug.Assert(this.dalamudAtlas is not null, "this.dalamudAtlas is not null"); if (this.scene == null) this.InitScene(swapChain); @@ -655,7 +657,8 @@ internal class InterfaceManager : IInternalDisposableService Util.Fatal("Failed to initialize Dalamud base fonts.\nPlease report this error.", "Dalamud"); } - return this.presentHook!.Original(swapChain, syncInterval, presentFlags); + this.reshadeOnPresentHook!.Original(swapChain, flags, presentParams); + return; } this.CumulativePresentCalls++; @@ -664,22 +667,49 @@ internal class InterfaceManager : IInternalDisposableService while (this.runBeforeImGuiRender.TryDequeue(out var action)) action.InvokeSafely(); - if (this.address.IsReshade) + this.reshadeOnPresentHook!.Original(swapChain, flags, presentParams); + + RenderImGui(this.scene!); + this.PostImGuiRender(); + this.IsMainThreadInPresent = false; + } + + private IntPtr PresentDetour(IntPtr swapChain, uint syncInterval, uint presentFlags) + { + if (!SwapChainHelper.IsGameDeviceSwapChain(swapChain)) + return this.dxgiPresentHook!.Original(swapChain, syncInterval, presentFlags); + + Debug.Assert(this.dxgiPresentHook is not null, "How did PresentDetour get called when presentHook is null?"); + Debug.Assert(this.dalamudAtlas is not null, "dalamudAtlas should have been set already"); + + if (this.scene == null) + this.InitScene(swapChain); + + Debug.Assert(this.scene is not null, "InitScene did not set the scene field, but did not throw an exception."); + + if (!this.dalamudAtlas!.HasBuiltAtlas) { - var pRes = this.presentHook!.Original(swapChain, syncInterval, presentFlags); + if (this.dalamudAtlas.BuildTask.Exception != null) + { + // TODO: Can we do something more user-friendly here? Unload instead? + Log.Error(this.dalamudAtlas.BuildTask.Exception, "Failed to initialize Dalamud base fonts"); + Util.Fatal("Failed to initialize Dalamud base fonts.\nPlease report this error.", "Dalamud"); + } - RenderImGui(this.scene!); - this.PostImGuiRender(); - this.IsMainThreadInPresent = false; - - return pRes; + return this.dxgiPresentHook!.Original(swapChain, syncInterval, presentFlags); } + this.CumulativePresentCalls++; + this.IsMainThreadInPresent = true; + + while (this.runBeforeImGuiRender.TryDequeue(out var action)) + action.InvokeSafely(); + RenderImGui(this.scene!); this.PostImGuiRender(); this.IsMainThreadInPresent = false; - return this.presentHook!.Original(swapChain, syncInterval, presentFlags); + return this.dxgiPresentHook!.Original(swapChain, syncInterval, presentFlags); } private void PostImGuiRender() @@ -711,7 +741,7 @@ internal class InterfaceManager : IInternalDisposableService [ServiceManager.CallWhenServicesReady( "InterfaceManager accepts event registration and stuff even when the game window is not ready.")] - private void ContinueConstruction( + private unsafe void ContinueConstruction( TargetSigScanner sigScanner, FontAtlasFactory fontAtlasFactory) { @@ -787,8 +817,6 @@ internal class InterfaceManager : IInternalDisposableService // This will wait for scene on its own. We just wait for this.dalamudAtlas.BuildTask in this.InitScene. _ = this.dalamudAtlas.BuildFontsAsync(); - this.address.Setup(sigScanner); - try { if (Service.Get().WindowIsImmersive) @@ -799,32 +827,52 @@ internal class InterfaceManager : IInternalDisposableService Log.Error(ex, "Could not enable immersive mode"); } - this.setCursorHook = Hook.FromImport(null, "user32.dll", "SetCursor", 0, this.SetCursorDetour); - this.presentHook = Hook.FromAddress(this.address.Present, this.PresentDetour); - this.resizeBuffersHook = Hook.FromAddress(this.address.ResizeBuffers, this.ResizeBuffersDetour); + this.setCursorHook = Hook.FromImport( + null, + "user32.dll", + "SetCursor", + 0, + this.SetCursorDetour); + + SwapChainHelper.BusyWaitForGameDeviceSwapChain(); + SwapChainHelper.DetectReShade(); Log.Verbose("===== S W A P C H A I N ====="); - Log.Verbose($"Present address 0x{this.presentHook!.Address.ToInt64():X}"); - Log.Verbose($"ResizeBuffers address 0x{this.resizeBuffersHook!.Address.ToInt64():X}"); + this.resizeBuffersHook = Hook.FromAddress( + (nint)SwapChainHelper.GameDeviceSwapChainVtbl->ResizeBuffers, + this.ResizeBuffersDetour); + Log.Verbose($"ResizeBuffers address {Util.DescribeAddress(this.resizeBuffersHook!.Address)}"); + + if (SwapChainHelper.ReshadeOnPresent is null) + { + var addr = (nint)SwapChainHelper.GameDeviceSwapChainVtbl->Present; + this.dxgiPresentHook = Hook.FromAddress(addr, this.PresentDetour); + Log.Verbose($"ReShade::DXGISwapChain::on_present address {Util.DescribeAddress(addr)}"); + } + else + { + var addr = (nint)SwapChainHelper.ReshadeOnPresent; + this.reshadeOnPresentHook = Hook.FromAddress(addr, this.ReshadeOnPresentDetour); + Log.Verbose($"IDXGISwapChain::Present address {Util.DescribeAddress(addr)}"); + } this.setCursorHook.Enable(); - this.presentHook.Enable(); + this.dxgiPresentHook?.Enable(); + this.reshadeOnPresentHook?.Enable(); this.resizeBuffersHook.Enable(); } private IntPtr ResizeBuffersDetour(IntPtr swapChain, uint bufferCount, uint width, uint height, uint newFormat, uint swapChainFlags) { + if (!SwapChainHelper.IsGameDeviceSwapChain(swapChain)) + return this.resizeBuffersHook!.Original(swapChain, bufferCount, width, height, newFormat, swapChainFlags); + #if DEBUG Log.Verbose($"Calling resizebuffers swap@{swapChain.ToInt64():X}{bufferCount} {width} {height} {newFormat} {swapChainFlags}"); #endif this.ResizeBuffers?.InvokeSafely(); - // We have to ensure we're working with the main swapchain, - // as viewports might be resizing as well - if (this.scene == null || swapChain != this.scene.SwapChain.NativePointer) - return this.resizeBuffersHook!.Original(swapChain, bufferCount, width, height, newFormat, swapChainFlags); - this.scene?.OnPreResize(); var ret = this.resizeBuffersHook!.Original(swapChain, bufferCount, width, height, newFormat, swapChainFlags); diff --git a/Dalamud/Interface/Internal/SwapChainHelper.cs b/Dalamud/Interface/Internal/SwapChainHelper.cs new file mode 100644 index 000000000..09c1f52fd --- /dev/null +++ b/Dalamud/Interface/Internal/SwapChainHelper.cs @@ -0,0 +1,193 @@ +using System.Diagnostics; +using System.Threading; + +using Dalamud.Game; +using Dalamud.Utility; + +using FFXIVClientStructs.FFXIV.Client.Graphics.Kernel; + +using Serilog; + +using TerraFX.Interop.DirectX; +using TerraFX.Interop.Windows; + +namespace Dalamud.Interface.Internal; + +/// Helper for dealing with swap chains. +internal static unsafe class SwapChainHelper +{ + /// + /// Gets the function pointer for ReShade's DXGISwapChain::on_present. + /// Source. + /// + public static delegate* unmanaged ReshadeOnPresent { get; private set; } + + /// Gets the game's active instance of IDXGISwapChain that is initialized. + /// Address of the game's instance of IDXGISwapChain, or null if not available (yet.) + public static IDXGISwapChain* GameDeviceSwapChain + { + get + { + var kernelDev = Device.Instance(); + if (kernelDev == null) + return null; + + var swapChain = kernelDev->SwapChain; + if (swapChain == null) + return null; + + // BackBuffer should be something from IDXGISwapChain->GetBuffer, which means that IDXGISwapChain itself + // must have been fully initialized. + if (swapChain->BackBuffer == null) + return null; + + return (IDXGISwapChain*)swapChain->DXGISwapChain; + } + } + + /// Gets the vtable of . + public static IDXGISwapChain.Vtbl* GameDeviceSwapChainVtbl + { + get + { + var s = GameDeviceSwapChain; + return (IDXGISwapChain.Vtbl*)(s is null ? null : s->lpVtbl); + } + } + + /// + public static bool IsGameDeviceSwapChain(nint punk) => IsGameDeviceSwapChain((IUnknown*)punk); + + /// Determines if the given instance of IUnknown is the game device's swap chain. + /// Object to check. + /// Type of the object to check. + /// true if the object is the game's swap chain. + public static bool IsGameDeviceSwapChain(T* punk) where T : unmanaged, IUnknown.Interface + { + // https://learn.microsoft.com/en-us/windows/win32/api/unknwn/nf-unknwn-iunknown-queryinterface(refiid_void) + // For any given COM object (also known as a COM component), a specific query for the IUnknown interface on any + // of the object's interfaces must always return the same pointer value. + + var gdsc = GameDeviceSwapChain; + if (gdsc is null || punk is null) + return false; + + fixed (Guid* iid = &IID.IID_IUnknown) + { + using var u1 = default(ComPtr); + if (gdsc->QueryInterface(iid, (void**)u1.GetAddressOf()).FAILED) + return false; + + using var u2 = default(ComPtr); + if (punk->QueryInterface(iid, (void**)u2.GetAddressOf()).FAILED) + return false; + + return u1.Get() == u2.Get(); + } + } + + /// Wait for the game to have finished initializing the IDXGISwapChain. + public static void BusyWaitForGameDeviceSwapChain() + { + while (GameDeviceSwapChain is null) + Thread.Yield(); + } + + /// Detects ReShade and populate . + public static void DetectReShade() + { + var modules = Process.GetCurrentProcess().Modules; + foreach (ProcessModule processModule in modules) + { + if (!processModule.FileName.EndsWith("game\\dxgi.dll", StringComparison.InvariantCultureIgnoreCase)) + continue; + + try + { + var fileInfo = FileVersionInfo.GetVersionInfo(processModule.FileName); + + if (fileInfo.FileDescription == null) + break; + + if (!fileInfo.FileDescription.Contains("GShade") && !fileInfo.FileDescription.Contains("ReShade")) + break; + + // warning: these comments may no longer be accurate. + // reshade master@4232872 RVA + // var p = processModule.BaseAddress + 0x82C7E0; // DXGISwapChain::Present + // var p = processModule.BaseAddress + 0x82FAC0; // DXGISwapChain::runtime_present + // DXGISwapChain::handle_device_loss =>df DXGISwapChain::Present => DXGISwapChain::runtime_present + // 5.2+ - F6 C2 01 0F 85 + // 6.0+ - F6 C2 01 0F 85 88 + + var scanner = new SigScanner(processModule); + var reShadeDxgiPresent = nint.Zero; + + if (fileInfo.FileVersion?.StartsWith("6.") == true) + { + // No Addon + if (scanner.TryScanText("F6 C2 01 0F 85 A8", out reShadeDxgiPresent)) + { + Log.Information("Hooking present for ReShade 6 No-Addon"); + } + + // Addon + else if (scanner.TryScanText("F6 C2 01 0F 85 88", out reShadeDxgiPresent)) + { + Log.Information("Hooking present for ReShade 6 Addon"); + } + + // Fallback + else + { + Log.Error("Failed to get ReShade 6 DXGISwapChain::on_present offset!"); + } + } + + // Looks like this sig only works for GShade 4 + if (reShadeDxgiPresent == nint.Zero && fileInfo.FileDescription?.Contains("GShade 4.") == true) + { + if (scanner.TryScanText("E8 ?? ?? ?? ?? 45 0F B6 5E ??", out reShadeDxgiPresent)) + { + Log.Information("Hooking present for GShade 4"); + } + else + { + Log.Error("Failed to find GShade 4 DXGISwapChain::on_present offset!"); + } + } + + if (reShadeDxgiPresent == nint.Zero) + { + if (scanner.TryScanText("F6 C2 01 0F 85", out reShadeDxgiPresent)) + { + Log.Information("Hooking present for ReShade with fallback 5.X sig"); + } + else + { + Log.Error("Failed to find ReShade DXGISwapChain::on_present offset with fallback sig!"); + } + } + + Log.Information( + "ReShade DLL: {FileName} ({Info} - {Version}) with DXGISwapChain::on_present at {Address}", + processModule.FileName, + fileInfo.FileDescription ?? "Unknown", + fileInfo.FileVersion ?? "Unknown", + Util.DescribeAddress(reShadeDxgiPresent)); + + if (reShadeDxgiPresent != nint.Zero) + { + ReshadeOnPresent = (delegate* unmanaged)reShadeDxgiPresent; + } + + break; + } + catch (Exception e) + { + Log.Error(e, "Failed to get ReShade version info"); + break; + } + } + } +} diff --git a/Dalamud/Interface/Internal/Windows/Data/Widgets/AddonWidget.cs b/Dalamud/Interface/Internal/Windows/Data/Widgets/AddonWidget.cs index cf4979368..18bcd9334 100644 --- a/Dalamud/Interface/Internal/Windows/Data/Widgets/AddonWidget.cs +++ b/Dalamud/Interface/Internal/Windows/Data/Widgets/AddonWidget.cs @@ -50,7 +50,7 @@ internal unsafe class AddonWidget : IDataWindowWidget var addon = (FFXIVClientStructs.FFXIV.Component.GUI.AtkUnitBase*)address; var name = addon->NameString; - ImGui.TextUnformatted($"{name} - 0x{address.ToInt64():X}\n v:{addon->IsVisible} x:{addon->X} y:{addon->Y} s:{addon->Scale}, w:{addon->RootNode->Width}, h:{addon->RootNode->Height}"); + ImGui.TextUnformatted($"{name} - {Util.DescribeAddress(address)}\n v:{addon->IsVisible} x:{addon->X} y:{addon->Y} s:{addon->Scale}, w:{addon->RootNode->Width}, h:{addon->RootNode->Height}"); if (ImGui.Button("Find Agent")) { @@ -59,7 +59,7 @@ internal unsafe class AddonWidget : IDataWindowWidget if (this.findAgentInterfacePtr != nint.Zero) { - ImGui.TextUnformatted($"Agent: 0x{this.findAgentInterfacePtr.ToInt64():X}"); + ImGui.TextUnformatted($"Agent: {Util.DescribeAddress(this.findAgentInterfacePtr)}"); ImGui.SameLine(); if (ImGui.Button("C")) diff --git a/Dalamud/Interface/Internal/Windows/Data/Widgets/AddressesWidget.cs b/Dalamud/Interface/Internal/Windows/Data/Widgets/AddressesWidget.cs index eb1164eb2..c7ed526e1 100644 --- a/Dalamud/Interface/Internal/Windows/Data/Widgets/AddressesWidget.cs +++ b/Dalamud/Interface/Internal/Windows/Data/Widgets/AddressesWidget.cs @@ -1,6 +1,7 @@ using System.Collections.Generic; using Dalamud.Game; +using Dalamud.Utility; using ImGuiNET; @@ -57,7 +58,7 @@ internal class AddressesWidget : IDataWindowWidget foreach (var valueTuple in debugScannedValue.Value) { ImGui.TextUnformatted( - $" {valueTuple.ClassName} - 0x{valueTuple.Address.ToInt64():X}"); + $" {valueTuple.ClassName} - {Util.DescribeAddress(valueTuple.Address)}"); ImGui.SameLine(); if (ImGui.Button($"C##{valueTuple.Address.ToInt64():X}")) diff --git a/Dalamud/Interface/Internal/Windows/Data/Widgets/ConditionWidget.cs b/Dalamud/Interface/Internal/Windows/Data/Widgets/ConditionWidget.cs index c3305c168..355a73a71 100644 --- a/Dalamud/Interface/Internal/Windows/Data/Widgets/ConditionWidget.cs +++ b/Dalamud/Interface/Internal/Windows/Data/Widgets/ConditionWidget.cs @@ -1,4 +1,5 @@ using Dalamud.Game.ClientState.Conditions; +using Dalamud.Utility; using ImGuiNET; @@ -30,7 +31,7 @@ internal class ConditionWidget : IDataWindowWidget var condition = Service.Get(); #if DEBUG - ImGui.Text($"ptr: 0x{condition.Address.ToInt64():X}"); + ImGui.Text($"ptr: {Util.DescribeAddress(condition.Address)}"); #endif ImGui.Text("Current Conditions:"); diff --git a/Dalamud/Interface/Internal/Windows/Data/Widgets/GamepadWidget.cs b/Dalamud/Interface/Internal/Windows/Data/Widgets/GamepadWidget.cs index 3e87540c3..5121d82e6 100644 --- a/Dalamud/Interface/Internal/Windows/Data/Widgets/GamepadWidget.cs +++ b/Dalamud/Interface/Internal/Windows/Data/Widgets/GamepadWidget.cs @@ -1,4 +1,5 @@ using Dalamud.Game.ClientState.GamePad; +using Dalamud.Utility; using ImGuiNET; @@ -29,14 +30,14 @@ internal class GamepadWidget : IDataWindowWidget { var gamepadState = Service.Get(); - ImGui.Text($"GamepadInput 0x{gamepadState.GamepadInputAddress.ToInt64():X}"); + ImGui.Text($"GamepadInput {Util.DescribeAddress(gamepadState.GamepadInputAddress)}"); #if DEBUG if (ImGui.IsItemHovered()) ImGui.SetMouseCursor(ImGuiMouseCursor.Hand); if (ImGui.IsItemClicked()) - ImGui.SetClipboardText($"0x{gamepadState.GamepadInputAddress.ToInt64():X}"); + ImGui.SetClipboardText($"{Util.DescribeAddress(gamepadState.GamepadInputAddress)}"); #endif this.DrawHelper( diff --git a/Dalamud/Memory/MemoryHelper.cs b/Dalamud/Memory/MemoryHelper.cs index 09f45e2d3..212feb128 100644 --- a/Dalamud/Memory/MemoryHelper.cs +++ b/Dalamud/Memory/MemoryHelper.cs @@ -5,6 +5,7 @@ using System.Text; using Dalamud.Game.Text.SeStringHandling; using Dalamud.Memory.Exceptions; +using Dalamud.Utility; using FFXIVClientStructs.FFXIV.Client.System.Memory; using FFXIVClientStructs.FFXIV.Client.System.String; @@ -787,11 +788,11 @@ public static unsafe class MemoryHelper var result = VirtualProtect(memoryAddress, (nuint)length, newPermissions, out var oldPermissions); if (!result) - throw new MemoryPermissionException($"Unable to change permissions at 0x{memoryAddress.ToInt64():X} of length {length} and permission {newPermissions} (result={result})"); + throw new MemoryPermissionException($"Unable to change permissions at {Util.DescribeAddress(memoryAddress)} of length {length} and permission {newPermissions} (result={result})"); var last = Marshal.GetLastWin32Error(); if (last > 0) - throw new MemoryPermissionException($"Unable to change permissions at 0x{memoryAddress.ToInt64():X} of length {length} and permission {newPermissions} (error={last})"); + throw new MemoryPermissionException($"Unable to change permissions at {Util.DescribeAddress(memoryAddress)} of length {length} and permission {newPermissions} (error={last})"); return oldPermissions; } @@ -862,11 +863,11 @@ public static unsafe class MemoryHelper var result = NativeFunctions.ReadProcessMemory((nint)0xFFFFFFFF, memoryAddress, value, length, out _); if (!result) - throw new MemoryReadException($"Unable to read memory at 0x{memoryAddress.ToInt64():X} 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(); if (last > 0) - throw new MemoryReadException($"Unable to read memory at 0x{memoryAddress.ToInt64():X} of length {length} (error={last})"); + throw new MemoryReadException($"Unable to read memory at {Util.DescribeAddress(memoryAddress)} of length {length} (error={last})"); } } @@ -884,11 +885,11 @@ public static unsafe class MemoryHelper var result = NativeFunctions.WriteProcessMemory((nint)0xFFFFFFFF, memoryAddress, data, length, out _); if (!result) - throw new MemoryWriteException($"Unable to write memory at 0x{memoryAddress.ToInt64():X} 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(); if (last > 0) - throw new MemoryWriteException($"Unable to write memory at 0x{memoryAddress.ToInt64():X} of length {length} (error={last})"); + throw new MemoryWriteException($"Unable to write memory at {Util.DescribeAddress(memoryAddress)} of length {length} (error={last})"); } } diff --git a/Dalamud/Utility/Util.cs b/Dalamud/Utility/Util.cs index a9be33244..927f7b310 100644 --- a/Dalamud/Utility/Util.cs +++ b/Dalamud/Utility/Util.cs @@ -19,7 +19,6 @@ using Dalamud.Game.ClientState.Objects.SubKinds; using Dalamud.Game.ClientState.Objects.Types; using Dalamud.Interface.Colors; using Dalamud.Interface.Utility; - using ImGuiNET; using Lumina.Excel.GeneratedSheets; using Serilog; @@ -28,7 +27,8 @@ using Windows.Win32.Storage.FileSystem; using Windows.Win32.System.Memory; using Windows.Win32.System.Ole; -using HWND = Windows.Win32.Foundation.HWND; +using static TerraFX.Interop.Windows.Windows; + using Win32_PInvoke = Windows.Win32.PInvoke; namespace Dalamud.Utility; @@ -38,6 +38,28 @@ namespace Dalamud.Utility; /// public static class Util { + private static readonly string[] PageProtectionFlagNames = [ + "PAGE_NOACCESS", + "PAGE_READONLY", + "PAGE_READWRITE", + "PAGE_WRITECOPY", + "PAGE_EXECUTE", + "PAGE_EXECUTE_READ", + "PAGE_EXECUTE_READWRITE", + "PAGE_EXECUTE_WRITECOPY", + "PAGE_GUARD", + "PAGE_NOCACHE", + "PAGE_WRITECOMBINE", + "PAGE_GRAPHICS_NOACCESS", + "PAGE_GRAPHICS_READONLY", + "PAGE_GRAPHICS_READWRITE", + "PAGE_GRAPHICS_EXECUTE", + "PAGE_GRAPHICS_EXECUTE_READ", + "PAGE_GRAPHICS_EXECUTE_READWRITE", + "PAGE_GRAPHICS_COHERENT", + "PAGE_GRAPHICS_NOCACHE", + ]; + private static readonly Type GenericSpanType = typeof(Span<>); private static string? gitHashInternal; private static int? gitCommitCountInternal; @@ -160,6 +182,100 @@ public static class Util return gitHashClientStructsInternal; } + /// + public static unsafe string DescribeAddress(void* p) => DescribeAddress((nint)p); + + /// Describes a memory address relative to module, or allocation base. + /// Address. + /// Address description. + public static unsafe string DescribeAddress(nint p) + { + Span namebuf = stackalloc char[9]; + var modules = Process.GetCurrentProcess().Modules; + for (var i = 0; i < modules.Count; i++) + { + if (p < modules[i].BaseAddress) continue; + var d = p - modules[i].BaseAddress; + if (d > modules[i].ModuleMemorySize) continue; + + // Display module name without path, only if there exists exactly one module loaded in the memory. + var fileName = modules[i].ModuleName; + for (var j = 0; j < modules.Count; j++) + { + if (i == j) + continue; + if (!modules[j].ModuleName.Equals(fileName, StringComparison.InvariantCultureIgnoreCase)) + continue; + fileName = modules[i].FileName; + break; + } + + var dos = (IMAGE_DOS_HEADER*)modules[i].BaseAddress; + if (dos->e_magic != 0x5A4D) + return $"0x{p:X}({fileName}+0x{d:X}: ???)"; + + Span sections; + switch (((IMAGE_NT_HEADERS32*)(modules[i].BaseAddress + dos->e_lfanew))->OptionalHeader.Magic) + { + case IMAGE.IMAGE_NT_OPTIONAL_HDR32_MAGIC: + { + var nth = (IMAGE_NT_HEADERS32*)(modules[i].BaseAddress + dos->e_lfanew); + if (d < dos->e_lfanew + sizeof(IMAGE_NT_HEADERS32) + + (nth->FileHeader.NumberOfSections * sizeof(IMAGE_SECTION_HEADER))) + goto default; + sections = new( + (void*)(modules[i].BaseAddress + dos->e_lfanew + sizeof(IMAGE_NT_HEADERS32)), + nth->FileHeader.NumberOfSections); + break; + } + + case IMAGE.IMAGE_NT_OPTIONAL_HDR64_MAGIC: + { + var nth = (IMAGE_NT_HEADERS64*)(modules[i].BaseAddress + dos->e_lfanew); + if (d < dos->e_lfanew + sizeof(IMAGE_NT_HEADERS64) + + (nth->FileHeader.NumberOfSections * sizeof(IMAGE_SECTION_HEADER))) + goto default; + sections = new( + (void*)(modules[i].BaseAddress + dos->e_lfanew + sizeof(IMAGE_NT_HEADERS64)), + nth->FileHeader.NumberOfSections); + break; + } + + default: + return $"0x{p:X}({fileName}+0x{d:X}: header)"; + } + + for (var j = 0; j < sections.Length; j++) + { + if (d >= sections[j].VirtualAddress && d < sections[j].VirtualAddress + sections[j].Misc.VirtualSize) + { + var d2 = d - sections[j].VirtualAddress; + var name8 = new Span((byte*)Unsafe.AsPointer(ref sections[j].Name[0]), 8).TrimEnd((byte)0); + return $"0x{p:X}({fileName}+0x{d:X}({namebuf[..Encoding.UTF8.GetChars(name8, namebuf)]}+0x{d2:X}))"; + } + } + + return $"0x{p:X}({fileName}+0x{d:X}: ???)"; + } + + MEMORY_BASIC_INFORMATION mbi; + if (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) + continue; + if (c++ == 0) + sb.Append(" | "); + sb.Append(PageProtectionFlagNames[i]); + } + + return sb.Append(')').ToString(); + } + /// /// Read memory from an offset and hexdump them via Serilog. /// @@ -858,7 +974,7 @@ public static class Util Win32_PInvoke.GlobalUnlock(hGlobal); - if (Win32_PInvoke.OpenClipboard(HWND.Null)) + if (Win32_PInvoke.OpenClipboard(default)) { Win32_PInvoke.SetClipboardData( (uint)CLIPBOARD_FORMAT.CF_HDROP,