diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 9466cb083..f552e446b 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -1,10 +1,9 @@ name: Build Dalamud on: [push, pull_request, workflow_dispatch] -# Globally blocking because of git pushes in deploy step concurrency: - group: build_dalamud_${{ github.repository_owner }} - cancel-in-progress: false + group: build_dalamud_${{ github.ref_name }} + cancel-in-progress: true jobs: build: @@ -34,8 +33,12 @@ jobs: ($env:REPO_NAME) >> VERSION ($env:BRANCH) >> VERSION ($env:COMMIT) >> VERSION + - name: git status + run: git status - name: Build and Test Dalamud run: .\build.ps1 ci + - name: git status + run: git status - name: Sign Dalamud if: ${{ github.repository_owner == 'goatcorp' && github.event_name == 'push' }} env: diff --git a/.nuke/build.schema.json b/.nuke/build.schema.json index 6ffb3bb01..03211ce8f 100644 --- a/.nuke/build.schema.json +++ b/.nuke/build.schema.json @@ -1,57 +1,19 @@ { "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Build Schema", + "$ref": "#/definitions/build", "definitions": { - "Host": { - "type": "string", - "enum": [ - "AppVeyor", - "AzurePipelines", - "Bamboo", - "Bitbucket", - "Bitrise", - "GitHubActions", - "GitLab", - "Jenkins", - "Rider", - "SpaceAutomation", - "TeamCity", - "Terminal", - "TravisCI", - "VisualStudio", - "VSCode" - ] - }, - "ExecutableTarget": { - "type": "string", - "enum": [ - "CI", - "Clean", - "Compile", - "CompileCImGui", - "CompileCImGuizmo", - "CompileCImPlot", - "CompileDalamud", - "CompileDalamudBoot", - "CompileDalamudCrashHandler", - "CompileImGuiNatives", - "CompileInjector", - "Restore", - "SetCILogging", - "Test" - ] - }, - "Verbosity": { - "type": "string", - "description": "", - "enum": [ - "Verbose", - "Normal", - "Minimal", - "Quiet" - ] - }, - "NukeBuild": { + "build": { + "type": "object", "properties": { + "Configuration": { + "type": "string", + "description": "Configuration to build - Default is 'Debug' (local) or 'Release' (server)", + "enum": [ + "Debug", + "Release" + ] + }, "Continue": { "type": "boolean", "description": "Indicates to continue a previously failed build attempt" @@ -61,8 +23,29 @@ "description": "Shows the help text for this build assembly" }, "Host": { + "type": "string", "description": "Host for execution. Default is 'automatic'", - "$ref": "#/definitions/Host" + "enum": [ + "AppVeyor", + "AzurePipelines", + "Bamboo", + "Bitbucket", + "Bitrise", + "GitHubActions", + "GitLab", + "Jenkins", + "Rider", + "SpaceAutomation", + "TeamCity", + "Terminal", + "TravisCI", + "VisualStudio", + "VSCode" + ] + }, + "IsDocsBuild": { + "type": "boolean", + "description": "Whether we are building for documentation - emits generated files" }, "NoLogo": { "type": "boolean", @@ -91,46 +74,63 @@ "type": "array", "description": "List of targets to be skipped. Empty list skips all dependencies", "items": { - "$ref": "#/definitions/ExecutableTarget" + "type": "string", + "enum": [ + "CI", + "Clean", + "Compile", + "CompileCImGui", + "CompileCImGuizmo", + "CompileCImPlot", + "CompileDalamud", + "CompileDalamudBoot", + "CompileDalamudCrashHandler", + "CompileImGuiNatives", + "CompileInjector", + "Restore", + "SetCILogging", + "Test" + ] } }, + "Solution": { + "type": "string", + "description": "Path to a solution file that is automatically loaded" + }, "Target": { "type": "array", "description": "List of targets to be invoked. Default is '{default_target}'", "items": { - "$ref": "#/definitions/ExecutableTarget" + "type": "string", + "enum": [ + "CI", + "Clean", + "Compile", + "CompileCImGui", + "CompileCImGuizmo", + "CompileCImPlot", + "CompileDalamud", + "CompileDalamudBoot", + "CompileDalamudCrashHandler", + "CompileImGuiNatives", + "CompileInjector", + "Restore", + "SetCILogging", + "Test" + ] } }, "Verbosity": { + "type": "string", "description": "Logging verbosity during build execution. Default is 'Normal'", - "$ref": "#/definitions/Verbosity" - } - } - } - }, - "allOf": [ - { - "properties": { - "Configuration": { - "type": "string", - "description": "Configuration to build - Default is 'Debug' (local) or 'Release' (server)", "enum": [ - "Debug", - "Release" + "Minimal", + "Normal", + "Quiet", + "Verbose" ] - }, - "IsDocsBuild": { - "type": "boolean", - "description": "Whether we are building for documentation - emits generated files" - }, - "Solution": { - "type": "string", - "description": "Path to a solution file that is automatically loaded" } } - }, - { - "$ref": "#/definitions/NukeBuild" } - ] -} + } +} \ No newline at end of file diff --git a/Dalamud.Boot/crashhandler_shared.h b/Dalamud.Boot/crashhandler_shared.h index 0308306ce..8d93e4460 100644 --- a/Dalamud.Boot/crashhandler_shared.h +++ b/Dalamud.Boot/crashhandler_shared.h @@ -6,8 +6,6 @@ #define WIN32_LEAN_AND_MEAN #include -#define CUSTOM_EXCEPTION_EXTERNAL_EVENT 0x12345679 - struct exception_info { LPEXCEPTION_POINTERS pExceptionPointers; diff --git a/Dalamud.Boot/dllmain.cpp b/Dalamud.Boot/dllmain.cpp index 687089f82..80a16f89a 100644 --- a/Dalamud.Boot/dllmain.cpp +++ b/Dalamud.Boot/dllmain.cpp @@ -331,51 +331,6 @@ HRESULT WINAPI InitializeImpl(LPVOID lpParam, HANDLE hMainThreadContinue) { logging::I("VEH was disabled manually"); } - // ============================== CLR Reporting =================================== // - - // This is pretty horrible - CLR just doesn't provide a way for us to handle these events, and the API for it - // was pushed back to .NET 11, so we have to hook ReportEventW and catch them ourselves for now. - // Ideally all of this will go away once they get to it. - static std::shared_ptr> s_report_event_hook; - s_report_event_hook = std::make_shared>( - "advapi32.dll!ReportEventW (global import, hook_clr_report_event)", L"advapi32.dll", "ReportEventW"); - s_report_event_hook->set_detour([hook = s_report_event_hook.get()]( - HANDLE hEventLog, - WORD wType, - WORD wCategory, - DWORD dwEventID, - PSID lpUserSid, - WORD wNumStrings, - DWORD dwDataSize, - LPCWSTR* lpStrings, - LPVOID lpRawData)-> BOOL { - - // Check for CLR Error Event IDs - // https://github.com/dotnet/runtime/blob/v10.0.0/src/coreclr/vm/eventreporter.cpp#L370 - if (dwEventID != 1026 && // ERT_UnhandledException: The process was terminated due to an unhandled exception - dwEventID != 1025 && // ERT_ManagedFailFast: The application requested process termination through System.Environment.FailFast - dwEventID != 1023 && // ERT_UnmanagedFailFast: The process was terminated due to an internal error in the .NET Runtime - dwEventID != 1027 && // ERT_StackOverflow: The process was terminated due to a stack overflow - dwEventID != 1028) // ERT_CodeContractFailed: The application encountered a bug. A managed code contract (precondition, postcondition, object invariant, or assert) failed - { - return hook->call_original(hEventLog, wType, wCategory, dwEventID, lpUserSid, wNumStrings, dwDataSize, lpStrings, lpRawData); - } - - if (wNumStrings == 0 || lpStrings == nullptr) { - logging::W("ReportEventW called with no strings."); - return hook->call_original(hEventLog, wType, wCategory, dwEventID, lpUserSid, wNumStrings, dwDataSize, lpStrings, lpRawData); - } - - // In most cases, DalamudCrashHandler will kill us now, so call original here to make sure we still write to the event log. - const BOOL original_ret = hook->call_original(hEventLog, wType, wCategory, dwEventID, lpUserSid, wNumStrings, dwDataSize, lpStrings, lpRawData); - - const std::wstring error_details(lpStrings[0]); - veh::raise_external_event(error_details); - - return original_ret; - }); - logging::I("ReportEventW hook installed."); - // ============================== Dalamud ==================================== // if (static_cast(g_startInfo.BootWaitMessageBox) & static_cast(DalamudStartInfo::WaitMessageboxFlags::BeforeDalamudEntrypoint)) diff --git a/Dalamud.Boot/veh.cpp b/Dalamud.Boot/veh.cpp index 8013354ae..b75256af8 100644 --- a/Dalamud.Boot/veh.cpp +++ b/Dalamud.Boot/veh.cpp @@ -31,8 +31,6 @@ HANDLE g_crashhandler_process = nullptr; HANDLE g_crashhandler_event = nullptr; HANDLE g_crashhandler_pipe_write = nullptr; -wchar_t g_external_event_info[16384] = L""; - std::recursive_mutex g_exception_handler_mutex; std::chrono::time_point g_time_start; @@ -193,11 +191,7 @@ LONG exception_handler(EXCEPTION_POINTERS* ex) DuplicateHandle(GetCurrentProcess(), g_crashhandler_event, g_crashhandler_process, &exinfo.hEventHandle, 0, TRUE, DUPLICATE_SAME_ACCESS); std::wstring stackTrace; - if (ex->ExceptionRecord->ExceptionCode == CUSTOM_EXCEPTION_EXTERNAL_EVENT) - { - stackTrace = std::wstring(g_external_event_info); - } - else if (!g_clr) + if (!g_clr) { stackTrace = L"(no CLR stack trace available)"; } @@ -258,12 +252,6 @@ LONG WINAPI structured_exception_handler(EXCEPTION_POINTERS* ex) LONG WINAPI vectored_exception_handler(EXCEPTION_POINTERS* ex) { - // special case for CLR exceptions, always trigger crash handler - if (ex->ExceptionRecord->ExceptionCode == CUSTOM_EXCEPTION_EXTERNAL_EVENT) - { - return exception_handler(ex); - } - if (ex->ExceptionRecord->ExceptionCode == 0x12345678) { // pass @@ -447,10 +435,3 @@ bool veh::remove_handler() } return false; } - -void veh::raise_external_event(const std::wstring& info) -{ - const auto info_size = std::min(info.size(), std::size(g_external_event_info) - 1); - wcsncpy_s(g_external_event_info, info.c_str(), info_size); - RaiseException(CUSTOM_EXCEPTION_EXTERNAL_EVENT, 0, 0, nullptr); -} diff --git a/Dalamud.Boot/veh.h b/Dalamud.Boot/veh.h index 2a02c374e..1905272ea 100644 --- a/Dalamud.Boot/veh.h +++ b/Dalamud.Boot/veh.h @@ -4,5 +4,4 @@ namespace veh { bool add_handler(bool doFullDump, const std::string& workingDirectory); bool remove_handler(); - void raise_external_event(const std::wstring& info); } diff --git a/Dalamud/Dalamud.csproj b/Dalamud/Dalamud.csproj index 1c16891b7..0bb268060 100644 --- a/Dalamud/Dalamud.csproj +++ b/Dalamud/Dalamud.csproj @@ -121,8 +121,6 @@ PreserveNewest - - diff --git a/Dalamud/EntryPoint.cs b/Dalamud/EntryPoint.cs index b5504b046..15077f3d8 100644 --- a/Dalamud/EntryPoint.cs +++ b/Dalamud/EntryPoint.cs @@ -263,7 +263,7 @@ public sealed class EntryPoint var symbolPath = Path.Combine(info.AssetDirectory, "UIRes", "pdb"); var searchPath = $".;{symbolPath}"; - var currentProcess = Windows.Win32.PInvoke.GetCurrentProcess(); + var currentProcess = Windows.Win32.PInvoke.GetCurrentProcess_SafeHandle(); // Remove any existing Symbol Handler and Init a new one with our search path added Windows.Win32.PInvoke.SymCleanup(currentProcess); diff --git a/Dalamud/Game/Addon/Events/AddonEventManager.cs b/Dalamud/Game/Addon/Events/AddonEventManager.cs index 980404940..945197e2b 100644 --- a/Dalamud/Game/Addon/Events/AddonEventManager.cs +++ b/Dalamud/Game/Addon/Events/AddonEventManager.cs @@ -9,6 +9,7 @@ using Dalamud.Logging.Internal; using Dalamud.Plugin.Internal.Types; using Dalamud.Plugin.Services; +using FFXIVClientStructs.FFXIV.Client.UI; using FFXIVClientStructs.FFXIV.Component.GUI; namespace Dalamud.Game.Addon.Events; @@ -31,21 +32,25 @@ internal unsafe class AddonEventManager : IInternalDisposableService private readonly AddonLifecycleEventListener finalizeEventListener; - private readonly Hook onUpdateCursor; + private readonly AddonEventManagerAddressResolver address; + private readonly Hook onUpdateCursor; private readonly ConcurrentDictionary pluginEventControllers; - private AtkCursor.CursorType? cursorOverride; + private AddonCursorType? cursorOverride; [ServiceManager.ServiceConstructor] - private AddonEventManager() + private AddonEventManager(TargetSigScanner sigScanner) { + this.address = new AddonEventManagerAddressResolver(); + this.address.Setup(sigScanner); + this.pluginEventControllers = new ConcurrentDictionary(); this.pluginEventControllers.TryAdd(DalamudInternalKey, new PluginEventController()); this.cursorOverride = null; - this.onUpdateCursor = Hook.FromAddress(AtkUnitManager.Addresses.UpdateCursor.Value, this.UpdateCursorDetour); + this.onUpdateCursor = Hook.FromAddress(this.address.UpdateCursor, this.UpdateCursorDetour); this.finalizeEventListener = new AddonLifecycleEventListener(AddonEvent.PreFinalize, string.Empty, this.OnAddonFinalize); this.addonLifecycle.RegisterListener(this.finalizeEventListener); @@ -53,6 +58,8 @@ internal unsafe class AddonEventManager : IInternalDisposableService this.onUpdateCursor.Enable(); } + private delegate nint UpdateCursorDelegate(RaptureAtkModule* module); + /// void IInternalDisposableService.DisposeService() { @@ -110,7 +117,7 @@ internal unsafe class AddonEventManager : IInternalDisposableService /// Force the game cursor to be the specified cursor. /// /// Which cursor to use. - internal void SetCursor(AddonCursorType cursor) => this.cursorOverride = (AtkCursor.CursorType)cursor; + internal void SetCursor(AddonCursorType cursor) => this.cursorOverride = cursor; /// /// Un-forces the game cursor. @@ -161,7 +168,7 @@ internal unsafe class AddonEventManager : IInternalDisposableService } } - private void UpdateCursorDetour(AtkUnitManager* thisPtr) + private nint UpdateCursorDetour(RaptureAtkModule* module) { try { @@ -169,14 +176,13 @@ internal unsafe class AddonEventManager : IInternalDisposableService if (this.cursorOverride is not null && atkStage is not null) { - ref var atkCursor = ref atkStage->AtkCursor; - - if (atkCursor.Type != this.cursorOverride) + var cursor = (AddonCursorType)atkStage->AtkCursor.Type; + if (cursor != this.cursorOverride) { - atkCursor.SetCursorType((AtkCursor.CursorType)this.cursorOverride, 1); + AtkStage.Instance()->AtkCursor.SetCursorType((AtkCursor.CursorType)this.cursorOverride, 1); } - return; + return nint.Zero; } } catch (Exception e) @@ -184,7 +190,7 @@ internal unsafe class AddonEventManager : IInternalDisposableService Log.Error(e, "Exception in UpdateCursorDetour."); } - this.onUpdateCursor!.Original(thisPtr); + return this.onUpdateCursor!.Original(module); } } diff --git a/Dalamud/Game/Addon/Events/AddonEventManagerAddressResolver.cs b/Dalamud/Game/Addon/Events/AddonEventManagerAddressResolver.cs new file mode 100644 index 000000000..ec1c51a12 --- /dev/null +++ b/Dalamud/Game/Addon/Events/AddonEventManagerAddressResolver.cs @@ -0,0 +1,23 @@ +using Dalamud.Plugin.Services; + +namespace Dalamud.Game.Addon.Events; + +/// +/// AddonEventManager memory address resolver. +/// +internal class AddonEventManagerAddressResolver : BaseAddressResolver +{ + /// + /// Gets the address of the AtkModule UpdateCursor method. + /// + public nint UpdateCursor { get; private set; } + + /// + /// Scan for and setup any configured address pointers. + /// + /// The signature scanner to facilitate setup. + protected override void Setup64Bit(ISigScanner scanner) + { + this.UpdateCursor = scanner.ScanText("48 89 74 24 ?? 48 89 7C 24 ?? 41 56 48 83 EC 20 4C 8B F1 E8 ?? ?? ?? ?? 49 8B CE"); // unnamed in CS + } +} diff --git a/Dalamud/Hooking/Hook.cs b/Dalamud/Hooking/Hook.cs index 1cd3ef91d..faf4658a5 100644 --- a/Dalamud/Hooking/Hook.cs +++ b/Dalamud/Hooking/Hook.cs @@ -201,19 +201,19 @@ public abstract class Hook : IDalamudHook where T : Delegate if (EnvironmentConfiguration.DalamudForceMinHook) useMinHook = true; - var moduleHandle = Windows.Win32.PInvoke.GetModuleHandle(moduleName); - if (moduleHandle.IsNull) + using var moduleHandle = Windows.Win32.PInvoke.GetModuleHandle(moduleName); + if (moduleHandle.IsInvalid) throw new Exception($"Could not get a handle to module {moduleName}"); - var procAddress = Windows.Win32.PInvoke.GetProcAddress(moduleHandle, exportName); - if (procAddress.IsNull) + var procAddress = (nint)Windows.Win32.PInvoke.GetProcAddress(moduleHandle, exportName); + if (procAddress == IntPtr.Zero) throw new Exception($"Could not get the address of {moduleName}::{exportName}"); - var address = HookManager.FollowJmp(procAddress.Value); + procAddress = HookManager.FollowJmp(procAddress); if (useMinHook) - return new MinHookHook(address, detour, Assembly.GetCallingAssembly()); + return new MinHookHook(procAddress, detour, Assembly.GetCallingAssembly()); else - return new ReloadedHook(address, detour, Assembly.GetCallingAssembly()); + return new ReloadedHook(procAddress, detour, Assembly.GetCallingAssembly()); } /// diff --git a/Dalamud/Interface/ImGuiSeStringRenderer/SeStringDrawState.cs b/Dalamud/Interface/ImGuiSeStringRenderer/SeStringDrawState.cs index 3a21e0db9..11c1120b4 100644 --- a/Dalamud/Interface/ImGuiSeStringRenderer/SeStringDrawState.cs +++ b/Dalamud/Interface/ImGuiSeStringRenderer/SeStringDrawState.cs @@ -76,7 +76,7 @@ public unsafe ref struct SeStringDrawState this.splitter = default; this.GetEntity = ssdp.GetEntity; this.ScreenOffset = new(MathF.Round(this.ScreenOffset.X), MathF.Round(this.ScreenOffset.Y)); - this.FontSizeScale = this.FontSize / this.Font.FontSize; + this.FontSizeScale = this.FontSize / this.Font->FontSize; this.LineHeight = MathF.Round(ssdp.EffectiveLineHeight); this.LinkUnderlineThickness = ssdp.LinkUnderlineThickness ?? 0f; this.Opacity = ssdp.EffectiveOpacity; @@ -106,7 +106,7 @@ public unsafe ref struct SeStringDrawState public Vector2 ScreenOffset { get; } /// - public ImFontPtr Font { get; } + public ImFont* Font { get; } /// public float FontSize { get; } @@ -256,7 +256,7 @@ public unsafe ref struct SeStringDrawState /// Offset of the glyph in pixels w.r.t. . internal void DrawGlyph(scoped in ImGuiHelpers.ImFontGlyphReal g, Vector2 offset) { - var texId = this.Font.ContainerAtlas.Textures.Ref(g.TextureIndex).TexID; + var texId = this.Font->ContainerAtlas->Textures.Ref(g.TextureIndex).TexID; var xy0 = new Vector2( MathF.Round(g.X0 * this.FontSizeScale), MathF.Round(g.Y0 * this.FontSizeScale)); @@ -313,7 +313,7 @@ public unsafe ref struct SeStringDrawState offset += this.ScreenOffset; offset.Y += (this.LinkUnderlineThickness - 1) / 2f; - offset.Y += MathF.Round(((this.LineHeight - this.FontSize) / 2) + (this.Font.Ascent * this.FontSizeScale)); + offset.Y += MathF.Round(((this.LineHeight - this.FontSize) / 2) + (this.Font->Ascent * this.FontSizeScale)); this.SetCurrentChannel(SeStringDrawChannel.Foreground); this.DrawList.AddLine( @@ -340,9 +340,9 @@ public unsafe ref struct SeStringDrawState internal readonly ref ImGuiHelpers.ImFontGlyphReal FindGlyph(Rune rune) { var p = rune.Value is >= ushort.MinValue and < ushort.MaxValue - ? (ImFontGlyphPtr)this.Font.FindGlyph((ushort)rune.Value) - : this.Font.FallbackGlyph; - return ref *(ImGuiHelpers.ImFontGlyphReal*)p.Handle; + ? this.Font->FindGlyph((ushort)rune.Value) + : this.Font->FallbackGlyph; + return ref *(ImGuiHelpers.ImFontGlyphReal*)p; } /// Gets the glyph corresponding to the given codepoint. @@ -375,7 +375,7 @@ public unsafe ref struct SeStringDrawState return 0; return MathF.Round( - this.Font.GetDistanceAdjustmentForPair( + this.Font->GetDistanceAdjustmentForPair( (ushort)left.Value, (ushort)right.Value) * this.FontSizeScale); } diff --git a/Dalamud/NativeMethods.json b/Dalamud/NativeMethods.json index 46fd3504f..ffb313dfc 100644 --- a/Dalamud/NativeMethods.json +++ b/Dalamud/NativeMethods.json @@ -1,5 +1,4 @@ { "$schema": "https://aka.ms/CsWin32.schema.json", - "useSafeHandles": false, "allowMarshaling": false } diff --git a/Dalamud/SafeMemory.cs b/Dalamud/SafeMemory.cs index ca0c8ff92..a8ac40a5d 100644 --- a/Dalamud/SafeMemory.cs +++ b/Dalamud/SafeMemory.cs @@ -1,8 +1,6 @@ using System.Runtime.InteropServices; using System.Text; -using Windows.Win32.Foundation; - namespace Dalamud; /// @@ -14,11 +12,11 @@ namespace Dalamud; /// public static class SafeMemory { - private static readonly HANDLE Handle; + private static readonly SafeHandle Handle; static SafeMemory() { - Handle = Windows.Win32.PInvoke.GetCurrentProcess(); + Handle = Windows.Win32.PInvoke.GetCurrentProcess_SafeHandle(); } /// @@ -30,12 +28,6 @@ public static class SafeMemory /// Whether the read succeeded. public static unsafe bool ReadBytes(IntPtr address, int count, out byte[] buffer) { - if (Handle.IsNull) - { - buffer = []; - return false; - } - buffer = new byte[count <= 0 ? 0 : count]; fixed (byte* p = buffer) { @@ -62,9 +54,6 @@ public static class SafeMemory /// Whether the write succeeded. public static unsafe bool WriteBytes(IntPtr address, byte[] buffer) { - if (Handle.IsNull) - return false; - if (buffer.Length == 0) return true; diff --git a/Dalamud/Utility/FilesystemUtil.cs b/Dalamud/Utility/FilesystemUtil.cs index f1b62ee21..3b4298b37 100644 --- a/Dalamud/Utility/FilesystemUtil.cs +++ b/Dalamud/Utility/FilesystemUtil.cs @@ -1,8 +1,7 @@ -using System.ComponentModel; +using System.ComponentModel; using System.IO; using System.Text; -using Windows.Win32.Foundation; using Windows.Win32.Storage.FileSystem; namespace Dalamud.Utility; @@ -48,39 +47,30 @@ public static class FilesystemUtil // Open the temp file var tempPath = path + ".tmp"; - var tempFile = Windows.Win32.PInvoke.CreateFile( + using var tempFile = Windows.Win32.PInvoke.CreateFile( tempPath, (uint)(FILE_ACCESS_RIGHTS.FILE_GENERIC_READ | FILE_ACCESS_RIGHTS.FILE_GENERIC_WRITE), FILE_SHARE_MODE.FILE_SHARE_NONE, null, FILE_CREATION_DISPOSITION.CREATE_ALWAYS, FILE_FLAGS_AND_ATTRIBUTES.FILE_ATTRIBUTE_NORMAL, - HANDLE.Null); + null); - if (tempFile.IsNull) + if (tempFile.IsInvalid) throw new Win32Exception(); // Write the data uint bytesWritten = 0; - fixed (byte* ptr = bytes) - { - if (!Windows.Win32.PInvoke.WriteFile(tempFile, ptr, (uint)bytes.Length, &bytesWritten, null)) - throw new Win32Exception(); - } + if (!Windows.Win32.PInvoke.WriteFile(tempFile, new ReadOnlySpan(bytes), &bytesWritten, null)) + throw new Win32Exception(); if (bytesWritten != bytes.Length) - { - Windows.Win32.PInvoke.CloseHandle(tempFile); throw new Exception($"Could not write all bytes to temp file ({bytesWritten} of {bytes.Length})"); - } if (!Windows.Win32.PInvoke.FlushFileBuffers(tempFile)) - { - Windows.Win32.PInvoke.CloseHandle(tempFile); throw new Win32Exception(); - } - Windows.Win32.PInvoke.CloseHandle(tempFile); + tempFile.Close(); if (!Windows.Win32.PInvoke.MoveFileEx(tempPath, path, MOVE_FILE_FLAGS.MOVEFILE_REPLACE_EXISTING | MOVE_FILE_FLAGS.MOVEFILE_WRITE_THROUGH)) throw new Win32Exception(); diff --git a/Dalamud/Utility/Util.cs b/Dalamud/Utility/Util.cs index f50efcf0d..19610ef64 100644 --- a/Dalamud/Utility/Util.cs +++ b/Dalamud/Utility/Util.cs @@ -858,7 +858,7 @@ public static partial class Util var sizeWithTerminators = pathBytesSize + (pathBytes.Length * 2); var dropFilesSize = sizeof(DROPFILES); - var hGlobal = Win32_PInvoke.GlobalAlloc( + var hGlobal = Win32_PInvoke.GlobalAlloc_SafeHandle( GLOBAL_ALLOC_FLAGS.GHND, // struct size + size of encoded strings + null terminator for each // string + two null terminators for end of list @@ -896,11 +896,12 @@ public static partial class Util { Win32_PInvoke.SetClipboardData( (uint)CLIPBOARD_FORMAT.CF_HDROP, - (Windows.Win32.Foundation.HANDLE)hGlobal.Value); + hGlobal); Win32_PInvoke.CloseClipboard(); return true; } + hGlobal.Dispose(); return false; } diff --git a/DalamudCrashHandler/DalamudCrashHandler.cpp b/DalamudCrashHandler/DalamudCrashHandler.cpp index 1feec4b2f..ec7115ffd 100644 --- a/DalamudCrashHandler/DalamudCrashHandler.cpp +++ b/DalamudCrashHandler/DalamudCrashHandler.cpp @@ -119,7 +119,7 @@ std::wstring describe_module(const std::filesystem::path& path) { return std::format(L"", GetLastError()); UINT size = 0; - + std::wstring version = L"v?.?.?.?"; if (LPVOID lpBuffer; VerQueryValueW(block.data(), L"\\", &lpBuffer, &size)) { const auto& v = *static_cast(lpBuffer); @@ -176,7 +176,7 @@ const std::map& get_remote_modules() { std::vector buf(8192); for (size_t i = 0; i < 64; i++) { if (DWORD needed; !EnumProcessModules(g_hProcess, &buf[0], static_cast(std::span(buf).size_bytes()), &needed)) { - std::cerr << std::format("EnumProcessModules error: 0x{:x}", GetLastError()) << std::endl; + std::cerr << std::format("EnumProcessModules error: 0x{:x}", GetLastError()) << std::endl; break; } else if (needed > std::span(buf).size_bytes()) { buf.resize(needed / sizeof(HMODULE) + 16); @@ -201,7 +201,7 @@ const std::map& get_remote_modules() { data[hModule] = nth64.OptionalHeader.SizeOfImage; } - + return data; }(); @@ -292,43 +292,35 @@ std::wstring to_address_string(const DWORD64 address, const bool try_ptrderef = void print_exception_info(HANDLE hThread, const EXCEPTION_POINTERS& ex, const CONTEXT& ctx, std::wostringstream& log) { std::vector exRecs; - if (ex.ExceptionRecord) - { + if (ex.ExceptionRecord) { size_t rec_index = 0; size_t read; - + exRecs.emplace_back(); for (auto pRemoteExRec = ex.ExceptionRecord; - pRemoteExRec && rec_index < 64; - rec_index++) - { - exRecs.emplace_back(); - - if (!ReadProcessMemory(g_hProcess, pRemoteExRec, &exRecs.back(), sizeof exRecs.back(), &read) - || read < offsetof(EXCEPTION_RECORD, ExceptionInformation) - || read < static_cast(reinterpret_cast(&exRecs.back().ExceptionInformation[exRecs. - back().NumberParameters]) - reinterpret_cast(&exRecs.back()))) - { - exRecs.pop_back(); - break; - } + pRemoteExRec + && rec_index < 64 + && ReadProcessMemory(g_hProcess, pRemoteExRec, &exRecs.back(), sizeof exRecs.back(), &read) + && read >= offsetof(EXCEPTION_RECORD, ExceptionInformation) + && read >= static_cast(reinterpret_cast(&exRecs.back().ExceptionInformation[exRecs.back().NumberParameters]) - reinterpret_cast(&exRecs.back())); + rec_index++) { log << std::format(L"\nException Info #{}\n", rec_index); log << std::format(L"Address: {:X}\n", exRecs.back().ExceptionCode); log << std::format(L"Flags: {:X}\n", exRecs.back().ExceptionFlags); log << std::format(L"Address: {:X}\n", reinterpret_cast(exRecs.back().ExceptionAddress)); - if (exRecs.back().NumberParameters) - { - log << L"Parameters: "; - for (DWORD i = 0; i < exRecs.back().NumberParameters; ++i) - { - if (i != 0) - log << L", "; - log << std::format(L"{:X}", exRecs.back().ExceptionInformation[i]); - } + if (!exRecs.back().NumberParameters) + continue; + log << L"Parameters: "; + for (DWORD i = 0; i < exRecs.back().NumberParameters; ++i) { + if (i != 0) + log << L", "; + log << std::format(L"{:X}", exRecs.back().ExceptionInformation[i]); } pRemoteExRec = exRecs.back().ExceptionRecord; + exRecs.emplace_back(); } + exRecs.pop_back(); } log << L"\nCall Stack\n{"; @@ -418,7 +410,7 @@ void print_exception_info_extended(const EXCEPTION_POINTERS& ex, const CONTEXT& std::wstring escape_shell_arg(const std::wstring& arg) { // https://docs.microsoft.com/en-us/archive/blogs/twistylittlepassagesallalike/everyone-quotes-command-line-arguments-the-wrong-way - + std::wstring res; if (!arg.empty() && arg.find_first_of(L" \t\n\v\"") == std::wstring::npos) { res.append(arg); @@ -512,7 +504,7 @@ void export_tspack(HWND hWndParent, const std::filesystem::path& logDir, const s filePath.emplace(pFilePath); std::fstream fileStream(*filePath, std::ios::binary | std::ios::in | std::ios::out | std::ios::trunc); - + mz_zip_archive zipa{}; zipa.m_pIO_opaque = &fileStream; zipa.m_pRead = [](void* pOpaque, mz_uint64 file_ofs, void* pBuf, size_t n) -> size_t { @@ -574,7 +566,7 @@ void export_tspack(HWND hWndParent, const std::filesystem::path& logDir, const s const auto hLogFile = CreateFileW(logFilePath.c_str(), GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, nullptr); if (hLogFile == INVALID_HANDLE_VALUE) throw_last_error(std::format("indiv. log file: CreateFileW({})", ws_to_u8(logFilePath.wstring()))); - + std::unique_ptr hLogFileClose(hLogFile, &CloseHandle); LARGE_INTEGER size, baseOffset{}; @@ -703,7 +695,7 @@ int main() { // IFileSaveDialog only works on STA CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); - + std::vector args; if (int argc = 0; const auto argv = CommandLineToArgvW(GetCommandLineW(), &argc)) { for (auto i = 0; i < argc; i++) @@ -831,14 +823,14 @@ int main() { hr = pOleWindow->GetWindow(&hwndProgressDialog); if (SUCCEEDED(hr)) { - SetWindowPos(hwndProgressDialog, HWND_TOPMOST, 0, 0, 0, 0, + SetWindowPos(hwndProgressDialog, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW); SetForegroundWindow(hwndProgressDialog); } - + pOleWindow->Release(); } - + } else { std::cerr << "Failed to create progress window" << std::endl; @@ -860,14 +852,14 @@ int main() { https://github.com/sumatrapdfreader/sumatrapdf/blob/master/src/utils/DbgHelpDyn.cpp */ - + if (g_bSymbolsAvailable) { SymRefreshModuleList(g_hProcess); } else if(!assetDir.empty()) { auto symbol_search_path = std::format(L".;{}", (assetDir / "UIRes" / "pdb").wstring()); - + g_bSymbolsAvailable = SymInitializeW(g_hProcess, symbol_search_path.c_str(), true); std::wcout << std::format(L"Init symbols with PDB at {}", symbol_search_path) << std::endl; @@ -878,12 +870,12 @@ int main() { g_bSymbolsAvailable = SymInitializeW(g_hProcess, nullptr, true); std::cout << "Init symbols without PDB" << std::endl; } - + if (!g_bSymbolsAvailable) { std::wcerr << std::format(L"SymInitialize error: 0x{:x}", GetLastError()) << std::endl; } - if (pProgressDialog) + if (pProgressDialog) pProgressDialog->SetLine(3, L"Reading troubleshooting data", FALSE, NULL); std::wstring stackTrace(exinfo.dwStackTraceLength, L'\0'); @@ -938,23 +930,13 @@ int main() { } while (false); } - const bool is_external_event = exinfo.ExceptionRecord.ExceptionCode == CUSTOM_EXCEPTION_EXTERNAL_EVENT; - std::wostringstream log; - - if (!is_external_event) - { - log << std::format(L"Unhandled native exception occurred at {}", to_address_string(exinfo.ContextRecord.Rip, false)) << std::endl; - log << std::format(L"Code: {:X}", exinfo.ExceptionRecord.ExceptionCode) << std::endl; - } - else - { - log << L"CLR error occurred" << std::endl; - } + log << std::format(L"Unhandled native exception occurred at {}", to_address_string(exinfo.ContextRecord.Rip, false)) << std::endl; + log << std::format(L"Code: {:X}", exinfo.ExceptionRecord.ExceptionCode) << std::endl; if (shutup) log << L"======= Crash handler was globally muted(shutdown?) =======" << std::endl; - + if (dumpPath.empty()) log << L"Dump skipped" << std::endl; else if (dumpError.empty()) @@ -967,19 +949,9 @@ int main() { if (pProgressDialog) pProgressDialog->SetLine(3, L"Refreshing Module List", FALSE, NULL); - std::wstring window_log_str; - - // Cut the log here for external events, the rest is unreadable and doesn't matter since we can't get - // symbols for mixed-mode stacks yet. - if (is_external_event) - window_log_str = log.str(); - SymRefreshModuleList(GetCurrentProcess()); print_exception_info(exinfo.hThreadHandle, exinfo.ExceptionPointers, exinfo.ContextRecord, log); - - if (!is_external_event) - window_log_str = log.str(); - + const auto window_log_str = log.str(); print_exception_info_extended(exinfo.ExceptionPointers, exinfo.ContextRecord, log); std::wofstream(logPath) << log.str(); @@ -1031,7 +1003,7 @@ int main() { R"aa(Help | Open log directory | Open log file)aa" ); #endif - + // Can't do this, xiv stops pumping messages here //config.hwndParent = FindWindowA("FFXIVGAME", NULL); @@ -1084,13 +1056,13 @@ int main() { return (*reinterpret_cast(dwRefData))(hwnd, uNotification, wParam, lParam); }; config.lpCallbackData = reinterpret_cast(&callback); - + if (pProgressDialog) { pProgressDialog->StopProgressDialog(); pProgressDialog->Release(); pProgressDialog = NULL; } - + const auto kill_game = [&] { TerminateProcess(g_hProcess, exinfo.ExceptionRecord.ExceptionCode); }; if (shutup) { diff --git a/Directory.Packages.props b/Directory.Packages.props index 58e355400..6c5070d35 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -27,7 +27,7 @@ - +