From a0022f63583c9e64aaa1b1fbb5b901b8659c2b79 Mon Sep 17 00:00:00 2001 From: goat <16760685+goaaats@users.noreply.github.com> Date: Fri, 15 Jul 2022 19:03:42 +0200 Subject: [PATCH 01/58] feat: add EffectiveVersion attrib to LocalPluginManifest --- Dalamud/Plugin/Internal/Types/LocalPluginManifest.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Dalamud/Plugin/Internal/Types/LocalPluginManifest.cs b/Dalamud/Plugin/Internal/Types/LocalPluginManifest.cs index 0853b19be..a3d89fd3b 100644 --- a/Dalamud/Plugin/Internal/Types/LocalPluginManifest.cs +++ b/Dalamud/Plugin/Internal/Types/LocalPluginManifest.cs @@ -1,3 +1,4 @@ +using System; using System.IO; using Dalamud.Utility; @@ -36,6 +37,11 @@ internal record LocalPluginManifest : PluginManifest /// public bool IsThirdParty => !this.InstalledFromUrl.IsNullOrEmpty() && this.InstalledFromUrl != PluginRepository.MainRepoUrl; + /// + /// Gets the effective version of this plugin. + /// + public Version EffectiveVersion => this.Testing && this.TestingAssemblyVersion != null ? this.TestingAssemblyVersion : this.AssemblyVersion; + /// /// Save a plugin manifest to file. /// From 465ae1857d0316fbd0caa85a3fbae69100cb0fd5 Mon Sep 17 00:00:00 2001 From: goat <16760685+goaaats@users.noreply.github.com> Date: Fri, 15 Jul 2022 19:04:23 +0200 Subject: [PATCH 02/58] feat: don't remove plugins from installer when disabling, always draw delete button --- .../PluginInstaller/PluginInstallerWindow.cs | 33 ++++++++++++------- 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/Dalamud/Interface/Internal/Windows/PluginInstaller/PluginInstallerWindow.cs b/Dalamud/Interface/Internal/Windows/PluginInstaller/PluginInstallerWindow.cs index c670c9fa9..2bfbf8e08 100644 --- a/Dalamud/Interface/Internal/Windows/PluginInstaller/PluginInstallerWindow.cs +++ b/Dalamud/Interface/Internal/Windows/PluginInstaller/PluginInstallerWindow.cs @@ -1616,11 +1616,7 @@ namespace Dalamud.Interface.Internal.Windows.PluginInstaller this.DrawUpdateSinglePluginButton(availablePluginUpdate); ImGui.SameLine(); - var version = plugin.AssemblyName?.Version; - version ??= plugin.Manifest.Testing - ? plugin.Manifest.TestingAssemblyVersion - : plugin.Manifest.AssemblyVersion; - ImGui.TextColored(ImGuiColors.DalamudGrey3, $" v{version}"); + ImGui.TextColored(ImGuiColors.DalamudGrey3, $" v{plugin.Manifest.EffectiveVersion}"); if (plugin.IsDev) { @@ -1637,7 +1633,7 @@ namespace Dalamud.Interface.Internal.Windows.PluginInstaller if (hasChangelog) { - if (ImGui.TreeNode($"Changelog (v{plugin.Manifest.AssemblyVersion})")) + if (ImGui.TreeNode($"Changelog (v{plugin.Manifest.EffectiveVersion})")) { this.DrawInstalledPluginChangelog(plugin.Manifest); } @@ -1752,10 +1748,10 @@ namespace Dalamud.Interface.Internal.Windows.PluginInstaller if (!disableTask.Result) return; - if (!plugin.IsDev) + /*if (!plugin.IsDev) { pluginManager.RemovePlugin(plugin); - } + }*/ notifications.AddNotification(Locs.Notifications_PluginDisabled(plugin.Manifest.Name), Locs.Notifications_PluginDisabledTitle, NotificationType.Success); }); @@ -1931,18 +1927,29 @@ namespace Dalamud.Interface.Internal.Windows.PluginInstaller private void DrawDeletePluginButton(LocalPlugin plugin) { - var unloaded = plugin.State == PluginState.Unloaded || plugin.State == PluginState.LoadError; + /*var unloaded = plugin.State == PluginState.Unloaded || plugin.State == PluginState.LoadError; // When policy check fails, the plugin is never loaded var showButton = unloaded && (plugin.IsDev || plugin.IsOutdated || plugin.IsBanned || plugin.IsOrphaned || !plugin.CheckPolicy()); if (!showButton) - return; + return;*/ var pluginManager = Service.Get(); ImGui.SameLine(); - if (plugin.HasEverStartedLoad) + if (plugin.State != PluginState.Unloaded && plugin.State != PluginState.LoadError) + { + ImGui.PushFont(InterfaceManager.IconFont); + ImGuiComponents.DisabledButton(FontAwesomeIcon.TrashAlt.ToIconString()); + ImGui.PopFont(); + + if (ImGui.IsItemHovered()) + { + ImGui.SetTooltip(Locs.PluginButtonToolTip_DeletePluginLoaded); + } + } + else if (plugin.HasEverStartedLoad) { ImGui.PushFont(InterfaceManager.IconFont); ImGuiComponents.DisabledButton(FontAwesomeIcon.TrashAlt.ToIconString()); @@ -2384,7 +2391,9 @@ namespace Dalamud.Interface.Internal.Windows.PluginInstaller public static string PluginButtonToolTip_DeletePlugin => Loc.Localize("InstallerDeletePlugin ", "Delete plugin"); - public static string PluginButtonToolTip_DeletePluginRestricted => Loc.Localize("InstallerDeletePluginRestricted", "Cannot delete - please try restarting the game."); + public static string PluginButtonToolTip_DeletePluginRestricted => Loc.Localize("InstallerDeletePluginRestricted", "Cannot delete right now - please restart the game."); + + public static string PluginButtonToolTip_DeletePluginLoaded => Loc.Localize("InstallerDeletePluginLoaded", "Disable this plugin before deleting it."); public static string PluginButtonToolTip_VisitPluginUrl => Loc.Localize("InstallerVisitPluginUrl", "Visit plugin URL"); From 5e31537a0ca04ce0144350fbe1019984e3123c0c Mon Sep 17 00:00:00 2001 From: goat <16760685+goaaats@users.noreply.github.com> Date: Fri, 15 Jul 2022 19:05:30 +0200 Subject: [PATCH 03/58] fix: only load latest version of a plugin, don't automatically re-enable plugins --- Dalamud/Plugin/Internal/PluginManager.cs | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/Dalamud/Plugin/Internal/PluginManager.cs b/Dalamud/Plugin/Internal/PluginManager.cs index 4569a1d07..f44c4ff9d 100644 --- a/Dalamud/Plugin/Internal/PluginManager.cs +++ b/Dalamud/Plugin/Internal/PluginManager.cs @@ -307,6 +307,7 @@ internal partial class PluginManager : IDisposable, IServiceType // Add installed plugins. These are expected to be in a specific format so we can look for exactly that. foreach (var pluginDir in this.pluginDirectory.GetDirectories()) { + var versionsDefs = new List(); foreach (var versionDir in pluginDir.GetDirectories()) { var dllFile = new FileInfo(Path.Combine(versionDir.FullName, $"{pluginDir.Name}.dll")); @@ -317,7 +318,16 @@ internal partial class PluginManager : IDisposable, IServiceType var manifest = LocalPluginManifest.Load(manifestFile); - pluginDefs.Add(new PluginDef(dllFile, manifest, false)); + versionsDefs.Add(new PluginDef(dllFile, manifest, false)); + } + + try + { + pluginDefs.Add(versionsDefs.OrderByDescending(x => x.Manifest!.EffectiveVersion).First()); + } + catch (Exception ex) + { + Log.Error(ex, "Couldn't choose best version for plugin: {Name}", pluginDir.Name); } } @@ -712,10 +722,14 @@ internal partial class PluginManager : IDisposable, IServiceType { try { - if (plugin.IsDisabled) - plugin.Enable(); - - await plugin.LoadAsync(reason); + if (!plugin.IsDisabled) + { + await plugin.LoadAsync(reason); + } + else + { + Log.Verbose($"{name} was disabled"); + } } catch (InvalidPluginException) { From 1c581d4be3f5827e34c4459245185900d5270176 Mon Sep 17 00:00:00 2001 From: goat <16760685+goaaats@users.noreply.github.com> Date: Fri, 15 Jul 2022 19:16:48 +0200 Subject: [PATCH 04/58] fix: handle broken sigs file gracefully --- Dalamud/Game/SigScanner.cs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/Dalamud/Game/SigScanner.cs b/Dalamud/Game/SigScanner.cs index 0ccc445f6..15a0f6116 100644 --- a/Dalamud/Game/SigScanner.cs +++ b/Dalamud/Game/SigScanner.cs @@ -532,7 +532,17 @@ namespace Dalamud.Game return; } - this.textCache = JsonConvert.DeserializeObject>(File.ReadAllText(this.cacheFile.FullName)) ?? new ConcurrentDictionary(); + try + { + this.textCache = + JsonConvert.DeserializeObject>( + File.ReadAllText(this.cacheFile.FullName)) ?? new ConcurrentDictionary(); + } + catch (Exception ex) + { + this.textCache = new ConcurrentDictionary(); + Log.Error(ex, "Couldn't load cached sigs"); + } } } } From b4808be46292b1bb07c1a0630a85e40ba9df872a Mon Sep 17 00:00:00 2001 From: goat <16760685+goaaats@users.noreply.github.com> Date: Fri, 15 Jul 2022 19:24:49 +0200 Subject: [PATCH 05/58] chore: ignore HasEverStartedLoad check for devplugins --- .../Internal/Windows/PluginInstaller/PluginInstallerWindow.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dalamud/Interface/Internal/Windows/PluginInstaller/PluginInstallerWindow.cs b/Dalamud/Interface/Internal/Windows/PluginInstaller/PluginInstallerWindow.cs index 2bfbf8e08..f68c3a23b 100644 --- a/Dalamud/Interface/Internal/Windows/PluginInstaller/PluginInstallerWindow.cs +++ b/Dalamud/Interface/Internal/Windows/PluginInstaller/PluginInstallerWindow.cs @@ -1949,7 +1949,7 @@ namespace Dalamud.Interface.Internal.Windows.PluginInstaller ImGui.SetTooltip(Locs.PluginButtonToolTip_DeletePluginLoaded); } } - else if (plugin.HasEverStartedLoad) + else if (plugin.HasEverStartedLoad && !plugin.IsDev) { ImGui.PushFont(InterfaceManager.IconFont); ImGuiComponents.DisabledButton(FontAwesomeIcon.TrashAlt.ToIconString()); From e5fc5dac3e66a984d2cab1302704009169cc918d Mon Sep 17 00:00:00 2001 From: liam <6005409+lmcintyre@users.noreply.github.com> Date: Fri, 15 Jul 2022 13:59:16 -0400 Subject: [PATCH 06/58] Add VirtualKey -> ImGuiKey helper method to ImGuiHelpers (#917) Co-authored-by: goat --- Dalamud/Interface/ImGuiHelpers.cs | 22 ++++++++++++++++++++++ lib/ImGuiScene | 2 +- 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/Dalamud/Interface/ImGuiHelpers.cs b/Dalamud/Interface/ImGuiHelpers.cs index 3e39e9429..67c0c687d 100644 --- a/Dalamud/Interface/ImGuiHelpers.cs +++ b/Dalamud/Interface/ImGuiHelpers.cs @@ -4,7 +4,9 @@ using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Numerics; +using Dalamud.Game.ClientState.Keys; using ImGuiNET; +using ImGuiScene; namespace Dalamud.Interface { @@ -215,6 +217,26 @@ namespace Dalamud.Interface target.Value!.BuildLookupTable(); } + /// + /// Map a VirtualKey keycode to an ImGuiKey enum value. + /// + /// The VirtualKey value to retrieve the ImGuiKey counterpart for. + /// The ImGuiKey that corresponds to this VirtualKey, or ImGuiKey.None otherwise. + public static ImGuiKey VirtualKeyToImGuiKey(VirtualKey key) + { + return ImGui_Input_Impl_Direct.VirtualKeyToImGuiKey((int)key); + } + + /// + /// Map an ImGuiKey enum value to a VirtualKey code. + /// + /// The ImGuiKey value to retrieve the VirtualKey counterpart for. + /// The VirtualKey that corresponds to this ImGuiKey, or VirtualKey.NO_KEY otherwise. + public static VirtualKey ImGuiKeyToVirtualKey(ImGuiKey key) + { + return (VirtualKey)ImGui_Input_Impl_Direct.ImGuiKeyToVirtualKey(key); + } + /// /// Get data needed for each new frame. /// diff --git a/lib/ImGuiScene b/lib/ImGuiScene index 1e4c06b36..5dca91763 160000 --- a/lib/ImGuiScene +++ b/lib/ImGuiScene @@ -1 +1 @@ -Subproject commit 1e4c06b36013efb367c8c57c7f724a30f67a3973 +Subproject commit 5dca917630078e807ecabf5b33ccb8863655d7a4 From 46c2511c841ae8bd83c65d04f42dc26aca517cff Mon Sep 17 00:00:00 2001 From: goat <16760685+goaaats@users.noreply.github.com> Date: Sat, 16 Jul 2022 00:13:07 +0200 Subject: [PATCH 07/58] fix: don't try to disable plugin during update if it already has been --- Dalamud/Plugin/Internal/PluginManager.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Dalamud/Plugin/Internal/PluginManager.cs b/Dalamud/Plugin/Internal/PluginManager.cs index f44c4ff9d..22dcbed7a 100644 --- a/Dalamud/Plugin/Internal/PluginManager.cs +++ b/Dalamud/Plugin/Internal/PluginManager.cs @@ -968,7 +968,9 @@ internal partial class PluginManager : IDisposable, IServiceType { try { - plugin.Disable(); + if (!plugin.IsDisabled) + plugin.Disable(); + lock (this.pluginListLock) { this.InstalledPlugins = this.InstalledPlugins.Remove(plugin); From 30b1a4d383b86dea0b6f3ca90258e2d3f000d753 Mon Sep 17 00:00:00 2001 From: goat <16760685+goaaats@users.noreply.github.com> Date: Sat, 16 Jul 2022 00:18:34 +0200 Subject: [PATCH 08/58] feat: ImGuiHelpers.Center helpers, toggle switch component --- .../ImGuiComponents.ToggleSwitch.cs | 72 +++++++++++++++++++ Dalamud/Interface/ImGuiHelpers.cs | 30 ++++++++ 2 files changed, 102 insertions(+) create mode 100644 Dalamud/Interface/Components/ImGuiComponents.ToggleSwitch.cs diff --git a/Dalamud/Interface/Components/ImGuiComponents.ToggleSwitch.cs b/Dalamud/Interface/Components/ImGuiComponents.ToggleSwitch.cs new file mode 100644 index 000000000..83c238c06 --- /dev/null +++ b/Dalamud/Interface/Components/ImGuiComponents.ToggleSwitch.cs @@ -0,0 +1,72 @@ +using System.Numerics; + +using ImGuiNET; + +namespace Dalamud.Interface.Components +{ + /// + /// Component for toggle buttons. + /// + public static partial class ImGuiComponents + { + /// + /// Draw a toggle button. + /// + /// The id of the button. + /// The state of the switch. + /// If the button has been interacted with this frame. + public static bool ToggleButton(string id, ref bool v) + { + var colors = ImGui.GetStyle().Colors; + var p = ImGui.GetCursorScreenPos(); + var drawList = ImGui.GetWindowDrawList(); + + var height = ImGui.GetFrameHeight(); + var width = height * 1.55f; + var radius = height * 0.50f; + + // TODO: animate + + var changed = false; + ImGui.InvisibleButton(id, new Vector2(width, height)); + if (ImGui.IsItemClicked()) + { + v = !v; + changed = true; + } + + if (ImGui.IsItemHovered()) + drawList.AddRectFilled(p, new Vector2(p.X + width, p.Y + height), ImGui.GetColorU32(v ? colors[(int)ImGuiCol.ButtonActive] : new Vector4(0.78f, 0.78f, 0.78f, 1.0f)), height * 0.5f); + else + drawList.AddRectFilled(p, new Vector2(p.X + width, p.Y + height), ImGui.GetColorU32(v ? colors[(int)ImGuiCol.Button] : new Vector4(0.55f, 0.55f, 0.55f, 1.0f)), height * 0.50f); + drawList.AddCircleFilled(new Vector2(p.X + radius + ((v ? 1 : 0) * (width - (radius * 2.0f))), p.Y + radius), radius - 1.5f, ImGui.ColorConvertFloat4ToU32(new Vector4(255, 255, 255, 255))); + + return changed; + } + + /// + /// Draw a disabled toggle button. + /// + /// The id of the button. + /// The state of the switch. + public static void DisabledToggleButton(string id, bool v) + { + var colors = ImGui.GetStyle().Colors; + var p = ImGui.GetCursorScreenPos(); + var drawList = ImGui.GetWindowDrawList(); + + var height = ImGui.GetFrameHeight(); + var width = height * 1.55f; + var radius = height * 0.50f; + + // TODO: animate + // TODO: dim colors + ImGui.InvisibleButton(id, new Vector2(width, height)); + + var dimFactor = 0.5f; + + drawList.AddRectFilled(p, new Vector2(p.X + width, p.Y + height), ImGui.GetColorU32(v ? colors[(int)ImGuiCol.Button] * dimFactor : new Vector4(0.55f, 0.55f, 0.55f, 1.0f) * dimFactor), height * 0.50f); + drawList.AddCircleFilled(new Vector2(p.X + radius + ((v ? 1 : 0) * (width - (radius * 2.0f))), p.Y + radius), radius - 1.5f, ImGui.ColorConvertFloat4ToU32(new Vector4(255, 255, 255, 255) * dimFactor)); + } + } +} diff --git a/Dalamud/Interface/ImGuiHelpers.cs b/Dalamud/Interface/ImGuiHelpers.cs index 67c0c687d..8aaf860dc 100644 --- a/Dalamud/Interface/ImGuiHelpers.cs +++ b/Dalamud/Interface/ImGuiHelpers.cs @@ -237,6 +237,36 @@ namespace Dalamud.Interface return (VirtualKey)ImGui_Input_Impl_Direct.ImGuiKeyToVirtualKey(key); } + /// + /// Show centered text. + /// + /// Text to show. + public static void CenteredText(string text) + { + CenterCursorForText(text); + ImGui.TextUnformatted(text); + } + + /// + /// Center the ImGui cursor for a certain text. + /// + /// The text to center for. + public static void CenterCursorForText(string text) + { + var textWidth = ImGui.CalcTextSize(text).X; + CenterCursorFor((int)textWidth); + } + + /// + /// Center the ImGui cursor for an item with a certain width. + /// + /// The width to center for. + public static void CenterCursorFor(int itemWidth) + { + var window = (int)ImGui.GetWindowWidth(); + ImGui.SetCursorPosX((window / 2) - (itemWidth / 2)); + } + /// /// Get data needed for each new frame. /// From 8863eb9a20a5b7f7c604de53a9978064e8ae514d Mon Sep 17 00:00:00 2001 From: goat <16760685+goaaats@users.noreply.github.com> Date: Sat, 16 Jul 2022 00:19:04 +0200 Subject: [PATCH 09/58] chore: better UI for disabled plugins, plugin toggles --- .../Internal/Windows/PluginImageCache.cs | 9 + .../PluginInstaller/PluginInstallerWindow.cs | 199 +++++++++++++----- 2 files changed, 159 insertions(+), 49 deletions(-) diff --git a/Dalamud/Interface/Internal/Windows/PluginImageCache.cs b/Dalamud/Interface/Internal/Windows/PluginImageCache.cs index bea3bdd77..e8826e333 100644 --- a/Dalamud/Interface/Internal/Windows/PluginImageCache.cs +++ b/Dalamud/Interface/Internal/Windows/PluginImageCache.cs @@ -54,6 +54,7 @@ namespace Dalamud.Interface.Internal.Windows private readonly ConcurrentDictionary pluginImagesMap = new(); private readonly Task emptyTextureTask; + private readonly Task disabledIconTask; private readonly Task defaultIconTask; private readonly Task troubleIconTask; private readonly Task updateIconTask; @@ -71,6 +72,7 @@ namespace Dalamud.Interface.Internal.Windows this.emptyTextureTask = imwst.ContinueWith(task => task.Result.Manager.LoadImageRaw(new byte[64], 8, 8, 4)!); this.defaultIconTask = imwst.ContinueWith(task => TaskWrapIfNonNull(task.Result.Manager.LoadImage(Path.Combine(dalamud.AssetDirectory.FullName, "UIRes", "defaultIcon.png"))) ?? this.emptyTextureTask).Unwrap(); + this.disabledIconTask = imwst.ContinueWith(task => TaskWrapIfNonNull(task.Result.Manager.LoadImage(Path.Combine(dalamud.AssetDirectory.FullName, "UIRes", "disabledIcon.png"))) ?? this.emptyTextureTask).Unwrap(); this.troubleIconTask = imwst.ContinueWith(task => TaskWrapIfNonNull(task.Result.Manager.LoadImage(Path.Combine(dalamud.AssetDirectory.FullName, "UIRes", "troubleIcon.png"))) ?? this.emptyTextureTask).Unwrap(); this.updateIconTask = imwst.ContinueWith(task => TaskWrapIfNonNull(task.Result.Manager.LoadImage(Path.Combine(dalamud.AssetDirectory.FullName, "UIRes", "updateIcon.png"))) ?? this.emptyTextureTask).Unwrap(); this.installedIconTask = imwst.ContinueWith(task => TaskWrapIfNonNull(task.Result.Manager.LoadImage(Path.Combine(dalamud.AssetDirectory.FullName, "UIRes", "installedIcon.png"))) ?? this.emptyTextureTask).Unwrap(); @@ -91,6 +93,13 @@ namespace Dalamud.Interface.Internal.Windows ? this.emptyTextureTask.Result : this.emptyTextureTask.GetAwaiter().GetResult(); + /// + /// Gets the default plugin icon. + /// + public TextureWrap DisabledIcon => this.disabledIconTask.IsCompleted + ? this.disabledIconTask.Result + : this.disabledIconTask.GetAwaiter().GetResult(); + /// /// Gets the default plugin icon. /// diff --git a/Dalamud/Interface/Internal/Windows/PluginInstaller/PluginInstallerWindow.cs b/Dalamud/Interface/Internal/Windows/PluginInstaller/PluginInstallerWindow.cs index f68c3a23b..ad4a2d41a 100644 --- a/Dalamud/Interface/Internal/Windows/PluginInstaller/PluginInstallerWindow.cs +++ b/Dalamud/Interface/Internal/Windows/PluginInstaller/PluginInstallerWindow.cs @@ -83,6 +83,9 @@ namespace Dalamud.Interface.Internal.Windows.PluginInstaller private OperationStatus installStatus = OperationStatus.Idle; private OperationStatus updateStatus = OperationStatus.Idle; + private OperationStatus enableDisableStatus = OperationStatus.Idle; + + private LoadingIndicatorKind loadingIndicatorKind = LoadingIndicatorKind.Unknown; /// /// Initializes a new instance of the class. @@ -130,6 +133,17 @@ namespace Dalamud.Interface.Internal.Windows.PluginInstaller Complete, } + private enum LoadingIndicatorKind + { + Unknown, + EnablingSingle, + DisablingSingle, + UpdatingSingle, + UpdatingAll, + Installing, + Manager, + } + private enum PluginSortKind { Alphabetical, @@ -139,6 +153,10 @@ namespace Dalamud.Interface.Internal.Windows.PluginInstaller NotInstalled, } + private bool AnyOperationInProgress => this.installStatus == OperationStatus.InProgress || + this.updateStatus == OperationStatus.InProgress || + this.enableDisableStatus == OperationStatus.InProgress; + /// public void Dispose() { @@ -184,6 +202,7 @@ namespace Dalamud.Interface.Internal.Windows.PluginInstaller this.DrawFooter(); this.DrawErrorModal(); this.DrawFeedbackModal(); + this.DrawProgressOverlay(); } /// @@ -194,6 +213,69 @@ namespace Dalamud.Interface.Internal.Windows.PluginInstaller this.imageCache.ClearIconCache(); } + private void DrawProgressOverlay() + { + var pluginManager = Service.Get(); + + var isWaitingManager = !pluginManager.PluginsReady || + !pluginManager.ReposReady; + var isLoading = this.AnyOperationInProgress || + isWaitingManager; + + if (isWaitingManager) + this.loadingIndicatorKind = LoadingIndicatorKind.Manager; + + if (!isLoading) + return; + + ImGui.SetCursorPos(Vector2.Zero); + + var windowSize = ImGui.GetWindowSize(); + + ImGui.PushStyleVar(ImGuiStyleVar.WindowPadding, Vector2.Zero); + ImGui.PushStyleVar(ImGuiStyleVar.FramePadding, Vector2.Zero); + ImGui.PushStyleVar(ImGuiStyleVar.CellPadding, Vector2.Zero);; + ImGui.PushStyleVar(ImGuiStyleVar.ChildBorderSize, 0); + ImGui.PushStyleVar(ImGuiStyleVar.ChildRounding, 0); + + ImGui.SetNextWindowBgAlpha(0.8f); + if (ImGui.BeginChild("###installerLoadingFrame", new Vector2(-1, -1), false)) + { + ImGui.SetCursorPosY(windowSize.Y / 2); + + switch (this.loadingIndicatorKind) + { + case LoadingIndicatorKind.Unknown: + ImGuiHelpers.CenteredText("Doing something, not sure what!"); + break; + case LoadingIndicatorKind.EnablingSingle: + ImGuiHelpers.CenteredText("Enabling plugin..."); + break; + case LoadingIndicatorKind.DisablingSingle: + ImGuiHelpers.CenteredText("Disabling plugin..."); + break; + case LoadingIndicatorKind.UpdatingSingle: + ImGuiHelpers.CenteredText("Updating plugin..."); + break; + case LoadingIndicatorKind.UpdatingAll: + ImGuiHelpers.CenteredText("Updating plugins..."); + break; + case LoadingIndicatorKind.Installing: + ImGuiHelpers.CenteredText("Installing plugin..."); + break; + case LoadingIndicatorKind.Manager: + ImGuiHelpers.CenteredText("Loading repositories and plugins..."); + break; + default: + throw new ArgumentOutOfRangeException(); + } + + ImGui.EndChild(); + } + + ImGui.PopStyleVar(5); + } + private void DrawHeader() { var style = ImGui.GetStyle(); @@ -336,6 +418,7 @@ namespace Dalamud.Interface.Internal.Windows.PluginInstaller if (ImGui.Button(Locs.FooterButton_UpdatePlugins)) { this.updateStatus = OperationStatus.InProgress; + this.loadingIndicatorKind = LoadingIndicatorKind.UpdatingAll; Task.Run(() => pluginManager.UpdatePluginsAsync()) .ContinueWith(task => @@ -1104,6 +1187,8 @@ namespace Dalamud.Interface.Internal.Windows.PluginInstaller ImGui.SetCursorPos(startCursor); + var pluginDisabled = plugin is { IsDisabled: true }; + var iconSize = ImGuiHelpers.ScaledVector2(64, 64); var cursorBeforeImage = ImGui.GetCursorPos(); var rectOffset = ImGui.GetWindowContentRegionMin() + ImGui.GetWindowPos(); @@ -1116,7 +1201,18 @@ namespace Dalamud.Interface.Internal.Windows.PluginInstaller iconTex = cachedIconTex; } + if (pluginDisabled) + { + ImGui.PushStyleVar(ImGuiStyleVar.Alpha, 0.5f); + } + ImGui.Image(iconTex.ImGuiHandle, iconSize); + + if (pluginDisabled) + { + ImGui.PopStyleVar(); + } + ImGui.SameLine(); ImGui.SetCursorPos(cursorBeforeImage); } @@ -1125,8 +1221,10 @@ namespace Dalamud.Interface.Internal.Windows.PluginInstaller if (updateAvailable) ImGui.Image(this.imageCache.UpdateIcon.ImGuiHandle, iconSize); - else if (trouble) + else if (trouble && !pluginDisabled) ImGui.Image(this.imageCache.TroubleIcon.ImGuiHandle, iconSize); + else if (pluginDisabled) + ImGui.Image(this.imageCache.DisabledIcon.ImGuiHandle, iconSize); else if (isLoaded && isThirdParty) ImGui.Image(this.imageCache.ThirdInstalledIcon.ImGuiHandle, iconSize); else if (isThirdParty) @@ -1357,6 +1455,7 @@ namespace Dalamud.Interface.Internal.Windows.PluginInstaller if (ImGui.Button($"{buttonText}##{buttonText}{index}")) { this.installStatus = OperationStatus.InProgress; + this.loadingIndicatorKind = LoadingIndicatorKind.Installing; Task.Run(() => pluginManager.InstallPluginAsync(manifest, useTesting, PluginLoadReason.Installer)) .ContinueWith(task => @@ -1450,8 +1549,6 @@ namespace Dalamud.Interface.Internal.Windows.PluginInstaller { var configuration = Service.Get(); var commandManager = Service.Get(); - var pluginManager = Service.Get(); - var startInfo = Service.Get(); var trouble = false; @@ -1697,10 +1794,8 @@ namespace Dalamud.Interface.Internal.Windows.PluginInstaller private void DrawPluginControlButton(LocalPlugin plugin) { - var configuration = Service.Get(); var notifications = Service.Get(); var pluginManager = Service.Get(); - var startInfo = Service.Get(); // Disable everything if the updater is running or another plugin is operating var disabled = this.updateStatus == OperationStatus.InProgress || this.installStatus == OperationStatus.InProgress; @@ -1714,91 +1809,90 @@ namespace Dalamud.Interface.Internal.Windows.PluginInstaller // Disable everything if the plugin failed to load disabled = disabled || plugin.State == PluginState.LoadError || plugin.State == PluginState.DependencyResolutionFailed; - if (plugin.State == PluginState.Loading || plugin.State == PluginState.Unloading) + // Disable everything if we're working + disabled = disabled || plugin.State == PluginState.Loading || plugin.State == PluginState.Unloading; + + var toggleId = plugin.Manifest.InternalName; + var isLoadedAndUnloadable = plugin.State == PluginState.Loaded || + plugin.State == PluginState.DependencyResolutionFailed; + + if (plugin.State == PluginState.UnloadError) { - ImGuiComponents.DisabledButton(Locs.PluginButton_Working); + ImGuiComponents.DisabledButton(FontAwesomeIcon.Frown); + + if (ImGui.IsItemHovered()) + ImGui.SetTooltip(Locs.PluginButtonToolTip_UnloadFailed); } - else if (plugin.State == PluginState.Loaded || plugin.State == PluginState.LoadError || plugin.State == PluginState.DependencyResolutionFailed) + else if (disabled) { - if (pluginManager.SafeMode) + ImGuiComponents.DisabledToggleButton(toggleId, isLoadedAndUnloadable); + } + else + { + if (ImGuiComponents.ToggleButton(toggleId, ref isLoadedAndUnloadable)) { - ImGuiComponents.DisabledButton(Locs.PluginButton_SafeMode); - } - else if (disabled) - { - ImGuiComponents.DisabledButton(Locs.PluginButton_Disable); - } - else - { - if (ImGui.Button(Locs.PluginButton_Disable)) + if (!isLoadedAndUnloadable) { + this.enableDisableStatus = OperationStatus.InProgress; + this.loadingIndicatorKind = LoadingIndicatorKind.DisablingSingle; + Task.Run(() => { var unloadTask = Task.Run(() => plugin.UnloadAsync()) - .ContinueWith(this.DisplayErrorContinuation, Locs.ErrorModal_UnloadFail(plugin.Name)); + .ContinueWith(this.DisplayErrorContinuation, Locs.ErrorModal_UnloadFail(plugin.Name)); unloadTask.Wait(); if (!unloadTask.Result) return; var disableTask = Task.Run(() => plugin.Disable()) - .ContinueWith(this.DisplayErrorContinuation, Locs.ErrorModal_DisableFail(plugin.Name)); + .ContinueWith(this.DisplayErrorContinuation, Locs.ErrorModal_DisableFail(plugin.Name)); disableTask.Wait(); + this.enableDisableStatus = OperationStatus.Complete; + if (!disableTask.Result) return; - /*if (!plugin.IsDev) - { - pluginManager.RemovePlugin(plugin); - }*/ - notifications.AddNotification(Locs.Notifications_PluginDisabled(plugin.Manifest.Name), Locs.Notifications_PluginDisabledTitle, NotificationType.Success); }); } - } - - if (plugin.State == PluginState.Loaded) - { - // Only if the plugin isn't broken. - this.DrawOpenPluginSettingsButton(plugin); - } - } - else if (plugin.State == PluginState.Unloaded) - { - if (disabled) - { - ImGuiComponents.DisabledButton(Locs.PluginButton_Load); - } - else - { - if (ImGui.Button(Locs.PluginButton_Load)) + else { + this.enableDisableStatus = OperationStatus.InProgress; + this.loadingIndicatorKind = LoadingIndicatorKind.EnablingSingle; + Task.Run(() => { var enableTask = Task.Run(() => plugin.Enable()) - .ContinueWith(this.DisplayErrorContinuation, Locs.ErrorModal_EnableFail(plugin.Name)); + .ContinueWith(this.DisplayErrorContinuation, Locs.ErrorModal_EnableFail(plugin.Name)); enableTask.Wait(); if (!enableTask.Result) return; var loadTask = Task.Run(() => plugin.LoadAsync(PluginLoadReason.Installer)) - .ContinueWith(this.DisplayErrorContinuation, Locs.ErrorModal_LoadFail(plugin.Name)); + .ContinueWith(this.DisplayErrorContinuation, Locs.ErrorModal_LoadFail(plugin.Name)); loadTask.Wait(); + this.enableDisableStatus = OperationStatus.Complete; + if (!loadTask.Result) return; + + notifications.AddNotification(Locs.Notifications_PluginEnabled(plugin.Manifest.Name), Locs.Notifications_PluginEnabledTitle, NotificationType.Success); }); } } } - else if (plugin.State == PluginState.UnloadError) - { - ImGuiComponents.DisabledButton(FontAwesomeIcon.Frown); - if (ImGui.IsItemHovered()) - ImGui.SetTooltip(Locs.PluginButtonToolTip_UnloadFailed); + ImGui.SameLine(); + ImGuiHelpers.ScaledDummy(15, 0); + + if (plugin.State == PluginState.Loaded) + { + // Only if the plugin isn't broken. + this.DrawOpenPluginSettingsButton(plugin); } } @@ -1811,6 +1905,7 @@ namespace Dalamud.Interface.Internal.Windows.PluginInstaller if (ImGuiComponents.IconButton(FontAwesomeIcon.Download)) { this.installStatus = OperationStatus.InProgress; + this.loadingIndicatorKind = LoadingIndicatorKind.UpdatingSingle; Task.Run(async () => await pluginManager.UpdateSinglePluginAsync(update, true, false)) .ContinueWith(task => @@ -1879,6 +1974,8 @@ namespace Dalamud.Interface.Internal.Windows.PluginInstaller private void DrawDevPluginButtons(LocalPlugin localPlugin) { + ImGui.SameLine(); + var configuration = Service.Get(); if (localPlugin is LocalDevPlugin plugin) @@ -2425,6 +2522,10 @@ namespace Dalamud.Interface.Internal.Windows.PluginInstaller public static string Notifications_PluginDisabled(string name) => Loc.Localize("NotificationsPluginDisabled", "'{0}' was disabled.").Format(name); + public static string Notifications_PluginEnabledTitle => Loc.Localize("NotificationsPluginEnabledTitle", "Plugin enabled!"); + + public static string Notifications_PluginEnabled(string name) => Loc.Localize("NotificationsPluginEnabled", "'{0}' was enabled.").Format(name); + #endregion #region Footer From 4dc48285c4eaf73b52bd570052e6e85996bc445d Mon Sep 17 00:00:00 2001 From: goat <16760685+goaaats@users.noreply.github.com> Date: Sat, 16 Jul 2022 00:38:33 +0200 Subject: [PATCH 10/58] feat: add RaiseFailFastException hook prototype --- Dalamud.Boot/xivfixes.cpp | 35 ++++++++++++++++++++++++++++++++++ Dalamud.Boot/xivfixes.h | 1 + Dalamud.Injector/EntryPoint.cs | 2 +- 3 files changed, 37 insertions(+), 1 deletion(-) diff --git a/Dalamud.Boot/xivfixes.cpp b/Dalamud.Boot/xivfixes.cpp index 1f11d7ed5..13315669c 100644 --- a/Dalamud.Boot/xivfixes.cpp +++ b/Dalamud.Boot/xivfixes.cpp @@ -493,6 +493,40 @@ void xivfixes::backup_userdata_save(bool bApply) { } } +void xivfixes::clr_failfast_hijack(bool bApply) +{ + static const char* LogTag = "[xivfixes:clr_failfast_hijack]"; + static std::optional> s_HookClrFatalError; + + if (bApply) + { + if (!g_startInfo.BootEnabledGameFixes.contains("clr_failfast_hijack")) { + logging::I("{} Turned off via environment variable.", LogTag); + return; + } + + s_HookClrFatalError.emplace("kernel32.dll!RaiseFailFastException (import, backup_userdata_save)", "kernel32.dll", "RaiseFailFastException", 0); + + s_HookClrFatalError->set_detour([](PEXCEPTION_RECORD pExceptionRecord, + _In_opt_ PCONTEXT pContextRecord, + _In_ DWORD dwFlags) + { + MessageBoxW(nullptr, L"An error in a Dalamud plugin was detected and the game cannot continue.\n\nPlease take a screenshot of this error message and let us know about it.", L"Dalamud", MB_OK | MB_ICONERROR); + + return s_HookClrFatalError->call_original(pExceptionRecord, pContextRecord, dwFlags); + }); + + logging::I("{} Enable", LogTag); + } + else + { + if (s_HookClrFatalError) { + logging::I("{} Disable ClrFatalError", LogTag); + s_HookClrFatalError.reset(); + } + } +} + void xivfixes::apply_all(bool bApply) { for (const auto& [taskName, taskFunction] : std::initializer_list> { @@ -501,6 +535,7 @@ void xivfixes::apply_all(bool bApply) { { "disable_game_openprocess_access_check", &disable_game_openprocess_access_check }, { "redirect_openprocess", &redirect_openprocess }, { "backup_userdata_save", &backup_userdata_save }, + { "clr_failfast_hijack", &clr_failfast_hijack } } ) { try { diff --git a/Dalamud.Boot/xivfixes.h b/Dalamud.Boot/xivfixes.h index 556c5422f..894c60880 100644 --- a/Dalamud.Boot/xivfixes.h +++ b/Dalamud.Boot/xivfixes.h @@ -6,6 +6,7 @@ namespace xivfixes { void disable_game_openprocess_access_check(bool bApply); void redirect_openprocess(bool bApply); void backup_userdata_save(bool bApply); + void clr_failfast_hijack(bool bApply); void apply_all(bool bApply); } diff --git a/Dalamud.Injector/EntryPoint.cs b/Dalamud.Injector/EntryPoint.cs index 2ef37e4b0..39aaebfc2 100644 --- a/Dalamud.Injector/EntryPoint.cs +++ b/Dalamud.Injector/EntryPoint.cs @@ -317,7 +317,7 @@ namespace Dalamud.Injector startInfo.BootShowConsole = args.Contains("--console"); startInfo.BootEnableEtw = args.Contains("--etw"); startInfo.BootLogPath = GetLogPath("dalamud.boot"); - startInfo.BootEnabledGameFixes = new List { "prevent_devicechange_crashes", "disable_game_openprocess_access_check", "redirect_openprocess", "backup_userdata_save" }; + startInfo.BootEnabledGameFixes = new List { "prevent_devicechange_crashes", "disable_game_openprocess_access_check", "redirect_openprocess", "backup_userdata_save", "clr_failfast_hijack" }; startInfo.BootDotnetOpenProcessHookMode = 0; startInfo.BootWaitMessageBox |= args.Contains("--msgbox1") ? 1 : 0; startInfo.BootWaitMessageBox |= args.Contains("--msgbox2") ? 2 : 0; From 2549877546f293a65c6076ac42122f0c23b51b7b Mon Sep 17 00:00:00 2001 From: goat <16760685+goaaats@users.noreply.github.com> Date: Sat, 16 Jul 2022 00:39:01 +0200 Subject: [PATCH 11/58] build: 6.4.0.37 --- Dalamud/Dalamud.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dalamud/Dalamud.csproj b/Dalamud/Dalamud.csproj index 1be87123b..8c5c83d79 100644 --- a/Dalamud/Dalamud.csproj +++ b/Dalamud/Dalamud.csproj @@ -8,7 +8,7 @@ - 6.4.0.36 + 6.4.0.37 XIV Launcher addon framework $(DalamudVersion) $(DalamudVersion) From 04464de9e6209f0fc6f54c76306c1de30945f2b1 Mon Sep 17 00:00:00 2001 From: goat <16760685+goaaats@users.noreply.github.com> Date: Sat, 16 Jul 2022 21:00:20 +0200 Subject: [PATCH 12/58] chore: add back minhook env var --- Dalamud/Hooking/Hook.cs | 10 +++++++ .../Interface/Internal/DalamudInterface.cs | 26 +++++++++++++++---- 2 files changed, 31 insertions(+), 5 deletions(-) diff --git a/Dalamud/Hooking/Hook.cs b/Dalamud/Hooking/Hook.cs index 9f56b8b31..104d4f5a7 100644 --- a/Dalamud/Hooking/Hook.cs +++ b/Dalamud/Hooking/Hook.cs @@ -3,6 +3,7 @@ using System.Diagnostics; using System.Reflection; using System.Runtime.InteropServices; +using Dalamud.Configuration.Internal; using Dalamud.Hooking.Internal; namespace Dalamud.Hooking @@ -59,6 +60,9 @@ namespace Dalamud.Hooking [Obsolete("Use Hook.FromAddress instead.")] private Hook(IntPtr address, T detour, bool useMinHook, Assembly callingAssembly) { + if (EnvironmentConfiguration.DalamudForceMinHook) + useMinHook = true; + address = HookManager.FollowJmp(address); if (useMinHook) this.compatHookImpl = new MinHookHook(address, detour, callingAssembly); @@ -214,6 +218,9 @@ namespace Dalamud.Hooking /// The hook with the supplied parameters. public static Hook FromSymbol(string moduleName, string exportName, T detour, bool useMinHook) { + if (EnvironmentConfiguration.DalamudForceMinHook) + useMinHook = true; + var moduleHandle = NativeFunctions.GetModuleHandleW(moduleName); if (moduleHandle == IntPtr.Zero) throw new Exception($"Could not get a handle to module {moduleName}"); @@ -240,6 +247,9 @@ namespace Dalamud.Hooking /// The hook with the supplied parameters. public static Hook FromAddress(IntPtr procAddress, T detour, bool useMinHook = false) { + if (EnvironmentConfiguration.DalamudForceMinHook) + useMinHook = true; + procAddress = HookManager.FollowJmp(procAddress); if (useMinHook) return new MinHookHook(procAddress, detour, Assembly.GetCallingAssembly()); diff --git a/Dalamud/Interface/Internal/DalamudInterface.cs b/Dalamud/Interface/Internal/DalamudInterface.cs index 503771197..abbc79696 100644 --- a/Dalamud/Interface/Internal/DalamudInterface.cs +++ b/Dalamud/Interface/Internal/DalamudInterface.cs @@ -12,6 +12,7 @@ using Dalamud.Configuration.Internal; using Dalamud.Game.ClientState.Conditions; using Dalamud.Game.Gui; using Dalamud.Game.Internal; +using Dalamud.Interface.Colors; using Dalamud.Interface.Internal.ManagedAsserts; using Dalamud.Interface.Internal.Windows; using Dalamud.Interface.Internal.Windows.PluginInstaller; @@ -369,7 +370,7 @@ namespace Dalamud.Interface.Internal /// Toggles the . /// public void ToggleStyleEditorWindow() => this.selfTestWindow.Toggle(); - + /// /// Toggles the . /// @@ -438,12 +439,9 @@ namespace Dalamud.Interface.Internal if (!this.isImGuiDrawDevMenu && !condition.Any()) { - var config = Service.Get(); - ImGui.PushStyleColor(ImGuiCol.Button, Vector4.Zero); ImGui.PushStyleColor(ImGuiCol.ButtonActive, Vector4.Zero); ImGui.PushStyleColor(ImGuiCol.ButtonHovered, Vector4.Zero); - ImGui.PushStyleColor(ImGuiCol.Text, new Vector4(0, 0, 0, 1)); ImGui.PushStyleColor(ImGuiCol.TextSelectedBg, new Vector4(0, 0, 0, 1)); ImGui.PushStyleColor(ImGuiCol.Border, new Vector4(0, 0, 0, 1)); ImGui.PushStyleColor(ImGuiCol.BorderShadow, new Vector4(0, 0, 0, 1)); @@ -467,8 +465,26 @@ namespace Dalamud.Interface.Internal ImGui.End(); } + if (EnvironmentConfiguration.DalamudForceMinHook) + { + ImGui.SetNextWindowPos(windowPos, ImGuiCond.Always); + ImGui.SetNextWindowBgAlpha(1); + + if (ImGui.Begin( + "Disclaimer", + ImGuiWindowFlags.AlwaysAutoResize | ImGuiWindowFlags.NoBackground | + ImGuiWindowFlags.NoDecoration | ImGuiWindowFlags.NoMove | + ImGuiWindowFlags.NoScrollbar | ImGuiWindowFlags.NoMouseInputs | + ImGuiWindowFlags.NoResize | ImGuiWindowFlags.NoSavedSettings)) + { + ImGui.TextColored(ImGuiColors.DalamudRed, "Is force MinHook!"); + } + + ImGui.End(); + } + ImGui.PopStyleVar(4); - ImGui.PopStyleColor(8); + ImGui.PopStyleColor(7); } } From 8fea79260cd5fe026c59f85e12f3713588321e19 Mon Sep 17 00:00:00 2001 From: goat <16760685+goaaats@users.noreply.github.com> Date: Sat, 16 Jul 2022 21:03:38 +0200 Subject: [PATCH 13/58] deps: update Reloaded.Hooks to 4.2.0 --- Dalamud/Dalamud.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dalamud/Dalamud.csproj b/Dalamud/Dalamud.csproj index 8c5c83d79..9faa3b938 100644 --- a/Dalamud/Dalamud.csproj +++ b/Dalamud/Dalamud.csproj @@ -74,7 +74,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive From 8d9070a1b37c000cfdcd2970f398dbbfbd42d4c6 Mon Sep 17 00:00:00 2001 From: goat <16760685+goaaats@users.noreply.github.com> Date: Sun, 17 Jul 2022 11:24:07 +0200 Subject: [PATCH 14/58] deps: sync reloaded versions across projects --- Dalamud.Injector/Dalamud.Injector.csproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Dalamud.Injector/Dalamud.Injector.csproj b/Dalamud.Injector/Dalamud.Injector.csproj index 9f780b080..43fbb33c1 100644 --- a/Dalamud.Injector/Dalamud.Injector.csproj +++ b/Dalamud.Injector/Dalamud.Injector.csproj @@ -64,8 +64,8 @@ - - + + From 8c066451ecd0ab00dbc2d842d7bd605b34b12976 Mon Sep 17 00:00:00 2001 From: goat <16760685+goaaats@users.noreply.github.com> Date: Sun, 17 Jul 2022 14:46:48 +0200 Subject: [PATCH 15/58] fix: update Iced in Injector, fix build --- Dalamud.Injector/Dalamud.Injector.csproj | 2 +- Dalamud.Injector/EntryPoint.cs | 2 +- Dalamud.Injector/Injector.cs | 86 +++++++++++------------- Dalamud.Injector/NativeFunctions.cs | 4 +- Dalamud/Dalamud.cs | 3 - Dalamud/EntryPoint.cs | 1 - 6 files changed, 44 insertions(+), 54 deletions(-) diff --git a/Dalamud.Injector/Dalamud.Injector.csproj b/Dalamud.Injector/Dalamud.Injector.csproj index 43fbb33c1..3a4da8053 100644 --- a/Dalamud.Injector/Dalamud.Injector.csproj +++ b/Dalamud.Injector/Dalamud.Injector.csproj @@ -60,7 +60,7 @@ - + diff --git a/Dalamud.Injector/EntryPoint.cs b/Dalamud.Injector/EntryPoint.cs index 39aaebfc2..7bd4cdbe9 100644 --- a/Dalamud.Injector/EntryPoint.cs +++ b/Dalamud.Injector/EntryPoint.cs @@ -731,7 +731,7 @@ namespace Dalamud.Injector using var startInfoBuffer = new MemoryBufferHelper(process).CreatePrivateMemoryBuffer(startInfoBytes.Length + 0x8); var startInfoAddress = startInfoBuffer.Add(startInfoBytes); - if (startInfoAddress == IntPtr.Zero) + if (startInfoAddress == 0) throw new Exception("Unable to allocate start info JSON"); injector.GetFunctionAddress(bootModule, "Initialize", out var initAddress); diff --git a/Dalamud.Injector/Injector.cs b/Dalamud.Injector/Injector.cs index e5664388e..17ca5ccb5 100644 --- a/Dalamud.Injector/Injector.cs +++ b/Dalamud.Injector/Injector.cs @@ -32,11 +32,11 @@ namespace Dalamud.Injector private readonly CircularBuffer circularBuffer; private readonly PrivateMemoryBuffer memoryBuffer; - private IntPtr loadLibraryShellPtr; - private IntPtr loadLibraryRetPtr; + private nuint loadLibraryShellPtr; + private nuint loadLibraryRetPtr; - private IntPtr getProcAddressShellPtr; - private IntPtr getProcAddressRetPtr; + private nuint getProcAddressShellPtr; + private nuint getProcAddressRetPtr; /// /// Initializes a new instance of the class. @@ -85,11 +85,11 @@ namespace Dalamud.Injector { var lpParameter = this.WriteNullTerminatedUnicodeString(modulePath); - if (lpParameter == IntPtr.Zero) + if (lpParameter == 0) throw new Exception("Unable to allocate LoadLibraryW parameter"); this.CallRemoteFunction(this.loadLibraryShellPtr, lpParameter, out var err); - address = this.extMemory.Read(this.loadLibraryRetPtr); + this.extMemory.Read(this.loadLibraryRetPtr, out address); if (address == IntPtr.Zero) throw new Exception($"LoadLibraryW(\"{modulePath}\") failure: {new Win32Exception((int)err).Message} ({err})"); } @@ -100,17 +100,17 @@ namespace Dalamud.Injector /// Module address. /// Name of the exported method. /// Address to the function. - public void GetFunctionAddress(IntPtr module, string functionName, out IntPtr address) + public void GetFunctionAddress(IntPtr module, string functionName, out nuint address) { var functionNamePtr = this.WriteNullTerminatedASCIIString(functionName); var getProcAddressParams = new GetProcAddressParams(module, functionNamePtr); var lpParameter = this.circularBuffer.Add(ref getProcAddressParams); - if (lpParameter == IntPtr.Zero) + if (lpParameter == 0) throw new Exception("Unable to allocate GetProcAddress parameter ptr"); this.CallRemoteFunction(this.getProcAddressShellPtr, lpParameter, out var err); - address = this.extMemory.Read(this.getProcAddressRetPtr); - if (address == IntPtr.Zero) + this.extMemory.Read(this.getProcAddressRetPtr, out address); + if (address == 0) throw new Exception($"GetProcAddress(0x{module:X}, \"{functionName}\") failure: {new Win32Exception((int)err).Message} ({err})"); } @@ -120,7 +120,7 @@ namespace Dalamud.Injector /// Method address. /// Parameter address. /// Thread exit code. - public void CallRemoteFunction(IntPtr methodAddress, IntPtr parameterAddress, out uint exitCode) + public void CallRemoteFunction(nuint methodAddress, nuint parameterAddress, out uint exitCode) { // Create and initialize a thread at our address and parameter address. var threadHandle = CreateRemoteThread( @@ -151,26 +151,23 @@ namespace Dalamud.Injector Log.Verbose($"LoadLibraryW: 0x{functionAddr.ToInt64():X}"); var functionPtr = this.memoryBuffer.Add(ref functionAddr); - Log.Verbose($"LoadLibraryPtr: 0x{functionPtr.ToInt64():X}"); + Log.Verbose($"LoadLibraryPtr: 0x{functionPtr:X}"); - if (functionPtr == IntPtr.Zero) + if (functionPtr == 0) throw new Exception("Unable to allocate LoadLibraryW function ptr"); var dummy = IntPtr.Zero; this.loadLibraryRetPtr = this.memoryBuffer.Add(ref dummy); - Log.Verbose($"LoadLibraryRetPtr: 0x{this.loadLibraryRetPtr.ToInt64():X}"); + Log.Verbose($"LoadLibraryRetPtr: 0x{this.loadLibraryRetPtr:X}"); - if (this.loadLibraryRetPtr == IntPtr.Zero) + if (this.loadLibraryRetPtr == 0) throw new Exception("Unable to allocate LoadLibraryW return value"); - var func = functionPtr.ToInt64(); - var retVal = this.loadLibraryRetPtr.ToInt64(); - var asm = new Assembler(64); asm.sub(rsp, 40); // sub rsp, 40 // Re-align stack to 16 byte boundary + shadow space. - asm.call(__qword_ptr[__qword_ptr[func]]); // call qword [qword func] // CreateRemoteThread lpParameter with string already in ECX. - asm.mov(__qword_ptr[__qword_ptr[retVal]], rax); // mov qword [qword retVal], rax // + asm.call(__qword_ptr[__qword_ptr[functionPtr]]); // call qword [qword func] // CreateRemoteThread lpParameter with string already in ECX. + asm.mov(__qword_ptr[__qword_ptr[this.loadLibraryRetPtr]], rax); // mov qword [qword retVal], rax // asm.add(rsp, 40); // add rsp, 40 // Re-align stack to 16 byte boundary + shadow space. asm.mov(rax, (ulong)getLastErrorAddr); // mov rax, pfnGetLastError // Change return address to GetLastError. asm.push(rax); // push rax // @@ -178,18 +175,18 @@ namespace Dalamud.Injector var bytes = this.Assemble(asm); this.loadLibraryShellPtr = this.memoryBuffer.Add(bytes); - Log.Verbose($"LoadLibraryShellPtr: 0x{this.loadLibraryShellPtr.ToInt64():X}"); + Log.Verbose($"LoadLibraryShellPtr: 0x{this.loadLibraryShellPtr:X}"); - if (this.loadLibraryShellPtr == IntPtr.Zero) + if (this.loadLibraryShellPtr == 0) throw new Exception("Unable to allocate LoadLibraryW shellcode"); this.extMemory.ChangePermission(this.loadLibraryShellPtr, bytes.Length, Reloaded.Memory.Kernel32.Kernel32.MEM_PROTECTION.PAGE_EXECUTE_READWRITE); #if DEBUG - var outFunctionPtr = this.extMemory.Read(functionPtr); + this.extMemory.Read(functionPtr, out var outFunctionPtr); Log.Verbose($"LoadLibraryPtr: {this.GetResultMarker(outFunctionPtr == functionAddr)}"); - var outRetPtr = this.extMemory.Read(this.loadLibraryRetPtr); + this.extMemory.Read(this.loadLibraryRetPtr, out var outRetPtr); Log.Verbose($"LoadLibraryRet: {this.GetResultMarker(dummy == outRetPtr)}"); this.extMemory.ReadRaw(this.loadLibraryShellPtr, out var outBytes, bytes.Length); @@ -207,28 +204,25 @@ namespace Dalamud.Injector Log.Verbose($"GetProcAddress: 0x{functionAddr.ToInt64():X}"); var functionPtr = this.memoryBuffer.Add(ref functionAddr); - Log.Verbose($"GetProcAddressPtr: 0x{functionPtr.ToInt64():X}"); + Log.Verbose($"GetProcAddressPtr: 0x{functionPtr:X}"); - if (functionPtr == IntPtr.Zero) + if (functionPtr == 0) throw new Exception("Unable to allocate GetProcAddress function ptr"); var dummy = IntPtr.Zero; this.getProcAddressRetPtr = this.memoryBuffer.Add(ref dummy); - Log.Verbose($"GetProcAddressRetPtr: 0x{this.loadLibraryRetPtr.ToInt64():X}"); + Log.Verbose($"GetProcAddressRetPtr: 0x{this.loadLibraryRetPtr:X}"); - if (this.getProcAddressRetPtr == IntPtr.Zero) + if (this.getProcAddressRetPtr == 0) throw new Exception("Unable to allocate GetProcAddress return value"); - var func = functionPtr.ToInt64(); - var retVal = this.getProcAddressRetPtr.ToInt64(); - var asm = new Assembler(64); asm.sub(rsp, 40); // sub rsp, 40 // Re-align stack to 16 byte boundary +32 shadow space asm.mov(rdx, __qword_ptr[__qword_ptr[rcx + 8]]); // mov rdx, qword [qword rcx + 8] // lpProcName asm.mov(rcx, __qword_ptr[__qword_ptr[rcx + 0]]); // mov rcx, qword [qword rcx + 0] // hModule - asm.call(__qword_ptr[__qword_ptr[func]]); // call qword [qword func] // - asm.mov(__qword_ptr[__qword_ptr[retVal]], rax); // mov qword [qword retVal] // + asm.call(__qword_ptr[__qword_ptr[functionPtr]]); // call qword [qword func] // + asm.mov(__qword_ptr[__qword_ptr[this.getProcAddressRetPtr]], rax); // mov qword [qword retVal] // asm.add(rsp, 40); // add rsp, 40 // Re-align stack to 16 byte boundary + shadow space. asm.mov(rax, (ulong)getLastErrorAddr); // mov rax, pfnGetLastError // Change return address to GetLastError. asm.push(rax); // push rax // @@ -236,18 +230,18 @@ namespace Dalamud.Injector var bytes = this.Assemble(asm); this.getProcAddressShellPtr = this.memoryBuffer.Add(bytes); - Log.Verbose($"GetProcAddressShellPtr: 0x{this.getProcAddressShellPtr.ToInt64():X}"); + Log.Verbose($"GetProcAddressShellPtr: 0x{this.getProcAddressShellPtr:X}"); - if (this.getProcAddressShellPtr == IntPtr.Zero) + if (this.getProcAddressShellPtr == 0) throw new Exception("Unable to allocate GetProcAddress shellcode"); this.extMemory.ChangePermission(this.getProcAddressShellPtr, bytes.Length, Reloaded.Memory.Kernel32.Kernel32.MEM_PROTECTION.PAGE_EXECUTE_READWRITE); #if DEBUG - var outFunctionPtr = this.extMemory.Read(functionPtr); + this.extMemory.Read(functionPtr, out var outFunctionPtr); Log.Verbose($"GetProcAddressPtr: {this.GetResultMarker(outFunctionPtr == functionAddr)}"); - var outRetPtr = this.extMemory.Read(this.loadLibraryRetPtr); + this.extMemory.Read(this.loadLibraryRetPtr, out var outRetPtr); Log.Verbose($"GetProcAddressRet: {this.GetResultMarker(dummy == outRetPtr)}"); this.extMemory.ReadRaw(this.getProcAddressShellPtr, out var outBytes, bytes.Length); @@ -298,33 +292,33 @@ namespace Dalamud.Injector return exportFunction.Address; } - private IntPtr WriteNullTerminatedASCIIString(string value) + private nuint WriteNullTerminatedASCIIString(string value) { var bytes = Encoding.ASCII.GetBytes(value + '\0'); var address = this.circularBuffer.Add(bytes); - if (address == IntPtr.Zero) + if (address == 0) throw new Exception("Unable to write ASCII string to buffer"); #if DEBUG this.extMemory.ReadRaw(address, out var outBytes, bytes.Length); - Log.Verbose($"WriteASCII: {this.GetResultMarker(Enumerable.SequenceEqual(bytes, outBytes))} 0x{address.ToInt64():X} {value}"); + Log.Verbose($"WriteASCII: {this.GetResultMarker(Enumerable.SequenceEqual(bytes, outBytes))} 0x{address:X} {value}"); #endif return address; } - private IntPtr WriteNullTerminatedUnicodeString(string value) + private nuint WriteNullTerminatedUnicodeString(string value) { var bytes = Encoding.Unicode.GetBytes(value + '\0'); var address = this.circularBuffer.Add(bytes); - if (address == IntPtr.Zero) + if (address == 0) throw new Exception("Unable to write Unicode string to buffer"); #if DEBUG this.extMemory.ReadRaw(address, out var outBytes, bytes.Length); - Log.Verbose($"WriteUnicode: {this.GetResultMarker(Enumerable.SequenceEqual(bytes, outBytes))} 0x{address.ToInt64():X} {value}"); + Log.Verbose($"WriteUnicode: {this.GetResultMarker(Enumerable.SequenceEqual(bytes, outBytes))} 0x{address:X} {value}"); #endif return address; @@ -337,15 +331,15 @@ namespace Dalamud.Injector [StructLayout(LayoutKind.Sequential)] private struct GetProcAddressParams { - public GetProcAddressParams(IntPtr hModule, IntPtr lPProcName) + public GetProcAddressParams(IntPtr hModule, nuint lPProcName) { this.HModule = hModule.ToInt64(); - this.LPProcName = lPProcName.ToInt64(); + this.LPProcName = lPProcName; } public long HModule { get; set; } - public long LPProcName { get; set; } + public nuint LPProcName { get; set; } } } } diff --git a/Dalamud.Injector/NativeFunctions.cs b/Dalamud.Injector/NativeFunctions.cs index f1749ae0a..2a4654aaf 100644 --- a/Dalamud.Injector/NativeFunctions.cs +++ b/Dalamud.Injector/NativeFunctions.cs @@ -661,8 +661,8 @@ namespace Dalamud.Injector IntPtr hProcess, IntPtr lpThreadAttributes, UIntPtr dwStackSize, - IntPtr lpStartAddress, - IntPtr lpParameter, + nuint lpStartAddress, + nuint lpParameter, CreateThreadFlags dwCreationFlags, out uint lpThreadId); diff --git a/Dalamud/Dalamud.cs b/Dalamud/Dalamud.cs index 24011b489..cc858eee8 100644 --- a/Dalamud/Dalamud.cs +++ b/Dalamud/Dalamud.cs @@ -28,7 +28,6 @@ namespace Dalamud #region Internals private readonly ManualResetEvent unloadSignal; - private bool hasDisposedPlugins = false; #endregion @@ -117,8 +116,6 @@ namespace Dalamud /// public void DisposePlugins() { - this.hasDisposedPlugins = true; - // this must be done before unloading interface manager, in order to do rebuild // the correct cascaded WndProc (IME -> RawDX11Scene -> Game). Otherwise the game // will not receive any windows messages diff --git a/Dalamud/EntryPoint.cs b/Dalamud/EntryPoint.cs index 5fef315d5..029e8dd55 100644 --- a/Dalamud/EntryPoint.cs +++ b/Dalamud/EntryPoint.cs @@ -10,7 +10,6 @@ using Dalamud.Configuration.Internal; using Dalamud.Logging.Internal; using Dalamud.Support; using Dalamud.Utility; -using ImGuiNET; using Newtonsoft.Json; using PInvoke; using Serilog; From f65ccca675806d456ae637fe6f193ff6e3e0eef0 Mon Sep 17 00:00:00 2001 From: goat <16760685+goaaats@users.noreply.github.com> Date: Sun, 17 Jul 2022 15:39:41 +0200 Subject: [PATCH 16/58] fix: don't auto-update disabled plugins, don't let styles modify our toggle switch --- Dalamud/Game/ChatHandlers.cs | 4 +- .../ImGuiComponents.ToggleSwitch.cs | 9 ++- .../PluginInstaller/PluginInstallerWindow.cs | 7 +- Dalamud/Interface/Style/StyleModel.cs | 65 ++++++++++++++++++- Dalamud/Interface/Style/StyleModelV1.cs | 40 ++++++++++-- Dalamud/Plugin/Internal/PluginManager.cs | 5 +- 6 files changed, 112 insertions(+), 18 deletions(-) diff --git a/Dalamud/Game/ChatHandlers.cs b/Dalamud/Game/ChatHandlers.cs index 7215568aa..91467bfc9 100644 --- a/Dalamud/Game/ChatHandlers.cs +++ b/Dalamud/Game/ChatHandlers.cs @@ -292,7 +292,7 @@ namespace Dalamud.Game this.hasAutoUpdatedPlugins = true; - Task.Run(() => pluginManager.UpdatePluginsAsync(!this.configuration.AutoUpdatePlugins)).ContinueWith(task => + Task.Run(() => pluginManager.UpdatePluginsAsync(true, !this.configuration.AutoUpdatePlugins)).ContinueWith(task => { if (task.IsFaulted) { @@ -301,7 +301,7 @@ namespace Dalamud.Game } var updatedPlugins = task.Result; - if (updatedPlugins != null && updatedPlugins.Any()) + if (updatedPlugins.Any()) { if (this.configuration.AutoUpdatePlugins) { diff --git a/Dalamud/Interface/Components/ImGuiComponents.ToggleSwitch.cs b/Dalamud/Interface/Components/ImGuiComponents.ToggleSwitch.cs index 83c238c06..bdf14b430 100644 --- a/Dalamud/Interface/Components/ImGuiComponents.ToggleSwitch.cs +++ b/Dalamud/Interface/Components/ImGuiComponents.ToggleSwitch.cs @@ -36,10 +36,10 @@ namespace Dalamud.Interface.Components } if (ImGui.IsItemHovered()) - drawList.AddRectFilled(p, new Vector2(p.X + width, p.Y + height), ImGui.GetColorU32(v ? colors[(int)ImGuiCol.ButtonActive] : new Vector4(0.78f, 0.78f, 0.78f, 1.0f)), height * 0.5f); + drawList.AddRectFilled(p, new Vector2(p.X + width, p.Y + height), ImGui.GetColorU32(!v ? colors[(int)ImGuiCol.ButtonActive] : new Vector4(0.78f, 0.78f, 0.78f, 1.0f)), height * 0.5f); else - drawList.AddRectFilled(p, new Vector2(p.X + width, p.Y + height), ImGui.GetColorU32(v ? colors[(int)ImGuiCol.Button] : new Vector4(0.55f, 0.55f, 0.55f, 1.0f)), height * 0.50f); - drawList.AddCircleFilled(new Vector2(p.X + radius + ((v ? 1 : 0) * (width - (radius * 2.0f))), p.Y + radius), radius - 1.5f, ImGui.ColorConvertFloat4ToU32(new Vector4(255, 255, 255, 255))); + drawList.AddRectFilled(p, new Vector2(p.X + width, p.Y + height), ImGui.GetColorU32(!v ? colors[(int)ImGuiCol.Button] * 0.6f : new Vector4(0.35f, 0.35f, 0.35f, 1.0f)), height * 0.50f); + drawList.AddCircleFilled(new Vector2(p.X + radius + ((v ? 1 : 0) * (width - (radius * 2.0f))), p.Y + radius), radius - 1.5f, ImGui.ColorConvertFloat4ToU32(new Vector4(1, 1, 1, 1))); return changed; } @@ -60,13 +60,12 @@ namespace Dalamud.Interface.Components var radius = height * 0.50f; // TODO: animate - // TODO: dim colors ImGui.InvisibleButton(id, new Vector2(width, height)); var dimFactor = 0.5f; drawList.AddRectFilled(p, new Vector2(p.X + width, p.Y + height), ImGui.GetColorU32(v ? colors[(int)ImGuiCol.Button] * dimFactor : new Vector4(0.55f, 0.55f, 0.55f, 1.0f) * dimFactor), height * 0.50f); - drawList.AddCircleFilled(new Vector2(p.X + radius + ((v ? 1 : 0) * (width - (radius * 2.0f))), p.Y + radius), radius - 1.5f, ImGui.ColorConvertFloat4ToU32(new Vector4(255, 255, 255, 255) * dimFactor)); + drawList.AddCircleFilled(new Vector2(p.X + radius + ((v ? 1 : 0) * (width - (radius * 2.0f))), p.Y + radius), radius - 1.5f, ImGui.ColorConvertFloat4ToU32(new Vector4(1, 1, 1, 1) * dimFactor)); } } } diff --git a/Dalamud/Interface/Internal/Windows/PluginInstaller/PluginInstallerWindow.cs b/Dalamud/Interface/Internal/Windows/PluginInstaller/PluginInstallerWindow.cs index ad4a2d41a..5d6f59a08 100644 --- a/Dalamud/Interface/Internal/Windows/PluginInstaller/PluginInstallerWindow.cs +++ b/Dalamud/Interface/Internal/Windows/PluginInstaller/PluginInstallerWindow.cs @@ -14,6 +14,7 @@ using Dalamud.Game.Command; using Dalamud.Interface.Colors; using Dalamud.Interface.Components; using Dalamud.Interface.Internal.Notifications; +using Dalamud.Interface.Style; using Dalamud.Interface.Windowing; using Dalamud.Logging.Internal; using Dalamud.Plugin; @@ -420,7 +421,7 @@ namespace Dalamud.Interface.Internal.Windows.PluginInstaller this.updateStatus = OperationStatus.InProgress; this.loadingIndicatorKind = LoadingIndicatorKind.UpdatingAll; - Task.Run(() => pluginManager.UpdatePluginsAsync()) + Task.Run(() => pluginManager.UpdatePluginsAsync(true, false)) .ContinueWith(task => { this.updateStatus = OperationStatus.Complete; @@ -1816,6 +1817,8 @@ namespace Dalamud.Interface.Internal.Windows.PluginInstaller var isLoadedAndUnloadable = plugin.State == PluginState.Loaded || plugin.State == PluginState.DependencyResolutionFailed; + StyleModelV1.DalamudStandard.Push(); + if (plugin.State == PluginState.UnloadError) { ImGuiComponents.DisabledButton(FontAwesomeIcon.Frown); @@ -1886,6 +1889,8 @@ namespace Dalamud.Interface.Internal.Windows.PluginInstaller } } + StyleModelV1.DalamudStandard.Pop(); + ImGui.SameLine(); ImGuiHelpers.ScaledDummy(15, 0); diff --git a/Dalamud/Interface/Style/StyleModel.cs b/Dalamud/Interface/Style/StyleModel.cs index 978ef78dc..a66a8ff81 100644 --- a/Dalamud/Interface/Style/StyleModel.cs +++ b/Dalamud/Interface/Style/StyleModel.cs @@ -1,10 +1,11 @@ using System; using System.Collections.Generic; using System.Linq; - +using System.Numerics; using Dalamud.Configuration.Internal; using Dalamud.Interface.Colors; using Dalamud.Utility; +using ImGuiNET; using Newtonsoft.Json; using Serilog; @@ -15,6 +16,10 @@ namespace Dalamud.Interface.Style /// public abstract class StyleModel { + private static int NumPushedStyles = 0; + private static int NumPushedColors = 0; + private static bool HasPushedOnce = false; + /// /// Gets or sets the name of the style model. /// @@ -84,7 +89,7 @@ namespace Dalamud.Interface.Style configuration.SavedStyles = new List(); configuration.SavedStyles.AddRange(configuration.SavedStylesOld); - Log.Information("Transferred {0} styles", configuration.SavedStyles.Count); + Log.Information("Transferred {NumStyles} styles", configuration.SavedStyles.Count); configuration.SavedStylesOld = null; configuration.Save(); @@ -123,6 +128,60 @@ namespace Dalamud.Interface.Style /// /// Pop this style model from the ImGui style/color stack. /// - public abstract void Pop(); + public void Pop() + { + if (!HasPushedOnce) + throw new InvalidOperationException("Wasn't pushed at least once."); + + ImGui.PopStyleVar(NumPushedStyles); + ImGui.PopStyleColor(NumPushedColors); + } + + /// + /// Push a style var. + /// + /// Style kind. + /// Style var. + protected void PushStyleHelper(ImGuiStyleVar style, float arg) + { + ImGui.PushStyleVar(style, arg); + + if (!HasPushedOnce) + NumPushedStyles++; + } + + /// + /// Push a style var. + /// + /// Style kind. + /// Style var. + protected void PushStyleHelper(ImGuiStyleVar style, Vector2 arg) + { + ImGui.PushStyleVar(style, arg); + + if (!HasPushedOnce) + NumPushedStyles++; + } + + /// + /// Push a style color. + /// + /// Color kind. + /// Color value. + protected void PushColorHelper(ImGuiCol color, Vector4 value) + { + ImGui.PushStyleColor(color, value); + + if (!HasPushedOnce) + NumPushedColors++; + } + + /// + /// Indicate that you have pushed. + /// + protected void DonePushing() + { + HasPushedOnce = true; + } } } diff --git a/Dalamud/Interface/Style/StyleModelV1.cs b/Dalamud/Interface/Style/StyleModelV1.cs index 0d10387c4..54aff8f84 100644 --- a/Dalamud/Interface/Style/StyleModelV1.cs +++ b/Dalamud/Interface/Style/StyleModelV1.cs @@ -487,13 +487,41 @@ namespace Dalamud.Interface.Style /// public override void Push() { - throw new NotImplementedException(); - } + this.PushStyleHelper(ImGuiStyleVar.Alpha, this.Alpha); + this.PushStyleHelper(ImGuiStyleVar.WindowPadding, this.WindowPadding); + this.PushStyleHelper(ImGuiStyleVar.WindowRounding, this.WindowRounding); + this.PushStyleHelper(ImGuiStyleVar.WindowBorderSize, this.WindowBorderSize); + this.PushStyleHelper(ImGuiStyleVar.WindowTitleAlign, this.WindowTitleAlign); + this.PushStyleHelper(ImGuiStyleVar.ChildRounding, this.ChildRounding); + this.PushStyleHelper(ImGuiStyleVar.ChildBorderSize, this.ChildBorderSize); + this.PushStyleHelper(ImGuiStyleVar.PopupRounding, this.PopupRounding); + this.PushStyleHelper(ImGuiStyleVar.PopupBorderSize, this.PopupBorderSize); + this.PushStyleHelper(ImGuiStyleVar.FramePadding, this.FramePadding); + this.PushStyleHelper(ImGuiStyleVar.FrameRounding, this.FrameRounding); + this.PushStyleHelper(ImGuiStyleVar.FrameBorderSize, this.FrameBorderSize); + this.PushStyleHelper(ImGuiStyleVar.ItemSpacing, this.ItemSpacing); + this.PushStyleHelper(ImGuiStyleVar.ItemInnerSpacing, this.ItemInnerSpacing); + this.PushStyleHelper(ImGuiStyleVar.CellPadding, this.CellPadding); + this.PushStyleHelper(ImGuiStyleVar.IndentSpacing, this.IndentSpacing); + this.PushStyleHelper(ImGuiStyleVar.ScrollbarSize, this.ScrollbarSize); + this.PushStyleHelper(ImGuiStyleVar.ScrollbarRounding, this.ScrollbarRounding); + this.PushStyleHelper(ImGuiStyleVar.GrabMinSize, this.GrabMinSize); + this.PushStyleHelper(ImGuiStyleVar.GrabRounding, this.GrabRounding); + this.PushStyleHelper(ImGuiStyleVar.TabRounding, this.TabRounding); + this.PushStyleHelper(ImGuiStyleVar.ButtonTextAlign, this.ButtonTextAlign); + this.PushStyleHelper(ImGuiStyleVar.SelectableTextAlign, this.SelectableTextAlign); - /// - public override void Pop() - { - throw new NotImplementedException(); + foreach (var imGuiCol in Enum.GetValues()) + { + if (imGuiCol == ImGuiCol.COUNT) + { + continue; + } + + this.PushColorHelper(imGuiCol, this.Colors[imGuiCol.ToString()]); + } + + this.DonePushing(); } } } diff --git a/Dalamud/Plugin/Internal/PluginManager.cs b/Dalamud/Plugin/Internal/PluginManager.cs index 22dcbed7a..e8232d386 100644 --- a/Dalamud/Plugin/Internal/PluginManager.cs +++ b/Dalamud/Plugin/Internal/PluginManager.cs @@ -884,7 +884,7 @@ internal partial class PluginManager : IDisposable, IServiceType /// /// Perform a dry run, don't install anything. /// Success or failure and a list of updated plugin metadata. - public async Task> UpdatePluginsAsync(bool dryRun = false) + public async Task> UpdatePluginsAsync(bool ignoreDisabled, bool dryRun) { Log.Information("Starting plugin update"); @@ -897,6 +897,9 @@ internal partial class PluginManager : IDisposable, IServiceType if (plugin.InstalledPlugin.IsDev) continue; + if (plugin.InstalledPlugin.Manifest.Disabled && ignoreDisabled) + continue; + var result = await this.UpdateSinglePluginAsync(plugin, false, dryRun); if (result != null) updatedList.Add(result); From 396e5fefca46272bae118a8c547f7184ade5064a Mon Sep 17 00:00:00 2001 From: goat <16760685+goaaats@users.noreply.github.com> Date: Sun, 17 Jul 2022 15:42:41 +0200 Subject: [PATCH 17/58] build: 6.4.0.38 --- Dalamud/Dalamud.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dalamud/Dalamud.csproj b/Dalamud/Dalamud.csproj index 9faa3b938..143fb39b3 100644 --- a/Dalamud/Dalamud.csproj +++ b/Dalamud/Dalamud.csproj @@ -8,7 +8,7 @@ - 6.4.0.37 + 6.4.0.38 XIV Launcher addon framework $(DalamudVersion) $(DalamudVersion) From 8cb28d0ebe6d678f5d0dd46a4d601efe1795540a Mon Sep 17 00:00:00 2001 From: kizer Date: Mon, 18 Jul 2022 00:41:27 +0900 Subject: [PATCH 18/58] Call RebuildFonts on next Framework.Tick on NewFontRef (#926) --- Dalamud/Interface/GameFonts/GameFontManager.cs | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/Dalamud/Interface/GameFonts/GameFontManager.cs b/Dalamud/Interface/GameFonts/GameFontManager.cs index fa1098be7..af71fe67f 100644 --- a/Dalamud/Interface/GameFonts/GameFontManager.cs +++ b/Dalamud/Interface/GameFonts/GameFontManager.cs @@ -6,6 +6,7 @@ using System.Runtime.InteropServices; using System.Text; using System.Threading.Tasks; using Dalamud.Data; +using Dalamud.Game; using Dalamud.Interface.Internal; using Dalamud.Utility.Timing; using ImGuiNET; @@ -184,16 +185,9 @@ namespace Dalamud.Interface.GameFonts needRebuild = !this.fonts.ContainsKey(style); if (needRebuild) { - if (interfaceManager.IsBuildingFontsBeforeAtlasBuild && this.isBetweenBuildFontsAndRightAfterImGuiIoFontsBuild) - { - Log.Information("[GameFontManager] NewFontRef: Building {0} right now, as it is called while BuildFonts is already in progress yet atlas build has not been called yet.", style.ToString()); - this.EnsureFont(style); - } - else - { - Log.Information("[GameFontManager] NewFontRef: Calling RebuildFonts because {0} has been requested.", style.ToString()); - interfaceManager.RebuildFonts(); - } + Log.Information("[GameFontManager] NewFontRef: Queueing RebuildFonts because {0} has been requested.", style.ToString()); + Service.GetAsync() + .ContinueWith(task => task.Result.RunOnTick(() => interfaceManager.RebuildFonts())); } return new(this, style); From 3ec6d05ec0617add811b44eb76c816d60d02022d Mon Sep 17 00:00:00 2001 From: goat <16760685+goaaats@users.noreply.github.com> Date: Sun, 17 Jul 2022 21:33:51 +0200 Subject: [PATCH 19/58] chore: better info about what blocks the installer --- .../PluginInstaller/PluginInstallerWindow.cs | 40 ++++++++++++++++++- Dalamud/Plugin/Internal/PluginManager.cs | 4 +- .../Plugin/Internal/Types/PluginRepository.cs | 3 +- 3 files changed, 44 insertions(+), 3 deletions(-) diff --git a/Dalamud/Interface/Internal/Windows/PluginInstaller/PluginInstallerWindow.cs b/Dalamud/Interface/Internal/Windows/PluginInstaller/PluginInstallerWindow.cs index 5d6f59a08..237fd61ca 100644 --- a/Dalamud/Interface/Internal/Windows/PluginInstaller/PluginInstallerWindow.cs +++ b/Dalamud/Interface/Internal/Windows/PluginInstaller/PluginInstallerWindow.cs @@ -265,7 +265,45 @@ namespace Dalamud.Interface.Internal.Windows.PluginInstaller ImGuiHelpers.CenteredText("Installing plugin..."); break; case LoadingIndicatorKind.Manager: - ImGuiHelpers.CenteredText("Loading repositories and plugins..."); + { + if (pluginManager.PluginsReady && !pluginManager.ReposReady) + { + ImGuiHelpers.CenteredText("Loading repositories..."); + } + else if (!pluginManager.PluginsReady && pluginManager.ReposReady) + { + ImGuiHelpers.CenteredText("Loading installed plugins..."); + } + else + { + ImGuiHelpers.CenteredText("Loading repositories and plugins..."); + } + + var currentProgress = 0; + var total = 0; + + var pendingRepos = pluginManager.Repos.ToArray() + .Where(x => (x.State != PluginRepositoryState.Success && + x.State != PluginRepositoryState.Fail) && + x.IsEnabled) + .ToArray(); + var allRepoCount = + pluginManager.Repos.Count(x => x.State != PluginRepositoryState.Fail && x.IsEnabled); + + foreach (var repo in pendingRepos) + { + ImGuiHelpers.CenteredText($"{repo.PluginMasterUrl}: {repo.State}"); + } + + currentProgress += allRepoCount - pendingRepos.Length; + total += allRepoCount; + + if (currentProgress != total) + { + ImGui.ProgressBar(currentProgress / (float)total, new Vector2(windowSize.X / 3, 50)); + } + } + break; default: throw new ArgumentOutOfRangeException(); diff --git a/Dalamud/Plugin/Internal/PluginManager.cs b/Dalamud/Plugin/Internal/PluginManager.cs index e8232d386..92307cff1 100644 --- a/Dalamud/Plugin/Internal/PluginManager.cs +++ b/Dalamud/Plugin/Internal/PluginManager.cs @@ -125,7 +125,7 @@ internal partial class PluginManager : IDisposable, IServiceType /// /// Gets a value indicating whether all added repos are not in progress. /// - public bool ReposReady => this.Repos.All(repo => repo.State != PluginRepositoryState.InProgress); + public bool ReposReady => this.Repos.All(repo => repo.State != PluginRepositoryState.InProgress || repo.State != PluginRepositoryState.Fail); /// /// Gets a value indicating whether the plugin manager started in safe mode. @@ -492,7 +492,9 @@ internal partial class PluginManager : IDisposable, IServiceType /// A representing the asynchronous operation. public async Task ReloadPluginMastersAsync(bool notify = true) { + Log.Information("Now reloading all PluginMasters..."); await Task.WhenAll(this.Repos.Select(repo => repo.ReloadPluginMasterAsync())); + Log.Information("PluginMasters reloaded, now refiltering..."); this.RefilterPluginMasters(notify); } diff --git a/Dalamud/Plugin/Internal/Types/PluginRepository.cs b/Dalamud/Plugin/Internal/Types/PluginRepository.cs index 82e4fb13d..5c7659d90 100644 --- a/Dalamud/Plugin/Internal/Types/PluginRepository.cs +++ b/Dalamud/Plugin/Internal/Types/PluginRepository.cs @@ -24,6 +24,7 @@ internal class PluginRepository private static readonly HttpClient HttpClient = new() { + Timeout = TimeSpan.FromSeconds(20), DefaultRequestHeaders = { CacheControl = new CacheControlHeaderValue @@ -109,7 +110,7 @@ internal class PluginRepository this.PluginMaster = pluginMaster.AsReadOnly(); - Log.Debug($"Successfully fetched repo: {this.PluginMasterUrl}"); + Log.Information($"Successfully fetched repo: {this.PluginMasterUrl}"); this.State = PluginRepositoryState.Success; } catch (Exception ex) From 264adf6ee7c84fe0f55650f9178b9338aed36ac7 Mon Sep 17 00:00:00 2001 From: goat <16760685+goaaats@users.noreply.github.com> Date: Sun, 17 Jul 2022 21:34:15 +0200 Subject: [PATCH 20/58] build: 6.4.0.39 --- Dalamud/Dalamud.csproj | 2 +- lib/CoreCLR/boot.cpp | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Dalamud/Dalamud.csproj b/Dalamud/Dalamud.csproj index 143fb39b3..22d2d8b16 100644 --- a/Dalamud/Dalamud.csproj +++ b/Dalamud/Dalamud.csproj @@ -8,7 +8,7 @@ - 6.4.0.38 + 6.4.0.39 XIV Launcher addon framework $(DalamudVersion) $(DalamudVersion) diff --git a/lib/CoreCLR/boot.cpp b/lib/CoreCLR/boot.cpp index c16ee3984..d2ee58112 100644 --- a/lib/CoreCLR/boot.cpp +++ b/lib/CoreCLR/boot.cpp @@ -41,9 +41,10 @@ int InitializeClrAndGetEntryPoint( int result; SetEnvironmentVariable(L"DOTNET_MULTILEVEL_LOOKUP", L"0"); - //SetEnvironmentVariable(L"COMPlus_legacyCorruptedStateExceptionsPolicy", L"1"); + SetEnvironmentVariable(L"COMPlus_legacyCorruptedStateExceptionsPolicy", L"1"); SetEnvironmentVariable(L"DOTNET_legacyCorruptedStateExceptionsPolicy", L"1"); SetEnvironmentVariable(L"COMPLUS_ForceENC", L"1"); + SetEnvironmentVariable(L"DOTNET_ForceENC", L"1"); // Enable Dynamic PGO SetEnvironmentVariable(L"DOTNET_TieredPGO", L"1"); From 177f9f9f90b4be6bc28dc1eed21e15c34adfc396 Mon Sep 17 00:00:00 2001 From: goat <16760685+goaaats@users.noreply.github.com> Date: Sun, 17 Jul 2022 22:51:50 +0200 Subject: [PATCH 21/58] chore: throw when address is negative --- Dalamud/Hooking/Internal/HookManager.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Dalamud/Hooking/Internal/HookManager.cs b/Dalamud/Hooking/Internal/HookManager.cs index b87ab1796..51047b97e 100644 --- a/Dalamud/Hooking/Internal/HookManager.cs +++ b/Dalamud/Hooking/Internal/HookManager.cs @@ -68,6 +68,9 @@ namespace Dalamud.Hooking.Internal return address; } + if (address.ToInt64() <= 0) + throw new InvalidOperationException($"Address was <= 0, this can't be happening?! ({address:X})"); + var bytes = MemoryHelper.ReadRaw(address, 8); var codeReader = new ByteArrayCodeReader(bytes); From 591b4ee032f0aa93a5d9c483734e16a8cd1365d1 Mon Sep 17 00:00:00 2001 From: goat <16760685+goaaats@users.noreply.github.com> Date: Sun, 17 Jul 2022 22:56:12 +0200 Subject: [PATCH 22/58] chore: don't save negative results from SigScanner... --- Dalamud/Game/SigScanner.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Dalamud/Game/SigScanner.cs b/Dalamud/Game/SigScanner.cs index 15a0f6116..ffc7188f4 100644 --- a/Dalamud/Game/SigScanner.cs +++ b/Dalamud/Game/SigScanner.cs @@ -326,7 +326,9 @@ namespace Dalamud.Game if (insnByte == 0xE8 || insnByte == 0xE9) scanRet = ReadJmpCallSig(scanRet); - if (this.textCache != null) + // If this is below the module, there's bound to be a problem with the sig/resolution... Let's not save it + // TODO: THIS IS A HACK! FIX THE ROOT CAUSE! + if (this.textCache != null && scanRet.ToInt64() >= this.Module.BaseAddress.ToInt64()) { this.textCache[signature] = scanRet.ToInt64() - this.Module.BaseAddress.ToInt64(); } @@ -377,7 +379,7 @@ namespace Dalamud.Game } catch (Exception e) { - Log.Warning(e, "Failed to save cache to {0}", this.cacheFile); + Log.Warning(e, "Failed to save cache to {CachePath}", this.cacheFile); } } From d21c0f194779e21beeacce4dfab78955e3bb99fe Mon Sep 17 00:00:00 2001 From: goat <16760685+goaaats@users.noreply.github.com> Date: Sun, 17 Jul 2022 22:58:29 +0200 Subject: [PATCH 23/58] build: 6.4.0.40 --- Dalamud/Dalamud.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dalamud/Dalamud.csproj b/Dalamud/Dalamud.csproj index 22d2d8b16..2dedb7f04 100644 --- a/Dalamud/Dalamud.csproj +++ b/Dalamud/Dalamud.csproj @@ -8,7 +8,7 @@ - 6.4.0.39 + 6.4.0.40 XIV Launcher addon framework $(DalamudVersion) $(DalamudVersion) From 3de56c992fb33841a91fad6008aabb127964f281 Mon Sep 17 00:00:00 2001 From: goat <16760685+goaaats@users.noreply.github.com> Date: Sun, 17 Jul 2022 23:11:46 +0200 Subject: [PATCH 24/58] fix: activate reloaded hook immediately after instantiation --- Dalamud/Hooking/Internal/ReloadedHook.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Dalamud/Hooking/Internal/ReloadedHook.cs b/Dalamud/Hooking/Internal/ReloadedHook.cs index d82b452a3..ae4425785 100644 --- a/Dalamud/Hooking/Internal/ReloadedHook.cs +++ b/Dalamud/Hooking/Internal/ReloadedHook.cs @@ -29,6 +29,7 @@ namespace Dalamud.Hooking.Internal } this.hookImpl = ReloadedHooks.Instance.CreateHook(detour, address.ToInt64()); + this.hookImpl.Activate(); HookManager.TrackedHooks.TryAdd(Guid.NewGuid(), new HookInfo(this, detour, callingAssembly)); } @@ -75,9 +76,6 @@ namespace Dalamud.Hooking.Internal lock (HookManager.HookEnableSyncRoot) { - if (!this.hookImpl.IsHookActivated) - this.hookImpl.Activate(); - if (!this.hookImpl.IsHookEnabled) this.hookImpl.Enable(); } From 2cfb9cc70645a71537880acb79e0756e2a41ac63 Mon Sep 17 00:00:00 2001 From: goat <16760685+goaaats@users.noreply.github.com> Date: Mon, 18 Jul 2022 18:55:56 +0200 Subject: [PATCH 25/58] deps: switch to goaaats.Reloaded.Hooks until concurrency fix is upstreamed https://github.com/goatcorp/goatcorp.Reloaded.Hooks/commit/7d03dac73dc0298ff85f977735d7a9e169d3ac10 --- Dalamud/Dalamud.csproj | 2 +- Dalamud/Hooking/Internal/ReloadedHook.cs | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/Dalamud/Dalamud.csproj b/Dalamud/Dalamud.csproj index 2dedb7f04..45676036d 100644 --- a/Dalamud/Dalamud.csproj +++ b/Dalamud/Dalamud.csproj @@ -74,7 +74,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/Dalamud/Hooking/Internal/ReloadedHook.cs b/Dalamud/Hooking/Internal/ReloadedHook.cs index ae4425785..28c22b4f8 100644 --- a/Dalamud/Hooking/Internal/ReloadedHook.cs +++ b/Dalamud/Hooking/Internal/ReloadedHook.cs @@ -30,6 +30,7 @@ namespace Dalamud.Hooking.Internal this.hookImpl = ReloadedHooks.Instance.CreateHook(detour, address.ToInt64()); this.hookImpl.Activate(); + this.hookImpl.Disable(); HookManager.TrackedHooks.TryAdd(Guid.NewGuid(), new HookInfo(this, detour, callingAssembly)); } From 2d6aa80bda31a91a3a892f7435cb627d36a8f2f7 Mon Sep 17 00:00:00 2001 From: liam <6005409+lmcintyre@users.noreply.github.com> Date: Thu, 21 Jul 2022 14:10:48 -0400 Subject: [PATCH 26/58] Fix ImGui Gamepad navigation. (#928) Co-authored-by: goat --- .../Interface/Internal/InterfaceManager.cs | 53 +++++++++++++------ lib/ImGuiScene | 2 +- 2 files changed, 38 insertions(+), 17 deletions(-) diff --git a/Dalamud/Interface/Internal/InterfaceManager.cs b/Dalamud/Interface/Internal/InterfaceManager.cs index 3f7ce9a4a..4df424b1b 100644 --- a/Dalamud/Interface/Internal/InterfaceManager.cs +++ b/Dalamud/Interface/Internal/InterfaceManager.cs @@ -1021,22 +1021,43 @@ namespace Dalamud.Interface.Internal if (gamepadEnabled && (ImGui.GetIO().ConfigFlags & ImGuiConfigFlags.NavEnableGamepad) > 0) { - ImGui.GetIO().NavInputs[(int)ImGuiNavInput.Activate] = gamepadState.Raw(GamepadButtons.South); - ImGui.GetIO().NavInputs[(int)ImGuiNavInput.Cancel] = gamepadState.Raw(GamepadButtons.East); - ImGui.GetIO().NavInputs[(int)ImGuiNavInput.Input] = gamepadState.Raw(GamepadButtons.North); - ImGui.GetIO().NavInputs[(int)ImGuiNavInput.Menu] = gamepadState.Raw(GamepadButtons.West); - ImGui.GetIO().NavInputs[(int)ImGuiNavInput.DpadLeft] = gamepadState.Raw(GamepadButtons.DpadLeft); - ImGui.GetIO().NavInputs[(int)ImGuiNavInput.DpadRight] = gamepadState.Raw(GamepadButtons.DpadRight); - ImGui.GetIO().NavInputs[(int)ImGuiNavInput.DpadUp] = gamepadState.Raw(GamepadButtons.DpadUp); - ImGui.GetIO().NavInputs[(int)ImGuiNavInput.DpadDown] = gamepadState.Raw(GamepadButtons.DpadDown); - ImGui.GetIO().NavInputs[(int)ImGuiNavInput.LStickLeft] = gamepadState.LeftStickLeft; - ImGui.GetIO().NavInputs[(int)ImGuiNavInput.LStickRight] = gamepadState.LeftStickRight; - ImGui.GetIO().NavInputs[(int)ImGuiNavInput.LStickUp] = gamepadState.LeftStickUp; - ImGui.GetIO().NavInputs[(int)ImGuiNavInput.LStickDown] = gamepadState.LeftStickDown; - ImGui.GetIO().NavInputs[(int)ImGuiNavInput.FocusPrev] = gamepadState.Raw(GamepadButtons.L1); - ImGui.GetIO().NavInputs[(int)ImGuiNavInput.FocusNext] = gamepadState.Raw(GamepadButtons.R1); - ImGui.GetIO().NavInputs[(int)ImGuiNavInput.TweakSlow] = gamepadState.Raw(GamepadButtons.L2); - ImGui.GetIO().NavInputs[(int)ImGuiNavInput.TweakFast] = gamepadState.Raw(GamepadButtons.R2); + var northButton = gamepadState.Raw(GamepadButtons.North) != 0; + var eastButton = gamepadState.Raw(GamepadButtons.East) != 0; + var southButton = gamepadState.Raw(GamepadButtons.South) != 0; + var westButton = gamepadState.Raw(GamepadButtons.West) != 0; + var dPadUp = gamepadState.Raw(GamepadButtons.DpadUp) != 0; + var dPadRight = gamepadState.Raw(GamepadButtons.DpadRight) != 0; + var dPadDown = gamepadState.Raw(GamepadButtons.DpadDown) != 0; + var dPadLeft = gamepadState.Raw(GamepadButtons.DpadLeft) != 0; + var leftStickUp = gamepadState.LeftStickUp; + var leftStickRight = gamepadState.LeftStickRight; + var leftStickDown = gamepadState.LeftStickDown; + var leftStickLeft = gamepadState.LeftStickLeft; + var l1Button = gamepadState.Raw(GamepadButtons.L1) != 0; + var l2Button = gamepadState.Raw(GamepadButtons.L2) != 0; + var r1Button = gamepadState.Raw(GamepadButtons.R1) != 0; + var r2Button = gamepadState.Raw(GamepadButtons.R2) != 0; + + var io = ImGui.GetIO(); + io.AddKeyEvent(ImGuiKey.GamepadFaceUp, northButton); + io.AddKeyEvent(ImGuiKey.GamepadFaceRight, eastButton); + io.AddKeyEvent(ImGuiKey.GamepadFaceDown, southButton); + io.AddKeyEvent(ImGuiKey.GamepadFaceLeft, westButton); + + io.AddKeyEvent(ImGuiKey.GamepadDpadUp, dPadUp); + io.AddKeyEvent(ImGuiKey.GamepadDpadRight, dPadRight); + io.AddKeyEvent(ImGuiKey.GamepadDpadDown, dPadDown); + io.AddKeyEvent(ImGuiKey.GamepadDpadLeft, dPadLeft); + + io.AddKeyAnalogEvent(ImGuiKey.GamepadLStickUp, leftStickUp != 0, leftStickUp); + io.AddKeyAnalogEvent(ImGuiKey.GamepadLStickRight, leftStickRight != 0, leftStickRight); + io.AddKeyAnalogEvent(ImGuiKey.GamepadLStickDown, leftStickDown != 0, leftStickDown); + io.AddKeyAnalogEvent(ImGuiKey.GamepadLStickLeft, leftStickLeft != 0, leftStickLeft); + + io.AddKeyEvent(ImGuiKey.GamepadL1, l1Button); + io.AddKeyEvent(ImGuiKey.GamepadL2, l2Button); + io.AddKeyEvent(ImGuiKey.GamepadR1, r1Button); + io.AddKeyEvent(ImGuiKey.GamepadR2, r2Button); if (gamepadState.Pressed(GamepadButtons.R3) > 0) { diff --git a/lib/ImGuiScene b/lib/ImGuiScene index 5dca91763..b92b51eb4 160000 --- a/lib/ImGuiScene +++ b/lib/ImGuiScene @@ -1 +1 @@ -Subproject commit 5dca917630078e807ecabf5b33ccb8863655d7a4 +Subproject commit b92b51eb4f8f677a11cb076f147977876d45fb6f From bf96d4dad3747e79730a8cafdd897346aa9eb374 Mon Sep 17 00:00:00 2001 From: goat Date: Thu, 21 Jul 2022 20:22:18 +0200 Subject: [PATCH 27/58] deps: update FFXIVClientStructs --- lib/FFXIVClientStructs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/FFXIVClientStructs b/lib/FFXIVClientStructs index 61af16050..a0244fc29 160000 --- a/lib/FFXIVClientStructs +++ b/lib/FFXIVClientStructs @@ -1 +1 @@ -Subproject commit 61af16050cffccf587e3f4c74e623b81edbac44d +Subproject commit a0244fc290eb64fccfac2fd36704f7371f5c5c7f From da61602896036b8382f1ba3f86cf173cf787bb62 Mon Sep 17 00:00:00 2001 From: goat Date: Fri, 22 Jul 2022 00:03:12 +0200 Subject: [PATCH 28/58] deps: actually update FFXIVClientStructs --- lib/FFXIVClientStructs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/FFXIVClientStructs b/lib/FFXIVClientStructs index a0244fc29..f8268ce60 160000 --- a/lib/FFXIVClientStructs +++ b/lib/FFXIVClientStructs @@ -1 +1 @@ -Subproject commit a0244fc290eb64fccfac2fd36704f7371f5c5c7f +Subproject commit f8268ce6057b7aae34fc0af4b630defdc5b070f5 From be648612627d5194046ccf2f23bf448de5b967f7 Mon Sep 17 00:00:00 2001 From: Aireil <33433913+Aireil@users.noreply.github.com> Date: Sat, 23 Jul 2022 15:24:36 +0200 Subject: [PATCH 29/58] fix: only run deploy job on goatcorp (#931) --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index c0eba166a..598602be8 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -38,7 +38,7 @@ jobs: deploy_stg: name: Deploy dalamud-distrib staging - if: ${{ github.event_name == 'push' }} + if: ${{ github.repository_owner == 'goatcorp' && github.event_name == 'push' }} needs: build runs-on: windows-latest steps: From cebfe5443dd6e93d5753586da6493f68682614b1 Mon Sep 17 00:00:00 2001 From: Aireil <33433913+Aireil@users.noreply.github.com> Date: Sat, 23 Jul 2022 15:25:53 +0200 Subject: [PATCH 30/58] feat: add /xlstats and /xlbranch (#930) --- Dalamud/Interface/Internal/DalamudCommands.cs | 22 +++++++++++++++++++ .../Interface/Internal/DalamudInterface.cs | 5 +++++ 2 files changed, 27 insertions(+) diff --git a/Dalamud/Interface/Internal/DalamudCommands.cs b/Dalamud/Interface/Internal/DalamudCommands.cs index 11c8cb7f0..c58b99baa 100644 --- a/Dalamud/Interface/Internal/DalamudCommands.cs +++ b/Dalamud/Interface/Internal/DalamudCommands.cs @@ -72,6 +72,18 @@ namespace Dalamud.Interface.Internal ShowInHelp = false, }); + commandManager.AddHandler("/xlstats", new CommandInfo(this.OnTogglePluginStats) + { + HelpMessage = Loc.Localize("DalamudPluginStats", "Draw plugin statistics window"), + ShowInHelp = false, + }); + + commandManager.AddHandler("/xlbranch", new CommandInfo(this.OnToggleBranchSwitcher) + { + HelpMessage = Loc.Localize("DalamudBranchSwitcher", "Draw branch switcher"), + ShowInHelp = false, + }); + commandManager.AddHandler("/xldata", new CommandInfo(this.OnDebugDrawDataMenu) { HelpMessage = Loc.Localize("DalamudDevDataMenuHelp", "Draw dev data menu DEBUG. Usage: /xldata [Data Dropdown Type]"), @@ -267,6 +279,16 @@ namespace Dalamud.Interface.Internal Service.Get().ToggleDevMenu(); } + private void OnTogglePluginStats(string command, string arguments) + { + Service.Get().TogglePluginStatsWindow(); + } + + private void OnToggleBranchSwitcher(string command, string arguments) + { + Service.Get().ToggleBranchSwitcher(); + } + private void OnDebugDrawDataMenu(string command, string arguments) { var dalamudInterface = Service.Get(); diff --git a/Dalamud/Interface/Internal/DalamudInterface.cs b/Dalamud/Interface/Internal/DalamudInterface.cs index abbc79696..6e6e21301 100644 --- a/Dalamud/Interface/Internal/DalamudInterface.cs +++ b/Dalamud/Interface/Internal/DalamudInterface.cs @@ -376,6 +376,11 @@ namespace Dalamud.Interface.Internal /// public void ToggleProfilerWindow() => this.profilerWindow.Toggle(); + /// + /// Toggles the . + /// + public void ToggleBranchSwitcher() => this.branchSwitcherWindow.Toggle(); + #endregion private void OnDraw() From 78172f06d12211c8aaba9f6a29d0059e7d551deb Mon Sep 17 00:00:00 2001 From: marzent <44967463+marzent@users.noreply.github.com> Date: Sat, 23 Jul 2022 15:26:46 +0200 Subject: [PATCH 31/58] Change `Dalamud.Boot` abort behaviour on detach (#925) --- Dalamud.Boot/dllmain.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Dalamud.Boot/dllmain.cpp b/Dalamud.Boot/dllmain.cpp index 83e678f08..5a2665f09 100644 --- a/Dalamud.Boot/dllmain.cpp +++ b/Dalamud.Boot/dllmain.cpp @@ -172,6 +172,9 @@ BOOL APIENTRY DllMain(const HMODULE hModule, const DWORD dwReason, LPVOID lpRese break; case DLL_PROCESS_DETACH: + // do not show debug message boxes on abort() here + _set_abort_behavior(0, _WRITE_ABORT_MSG); + // process is terminating; don't bother cleaning up if (lpReserved) return TRUE; From 62831f8d1e15c7c1e458ea843d5b7a5649b2aba2 Mon Sep 17 00:00:00 2001 From: kizer Date: Sat, 23 Jul 2022 22:29:17 +0900 Subject: [PATCH 32/58] Make anonymous feedbacks explicit (#932) --- .../PluginInstaller/PluginInstallerWindow.cs | 135 +++++++++++++++--- 1 file changed, 112 insertions(+), 23 deletions(-) diff --git a/Dalamud/Interface/Internal/Windows/PluginInstaller/PluginInstallerWindow.cs b/Dalamud/Interface/Internal/Windows/PluginInstaller/PluginInstallerWindow.cs index 237fd61ca..ced291282 100644 --- a/Dalamud/Interface/Internal/Windows/PluginInstaller/PluginInstallerWindow.cs +++ b/Dalamud/Interface/Internal/Windows/PluginInstaller/PluginInstallerWindow.cs @@ -60,14 +60,17 @@ namespace Dalamud.Interface.Internal.Windows.PluginInstaller private bool errorModalDrawing = true; private bool errorModalOnNextFrame = false; private string errorModalMessage = string.Empty; + private TaskCompletionSource? errorModalTaskCompletionSource; private bool feedbackModalDrawing = true; private bool feedbackModalOnNextFrame = false; + private bool feedbackModalOnNextFrameDontClear = false; private string feedbackModalBody = string.Empty; private string feedbackModalContact = string.Empty; private bool feedbackModalIncludeException = false; private PluginManifest? feedbackPlugin = null; private bool feedbackIsTesting = false; + private bool feedbackIsAnonymous = false; private int updatePluginCount = 0; private List? updatedPlugins; @@ -526,6 +529,7 @@ namespace Dalamud.Interface.Internal.Windows.PluginInstaller if (ImGui.Button(Locs.ErrorModalButton_Ok, new Vector2(buttonWidth, 40))) { ImGui.CloseCurrentPopup(); + errorModalTaskCompletionSource?.SetResult(); } ImGui.EndPopup(); @@ -568,7 +572,43 @@ namespace Dalamud.Interface.Internal.Windows.PluginInstaller ImGui.Spacing(); - ImGui.InputText(Locs.FeedbackModal_ContactInformation, ref this.feedbackModalContact, 100); + if (ImGui.Checkbox(Locs.FeedbackModal_ContactAnonymous, ref this.feedbackIsAnonymous)) + { + if (this.feedbackIsAnonymous) + this.feedbackModalContact = string.Empty; + } + + if (this.feedbackIsAnonymous) + { + ImGui.BeginDisabled(); + ImGui.InputText(Locs.FeedbackModal_ContactInformation, ref this.feedbackModalContact, 0); + ImGui.EndDisabled(); + ImGui.PushStyleColor(ImGuiCol.Text, ImGuiColors.DalamudRed); + ImGui.Text(Locs.FeedbackModal_ContactAnonymousWarning); + ImGui.PopStyleColor(); + } + else + { + ImGui.InputText(Locs.FeedbackModal_ContactInformation, ref this.feedbackModalContact, 100); + + ImGui.SameLine(); + + if (ImGui.Button(Locs.FeedbackModal_ContactInformationDiscordButton)) + { + Process.Start(new ProcessStartInfo(Locs.FeedbackModal_ContactInformationDiscordUrl) + { + UseShellExecute = true, + }); + } + + ImGui.Text(Locs.FeedbackModal_ContactInformationHelp); + + ImGui.PushStyleColor(ImGuiCol.Text, ImGuiColors.DalamudRed); + ImGui.Text(Locs.FeedbackModal_ContactInformationWarning); + ImGui.PopStyleColor(); + } + + ImGui.Spacing(); ImGui.Checkbox(Locs.FeedbackModal_IncludeLastError, ref this.feedbackModalIncludeException); ImGui.TextColored(ImGuiColors.DalamudGrey, Locs.FeedbackModal_IncludeLastErrorHint); @@ -582,25 +622,52 @@ namespace Dalamud.Interface.Internal.Windows.PluginInstaller if (ImGui.Button(Locs.ErrorModalButton_Ok, new Vector2(buttonWidth, 40))) { - if (this.feedbackPlugin != null) + if (!this.feedbackIsAnonymous && string.IsNullOrWhiteSpace(this.feedbackModalContact)) { - Task.Run(async () => await BugBait.SendFeedback(this.feedbackPlugin, this.feedbackIsTesting, this.feedbackModalBody, this.feedbackModalContact, this.feedbackModalIncludeException)) - .ContinueWith( - t => - { - var notif = Service.Get(); - if (t.IsCanceled || t.IsFaulted) - notif.AddNotification(Locs.FeedbackModal_NotificationError, Locs.FeedbackModal_Title, NotificationType.Error); - else - notif.AddNotification(Locs.FeedbackModal_NotificationSuccess, Locs.FeedbackModal_Title, NotificationType.Success); - }); + this.ShowErrorModal(Locs.FeedbackModal_ContactInformationRequired) + .ContinueWith(_ => + { + this.feedbackModalOnNextFrameDontClear = true; + this.feedbackModalOnNextFrame = true; + }); } else { - Log.Error("FeedbackPlugin was null."); - } + if (this.feedbackPlugin != null) + { + Task.Run(async () => await BugBait.SendFeedback( + this.feedbackPlugin, + this.feedbackIsTesting, + this.feedbackModalBody, + this.feedbackModalContact, + this.feedbackModalIncludeException)) + .ContinueWith( + t => + { + var notif = Service.Get(); + if (t.IsCanceled || t.IsFaulted) + { + notif.AddNotification( + Locs.FeedbackModal_NotificationError, + Locs.FeedbackModal_Title, + NotificationType.Error); + } + else + { + notif.AddNotification( + Locs.FeedbackModal_NotificationSuccess, + Locs.FeedbackModal_Title, + NotificationType.Success); + } + }); + } + else + { + Log.Error("FeedbackPlugin was null."); + } - ImGui.CloseCurrentPopup(); + ImGui.CloseCurrentPopup(); + } } ImGui.EndPopup(); @@ -611,9 +678,17 @@ namespace Dalamud.Interface.Internal.Windows.PluginInstaller ImGui.OpenPopup(modalTitle); this.feedbackModalOnNextFrame = false; this.feedbackModalDrawing = true; - this.feedbackModalBody = string.Empty; - this.feedbackModalContact = string.Empty; - this.feedbackModalIncludeException = false; + if (!this.feedbackModalOnNextFrameDontClear) + { + this.feedbackModalBody = string.Empty; + this.feedbackModalContact = string.Empty; + this.feedbackModalIncludeException = false; + this.feedbackIsAnonymous = false; + } + else + { + this.feedbackModalOnNextFrameDontClear = false; + } } } @@ -2113,9 +2188,7 @@ namespace Dalamud.Interface.Internal.Windows.PluginInstaller { Log.Error(ex, $"Plugin installer threw an error during removal of {plugin.Name}"); - this.errorModalMessage = Locs.ErrorModal_DeleteFail(plugin.Name); - this.errorModalDrawing = true; - this.errorModalOnNextFrame = true; + this.ShowErrorModal(Locs.ErrorModal_DeleteFail(plugin.Name)); } } @@ -2349,11 +2422,13 @@ namespace Dalamud.Interface.Internal.Windows.PluginInstaller return true; } - private void ShowErrorModal(string message) + private Task ShowErrorModal(string message) { this.errorModalMessage = message; this.errorModalDrawing = true; this.errorModalOnNextFrame = true; + this.errorModalTaskCompletionSource = new TaskCompletionSource(); + return this.errorModalTaskCompletionSource!.Task; } private void UpdateCategoriesOnSearchChange() @@ -2631,12 +2706,26 @@ namespace Dalamud.Interface.Internal.Windows.PluginInstaller public static string FeedbackModal_Title => Loc.Localize("InstallerFeedback", "Send Feedback"); - public static string FeedbackModal_Text(string pluginName) => Loc.Localize("InstallerFeedbackInfo", "You can send feedback to the developer of \"{0}\" here.\nYou can include your Discord tag or email address if you wish to give them the opportunity to answer.\n\nIf you put your Discord name into the contact information field, please join the XIVLauncher discord server so we can get in touch.").Format(pluginName); + public static string FeedbackModal_Text(string pluginName) => Loc.Localize("InstallerFeedbackInfo", "You can send feedback to the developer of \"{0}\" here.").Format(pluginName); public static string FeedbackModal_HasUpdate => Loc.Localize("InstallerFeedbackHasUpdate", "A new version of this plugin is available, please update before reporting bugs."); + public static string FeedbackModal_ContactAnonymous => Loc.Localize("InstallerFeedbackContactAnonymous", "Submit feedback anonymously"); + + public static string FeedbackModal_ContactAnonymousWarning => Loc.Localize("InstallerFeedbackContactAnonymousWarning", "No response will be forthcoming.\nUntick \"{0}\" and provide contact information if you need help.").Format(FeedbackModal_ContactAnonymous); + public static string FeedbackModal_ContactInformation => Loc.Localize("InstallerFeedbackContactInfo", "Contact information"); + public static string FeedbackModal_ContactInformationHelp => Loc.Localize("InstallerFeedbackContactInfoHelp", "Discord usernames and e-mail addresses are accepted.\nIf you submit a Discord username, please join our discord server so that we can reach out to you easier."); + + public static string FeedbackModal_ContactInformationWarning => Loc.Localize("InstallerFeedbackContactInfoWarning", "Do not submit in-game character names."); + + public static string FeedbackModal_ContactInformationRequired => Loc.Localize("InstallerFeedbackContactInfoRequired", "Contact information has not been provided. If you do not want to provide contact information, tick on \"{0}\" above.").Format(FeedbackModal_ContactAnonymous); + + public static string FeedbackModal_ContactInformationDiscordButton => Loc.Localize("ContactInformationDiscordButton", "Join Goat Place Discord"); + + public static string FeedbackModal_ContactInformationDiscordUrl => Loc.Localize("ContactInformationDiscordUrl", "https://goat.place/"); + public static string FeedbackModal_IncludeLastError => Loc.Localize("InstallerFeedbackIncludeLastError", "Include last error message"); public static string FeedbackModal_IncludeLastErrorHint => Loc.Localize("InstallerFeedbackIncludeLastErrorHint", "This option can give the plugin developer useful feedback on what exactly went wrong."); From 1574ef7c33b4ea36d846c1f62c2a6f20e8d52795 Mon Sep 17 00:00:00 2001 From: Aireil <33433913+Aireil@users.noreply.github.com> Date: Sat, 23 Jul 2022 15:47:24 +0200 Subject: [PATCH 33/58] Rework plugin deletion + misc (#927) * fix: stuck overlay on errors * feat: delete old plugin versions * feat: ignore error tag when outdated, banned, or orphaned * feat: rework plugin deletion --- .../PluginInstaller/PluginInstallerWindow.cs | 73 ++++++++++++++----- Dalamud/Plugin/Internal/PluginManager.cs | 27 ++++++- Dalamud/Plugin/Internal/Types/LocalPlugin.cs | 11 +++ .../Internal/Types/LocalPluginManifest.cs | 5 ++ 4 files changed, 97 insertions(+), 19 deletions(-) diff --git a/Dalamud/Interface/Internal/Windows/PluginInstaller/PluginInstallerWindow.cs b/Dalamud/Interface/Internal/Windows/PluginInstaller/PluginInstallerWindow.cs index ced291282..c8ec0a9ac 100644 --- a/Dalamud/Interface/Internal/Windows/PluginInstaller/PluginInstallerWindow.cs +++ b/Dalamud/Interface/Internal/Windows/PluginInstaller/PluginInstallerWindow.cs @@ -1689,7 +1689,8 @@ namespace Dalamud.Interface.Internal.Windows.PluginInstaller } // Load error - if (plugin.State is PluginState.LoadError or PluginState.DependencyResolutionFailed && plugin.CheckPolicy()) + if (plugin.State is PluginState.LoadError or PluginState.DependencyResolutionFailed && plugin.CheckPolicy() + && !plugin.IsOutdated && !plugin.IsBanned && !plugin.IsOrphaned) { label += Locs.PluginTitleMod_LoadError; trouble = true; @@ -1749,6 +1750,12 @@ namespace Dalamud.Interface.Internal.Windows.PluginInstaller trouble = true; } + // Scheduled for deletion + if (plugin.Manifest.ScheduledForDeletion) + { + label += Locs.PluginTitleMod_ScheduledForDeletion; + } + ImGui.PushID($"installed{index}{plugin.Manifest.InternalName}"); var hasChangelog = !plugin.Manifest.Changelog.IsNullOrEmpty(); @@ -1959,7 +1966,10 @@ namespace Dalamud.Interface.Internal.Windows.PluginInstaller unloadTask.Wait(); if (!unloadTask.Result) + { + this.enableDisableStatus = OperationStatus.Complete; return; + } var disableTask = Task.Run(() => plugin.Disable()) .ContinueWith(this.DisplayErrorContinuation, Locs.ErrorModal_DisableFail(plugin.Name)); @@ -1985,7 +1995,10 @@ namespace Dalamud.Interface.Internal.Windows.PluginInstaller enableTask.Wait(); if (!enableTask.Result) + { + this.enableDisableStatus = OperationStatus.Complete; return; + } var loadTask = Task.Run(() => plugin.LoadAsync(PluginLoadReason.Installer)) .ContinueWith(this.DisplayErrorContinuation, Locs.ErrorModal_LoadFail(plugin.Name)); @@ -2152,8 +2165,10 @@ namespace Dalamud.Interface.Internal.Windows.PluginInstaller var pluginManager = Service.Get(); + var devNotDeletable = plugin.IsDev && plugin.State != PluginState.Unloaded && plugin.State != PluginState.DependencyResolutionFailed; + ImGui.SameLine(); - if (plugin.State != PluginState.Unloaded && plugin.State != PluginState.LoadError) + if (plugin.State == PluginState.Loaded || devNotDeletable) { ImGui.PushFont(InterfaceManager.IconFont); ImGuiComponents.DisabledButton(FontAwesomeIcon.TrashAlt.ToIconString()); @@ -2161,18 +2176,9 @@ namespace Dalamud.Interface.Internal.Windows.PluginInstaller if (ImGui.IsItemHovered()) { - ImGui.SetTooltip(Locs.PluginButtonToolTip_DeletePluginLoaded); - } - } - else if (plugin.HasEverStartedLoad && !plugin.IsDev) - { - ImGui.PushFont(InterfaceManager.IconFont); - ImGuiComponents.DisabledButton(FontAwesomeIcon.TrashAlt.ToIconString()); - ImGui.PopFont(); - - if (ImGui.IsItemHovered()) - { - ImGui.SetTooltip(Locs.PluginButtonToolTip_DeletePluginRestricted); + ImGui.SetTooltip(plugin.State == PluginState.Loaded + ? Locs.PluginButtonToolTip_DeletePluginLoaded + : Locs.PluginButtonToolTip_DeletePluginRestricted); } } else @@ -2181,8 +2187,19 @@ namespace Dalamud.Interface.Internal.Windows.PluginInstaller { try { - plugin.DllFile.Delete(); - pluginManager.RemovePlugin(plugin); + if (plugin.IsDev) + { + plugin.DllFile.Delete(); + } + else + { + plugin.ScheduleDeletion(!plugin.Manifest.ScheduledForDeletion); + } + + if (plugin.State is PluginState.Unloaded or PluginState.DependencyResolutionFailed) + { + pluginManager.RemovePlugin(plugin); + } } catch (Exception ex) { @@ -2194,7 +2211,21 @@ namespace Dalamud.Interface.Internal.Windows.PluginInstaller if (ImGui.IsItemHovered()) { - ImGui.SetTooltip(Locs.PluginButtonToolTip_DeletePlugin); + string tooltipMessage; + if (plugin.Manifest.ScheduledForDeletion) + { + tooltipMessage = Locs.PluginButtonToolTip_DeletePluginScheduledCancel; + } + else if (plugin.State is PluginState.Unloaded or PluginState.DependencyResolutionFailed) + { + tooltipMessage = Locs.PluginButtonToolTip_DeletePlugin; + } + else + { + tooltipMessage = Locs.PluginButtonToolTip_DeletePluginScheduled; + } + + ImGui.SetTooltip(tooltipMessage); } } } @@ -2530,9 +2561,11 @@ namespace Dalamud.Interface.Internal.Windows.PluginInstaller public static string PluginTitleMod_OutdatedError => Loc.Localize("InstallerOutdatedError", " (outdated)"); public static string PluginTitleMod_BannedError => Loc.Localize("InstallerBannedError", " (automatically disabled)"); - + public static string PluginTitleMod_OrphanedError => Loc.Localize("InstallerOrphanedError", " (unknown repository)"); + public static string PluginTitleMod_ScheduledForDeletion => Loc.Localize("InstallerScheduledForDeletion", " (scheduled for deletion)"); + public static string PluginTitleMod_New => Loc.Localize("InstallerNewPlugin ", " New!"); #endregion @@ -2608,6 +2641,10 @@ namespace Dalamud.Interface.Internal.Windows.PluginInstaller public static string PluginButtonToolTip_DeletePluginRestricted => Loc.Localize("InstallerDeletePluginRestricted", "Cannot delete right now - please restart the game."); + public static string PluginButtonToolTip_DeletePluginScheduled => Loc.Localize("InstallerDeletePluginScheduled", "Delete plugin on next restart"); + + public static string PluginButtonToolTip_DeletePluginScheduledCancel => Loc.Localize("InstallerDeletePluginScheduledCancel", "Cancel scheduled deletion"); + public static string PluginButtonToolTip_DeletePluginLoaded => Loc.Localize("InstallerDeletePluginLoaded", "Disable this plugin before deleting it."); public static string PluginButtonToolTip_VisitPluginUrl => Loc.Localize("InstallerVisitPluginUrl", "Visit plugin URL"); diff --git a/Dalamud/Plugin/Internal/PluginManager.cs b/Dalamud/Plugin/Internal/PluginManager.cs index 92307cff1..8f8a2a963 100644 --- a/Dalamud/Plugin/Internal/PluginManager.cs +++ b/Dalamud/Plugin/Internal/PluginManager.cs @@ -848,10 +848,17 @@ internal partial class PluginManager : IDisposable, IServiceType } else { - foreach (var versionDir in versionDirs) + for (var i = 0; i < versionDirs.Length; i++) { + var versionDir = versionDirs[i]; try { + if (i != 0) + { + Log.Information($"Old version: cleaning up {versionDir.FullName}"); + versionDir.Delete(true); + continue; + } var dllFile = new FileInfo(Path.Combine(versionDir.FullName, $"{pluginDir.Name}.dll")); if (!dllFile.Exists) { @@ -865,6 +872,21 @@ internal partial class PluginManager : IDisposable, IServiceType { Log.Information($"Missing manifest: cleaning up {versionDir.FullName}"); versionDir.Delete(true); + continue; + } + + if (manifestFile.Length == 0) + { + Log.Information($"Manifest empty: cleaning up {versionDir.FullName}"); + versionDir.Delete(true); + continue; + } + + var manifest = LocalPluginManifest.Load(manifestFile); + if (manifest.ScheduledForDeletion) + { + Log.Information($"Scheduled deletion: cleaning up {versionDir.FullName}"); + versionDir.Delete(true); } } catch (Exception ex) @@ -902,6 +924,9 @@ internal partial class PluginManager : IDisposable, IServiceType if (plugin.InstalledPlugin.Manifest.Disabled && ignoreDisabled) continue; + if (plugin.InstalledPlugin.Manifest.ScheduledForDeletion) + continue; + var result = await this.UpdateSinglePluginAsync(plugin, false, dryRun); if (result != null) updatedList.Add(result); diff --git a/Dalamud/Plugin/Internal/Types/LocalPlugin.cs b/Dalamud/Plugin/Internal/Types/LocalPlugin.cs index f1647e217..d9e4719c9 100644 --- a/Dalamud/Plugin/Internal/Types/LocalPlugin.cs +++ b/Dalamud/Plugin/Internal/Types/LocalPlugin.cs @@ -560,6 +560,7 @@ internal class LocalPlugin : IDisposable throw new InvalidPluginOperationException($"Unable to enable {this.Name}, not disabled"); this.Manifest.Disabled = false; + this.Manifest.ScheduledForDeletion = false; this.SaveManifest(); } @@ -614,6 +615,16 @@ internal class LocalPlugin : IDisposable this.SaveManifest(); } + /// + /// Schedule the deletion of this plugin on next cleanup. + /// + /// Schedule or cancel the deletion. + public void ScheduleDeletion(bool status = true) + { + this.Manifest.ScheduledForDeletion = status; + this.SaveManifest(); + } + private static void SetupLoaderConfig(LoaderConfig config) { config.IsUnloadable = true; diff --git a/Dalamud/Plugin/Internal/Types/LocalPluginManifest.cs b/Dalamud/Plugin/Internal/Types/LocalPluginManifest.cs index a3d89fd3b..634fbc75c 100644 --- a/Dalamud/Plugin/Internal/Types/LocalPluginManifest.cs +++ b/Dalamud/Plugin/Internal/Types/LocalPluginManifest.cs @@ -24,6 +24,11 @@ internal record LocalPluginManifest : PluginManifest /// public bool Testing { get; set; } + /// + /// Gets or sets a value indicating whether the plugin should be deleted during the next cleanup. + /// + public bool ScheduledForDeletion { get; set; } + /// /// Gets or sets the 3rd party repo URL that this plugin was installed from. Used to display where the plugin was /// sourced from on the installed plugin view. This should not be included in the plugin master. This value is null From 379c52f5d4c9bc2abe74ba689e2871310c8f431e Mon Sep 17 00:00:00 2001 From: kizer Date: Sun, 24 Jul 2022 00:36:37 +0900 Subject: [PATCH 34/58] Show error messagebox outside CLR from VEH (#933) --- Dalamud.Boot/veh.cpp | 35 +++++++++++++++++++---------------- Dalamud/EntryPoint.cs | 21 ++++++++------------- 2 files changed, 27 insertions(+), 29 deletions(-) diff --git a/Dalamud.Boot/veh.cpp b/Dalamud.Boot/veh.cpp index 5691d5b24..887a189bb 100644 --- a/Dalamud.Boot/veh.cpp +++ b/Dalamud.Boot/veh.cpp @@ -2,6 +2,9 @@ #include "veh.h" +#include "logging.h" +#include "utils.h" + PVOID g_veh_handle = nullptr; bool g_veh_do_full_dump = false; @@ -218,7 +221,7 @@ void print_exception_info(const EXCEPTION_POINTERS* ex, std::wostringstream& log LONG exception_handler(EXCEPTION_POINTERS* ex) { - static std::mutex s_exception_handler_mutex; + static std::recursive_mutex s_exception_handler_mutex; if (!is_whitelist_exception(ex->ExceptionRecord->ExceptionCode)) return EXCEPTION_CONTINUE_SEARCH; @@ -229,16 +232,13 @@ LONG exception_handler(EXCEPTION_POINTERS* ex) // block any other exceptions hitting the veh while the messagebox is open const auto lock = std::lock_guard(s_exception_handler_mutex); - DWORD64 module_base; - std::filesystem::path module_path; - - get_module_file_and_base(reinterpret_cast(&exception_handler), module_base, module_path); + const auto module_path = utils::loaded_module(g_hModule).path().parent_path(); #ifndef NDEBUG - std::wstring dmp_path = module_path.replace_filename(L"dalamud_appcrashd.dmp").wstring(); + const auto dmp_path = (module_path / L"dalamud_appcrashd.dmp").wstring(); #else - std::wstring dmp_path = module_path.replace_filename(L"dalamud_appcrash.dmp").wstring(); + const auto dmp_path = (module_path / L"dalamud_appcrash.dmp").wstring(); #endif - std::wstring log_path = module_path.replace_filename(L"dalamud_appcrash.log").wstring(); + const auto log_path = (module_path / L"dalamud_appcrash.log").wstring(); std::wostringstream log; log << std::format(L"Unhandled native exception occurred at {}", to_address_string(ex->ContextRecord->Rip, false)) << std::endl; @@ -262,6 +262,7 @@ LONG exception_handler(EXCEPTION_POINTERS* ex) MiniDumpWriteDump(GetCurrentProcess(), GetCurrentProcessId(), file, miniDumpType, &ex_info, nullptr, nullptr); CloseHandle(file); + std::wstring message; void* fn; if (const auto err = static_cast(g_clr->get_function_pointer( L"Dalamud.EntryPoint, Dalamud", @@ -269,25 +270,27 @@ LONG exception_handler(EXCEPTION_POINTERS* ex) L"Dalamud.EntryPoint+VehDelegate, Dalamud", nullptr, nullptr, &fn))) { - const auto formatted = std::format( + message = std::format( L"An error within the game has occurred.\n\n" L"This may be caused by a faulty plugin, a broken TexTools modification, any other third-party tool or simply a bug in the game.\n" L"Please try \"Start Over\" or \"Download Index Backup\" in TexTools, an integrity check in the XIVLauncher settings, and disabling plugins you don't need.\n\n" L"The log file is located at:\n" L"{1}\n\n" - L"Press OK to exit the application.\n\nFailed to read stack trace: {2:08x}", + L"Press OK to exit the application.\n\nFailed to read stack trace: 0x{2:08x}", dmp_path, log_path, err); - - // show in another thread to prevent messagebox from pumping messages of current thread - std::thread([&]() { - MessageBoxW(nullptr, formatted.c_str(), L"Dalamud Error", MB_OK | MB_ICONERROR | MB_TOPMOST); - }).join(); } else { - ((void(__stdcall*)(const void*, const void*, const void*))fn)(dmp_path.c_str(), log_path.c_str(), log.str().c_str()); + const auto pMessage = ((wchar_t*(__stdcall*)(const void*, const void*, const void*))fn)(dmp_path.c_str(), log_path.c_str(), log.str().c_str()); + message = pMessage; + // Don't free it, as the program's going to be quit anyway } + logging::E(std::format(L"Trapped in VEH handler: {}", message)); + + // show in another thread to prevent messagebox from pumping messages of current thread + std::thread([&]() { MessageBoxW(nullptr, message.c_str(), L"Dalamud Error", MB_ICONERROR | MB_TOPMOST | MB_OK); }).join(); + return EXCEPTION_CONTINUE_SEARCH; } diff --git a/Dalamud/EntryPoint.cs b/Dalamud/EntryPoint.cs index 029e8dd55..f0b5b884d 100644 --- a/Dalamud/EntryPoint.cs +++ b/Dalamud/EntryPoint.cs @@ -3,6 +3,7 @@ using System.Diagnostics; using System.IO; using System.Net; using System.Runtime.InteropServices; +using System.Text; using System.Threading; using System.Threading.Tasks; @@ -43,7 +44,8 @@ namespace Dalamud /// Path to minidump file created in UTF-16. /// Path to log file to create in UTF-16. /// Log text in UTF-16. - public delegate void VehDelegate(IntPtr dumpPath, IntPtr logPath, IntPtr log); + /// HGLOBAL for message. + public delegate IntPtr VehDelegate(IntPtr dumpPath, IntPtr logPath, IntPtr log); /// /// Initialize Dalamud. @@ -67,7 +69,7 @@ namespace Dalamud /// Path to minidump file created in UTF-16. /// Path to log file to create in UTF-16. /// Log text in UTF-16. - public static void VehCallback(IntPtr dumpPath, IntPtr logPath, IntPtr log) + public static IntPtr VehCallback(IntPtr dumpPath, IntPtr logPath, IntPtr log) { string stackTrace; try @@ -97,19 +99,12 @@ namespace Dalamud msg += "\n\nAdditionally, failed to write file: " + e.ToString(); } - // Show in another thread to prevent messagebox from pumping messages of current thread. - var msgThread = new Thread(() => - { - Utility.Util.Fatal( - msg.Format( + msg = msg.Format( Marshal.PtrToStringUni(dumpPath), Marshal.PtrToStringUni(logPath), - stackTrace), - "Dalamud Error", - false); - }); - msgThread.Start(); - msgThread.Join(); + stackTrace); + + return Marshal.StringToHGlobalUni(msg); } /// From 8bc4d362adf65d7afb22b6d4c40f7612961e0313 Mon Sep 17 00:00:00 2001 From: goat Date: Sat, 23 Jul 2022 20:25:05 +0200 Subject: [PATCH 35/58] feat: new VEH experience --- Dalamud.Boot/Dalamud.Boot.rc | 71 ++++++++ Dalamud.Boot/Dalamud.Boot.vcxproj | 9 +- Dalamud.Boot/Dalamud.Boot.vcxproj.filters | 9 +- Dalamud.Boot/dalamud.ico | Bin 0 -> 53179 bytes Dalamud.Boot/resource.h | 16 ++ Dalamud.Boot/veh.cpp | 192 ++++++++++++++++------ Dalamud.Boot/xivfixes.cpp | 11 +- Dalamud.Injector/EntryPoint.cs | 3 +- Dalamud/EntryPoint.cs | 8 +- Dalamud/Plugin/Internal/PluginManager.cs | 11 ++ 10 files changed, 269 insertions(+), 61 deletions(-) create mode 100644 Dalamud.Boot/Dalamud.Boot.rc create mode 100644 Dalamud.Boot/dalamud.ico create mode 100644 Dalamud.Boot/resource.h diff --git a/Dalamud.Boot/Dalamud.Boot.rc b/Dalamud.Boot/Dalamud.Boot.rc new file mode 100644 index 000000000..daa41a282 --- /dev/null +++ b/Dalamud.Boot/Dalamud.Boot.rc @@ -0,0 +1,71 @@ +// Microsoft Visual C++ generated resource script. +// +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "winres.h" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (United Kingdom) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENG) +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_UK +#pragma code_page(1252) + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE +BEGIN + "#include ""winres.h""\r\n" + "\0" +END + +3 TEXTINCLUDE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Icon +// + +// Icon with lowest ID value placed first to ensure application icon +// remains consistent on all systems. +IDI_ICON1 ICON "dalamud.ico" + +#endif // English (United Kingdom) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED + diff --git a/Dalamud.Boot/Dalamud.Boot.vcxproj b/Dalamud.Boot/Dalamud.Boot.vcxproj index e71750f47..3733fe388 100644 --- a/Dalamud.Boot/Dalamud.Boot.vcxproj +++ b/Dalamud.Boot/Dalamud.Boot.vcxproj @@ -165,14 +165,21 @@ + + + + + + + - \ No newline at end of file + diff --git a/Dalamud.Boot/Dalamud.Boot.vcxproj.filters b/Dalamud.Boot/Dalamud.Boot.vcxproj.filters index b527ec60c..27483eeed 100644 --- a/Dalamud.Boot/Dalamud.Boot.vcxproj.filters +++ b/Dalamud.Boot/Dalamud.Boot.vcxproj.filters @@ -138,5 +138,12 @@ MinHook + - \ No newline at end of file + + + + + + + diff --git a/Dalamud.Boot/dalamud.ico b/Dalamud.Boot/dalamud.ico new file mode 100644 index 0000000000000000000000000000000000000000..1cd63765d401c387188f8588076582da1369f71a GIT binary patch literal 53179 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 V`M7xULJmNEEY0jptBm2X{{ysvb7}wp literal 0 HcmV?d00001 diff --git a/Dalamud.Boot/resource.h b/Dalamud.Boot/resource.h new file mode 100644 index 000000000..51acf37df --- /dev/null +++ b/Dalamud.Boot/resource.h @@ -0,0 +1,16 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by Dalamud.Boot.rc +// +#define IDI_ICON1 101 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 102 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1001 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/Dalamud.Boot/veh.cpp b/Dalamud.Boot/veh.cpp index 887a189bb..6979b2564 100644 --- a/Dalamud.Boot/veh.cpp +++ b/Dalamud.Boot/veh.cpp @@ -1,10 +1,26 @@ #include "pch.h" +#include "resource.h" + #include "veh.h" +#include + #include "logging.h" #include "utils.h" +#pragma comment(lib, "comctl32.lib") + +#if defined _M_IX86 +#pragma comment(linker, "/manifestdependency:\"type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='x86' publicKeyToken='6595b64144ccf1df' language='*'\"") +#elif defined _M_IA64 +#pragma comment(linker, "/manifestdependency:\"type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='ia64' publicKeyToken='6595b64144ccf1df' language='*'\"") +#elif defined _M_X64 +#pragma comment(linker, "/manifestdependency:\"type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='amd64' publicKeyToken='6595b64144ccf1df' language='*'\"") +#else +#pragma comment(linker, "/manifestdependency:\"type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'\"") +#endif + PVOID g_veh_handle = nullptr; bool g_veh_do_full_dump = false; @@ -69,11 +85,11 @@ bool get_module_file_and_base(const DWORD64 address, DWORD64& module_base, std:: } -bool is_ffxiv_address(const DWORD64 address) +bool is_ffxiv_address(const wchar_t* module_name, const DWORD64 address) { DWORD64 module_base; if (std::filesystem::path module_path; get_module_file_and_base(address, module_base, module_path)) - return _wcsicmp(module_path.filename().c_str(), L"ffxiv_dx11.exe") == 0; + return _wcsicmp(module_path.filename().c_str(), module_name) == 0; return false; } @@ -118,53 +134,9 @@ std::wstring to_address_string(const DWORD64 address, const bool try_ptrderef = return value != 0 ? std::format(L"{} [{}]", addr_str, to_address_string(value, false)) : addr_str; } - -void print_exception_info(const EXCEPTION_POINTERS* ex, std::wostringstream& log) +void print_exception_info_extended(const EXCEPTION_POINTERS* ex, std::wostringstream& log) { - size_t rec_index = 0; - for (auto rec = ex->ExceptionRecord; rec; rec = rec->ExceptionRecord) - { - log << std::format(L"\nException Info #{}\n", ++rec_index); - log << std::format(L"Address: {:X}\n", rec->ExceptionCode); - log << std::format(L"Flags: {:X}\n", rec->ExceptionFlags); - log << std::format(L"Address: {:X}\n", reinterpret_cast(rec->ExceptionAddress)); - if (!rec->NumberParameters) - continue; - log << L"Parameters: "; - for (DWORD i = 0; i < rec->NumberParameters; ++i) - { - if (i != 0) - log << L", "; - log << std::format(L"{:X}", rec->ExceptionInformation[i]); - } - } - - log << L"\nCall Stack\n{"; - - STACKFRAME64 sf; - sf.AddrPC.Offset = ex->ContextRecord->Rip; - sf.AddrPC.Mode = AddrModeFlat; - sf.AddrStack.Offset = ex->ContextRecord->Rsp; - sf.AddrStack.Mode = AddrModeFlat; - sf.AddrFrame.Offset = ex->ContextRecord->Rbp; - sf.AddrFrame.Mode = AddrModeFlat; CONTEXT ctx = *ex->ContextRecord; - int frame_index = 0; - - log << std::format(L"\n [{}]\t{}", frame_index++, to_address_string(sf.AddrPC.Offset, false)); - - do - { - if (!StackWalk64(IMAGE_FILE_MACHINE_AMD64, GetCurrentProcess(), GetCurrentThread(), &sf, &ctx, nullptr, SymFunctionTableAccess64, SymGetModuleBase64, nullptr)) - break; - - log << std::format(L"\n [{}]\t{}", frame_index++, to_address_string(sf.AddrPC.Offset, false)); - - } while (sf.AddrReturn.Offset != 0 && sf.AddrPC.Offset != sf.AddrReturn.Offset); - - log << L"\n}\n"; - - ctx = *ex->ContextRecord; log << L"\nRegisters\n{"; @@ -219,6 +191,70 @@ void print_exception_info(const EXCEPTION_POINTERS* ex, std::wostringstream& log log << L"\n}\n"; } +void print_exception_info(const EXCEPTION_POINTERS* ex, std::wostringstream& log) +{ + size_t rec_index = 0; + for (auto rec = ex->ExceptionRecord; rec; rec = rec->ExceptionRecord) + { + log << std::format(L"\nException Info #{}\n", ++rec_index); + log << std::format(L"Address: {:X}\n", rec->ExceptionCode); + log << std::format(L"Flags: {:X}\n", rec->ExceptionFlags); + log << std::format(L"Address: {:X}\n", reinterpret_cast(rec->ExceptionAddress)); + if (!rec->NumberParameters) + continue; + log << L"Parameters: "; + for (DWORD i = 0; i < rec->NumberParameters; ++i) + { + if (i != 0) + log << L", "; + log << std::format(L"{:X}", rec->ExceptionInformation[i]); + } + } + + log << L"\nCall Stack\n{"; + + STACKFRAME64 sf; + sf.AddrPC.Offset = ex->ContextRecord->Rip; + sf.AddrPC.Mode = AddrModeFlat; + sf.AddrStack.Offset = ex->ContextRecord->Rsp; + sf.AddrStack.Mode = AddrModeFlat; + sf.AddrFrame.Offset = ex->ContextRecord->Rbp; + sf.AddrFrame.Mode = AddrModeFlat; + CONTEXT ctx = *ex->ContextRecord; + int frame_index = 0; + + log << std::format(L"\n [{}]\t{}", frame_index++, to_address_string(sf.AddrPC.Offset, false)); + + do + { + if (!StackWalk64(IMAGE_FILE_MACHINE_AMD64, GetCurrentProcess(), GetCurrentThread(), &sf, &ctx, nullptr, SymFunctionTableAccess64, SymGetModuleBase64, nullptr)) + break; + + log << std::format(L"\n [{}]\t{}", frame_index++, to_address_string(sf.AddrPC.Offset, false)); + + } while (sf.AddrReturn.Offset != 0 && sf.AddrPC.Offset != sf.AddrReturn.Offset); + + log << L"\n}\n"; +} + +HRESULT CALLBACK TaskDialogCallbackProc(HWND hwnd, + UINT uNotification, + WPARAM wParam, + LPARAM lParam, + LONG_PTR dwRefData) +{ + HRESULT hr = S_OK; + + switch (uNotification) + { + case TDN_CREATED: + SetWindowPos(hwnd, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE); + break; + } + + return hr; +} + LONG exception_handler(EXCEPTION_POINTERS* ex) { static std::recursive_mutex s_exception_handler_mutex; @@ -226,7 +262,8 @@ LONG exception_handler(EXCEPTION_POINTERS* ex) if (!is_whitelist_exception(ex->ExceptionRecord->ExceptionCode)) return EXCEPTION_CONTINUE_SEARCH; - if (!is_ffxiv_address(ex->ContextRecord->Rip)) + if (!is_ffxiv_address(L"ffxiv_dx11.exe", ex->ContextRecord->Rip) && + !is_ffxiv_address(L"cimgui.dll", ex->ContextRecord->Rip)) return EXCEPTION_CONTINUE_SEARCH; // block any other exceptions hitting the veh while the messagebox is open @@ -248,6 +285,8 @@ LONG exception_handler(EXCEPTION_POINTERS* ex) SymRefreshModuleList(GetCurrentProcess()); print_exception_info(ex, log); + auto window_log_str = log.str(); + print_exception_info_extended(ex, log); MINIDUMP_EXCEPTION_INFORMATION ex_info; ex_info.ClientPointers = false; @@ -257,9 +296,9 @@ LONG exception_handler(EXCEPTION_POINTERS* ex) auto miniDumpType = MiniDumpWithDataSegs; if (g_veh_do_full_dump) miniDumpType = MiniDumpWithFullMemory; - + HANDLE file = CreateFileW(dmp_path.c_str(), GENERIC_WRITE, FILE_SHARE_WRITE, nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr); - MiniDumpWriteDump(GetCurrentProcess(), GetCurrentProcessId(), file, miniDumpType, &ex_info, nullptr, nullptr); + //MiniDumpWriteDump(GetCurrentProcess(), GetCurrentProcessId(), file, miniDumpType, &ex_info, nullptr, nullptr); CloseHandle(file); std::wstring message; @@ -289,7 +328,58 @@ LONG exception_handler(EXCEPTION_POINTERS* ex) logging::E(std::format(L"Trapped in VEH handler: {}", message)); // show in another thread to prevent messagebox from pumping messages of current thread - std::thread([&]() { MessageBoxW(nullptr, message.c_str(), L"Dalamud Error", MB_ICONERROR | MB_TOPMOST | MB_OK); }).join(); + std::thread([&]() + { + int nButtonPressed = 0; + TASKDIALOGCONFIG config = {0}; + const TASKDIALOG_BUTTON buttons[] = { + {IDOK, L"Disable all plugins"}, + {IDABORT, L"Open help page"}, + }; + config.cbSize = sizeof(config); + config.hInstance = g_hModule; + config.dwCommonButtons = TDCBF_CLOSE_BUTTON; + config.pszMainIcon = MAKEINTRESOURCE(IDI_ICON1); + //config.hMainIcon = dalamud_icon; + config.pszMainInstruction = L"An error occurred"; + config.pszContent = message.c_str(); + config.pButtons = buttons; + config.cButtons = ARRAYSIZE(buttons); + config.pszExpandedInformation = window_log_str.c_str(); + config.pszWindowTitle = L"Dalamud Error"; + config.nDefaultButton = IDCLOSE; + config.cxWidth = 300; + + // Can't do this, xiv stops pumping messages here + //config.hwndParent = FindWindowA("FFXIVGAME", NULL); + + config.pfCallback = TaskDialogCallbackProc; + + TaskDialogIndirect(&config, &nButtonPressed, NULL, NULL); + switch (nButtonPressed) + { + case IDOK: + TCHAR szPath[MAX_PATH]; + if (SUCCEEDED(SHGetFolderPath(NULL, CSIDL_APPDATA, NULL, SHGFP_TYPE_CURRENT, szPath))) + { + auto appdata = std::filesystem::path(szPath); + auto safemode_file_path = ( appdata / "XIVLauncher" / ".dalamud_safemode" ); + + std::ofstream ofs(safemode_file_path); + ofs << "STAY SAFE!!!"; + ofs.close(); + } + + break; + case IDABORT: + ShellExecute(0, 0, L"https://goatcorp.github.io/faq/", 0, 0 , SW_SHOW ); + break; + case IDCANCEL: + break; + default: + break; + } + }).join(); return EXCEPTION_CONTINUE_SEARCH; } diff --git a/Dalamud.Boot/xivfixes.cpp b/Dalamud.Boot/xivfixes.cpp index 13315669c..338938fc1 100644 --- a/Dalamud.Boot/xivfixes.cpp +++ b/Dalamud.Boot/xivfixes.cpp @@ -497,6 +497,7 @@ void xivfixes::clr_failfast_hijack(bool bApply) { static const char* LogTag = "[xivfixes:clr_failfast_hijack]"; static std::optional> s_HookClrFatalError; + static std::optional> s_HookSetUnhandledExceptionFilter; if (bApply) { @@ -506,7 +507,8 @@ void xivfixes::clr_failfast_hijack(bool bApply) } s_HookClrFatalError.emplace("kernel32.dll!RaiseFailFastException (import, backup_userdata_save)", "kernel32.dll", "RaiseFailFastException", 0); - + s_HookSetUnhandledExceptionFilter.emplace("kernel32.dll!SetUnhandledExceptionFilter (lpTopLevelExceptionFilter)", "kernel32.dll", "SetUnhandledExceptionFilter", 0); + s_HookClrFatalError->set_detour([](PEXCEPTION_RECORD pExceptionRecord, _In_opt_ PCONTEXT pContextRecord, _In_ DWORD dwFlags) @@ -516,6 +518,12 @@ void xivfixes::clr_failfast_hijack(bool bApply) return s_HookClrFatalError->call_original(pExceptionRecord, pContextRecord, dwFlags); }); + s_HookSetUnhandledExceptionFilter->set_detour([](LPTOP_LEVEL_EXCEPTION_FILTER lpTopLevelExceptionFilter) -> LPTOP_LEVEL_EXCEPTION_FILTER + { + logging::I("{} SetUnhandledExceptionFilter", LogTag); + return nullptr; + }); + logging::I("{} Enable", LogTag); } else @@ -523,6 +531,7 @@ void xivfixes::clr_failfast_hijack(bool bApply) if (s_HookClrFatalError) { logging::I("{} Disable ClrFatalError", LogTag); s_HookClrFatalError.reset(); + s_HookSetUnhandledExceptionFilter.reset(); } } } diff --git a/Dalamud.Injector/EntryPoint.cs b/Dalamud.Injector/EntryPoint.cs index 7bd4cdbe9..88a240c65 100644 --- a/Dalamud.Injector/EntryPoint.cs +++ b/Dalamud.Injector/EntryPoint.cs @@ -322,7 +322,8 @@ namespace Dalamud.Injector startInfo.BootWaitMessageBox |= args.Contains("--msgbox1") ? 1 : 0; startInfo.BootWaitMessageBox |= args.Contains("--msgbox2") ? 2 : 0; startInfo.BootWaitMessageBox |= args.Contains("--msgbox3") ? 4 : 0; - startInfo.BootVehEnabled = args.Contains("--veh"); + // startInfo.BootVehEnabled = args.Contains("--veh"); + startInfo.BootVehEnabled = true; startInfo.BootVehFull = args.Contains("--veh-full"); startInfo.NoLoadPlugins = args.Contains("--no-plugin"); startInfo.NoLoadThirdPartyPlugins = args.Contains("--no-third-plugin"); diff --git a/Dalamud/EntryPoint.cs b/Dalamud/EntryPoint.cs index f0b5b884d..cfc934f36 100644 --- a/Dalamud/EntryPoint.cs +++ b/Dalamud/EntryPoint.cs @@ -81,12 +81,8 @@ namespace Dalamud stackTrace = "Fail: " + e.ToString(); } - var msg = "An error within the game has occurred.\n\n" - + "This may be caused by a faulty plugin, a broken TexTools modification, any other third-party tool or simply a bug in the game.\n" - + "Please try \"Start Over\" or \"Download Index Backup\" in TexTools, an integrity check in the XIVLauncher settings, and disabling plugins you don't need.\n\n" - + "The log file is located at:\n" - + "{1}\n\n" - + "Press OK to exit the application.\n\nStack trace:\n{2}"; + var msg = "This may be caused by a faulty plugin, a broken TexTools modification, any other third-party tool or simply a bug in the game.\n\n" + + "Please attempt an integrity check in the XIVLauncher settings, and disabling plugins you don't need."; try { diff --git a/Dalamud/Plugin/Internal/PluginManager.cs b/Dalamud/Plugin/Internal/PluginManager.cs index 8f8a2a963..fc98709ee 100644 --- a/Dalamud/Plugin/Internal/PluginManager.cs +++ b/Dalamud/Plugin/Internal/PluginManager.cs @@ -69,6 +69,17 @@ internal partial class PluginManager : IDisposable, IServiceType this.devPluginDirectory.Create(); this.SafeMode = EnvironmentConfiguration.DalamudNoPlugins || this.configuration.PluginSafeMode || this.startInfo.NoLoadPlugins; + + try + { + var appdata = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData); + this.SafeMode = this.SafeMode || File.Exists(Path.Combine(appdata, "XIVLauncher", ".dalamud_safemode")); + } + catch (Exception ex) + { + Log.Error(ex, "Couldn't check safe mode file"); + } + if (this.SafeMode) { this.configuration.PluginSafeMode = false; From 359d11f7cf80b65b6151b8c3e5b5ad1be2de9b57 Mon Sep 17 00:00:00 2001 From: goat Date: Sat, 23 Jul 2022 21:03:48 +0200 Subject: [PATCH 36/58] chore: force net6 SDK --- global.json | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 global.json diff --git a/global.json b/global.json new file mode 100644 index 000000000..9e5e1fd1d --- /dev/null +++ b/global.json @@ -0,0 +1,7 @@ +{ + "sdk": { + "version": "6.0.0", + "rollForward": "latestMajor", + "allowPrerelease": true + } +} \ No newline at end of file From 548100d9ea21e3d6a223680eb4dfac6de6f692d2 Mon Sep 17 00:00:00 2001 From: goat Date: Sat, 23 Jul 2022 21:32:21 +0200 Subject: [PATCH 37/58] deps: update Reloaded.Hooks to fork v2, fixes openprocess calls --- Dalamud.Boot/veh.cpp | 2 +- Dalamud/Dalamud.csproj | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Dalamud.Boot/veh.cpp b/Dalamud.Boot/veh.cpp index 6979b2564..1ab0e5c9b 100644 --- a/Dalamud.Boot/veh.cpp +++ b/Dalamud.Boot/veh.cpp @@ -372,7 +372,7 @@ LONG exception_handler(EXCEPTION_POINTERS* ex) break; case IDABORT: - ShellExecute(0, 0, L"https://goatcorp.github.io/faq/", 0, 0 , SW_SHOW ); + ShellExecute(0, 0, L"https://goatcorp.github.io/faq?utm_source=vectored", 0, 0 , SW_SHOW ); break; case IDCANCEL: break; diff --git a/Dalamud/Dalamud.csproj b/Dalamud/Dalamud.csproj index 45676036d..f77479ee7 100644 --- a/Dalamud/Dalamud.csproj +++ b/Dalamud/Dalamud.csproj @@ -74,7 +74,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive From ed122d393161f4593bcec4e5fb4d2d6adf2d365e Mon Sep 17 00:00:00 2001 From: goat Date: Sat, 23 Jul 2022 21:32:50 +0200 Subject: [PATCH 38/58] build: 6.4.0.41 --- Dalamud/Dalamud.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dalamud/Dalamud.csproj b/Dalamud/Dalamud.csproj index f77479ee7..0a2cb0997 100644 --- a/Dalamud/Dalamud.csproj +++ b/Dalamud/Dalamud.csproj @@ -8,7 +8,7 @@ - 6.4.0.40 + 6.4.0.41 XIV Launcher addon framework $(DalamudVersion) $(DalamudVersion) From aa1819edef5b839594ee673bde8945e3e1263da2 Mon Sep 17 00:00:00 2001 From: goat Date: Sun, 24 Jul 2022 11:46:58 +0200 Subject: [PATCH 39/58] chore: clear file-based safe mode in PluginManager --- Dalamud/Plugin/Internal/PluginManager.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/Dalamud/Plugin/Internal/PluginManager.cs b/Dalamud/Plugin/Internal/PluginManager.cs index fc98709ee..003636cff 100644 --- a/Dalamud/Plugin/Internal/PluginManager.cs +++ b/Dalamud/Plugin/Internal/PluginManager.cs @@ -73,7 +73,13 @@ internal partial class PluginManager : IDisposable, IServiceType try { var appdata = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData); - this.SafeMode = this.SafeMode || File.Exists(Path.Combine(appdata, "XIVLauncher", ".dalamud_safemode")); + var safeModeFile = Path.Combine(appdata, "XIVLauncher", ".dalamud_safemode"); + + if (File.Exists(safeModeFile)) + { + this.SafeMode = true; + File.Delete(safeModeFile); + } } catch (Exception ex) { From a585c3f1af422d1eaa36b1a52ef4f31bef5cdc02 Mon Sep 17 00:00:00 2001 From: goat Date: Sun, 24 Jul 2022 11:47:31 +0200 Subject: [PATCH 40/58] build: 6.4.0.42 --- Dalamud/Dalamud.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dalamud/Dalamud.csproj b/Dalamud/Dalamud.csproj index 0a2cb0997..eac10e742 100644 --- a/Dalamud/Dalamud.csproj +++ b/Dalamud/Dalamud.csproj @@ -8,7 +8,7 @@ - 6.4.0.41 + 6.4.0.42 XIV Launcher addon framework $(DalamudVersion) $(DalamudVersion) From 805a887f2e3eb277e31b5ce363e5727d6086eda7 Mon Sep 17 00:00:00 2001 From: goat Date: Sun, 24 Jul 2022 18:49:46 +0200 Subject: [PATCH 41/58] feat: add minidump writer --- Dalamud.Boot/Dalamud.Boot.vcxproj | 3 +- Dalamud.Boot/Dalamud.Boot.vcxproj.filters | 3 +- Dalamud.Boot/crashhandler_shared.h | 15 ++ Dalamud.Boot/dllmain.cpp | 2 +- Dalamud.Boot/veh.cpp | 78 ++++++++-- Dalamud.Boot/veh.h | 2 +- Dalamud.sln | 14 ++ DalamudCrashHandler/DalamudCrashHandler.cpp | 136 ++++++++++++++++++ DalamudCrashHandler/DalamudCrashHandler.rc | 71 +++++++++ .../DalamudCrashHandler.vcxproj | 93 ++++++++++++ .../DalamudCrashHandler.vcxproj.filters | 40 ++++++ DalamudCrashHandler/dalamud.ico | Bin 0 -> 53179 bytes DalamudCrashHandler/resource.h | 16 +++ build/DalamudBuild.cs | 16 +++ 14 files changed, 477 insertions(+), 12 deletions(-) create mode 100644 Dalamud.Boot/crashhandler_shared.h create mode 100644 DalamudCrashHandler/DalamudCrashHandler.cpp create mode 100644 DalamudCrashHandler/DalamudCrashHandler.rc create mode 100644 DalamudCrashHandler/DalamudCrashHandler.vcxproj create mode 100644 DalamudCrashHandler/DalamudCrashHandler.vcxproj.filters create mode 100644 DalamudCrashHandler/dalamud.ico create mode 100644 DalamudCrashHandler/resource.h diff --git a/Dalamud.Boot/Dalamud.Boot.vcxproj b/Dalamud.Boot/Dalamud.Boot.vcxproj index 3733fe388..77783e269 100644 --- a/Dalamud.Boot/Dalamud.Boot.vcxproj +++ b/Dalamud.Boot/Dalamud.Boot.vcxproj @@ -162,6 +162,7 @@ + @@ -182,4 +183,4 @@ - + \ No newline at end of file diff --git a/Dalamud.Boot/Dalamud.Boot.vcxproj.filters b/Dalamud.Boot/Dalamud.Boot.vcxproj.filters index 27483eeed..8b4483684 100644 --- a/Dalamud.Boot/Dalamud.Boot.vcxproj.filters +++ b/Dalamud.Boot/Dalamud.Boot.vcxproj.filters @@ -139,6 +139,7 @@ MinHook + @@ -146,4 +147,4 @@ - + \ No newline at end of file diff --git a/Dalamud.Boot/crashhandler_shared.h b/Dalamud.Boot/crashhandler_shared.h new file mode 100644 index 000000000..0c1649cc4 --- /dev/null +++ b/Dalamud.Boot/crashhandler_shared.h @@ -0,0 +1,15 @@ +#pragma once + +#include "windows.h" + +struct exception_info +{ + void* ExceptionPointers; // Cannot dereference! + DWORD ThreadId; + DWORD ProcessId; + BOOL DoFullDump; + wchar_t DumpPath[1000]; +}; + +constexpr wchar_t SHARED_INFO_FILE_NAME[] = L"DalamudCrashInfoShare"; +constexpr wchar_t CRASHDUMP_EVENT_NAME[] = L"Global\\DalamudRequestWriteDump"; diff --git a/Dalamud.Boot/dllmain.cpp b/Dalamud.Boot/dllmain.cpp index 5a2665f09..d5660e976 100644 --- a/Dalamud.Boot/dllmain.cpp +++ b/Dalamud.Boot/dllmain.cpp @@ -136,7 +136,7 @@ DWORD WINAPI InitializeImpl(LPVOID lpParam, HANDLE hMainThreadContinue) { if (utils::is_running_on_linux()) { logging::I("=> VEH was disabled, running on linux"); } else if (g_startInfo.BootVehEnabled) { - if (veh::add_handler(g_startInfo.BootVehFull)) + if (veh::add_handler(g_startInfo.BootVehFull, g_startInfo.WorkingDirectory)) logging::I("=> Done!"); else logging::I("=> Failed!"); diff --git a/Dalamud.Boot/veh.cpp b/Dalamud.Boot/veh.cpp index 1ab0e5c9b..b29c38b07 100644 --- a/Dalamud.Boot/veh.cpp +++ b/Dalamud.Boot/veh.cpp @@ -9,6 +9,8 @@ #include "logging.h" #include "utils.h" +#include "crashhandler_shared.h" + #pragma comment(lib, "comctl32.lib") #if defined _M_IX86 @@ -24,6 +26,9 @@ PVOID g_veh_handle = nullptr; bool g_veh_do_full_dump = false; +exception_info* g_crashhandler_shared_info; +HANDLE g_crashhandler_event; + bool is_whitelist_exception(const DWORD code) { switch (code) @@ -292,14 +297,19 @@ LONG exception_handler(EXCEPTION_POINTERS* ex) ex_info.ClientPointers = false; ex_info.ExceptionPointers = ex; ex_info.ThreadId = GetCurrentThreadId(); - - auto miniDumpType = MiniDumpWithDataSegs; - if (g_veh_do_full_dump) - miniDumpType = MiniDumpWithFullMemory; - HANDLE file = CreateFileW(dmp_path.c_str(), GENERIC_WRITE, FILE_SHARE_WRITE, nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr); - //MiniDumpWriteDump(GetCurrentProcess(), GetCurrentProcessId(), file, miniDumpType, &ex_info, nullptr, nullptr); - CloseHandle(file); + if (g_crashhandler_shared_info && g_crashhandler_event) + { + memset(g_crashhandler_shared_info, 0, sizeof(exception_info)); + + wcsncpy_s(g_crashhandler_shared_info->DumpPath, dmp_path.c_str(), 1000); + g_crashhandler_shared_info->ThreadId = GetThreadId(GetCurrentThread()); + g_crashhandler_shared_info->ProcessId = GetCurrentProcessId(); + g_crashhandler_shared_info->ExceptionPointers = ex; + g_crashhandler_shared_info->DoFullDump = g_veh_do_full_dump; + + SetEvent(g_crashhandler_event); + } std::wstring message; void* fn; @@ -384,7 +394,7 @@ LONG exception_handler(EXCEPTION_POINTERS* ex) return EXCEPTION_CONTINUE_SEARCH; } -bool veh::add_handler(bool doFullDump) +bool veh::add_handler(bool doFullDump, std::string workingDirectory) { if (g_veh_handle) return false; @@ -393,6 +403,58 @@ bool veh::add_handler(bool doFullDump) g_veh_do_full_dump = doFullDump; + auto file_mapping = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, sizeof(exception_info), SHARED_INFO_FILE_NAME); + if (!file_mapping) { + std::cout << "Could not map info share file.\n"; + g_crashhandler_shared_info = nullptr; + } + else + { + g_crashhandler_shared_info = (exception_info*)MapViewOfFile(file_mapping, FILE_MAP_ALL_ACCESS, 0, 0, sizeof(exception_info)); + if (!g_crashhandler_shared_info) { + std::cout << "Could not map view of info share file.\n"; + } + } + + g_crashhandler_event = CreateEvent( + NULL, // default security attributes + TRUE, // manual-reset event + FALSE, // initial state is nonsignaled + CRASHDUMP_EVENT_NAME // object name + ); + + if (!g_crashhandler_event) + { + std::cout << "Couldn't acquire event handle\n"; + } + + auto handler_path = std::filesystem::path(workingDirectory) / "DalamudCrashHandler.exe"; + + // additional information + STARTUPINFO si; + PROCESS_INFORMATION pi; + + // set the size of the structures + ZeroMemory( &si, sizeof(si) ); + si.cb = sizeof(si); + ZeroMemory( &pi, sizeof(pi) ); + + CreateProcess( handler_path.c_str(), // the path + NULL, // Command line + NULL, // Process handle not inheritable + NULL, // Thread handle not inheritable + FALSE, // Set handle inheritance to FALSE + 0, // No creation flags + NULL, // Use parent's environment block + NULL, // Use parent's starting directory + &si, // Pointer to STARTUPINFO structure + &pi // Pointer to PROCESS_INFORMATION structure (removed extra parentheses) + ); + + // Close process and thread handles. + CloseHandle( pi.hProcess ); + CloseHandle( pi.hThread ); + return g_veh_handle != nullptr; } diff --git a/Dalamud.Boot/veh.h b/Dalamud.Boot/veh.h index bf0c549f3..7820d6982 100644 --- a/Dalamud.Boot/veh.h +++ b/Dalamud.Boot/veh.h @@ -2,6 +2,6 @@ namespace veh { - bool add_handler(bool doFullDump); + bool add_handler(bool doFullDump, std::string workingDirectory); bool remove_handler(); } diff --git a/Dalamud.sln b/Dalamud.sln index 546e31dfe..a9cdf123d 100644 --- a/Dalamud.sln +++ b/Dalamud.sln @@ -34,6 +34,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FFXIVClientStructs", "lib\F EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FFXIVClientStructs.Generators", "lib\FFXIVClientStructs\FFXIVClientStructs.Generators\FFXIVClientStructs.Generators.csproj", "{05AB2F46-268B-4915-806F-DDF813E2D59D}" EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "DalamudCrashHandler", "DalamudCrashHandler\DalamudCrashHandler.vcxproj", "{317A264C-920B-44A1-8A34-F3A6827B0705}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -186,6 +188,18 @@ Global {05AB2F46-268B-4915-806F-DDF813E2D59D}.Release|x64.Build.0 = Release|Any CPU {05AB2F46-268B-4915-806F-DDF813E2D59D}.Release|x86.ActiveCfg = Release|Any CPU {05AB2F46-268B-4915-806F-DDF813E2D59D}.Release|x86.Build.0 = Release|Any CPU + {317A264C-920B-44A1-8A34-F3A6827B0705}.Debug|Any CPU.ActiveCfg = Debug|x64 + {317A264C-920B-44A1-8A34-F3A6827B0705}.Debug|Any CPU.Build.0 = Debug|x64 + {317A264C-920B-44A1-8A34-F3A6827B0705}.Debug|x64.ActiveCfg = Debug|x64 + {317A264C-920B-44A1-8A34-F3A6827B0705}.Debug|x64.Build.0 = Debug|x64 + {317A264C-920B-44A1-8A34-F3A6827B0705}.Debug|x86.ActiveCfg = Debug|Win32 + {317A264C-920B-44A1-8A34-F3A6827B0705}.Debug|x86.Build.0 = Debug|Win32 + {317A264C-920B-44A1-8A34-F3A6827B0705}.Release|Any CPU.ActiveCfg = Release|x64 + {317A264C-920B-44A1-8A34-F3A6827B0705}.Release|Any CPU.Build.0 = Release|x64 + {317A264C-920B-44A1-8A34-F3A6827B0705}.Release|x64.ActiveCfg = Release|x64 + {317A264C-920B-44A1-8A34-F3A6827B0705}.Release|x64.Build.0 = Release|x64 + {317A264C-920B-44A1-8A34-F3A6827B0705}.Release|x86.ActiveCfg = Release|Win32 + {317A264C-920B-44A1-8A34-F3A6827B0705}.Release|x86.Build.0 = Release|Win32 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/DalamudCrashHandler/DalamudCrashHandler.cpp b/DalamudCrashHandler/DalamudCrashHandler.cpp new file mode 100644 index 000000000..e7bc79055 --- /dev/null +++ b/DalamudCrashHandler/DalamudCrashHandler.cpp @@ -0,0 +1,136 @@ +#include +#include +#include +#include + +#include "../Dalamud.Boot/crashhandler_shared.h" + +DWORD WINAPI MyThreadFunction(LPVOID lpParam) +{ + while (true) + { + PROCESSENTRY32 entry; + entry.dwSize = sizeof(PROCESSENTRY32); + + HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, NULL); + + if (Process32First(snapshot, &entry) == TRUE) + { + bool had_xiv = false; + + while (Process32Next(snapshot, &entry) == TRUE) + { + // Exit if there's another crash handler + // TODO(goat): We should make this more robust and ensure that there is one per PID + if (_wcsicmp(entry.szExeFile, L"DalamudCrashHandler.exe") == 0 && + entry.th32ProcessID != GetCurrentProcessId()) + { + ExitProcess(0); + break; + } + + if (_wcsicmp(entry.szExeFile, L"ffxiv_dx11.exe") == 0) + { + had_xiv = true; + } + } + + if (!had_xiv) + { + ExitProcess(0); + break; + } + } + + CloseHandle(snapshot); + + Sleep(1000); + } +} + +int main() +{ + CreateThread( + NULL, // default security attributes + 0, // use default stack size + MyThreadFunction, // thread function name + NULL, // argument to thread function + 0, // use default creation flags + NULL); // returns the thread identifier + + auto file_mapping = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, sizeof(exception_info), SHARED_INFO_FILE_NAME); + if (!file_mapping) { + std::cout << "Could not map info share file.\n"; + return -2; + } + + auto file_ptr = MapViewOfFile(file_mapping, FILE_MAP_READ, 0, 0, sizeof(exception_info)); + if (!file_ptr) { + std::cout << "Could not map view of info share file.\n"; + return -3; + } + + std::cout << "Waiting for crash...\n"; + + auto crash_event = CreateEvent( + NULL, // default security attributes + TRUE, // manual-reset event + FALSE, // initial state is nonsignaled + CRASHDUMP_EVENT_NAME // object name + ); + + if (!crash_event) + { + std::cout << "Couldn't acquire event handle\n"; + return -1; + } + + auto wait_result = WaitForSingleObject(crash_event, INFINITE); + std::cout << "Crash triggered, writing dump!\n"; + + auto info_share = (exception_info*)file_ptr; + + if (!info_share->ExceptionPointers) + { + std::cout << "info_share->ExceptionPointers was nullptr\n"; + return -4; + } + + MINIDUMP_EXCEPTION_INFORMATION mdmp_info; + mdmp_info.ClientPointers = true; + mdmp_info.ExceptionPointers = (PEXCEPTION_POINTERS)info_share->ExceptionPointers; + mdmp_info.ThreadId = info_share->ThreadId; + + + std::cout << "Dump for " << info_share->ProcessId << std::endl; + HANDLE file = CreateFileW(info_share->DumpPath, GENERIC_WRITE, FILE_SHARE_WRITE, nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr); + if (!file) + { + auto hr = GetLastError(); + std::cout << "Failed to open dump file: " << std::hex << hr << std::endl; + return -6; + } + + auto process = OpenProcess(PROCESS_VM_READ | PROCESS_VM_WRITE | PROCESS_QUERY_INFORMATION, FALSE, info_share->ProcessId); + if (!process) + { + auto hr = GetLastError(); + std::cout << "Failed to open " << info_share->ProcessId << ": " << std::hex << hr << std::endl; + return -6; + } + + auto success = MiniDumpWriteDump(process, info_share->ProcessId, file, MiniDumpWithFullMemory, &mdmp_info, NULL, NULL); + if (!success) + { + auto hr = GetLastError(); + std::cout << "Failed: " << std::hex << hr << std::endl; + } + + // TODO(goat): Technically, we should have another event or a semaphore to block xiv while dumping... + + CloseHandle(file); + CloseHandle(process); + CloseHandle(file_mapping); + + return 0; +} diff --git a/DalamudCrashHandler/DalamudCrashHandler.rc b/DalamudCrashHandler/DalamudCrashHandler.rc new file mode 100644 index 000000000..daa41a282 --- /dev/null +++ b/DalamudCrashHandler/DalamudCrashHandler.rc @@ -0,0 +1,71 @@ +// Microsoft Visual C++ generated resource script. +// +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "winres.h" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (United Kingdom) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENG) +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_UK +#pragma code_page(1252) + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE +BEGIN + "#include ""winres.h""\r\n" + "\0" +END + +3 TEXTINCLUDE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Icon +// + +// Icon with lowest ID value placed first to ensure application icon +// remains consistent on all systems. +IDI_ICON1 ICON "dalamud.ico" + +#endif // English (United Kingdom) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED + diff --git a/DalamudCrashHandler/DalamudCrashHandler.vcxproj b/DalamudCrashHandler/DalamudCrashHandler.vcxproj new file mode 100644 index 000000000..b56628f6a --- /dev/null +++ b/DalamudCrashHandler/DalamudCrashHandler.vcxproj @@ -0,0 +1,93 @@ + + + + + Debug + x64 + + + Release + x64 + + + + 16.0 + Win32Proj + {317a264c-920b-44a1-8a34-f3a6827b0705} + DalamudCrashHandler + 10.0 + ..\bin\$(Configuration)\ + + + + Application + true + v143 + Unicode + + + Application + false + v143 + true + Unicode + + + + + + + + + + + + + + + + Level3 + true + _DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + + + Windows + true + Dbghelp.lib;$(CoreLibraryDependencies);%(AdditionalDependencies) + mainCRTStartup + + + + + Level3 + true + true + true + NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + + + Console + true + true + true + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/DalamudCrashHandler/DalamudCrashHandler.vcxproj.filters b/DalamudCrashHandler/DalamudCrashHandler.vcxproj.filters new file mode 100644 index 000000000..890f66520 --- /dev/null +++ b/DalamudCrashHandler/DalamudCrashHandler.vcxproj.filters @@ -0,0 +1,40 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Source Files + + + + + Header Files + + + Header Files + + + + + Resource Files + + + + + Resource Files + + + \ No newline at end of file diff --git a/DalamudCrashHandler/dalamud.ico b/DalamudCrashHandler/dalamud.ico new file mode 100644 index 0000000000000000000000000000000000000000..1cd63765d401c387188f8588076582da1369f71a GIT binary patch literal 53179 zcmdSB2|U#Q_CNj}!^pmsJ+w(h5i&EF8T-CevJ%7i6uk#K;FbE66 z%ZtDzg?KU}h$Y+!3xEF{&xRnC@HZ)`@1HNh?{v8lL|*=v&ub9mg9L(baiO0Pt~Ch= zlA?_uGVlTIQVIyO_%GZ*Kh^LoO8@MoqdS2BVRUpB=}&i~tv!KgYmGB$YmRehlE?n^ z^I94o5iN~zj3(vXW)1Q%$Di&19-yfAAsXrfszA$r`@D(8Bw}tei80wescB?B>0n?y zxk_2#E$|#gG%4>{H7Rd7DeAolMfIr-MYU_CrRNla(vQn{+av|pJHmRm(FhK=* z5w&&SAsXZneht!~8a&5KCN}RU6CX+LaGhkbaGpdgTqYO00G^1k`6P>>@uZc$;lxsq z^BR!VAx+At2#N3#Ls9L(Ti6BsYZOaw5Sfvkje^(Iu|d0Q>bkJ=zF zZs02c&w7%HE#B~KD2Z@0nnburB@t2-NrYIO@zzPi)M^rAXfi1UvT^~;N3}ISI)R)G zVSZf5svSf!vB4Y0nhJcKNrXZv5+NC6aTu^1T-0QAyqc5=L|1o$U0Z8n1IP+}b{|Fc zSuTt>heWJ(A`x!L0$&P=a9Wv!k6A(@1o4vyUdw;X+mDt)dWC3dF%ZDKlC0W_%J?ve zQ0he@us@#rZA&6lA|zrJmPELR$d)KKpSJ5B$)-J^CJh!5U7N|U6-+0f2%A2D093_3Q z5xXgBy+42VdpEEbAZB2rpbdlm^T4jB7#K|^fxTL7X!2bLLC&bmmur&W62Qje$f}(P z*xOxX)pl=HVh+Y?_Y{KC?_JO*x@Rl_TX+OW3~WD}zQM!+(D4G$lMT8C6R51w{X+n3 zv5AISpRB4%KCI=Y1h9?nupc0n?vofxmkGqu?f=I7gBNPockG@-3{59}p>GAij;?<>b*1YdlU_yG+jc>}o4{^nkcicrVXr}8pJIhQis{Eb^`q|}E!rp0 zW{}BISTjq(hQpru(LzgOtVDzSmZCu(!Y=N6s+~fx7g&*qHHEMb#ghnkm`Q~D2<(Au z+jma#npsaGTkZdP4A3uPWHy0;_3j9`6SOqP&0s$5K_)Lj-Z2_#FS%i_Fej_F835*; z&}SQoa2xFIJ+RYdh;86vzryCWlgoDOg88)kYaY-qtounEtjn#M6cqPJKFB+RqW1D4 zV26FTS&2k^=m>k~3D{3gk?@!CWc&lL%~{A++eu`b{UjFpRRBF%r%9efVE;y79+^Sb z1h6GK;QNTEvCV=#VPK$db--OgQG0O=`rb<>wmOjrRhyyjBG_+oq3=KvAshCa6mAkB zby3Wy9s&u2j)cR0X#nzOhrVTj4nzM&fZGpbLZOh~Fp;)YAsTA^;?Vc*#l3fN4=le8 z`|nZc+XOIQCK1l4kqAeHNQ5vZlmV(IMq4K_&~icFyFuo0(C9BrMzVF?28L*e`ArY=Slkk^pNQ84TB*G~c*b5O7K70j< zun)%I!TO``AMK)_X)uXsYfdl&b{^0{73ljA^nD5XE(Gl70e3j`Ee-NmM#AU8KAoiv z{((M;aAXw;e~^h2VZb=N#YqI$zw#4)@IhPX7yQ9zAn(4VAa4)o`!cMZbePKvAQNp8 zK7SL;r96pn67U{T0^B4Le!l<-ABvEPPN1JV|3W^$?)%sGAP=x9k1?QwQlQ^NkjXCa z!;(mZOz=rgg6_oHknmw-kjZKi!AFWrw8Z=)KE|(kknpzvPZ7{{W|&J>z`X(H(i8j- z3W*SINg@QEB@uS(Lcd&Of*mWF;0!V{|8*Dt@V(gzR(SRZPy!o?Km{2E0WU8jGQsH_ znP8==OD=@(EP=NE!+rkV4@vla2uURp`tb#wMR8k8kqPGZ;6q7(F9dTy{=LWk^xw!t z8<3kVhD3llCFJ-2 z{V-tlH!oP*i*X3rqV%7>P`?fK3e<-~Arsrzk%^Cg{m38QqrR3?*rLCc3}ZhFK9M~5T<*a43HUClVAI6W zyaT$<(d)bakLILCw!wS@hBvSWw4-DRzTGwO(IMu3^oKlu{UsU`00t%)cN*~4Kz%bU z?T@J6L;~J5z&8#2GAWcl_&5+7457pa{;D4MA-0geVAE8Z0e_)641Bb5@EHVw7IRfr zzMrA}OZokpH+qlqgYkmTJ-G+kozO1Yaj<3RnDv2oHrN(xEsYOo3=4Yx(+Ao>QR_Yg za#tY{>qSV!x(ncsdx5Xd3HcB-K4FGf0t570mVfmb<{AN#1({z4KUxa)r&0bu+`u~r zcn86}tHWI5K<}ddNF%Kw}s*769LW3B)iQD1XSE{T0t& z{{i$2G21bP0htGb%(GzJV%l1h%-R~`Jit2_cs~c;_OQl@+gv3?i$)MhZ=F7SPilkkPw zWI{Rk>X#PTps}4f#CX746k=*6Sm!u&t^c@So)H6>XBab@dtuf^*BI!xrp6c=PqG7V zi1~(}0q;8C4R)WX%>{aP5@O0&@QqWzf7K%4FPcNlung!23pAETU|k`= z7Y}R97j&H${fw^XZ5E4ui#hO51(_d)v75qtbHdoauQAFf6Uclk@V*JWQ-L=H{QW4< zbxrWeJ%D#7jQbo3f29U^Cz1$fAy=8ao=k{`IY|780bSofe8Bf4tgUd+b0yGkCg95p zbOC%9O_06pFEWn?`yvVZDCjukuGI!Gu)YO=w?F7w9PoymZq-qc`5@?8GR!x`To>&j zE`Ye~48&kZg&{T&Kz;2W7rLGefxxD-0&i7VW4B=Z5g>Cd7>LqwCEZG`+}}9H5hjw@V*Ip-U;Kr0J`Q3U$np%)i=~vWP@?9 z1Kvi!I}CV-0dH5}9RR!!gPx=FT>-jw3dVj6;zuo5-^ygd9Zr~UYZBq256m~j?59_# z6R)80DNqbx2!dR62V?V&)z8YdR(ciHAn;#7n zAO50ibnqLVZ-sHg9@g>zWS$H2oeT4Q3T(?7;0^ioJWUuk#J8vTfj0qUZV0h0%*8&C z%K^wcJANmE1JmDR_&2_wz#d9ct%rTIRT%Ufat$=dzubXbLuoP0w+o4IfsaH;2brIQ zya1YShz8vcl?9nY9PAI@?FAY5{f*xL@js(`7wjci7`G_c7La!#%>9Kt7`GpcdpYpl zMj}M{lL#SEBzzG3#uxGisLXAdAlLtoGx~#H0$;!Y=L&?Y5Sw3z^_2fIkO`pa}BY_6I%xb6=7039!aa1CE%ru*Qsl_bM1Sg+y?> z4r5Lx6L!FwGXuQl@SCmw9FxEE6xbA4V?jXsLI3@v$V68m;4Ka@ekAZb2yr+UnP`Nl z<4J#~r+@8hm}`)QKadj+WDZ!iF~j)`oL%WDk%_911JD5fK=of^@((;sCRzZ79YBza zAesPaE`j_5;4z1LBIF7duVMIi5NPTbnt=8@jYB(5QeccrC@==O`504i81h6kmORdc zm7GDa$LA5&`c?!x7>!`)7%FBQvJ-H0v;vNf7lN1-Q{T3az8y1%HPL&DZ=dT`2xvVlhEzInn$7wF~v>fds(Ml}2M| z@cI6yhJfM);sPJt1qfoJ(f=v@C|*s<9MJm+@U^9)S4h3|XxY zV#C+}Gx*7nkNhP^xgBtygji!4(Ekj66ex-pXe-qFz}aCx&p%Qt@c+^PAvcSj)7nEm z!NVgG>W)L_Z@cp)e9Z-ZzKc^Wha40)Or(0W3?BpmV; zPLQuz1+^VaXb$6FxlpW-vjhCyanN=_j4J~4uXOnr&o1V#cT5UFTMl_92IP7s0sA;$ ze*rmZs>?wd<1!PVJ>^Xq_3*bg`;a1>7vizP8{2l*dU5qW@ zcYs_9*qOm4KWrqK_yl5-4yZ$W{0Ok`Bdb1IK~`;q{B7f(!uoqZM(YyMoF3@RQD|F$ zmZ6^kH)3c6^)-eQdjb19z}*YEwnO00aH2lVuP@k_Am_JOccTQ@)xpkaft}d`1Ul0X z*xOK2f*jl!U~hrkh$!SJAV#nMbvOUiJ&G0NjppYfpuGul$DyCm3ynSWjV71@`)vo*I?|U=tMxn{)~Xu1PD!eb5ZgCggdAijYJO`Or2YK6vBaqXAoJ}Rvm)^snwNU5<=OirXS;_C!bN%f2@7$p0C+NBa zoj(A$J3(*cQ2amED(dqB@qpap0Q*b8ZUVTW_UY}~@4n5@PY#Q=M)fJ=d>>nZzMlr2 zF@^kRHeertvxeOyd^zM8zhg%qg7XzDoVD=%QO(%T&i~*B#S0_~*y{jyIp~cPS`Vjh zI)!3~8p??!fc+$3p9k!fU?a6)t!je4gU-V`Reuil6^ix7d(WAF(awor$zpfc*{R zLQj$jH~4`N3gicWTqu6P&J1Tw>)@P;^B*~D`tj^<+h5iy8f)7uu2rpxTc9&%K<)v6 zT??=u1nhUg)-V8jE38#_SgWhh^Ilk|8$f5yKrJ=Y!ZgC!)I+Gvs|*8O{|53dgPbPh zZA&4KS^8VH=s6Wi0&r%evegdGs_g!`IY2uG+$gbu+)V&`K47l`>@>ih4cJ5ATq7D} z-VfO6fL#}4zmu%m#YQG{uq^63u`vvE20g>v1K2+S_9uWH&RVW*U(CxAivJ4#R=~d% zt!K2Gl!kLQH8@LWM$f+f*$Z%^I*-~%MZkU$u-60j9*}z;$bA=J4~BF8x2TN-eYXOg z(Sp1=>_-i9fIS`b-2=`HpjPa5J=8=-!dZY1sBP3S zwU}i7^?BXzKJ*8F{O%KcU;^yIfISniPlDXv0rrbf!$|?T`-8qWg3iEMVv8f#87_4q zoF5X<^NxFMP){EMx&r4tSNB8ye+8WPz}Z8#l{)bhlRDw-?{Y?KJpn%x;I{k4k7Ps7 z-+q0e>lDr&G0>{RdPQqkQ$SyWzMsLt+0bwD8x*MRppb_IK<-I^{Uyk~4X}p*c1gev z=aDU$Bx2(h@CD%Pu>$HX%X>iXuR&L?gWRh@?(HCRL$caA$c?9~!#SN4)OteR_QbDY z{k;Fh7n1_qo!}d!!k#K&i266b_`oQBAUVL^0aOOqBY;vs-|vCmM1h?VMQd{5T+%l?>P~fZSm$SxxGM z^tEvI0`+@IL^yAP_W#Zpe=>p_)Sm(Dtk8ylZ;%4}>P6U}cEH}mj{0Q3*hmMkH8hYt zKbq46U1tYU0)5W`eZLBOlA?~*Ecj%9nIiz);Kz4y0``M|y&CL{3t(RX`c4Jx!+`xG zoK2o55sD=N`x1~loQ-821Gzti^OI1(&O;)^!#QmXoL}4oU5ofN^q=>OJ`KE^11%e1 zw+7rJus0Qe+zo)(0J9#*ya9BE400FH)B1z~x$^>cDbRNpz?}p7uCBeg65{T`pZ%gY zpyxnMFouRRfc+)d88fi&pgXsA19m$&(}gq4%WFY*;5;t{C>=1L23vG|7sy-<&b^iZ zZm44oWd-_+zJL1xl{XOT14qHWngz0N0_^tzdpKb40PNwQGg|?>K49kuxo-mOM8NI} zYxM@`jUI*A1@-5T!T){23buU};I{)^hnnu1w}5>H!`W0R)VUU!f!x8y(<6I7yVLr~WgWfD+A*n^cnNu`+z6)arw;~aOUEmCADbV71)L#Mqoj)w@ ztGZCXlJy*FVGx!2keD_y$Iy)4surp?12z_?14N%EMRwrwOS7P-U9N6_1X$` z(hcCdKfC}w@M@5|9bjKWCfvpXc945PIba8ye{MbKyDo`vf*JJYCmO&F^Kb~p5M&GI zrTQeICku(_{qy_3cLUfL&k{iH@J`ZFn#07U;(F@LC-3Vl&Bg@Y(|HKW*elZ7(?oq$kcQ#n3Xxv1D9LFOR zJM60sfc-A$OeA2Zfxd@;zTX15!#;%8xfJ4Hox=Hh9{4pmEb4GZ3fSSh(?>z)r2se7 z<3!7m@tN@3?|M#B^#Hr?h7!aCE?`d`m;MRZfAgnc*Ab1yy7U(iJ9VQzK`&r`2y%Z0 z*zW=MNWc#M@4Y0@8GqQD*Z}(~SgWwN=7O%A(}8ynpq42K#*l;u?7^^BVb2W*dlL-v zh1NF&{p3%9bAENa71#tj%+Ejm8*TtMYTx0#wZ|mD4!&?h8tBY%zz#OO9NrJO1%71d zP7=Nt)@eRB=nT|BWP(no3xUpne|8MU6bEBKYeB;Oz{acw+^`-3I7x&(;2(MY8&Ljn z7i6fJLGuFO6Yv6d3&0M#dQTCsqqPjT!A91?S~XW!xd`^}oH)qc6!uk+aU$T1gY*4p zXi<3w!&rjAhxV5Qy=PTNYuo<=_B@Y(4{_y3U=a0Aa$pfXm7 z`-DN~Vb8n>`jQ1UKdlaQ27KZ~*k9v8f1+V6gv9{%RIrgi{;)oMVXeAzLp{v~cn5+L zt?wWato|d|f6atMxCDGrDnQi0Coodn0r$zPV6Dc3+)u#!rYqsS3h;MeZz2SifWF*> zcQ>wq++prqVGQ@RB1PfSO=zA8XzwZ9Oau2ux#}U}0qM?2#`~b)uPg2AM#$*bMzf6q6_#H4m^OJT^+9vGB<;Kgb}=#uIE5e)kNRb zf!|R6u&@8-mn4EUV738*ce>CTAEG{x9@G_SLtVKBy2uF{j?L2~+!GhNWL#Dv1 zVDO$7f?#W)r9qw40JP)q9vBeRR543I*9-<7;RW{*>Z_0?WVD61!xq~GglVy5P%)C| z-{Fhni|qnpw%9VL-@j+jzJJe{0DP#NzQuiS8N#N1Smi(M3|+7T`TeeG>%wH8WC z&>+mrp=$7MY)|Gp2y*yld` z-TTtRK8E=bzKCzVd1c2K`O%>+mDW#Rn9P~7geMx9$X2&5^&59xvAj8ZyZoyzv&_PJ z>SXG{N-yM+ZP$WUQDvp%DRc3!EErS9w2o;+{MswFT~}_U*=6Hr6_1$e>*VAK++kK* zU^ypgt9azfj;me?7nO4l^|v>cD9N`xN#jg?6w9~s!7S!z!dEWKt6n?i4tVR|B$><0 z@)XI;bjn-qZ;a(zRh2oVrHloJf~xPW`t@%(*vEkkTr*lcq7(r4lPJ6ny@c`AojEQO!XEGp7ibw6ha$+`k2uEskA~ zHvK`7s+MhfB`cv;W`SdNc>m?csagkes>ZZVIP%ZGlsA2%WN>#X^nB*R85O{`!DB&nM=e%5J@S}V6lF`SM zgf$Z3J|mi8{)@wEsjhr^`=sotIoYP`dDLa4fpn}>X29JO3Gat@NmK=1`E2JT;^ghL zGe=^C-B|g>hC5FmfAYo%-#>9q@(KpzmT`5t^)sHE@%F(7682HEVsEdhIGx(mab0#$ za#dW*R4}enxn1JK%WKRTM#T1#XeYu0#$Nh5`pTN^Q7KFT{hG#ey|&7T#Z@og(VX&F zi|pZZvQL-UatNQ!4CJJXWgiTx%<{fnj$?l0>{cTY<=@*!*%pF5Iy9h9m#q=7@%bF? z+sd;;%Dn3ej#l~kSWx1o1e!|Vz2b|Ol9)pe-EC8}4ZnmROWIlLg)_g&*gY5SfZrfpX8pANS;@)V)DlT%63g7X`r&gQ zw4AWpwEVf~$c3jnyUcw8ds4V+mbH@Y%_}|l@MH@`>XmOiyXJF& zSHnRGl-~0^RB3QJDM&IW?(r7EI1y3f)q-~&p1<5ACZUq)BD`LvUsQjc5RSJhMoKi)vNgK za48Z`&%O`MY+!xLwc11UXl1o6ZAI+2)pEz>E}8EW(cukPzs~fuQZADB!oB&zg83yJ z2qm@G?7is0GwYV-6z(Y&4-@M)91^J&p$YJJ@Y&;C!ZWKdX}YoZs5lpHy-tB~`t{tE z`$d_MZtJ!crmPaowwvisbuzzN74={*^+e=-E3(1{yV8|o_i^@b8FjXQ^tdbby$>ll zps$W&NRXNPvNvU0N^hYUxAG{DZMFQ%3qB)ssh*o-%}zB&H2=g8xcu8f_7(BOg|f26 z`ChN;SBP86?a+Ey6h`nETHZG`bmCM*@kZf8)P1ttTSCRG`*L1nd8K)du)lMnvF98g;G%!I<+;6^D&tfdb$bF{ox~NC#o0oHnVOR}9Im$T4uMfDS+chnpQOby2-xOl=>Fw;4 zkN6a8g{bGXNXRw@Jg5sk&RTixI+n|BJi9c5g4QJMT`i zR*lc~UamZ8>vSmNW7v*M)nK=JB8t9w4dOa6@;a&30^Q3?*Orjvm+x6Sw6bPb)b7L5 z`nwPL1>iCj#fHh(D={%WXKb1Rg!jBF=KU7Bmr3>$shx1TeE8a@`D5+%X;ZwGE%ILBABqnj-8vrSu1N)^ptdbR_v8-wKb+H`}t#! zkSGZUHW_!F`oQp1r=ewo_`v{da#7sG(@3YR~3%j@)Y-BGgRlex~?(eI1Z#^WQw5f9uxt)wdl5fe%vy zp1d49`}!c4NPa;jcBgXC9nb{d(%T&_yVo{7+^dT%tytC|0^QU*(Q;mWI=#_!YG0uFvh{ zT3U8Knic2>a36{Bt+B`pe>leBeAJu^*-EXLPM9lxIVyzJ(J1dTsOtwwlnyHVN(zHGi0+9B;dx_r*jS#_Cj z6NQq8?WbApO@(y%a;=ZNTspE&W<^-iw=>T`$3>AVx4NI(>^s)2yUirdJ)&dWx7M*X zDQw)!aLuR(=WxRdxis18;-P_gaZfqn1f!#@8a{#06!e35hU4^u*NQVyOl)ju1F5vXKwN=H99+UNd3i?dTTVzZ&T*a@i_<3mC7{WYhvTF0iS_gDm2=6xiR_8K~1)1_3Jdc~PSd8`R*=1gdM%r4{0 zn(rpLonP2oAeDk$V4oKV;yq~OBs#LUIXHb*wv8UOspLhVlf$aCs21ILUCnP_2nKoY ztA4_!y`v%hSJV0B73H=MNymAb8|Rl zWYqYOxToNF$J(J$nffl3cDZXgPGzNZ&)SV?ADFF29GazSM?RepThsTzC-u(P6bo8# z_DbGZRq7-9`|1e6MBT5Ntr6x9Sh}c|7Ke6V znbs*@nfB`GR)UX5XkmN6^SnZ#$N5gE4n~d+>3>NOENm$9f6RG5KFtzHHahHT9%2 zJYz?NXoKRaj>oUr7MYl7 z|9m}x#Q#=m)SowSK2^YaM8ZkA_GsEbl-b_yWm87AIGT*c7yJ389dT4)o|CiKS9xb1 z1w0LXhqV};+jlH+Q_9Fwd;6!a0yO;TuZ+uuJWa=kblkffs8?FLJ`{D%<``ufGb znO4I@^}7!K_-Yr*-dB%xpNVX|{_V1}UCzaaQZ>1B*4Nq@ez6*{NwLf2%GYeH^59~4 zDC(P&!Z7C;@n3OEHC(l7+PJ*MzBLALG|p~vXpqXDE%jnEznMN-S#IfY&$ic_W18Z1 z<6Bm(4y{3usB&``)4k?bnJUx1QvL=}VkIYAFtk>hp@r0a!K=A8imllb%wIp)k2g`m zXF@xl564VB6TQ_$uT>L_i8;tJlQ7N|n_@3Ym8((T+`-q6Nn$z6TUBq&nuIIQb+1*V zeWbD~ZhgumaaymdfW-DmBQw`}$HK*lt0KliuU{Ls3YPpwQ68)&On@6UWQRB`v($s+r@$Qy-NZsz?+3Ue-{$7D@i=$I!L zlkmjGnU(Et>UMeav9>kM-uB7i-Bo$*@pih$gTg2h)3rAG_3Pha%~cXisBMY|8}Ha{ z3~QTSK`Yx_u&NGI#e7T0HLX6fgQ~H)MVXK}`Fftfc)Ty{$#jXT*cjJ5rzg*R7EQ$X zeLJQ#+;d_j?kbMTaK7hVgCo{?8J$lnD|ePmci>x--gL~rv0cyf;_R5mo`qg)=eD5p zC4wb9)0MP$6cggd0!;CmJ7X$raa4p;{rEnKNncF?sbP9>?l+-MWs4{AS$7G^)Xy7t z3$s^hk5p=&$7kKYZ|qkbCzA0f@(ouU`^Tst^7LKW!RFEs$X~axNg?mnuKL?x0HhvL*4JRdGb$x;an?v z$X3l0ue>$~{W~>;Iak7_QIF*sXzDmUInik;`^Cr1Z z7uxL_3SXBf3aaAjwc45V`PVEX(~Kf3;%wkk`T5RQ6sHUA zlGRoo^2!&LI(D6!vf}R3;b6)UMHUK1AAiNgTo*6)>24~?b`q&^I?|-Di9^~=Bjcsl zfi7ipj@Nhc6>WPvK^<&4G!=>g$M`WD>G?dO9!sJUSupGRSbv&P3H@U%}XMI_A z(}41sb@wQ&HGLxUE9;w9zT7qUg;vHgxKoZg#kg;UP2Wl=VO`6z_7e%Ks9H?OwF?`Utd~)F2^4hW3IIhAd?gr=Wy<<<>5*6)PGe0J( zD9mno{{XuwPUS{!wNqC7CLZIYgEm*LMoPr4;?ff#V>8_k^2^-5ah{#pSrpIrqFiIL zsqn4?uIEij#>2)v$6jRByd^y65ScdMXiqm|*JjZwelefzELKwx!`a;*+31NIn>qeY z^mHbbD4cRT&2`3tKFhb-Aq}F^g@mz*zGIl(kuJI&xY%e)2wMs>7j`E$nX`YHF_WLB zae(aC9R74Co#D|FI%Pgn;>3c9b1d0dEycG-Uo2RhWcs-Ke&UG}Gzl(yTxNFlCAGK; zNy)m;+&%C2D$zIRl86WeRskh6=M6+=d{aNnfYMp!nG)ADv7^ee_)7bjd? zB`-P;(m&lJvcF}N(bTd&K7-o9TrAZ1ErI3IteIhxF~Nr`^VAW?yCNodhFjD+B*L(5 zwx+yqDWWA*lV3YO_%`wAiJ1#GAJX}++>#${>^sI=)$iZkw@!I>i=wfjD6^X1O#Z~X z%j*uD8QA&Q>LQ(9|1s6@F!6$oE<1M%ZA$l<@Q0PP1k)Sp?H9gr24#~r+dIu&H~y-0 zd);Gp>?MVuefR0LOgEC#O7(+u9Cp%lPLKJ{92?kC)2O>?$y`Voer$FhS8`y3_G5Ez zg_jDxBc3-rGzYIH_o|4s$}BrrVI7;@=F!6_6mc^QsWBC-+U(;x!;Du~NyVH_MNIYE zY|e`d1+~K4Z9Es}nnngW0~cK6MIe96aGSR1*S&6DG3q(ui;tl^_>A;nBv?|#rLx;J zQlmd^7;ICtFi&j0WwNxpf-2FR)BK1}(rqY7{F*?xqi0yX^h+dHbbXWDtL5XfnpUC_ zmgc(8^OwAKUsu1a^gg*?!2j-2sy)Sm`tFl^R~2oqYfUCt2s{Xh}^JQ;ch;% zR!y{Rwqy0^$p*axkJ`%#m5*mmziO6g(DCX*#&^1yb*a>CDl@Lu2%7bEil|nJe5-G^ zs|s7hBFhurK2jVdL<#09I$ML(Y&`mHHp_soz-5r@_#^vO6NtMZcUbk>NKuQsshWaw zT+KG|V$1?^o{fQOVk^7yd=SZd;q-e^%_+N}1BXaacfW)+i^Okm z&6mO}6Bv6cY{kOQafPgW$NY@F!Zv0;^CA6pv9&TXFVsDyvE{sY3U)v9w6WnW2hKsN zl|WnQ@@Wqmf!0EQobx8qH*s3<+Nz;9iu8||SHaOzkM2Jn=g!xXo_-*Hx+ed@VeQM_ zbhq3N-~O}dm)|rjXJ;vmdTe_A*(!^mtVcO4)!^r9^AVYIVk^_CR;pi0@4C-uNfIR_ zazJun-S&p-d0hRegj1d#nQy6L$G4~KX$~ba{6my2EQM0Gi0h27${r48oxuh;xaVi} zNT0@s7h0N|Th%y5MW%_#W;IA{l6{xqCRhj?wNuEo17!B#B&^H5Jo@+z@BZa7&V6RP z-)&=N|EQ7MRJxNXg=N2DAKQ&!FHUz~-45xLAc+;aF<1jM7_}=dcVWzenQD_gK{w zhEGquDz98BQy62ZAkHS9dBiMmYHhO|?X_+Cs^L-x>(YMTu2lOACqi^;_QY#Z{b*IZ z11YWS+A7E;+N#B8uuhEFj zvoKg@Oo$t%6|lYJPZWAsg#r%kl;iUrm( zO!!S~GQ5eUl{d}W`MTu<`deDGXynal$z?NWI-eGiS z_}Ixml#j(vsIp7c+DE5pCwA;dp04%lM>-4bp8hZVby-ByZsH66RteE&8P?CJn+wBQ%j>F|^VD5$WBc^G z7c!D#^cOa@j7Jcn<4U3*eu%xu+~)r3(b@fpyVG$V8>O;o&mcY-mvkwfersj0$@}b7 z=yE#Z#51r%{Nz{Xyr5{YY`7WBrlRBPw3U0&6$aJ4aoW+uk9ERm@K z7yhh$Wp`rt+1^Mg^dHbOQj}GSI9(4p-vPy0YlG^Hi|I8sKtT%DHlkY-};tF-{EB9;&XHFyzs?S?QRF?V%j<8?_R9c3#?Bjk)zpL z?%ZEC9>ILkbacAlhNLiNzEwU^{E7cT^wF+EXQ zVW~OFe&22C>%I!n{5RBHan9-XJ}&nw0<=DDyrUIxtbHmr#kBJ}qk(mJPLWi2jnBX) zYuN71xLta9{K|3S%$F3uR;Qk#_-f7jdA1p2iZeFcgYprj2d_HK&pqr$M(XsZ;@GN&RUk%;~4ji5>L~uVk4J9in%w z)}_Bx<-uLNy>uozgX5sBUVh7`?tbR>0;iFgLpo7VE99bC zRj%C`A&%0mw{Pc#xkV@vgqCbb*&E38zHe!iKo2q^v-{{+0SjZbWx#Fo&V_Rx(OT(n zKr_xdIscaaZePXYby49tD;rIy*Xdg?Y9zU0zNgYwUhkZ>&PXuw(OEwGueT+pog<1$}F;97H3k0egC!P)0aX$ zZnvthbx*4{Nd)9xx!H3sY_8*`x0SSUT6>3l`?!PoIjjZKp4YcuHFSKaSiUc`TE2RO z;DncNnRbtrTYhujis~R=T6Jo_%bjNd*YkwR)DEz{6TcjWy@bd1ynk_WI@?9q&$@qn zu=ZMS%~gHpv%J}FRtWArHo))nj(ZB9?*E~S!eK5T`+&@}e~&z)yWLgs>G^>#Ck6~^ zSmWK_1WNL?HZJgw_u1C83a-V9iMoC|KfrT0YzGUdjPlGq$Ez*%CIZK4Dsrvz_FTqi z=*c?wUtjdDx}GcP@?9kXPf_p#(#8Brh7`abqn$({OW zcQpBsfYI{gLxFXIUbwwRW6SK6w#$*EwoPiB(%leG3}52d=y{aBJy+sZzg1EDwO!VZ zC-;!4vnG!8S(hY3%;Rs$aKiEQ=zE?MZBw*jjpM?_-1FKRxF-@adDp_+%KUE(Xr>#L zT?@@LiNXXD_&ye8r1Ll(3&?3Z(!tnNRuGClJ?#L<%aL#-2sWO~DSHDZNP%1iGyVX&pGd^@z(GEG?&=BSh z|4YTwh~VXl{`RWRtC`-pax41qe9FW;Ty}=LV$U%qw}s1j9&Y5sU{2vpr8dNqR$7Pb zrg_JEbk$i_t*-FACv;wGlastg*tf*J_S|8W0`bo_wNCcq}fHDDb6; zmfjc;c3M0BQh(UPwM2lY#$-jbI*YHpWp%;UbTsv$<*j(<7l zkN@-~g^4GS5>$2g1{+53Q0I6^_4Z)<+9$EQWcOowZeHd%_6ak+Ja*v0mUnX2m>}sH z=ar|sqY+`{RUc~YwLK?FTU=hvM{qE0k0~FX35m*%N;UE=X^h{n8NYMf#?P}*Yo>tx zbL!iynv8K zi-*q9T@WUAvh%AXjbBSLtGGVNYqTvW!}fCF*g}k#DfUuGZ_K^T$!iaeIx48es5gxB zx3{*}ds@{T>Sk-SqaR6R0u)vGyw%S`_MF1$jU@QOhJ!O|(o z${koXo0ktuLSB)DUH7Pz4%|&2uKL=7O-k2r()47msXh#q+1kM)6ll z$&~ZwZb%34KB>HAb%e9?YVU1+f_vsg_bMv|Z-&sekkRs(Gdp)io+fvkoP6dCxoXM< zW=^fILM6+m57C=P)>LPokZ8>Dx#!uvH2`1w#q8C`vRBLzkt1KpHa7~Cj93N=n2p4i zvxiFFz|APsJdYY+Ny`)yJvBwPenwk+rj299*j402M)6_pkCP(FFO9^)Ip?LK^Aa2O zl+ibiizp}eZP;2{ai3eI(C@MSiRP5-FiS z(k8cb@5phZM+{PeptCX}o0E6%7P55drF+*`liuG}*4C?BK5^I7DDhBo*&)|>`W`({{Ee28#R3-Q`a<& z5C-!(_SuV?!Pz!v!+QCHWgc%Gaox^qy#B-zWZ3z3TYwX?-;UZbeY6}GkdzB93#I=e z|42^qdDg)BIPRr=nK*HUroe2wXH?^q1YC6+|I~zyK98XG-M~GM%gs(nX_e;9EU(o_ zTR{t>FQ;TUZ!8l&nI5{|Hl_=am)sKm-WL-fdpO9je!;@FbORE^vc({gxQWSM)AG@- z_>``t3Em>yd!#;D8aF(AGX_@*tV6*pATgZFV2^|Co;HA`<^Yb(16 zFBV#uBT~x~OTOXhryu3HbeLK$%3XrD7?*h^*o-)BAJ5C-+dR7_(9_%!OSAXU#8)oa z8~0|-&D-iY;ZfZ#m-y3X@I5o5?nkmlF6RjUtDaE&oE&j}uUjxmI^NMgrM*OUY)sm?|fJ>3%7# z|HL(%G>e^S@S2N*)H>t!clLEQXtVJ9Zw}Znv|A%zx|;^XesI4R?>Rh&39n@gWL7)i7ptRODN_|D2v+fNwFb z968;*KDhnBa%yMS)w8vS4%6j?ZM3s?B#b{O$33EXZ?{$#s2_IU)qF-UqRr|0xUtED z;@+ey@3jQ)dJ5GXpw=juN%=`RHD)+%TqAn>O~UZrtDrt&86_>qlB9$+RIVd#XOB~b zn0GySz=b33dv`;2T)@D)r;=)3v%8hD(oO_l8$i{JCKW1iE#<&Viu&=}57hSTN_HzL zn!x2G(X{yeInGXQbPx!GrMQ0ks>Ly6e}a+&bxiNE6~WY!nD}ytPi>!H1hA zqUloX9l?^d7|C2?x`vB!_h|dfTxAriQ}9Z>T5Qmo>2D&TS=*)R)hzqF*{qvpGLoF` za8JzW@!l5X2+cf`!w~mt2zh@?+Lv)Fu=I{}JI}qkq1dyV-=xgmu`B-AMNx=r6!B)N z_bA!f#~vYAC&|uWQ_?s0Q)wF9bj6$5m@j27^ZhYJ1sRoLAtz%${MU)ue);(;Vc0#(3c_?*L~9T(Uno<5=!25Rxjlh6gHpTtgxlv3F$^Yv!iII*kzSz zJUyI6qfTk{S%JH5f;XRi;lxrq)^M>Hau4@v2P9&(6#P&gY-if$5%SY&S_}Wi!UB2$m(mJ*~ zOwCf7BcS2hsmg?xH#M0xrTY3zdJordJ$oa+f+LR16d0ppR_#sWs(sKdcFOQg45P`# zU|7CpqlV%TH?4{}0_WH06zZ5&F~#$)i(-Vie!sJC=~E_A>b}SWCqB?5c5IYyJI#>X z$K@)L{LpsuTBZ;&qT89BB_eS_=OZ|(x*f$En*FJTBpamIhs&}kXP2YR5y*f6M-^gjNUcep4>W(kCZ7RL} zLgKD3)^uI^VoTI|(mR-WI=%S<2LH6gh4>j`rf`U3U;UvC3zGTZ}Ws@1d8Zz_tudPGw_ao|dTl#oBE$Nn1=@mOJRL&`U zwmNsO=*>QdXZj-D`?h=@HPLvqBIEOFQ_AQ`_g80fdYSDL?kbf@cciqk-Z_X%@b^vn zax}62qJ{^%dhdB3!5Hh;g|`L|JHn^9FXgxpHPlipLTu+fSguVyn#=i8X6 zTMELts^68fS7!>SW^J>gJB@0g1s9wW?8XJ=IlWrZ=_Q|U88Z=u*qfO2^ocJpH;cA5 zKS`i{J!M(;@{Hkeo`6qWeg{r$C$Uz?8WnxsPrJrax9(a2ZuelP{EL=@`yOs3%B$w zIXI0?nut1=_Z>=-x1JJyz+si@Z8j#AFJ6>evcAmo{AG%emtMWhZDoS0 zmAtf&xcqy^2>X5R-CMNk^SE2GX*`jEa+(dz3U z%ZA+;R#%8{d&JO2(&U{eZ-hsT$M4G-7?z&jQL#ty!eiyMOXvIQlUFI4Z*k5TW5<8& zUh}T!BR-tZC^s<3fKGY%M8|8iFpAz1`HUq(+1^9g?*9XyKw!TlZn)|3vz5)PiOO?3 z%a$RixerZ2puM2Q_$8qm4L1nhl3MAnYYA1~Z%8U*k1+h+@Qf(ih-q22s_dTujW$_2 zP^TK6RdD$)3BJ#072qgI8xa$ak^>poAJ`K3)X^lIsJIzdCW2}E%I-jX%m$Ph>OdC> zk0%z9{vx;y$cq^?$I|_#hLeD;FwL2`#c}eNn_VDFVqc1EttE%g)CPNTYa;S+o^J&F ziXcpB*Lool1Re+e&;kC{1RO9+8vpmY20Bs06k0X|egS*~bdK?@6^Hu4B0BwlF=J1n zqhVj0pXM+M_%mi{iS2+}fqM&R8i;ci0FwM)9RMZ_+^+IvhP@PKHR3dQj-bnKn<#w( zT!1tRSQ`5?#5mAOK{CmjNIesza&?75j1x>($???|86>I6_9MUrz{7&6MIz!Ps)-&PH< zg3$`#l46bbPC@S2PU-B>8{9Zvg3oM*%QTTefilL<$W}FM;Q$)7X1BN3H6UEDy(Q8~ z1n8-@^*0*n!yAY*Y@V48%(_8%d0k&*Xj1bULMISS->ZPX70@Ih4qHSR|C455AUY)Q z*YSwYGjnzhT?E_P+#zunl}vU41&9A@13(++uLagYS_wP~^Ezfyn~biOAj()3Sb=Cj z-GJ1BJrMB&On-2fB!As{v{&ZmNI@T;ZvGSm#sfD?I?wjYK)C`J9vh&c%&>G3)^zah z^)ehJX?^S(N9z5&;7X)B5>CKD>QhFdJ8-DP(Kag(wfXN1!z7h+n*y#i>;XOy)X@^M zrvSrK5a>^A#nnh!DYyY!8-tqBCtyZmU$GfMKtx#;)&|%#^P^;NCqkstSp?8+5W8U7 zrypu2xCAI0mMg%hvQ~0{j$S39h<6_O2zN%!pOMiE_%*l@RMU2O3K*3HN(L1~zV9rj z_|*w3KrHzDM0fybFz}ZQ#uzws5jp>Hp*{=|*mpzxoakemjmR>yT@Hf<_r}nI_N5|C z#rZ}`g#A|rfC<|TfN~iMg(V3v+8V&KNaMjxZEC~Lz}|%D!}3ISa1rrAABVI*_NsA^ zLhB9P=A=tcHEY^GUhsFrxu0j*`#tiUoU~Wbmk4zW{?vAauLXPWfPC@h&6f2^o=_*DO6Nu|&rWfYW6w6F!!(mAK8NVoe z#$x8gE~YA+m_Eic2{xqt5RYf(&a^__QSeGlX~AFTzb*hw*nS{@ z$g(xC4SBOTe-DWhHIfOSJF`K#bk^*Ho*3zD`mnP22DH4MTEW(EbRCWd7HI3E; zR+97$>}7~kJDcAiFiSZghh+CJ(sE6u6{0pDVi&^^iV@%xoAo7D&%|lDnJl1;C_o{H zev(ec?u{v=Ysizd!DqnVu&-$<7AJKomDuCSnVtFCVD%|Fnw>}FW{l7odFH3P`g0Cm zgwB6TOeDhj&HoX7qSIwQKNVCf}TQ5J(DAlz;~${iUU;>Ajlv?Nf>=6$*tFft_sc zK?Ip@Z6LU1!^?u}4L2$jadO)%^T-gWT#%F*8U!a`uaR~EPBlCLw3PGU$L$zAeaAVR zq>$vm?gGYw_Xe%#C|f?mzSiai!jxR>59JL7TNkYRYwo$d2YS z2xLI}8Ea3o{ybooHSCj~OEwm)P!P3-rT{ppuY$OwzLmHngi8&44!#_8A5u&E=4Lz# z8}aqw{8C$) z&UvROTmVj6z-#}n2>=sz7#K`>(ps1kh*|t7q$RNL0$w2;v>mV;(l5X(&gMR>fP-ev zc;_4h+$IP-Z}_v})tU|+Dz$K}Vjc5vVr16EcKgb63qChIAh^UK#4)u9^lY+DBD(*#Vm3g0pP+vmUd$+;_60MR z|B3)GVaM&mIkRkO>>na_!ZZ<$v|Esd0nNk$%^t+qY3+o8on7N}c0=DBX)^W%L=Q~o z)`$?r|7hS+!Q)EQosv4(9ta#O*ue1%oSJX~%>=seO2MsZ>(5Kg!YeSk-`2FWw9pdJ z*k2I6gU6A6Z+nQKxkz-+6g>9dk~o;CZ9f#;YUm(oKO*hVffoq1+*^PN*z8p`bG1ynxJeSs?-x6*%uzPZj8zN900fM74+50Z4KWIHF>oId z2)YxJ*~bw#XytLtAqWuvz+^&Q=Lz7SNPV#Po+T3~BRzn9C$S_%Qr+2A(y_MJBF2Ds zI^n$>|{V(;i+`~AIOq@?n)j|}0%hJMsAnG&71 zaIopFAn^|7X2VhncHN$BZ~|ut9ujUcX7xuGtkb>WjIF;C@YxIXjh{`0=i9;cgcia2 ziUwaVqMq+Q3&$WcX#vgn@Y`fuZNE)-BCDAy(IW!|YTKo#&-fU)ifEqJ+B_kch-Sct z|FKoyita#H2fTS##AAqaGWf{Aev6>)Kdr6-Xo4tlb|FMqx&milZbYmLOaksktVGC@ zj|MK08HeF_vtdO3S+54lwU9N>_*BoD&HMY04*(N(+#X^KwLQ`CUIu9b_EiKUN)u)& zLZ|Ks?B)LLN&Yrq$UfqjlgYM2nwilhqU47Oq6ruw32)o;S}wW?k`AVb9*MVU~QnE+B>#3 z44MxU=6mJ}Ql zJSO~E;sXr7oeLd$p&5WI;j?CY#QnzxfQdWpQ2DZ*?P!?9X=7k0(hZ3{zYn3Kw>Qvp z=EhVTlgi-Hn12#Ywc!N5zbQRu590J*EB$=xIWQe_FL(ql8L1ui&cF$2+;<-E_X2#7 z=J10JbOcYDgY%E=HxLKaKxg9Q+bLxaf0p!;z{?DMn2#I#S2zP#!9G6K$4?5K;}{R^ zBGDDo?3fv!YIsUgzRK_+3+4B+*<-Cv*YZs7#`iOLzZ9S6N<`CcfbBZLir^Yri-5T{ z2HWNgwrP=21|Q{cN!lG)0=Rxh#(Z#}mzv4>6JSU=S%2)Ki2#2VFhY4Cl&bOE1(bd}3fCy{kfX5PwSIf)+;qtlbi z;^hMHn>q9=j_=iq9xSH*>>M`({$m3`vI5aq`<3ktiF;%+F*~nE>W|%kSS3+bhs_)W znh0vG!6}V@mV^@QHh6H`9fA!BkWgWk=XnQlCj}-^q5$+G#&0!5s{Iea=a%U9@Pja4 zH&F{Y9Vsw%3kMO%O@{L_c-P>ihd#_F$@vvs`X3_g3OtVeC*$ry-*tk|N({#QHjm69 zUwI)p|E#v@j9@VRsosrmX7E-XP~i0(8KZ5;3etwc$0IEboQZwA(sLzdmWFqwl~b$P=Fj-Jw2 zV&~NLPm*u6&+R>dlTyup7H}XcdkY>mTqtP`>|>0-DO7T#;-_Zf89~0{u&>AYXSFRN7#NuNczwf2 z1)ca+hJM1|0`-YBSJ@nbfb9SRDMq?~u^A=t%}m_VlJj~&x!bA_-MLlY$}ev@0`IW} z_6@eicS8M}8{_>Vnz#Ve5cP9NZkEzgEnA;G| zK;GU>Os8FEMb^1HaHY*{8I)#OWNfvQ>KdrcV0unxL3_mbVus6QIDHP;!hc)DiX<1F7F1Kdw-?dLQ% zcSP)k*#LYA=qAKuiZyf_bl-fvx6i_{g|r4S#(Tw~di%K$OALL8Py20M+2rd9d>{KD z!@UHtNN>aU6Rfw%_&%Xi%jo$Gdr>+6tkx^)=+fi$4ew@fWgf(wTPfIV2P*gk+CIb( z`h1uV9E!vL6s%`+YbIW=6`oapAdC5b7vsZic9nP(I5&e&jA_YZmQb))G;DzxPC%^p za5(=1iq~lt@MTAYXSc3DyQAUT!Y3LPGwIK{{QyvdnTByoR@+{9C!|BcgE3brvens& zkDQd!{+2RbB-R8zXa}X{e9q1fi+KQ;xXVue{>|Kw6(BG7B1R_diDpS;a&2I!ZOllc zupdI)h#5(w`L3MybFo^YiM9T0{_Pdu8Je3xS{HkJ#4ya&$P+R1?fm0{bvTo>mhHYo z8S^MGIRknQeTXsrwvHYA(D^&Uu#2Qyu%7@9bdZMhFiezmYm@`|D@EkxhYek{zs%WK z3IrZm-|$%p-CKj-1ubV-woN9G6LMw(MkCIYxhanWUE&y%K%e z`!;7wyjJ06$$$)&Nce~kfM(3Wh@&NLcQk;f*s$N1bpG?|8gSqbx)=^b97l9F&IkQ7 zlUB6-EK+kB*Ai2dAA#QhouA2LjW}rzy9osVQ)}1=(}Eb+0?Z2^u-FEGiQn3(()r1t zO&3#u%N>AikycE#e4*`Q+XMj<-QhvN^+=CXE>9rK=yu)Q|!B>cCG7n+vWs zd?e|o4u;=a$Exm?4k+@!GG$)wk;UWWpPfC?yOFH6mTda;)eXmhd#6=mgv!J!ae-K_ zI$Lmk3rs0QP{74xiTS0eSxfnd_x7z*s^KasGCCAa~(;U1M+(*aNZK|(dQE1 zSd5+jsosqUls7kTjPxtnvCAKkzs2yWLYjOsa4w;~JEa(N!`zBf{K2}03VLu!K}U&? zg*GrgF)=1TFL+u~%cxR`C5r{Jn9zJ1d^G3{q>pTGk|54+7+x%v$eqES!x!xIzof2# zS~8|nPKZXih?g{cPw->Jc+4%BRS|WDtH7^hR%WSJZl44!i#gF5&o;ZvFXnKPpHkrq zFsn#3Vjiq5Fuu7Aznu?U!(tTxChpdn^0&uqNif3RhX@&oaL5%1frl+-p!H`80+R@b zze>`BEiEmd6^li>-FyFnUr;#ll1SZW0~Q($yv~df%fHfp0Cam`&ZWhK8P4B6Iq%xG zKL(!d2oo!F$FYt5E6PLd90_9lY;op=Kbqb2der_+;&i{1pMu>fb zK7wBY{Vf%f7-|dny|h47BXkA$4#AYX4Qitzi<}u*tVVDKJa1Sr6Tiq!eOhi_)#xb0 z;AM)%>4wg^y`IGWz^dRk6xgEiz&iwe`dbwo@=d9}-g`D>z~yy)s2~|CRwCv&@!f(m zO3N62B=}y2KFtHvvmR!zJ=v8|f*y@{ zy_CbL^I5Vi76G7*<7Wg_*B*!+FgF8ZS_^K(y(hN}f*C9%wWN|h}Nd#1h-nHr0Mz{y0371DN6i|m(xWA9b0^sxC)ha?g+L6_Rrq^Gd!>ifO6p|0T}srJHSQ_t zCG6h<&mZ*u`%|x6X{DO<^RpcQT-B=~SPno|$UEhV08j*i-o3m6F@ZQRpxJ&jL-mHb zTui}SZ}Yyyw+zRF*Ae<#CS40m>oChAQ(Madpj6CZ0Ag9-cER-d$oyMvkq3Z@yYCDw zEl@1>AOt24q%wXHL&<2OObr=ZV2%WKK#(oVc?|S#WU)3e1z8kY5 z20tRQjrp<(+iVL=1Qpr=%O~09&j{z88Xb$uE)34%_q#T5sHFW8_n?6Raj&&*5k4p+SWmGUIXoASWM)^JpCP^p+zGx=FdoE_kIg=d z`+ctlx)h2`ESEMH?1;HOFEA?0;p7E?G%vCMFmd;tD_<6+d{N;UI3{U>a@ZQLq()*ie> zS|EBUaq+u?5g5Pqwsd>o0VWI>(9N*5qSN1(Smv2&x}n%P7%Di^FkA`nvD9LE@;@Jr z4*`QdfWU98)@GHsiBRi#8`ucxaqO{##M(cNCPk)i%9t8*N@S}kNKuwUoN2SZA{g|5 zU~d;eV34<>3EN-_)A4V+o5bdZ`&=%Sg*HOk0Q-TASdreVO-g-Tocyxr>9{|}WCL&7fZ{evDZS^EBeDqet@2xpi>N9GZTo)isbK zCgU1MVZe1MmF_oEzaq)XdA(m|!ZZ0ir0Nf!T9 zY#1&Jj12Ierefa)jLvX!ex;UBwwz-3V=LfHqS^j4VD;27YDa}{!E+7*0&=Ow_WFWV z5t|dbdJiIfRza3k>>qWZ#Os{pF)IPDWIrNpMT=U6V9E+a`h7iF_AaH83t2`p1r$Vp-GGC^ zI};m-uLCCnTN;`rYT8b1D(*hJAdvnoBUZFeOk0d4@Lix5;Z{M?8+$dN668fz0qP~5 z&dW?tM$#dqXxIwtB;?-U4?tyNyjuiXK(*kBj>g>q3Vn(R8C;f&yNjay!?~pJ0839VrRdDfAsn>ZMEcNTO=K|VKp ztQe&}NC=X4aPa#T2pq#3wYFOd1*X{)qvC3B*vsg)lg}DttsvgF#CT&{1Yg3y<_1x{%mYKfd}8YO^E{07wAQ#pLbHDTQM(B7%*TwbLb6B z7%(8-SDv>6mO^ZR83^8n!0=btaoY?xRCWk)-jskpuM5gO=5x2;>##Tb^v8LQ$`lM; zElNE*FzK@bc3F%FOEqO3-4*|mY9;Vjf&kC}XvWPu`$Lkv^yors3 zYR@M|e;4WvJ}6zvRq6M!oZ(2t>hDsO>zqe9&_0y|*sc}SC=2lTZFP%s z>6(7u%~&rWcS+4((I({py#qeGjGqT_#2lPIYgd?X5QK>0V9#l#2!qdKKh!SzhW-}cLJ-omG^FAHAriMSI6v0WDOq@IDr--^0y~u z|H}bOB;J5of}Sh51wxb^Fp_``}D))8FX2d;9KZ|bOm;M z%wdFn-Ex(32D=ITpwSzI!p}WO>tLUhFb%%}9u<@~P9!5w1z?EA<(6B)aEzsmWM4=S zoSp)#X7rZO1=E=JPJ7zXf5b3`)Hh5_8PjXP@rs6Whx94WwJ}H>Osi#ft)WyPU)pdK zXk(<`6tnE(=5o10DZUrtXNENuWF4K;b4(`)Wj@8)1-Zz~uQ49D6g0Sq^6$^3?*DUL z1Gy8AAwHb={B9{o&3s8)Gy!17O8f8521VQ3CgcymnWI)%&DCK{*Z~scnDk_9Q zk1}3X=nO!XeuaIY%o43E9xzFv^!o<5V00e=ggObS3-)gu+jlJ)ffo?13QFA^q^jQ% z+-vDE>>s7y$5imOM#DNJIY5B^BZe`wzM=H_L`L@L%1>?H5FAP>3FZ+53P3MH^8J0q zYwrj?8}!d|xl9(<^c~<>%qj`?r?hceCf1!FBT>M$@l$ zOFG!D@ScwYu60O?uNv zJO*4NJT*gXHG`gvej|o4tZxIQ67Na;&gLxz4fr=-0xqZ7NmuH;b-HArrG=SbC9^v8Gn5(hr5B$>5$89hcZW9FJmm?h7iDlp=+lLum zwD^YM2%{euzTp{!dv=TsMmZLjy4oBS?{UUDtKntKzc9WJR6b-bb^nERjfpu(YZ%V3 zDLVq#dkBvH3If2_ngO(N{!YL?Nd2(0v@JOtb1Ebp_cSwI*vy$gPSk}Hh&_cj2(;rPz&jKQQ?ci|M6@XFgNFWyg74QrFOuNrQ8HjNnwF zFHmTk3D}8UCNhD_7;!nI?b2v^U*G_6_k`$d)rA1YVJ=0CB9N;+XE_8u0WQRPSg1U= zpPqv>70&z2k<*GUc$KR&5Muo zx3zBf4PwIf19M{Q?_%I~VhXTksOz#4(VzQj!*cZi5z| z=H9IKLBrMoyj5WZ;KvDmTH|O`u0_=HofGLP0LD{=-Xv(#`r3X%Q7a!pbjdG6x*(Ir zmGd0o%&5JfoZHh2QV+u+3R&`d(`R=KW`*Q9PY_-I$(72zGfkZg`v84G_llXfc>0eV z#*q3(H%*`ZY=?I3ey)I=??PlTtujs7%s~JW87I;N+e=Bb2b#dIgSsYC)=9wSz#z;| zP@uPp$yIsMlr#5mVZAIeoo zXQW>+U2neg>$;>bWNoO!nt=}{?6Ca@#QJf2?9N1OoDrX`kfvczOFuu2aCpzp_Tb`R zA2swND@`Xa)~sfHywUzZSBWOfdWP%Ps*Eja!6PBlHwjw7s`XXT-IJvoiQ7 zoS$Mmk6VnIidj~h!yd%E`%NNKI>3pQN#-NO`2+$m*7fq7LY8d^>d z9$`p*Bc*crl|q&so8ZSs5hKOf27y$eb`^BZByxM4#O=UK;6p%@k*>tPoM;mFv#Fu7 zABDCfwr?l65c;H- zX);_Xb03hWSv)m`LKnlH*eepGD4)X&(($&|KlKENEbMGxj6%aZv;cC+0f?ax(CMYU;l zIDI)?FxgUB_H)3J#@h&W1nxrmeX&^lSEh zDk!?4z-7SXf`c6;i=FuXf?bk2&|{KH*lab2({k&P!+4~wfig@MT<)M!I|W#CE}6jF zg5PEEx~1cDyO&}-d^-3u(BViMr|E=&7R(30g@}=bj9h}LdF&N5!;%Dq_!GeIYkAu9 z5kN=BqR+md_krua&%kki<9>cHSFq56+ZTlp&zDWywom2Dj=8n_RdIM-K*eeT85N;q&yy=2HBy~=&`)*o(d0PZvpk3U-YYV;)EGM{M(wl-gI)B9) zM-F4_sKcjav0U{F1&T1w|U z0`8HSUNAIGqk?Z}C+JV~IEn(6=R-1Ft6+paQcxqfR`AbD8ANd*xa&g804DCcTP161 zk9{zq$u}8sD&}f3nsC+m7Y7@Yv?X7o`YaojfPzE`lX(1i z)B*3Uw^Sp0IPiZt`E!pjjBT^J6@dP>ODUC~DzT*DXr$eO2f%L|4Is1)L29yn6)>bi z(<4JmnI#1WBN~Z5(%l&}EeMByf^(EIG2G@kX2uERh!-<+Mh>qE|CG3{`+&0q|0`jzF-0jGDCAj|XL)b%aiCR!A%a^-rRAYt z67(}WTPOiw;(ohTzN`WE0G&vv^PUV`hnNn~^~O6E#>-c2K5b0Dty6kSf$)P;Gte$% z$WKAq(e@1Ik3U-!&R83#zKf|1&@AyT=1~XZO)nzD9A@bh<@u9R<^l9toe(n_k8}li zJf>SfZ8=miJ$V&-eekMKS(;?Hq*mmdLiVOz4!Z*1PMf9o5GQm+h-AJjDFf9Qc0%-N zQ)agq9+CNz#3z^^=dhFTBcMMzH6bOfZhL^BV?;)mLC;J2dv3qijn-x$8IWakHuNVT z1g~X~w*ZpYko6P9CaI*j7W6ML-(5(;r3$Z4-^1a+fRt(t0^bKNElj-LR~`T+?zdZoXl+ZP zsnQkrE#g{Wde`f3UubWpYV&8w`3u16gp~I?hG}K;b%9?AcCMuTK%(OGM8N}&9scbc zJptP;30?(1V6+ubr$EF%=wNz!++ay-7*3GA6mYWOogB3z0Aa^sFN2vFRC=sSELefv z48GYx{pQMG&#_pwpF(|j8bJj60)p=2=kIfPw7vnMLXNi){KiRKR)Ms;9CHgn=5eH9 zBbgzHGct$FTG~@sMiDH#WbrvQ0e2wo&hWG{LQ(`73i zbtGWte;@exYoi{hTZe-dqHOtB8UQBlzkB6|Sra%Jdu8AcL?dL{LVFign{TTq5CI$7 z-UfJqXnd_|_&-HT-414e7!~RI4YnT>?B^gYQ7Q-CvOPqhyVutNfgB`pigKPQ2)6`S z=>LE#=tr%FO41 zTa*ActG_(&^#8H?MuKVB^+KnBHzlop8_pBFir5`<2(kZhqu~OXS1ZmRnl1klN?Ue}U-_2=E-P+<0wBpqY zEydlFw6_e?1Y-SZAuG z&a%5!8V-gg!EKga1k*wx_yFy1C^CnYZBG{b19(}?U$Q)+z9Ic&z8>iRqF!A}qyr-=K6;08le5!m5{g?IW_ZicVK(&^tGIE;W-UJ6`{G=*Z3u2O4J1y%^f&ySpmHn8-ptZ3l_cF0Vo*RAD+-I$`Fj(%SHXZx7OGxZPS!y1 z;T4cZVBe}(++l+eb3gr`sBcJ(&wL5Pk>GwtCBa>wKLjH?zi`~cZwoF!+*K$rJ}(8F zcyN6Ki3w2%ARIu(7lrqFV3t{Xxt9|JJF!6v?_nR#d z#{?VLFxhveb=se&n!bJF9<1CCQxxQy=K#wq zFdQp6=*W8tIuQr86trcRDeC4AR45ekJntp=H6eQWvf*r-cO;e|c>H%(y7$roQO3+$ zgt?{1WM7j>lPYV%g)FNv48(4TnBYN1gYf*3Vm{ToF@mAJs)BlbX+q)Y3h)v}CkQqJ zJw$+hp3V{{NuLqJVx^c^=Ez`O!wxdFN-)7Th{2*_vSOt7ux}}s^D%`ipnx3;8PoE7 zO9iRP@~Pr^19MejDMsgG`C6PGzGMKHc;N1pJY^+<(`_Q+w`9CWUUmLOX%n{DE~d*_ zu^aaGz%GKJhC7H_`zH=Yn~sWA_=0A1v2P)iczZbN_l*_18$BEiF{K9Gr?&qlI3E~o zsIk4Rq?1ywV2trOpvSX}_Q1iI@66I?@GM(|2#o@+vGj%=d%WpmlMlCb6#Fq=!ZxfCEDkyj;#4fkAZ!@q!jsr*q1K>L}%bA%*sS+ z_acU_*H)c>QCY~4<&YJ60!JY2fqf&vEL>wa66t%gyCuegLh5TxmvooyKN4fYlN7`m z(}}IW&b0a>9|-<}bUE;u?HXVQ!O4bAB{kc=R&XoiWs~g${77j>zA_q5K|pPSK#TEU zMVGt;BF~|=49!KhAan$q(p2J!Tw=XPhtBgedAd(ynk5JlG`sR1W6~) z^NoDP_Jsq$!~^#ra3uL|z>bJ>F+%`%y{78?i^?W!vrXm8E`)f-4?x3_Zb&!z5rU%} z#H5y8VZ~_?+++Byq{+5VAqY>L9KU_#9tU1G{E49Fn&jv&>?ruPVZ#(mhDo}_FcGtn z@Xw4^A+r7qw{j-6%GjqM@F8$h9+2V!i5k3AU6Su80y0I_=dmMTw#)0=4xa0b+q5V@%>pgYRA zugoEcZs5tFDVcPOgCdBesqsr(6wkzY=nllnF}j(};{5546dAx8djh9GR>4X>r210As zz5@YvYYhT3lR-HH^%8V3IFt6ay#{6qVzTj5f;%LpmvVDUuZ99M)-u?0AqRv7UN4$> z&>ofF*pkGo^j*+}pvjQY_3G;Bzer8WkoptcaYmd+=+Ny69EY?*>rcj?El7i+X~#Rj zVS`S=8q1X~HZLcz}SWd!>oEg`sF=6*SJ1LoHkF4ij!5?yXt(axl zCW~5K!YUAAr0F`>6dWvARzYk2Cy9#$Q`=$?D)eGm>-#baY4`658YPtkqh%&2Mx*bv z`5d~D$Vt8q`=TX45!i>2RXzzAg`w-!w|q^PUA0+l!hit~_u!_$@dW$s*}%hy0hphI zS5G{4l}w=|c-Zi3vg~E$`Tj8*r)N6J3jmTnw|y<}mki!=lC&vDUtmuMJ$G4An-2mm zvptn?;v)?`6Kq&&JsYLAGspDJ(TcA8u1L#ZKMUm!hHoo2@LsfZWqHb!&x##8vgU}P z^ZZ<1sBf&C;;Ru5=k}H6BW9$nbd~SCU7>T09 zs+hfKBR(=~IbhsDXfq<4dIzZ)`+3ZRf^sgAMKRQkkVYS;v^TRbZGkd?i3jglxp6iJ zb^tC#ya3Sk>RT7~wN`BomYg4gg8FAbZ{Q)MYY7a{iHLRMN7C66r}Z%4HyJ#Ky)DwQ ziiu8vXbk2=s{K3Q*OK0Jx&u;&dou-BesJ|24s%Fi$Io&C(R1 zRf|!|GbuIlTuY2D4-o1kcvf&>A&`)xfU_i?tz-eMr+Eqj>F+h*jtX$qr-EG4ZMH|X zxh4d)l2(({5s6#t8t3&EzmzRl2_T@5ErE0Z(qq7V7imn!+BpAmM6JCuk;?xK zSQ_b>Ck$IC#(nE2Hx&T^zC{6Jy-?C;l}D!=br}N0vbJE9LZ80b3HbKB~(^vL>SUUxldkE?CEd{$t zdQb3o*`EklZ9W=*05A3KSNZZAf(^47ulo!U+@=+Q4b!-9%f}vR{SP3M3S?Qm;6%Y@ z!V?9572F}Rayx41H)0qLX0qD_S4%Xv4FZ)BLCOU3=xf-5Tgwza6|m!op_N8sQIRoq z6E3TbV8+6-`O2e`^FxSPHW0Y(j~4Q^Rc$UdVZgSPFFO#FUEe3@weA5%5Vib3+WwAX zH~pE!%JdM#7Ke`~|&+k*%!NGfAL zMG~4V5i(>IE2}ccr@e!+>v*KT*iD8@h5ss;3}TBB54C-7lYxr`8zWlr*RI69lnLaf z3ozRF)0#9iyQ{9T!nx2BXs{Tg*i2;Cg^UQCX9h6wyL(kaStnva;_>t!U9YG%{T78y z7`Po|85FVtQeVW6h}!)fLaSg0!U@|F(B`0L2|I(YMk|t%T#&7eZ0p-n67Ag86g3|sf-?nU z3qV~PkWk}@VF;)Vy@zuJA5`k7S;_%rB1=<*jVn#o3@AwLBn=d7BJ&BcXtmI70j2<| zL+*gNo#20~+M+glJ;X7f4Zu?ge!W$R+PrJqpCoqXPer;P$g#IW9E$l6d+oECe?8RD#I1J@wlvK&RcF@QoRaLz9iK6&h|> zN8&KdILGe%2W_&6wu!}DO%lrU3iWuFa0mrLlcGB?wkT0fBa05u(PA^q!4^9ygloqD z7a^T57?S~;&jxlZ0!^flhkpTBv5lWp{48^n1Dy@u!F*(RDiMNgOK>7~!+y7@tX#?$ zgxTVtPg!E#TWu8G2 z$|eM%NQZw*`I!m+YqwJI0a9-JVZqhLMbN>(>Jn$lJdSW42%?%ag7f^IKUtOmjI%vn zuoPHE{fK_aU5tV5s%-&E{5TnS7l0-MZX=RiOfK%oYrRGtQWh*$!L zhGd&^Koa$WKLalrcNAKmC{vzA`j;IG1`FGY^ZZwlk<%A=pJ<-Y_42CoFAAHm{lLnX z9SM!PJ=51?fp?L11h3NOd~ECd_W>oO9>BUtcVZ94`52VZ?ZMf1)yG#h3Z(+}dg-ap z$lzt$?lI({3dL9_V0Q)0*le9YIeP-gx$QCjhNSatey zz$(LsEXYUqmZV33Rj_No%NzX?*h?|K>-y<4xYe&It665U4#0>ww^m7FQh%y`)9TMaP<|b1nqPx{8 zbR4_LzA3lgMsklK54AQ1C9Q9JV0wP$kOAbXXl^7hQsN!B3>c2Iw(Ua&9~)l?*@udA za|MD$_o)_>7w6M6)_HU&ul4DN;IkYq0aljOF9m^Rj4F-6N=`8yc+$aubSLnkOuorn zNJd5`m~jCu6`Tp$-1ugm6zY{Cu$EA8o3i4OldiKKr5QO{NYJn5A6E12IB@ZFa5G$v}=GQhm_2`hX8 zo#S~s7+yvil}YaY@QjQ>QZ2B3VhVP8{^BgoU%>#Y_4`teJ(%DgsKAE1+deHub+)5} zznkI={jhdH@_!;)<^jlmIuBmLBZx9)DT2U5z%)s3*uGn_3jGq$+zwc0#Jrr)6rg<$ z`v~ntK;zFhUPS?zYoi#=j5=t>!`lXd7mQyt8KZz(>KbXsj7(ssuS*wT;`jEhT;VE6 zMeL_3LD#=moqti-gdI}qpV19C5VH-jf!BhRB|W^FHV=6f=?~aH93vm;+I)Cer z#2*_8Hgq(;K9RJ9+lW}F1FkX|BVFhe1^ML&3WXkmO$~z-V4^80Bj_vG+xUK=m%q$C zn6?J}THpSk5IFw(vD*;|!ZhJLCz~(F2Y9k~D|TR2Nk7D1-EfWY{jnj-Mxav4SDJ`S zrZNw$G(ctutEMJrLGisT`W`C-(~I!o|F?JMQC5~$zJ7k+sVatoDv%iwR2-0?MvYN% zU~m8-4oRyqcHi#gM(s|o+#Xin+qbiB|IwY*y|Q|x`>q@97&Y3-RpSto7)MkP0R@c{ zY9P@lD57YgAXPvyRGqW$AN&2z`KkazP3L^xexJ2!d8+E*_nkWD-S2+)@Y@5`X1k98 zXuJCI^x-664`Lla)2&h0uNJf}nC}nF4hO!DbVS%o4GUpfNO$TDz`x<=&s{+87k`!{ zDUl@5*;zgo_$?WDWPS$TK=g)rjmSlkhKJvWPF#|1$AM=J?v{HoXX88m10f|{XIN9s z5>Du$_uh$sv;?F{mm0dFA}S?_(xnJOkdg#JKtLdZAkv8x3!orfN$3ayBGOBwNfSa5 zLJ7I?yXW88eRj^wJF~kp&+eI#x9CgLMh19B(xn}5sIt3!cxMl}cdILMwdjHRIv1qi zqM|at@E!5Q{P=w}&xV{xJ{IGfmwm^25sXXQDVkC|D`Q3`Vw&EgUdCfSEiCl;fdWA=EG z$$ou{mzBy2V9U8@I?L3BCIdS%O3?N1o87 z>Dz6zvV3(TCuW2mP1u;~n9uPw@NT!*FW1{R7?0Od0tf=`Hv)O{0IlZ{rPq`&jl0C_ zh{IWC{DSF}BK(d-IOUA+`ekjDv{lj$NvY%fgUU}9bY51uBCO7rCOnb(U2L(sblZ1X zjha~1_G&F-UxG%@Eez9@$zGmc=9n1T+yKHJ!er7lCHX3iRgLeuP4QC;{w#={U+YKY zRx6&(Z5q$8%ye=ZC>+*0`Z63B1|>-uO}u6|dvs+)cqT;jofVCdP&uB67cgvR39@VN z-*(GP4jEl6DID+BDx5ZHQRaDZD8Fq8kK1Z{gzr*Yp@p!VnFI`+ITc))Q1a$JwVFr< zfsBV@w{E0sZ>)f@EKxOjIXc`Pb)DcO_awDuA6nuglu8ToLQIT5z4!4idq1)kWhe#p z14WNnKioUv9$_Xtg3V1^7___W8%WSghKTlNg0>G}6q_T}9pGsPS;*MGGC6X?oFGP! zg{QXe2E-BQVN>lt?6Wq0YgitCXtk-&%vN5_8deA2;A_5xO1Zr(J|M819|d9Vwwy|h zyeRR+Y)qw*r7lK-)<{4Gsw|qs;GXiLDRJG1%+|~Zi^pFHpu|@hd;( zIeJv4*Wm9%=a=F4h3x}k_P?*sR;Yst!w-w1f>v3vrF04`ewPOy-Ot|Vxo|rU*)7X7 z_5%L=im?jaZuUxt)wX6MgpVxR;P3N(?x|X}BR@ro!Hwm~mH%Jy@aAG`ZJ~Ia8;e|1LFstuGuzE z*yS5?t?5g8YEN_)?T}0}iG?ltZ4}ri{xNxjjzF@6WQIK0@=_I=c+RJe&>|u6v`OSm1L^jVE@ab_%>>U}Br(zyuKbWKVex(K z<9=bl)(1f%hH#7-ni5g2{4t=p@cXP7cYWnEMVVnf`w+5Jwtx5A%Mhp)Jj#6JQ;x=h zM~7>ALvJB)CP{UyGimq3owS5T2Ec_I%3fKKT-CYRp7!+TPLj3+1{N7T241}x8SLV) z!g)|j^ZkiCrWx2S^$q$+w(s|%#CpLO1h;dqawJ|wg=8Guc=z`s_7zKfyJyA9;CwtC zf_d|zFoPVd7Bu29J9s977D0J&Bk$1@W5T0on*pSdi1j1NR{^`=fse0KzTvmp!d5Oa z$plv~zry_nD`$mf&rCNt5dDsfh9JH2zgj4_OG-4DmK||nO~T)GeXd{Dg5c<$g9kGy zscHP80UFW;)=HvPiOjh{CrbE5pfwPliilRGU7Uw+eh2QoToTA-i?|Zt4qDZZ& z>md%dantAEq3mHXsPVyETm1O+O(~6w*CvKg%Z+kG#$w0fxN+r8%v*u?wAxL#WxiLw zb-cIS5p(|CIg#@q21zbaxwK?3_g}NP=5dbNkHM)$&inWsfOmGt)o zAp4A#4r!tM#ns}ynZs-PgfQ30KhdbN1c9HMF}H^dC8nleFp0m1C!96k3EqE^WH+=(0gxQ%b)asT>qme;4;XJ+r z6e?QLd9Z(7kttrkji$gd-ta3~r$dM-O zA)>@_T(-$|f->$g=0Mi6y+U;JEHE!dGYOq?Nze_`z|lZh{u0Qf!hI0YT3KN^5*5L} z6yW>{&0|=c>STm3q@=?w9LZXO=+Cjb^c34F<5rUe)+CvZHVG90CH?v|Il#K{m$FfP zylACbJI7r!PXgbAcusTs!X3?p0Pgw-E#$*KSb8~Y`w5SVv9G+=iLfp%hUV6@P;F0> zodRwXVcOFyZ1u|q!FeGEpWD{B-q(!fe+>zvKZ_ke*hRFVTIr<)b3#nWl?+AbiSmT? zlQUk(X%mkV#%S$O`E@h6xb~VFp84@7<`AAAPm#rGC<~*F;<`L5neV)N5j2ZL)JCp4 zX6p8cP@*JtaWt=U;hv#zKj!k1>VE!b_wCa68&_k?62vRl$@ln?n(%1;E#b7m886Td z9g(NT66cJa&Gb)m_BjkB$`+biCZo1rPDV0JzNFQ-H+wVWlwP(^9&7kQ-KBQ2Yjp`Sh5I*$kTTuE{2A$&?4!#P=RzZ zH8lP-#6#FPA(_J0G_lo2W zGTLG4ZUqHi+T;xzj+HLt_=%34XDTq8yjJw-no8kYSgN%`in6nE}Dv#3613XC-aYO5Wy~$Y8g@OI=28fv|h6Vxp?N}IyJ&gsi1dQRIxrLz7anX>=F8?f7<-D;+JTZE=+ zYtOMM<((#?)9rXWG3jMtHnAi}7e$XVc8*7A5z`FHQJ36v1`hRf;Vng@uirPm@YnOQ zJFY=PiM(#Zhm!gQBx)LH_p7mr24*8lct*8==Lp8A3OD;B=Vd`+7?fshGj0cC$L}b& zsebhPPI=UuoyypZ2N`3U_TqmFzTD-MCSNjp9^*h-KZp!-k8(=peqRK>mIm2u>$qOh zHz0-pZNpAQs}}ZnUB8}${xRPd+n(yg^I;>bdOqv+_Z*1%=-nI z+^K&JyE#-N1EO^&*%@CfjJ?>Col#vtvNc*X*?!+w#*#-3j! zrS|ciE~K(}Ka9w=Y-n^0wA@uS+T%G2Iswk?F;D;bi!TY823g(I_cv`8`c4-mHZD7n`nxxcdFXE!$2d^pP8e>- zu3P< zbtXk8)P%&P?jp;BjTfIs*SSZUs{=k^cfV*5=rv-%8@_Z;hFfGjtGz!5i6`plefxW# zppNzLTJzM?_JrM#c}n`d>MSn+z2E&RvZ^Q5Wavr8HMDK*3a9i|Pb|6EnRy#XkV+c7 zR-zc{EGFe+MPwRd*{bfCIxdY`S<33i7csTm)r+@I;2J!trTd1lQ7P24<%_@t$6o!G z>QJ4;P&7tr$(MZcJlasfyok%5g-!lF%SsDtlOKnQX_N@W%Bm+7-5^b&GmLblWi|0N z%8P?Yq7I3b4dXl|qfO4IMi_7h*hQA4G;yIlVDs+fWK@SEY0zN8?Z~OM+#ND<2ftpm z7<1eWeXLWBjk&&Z$t+@H-^-XD4)QVXWs18ad|1r1<6S@kizXhJVi zg(1`GH5cX!+;frMkX^aS#rRP%f6JU+<3*P{Lfy3r_PbkdcFv#l@^x!mYYK|0W?qB! zlJ^xn=QT+A5ycMCpikr9CrhW3#i?xJh6eIq%An)zi+}9_&*CJTCvQL`mCk5faeMb$ zTf>bT^1?t`nlc%(m}e5zh$0EeS=7GrK!M!vWW#earym*z#sdg9)TV*Z;G^d@&S&85 zf(yEO)6ck~gpeHkmj34YBKC91k$zHw*sY`afuT7bXSwQYo2-Tg>Ye8iWsKbJ-vOiP zWrc=LTpoL9?iRJSgLy=*EyDCHa3==%J((tx8iftl!-24l3>CZ_$j$OxY@s;$hYOZH z{BP~JqO>dh8IZ*8xgpQH`J;x^A)$e1@n!E=?sdJ2aq>OY9L!x@Y-^o8=!eU(+y?0t zE2u-W^w=61CZ221NI`SDL0f<=p3t0fHCtDtYJK^5vIRGbawhv+e7`(%h}#7-e|W<+%m^Cw`$30mFOq^E18qIm!%GChuqtP~NWB zcy8{B^Vm#C;(x7Vg|@8vhJLuVaNz+R3(8bz)PP~oEfo4TnW)-J^{^Tq37X$@kLcNKq> zeGjF4QMhzpK-tFdF9^z9dl7d7UCY&BQ2fknwmV%YplHkJSk-d_=;ylc^q%6WFRfIj zvzK&Asu2VLma!4%` zVZ^P6zKQT%XMLu-tAj)47!Yi8`P-uPnNBvr{m4Qe}I zMFq5p}tD_sTeFQy{%DfR2chfFHss(h+K9$*#qsfQoDQUQ07pA6!84BgZqx>VKZX)K3*f&xx}c#Zlk(#qJcS=UGwgJgWpU9VGSi%MUz6o zo<5@nW{JjKiRqT-#c)idZPr?T0vAEkdAVHQlKaVxn<;f|eM?i>LZ_GKdewp2U3wi3 zy#8j#O)qC93wOqSa1+2ea?6!Ywt$u*)x8)EA<-#NwL*&R{Et3xk~KPwNflLqp!p71 z0vU!HKHlf`%+9b2cq53enty2Ntmxi`)@@$FaQM@)dWg7=hUltHFE6(i?z$M>8~ym& zKVLuFj#L2{f7#uWDp@Zo`wAKAGLKg{0_gPv+@x)Qw* z6QuDZRVE?{L(Cs3riiJv0CqO)m?S?@`Y9){wvi}Vs8vBp5Pi&9nq%|YBjUWf zxVyb>CY7iVmFPd3+wDCzshtTvDYVD^OoBwNg^MYSs2)p9Y7XC96>Z@o@B!=*OYOcG z!`A|%i@b4)sKm3Djm|SLLgi>!bWWQVeJwWI97l}JwoUX>6{jgMt^=42 zys6d{y(sDK9xm9bTy0pfp`*h}%v=t`&oV&nmwVycGQ9y0U_=_+V;wC(ARwEj9I(h~ zqPi~sd+*SX&IEgIF_{DO)^&xoxob<&9#GlEh%585Mej#jF2rLD7NiNae;KIQB5#9AH)QLY}HGR;fiPwDmVQq3g?V&;J>~P4Kg?qaDM z^Ni2NCQ}SN8BzjFG~G>P>DbI1H4|t3YEBTebW^QenRBK2*YhAr3pp(Gh)1DBGHafL zzRcQpvp=-N8z7w_iaGH7nthi4ogng;`Mj{0?A~6mOIPW9KRY11Iib z`jU|bYMbW8J#i0h{v=GseW#^L09$cxlWQb1x!Mx?sJV8t7YddBYqx^Zn`2&dwOevzz$} z8D|1u1AsYD;A>CYpVj{B|0xGnacTl(QsVNbcij9_WD>*x6_E(|_Kz z)ZH&dpJKB`)`et(<2$wSy8FQumg;~Em|dkJZ~ACTIlztQK(&Um(daS;@oIuyH0hL* zz!BF#=Z(oEsNy5qzXld%a9$G<{E9Yz>&u*e%;PRYoAS+{>de6WhpUq#O)T1H7 zU(*vYn(*QO^qEc=>xT=v3WWNH$ria+Gkt!=#k$S6!$d_@ppUVWzbF}uctsGZaise4 z`-9|!RiLSK6If|qhrmf)8s+Q#xgy3fN50t^)6pAoA>!1zj7Q;KykqKDw<<)FI zvwt?4-gy=I{F)^&fMM&?@aYB5HOyJF9hke1$Gs+(y8MkU+epdS*GKud)25lw8fi^(ScXS17)xAh+R4<*8_kCg_5qlM7g1(O{ zjr~Ucd}8vCodA;d=GmV=e^PNE%oCdGMRB8f-xv;~e_hX*jwrHloy(YgrJCTPDuI8Z z88U#_@ecuocFgf%mMQCAhtw66Is;5(d&dEr+%v#;C23;FLeqc4T`NM#V(3CL#hyzO zq`lN9$FPL?&w@)-ERAwyeewyjkKmmE6N`KBHh18gu6~Vxa zbp;Q6kAw2y(3*PT@lAWP<(|z)pAv&>`A1)rCUc&7?EdrCS%O>4r~NJv2VQM9?LgzA@T} z-}^Z4Xt}j`NOhsW4s0Xh$hY#*nvgetwmAMxxoZLtz}K#YG*jse7??ZBRGP9zLjKKM V`M7xULJmNEEY0jptBm2X{{ysvb7}wp literal 0 HcmV?d00001 diff --git a/DalamudCrashHandler/resource.h b/DalamudCrashHandler/resource.h new file mode 100644 index 000000000..cdeb6ea3c --- /dev/null +++ b/DalamudCrashHandler/resource.h @@ -0,0 +1,16 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by DalamudCrashHandler.rc +// +#define IDI_ICON1 101 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 102 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1001 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/build/DalamudBuild.cs b/build/DalamudBuild.cs index 01d6d3038..318af7851 100644 --- a/build/DalamudBuild.cs +++ b/build/DalamudBuild.cs @@ -31,6 +31,9 @@ public class DalamudBuild : NukeBuild AbsolutePath DalamudBootProjectDir => RootDirectory / "Dalamud.Boot"; AbsolutePath DalamudBootProjectFile => DalamudBootProjectDir / "Dalamud.Boot.vcxproj"; + + AbsolutePath DalamudCrashHandlerProjectDir => RootDirectory / "DalamudCrashHandler"; + AbsolutePath DalamudCrashHandlerProjectFile => DalamudBootProjectDir / "DalamudCrashHandler.vcxproj"; AbsolutePath InjectorProjectDir => RootDirectory / "Dalamud.Injector"; AbsolutePath InjectorProjectFile => InjectorProjectDir / "Dalamud.Injector.csproj"; @@ -71,6 +74,14 @@ public class DalamudBuild : NukeBuild .SetTargetPath(DalamudBootProjectFile) .SetConfiguration(Configuration)); }); + + Target CompileDalamudCrashHandler => _ => _ + .Executes(() => + { + MSBuildTasks.MSBuild(s => s + .SetTargetPath(DalamudCrashHandlerProjectFile) + .SetConfiguration(Configuration)); + }); Target CompileInjector => _ => _ .DependsOn(Restore) @@ -117,6 +128,11 @@ public class DalamudBuild : NukeBuild .SetProjectFile(DalamudBootProjectFile) .SetConfiguration(Configuration) .SetTargets("Clean")); + + MSBuildTasks.MSBuild(s => s + .SetProjectFile(DalamudCrashHandlerProjectFile) + .SetConfiguration(Configuration) + .SetTargets("Clean")); DotNetTasks.DotNetClean(s => s .SetProject(InjectorProjectFile) From 0e92ac0c4a448554d7386dd2210e91b507610227 Mon Sep 17 00:00:00 2001 From: goat Date: Sun, 24 Jul 2022 18:59:27 +0200 Subject: [PATCH 42/58] fix: actually depend on crash handler task --- build/DalamudBuild.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/build/DalamudBuild.cs b/build/DalamudBuild.cs index 318af7851..85a567e4f 100644 --- a/build/DalamudBuild.cs +++ b/build/DalamudBuild.cs @@ -104,6 +104,7 @@ public class DalamudBuild : NukeBuild Target Compile => _ => _ .DependsOn(CompileDalamud) .DependsOn(CompileDalamudBoot) + .DependsOn(CompileDalamudCrashHandler) .DependsOn(CompileInjector) .DependsOn(CompileInjectorBoot); From 1767bbf5b17ad1a672cf711b82d8835e5708117f Mon Sep 17 00:00:00 2001 From: goat Date: Sun, 24 Jul 2022 21:19:38 +0200 Subject: [PATCH 43/58] fix: use correct project file --- build/DalamudBuild.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/DalamudBuild.cs b/build/DalamudBuild.cs index 85a567e4f..d46e4b562 100644 --- a/build/DalamudBuild.cs +++ b/build/DalamudBuild.cs @@ -33,7 +33,7 @@ public class DalamudBuild : NukeBuild AbsolutePath DalamudBootProjectFile => DalamudBootProjectDir / "Dalamud.Boot.vcxproj"; AbsolutePath DalamudCrashHandlerProjectDir => RootDirectory / "DalamudCrashHandler"; - AbsolutePath DalamudCrashHandlerProjectFile => DalamudBootProjectDir / "DalamudCrashHandler.vcxproj"; + AbsolutePath DalamudCrashHandlerProjectFile => DalamudCrashHandlerProjectDir / "DalamudCrashHandler.vcxproj"; AbsolutePath InjectorProjectDir => RootDirectory / "Dalamud.Injector"; AbsolutePath InjectorProjectFile => InjectorProjectDir / "Dalamud.Injector.csproj"; From d2ecddbf3553eb4f91085c65e0428d9dccd47f60 Mon Sep 17 00:00:00 2001 From: goat Date: Sun, 24 Jul 2022 21:23:20 +0200 Subject: [PATCH 44/58] feat: optional CTD reports to gauge stability --- Dalamud.Boot/crashhandler_shared.h | 4 ++ Dalamud.Boot/veh.cpp | 17 +++++--- DalamudCrashHandler/DalamudCrashHandler.cpp | 39 ++++++++++++++++++- .../DalamudCrashHandler.vcxproj | 7 ++-- 4 files changed, 57 insertions(+), 10 deletions(-) diff --git a/Dalamud.Boot/crashhandler_shared.h b/Dalamud.Boot/crashhandler_shared.h index 0c1649cc4..6b17bb11f 100644 --- a/Dalamud.Boot/crashhandler_shared.h +++ b/Dalamud.Boot/crashhandler_shared.h @@ -9,6 +9,10 @@ struct exception_info DWORD ProcessId; BOOL DoFullDump; wchar_t DumpPath[1000]; + + // For metrics + DWORD ExceptionCode; + long long Lifetime; }; constexpr wchar_t SHARED_INFO_FILE_NAME[] = L"DalamudCrashInfoShare"; diff --git a/Dalamud.Boot/veh.cpp b/Dalamud.Boot/veh.cpp index b29c38b07..890c7ab0d 100644 --- a/Dalamud.Boot/veh.cpp +++ b/Dalamud.Boot/veh.cpp @@ -29,6 +29,8 @@ bool g_veh_do_full_dump = false; exception_info* g_crashhandler_shared_info; HANDLE g_crashhandler_event; +std::chrono::time_point g_time_start; + bool is_whitelist_exception(const DWORD code) { switch (code) @@ -292,11 +294,6 @@ LONG exception_handler(EXCEPTION_POINTERS* ex) print_exception_info(ex, log); auto window_log_str = log.str(); print_exception_info_extended(ex, log); - - MINIDUMP_EXCEPTION_INFORMATION ex_info; - ex_info.ClientPointers = false; - ex_info.ExceptionPointers = ex; - ex_info.ThreadId = GetCurrentThreadId(); if (g_crashhandler_shared_info && g_crashhandler_event) { @@ -307,6 +304,15 @@ LONG exception_handler(EXCEPTION_POINTERS* ex) g_crashhandler_shared_info->ProcessId = GetCurrentProcessId(); g_crashhandler_shared_info->ExceptionPointers = ex; g_crashhandler_shared_info->DoFullDump = g_veh_do_full_dump; + g_crashhandler_shared_info->ExceptionCode = ex->ExceptionRecord->ExceptionCode; + + const auto time_now = std::chrono::system_clock::now(); + auto lifetime = std::chrono::duration_cast( + time_now.time_since_epoch()).count() + - std::chrono::duration_cast( + g_time_start.time_since_epoch()).count(); + + g_crashhandler_shared_info->Lifetime = lifetime; SetEvent(g_crashhandler_event); } @@ -402,6 +408,7 @@ bool veh::add_handler(bool doFullDump, std::string workingDirectory) SetUnhandledExceptionFilter(nullptr); g_veh_do_full_dump = doFullDump; + g_time_start = std::chrono::system_clock::now(); auto file_mapping = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, sizeof(exception_info), SHARED_INFO_FILE_NAME); if (!file_mapping) { diff --git a/DalamudCrashHandler/DalamudCrashHandler.cpp b/DalamudCrashHandler/DalamudCrashHandler.cpp index e7bc79055..2fc85e495 100644 --- a/DalamudCrashHandler/DalamudCrashHandler.cpp +++ b/DalamudCrashHandler/DalamudCrashHandler.cpp @@ -1,11 +1,13 @@ #include +#include #include #include #include +#include #include "../Dalamud.Boot/crashhandler_shared.h" -DWORD WINAPI MyThreadFunction(LPVOID lpParam) +DWORD WINAPI ExitCheckThread(LPVOID lpParam) { while (true) { @@ -53,7 +55,7 @@ int main() CreateThread( NULL, // default security attributes 0, // use default stack size - MyThreadFunction, // thread function name + ExitCheckThread, // thread function name NULL, // argument to thread function 0, // use default creation flags NULL); // returns the thread identifier @@ -132,5 +134,38 @@ int main() CloseHandle(process); CloseHandle(file_mapping); + if (getenv("DALAMUD_NO_METRIC")) + return 0; + + HINTERNET internet = WinHttpOpen(L"DALAMUDCRASHHANDLER", WINHTTP_ACCESS_TYPE_AUTOMATIC_PROXY, NULL, NULL, WINHTTP_FLAG_SECURE_DEFAULTS); + HINTERNET connect = NULL, request = NULL; + if (internet) + { + connect = WinHttpConnect(internet, L"kamori.goats.dev", INTERNET_DEFAULT_HTTPS_PORT, 0); + } + + if (connect) + { + std::wstringstream url{ L"/Dalamud/Metric/ReportCrash/" }; + url << "?lt=" << info_share->Lifetime << "&code=" << std::hex << info_share->ExceptionCode; + + request = WinHttpOpenRequest(internet, L"GET", url.str().c_str(), NULL, NULL, NULL, 0); + } + + if (request) + { + bool sent = WinHttpSendRequest(request, + WINHTTP_NO_ADDITIONAL_HEADERS, + 0, WINHTTP_NO_REQUEST_DATA, 0, + 0, 0); + + if (!sent) + std::cout << "Failed to send metric: " << std::hex << GetLastError() << std::endl; + } + + if (request) WinHttpCloseHandle(request); + if (connect) WinHttpCloseHandle(connect); + if (internet) WinHttpCloseHandle(internet); + return 0; } diff --git a/DalamudCrashHandler/DalamudCrashHandler.vcxproj b/DalamudCrashHandler/DalamudCrashHandler.vcxproj index b56628f6a..e4aaa3eb1 100644 --- a/DalamudCrashHandler/DalamudCrashHandler.vcxproj +++ b/DalamudCrashHandler/DalamudCrashHandler.vcxproj @@ -48,13 +48,13 @@ Level3 true - _DEBUG;_CONSOLE;%(PreprocessorDefinitions) + _CRT_SECURE_NO_WARNINGS;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) true Windows true - Dbghelp.lib;$(CoreLibraryDependencies);%(AdditionalDependencies) + Winhttp.lib;Dbghelp.lib;$(CoreLibraryDependencies);%(AdditionalDependencies) mainCRTStartup @@ -64,7 +64,7 @@ true true true - NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + _CRT_SECURE_NO_WARNINGS;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) true @@ -72,6 +72,7 @@ true true true + Dbghelp.lib;$(CoreLibraryDependencies);%(AdditionalDependencies) From b6a85bf4d0d65224bbe5a6b50bef89c9457e9977 Mon Sep 17 00:00:00 2001 From: Lilith Date: Sun, 24 Jul 2022 17:18:56 -0400 Subject: [PATCH 45/58] Persistent plugin feedback contact details (#934) --- Dalamud/Configuration/Internal/DalamudConfiguration.cs | 5 +++++ .../Windows/PluginInstaller/PluginInstallerWindow.cs | 7 ++++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/Dalamud/Configuration/Internal/DalamudConfiguration.cs b/Dalamud/Configuration/Internal/DalamudConfiguration.cs index 1eed6a2fa..1b2a43c17 100644 --- a/Dalamud/Configuration/Internal/DalamudConfiguration.cs +++ b/Dalamud/Configuration/Internal/DalamudConfiguration.cs @@ -332,6 +332,11 @@ namespace Dalamud.Configuration.Internal /// public bool ShowDevBarInfo { get; set; } = true; + /// + /// Gets or sets the last-used contact details for the plugin feedback form. + /// + public string LastFeedbackContactDetails { get; set; } = string.Empty; + /// /// Load a configuration from the provided path. /// diff --git a/Dalamud/Interface/Internal/Windows/PluginInstaller/PluginInstallerWindow.cs b/Dalamud/Interface/Internal/Windows/PluginInstaller/PluginInstallerWindow.cs index c8ec0a9ac..3ec49aa66 100644 --- a/Dalamud/Interface/Internal/Windows/PluginInstaller/PluginInstallerWindow.cs +++ b/Dalamud/Interface/Internal/Windows/PluginInstaller/PluginInstallerWindow.cs @@ -666,6 +666,11 @@ namespace Dalamud.Interface.Internal.Windows.PluginInstaller Log.Error("FeedbackPlugin was null."); } + if (!string.IsNullOrWhiteSpace(this.feedbackModalContact)) + { + Service.Get().LastFeedbackContactDetails = this.feedbackModalContact; + } + ImGui.CloseCurrentPopup(); } } @@ -681,7 +686,7 @@ namespace Dalamud.Interface.Internal.Windows.PluginInstaller if (!this.feedbackModalOnNextFrameDontClear) { this.feedbackModalBody = string.Empty; - this.feedbackModalContact = string.Empty; + this.feedbackModalContact = Service.Get().LastFeedbackContactDetails; this.feedbackModalIncludeException = false; this.feedbackIsAnonymous = false; } From 55f34e9e9704285fa34f5760e0ebc2960faea7dd Mon Sep 17 00:00:00 2001 From: goat Date: Mon, 25 Jul 2022 00:27:54 +0200 Subject: [PATCH 46/58] ci: remove x86 option altogether? maybe? --- .nuke/build.schema.json | 2 ++ Dalamud.sln | 4 ---- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/.nuke/build.schema.json b/.nuke/build.schema.json index 9be9d59cd..27cfe4d84 100644 --- a/.nuke/build.schema.json +++ b/.nuke/build.schema.json @@ -75,6 +75,7 @@ "Compile", "CompileDalamud", "CompileDalamudBoot", + "CompileDalamudCrashHandler", "CompileInjector", "CompileInjectorBoot", "Restore", @@ -96,6 +97,7 @@ "Compile", "CompileDalamud", "CompileDalamudBoot", + "CompileDalamudCrashHandler", "CompileInjector", "CompileInjectorBoot", "Restore", diff --git a/Dalamud.sln b/Dalamud.sln index a9cdf123d..01d085349 100644 --- a/Dalamud.sln +++ b/Dalamud.sln @@ -192,14 +192,10 @@ Global {317A264C-920B-44A1-8A34-F3A6827B0705}.Debug|Any CPU.Build.0 = Debug|x64 {317A264C-920B-44A1-8A34-F3A6827B0705}.Debug|x64.ActiveCfg = Debug|x64 {317A264C-920B-44A1-8A34-F3A6827B0705}.Debug|x64.Build.0 = Debug|x64 - {317A264C-920B-44A1-8A34-F3A6827B0705}.Debug|x86.ActiveCfg = Debug|Win32 - {317A264C-920B-44A1-8A34-F3A6827B0705}.Debug|x86.Build.0 = Debug|Win32 {317A264C-920B-44A1-8A34-F3A6827B0705}.Release|Any CPU.ActiveCfg = Release|x64 {317A264C-920B-44A1-8A34-F3A6827B0705}.Release|Any CPU.Build.0 = Release|x64 {317A264C-920B-44A1-8A34-F3A6827B0705}.Release|x64.ActiveCfg = Release|x64 {317A264C-920B-44A1-8A34-F3A6827B0705}.Release|x64.Build.0 = Release|x64 - {317A264C-920B-44A1-8A34-F3A6827B0705}.Release|x86.ActiveCfg = Release|Win32 - {317A264C-920B-44A1-8A34-F3A6827B0705}.Release|x86.Build.0 = Release|Win32 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE From 2b1758230b2d9dae2b234d9dedb22da480522e43 Mon Sep 17 00:00:00 2001 From: Raymond Date: Mon, 25 Jul 2022 03:57:40 -0400 Subject: [PATCH 47/58] Fix DalamudCrashHandler CI (#935) --- Dalamud.sln | 12 ++-- .../DalamudCrashHandler.vcxproj | 60 ++++++++++++++++++- 2 files changed, 67 insertions(+), 5 deletions(-) diff --git a/Dalamud.sln b/Dalamud.sln index 01d085349..b7638754b 100644 --- a/Dalamud.sln +++ b/Dalamud.sln @@ -188,14 +188,18 @@ Global {05AB2F46-268B-4915-806F-DDF813E2D59D}.Release|x64.Build.0 = Release|Any CPU {05AB2F46-268B-4915-806F-DDF813E2D59D}.Release|x86.ActiveCfg = Release|Any CPU {05AB2F46-268B-4915-806F-DDF813E2D59D}.Release|x86.Build.0 = Release|Any CPU - {317A264C-920B-44A1-8A34-F3A6827B0705}.Debug|Any CPU.ActiveCfg = Debug|x64 - {317A264C-920B-44A1-8A34-F3A6827B0705}.Debug|Any CPU.Build.0 = Debug|x64 + {317A264C-920B-44A1-8A34-F3A6827B0705}.Debug|Any CPU.ActiveCfg = Debug|Win32 + {317A264C-920B-44A1-8A34-F3A6827B0705}.Debug|Any CPU.Build.0 = Debug|Win32 {317A264C-920B-44A1-8A34-F3A6827B0705}.Debug|x64.ActiveCfg = Debug|x64 {317A264C-920B-44A1-8A34-F3A6827B0705}.Debug|x64.Build.0 = Debug|x64 - {317A264C-920B-44A1-8A34-F3A6827B0705}.Release|Any CPU.ActiveCfg = Release|x64 - {317A264C-920B-44A1-8A34-F3A6827B0705}.Release|Any CPU.Build.0 = Release|x64 + {317A264C-920B-44A1-8A34-F3A6827B0705}.Debug|x86.ActiveCfg = Debug|x64 + {317A264C-920B-44A1-8A34-F3A6827B0705}.Debug|x86.Build.0 = Debug|x64 + {317A264C-920B-44A1-8A34-F3A6827B0705}.Release|Any CPU.ActiveCfg = Release|Win32 + {317A264C-920B-44A1-8A34-F3A6827B0705}.Release|Any CPU.Build.0 = Release|Win32 {317A264C-920B-44A1-8A34-F3A6827B0705}.Release|x64.ActiveCfg = Release|x64 {317A264C-920B-44A1-8A34-F3A6827B0705}.Release|x64.Build.0 = Release|x64 + {317A264C-920B-44A1-8A34-F3A6827B0705}.Release|x86.ActiveCfg = Release|x64 + {317A264C-920B-44A1-8A34-F3A6827B0705}.Release|x86.Build.0 = Release|x64 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/DalamudCrashHandler/DalamudCrashHandler.vcxproj b/DalamudCrashHandler/DalamudCrashHandler.vcxproj index e4aaa3eb1..007705e47 100644 --- a/DalamudCrashHandler/DalamudCrashHandler.vcxproj +++ b/DalamudCrashHandler/DalamudCrashHandler.vcxproj @@ -1,10 +1,18 @@ + + Debug + Win32 + Debug x64 + + Release + Win32 + Release x64 @@ -25,6 +33,12 @@ v143 Unicode + + Application + true + v143 + Unicode + Application false @@ -32,6 +46,13 @@ true Unicode + + Application + false + v143 + true + Unicode + @@ -40,9 +61,15 @@ + + + + + + @@ -58,6 +85,20 @@ mainCRTStartup + + + Level3 + true + _CRT_SECURE_NO_WARNINGS;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + + + Windows + true + Winhttp.lib;Dbghelp.lib;$(CoreLibraryDependencies);%(AdditionalDependencies) + mainCRTStartup + + Level3 @@ -72,7 +113,24 @@ true true true - Dbghelp.lib;$(CoreLibraryDependencies);%(AdditionalDependencies) + Winhttp.lib;Dbghelp.lib;$(CoreLibraryDependencies);%(AdditionalDependencies) + + + + + Level3 + true + true + true + _CRT_SECURE_NO_WARNINGS;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + + + Console + true + true + true + Winhttp.lib;Dbghelp.lib;$(CoreLibraryDependencies);%(AdditionalDependencies) From 7a4c468a5ca93322857186ca79f5957696bd65a1 Mon Sep 17 00:00:00 2001 From: goat Date: Mon, 25 Jul 2022 23:56:11 +0200 Subject: [PATCH 48/58] chore: build CrashHandler release with windows subsystem --- DalamudCrashHandler/DalamudCrashHandler.vcxproj | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/DalamudCrashHandler/DalamudCrashHandler.vcxproj b/DalamudCrashHandler/DalamudCrashHandler.vcxproj index 007705e47..8e832d384 100644 --- a/DalamudCrashHandler/DalamudCrashHandler.vcxproj +++ b/DalamudCrashHandler/DalamudCrashHandler.vcxproj @@ -109,7 +109,7 @@ true - Console + Windows true true true @@ -126,7 +126,7 @@ true - Console + Windows true true true @@ -149,4 +149,4 @@ - \ No newline at end of file + From 98e421a227308ee63fd44a92ec39dd9b3782af8d Mon Sep 17 00:00:00 2001 From: goat Date: Tue, 26 Jul 2022 01:07:54 +0200 Subject: [PATCH 49/58] chore: correct entrypoint --- DalamudCrashHandler/DalamudCrashHandler.vcxproj | 2 ++ 1 file changed, 2 insertions(+) diff --git a/DalamudCrashHandler/DalamudCrashHandler.vcxproj b/DalamudCrashHandler/DalamudCrashHandler.vcxproj index 8e832d384..eb3e186c3 100644 --- a/DalamudCrashHandler/DalamudCrashHandler.vcxproj +++ b/DalamudCrashHandler/DalamudCrashHandler.vcxproj @@ -114,6 +114,7 @@ true true Winhttp.lib;Dbghelp.lib;$(CoreLibraryDependencies);%(AdditionalDependencies) + mainCRTStartup @@ -131,6 +132,7 @@ true true Winhttp.lib;Dbghelp.lib;$(CoreLibraryDependencies);%(AdditionalDependencies) + mainCRTStartup From 58ceb1dc876108d628497c5897874c07973c32af Mon Sep 17 00:00:00 2001 From: kizer Date: Wed, 27 Jul 2022 04:14:48 +0900 Subject: [PATCH 50/58] Offer to restart game from VEH exception handler (#936) --- Dalamud.Boot/Dalamud.Boot.vcxproj | 8 +- Dalamud.Boot/DalamudStartInfo.h | 3 + Dalamud.Boot/crashhandler_shared.h | 25 +- Dalamud.Boot/dllmain.cpp | 2 +- Dalamud.Boot/pch.h | 17 +- Dalamud.Boot/utils.cpp | 33 + Dalamud.Boot/utils.h | 2 + Dalamud.Boot/veh.cpp | 492 ++++-------- Dalamud.Boot/veh.h | 2 +- Dalamud.Injector/EntryPoint.cs | 74 +- Dalamud.Injector/LegacyBlowfish.cs | 319 ++++++++ Dalamud.sln | 8 +- Dalamud/EntryPoint.cs | 40 +- .../Interface/Internal/DalamudInterface.cs | 10 + DalamudCrashHandler/DalamudCrashHandler.cpp | 740 ++++++++++++++---- .../DalamudCrashHandler.vcxproj | 120 +-- 16 files changed, 1279 insertions(+), 616 deletions(-) create mode 100644 Dalamud.Injector/LegacyBlowfish.cs diff --git a/Dalamud.Boot/Dalamud.Boot.vcxproj b/Dalamud.Boot/Dalamud.Boot.vcxproj index 77783e269..2e0f19aca 100644 --- a/Dalamud.Boot/Dalamud.Boot.vcxproj +++ b/Dalamud.Boot/Dalamud.Boot.vcxproj @@ -33,10 +33,12 @@ - $(LibraryPath) + true + $(SolutionDir)bin\lib\$(Configuration)\libMinHook\;$(VC_LibraryPath_x64);$(WindowsSDK_LibraryPath_x64) - $(SolutionDir)bin\lib\$(Configuration)\libMinHook\;$(LibraryPath) + false + $(SolutionDir)bin\lib\$(Configuration)\libMinHook\;$(VC_LibraryPath_x64);$(WindowsSDK_LibraryPath_x64) @@ -53,7 +55,7 @@ Windows true false - dbghelp.lib;Version.lib;%(AdditionalDependencies) + Version.lib;%(AdditionalDependencies) ..\lib\CoreCLR;%(AdditionalLibraryDirectories) diff --git a/Dalamud.Boot/DalamudStartInfo.h b/Dalamud.Boot/DalamudStartInfo.h index 4bd377811..ed9d5c4a3 100644 --- a/Dalamud.Boot/DalamudStartInfo.h +++ b/Dalamud.Boot/DalamudStartInfo.h @@ -8,6 +8,9 @@ struct DalamudStartInfo { BeforeDalamudConstruct = 1 << 2, }; friend void from_json(const nlohmann::json&, WaitMessageboxFlags&); + friend WaitMessageboxFlags operator &(WaitMessageboxFlags a, WaitMessageboxFlags b) { + return static_cast(static_cast(a) & static_cast(b)); + } enum class DotNetOpenProcessHookMode : int { ImportHooks = 0, diff --git a/Dalamud.Boot/crashhandler_shared.h b/Dalamud.Boot/crashhandler_shared.h index 6b17bb11f..c4bd606c7 100644 --- a/Dalamud.Boot/crashhandler_shared.h +++ b/Dalamud.Boot/crashhandler_shared.h @@ -1,19 +1,18 @@ #pragma once -#include "windows.h" +#include + +#define NOMINMAX +#define WIN32_LEAN_AND_MEAN +#include struct exception_info { - void* ExceptionPointers; // Cannot dereference! - DWORD ThreadId; - DWORD ProcessId; - BOOL DoFullDump; - wchar_t DumpPath[1000]; - - // For metrics - DWORD ExceptionCode; - long long Lifetime; + LPEXCEPTION_POINTERS pExceptionPointers; + EXCEPTION_POINTERS ExceptionPointers; + EXCEPTION_RECORD ExceptionRecord; + CONTEXT ContextRecord; + uint64_t nLifetime; + HANDLE hThreadHandle; + DWORD dwStackTraceLength; }; - -constexpr wchar_t SHARED_INFO_FILE_NAME[] = L"DalamudCrashInfoShare"; -constexpr wchar_t CRASHDUMP_EVENT_NAME[] = L"Global\\DalamudRequestWriteDump"; diff --git a/Dalamud.Boot/dllmain.cpp b/Dalamud.Boot/dllmain.cpp index d5660e976..9a741a47f 100644 --- a/Dalamud.Boot/dllmain.cpp +++ b/Dalamud.Boot/dllmain.cpp @@ -87,7 +87,7 @@ DWORD WINAPI InitializeImpl(LPVOID lpParam, HANDLE hMainThreadContinue) { logging::I("Dalamud.Boot Injectable, (c) 2021 XIVLauncher Contributors"); logging::I("Built at: " __DATE__ "@" __TIME__); - if (static_cast(g_startInfo.BootWaitMessageBox) & static_cast(DalamudStartInfo::WaitMessageboxFlags::BeforeInitialize)) + if ((g_startInfo.BootWaitMessageBox & DalamudStartInfo::WaitMessageboxFlags::BeforeInitialize) != DalamudStartInfo::WaitMessageboxFlags::None) MessageBoxW(nullptr, L"Press OK to continue (BeforeInitialize)", L"Dalamud Boot", MB_OK); if (minHookLoaded) { diff --git a/Dalamud.Boot/pch.h b/Dalamud.Boot/pch.h index bbd4b3d73..4cb2d7945 100644 --- a/Dalamud.Boot/pch.h +++ b/Dalamud.Boot/pch.h @@ -11,15 +11,16 @@ #define WIN32_LEAN_AND_MEAN #define NOMINMAX -// Windows Header Files -#include -#include +// Windows Header Files (1) +#include + +// Windows Header Files (2) +#include #include #include -#include -#include -#include +#include #include +#include // MSVC Compiler Intrinsic #include @@ -33,11 +34,11 @@ #include #include #include +#include #include #include #include #include -#include #include // https://www.akenotsuki.com/misc/srell/en/ @@ -50,8 +51,8 @@ #include "../lib/Nomade040-nmd/nmd_assembly.h" // https://github.com/dotnet/coreclr -#include "../lib/CoreCLR/CoreCLR.h" #include "../lib/CoreCLR/boot.h" +#include "../lib/CoreCLR/CoreCLR.h" // https://github.com/nlohmann/json #include "../lib/nlohmann-json/json.hpp" diff --git a/Dalamud.Boot/utils.cpp b/Dalamud.Boot/utils.cpp index f51ef7e49..e06e812ef 100644 --- a/Dalamud.Boot/utils.cpp +++ b/Dalamud.Boot/utils.cpp @@ -520,3 +520,36 @@ void utils::wait_for_game_window() { }; SendMessageW(game_window, WM_NULL, 0, 0); } + +std::wstring utils::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); + } else { + res.push_back(L'"'); + for (auto it = arg.begin(); ; ++it) { + size_t bsCount = 0; + + while (it != arg.end() && *it == L'\\') { + ++it; + ++bsCount; + } + + if (it == arg.end()) { + res.append(bsCount * 2, L'\\'); + break; + } else if (*it == L'"') { + res.append(bsCount * 2 + 1, L'\\'); + res.push_back(*it); + } else { + res.append(bsCount, L'\\'); + res.push_back(*it); + } + } + + res.push_back(L'"'); + } + return res; +} diff --git a/Dalamud.Boot/utils.h b/Dalamud.Boot/utils.h index bbcba1f84..ca350674a 100644 --- a/Dalamud.Boot/utils.h +++ b/Dalamud.Boot/utils.h @@ -260,4 +260,6 @@ namespace utils { HWND try_find_game_window(); void wait_for_game_window(); + + std::wstring escape_shell_arg(const std::wstring& arg); } diff --git a/Dalamud.Boot/veh.cpp b/Dalamud.Boot/veh.cpp index 890c7ab0d..bb3f37869 100644 --- a/Dalamud.Boot/veh.cpp +++ b/Dalamud.Boot/veh.cpp @@ -1,7 +1,5 @@ #include "pch.h" -#include "resource.h" - #include "veh.h" #include @@ -10,6 +8,7 @@ #include "utils.h" #include "crashhandler_shared.h" +#include "DalamudStartInfo.h" #pragma comment(lib, "comctl32.lib") @@ -25,9 +24,8 @@ PVOID g_veh_handle = nullptr; bool g_veh_do_full_dump = false; - -exception_info* g_crashhandler_shared_info; -HANDLE g_crashhandler_event; +HANDLE g_crashhandler_process = nullptr; +HANDLE g_crashhandler_pipe_write = nullptr; std::chrono::time_point g_time_start; @@ -73,7 +71,6 @@ bool is_whitelist_exception(const DWORD code) } } - bool get_module_file_and_base(const DWORD64 address, DWORD64& module_base, std::filesystem::path& module_file) { HMODULE handle; @@ -91,7 +88,6 @@ bool get_module_file_and_base(const DWORD64 address, DWORD64& module_base, std:: return false; } - bool is_ffxiv_address(const wchar_t* module_name, const DWORD64 address) { DWORD64 module_base; @@ -100,369 +96,223 @@ bool is_ffxiv_address(const wchar_t* module_name, const DWORD64 address) return false; } - -bool get_sym_from_addr(const DWORD64 address, DWORD64& displacement, std::wstring& symbol_name) +static void append_injector_launch_args(std::vector& args) { - union { - char buffer[sizeof(SYMBOL_INFOW) + MAX_SYM_NAME * sizeof(wchar_t)]{}; - SYMBOL_INFOW symbol; - }; - symbol.SizeOfStruct = sizeof(SYMBOL_INFO); - symbol.MaxNameLen = MAX_SYM_NAME; + args.emplace_back(L"-g"); + args.emplace_back(utils::loaded_module::current_process().path().wstring()); + if (g_startInfo.BootShowConsole) + args.emplace_back(L"--console"); + if (g_startInfo.BootEnableEtw) + args.emplace_back(L"--etw"); + if (g_startInfo.BootVehEnabled) + args.emplace_back(L"--veh"); + if (g_startInfo.BootVehFull) + args.emplace_back(L"--veh-full"); + if ((g_startInfo.BootWaitMessageBox & DalamudStartInfo::WaitMessageboxFlags::BeforeInitialize) != DalamudStartInfo::WaitMessageboxFlags::None) + args.emplace_back(L"--msgbox1"); + if ((g_startInfo.BootWaitMessageBox & DalamudStartInfo::WaitMessageboxFlags::BeforeDalamudEntrypoint) != DalamudStartInfo::WaitMessageboxFlags::None) + args.emplace_back(L"--msgbox2"); + if ((g_startInfo.BootWaitMessageBox & DalamudStartInfo::WaitMessageboxFlags::BeforeDalamudConstruct) != DalamudStartInfo::WaitMessageboxFlags::None) + args.emplace_back(L"--msgbox3"); - if (SymFromAddrW(GetCurrentProcess(), address, &displacement, &symbol) && symbol.Name[0]) - { - symbol_name = symbol.Name; - return true; + args.emplace_back(L"--"); + + if (int nArgs; LPWSTR * szArgList = CommandLineToArgvW(GetCommandLineW(), &nArgs)) { + for (auto i = 1; i < nArgs; i++) + args.emplace_back(szArgList[i]); + LocalFree(szArgList); } - return false; -} - - -std::wstring to_address_string(const DWORD64 address, const bool try_ptrderef = true) -{ - DWORD64 module_base; - std::filesystem::path module_path; - bool is_mod_addr = get_module_file_and_base(address, module_base, module_path); - - DWORD64 value = 0; - if(try_ptrderef && address > 0x10000 && address < 0x7FFFFFFE0000) - { - ReadProcessMemory(GetCurrentProcess(), reinterpret_cast(address), &value, sizeof value, nullptr); - } - - std::wstring addr_str = is_mod_addr ? - std::format(L"{}+{:X}", module_path.filename().c_str(), address - module_base) : - std::format(L"{:X}", address); - - DWORD64 displacement; - if (std::wstring symbol; get_sym_from_addr(address, displacement, symbol)) - return std::format(L"{}\t({})", addr_str, displacement != 0 ? std::format(L"{}+0x{:X}", symbol, displacement) : std::format(L"{}", symbol)); - return value != 0 ? std::format(L"{} [{}]", addr_str, to_address_string(value, false)) : addr_str; -} - -void print_exception_info_extended(const EXCEPTION_POINTERS* ex, std::wostringstream& log) -{ - CONTEXT ctx = *ex->ContextRecord; - - log << L"\nRegisters\n{"; - - log << std::format(L"\n RAX:\t{}", to_address_string(ctx.Rax)); - log << std::format(L"\n RBX:\t{}", to_address_string(ctx.Rbx)); - log << std::format(L"\n RCX:\t{}", to_address_string(ctx.Rcx)); - log << std::format(L"\n RDX:\t{}", to_address_string(ctx.Rdx)); - log << std::format(L"\n R8:\t{}", to_address_string(ctx.R8)); - log << std::format(L"\n R9:\t{}", to_address_string(ctx.R9)); - log << std::format(L"\n R10:\t{}", to_address_string(ctx.R10)); - log << std::format(L"\n R11:\t{}", to_address_string(ctx.R11)); - log << std::format(L"\n R12:\t{}", to_address_string(ctx.R12)); - log << std::format(L"\n R13:\t{}", to_address_string(ctx.R13)); - log << std::format(L"\n R14:\t{}", to_address_string(ctx.R14)); - log << std::format(L"\n R15:\t{}", to_address_string(ctx.R15)); - - log << std::format(L"\n RSI:\t{}", to_address_string(ctx.Rsi)); - log << std::format(L"\n RDI:\t{}", to_address_string(ctx.Rdi)); - log << std::format(L"\n RBP:\t{}", to_address_string(ctx.Rbp)); - log << std::format(L"\n RSP:\t{}", to_address_string(ctx.Rsp)); - log << std::format(L"\n RIP:\t{}", to_address_string(ctx.Rip)); - - log << L"\n}" << std::endl; - - if(0x10000 < ctx.Rsp && ctx.Rsp < 0x7FFFFFFE0000) - { - log << L"\nStack\n{"; - - for(DWORD64 i = 0; i < 16; i++) - log << std::format(L"\n [RSP+{:X}]\t{}", i * 8, to_address_string(*reinterpret_cast(ctx.Rsp + i * 8ull))); - - log << L"\n}\n"; - } - - log << L"\nModules\n{"; - - if(HANDLE snap = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE | TH32CS_SNAPMODULE32, GetCurrentProcessId()); snap != INVALID_HANDLE_VALUE) - { - MODULEENTRY32 mod; - mod.dwSize = sizeof MODULEENTRY32; - if(Module32First(snap, &mod)) - { - do - { - log << std::format(L"\n {:08X}\t{}", reinterpret_cast(mod.modBaseAddr), mod.szExePath); - } - while (Module32Next(snap, &mod)); - } - CloseHandle(snap); - } - - log << L"\n}\n"; -} - -void print_exception_info(const EXCEPTION_POINTERS* ex, std::wostringstream& log) -{ - size_t rec_index = 0; - for (auto rec = ex->ExceptionRecord; rec; rec = rec->ExceptionRecord) - { - log << std::format(L"\nException Info #{}\n", ++rec_index); - log << std::format(L"Address: {:X}\n", rec->ExceptionCode); - log << std::format(L"Flags: {:X}\n", rec->ExceptionFlags); - log << std::format(L"Address: {:X}\n", reinterpret_cast(rec->ExceptionAddress)); - if (!rec->NumberParameters) - continue; - log << L"Parameters: "; - for (DWORD i = 0; i < rec->NumberParameters; ++i) - { - if (i != 0) - log << L", "; - log << std::format(L"{:X}", rec->ExceptionInformation[i]); - } - } - - log << L"\nCall Stack\n{"; - - STACKFRAME64 sf; - sf.AddrPC.Offset = ex->ContextRecord->Rip; - sf.AddrPC.Mode = AddrModeFlat; - sf.AddrStack.Offset = ex->ContextRecord->Rsp; - sf.AddrStack.Mode = AddrModeFlat; - sf.AddrFrame.Offset = ex->ContextRecord->Rbp; - sf.AddrFrame.Mode = AddrModeFlat; - CONTEXT ctx = *ex->ContextRecord; - int frame_index = 0; - - log << std::format(L"\n [{}]\t{}", frame_index++, to_address_string(sf.AddrPC.Offset, false)); - - do - { - if (!StackWalk64(IMAGE_FILE_MACHINE_AMD64, GetCurrentProcess(), GetCurrentThread(), &sf, &ctx, nullptr, SymFunctionTableAccess64, SymGetModuleBase64, nullptr)) - break; - - log << std::format(L"\n [{}]\t{}", frame_index++, to_address_string(sf.AddrPC.Offset, false)); - - } while (sf.AddrReturn.Offset != 0 && sf.AddrPC.Offset != sf.AddrReturn.Offset); - - log << L"\n}\n"; -} - -HRESULT CALLBACK TaskDialogCallbackProc(HWND hwnd, - UINT uNotification, - WPARAM wParam, - LPARAM lParam, - LONG_PTR dwRefData) -{ - HRESULT hr = S_OK; - - switch (uNotification) - { - case TDN_CREATED: - SetWindowPos(hwnd, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE); - break; - } - - return hr; } LONG exception_handler(EXCEPTION_POINTERS* ex) { static std::recursive_mutex s_exception_handler_mutex; - if (!is_whitelist_exception(ex->ExceptionRecord->ExceptionCode)) - return EXCEPTION_CONTINUE_SEARCH; + if (ex->ExceptionRecord->ExceptionCode == 0x12345678) + { + // pass + } + else + { + if (!is_whitelist_exception(ex->ExceptionRecord->ExceptionCode)) + return EXCEPTION_CONTINUE_SEARCH; - if (!is_ffxiv_address(L"ffxiv_dx11.exe", ex->ContextRecord->Rip) && - !is_ffxiv_address(L"cimgui.dll", ex->ContextRecord->Rip)) - return EXCEPTION_CONTINUE_SEARCH; + if (!is_ffxiv_address(L"ffxiv_dx11.exe", ex->ContextRecord->Rip) && + !is_ffxiv_address(L"cimgui.dll", ex->ContextRecord->Rip)) + return EXCEPTION_CONTINUE_SEARCH; + } // block any other exceptions hitting the veh while the messagebox is open const auto lock = std::lock_guard(s_exception_handler_mutex); - const auto module_path = utils::loaded_module(g_hModule).path().parent_path(); -#ifndef NDEBUG - const auto dmp_path = (module_path / L"dalamud_appcrashd.dmp").wstring(); -#else - const auto dmp_path = (module_path / L"dalamud_appcrash.dmp").wstring(); -#endif - const auto log_path = (module_path / L"dalamud_appcrash.log").wstring(); + exception_info exinfo{}; + exinfo.pExceptionPointers = ex; + exinfo.ExceptionPointers = *ex; + exinfo.ContextRecord = *ex->ContextRecord; + exinfo.ExceptionRecord = ex->ExceptionRecord ? *ex->ExceptionRecord : EXCEPTION_RECORD{}; + const auto time_now = std::chrono::system_clock::now(); + auto lifetime = std::chrono::duration_cast( + time_now.time_since_epoch()).count() + - std::chrono::duration_cast( + g_time_start.time_since_epoch()).count(); + exinfo.nLifetime = lifetime; + DuplicateHandle(GetCurrentProcess(), GetCurrentThread(), g_crashhandler_process, &exinfo.hThreadHandle, 0, TRUE, DUPLICATE_SAME_ACCESS); - std::wostringstream log; - log << std::format(L"Unhandled native exception occurred at {}", to_address_string(ex->ContextRecord->Rip, false)) << std::endl; - log << std::format(L"Code: {:X}", ex->ExceptionRecord->ExceptionCode) << std::endl; - log << std::format(L"Dump at: {}", dmp_path) << std::endl; - log << L"Time: " << std::chrono::zoned_time{ std::chrono::current_zone(), std::chrono::system_clock::now() } << std::endl; - - SymRefreshModuleList(GetCurrentProcess()); - print_exception_info(ex, log); - auto window_log_str = log.str(); - print_exception_info_extended(ex, log); - - if (g_crashhandler_shared_info && g_crashhandler_event) - { - memset(g_crashhandler_shared_info, 0, sizeof(exception_info)); - - wcsncpy_s(g_crashhandler_shared_info->DumpPath, dmp_path.c_str(), 1000); - g_crashhandler_shared_info->ThreadId = GetThreadId(GetCurrentThread()); - g_crashhandler_shared_info->ProcessId = GetCurrentProcessId(); - g_crashhandler_shared_info->ExceptionPointers = ex; - g_crashhandler_shared_info->DoFullDump = g_veh_do_full_dump; - g_crashhandler_shared_info->ExceptionCode = ex->ExceptionRecord->ExceptionCode; - - const auto time_now = std::chrono::system_clock::now(); - auto lifetime = std::chrono::duration_cast( - time_now.time_since_epoch()).count() - - std::chrono::duration_cast( - g_time_start.time_since_epoch()).count(); - - g_crashhandler_shared_info->Lifetime = lifetime; - - SetEvent(g_crashhandler_event); - } - - std::wstring message; - void* fn; - if (const auto err = static_cast(g_clr->get_function_pointer( + std::wstring stackTrace; + if (void* fn; const auto err = static_cast(g_clr->get_function_pointer( L"Dalamud.EntryPoint, Dalamud", L"VehCallback", L"Dalamud.EntryPoint+VehDelegate, Dalamud", nullptr, nullptr, &fn))) { - message = std::format( - L"An error within the game has occurred.\n\n" - L"This may be caused by a faulty plugin, a broken TexTools modification, any other third-party tool or simply a bug in the game.\n" - L"Please try \"Start Over\" or \"Download Index Backup\" in TexTools, an integrity check in the XIVLauncher settings, and disabling plugins you don't need.\n\n" - L"The log file is located at:\n" - L"{1}\n\n" - L"Press OK to exit the application.\n\nFailed to read stack trace: 0x{2:08x}", - dmp_path, log_path, err); + stackTrace = std::format(L"Failed to read stack trace: 0x{:08x}", err); } else { - const auto pMessage = ((wchar_t*(__stdcall*)(const void*, const void*, const void*))fn)(dmp_path.c_str(), log_path.c_str(), log.str().c_str()); - message = pMessage; + stackTrace = static_cast(fn)(); // Don't free it, as the program's going to be quit anyway } + + exinfo.dwStackTraceLength = static_cast(stackTrace.size()); + if (DWORD written; !WriteFile(g_crashhandler_pipe_write, &exinfo, static_cast(sizeof exinfo), &written, nullptr) || sizeof exinfo != written) + return EXCEPTION_CONTINUE_SEARCH; - logging::E(std::format(L"Trapped in VEH handler: {}", message)); - - // show in another thread to prevent messagebox from pumping messages of current thread - std::thread([&]() - { - int nButtonPressed = 0; - TASKDIALOGCONFIG config = {0}; - const TASKDIALOG_BUTTON buttons[] = { - {IDOK, L"Disable all plugins"}, - {IDABORT, L"Open help page"}, - }; - config.cbSize = sizeof(config); - config.hInstance = g_hModule; - config.dwCommonButtons = TDCBF_CLOSE_BUTTON; - config.pszMainIcon = MAKEINTRESOURCE(IDI_ICON1); - //config.hMainIcon = dalamud_icon; - config.pszMainInstruction = L"An error occurred"; - config.pszContent = message.c_str(); - config.pButtons = buttons; - config.cButtons = ARRAYSIZE(buttons); - config.pszExpandedInformation = window_log_str.c_str(); - config.pszWindowTitle = L"Dalamud Error"; - config.nDefaultButton = IDCLOSE; - config.cxWidth = 300; - - // Can't do this, xiv stops pumping messages here - //config.hwndParent = FindWindowA("FFXIVGAME", NULL); - - config.pfCallback = TaskDialogCallbackProc; - - TaskDialogIndirect(&config, &nButtonPressed, NULL, NULL); - switch (nButtonPressed) - { - case IDOK: - TCHAR szPath[MAX_PATH]; - if (SUCCEEDED(SHGetFolderPath(NULL, CSIDL_APPDATA, NULL, SHGFP_TYPE_CURRENT, szPath))) - { - auto appdata = std::filesystem::path(szPath); - auto safemode_file_path = ( appdata / "XIVLauncher" / ".dalamud_safemode" ); - - std::ofstream ofs(safemode_file_path); - ofs << "STAY SAFE!!!"; - ofs.close(); - } - - break; - case IDABORT: - ShellExecute(0, 0, L"https://goatcorp.github.io/faq?utm_source=vectored", 0, 0 , SW_SHOW ); - break; - case IDCANCEL: - break; - default: - break; - } - }).join(); + if (DWORD written; !WriteFile(g_crashhandler_pipe_write, &stackTrace[0], static_cast(std::span(stackTrace).size_bytes()), &written, nullptr) || std::span(stackTrace).size_bytes() != written) + return EXCEPTION_CONTINUE_SEARCH; + SuspendThread(GetCurrentThread()); return EXCEPTION_CONTINUE_SEARCH; } -bool veh::add_handler(bool doFullDump, std::string workingDirectory) +bool veh::add_handler(bool doFullDump, const std::string& workingDirectory) { if (g_veh_handle) return false; + g_veh_handle = AddVectoredExceptionHandler(1, exception_handler); SetUnhandledExceptionFilter(nullptr); g_veh_do_full_dump = doFullDump; g_time_start = std::chrono::system_clock::now(); - auto file_mapping = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, sizeof(exception_info), SHARED_INFO_FILE_NAME); - if (!file_mapping) { - std::cout << "Could not map info share file.\n"; - g_crashhandler_shared_info = nullptr; + std::optional, decltype(&CloseHandle)>> hWritePipe; + std::optional, decltype(&CloseHandle)>> hReadPipeInheritable; + if (HANDLE hReadPipeRaw, hWritePipeRaw; CreatePipe(&hReadPipeRaw, &hWritePipeRaw, nullptr, 65536)) + { + hWritePipe.emplace(hWritePipeRaw, &CloseHandle); + + if (HANDLE hReadPipeInheritableRaw; DuplicateHandle(GetCurrentProcess(), hReadPipeRaw, GetCurrentProcess(), &hReadPipeInheritableRaw, 0, TRUE, DUPLICATE_SAME_ACCESS | DUPLICATE_CLOSE_SOURCE)) + { + hReadPipeInheritable.emplace(hReadPipeInheritableRaw, &CloseHandle); + } + else + { + logging::W("Failed to launch DalamudCrashHandler.exe: DuplicateHandle(1) error 0x{:x}", GetLastError()); + return false; + } } else { - g_crashhandler_shared_info = (exception_info*)MapViewOfFile(file_mapping, FILE_MAP_ALL_ACCESS, 0, 0, sizeof(exception_info)); - if (!g_crashhandler_shared_info) { - std::cout << "Could not map view of info share file.\n"; + logging::W("Failed to launch DalamudCrashHandler.exe: CreatePipe error 0x{:x}", GetLastError()); + return false; + } + + // additional information + STARTUPINFOEXW siex{}; + PROCESS_INFORMATION pi{}; + + siex.StartupInfo.cb = sizeof siex; + siex.StartupInfo.dwFlags = STARTF_USESHOWWINDOW; +#ifdef NDEBUG + siex.StartupInfo.wShowWindow = SW_HIDE; +#else + siex.StartupInfo.wShowWindow = SW_SHOW; +#endif + + // set up list of handles to inherit to child process + std::vector attributeListBuf; + if (SIZE_T size = 0; !InitializeProcThreadAttributeList(nullptr, 1, 0, &size)) + { + if (const auto err = GetLastError(); err != ERROR_INSUFFICIENT_BUFFER) + { + logging::W("Failed to launch DalamudCrashHandler.exe: InitializeProcThreadAttributeList(1) error 0x{:x}", err); + return false; + } + + attributeListBuf.resize(size); + siex.lpAttributeList = reinterpret_cast(&attributeListBuf[0]); + if (!InitializeProcThreadAttributeList(siex.lpAttributeList, 1, 0, &size)) + { + logging::W("Failed to launch DalamudCrashHandler.exe: InitializeProcThreadAttributeList(2) error 0x{:x}", GetLastError()); + return false; } } - - g_crashhandler_event = CreateEvent( - NULL, // default security attributes - TRUE, // manual-reset event - FALSE, // initial state is nonsignaled - CRASHDUMP_EVENT_NAME // object name - ); - - if (!g_crashhandler_event) + else { - std::cout << "Couldn't acquire event handle\n"; + logging::W("Failed to launch DalamudCrashHandler.exe: InitializeProcThreadAttributeList(0) was supposed to fail"); + return false; + } + std::unique_ptr, decltype(&DeleteProcThreadAttributeList)> cleanAttributeList(siex.lpAttributeList, &DeleteProcThreadAttributeList); + + std::vector handles; + + HANDLE hInheritableCurrentProcess; + if (!DuplicateHandle(GetCurrentProcess(), GetCurrentProcess(), GetCurrentProcess(), &hInheritableCurrentProcess, 0, TRUE, DUPLICATE_SAME_ACCESS)) + { + logging::W("Failed to launch DalamudCrashHandler.exe: DuplicateHandle(2) error 0x{:x}", GetLastError()); + return false; + } + handles.push_back(hInheritableCurrentProcess); + handles.push_back(hReadPipeInheritable->get()); + + std::vector args; + std::wstring argstr; + args.emplace_back((std::filesystem::path(workingDirectory) / "DalamudCrashHandler.exe").wstring()); + args.emplace_back(std::format(L"--process-handle={}", reinterpret_cast(hInheritableCurrentProcess))); + args.emplace_back(std::format(L"--exception-info-pipe-read-handle={}", reinterpret_cast(hReadPipeInheritable->get()))); + args.emplace_back(std::format(L"--asset-directory={}", unicode::convert(g_startInfo.AssetDirectory))); + args.emplace_back(std::format(L"--log-directory={}", g_startInfo.BootLogPath.empty() + ? utils::loaded_module(g_hModule).path().parent_path().wstring() + : std::filesystem::path(unicode::convert(g_startInfo.BootLogPath)).parent_path().wstring())); + args.emplace_back(L"--"); + append_injector_launch_args(args); + + for (const auto& arg : args) + { + argstr.append(utils::escape_shell_arg(arg)); + argstr.push_back(L' '); + } + argstr.pop_back(); + + if (!handles.empty() && !UpdateProcThreadAttribute(siex.lpAttributeList, 0, PROC_THREAD_ATTRIBUTE_HANDLE_LIST, &handles[0], std::span(handles).size_bytes(), nullptr, nullptr)) + { + logging::W("Failed to launch DalamudCrashHandler.exe: UpdateProcThreadAttribute error 0x{:x}", GetLastError()); + return false; } - auto handler_path = std::filesystem::path(workingDirectory) / "DalamudCrashHandler.exe"; - - // additional information - STARTUPINFO si; - PROCESS_INFORMATION pi; + if (!CreateProcessW( + args[0].c_str(), // The path + &argstr[0], // Command line + nullptr, // Process handle not inheritable + nullptr, // Thread handle not inheritable + TRUE, // Set handle inheritance to FALSE + EXTENDED_STARTUPINFO_PRESENT, // lpStartupInfo actually points to a STARTUPINFOEX(W) + nullptr, // Use parent's environment block + nullptr, // Use parent's starting directory + &siex.StartupInfo, // Pointer to STARTUPINFO structure + &pi // Pointer to PROCESS_INFORMATION structure (removed extra parentheses) + )) + { + logging::W("Failed to launch DalamudCrashHandler.exe: CreateProcessW error 0x{:x}", GetLastError()); + return false; + } - // set the size of the structures - ZeroMemory( &si, sizeof(si) ); - si.cb = sizeof(si); - ZeroMemory( &pi, sizeof(pi) ); + CloseHandle(pi.hThread); - CreateProcess( handler_path.c_str(), // the path - NULL, // Command line - NULL, // Process handle not inheritable - NULL, // Thread handle not inheritable - FALSE, // Set handle inheritance to FALSE - 0, // No creation flags - NULL, // Use parent's environment block - NULL, // Use parent's starting directory - &si, // Pointer to STARTUPINFO structure - &pi // Pointer to PROCESS_INFORMATION structure (removed extra parentheses) - ); - - // Close process and thread handles. - CloseHandle( pi.hProcess ); - CloseHandle( pi.hThread ); - - return g_veh_handle != nullptr; + g_crashhandler_process = pi.hProcess; + g_crashhandler_pipe_write = hWritePipe->release(); + logging::I("Launched DalamudCrashHandler.exe: PID {}", pi.dwProcessId); + return true; } bool veh::remove_handler() diff --git a/Dalamud.Boot/veh.h b/Dalamud.Boot/veh.h index 7820d6982..1905272ea 100644 --- a/Dalamud.Boot/veh.h +++ b/Dalamud.Boot/veh.h @@ -2,6 +2,6 @@ namespace veh { - bool add_handler(bool doFullDump, std::string workingDirectory); + bool add_handler(bool doFullDump, const std::string& workingDirectory); bool remove_handler(); } diff --git a/Dalamud.Injector/EntryPoint.cs b/Dalamud.Injector/EntryPoint.cs index 88a240c65..9b5cfc07e 100644 --- a/Dalamud.Injector/EntryPoint.cs +++ b/Dalamud.Injector/EntryPoint.cs @@ -7,7 +7,7 @@ using System.Linq; using System.Reflection; using System.Runtime.InteropServices; using System.Text; - +using System.Text.RegularExpressions; using Dalamud.Game; using Newtonsoft.Json; using Reloaded.Memory.Buffers; @@ -484,6 +484,7 @@ namespace Dalamud.Injector var withoutDalamud = false; var noFixAcl = false; var waitForGameWindow = true; + var encryptArguments = false; var parsingGameArgument = false; for (var i = 2; i < args.Count; i++) @@ -520,6 +521,43 @@ namespace Dalamud.Injector throw new CommandLineException($"\"{args[i]}\" is not a command line argument."); } + var checksumTable = "fX1pGtdS5CAP4_VL"; + var argDelimiterRegex = new Regex(" (? + { + if (!x.StartsWith("//**sqex0003") || !x.EndsWith("**//")) + return new List() { x }; + + var checksum = checksumTable.IndexOf(x[x.Length - 5]); + if (checksum == -1) + return new List() { x }; + + var encData = Convert.FromBase64String(x.Substring(12, x.Length - 12 - 5).Replace('-', '+').Replace('_', '/').Replace('*', '=')); + var rawData = new byte[encData.Length]; + + for (var i = (uint)checksum; i < 0x10000u; i += 0x10) + { + var bf = new LegacyBlowfish(Encoding.UTF8.GetBytes($"{i << 16:x08}")); + Buffer.BlockCopy(encData, 0, rawData, 0, rawData.Length); + bf.Decrypt(ref rawData); + var rawString = Encoding.UTF8.GetString(rawData).Split('\0', 2).First(); + encryptArguments = true; + var args = argDelimiterRegex.Split(rawString).Skip(1).Select(y => string.Join('=', kvDelimiterRegex.Split(y, 2)).Replace(" ", " ")).ToList(); + if (!args.Any()) + continue; + if (!args.First().StartsWith("T=")) + continue; + if (!uint.TryParse(args.First().Substring(2), out var tickCount)) + continue; + if (tickCount >> 16 != i) + continue; + return args.Skip(1); + } + + return new List() { x }; + }).ToList(); + if (showHelp) { ProcessHelpCommand(args, "launch"); @@ -603,7 +641,39 @@ namespace Dalamud.Injector }); } - var gameArgumentString = string.Join(" ", gameArguments.Select(x => EncodeParameterArgument(x))); + string gameArgumentString; + if (encryptArguments) + { + var rawTickCount = (uint)Environment.TickCount; + + if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + { + [System.Runtime.InteropServices.DllImport("c")] + static extern ulong clock_gettime_nsec_np(int clock_id); + + const int CLOCK_MONOTONIC_RAW = 4; + var rawTickCountFixed = (clock_gettime_nsec_np(CLOCK_MONOTONIC_RAW) / 1000000); + Log.Information("ArgumentBuilder::DeriveKey() fixing up rawTickCount from {0} to {1} on macOS", rawTickCount, rawTickCountFixed); + rawTickCount = (uint)rawTickCountFixed; + } + + var ticks = rawTickCount & 0xFFFF_FFFFu; + var key = ticks & 0xFFFF_0000u; + gameArguments.Insert(0, $"T={ticks}"); + + var escapeValue = (string x) => x.Replace(" ", " "); + gameArgumentString = gameArguments.Select(x => x.Split('=', 2)).Aggregate(new StringBuilder(), (whole, part) => whole.Append($" /{escapeValue(part[0])} ={escapeValue(part.Length > 1 ? part[1] : string.Empty)}")).ToString(); + var bf = new LegacyBlowfish(Encoding.UTF8.GetBytes($"{key:x08}")); + var ciphertext = bf.Encrypt(Encoding.UTF8.GetBytes(gameArgumentString)); + var base64Str = Convert.ToBase64String(ciphertext).Replace('+', '-').Replace('/', '_').Replace('=', '*'); + var checksum = checksumTable[(int)(key >> 16) & 0xF]; + gameArgumentString = $"//**sqex0003{base64Str}{checksum}**//"; + } + else + { + gameArgumentString = string.Join(" ", gameArguments.Select(x => EncodeParameterArgument(x))); + } + var process = GameStart.LaunchGame(Path.GetDirectoryName(gamePath), gamePath, gameArgumentString, noFixAcl, (Process p) => { if (!withoutDalamud && mode == "entrypoint") diff --git a/Dalamud.Injector/LegacyBlowfish.cs b/Dalamud.Injector/LegacyBlowfish.cs new file mode 100644 index 000000000..576940521 --- /dev/null +++ b/Dalamud.Injector/LegacyBlowfish.cs @@ -0,0 +1,319 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Dalamud.Injector +{ + internal class LegacyBlowfish + { + #region P-Array and S-Boxes + + private readonly uint[] p = + { + 0x243f6a88, 0x85a308d3, 0x13198a2e, 0x03707344, 0xa4093822, 0x299f31d0, + 0x082efa98, 0xec4e6c89, 0x452821e6, 0x38d01377, 0xbe5466cf, 0x34e90c6c, + 0xc0ac29b7, 0xc97c50dd, 0x3f84d5b5, 0xb5470917, 0x9216d5d9, 0x8979fb1b + }; + + private readonly uint[,] s = + { + { + 0xd1310ba6, 0x98dfb5ac, 0x2ffd72db, 0xd01adfb7, 0xb8e1afed, 0x6a267e96, + 0xba7c9045, 0xf12c7f99, 0x24a19947, 0xb3916cf7, 0x0801f2e2, 0x858efc16, + 0x636920d8, 0x71574e69, 0xa458fea3, 0xf4933d7e, 0x0d95748f, 0x728eb658, + 0x718bcd58, 0x82154aee, 0x7b54a41d, 0xc25a59b5, 0x9c30d539, 0x2af26013, + 0xc5d1b023, 0x286085f0, 0xca417918, 0xb8db38ef, 0x8e79dcb0, 0x603a180e, + 0x6c9e0e8b, 0xb01e8a3e, 0xd71577c1, 0xbd314b27, 0x78af2fda, 0x55605c60, + 0xe65525f3, 0xaa55ab94, 0x57489862, 0x63e81440, 0x55ca396a, 0x2aab10b6, + 0xb4cc5c34, 0x1141e8ce, 0xa15486af, 0x7c72e993, 0xb3ee1411, 0x636fbc2a, + 0x2ba9c55d, 0x741831f6, 0xce5c3e16, 0x9b87931e, 0xafd6ba33, 0x6c24cf5c, + 0x7a325381, 0x28958677, 0x3b8f4898, 0x6b4bb9af, 0xc4bfe81b, 0x66282193, + 0x61d809cc, 0xfb21a991, 0x487cac60, 0x5dec8032, 0xef845d5d, 0xe98575b1, + 0xdc262302, 0xeb651b88, 0x23893e81, 0xd396acc5, 0x0f6d6ff3, 0x83f44239, + 0x2e0b4482, 0xa4842004, 0x69c8f04a, 0x9e1f9b5e, 0x21c66842, 0xf6e96c9a, + 0x670c9c61, 0xabd388f0, 0x6a51a0d2, 0xd8542f68, 0x960fa728, 0xab5133a3, + 0x6eef0b6c, 0x137a3be4, 0xba3bf050, 0x7efb2a98, 0xa1f1651d, 0x39af0176, + 0x66ca593e, 0x82430e88, 0x8cee8619, 0x456f9fb4, 0x7d84a5c3, 0x3b8b5ebe, + 0xe06f75d8, 0x85c12073, 0x401a449f, 0x56c16aa6, 0x4ed3aa62, 0x363f7706, + 0x1bfedf72, 0x429b023d, 0x37d0d724, 0xd00a1248, 0xdb0fead3, 0x49f1c09b, + 0x075372c9, 0x80991b7b, 0x25d479d8, 0xf6e8def7, 0xe3fe501a, 0xb6794c3b, + 0x976ce0bd, 0x04c006ba, 0xc1a94fb6, 0x409f60c4, 0x5e5c9ec2, 0x196a2463, + 0x68fb6faf, 0x3e6c53b5, 0x1339b2eb, 0x3b52ec6f, 0x6dfc511f, 0x9b30952c, + 0xcc814544, 0xaf5ebd09, 0xbee3d004, 0xde334afd, 0x660f2807, 0x192e4bb3, + 0xc0cba857, 0x45c8740f, 0xd20b5f39, 0xb9d3fbdb, 0x5579c0bd, 0x1a60320a, + 0xd6a100c6, 0x402c7279, 0x679f25fe, 0xfb1fa3cc, 0x8ea5e9f8, 0xdb3222f8, + 0x3c7516df, 0xfd616b15, 0x2f501ec8, 0xad0552ab, 0x323db5fa, 0xfd238760, + 0x53317b48, 0x3e00df82, 0x9e5c57bb, 0xca6f8ca0, 0x1a87562e, 0xdf1769db, + 0xd542a8f6, 0x287effc3, 0xac6732c6, 0x8c4f5573, 0x695b27b0, 0xbbca58c8, + 0xe1ffa35d, 0xb8f011a0, 0x10fa3d98, 0xfd2183b8, 0x4afcb56c, 0x2dd1d35b, + 0x9a53e479, 0xb6f84565, 0xd28e49bc, 0x4bfb9790, 0xe1ddf2da, 0xa4cb7e33, + 0x62fb1341, 0xcee4c6e8, 0xef20cada, 0x36774c01, 0xd07e9efe, 0x2bf11fb4, + 0x95dbda4d, 0xae909198, 0xeaad8e71, 0x6b93d5a0, 0xd08ed1d0, 0xafc725e0, + 0x8e3c5b2f, 0x8e7594b7, 0x8ff6e2fb, 0xf2122b64, 0x8888b812, 0x900df01c, + 0x4fad5ea0, 0x688fc31c, 0xd1cff191, 0xb3a8c1ad, 0x2f2f2218, 0xbe0e1777, + 0xea752dfe, 0x8b021fa1, 0xe5a0cc0f, 0xb56f74e8, 0x18acf3d6, 0xce89e299, + 0xb4a84fe0, 0xfd13e0b7, 0x7cc43b81, 0xd2ada8d9, 0x165fa266, 0x80957705, + 0x93cc7314, 0x211a1477, 0xe6ad2065, 0x77b5fa86, 0xc75442f5, 0xfb9d35cf, + 0xebcdaf0c, 0x7b3e89a0, 0xd6411bd3, 0xae1e7e49, 0x00250e2d, 0x2071b35e, + 0x226800bb, 0x57b8e0af, 0x2464369b, 0xf009b91e, 0x5563911d, 0x59dfa6aa, + 0x78c14389, 0xd95a537f, 0x207d5ba2, 0x02e5b9c5, 0x83260376, 0x6295cfa9, + 0x11c81968, 0x4e734a41, 0xb3472dca, 0x7b14a94a, 0x1b510052, 0x9a532915, + 0xd60f573f, 0xbc9bc6e4, 0x2b60a476, 0x81e67400, 0x08ba6fb5, 0x571be91f, + 0xf296ec6b, 0x2a0dd915, 0xb6636521, 0xe7b9f9b6, 0xff34052e, 0xc5855664, + 0x53b02d5d, 0xa99f8fa1, 0x08ba4799, 0x6e85076a + }, + { + 0x4b7a70e9, 0xb5b32944, 0xdb75092e, 0xc4192623, 0xad6ea6b0, 0x49a7df7d, + 0x9cee60b8, 0x8fedb266, 0xecaa8c71, 0x699a17ff, 0x5664526c, 0xc2b19ee1, + 0x193602a5, 0x75094c29, 0xa0591340, 0xe4183a3e, 0x3f54989a, 0x5b429d65, + 0x6b8fe4d6, 0x99f73fd6, 0xa1d29c07, 0xefe830f5, 0x4d2d38e6, 0xf0255dc1, + 0x4cdd2086, 0x8470eb26, 0x6382e9c6, 0x021ecc5e, 0x09686b3f, 0x3ebaefc9, + 0x3c971814, 0x6b6a70a1, 0x687f3584, 0x52a0e286, 0xb79c5305, 0xaa500737, + 0x3e07841c, 0x7fdeae5c, 0x8e7d44ec, 0x5716f2b8, 0xb03ada37, 0xf0500c0d, + 0xf01c1f04, 0x0200b3ff, 0xae0cf51a, 0x3cb574b2, 0x25837a58, 0xdc0921bd, + 0xd19113f9, 0x7ca92ff6, 0x94324773, 0x22f54701, 0x3ae5e581, 0x37c2dadc, + 0xc8b57634, 0x9af3dda7, 0xa9446146, 0x0fd0030e, 0xecc8c73e, 0xa4751e41, + 0xe238cd99, 0x3bea0e2f, 0x3280bba1, 0x183eb331, 0x4e548b38, 0x4f6db908, + 0x6f420d03, 0xf60a04bf, 0x2cb81290, 0x24977c79, 0x5679b072, 0xbcaf89af, + 0xde9a771f, 0xd9930810, 0xb38bae12, 0xdccf3f2e, 0x5512721f, 0x2e6b7124, + 0x501adde6, 0x9f84cd87, 0x7a584718, 0x7408da17, 0xbc9f9abc, 0xe94b7d8c, + 0xec7aec3a, 0xdb851dfa, 0x63094366, 0xc464c3d2, 0xef1c1847, 0x3215d908, + 0xdd433b37, 0x24c2ba16, 0x12a14d43, 0x2a65c451, 0x50940002, 0x133ae4dd, + 0x71dff89e, 0x10314e55, 0x81ac77d6, 0x5f11199b, 0x043556f1, 0xd7a3c76b, + 0x3c11183b, 0x5924a509, 0xf28fe6ed, 0x97f1fbfa, 0x9ebabf2c, 0x1e153c6e, + 0x86e34570, 0xeae96fb1, 0x860e5e0a, 0x5a3e2ab3, 0x771fe71c, 0x4e3d06fa, + 0x2965dcb9, 0x99e71d0f, 0x803e89d6, 0x5266c825, 0x2e4cc978, 0x9c10b36a, + 0xc6150eba, 0x94e2ea78, 0xa5fc3c53, 0x1e0a2df4, 0xf2f74ea7, 0x361d2b3d, + 0x1939260f, 0x19c27960, 0x5223a708, 0xf71312b6, 0xebadfe6e, 0xeac31f66, + 0xe3bc4595, 0xa67bc883, 0xb17f37d1, 0x018cff28, 0xc332ddef, 0xbe6c5aa5, + 0x65582185, 0x68ab9802, 0xeecea50f, 0xdb2f953b, 0x2aef7dad, 0x5b6e2f84, + 0x1521b628, 0x29076170, 0xecdd4775, 0x619f1510, 0x13cca830, 0xeb61bd96, + 0x0334fe1e, 0xaa0363cf, 0xb5735c90, 0x4c70a239, 0xd59e9e0b, 0xcbaade14, + 0xeecc86bc, 0x60622ca7, 0x9cab5cab, 0xb2f3846e, 0x648b1eaf, 0x19bdf0ca, + 0xa02369b9, 0x655abb50, 0x40685a32, 0x3c2ab4b3, 0x319ee9d5, 0xc021b8f7, + 0x9b540b19, 0x875fa099, 0x95f7997e, 0x623d7da8, 0xf837889a, 0x97e32d77, + 0x11ed935f, 0x16681281, 0x0e358829, 0xc7e61fd6, 0x96dedfa1, 0x7858ba99, + 0x57f584a5, 0x1b227263, 0x9b83c3ff, 0x1ac24696, 0xcdb30aeb, 0x532e3054, + 0x8fd948e4, 0x6dbc3128, 0x58ebf2ef, 0x34c6ffea, 0xfe28ed61, 0xee7c3c73, + 0x5d4a14d9, 0xe864b7e3, 0x42105d14, 0x203e13e0, 0x45eee2b6, 0xa3aaabea, + 0xdb6c4f15, 0xfacb4fd0, 0xc742f442, 0xef6abbb5, 0x654f3b1d, 0x41cd2105, + 0xd81e799e, 0x86854dc7, 0xe44b476a, 0x3d816250, 0xcf62a1f2, 0x5b8d2646, + 0xfc8883a0, 0xc1c7b6a3, 0x7f1524c3, 0x69cb7492, 0x47848a0b, 0x5692b285, + 0x095bbf00, 0xad19489d, 0x1462b174, 0x23820e00, 0x58428d2a, 0x0c55f5ea, + 0x1dadf43e, 0x233f7061, 0x3372f092, 0x8d937e41, 0xd65fecf1, 0x6c223bdb, + 0x7cde3759, 0xcbee7460, 0x4085f2a7, 0xce77326e, 0xa6078084, 0x19f8509e, + 0xe8efd855, 0x61d99735, 0xa969a7aa, 0xc50c06c2, 0x5a04abfc, 0x800bcadc, + 0x9e447a2e, 0xc3453484, 0xfdd56705, 0x0e1e9ec9, 0xdb73dbd3, 0x105588cd, + 0x675fda79, 0xe3674340, 0xc5c43465, 0x713e38d8, 0x3d28f89e, 0xf16dff20, + 0x153e21e7, 0x8fb03d4a, 0xe6e39f2b, 0xdb83adf7 + }, + { + 0xe93d5a68, 0x948140f7, 0xf64c261c, 0x94692934, 0x411520f7, 0x7602d4f7, + 0xbcf46b2e, 0xd4a20068, 0xd4082471, 0x3320f46a, 0x43b7d4b7, 0x500061af, + 0x1e39f62e, 0x97244546, 0x14214f74, 0xbf8b8840, 0x4d95fc1d, 0x96b591af, + 0x70f4ddd3, 0x66a02f45, 0xbfbc09ec, 0x03bd9785, 0x7fac6dd0, 0x31cb8504, + 0x96eb27b3, 0x55fd3941, 0xda2547e6, 0xabca0a9a, 0x28507825, 0x530429f4, + 0x0a2c86da, 0xe9b66dfb, 0x68dc1462, 0xd7486900, 0x680ec0a4, 0x27a18dee, + 0x4f3ffea2, 0xe887ad8c, 0xb58ce006, 0x7af4d6b6, 0xaace1e7c, 0xd3375fec, + 0xce78a399, 0x406b2a42, 0x20fe9e35, 0xd9f385b9, 0xee39d7ab, 0x3b124e8b, + 0x1dc9faf7, 0x4b6d1856, 0x26a36631, 0xeae397b2, 0x3a6efa74, 0xdd5b4332, + 0x6841e7f7, 0xca7820fb, 0xfb0af54e, 0xd8feb397, 0x454056ac, 0xba489527, + 0x55533a3a, 0x20838d87, 0xfe6ba9b7, 0xd096954b, 0x55a867bc, 0xa1159a58, + 0xcca92963, 0x99e1db33, 0xa62a4a56, 0x3f3125f9, 0x5ef47e1c, 0x9029317c, + 0xfdf8e802, 0x04272f70, 0x80bb155c, 0x05282ce3, 0x95c11548, 0xe4c66d22, + 0x48c1133f, 0xc70f86dc, 0x07f9c9ee, 0x41041f0f, 0x404779a4, 0x5d886e17, + 0x325f51eb, 0xd59bc0d1, 0xf2bcc18f, 0x41113564, 0x257b7834, 0x602a9c60, + 0xdff8e8a3, 0x1f636c1b, 0x0e12b4c2, 0x02e1329e, 0xaf664fd1, 0xcad18115, + 0x6b2395e0, 0x333e92e1, 0x3b240b62, 0xeebeb922, 0x85b2a20e, 0xe6ba0d99, + 0xde720c8c, 0x2da2f728, 0xd0127845, 0x95b794fd, 0x647d0862, 0xe7ccf5f0, + 0x5449a36f, 0x877d48fa, 0xc39dfd27, 0xf33e8d1e, 0x0a476341, 0x992eff74, + 0x3a6f6eab, 0xf4f8fd37, 0xa812dc60, 0xa1ebddf8, 0x991be14c, 0xdb6e6b0d, + 0xc67b5510, 0x6d672c37, 0x2765d43b, 0xdcd0e804, 0xf1290dc7, 0xcc00ffa3, + 0xb5390f92, 0x690fed0b, 0x667b9ffb, 0xcedb7d9c, 0xa091cf0b, 0xd9155ea3, + 0xbb132f88, 0x515bad24, 0x7b9479bf, 0x763bd6eb, 0x37392eb3, 0xcc115979, + 0x8026e297, 0xf42e312d, 0x6842ada7, 0xc66a2b3b, 0x12754ccc, 0x782ef11c, + 0x6a124237, 0xb79251e7, 0x06a1bbe6, 0x4bfb6350, 0x1a6b1018, 0x11caedfa, + 0x3d25bdd8, 0xe2e1c3c9, 0x44421659, 0x0a121386, 0xd90cec6e, 0xd5abea2a, + 0x64af674e, 0xda86a85f, 0xbebfe988, 0x64e4c3fe, 0x9dbc8057, 0xf0f7c086, + 0x60787bf8, 0x6003604d, 0xd1fd8346, 0xf6381fb0, 0x7745ae04, 0xd736fccc, + 0x83426b33, 0xf01eab71, 0xb0804187, 0x3c005e5f, 0x77a057be, 0xbde8ae24, + 0x55464299, 0xbf582e61, 0x4e58f48f, 0xf2ddfda2, 0xf474ef38, 0x8789bdc2, + 0x5366f9c3, 0xc8b38e74, 0xb475f255, 0x46fcd9b9, 0x7aeb2661, 0x8b1ddf84, + 0x846a0e79, 0x915f95e2, 0x466e598e, 0x20b45770, 0x8cd55591, 0xc902de4c, + 0xb90bace1, 0xbb8205d0, 0x11a86248, 0x7574a99e, 0xb77f19b6, 0xe0a9dc09, + 0x662d09a1, 0xc4324633, 0xe85a1f02, 0x09f0be8c, 0x4a99a025, 0x1d6efe10, + 0x1ab93d1d, 0x0ba5a4df, 0xa186f20f, 0x2868f169, 0xdcb7da83, 0x573906fe, + 0xa1e2ce9b, 0x4fcd7f52, 0x50115e01, 0xa70683fa, 0xa002b5c4, 0x0de6d027, + 0x9af88c27, 0x773f8641, 0xc3604c06, 0x61a806b5, 0xf0177a28, 0xc0f586e0, + 0x006058aa, 0x30dc7d62, 0x11e69ed7, 0x2338ea63, 0x53c2dd94, 0xc2c21634, + 0xbbcbee56, 0x90bcb6de, 0xebfc7da1, 0xce591d76, 0x6f05e409, 0x4b7c0188, + 0x39720a3d, 0x7c927c24, 0x86e3725f, 0x724d9db9, 0x1ac15bb4, 0xd39eb8fc, + 0xed545578, 0x08fca5b5, 0xd83d7cd3, 0x4dad0fc4, 0x1e50ef5e, 0xb161e6f8, + 0xa28514d9, 0x6c51133c, 0x6fd5c7e7, 0x56e14ec4, 0x362abfce, 0xddc6c837, + 0xd79a3234, 0x92638212, 0x670efa8e, 0x406000e0 + }, + { + 0x3a39ce37, 0xd3faf5cf, 0xabc27737, 0x5ac52d1b, 0x5cb0679e, 0x4fa33742, + 0xd3822740, 0x99bc9bbe, 0xd5118e9d, 0xbf0f7315, 0xd62d1c7e, 0xc700c47b, + 0xb78c1b6b, 0x21a19045, 0xb26eb1be, 0x6a366eb4, 0x5748ab2f, 0xbc946e79, + 0xc6a376d2, 0x6549c2c8, 0x530ff8ee, 0x468dde7d, 0xd5730a1d, 0x4cd04dc6, + 0x2939bbdb, 0xa9ba4650, 0xac9526e8, 0xbe5ee304, 0xa1fad5f0, 0x6a2d519a, + 0x63ef8ce2, 0x9a86ee22, 0xc089c2b8, 0x43242ef6, 0xa51e03aa, 0x9cf2d0a4, + 0x83c061ba, 0x9be96a4d, 0x8fe51550, 0xba645bd6, 0x2826a2f9, 0xa73a3ae1, + 0x4ba99586, 0xef5562e9, 0xc72fefd3, 0xf752f7da, 0x3f046f69, 0x77fa0a59, + 0x80e4a915, 0x87b08601, 0x9b09e6ad, 0x3b3ee593, 0xe990fd5a, 0x9e34d797, + 0x2cf0b7d9, 0x022b8b51, 0x96d5ac3a, 0x017da67d, 0xd1cf3ed6, 0x7c7d2d28, + 0x1f9f25cf, 0xadf2b89b, 0x5ad6b472, 0x5a88f54c, 0xe029ac71, 0xe019a5e6, + 0x47b0acfd, 0xed93fa9b, 0xe8d3c48d, 0x283b57cc, 0xf8d56629, 0x79132e28, + 0x785f0191, 0xed756055, 0xf7960e44, 0xe3d35e8c, 0x15056dd4, 0x88f46dba, + 0x03a16125, 0x0564f0bd, 0xc3eb9e15, 0x3c9057a2, 0x97271aec, 0xa93a072a, + 0x1b3f6d9b, 0x1e6321f5, 0xf59c66fb, 0x26dcf319, 0x7533d928, 0xb155fdf5, + 0x03563482, 0x8aba3cbb, 0x28517711, 0xc20ad9f8, 0xabcc5167, 0xccad925f, + 0x4de81751, 0x3830dc8e, 0x379d5862, 0x9320f991, 0xea7a90c2, 0xfb3e7bce, + 0x5121ce64, 0x774fbe32, 0xa8b6e37e, 0xc3293d46, 0x48de5369, 0x6413e680, + 0xa2ae0810, 0xdd6db224, 0x69852dfd, 0x09072166, 0xb39a460a, 0x6445c0dd, + 0x586cdecf, 0x1c20c8ae, 0x5bbef7dd, 0x1b588d40, 0xccd2017f, 0x6bb4e3bb, + 0xdda26a7e, 0x3a59ff45, 0x3e350a44, 0xbcb4cdd5, 0x72eacea8, 0xfa6484bb, + 0x8d6612ae, 0xbf3c6f47, 0xd29be463, 0x542f5d9e, 0xaec2771b, 0xf64e6370, + 0x740e0d8d, 0xe75b1357, 0xf8721671, 0xaf537d5d, 0x4040cb08, 0x4eb4e2cc, + 0x34d2466a, 0x0115af84, 0xe1b00428, 0x95983a1d, 0x06b89fb4, 0xce6ea048, + 0x6f3f3b82, 0x3520ab82, 0x011a1d4b, 0x277227f8, 0x611560b1, 0xe7933fdc, + 0xbb3a792b, 0x344525bd, 0xa08839e1, 0x51ce794b, 0x2f32c9b7, 0xa01fbac9, + 0xe01cc87e, 0xbcc7d1f6, 0xcf0111c3, 0xa1e8aac7, 0x1a908749, 0xd44fbd9a, + 0xd0dadecb, 0xd50ada38, 0x0339c32a, 0xc6913667, 0x8df9317c, 0xe0b12b4f, + 0xf79e59b7, 0x43f5bb3a, 0xf2d519ff, 0x27d9459c, 0xbf97222c, 0x15e6fc2a, + 0x0f91fc71, 0x9b941525, 0xfae59361, 0xceb69ceb, 0xc2a86459, 0x12baa8d1, + 0xb6c1075e, 0xe3056a0c, 0x10d25065, 0xcb03a442, 0xe0ec6e0e, 0x1698db3b, + 0x4c98a0be, 0x3278e964, 0x9f1f9532, 0xe0d392df, 0xd3a0342b, 0x8971f21e, + 0x1b0a7441, 0x4ba3348c, 0xc5be7120, 0xc37632d8, 0xdf359f8d, 0x9b992f2e, + 0xe60b6f47, 0x0fe3f11d, 0xe54cda54, 0x1edad891, 0xce6279cf, 0xcd3e7e6f, + 0x1618b166, 0xfd2c1d05, 0x848fd2c5, 0xf6fb2299, 0xf523f357, 0xa6327623, + 0x93a83531, 0x56cccd02, 0xacf08162, 0x5a75ebb5, 0x6e163697, 0x88d273cc, + 0xde966292, 0x81b949d0, 0x4c50901b, 0x71c65614, 0xe6c6c7bd, 0x327a140a, + 0x45e1d006, 0xc3f27b9a, 0xc9aa53fd, 0x62a80f00, 0xbb25bfe2, 0x35bdd2f6, + 0x71126905, 0xb2040222, 0xb6cbcf7c, 0xcd769c2b, 0x53113ec0, 0x1640e3d3, + 0x38abbd60, 0x2547adf0, 0xba38209c, 0xf746ce76, 0x77afa1c5, 0x20756060, + 0x85cbfe4e, 0x8ae88dd8, 0x7aaaf9b0, 0x4cf9aa7e, 0x1948c25c, 0x02fb8a8c, + 0x01c36ae4, 0xd6ebe1f9, 0x90d4f869, 0xa65cdea0, 0x3f09252d, 0xc208e69f, + 0xb74e6132, 0xce77e25b, 0x578fdfe3, 0x3ac372e6 + } + }; + + #endregion + + private static readonly int Rounds = 16; + + /// + /// Initialize a new blowfish. + /// + /// The key to use. + /// Whether or not a sign confusion should be introduced during key init. This is needed for SE's implementation of blowfish. + public LegacyBlowfish(byte[] key) + { + foreach (var (i, keyFragment) in WrappingUInt32(key, this.p.Length)) + this.p[i] ^= keyFragment; + + uint l = 0, r = 0; + for (int i = 0; i < this.p.Length; i += 2) + (l, r) = (this.p[i], this.p[i + 1]) = Encrypt(l, r); + + for (int i = 0; i < this.s.GetLength(0); i++) + for (int j = 0; j < this.s.GetLength(1); j += 2) + (l, r) = (this.s[i, j], this.s[i, j + 1]) = Encrypt(l, r); + } + + public byte[] Encrypt(byte[] data) + { + var paddedLength = data.Length % 8 == 0 ? data.Length : data.Length + (8 - (data.Length % 8)); + var buffer = new byte[paddedLength]; + Buffer.BlockCopy(data, 0, buffer, 0, data.Length); + + for (int i = 0; i < paddedLength; i += 8) + { + var (l, r) = Encrypt(BitConverter.ToUInt32(buffer, i), BitConverter.ToUInt32(buffer, i + 4)); + CopyUInt32IntoArray(buffer, l, i); + CopyUInt32IntoArray(buffer, r, i + 4); + } + + return buffer; + } + + public void Decrypt(ref byte[] data) + { + for (int i = 0; i < data.Length; i += 8) + { + var (l, r) = Decrypt(BitConverter.ToUInt32(data, i), BitConverter.ToUInt32(data, i + 4)); + CopyUInt32IntoArray(data, l, i); + CopyUInt32IntoArray(data, r, i + 4); + } + } + + private static void CopyUInt32IntoArray(byte[] dest, uint val, int offset) + { + dest[offset] = (byte)(val & 0xFF); + dest[offset + 1] = (byte)((val >> 8) & 0xFF); + dest[offset + 2] = (byte)((val >> 16) & 0xFF); + dest[offset + 3] = (byte)((val >> 24) & 0xFF); + } + + private uint F(uint i) + { + return ((this.s[0, i >> 24] + + this.s[1, (i >> 16) & 0xFF]) + ^ this.s[2, (i >> 8) & 0xFF]) + + this.s[3, i & 0xFF]; + } + + private (uint, uint) Encrypt(uint l, uint r) + { + for (int i = 0; i < Rounds; i += 2) + { + l ^= this.p[i]; + r ^= F(l); + r ^= this.p[i + 1]; + l ^= F(r); + } + + return (r ^ this.p[17], l ^ this.p[16]); + } + + private (uint, uint) Decrypt(uint l, uint r) + { + for (int i = Rounds; i > 0; i -= 2) + { + l ^= this.p[i + 1]; + r ^= F(l); + r ^= this.p[i]; + l ^= F(r); + } + + return (r ^ this.p[0], l ^ this.p[1]); + } + + private static IEnumerable Cycle(IEnumerable source) + { + while (true) + foreach (TSource t in source) + yield return t; + } + + private IEnumerable<(int, uint)> WrappingUInt32(IEnumerable source, int count) + { + var enumerator = Cycle(source).GetEnumerator(); + + for (int i = 0; i < count; i++) + { + var n = 0u; + + for (var j = 0; j < 4 && enumerator.MoveNext(); j++) + { + n = (uint)((n << 8) | (sbyte)enumerator.Current); // NOTE(goat): THIS IS A BUG! SE's implementation wrongly uses signed numbers for this, so we need to as well. + } + + yield return (i, n); + } + } + } +} diff --git a/Dalamud.sln b/Dalamud.sln index b7638754b..a618f1156 100644 --- a/Dalamud.sln +++ b/Dalamud.sln @@ -188,14 +188,14 @@ Global {05AB2F46-268B-4915-806F-DDF813E2D59D}.Release|x64.Build.0 = Release|Any CPU {05AB2F46-268B-4915-806F-DDF813E2D59D}.Release|x86.ActiveCfg = Release|Any CPU {05AB2F46-268B-4915-806F-DDF813E2D59D}.Release|x86.Build.0 = Release|Any CPU - {317A264C-920B-44A1-8A34-F3A6827B0705}.Debug|Any CPU.ActiveCfg = Debug|Win32 - {317A264C-920B-44A1-8A34-F3A6827B0705}.Debug|Any CPU.Build.0 = Debug|Win32 + {317A264C-920B-44A1-8A34-F3A6827B0705}.Debug|Any CPU.ActiveCfg = Debug|x64 + {317A264C-920B-44A1-8A34-F3A6827B0705}.Debug|Any CPU.Build.0 = Debug|x64 {317A264C-920B-44A1-8A34-F3A6827B0705}.Debug|x64.ActiveCfg = Debug|x64 {317A264C-920B-44A1-8A34-F3A6827B0705}.Debug|x64.Build.0 = Debug|x64 {317A264C-920B-44A1-8A34-F3A6827B0705}.Debug|x86.ActiveCfg = Debug|x64 {317A264C-920B-44A1-8A34-F3A6827B0705}.Debug|x86.Build.0 = Debug|x64 - {317A264C-920B-44A1-8A34-F3A6827B0705}.Release|Any CPU.ActiveCfg = Release|Win32 - {317A264C-920B-44A1-8A34-F3A6827B0705}.Release|Any CPU.Build.0 = Release|Win32 + {317A264C-920B-44A1-8A34-F3A6827B0705}.Release|Any CPU.ActiveCfg = Release|x64 + {317A264C-920B-44A1-8A34-F3A6827B0705}.Release|Any CPU.Build.0 = Release|x64 {317A264C-920B-44A1-8A34-F3A6827B0705}.Release|x64.ActiveCfg = Release|x64 {317A264C-920B-44A1-8A34-F3A6827B0705}.Release|x64.Build.0 = Release|x64 {317A264C-920B-44A1-8A34-F3A6827B0705}.Release|x86.ActiveCfg = Release|x64 diff --git a/Dalamud/EntryPoint.cs b/Dalamud/EntryPoint.cs index cfc934f36..c28c3bbaf 100644 --- a/Dalamud/EntryPoint.cs +++ b/Dalamud/EntryPoint.cs @@ -3,7 +3,6 @@ using System.Diagnostics; using System.IO; using System.Net; using System.Runtime.InteropServices; -using System.Text; using System.Threading; using System.Threading.Tasks; @@ -41,11 +40,8 @@ namespace Dalamud /// /// A delegate used from VEH handler on exception which CoreCLR will fast fail by default. /// - /// Path to minidump file created in UTF-16. - /// Path to log file to create in UTF-16. - /// Log text in UTF-16. /// HGLOBAL for message. - public delegate IntPtr VehDelegate(IntPtr dumpPath, IntPtr logPath, IntPtr log); + public delegate IntPtr VehDelegate(); /// /// Initialize Dalamud. @@ -64,43 +60,19 @@ namespace Dalamud } /// - /// Show error message along with stack trace and exit. + /// Returns stack trace. /// - /// Path to minidump file created in UTF-16. - /// Path to log file to create in UTF-16. - /// Log text in UTF-16. - public static IntPtr VehCallback(IntPtr dumpPath, IntPtr logPath, IntPtr log) + /// HGlobal to wchar_t* stack trace c-string. + public static IntPtr VehCallback() { - string stackTrace; try { - stackTrace = Environment.StackTrace; + return Marshal.StringToHGlobalUni(Environment.StackTrace); } catch (Exception e) { - stackTrace = "Fail: " + e.ToString(); + return Marshal.StringToHGlobalUni("Fail: " + e); } - - var msg = "This may be caused by a faulty plugin, a broken TexTools modification, any other third-party tool or simply a bug in the game.\n\n" - + "Please attempt an integrity check in the XIVLauncher settings, and disabling plugins you don't need."; - - try - { - File.WriteAllText( - Marshal.PtrToStringUni(logPath), - "Stack trace:\n" + stackTrace + "\n\n" + Marshal.PtrToStringUni(log)); - } - catch (Exception e) - { - msg += "\n\nAdditionally, failed to write file: " + e.ToString(); - } - - msg = msg.Format( - Marshal.PtrToStringUni(dumpPath), - Marshal.PtrToStringUni(logPath), - stackTrace); - - return Marshal.StringToHGlobalUni(msg); } /// diff --git a/Dalamud/Interface/Internal/DalamudInterface.cs b/Dalamud/Interface/Internal/DalamudInterface.cs index 6e6e21301..fe81caf81 100644 --- a/Dalamud/Interface/Internal/DalamudInterface.cs +++ b/Dalamud/Interface/Internal/DalamudInterface.cs @@ -615,6 +615,16 @@ namespace Dalamud.Interface.Internal Service.Get().Unload(); } + if (ImGui.MenuItem("Restart game")) + { + [DllImport("kernel32.dll")] + [return: MarshalAs(UnmanagedType.Bool)] + static extern void RaiseException(uint dwExceptionCode, uint dwExceptionFlags, uint nNumberOfArguments, IntPtr lpArguments); + + RaiseException(0x12345678, 0, 0, IntPtr.Zero); + Process.GetCurrentProcess().Kill(); + } + if (ImGui.MenuItem("Kill game")) { Process.GetCurrentProcess().Kill(); diff --git a/DalamudCrashHandler/DalamudCrashHandler.cpp b/DalamudCrashHandler/DalamudCrashHandler.cpp index 2fc85e495..390b5085e 100644 --- a/DalamudCrashHandler/DalamudCrashHandler.cpp +++ b/DalamudCrashHandler/DalamudCrashHandler.cpp @@ -1,171 +1,633 @@ +#include +#include #include +#include +#include +#include +#include #include -#include +#include +#include +#include + +#define WIN32_LEAN_AND_MEAN +#define NOMINMAX +#include + +#include +#include #include -#include +#include +#include +#include #include +#pragma comment(lib, "comctl32.lib") +#pragma comment(linker, "/manifestdependency:\"type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'\"") + +#include "resource.h" #include "../Dalamud.Boot/crashhandler_shared.h" -DWORD WINAPI ExitCheckThread(LPVOID lpParam) -{ - while (true) - { - PROCESSENTRY32 entry; - entry.dwSize = sizeof(PROCESSENTRY32); +HANDLE g_hProcess = nullptr; +bool g_bSymbolsAvailable = false; - HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, NULL); +const std::map& get_remote_modules() { + static const auto data = [] { + std::map data; - if (Process32First(snapshot, &entry) == TRUE) - { - bool had_xiv = false; - - while (Process32Next(snapshot, &entry) == TRUE) - { - // Exit if there's another crash handler - // TODO(goat): We should make this more robust and ensure that there is one per PID - if (_wcsicmp(entry.szExeFile, L"DalamudCrashHandler.exe") == 0 && - entry.th32ProcessID != GetCurrentProcessId()) - { - ExitProcess(0); - break; - } - - if (_wcsicmp(entry.szExeFile, L"ffxiv_dx11.exe") == 0) - { - had_xiv = true; - } - } - - if (!had_xiv) - { - ExitProcess(0); + 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; + break; + } else if (needed > std::span(buf).size_bytes()) { + buf.resize(needed / sizeof(HMODULE) + 16); + } else { + buf.resize(needed / sizeof(HMODULE)); break; } } - CloseHandle(snapshot); + for (const auto& hModule : buf) { + IMAGE_DOS_HEADER dosh; + IMAGE_NT_HEADERS64 nth64; + if (size_t read; !ReadProcessMemory(g_hProcess, hModule, &dosh, sizeof dosh, &read) || read != sizeof dosh) { + std::cerr << std::format("Failed to read IMAGE_DOS_HEADER for module at 0x{:x}", reinterpret_cast(hModule)) << std::endl; + continue; + } - Sleep(1000); + if (size_t read; !ReadProcessMemory(g_hProcess, reinterpret_cast(hModule) + dosh.e_lfanew, &nth64, sizeof nth64, &read) || read != sizeof nth64) { + std::cerr << std::format("Failed to read IMAGE_NT_HEADERS64 for module at 0x{:x}", reinterpret_cast(hModule)) << std::endl; + continue; + } + + data[hModule] = nth64.OptionalHeader.SizeOfImage; + } + + return data; + }(); + + return data; +} + +const std::map& get_remote_module_paths() { + static const auto data = [] { + std::map data; + + std::wstring buf(PATHCCH_MAX_CCH, L'\0'); + for (const auto& hModule : get_remote_modules() | std::views::keys) { + buf.resize(PATHCCH_MAX_CCH, L'\0'); + buf.resize(GetModuleFileNameExW(g_hProcess, hModule, &buf[0], PATHCCH_MAX_CCH)); + if (buf.empty()) { + std::cerr << std::format("Failed to get path for module at 0x{:x}: error 0x{:x}", reinterpret_cast(hModule), GetLastError()) << std::endl; + continue; + } + + data[hModule] = buf; + } + + return data; + }(); + return data; +} + +bool get_module_file_and_base(const DWORD64 address, DWORD64& module_base, std::filesystem::path& module_file) { + for (const auto& [hModule, path] : get_remote_module_paths()) { + const auto nAddress = reinterpret_cast(hModule); + if (address < nAddress) + continue; + + const auto nAddressTo = nAddress + get_remote_modules().at(hModule); + if (nAddressTo <= address) + continue; + + module_base = nAddress; + module_file = path; + return true; + } + + return false; +} + +bool is_ffxiv_address(const wchar_t* module_name, const DWORD64 address) { + DWORD64 module_base; + if (std::filesystem::path module_path; get_module_file_and_base(address, module_base, module_path)) + return _wcsicmp(module_path.filename().c_str(), module_name) == 0; + return false; +} + +bool get_sym_from_addr(const DWORD64 address, DWORD64& displacement, std::wstring& symbol_name) { + if (!g_bSymbolsAvailable) + return false; + + union { + char buffer[sizeof(SYMBOL_INFOW) + MAX_SYM_NAME * sizeof(wchar_t)]{}; + SYMBOL_INFOW symbol; + }; + symbol.SizeOfStruct = sizeof(SYMBOL_INFO); + symbol.MaxNameLen = MAX_SYM_NAME; + + if (SymFromAddrW(g_hProcess, address, &displacement, &symbol) && symbol.Name[0]) { + symbol_name = symbol.Name; + return true; + } + return false; +} + +std::wstring to_address_string(const DWORD64 address, const bool try_ptrderef = true) { + DWORD64 module_base; + std::filesystem::path module_path; + bool is_mod_addr = get_module_file_and_base(address, module_base, module_path); + + DWORD64 value = 0; + if (try_ptrderef && address > 0x10000 && address < 0x7FFFFFFE0000) { + ReadProcessMemory(g_hProcess, reinterpret_cast(address), &value, sizeof value, nullptr); + } + + std::wstring addr_str = is_mod_addr ? std::format(L"{}+{:X}", module_path.filename().c_str(), address - module_base) : std::format(L"{:X}", address); + + DWORD64 displacement; + if (std::wstring symbol; get_sym_from_addr(address, displacement, symbol)) + return std::format(L"{}\t({})", addr_str, displacement != 0 ? std::format(L"{}+0x{:X}", symbol, displacement) : std::format(L"{}", symbol)); + return value != 0 ? std::format(L"{} [{}]", addr_str, to_address_string(value, false)) : addr_str; +} + +void print_exception_info(HANDLE hThread, const EXCEPTION_POINTERS& ex, const CONTEXT& ctx, std::wostringstream& log) { + std::vector exRecs; + if (ex.ExceptionRecord) { + size_t rec_index = 0; + size_t read; + exRecs.emplace_back(); + for (auto pRemoteExRec = ex.ExceptionRecord; + 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) + 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{"; + + STACKFRAME64 sf{}; + sf.AddrPC.Offset = ctx.Rip; + sf.AddrPC.Mode = AddrModeFlat; + sf.AddrStack.Offset = ctx.Rsp; + sf.AddrStack.Mode = AddrModeFlat; + sf.AddrFrame.Offset = ctx.Rbp; + sf.AddrFrame.Mode = AddrModeFlat; + int frame_index = 0; + + log << std::format(L"\n [{}]\t{}", frame_index++, to_address_string(sf.AddrPC.Offset, false)); + + const auto appendContextToLog = [&](const CONTEXT& ctxWalk) { + log << std::format(L"\n [{}]\t{}", frame_index++, to_address_string(sf.AddrPC.Offset, false)); + }; + + const auto tryStackWalk = [&] { + __try { + CONTEXT ctxWalk = ctx; + do { + if (!StackWalk64(IMAGE_FILE_MACHINE_AMD64, g_hProcess, hThread, &sf, &ctxWalk, nullptr, &SymFunctionTableAccess64, &SymGetModuleBase64, nullptr)) + break; + + appendContextToLog(ctxWalk); + + } while (sf.AddrReturn.Offset != 0 && sf.AddrPC.Offset != sf.AddrReturn.Offset); + return true; + } __except(EXCEPTION_EXECUTE_HANDLER) { + return false; + } + }; + + if (!tryStackWalk()) + log << L"\n Access violation while walking up the stack."; + + log << L"\n}\n"; +} + +void print_exception_info_extended(const EXCEPTION_POINTERS& ex, const CONTEXT& ctx, std::wostringstream& log) +{ + log << L"\nRegisters\n{"; + + log << std::format(L"\n RAX:\t{}", to_address_string(ctx.Rax)); + log << std::format(L"\n RBX:\t{}", to_address_string(ctx.Rbx)); + log << std::format(L"\n RCX:\t{}", to_address_string(ctx.Rcx)); + log << std::format(L"\n RDX:\t{}", to_address_string(ctx.Rdx)); + log << std::format(L"\n R8:\t{}", to_address_string(ctx.R8)); + log << std::format(L"\n R9:\t{}", to_address_string(ctx.R9)); + log << std::format(L"\n R10:\t{}", to_address_string(ctx.R10)); + log << std::format(L"\n R11:\t{}", to_address_string(ctx.R11)); + log << std::format(L"\n R12:\t{}", to_address_string(ctx.R12)); + log << std::format(L"\n R13:\t{}", to_address_string(ctx.R13)); + log << std::format(L"\n R14:\t{}", to_address_string(ctx.R14)); + log << std::format(L"\n R15:\t{}", to_address_string(ctx.R15)); + + log << std::format(L"\n RSI:\t{}", to_address_string(ctx.Rsi)); + log << std::format(L"\n RDI:\t{}", to_address_string(ctx.Rdi)); + log << std::format(L"\n RBP:\t{}", to_address_string(ctx.Rbp)); + log << std::format(L"\n RSP:\t{}", to_address_string(ctx.Rsp)); + log << std::format(L"\n RIP:\t{}", to_address_string(ctx.Rip)); + + log << L"\n}" << std::endl; + + if(0x10000 < ctx.Rsp && ctx.Rsp < 0x7FFFFFFE0000) + { + log << L"\nStack\n{"; + + DWORD64 stackData[16]; + size_t read; + ReadProcessMemory(g_hProcess, reinterpret_cast(ctx.Rsp), stackData, sizeof stackData, &read); + for(DWORD64 i = 0; i < 16 && i * sizeof(size_t) < read; i++) + log << std::format(L"\n [RSP+{:X}]\t{}", i * 8, to_address_string(stackData[i])); + + log << L"\n}\n"; + } + + log << L"\nModules\n{"; + + for (const auto& [hModule, path] : get_remote_module_paths()) + log << std::format(L"\n {:08X}\t{}", reinterpret_cast(hModule), path.wstring()); + + log << L"\n}\n"; +} + +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); + } else { + res.push_back(L'"'); + for (auto it = arg.begin(); ; ++it) { + size_t bsCount = 0; + + while (it != arg.end() && *it == L'\\') { + ++it; + ++bsCount; + } + + if (it == arg.end()) { + res.append(bsCount * 2, L'\\'); + break; + } else if (*it == L'"') { + res.append(bsCount * 2 + 1, L'\\'); + res.push_back(*it); + } else { + res.append(bsCount, L'\\'); + res.push_back(*it); + } + } + + res.push_back(L'"'); + } + return res; +} + +enum { + IdRadioRestartNormal = 101, + IdRadioRestartWithout3pPlugins, + IdRadioRestartWithoutPlugins, + IdRadioRestartWithoutDalamud, + + IdButtonRestart = 201, + IdButtonHelp = IDHELP, + IdButtonExit = IDCANCEL, +}; + +void restart_game_using_injector(int nRadioButton, const std::vector& launcherArgs) +{ + std::wstring pathStr(PATHCCH_MAX_CCH, L'\0'); + pathStr.resize(GetModuleFileNameExW(GetCurrentProcess(), GetModuleHandleW(nullptr), &pathStr[0], PATHCCH_MAX_CCH)); + + std::vector args; + args.emplace_back((std::filesystem::path(pathStr).parent_path() / L"Dalamud.Injector.exe").wstring()); + args.emplace_back(L"launch"); + switch (nRadioButton) { + case IdRadioRestartWithout3pPlugins: + args.emplace_back(L"--no-3rd-plugin"); + break; + case IdRadioRestartWithoutPlugins: + args.emplace_back(L"--no-plugin"); + break; + case IdRadioRestartWithoutDalamud: + args.emplace_back(L"--without-dalamud"); + break; + } + args.emplace_back(L"--"); + args.insert(args.end(), launcherArgs.begin(), launcherArgs.end()); + + std::wstring argstr; + for (const auto& arg : args) { + argstr.append(escape_shell_arg(arg)); + argstr.push_back(L' '); + } + argstr.pop_back(); + + STARTUPINFOW si{}; + si.cb = sizeof si; + si.dwFlags = STARTF_USESHOWWINDOW; +#ifndef NDEBUG + si.wShowWindow = SW_HIDE; +#else + si.wShowWindow = SW_SHOW; +#endif + PROCESS_INFORMATION pi{}; + if (CreateProcessW(args[0].c_str(), &argstr[0], nullptr, nullptr, FALSE, 0, nullptr, nullptr, &si, &pi)) { + CloseHandle(pi.hProcess); + CloseHandle(pi.hThread); + } else { + MessageBoxW(nullptr, std::format(L"Failed to restart: 0x{:x}", GetLastError()).c_str(), L"Dalamud Boot", MB_ICONERROR | MB_OK); } } -int main() -{ - CreateThread( - NULL, // default security attributes - 0, // use default stack size - ExitCheckThread, // thread function name - NULL, // argument to thread function - 0, // use default creation flags - NULL); // returns the thread identifier +int main() { + enum crash_handler_special_exit_codes { + InvalidParameter = -101, + ProcessExitedUnknownExitCode = -102, + }; - auto file_mapping = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, sizeof(exception_info), SHARED_INFO_FILE_NAME); - if (!file_mapping) { - std::cout << "Could not map info share file.\n"; - return -2; + HANDLE hPipeRead = nullptr; + std::filesystem::path assetDir, logDir; + std::optional> launcherArgs; + + std::vector args; + if (int argc = 0; const auto argv = CommandLineToArgvW(GetCommandLineW(), &argc)) { + for (auto i = 0; i < argc; i++) + args.emplace_back(argv[i]); + LocalFree(argv); + } + for (size_t i = 1; i < args.size(); i++) { + const auto arg = std::wstring_view(args[i]); + if (launcherArgs) { + launcherArgs->emplace_back(arg); + } else if (constexpr wchar_t pwszArgPrefix[] = L"--process-handle="; arg.starts_with(pwszArgPrefix)) { + g_hProcess = reinterpret_cast(std::wcstoull(&arg[ARRAYSIZE(pwszArgPrefix) - 1], nullptr, 0)); + } else if (constexpr wchar_t pwszArgPrefix[] = L"--exception-info-pipe-read-handle="; arg.starts_with(pwszArgPrefix)) { + hPipeRead = reinterpret_cast(std::wcstoull(&arg[ARRAYSIZE(pwszArgPrefix) - 1], nullptr, 0)); + } else if (constexpr wchar_t pwszArgPrefix[] = L"--asset-directory="; arg.starts_with(pwszArgPrefix)) { + assetDir = arg.substr(ARRAYSIZE(pwszArgPrefix) - 1); + } else if (constexpr wchar_t pwszArgPrefix[] = L"--log-directory="; arg.starts_with(pwszArgPrefix)) { + logDir = arg.substr(ARRAYSIZE(pwszArgPrefix) - 1); + } else if (arg == L"--") { + launcherArgs.emplace(); + } else { + std::wcerr << L"Invalid argument: " << arg << std::endl; + return InvalidParameter; + } } - auto file_ptr = MapViewOfFile(file_mapping, FILE_MAP_READ, 0, 0, sizeof(exception_info)); - if (!file_ptr) { - std::cout << "Could not map view of info share file.\n"; - return -3; + if (g_hProcess == nullptr) { + std::wcerr << L"Target process not specified" << std::endl; + return InvalidParameter; } - std::cout << "Waiting for crash...\n"; - - auto crash_event = CreateEvent( - NULL, // default security attributes - TRUE, // manual-reset event - FALSE, // initial state is nonsignaled - CRASHDUMP_EVENT_NAME // object name - ); - - if (!crash_event) - { - std::cout << "Couldn't acquire event handle\n"; - return -1; + if (hPipeRead == nullptr) { + std::wcerr << L"Read pipe handle not specified" << std::endl; + return InvalidParameter; } - auto wait_result = WaitForSingleObject(crash_event, INFINITE); - std::cout << "Crash triggered, writing dump!\n"; - - auto info_share = (exception_info*)file_ptr; - - if (!info_share->ExceptionPointers) - { - std::cout << "info_share->ExceptionPointers was nullptr\n"; - return -4; + const auto dwProcessId = GetProcessId(g_hProcess); + if (!dwProcessId){ + std::wcerr << L"Target process not specified" << std::endl; + return InvalidParameter; } - MINIDUMP_EXCEPTION_INFORMATION mdmp_info; - mdmp_info.ClientPointers = true; - mdmp_info.ExceptionPointers = (PEXCEPTION_POINTERS)info_share->ExceptionPointers; - mdmp_info.ThreadId = info_share->ThreadId; + while (true) { + std::cout << "Waiting for crash...\n"; + exception_info exinfo; + if (DWORD exsize{}; !ReadFile(hPipeRead, &exinfo, static_cast(sizeof exinfo), &exsize, nullptr) || exsize != sizeof exinfo) { + if (WaitForSingleObject(g_hProcess, 0) == WAIT_OBJECT_0) { + auto excode = static_cast(ProcessExitedUnknownExitCode); + if (!GetExitCodeProcess(g_hProcess, &excode)) + std::cerr << std::format("Process exited, but failed to read exit code; error: 0x{:x}", GetLastError()) << std::endl; + else + std::cout << std::format("Process exited with exit code {0} (0x{0:x})", excode) << std::endl; + break; + } - std::cout << "Dump for " << info_share->ProcessId << std::endl; - HANDLE file = CreateFileW(info_share->DumpPath, GENERIC_WRITE, FILE_SHARE_WRITE, nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr); - if (!file) - { - auto hr = GetLastError(); - std::cout << "Failed to open dump file: " << std::hex << hr << std::endl; - return -6; + const auto err = GetLastError(); + std::cerr << std::format("Failed to read exception information; error: 0x{:x}", err) << std::endl; + std::cerr << "Terminating target process." << std::endl; + TerminateProcess(g_hProcess, -1); + break; + } + + if (exinfo.ExceptionRecord.ExceptionCode == 0x12345678) { + std::cout << "Restart requested" << std::endl; + TerminateProcess(g_hProcess, 0); + restart_game_using_injector(IdRadioRestartNormal, *launcherArgs); + break; + } + + std::cout << "Crash triggered" << std::endl; + + if (g_bSymbolsAvailable) { + SymRefreshModuleList(g_hProcess); + } else if (g_bSymbolsAvailable = SymInitialize(g_hProcess, nullptr, true); g_bSymbolsAvailable) { + if (!assetDir.empty()) { + if (!SymSetSearchPathW(g_hProcess, std::format(L".;{}", (assetDir / "UIRes" / "pdb").wstring()).c_str())) + std::wcerr << std::format(L"SymSetSearchPathW error: 0x{:x}", GetLastError()) << std::endl; + } + } else { + std::wcerr << std::format(L"SymInitialize error: 0x{:x}", GetLastError()) << std::endl; + } + + std::wstring stackTrace(exinfo.dwStackTraceLength, L'\0'); + if (exinfo.dwStackTraceLength) { + if (DWORD read; !ReadFile(hPipeRead, &stackTrace[0], 2 * exinfo.dwStackTraceLength, &read, nullptr)) { + std::cout << std::format("Failed to read supplied stack trace: error 0x{:x}", GetLastError()) << std::endl; + } + } + + SYSTEMTIME st; + GetLocalTime(&st); + const auto dumpPath = logDir.empty() ? std::filesystem::path() : logDir / std::format("dalamud_appcrash_{:04}{:02}{:02}_{:02}{:02}{:02}_{:03}_{}.dmp", st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond, st.wMilliseconds, dwProcessId); + const auto logPath = logDir.empty() ? std::filesystem::path() : logDir / std::format("dalamud_appcrash_{:04}{:02}{:02}_{:02}{:02}{:02}_{:03}_{}.log", st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond, st.wMilliseconds, dwProcessId); + std::wstring dumpError; + if (dumpPath.empty()) { + std::cout << "Skipping dump path, as log directory has not been specified" << std::endl; + } else { + MINIDUMP_EXCEPTION_INFORMATION mdmp_info{}; + mdmp_info.ThreadId = GetThreadId(exinfo.hThreadHandle); + mdmp_info.ExceptionPointers = exinfo.pExceptionPointers; + mdmp_info.ClientPointers = TRUE; + + do { + const auto hDumpFile = CreateFileW(dumpPath.c_str(), GENERIC_READ | GENERIC_WRITE, 0, nullptr, CREATE_ALWAYS, 0, nullptr); + if (hDumpFile == INVALID_HANDLE_VALUE) { + std::wcerr << (dumpError = std::format(L"CreateFileW({}, GENERIC_READ | GENERIC_WRITE, 0, nullptr, CREATE_ALWAYS, 0, nullptr) error: 0x{:x}", dumpPath.wstring(), GetLastError())) << std::endl; + break; + } + + std::unique_ptr, decltype(&CloseHandle)> hDumpFilePtr(hDumpFile, &CloseHandle); + if (!MiniDumpWriteDump(g_hProcess, dwProcessId, hDumpFile, static_cast(MiniDumpWithDataSegs | MiniDumpWithModuleHeaders), &mdmp_info, nullptr, nullptr)) { + std::wcerr << (dumpError = std::format(L"MiniDumpWriteDump(0x{:x}, {}, 0x{:x}({}), MiniDumpWithFullMemory, ..., nullptr, nullptr) error: 0x{:x}", reinterpret_cast(g_hProcess), dwProcessId, reinterpret_cast(hDumpFile), dumpPath.wstring(), GetLastError())) << std::endl; + break; + } + + std::wcout << "Dump written to path: " << dumpPath << std::endl; + } while (false); + } + + std::wostringstream log; + 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 (dumpPath.empty()) + log << L"Dump skipped" << std::endl; + else if (dumpError.empty()) + log << std::format(L"Dump at: {}", dumpPath.wstring()) << std::endl; + else + log << std::format(L"Dump error: {}", dumpError) << std::endl; + log << L"Time: " << std::chrono::zoned_time{ std::chrono::current_zone(), std::chrono::system_clock::now() } << std::endl; + log << L"\n" << stackTrace << std::endl; + + SymRefreshModuleList(GetCurrentProcess()); + print_exception_info(exinfo.hThreadHandle, exinfo.ExceptionPointers, exinfo.ContextRecord, log); + auto window_log_str = log.str(); + print_exception_info_extended(exinfo.ExceptionPointers, exinfo.ContextRecord, log); + + std::wofstream(logPath) << log.str(); + + std::thread submitThread; + if (!getenv("DALAMUD_NO_METRIC")) { + auto url = std::format(L"/Dalamud/Metric/ReportCrash/?lt={}&code={:x}", exinfo.nLifetime, exinfo.ExceptionRecord.ExceptionCode); + + submitThread = std::thread([url = std::move(url)] { + const auto hInternet = WinHttpOpen(L"DALAMUDCRASHHANDLER", WINHTTP_ACCESS_TYPE_AUTOMATIC_PROXY, nullptr, nullptr, WINHTTP_FLAG_SECURE_DEFAULTS); + const auto hConnect = !hInternet ? nullptr : WinHttpConnect(hInternet, L"kamori.goats.dev", INTERNET_DEFAULT_HTTPS_PORT, 0); + const auto hRequest = !hConnect ? nullptr : WinHttpOpenRequest(hConnect, L"GET", url.c_str(), nullptr, nullptr, nullptr, 0); + const auto bSent = !hRequest ? false : WinHttpSendRequest(hRequest, + WINHTTP_NO_ADDITIONAL_HEADERS, + 0, WINHTTP_NO_REQUEST_DATA, 0, + 0, 0); + + if (!bSent) + std::cerr << std::format("Failed to send metric: 0x{:x}", GetLastError()) << std::endl; + + if (hRequest) WinHttpCloseHandle(hRequest); + if (hConnect) WinHttpCloseHandle(hConnect); + if (hInternet) WinHttpCloseHandle(hInternet); + }); + } + + TASKDIALOGCONFIG config = { 0 }; + + const TASKDIALOG_BUTTON radios[]{ + {IdRadioRestartNormal, L"Restart"}, + {IdRadioRestartWithout3pPlugins, L"Restart without 3rd party plugins"}, + {IdRadioRestartWithoutPlugins, L"Restart without any plugin"}, + {IdRadioRestartWithoutDalamud, L"Restart without Dalamud"}, + }; + + const TASKDIALOG_BUTTON buttons[]{ + {IdButtonRestart, L"Restart\nRestart the game, optionally without plugins or Dalamud."}, + {IdButtonExit, L"Exit\nExit the game."}, + }; + + config.cbSize = sizeof(config); + config.hInstance = GetModuleHandleW(nullptr); + config.dwFlags = TDF_ENABLE_HYPERLINKS | TDF_CAN_BE_MINIMIZED | TDF_ALLOW_DIALOG_CANCELLATION | TDF_USE_COMMAND_LINKS; + config.pszMainIcon = MAKEINTRESOURCE(IDI_ICON1); + config.pszMainInstruction = L"An error occurred"; + config.pszContent = (L"" + R"aa(This may be caused by a faulty plugin, a broken TexTools modification, any other third-party tool, or simply a bug in the game.)aa" "\n" + "\n" + R"aa(Try running integrity check in the XIVLauncher settings, and disabling plugins you don't need.)aa" + ); + config.pButtons = buttons; + config.cButtons = ARRAYSIZE(buttons); + config.nDefaultButton = IdButtonRestart; + config.pszExpandedInformation = window_log_str.c_str(); + config.pszWindowTitle = L"Dalamud Error"; + config.pRadioButtons = radios; + config.cRadioButtons = ARRAYSIZE(radios); + config.nDefaultRadioButton = IdRadioRestartNormal; + config.cxWidth = 300; + config.pszFooter = (L"" + R"aa(Help | Open log directory | Open log file | Attempt to resume)aa" + ); + + // Can't do this, xiv stops pumping messages here + //config.hwndParent = FindWindowA("FFXIVGAME", NULL); + + auto attemptResume = false; + const auto callback = [&](HWND hwnd, UINT uNotification, WPARAM wParam, LPARAM lParam) -> HRESULT { + switch (uNotification) { + case TDN_CREATED: + { + SetWindowPos(hwnd, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW); + return S_OK; + } + case TDN_HYPERLINK_CLICKED: + { + const auto link = std::wstring_view(reinterpret_cast(lParam)); + if (link == L"help") { + ShellExecuteW(hwnd, nullptr, L"https://goatcorp.github.io/faq?utm_source=vectored", nullptr, nullptr, SW_SHOW); + } else if (link == L"logdir") { + ShellExecuteW(hwnd, nullptr, L"explorer.exe", escape_shell_arg(std::format(L"/select,{}", logPath.wstring())).c_str(), nullptr, SW_SHOW); + } else if (link == L"logfile") { + ShellExecuteW(hwnd, nullptr, logPath.c_str(), nullptr, nullptr, SW_SHOW); + } else if (link == L"resume") { + attemptResume = true; + DestroyWindow(hwnd); + } + return S_OK; + } + } + + return S_OK; + }; + + config.pfCallback = [](HWND hwnd, UINT uNotification, WPARAM wParam, LPARAM lParam, LONG_PTR dwRefData) { + return (*reinterpret_cast(dwRefData))(hwnd, uNotification, wParam, lParam); + }; + config.lpCallbackData = reinterpret_cast(&callback); + + if (submitThread.joinable()) { + submitThread.join(); + submitThread = {}; + } + + int nButtonPressed = 0, nRadioButton = 0; + if (FAILED(TaskDialogIndirect(&config, &nButtonPressed, &nRadioButton, nullptr))) { + ResumeThread(exinfo.hThreadHandle); + } else { + switch (nButtonPressed) { + case IdButtonRestart: + { + TerminateProcess(g_hProcess, exinfo.ExceptionRecord.ExceptionCode); + restart_game_using_injector(nRadioButton, *launcherArgs); + break; + } + default: + if (attemptResume) + ResumeThread(exinfo.hThreadHandle); + else + TerminateProcess(g_hProcess, exinfo.ExceptionRecord.ExceptionCode); + } + } } - auto process = OpenProcess(PROCESS_VM_READ | PROCESS_VM_WRITE | PROCESS_QUERY_INFORMATION, FALSE, info_share->ProcessId); - if (!process) - { - auto hr = GetLastError(); - std::cout << "Failed to open " << info_share->ProcessId << ": " << std::hex << hr << std::endl; - return -6; - } - - auto success = MiniDumpWriteDump(process, info_share->ProcessId, file, MiniDumpWithFullMemory, &mdmp_info, NULL, NULL); - if (!success) - { - auto hr = GetLastError(); - std::cout << "Failed: " << std::hex << hr << std::endl; - } - - // TODO(goat): Technically, we should have another event or a semaphore to block xiv while dumping... - - CloseHandle(file); - CloseHandle(process); - CloseHandle(file_mapping); - - if (getenv("DALAMUD_NO_METRIC")) - return 0; - - HINTERNET internet = WinHttpOpen(L"DALAMUDCRASHHANDLER", WINHTTP_ACCESS_TYPE_AUTOMATIC_PROXY, NULL, NULL, WINHTTP_FLAG_SECURE_DEFAULTS); - HINTERNET connect = NULL, request = NULL; - if (internet) - { - connect = WinHttpConnect(internet, L"kamori.goats.dev", INTERNET_DEFAULT_HTTPS_PORT, 0); - } - - if (connect) - { - std::wstringstream url{ L"/Dalamud/Metric/ReportCrash/" }; - url << "?lt=" << info_share->Lifetime << "&code=" << std::hex << info_share->ExceptionCode; - - request = WinHttpOpenRequest(internet, L"GET", url.str().c_str(), NULL, NULL, NULL, 0); - } - - if (request) - { - bool sent = WinHttpSendRequest(request, - WINHTTP_NO_ADDITIONAL_HEADERS, - 0, WINHTTP_NO_REQUEST_DATA, 0, - 0, 0); - - if (!sent) - std::cout << "Failed to send metric: " << std::hex << GetLastError() << std::endl; - } - - if (request) WinHttpCloseHandle(request); - if (connect) WinHttpCloseHandle(connect); - if (internet) WinHttpCloseHandle(internet); - return 0; } diff --git a/DalamudCrashHandler/DalamudCrashHandler.vcxproj b/DalamudCrashHandler/DalamudCrashHandler.vcxproj index eb3e186c3..8aa951a6d 100644 --- a/DalamudCrashHandler/DalamudCrashHandler.vcxproj +++ b/DalamudCrashHandler/DalamudCrashHandler.vcxproj @@ -1,18 +1,16 @@ + + {317a264c-920b-44a1-8a34-f3a6827b0705} + DalamudCrashHandler + Debug + x64 + - - Debug - Win32 - Debug x64 - - Release - Win32 - Release x64 @@ -21,118 +19,60 @@ 16.0 Win32Proj - {317a264c-920b-44a1-8a34-f3a6827b0705} - DalamudCrashHandler 10.0 - ..\bin\$(Configuration)\ - + Application true v143 + false Unicode - - - Application - true - v143 - Unicode - - - Application - false - v143 - true - Unicode - - - Application - false - v143 - true - Unicode + ..\bin\$(Configuration)\ + obj\$(Configuration)\ - - - - - - - - - - - - - - - - - - + + true + + + false + + Level3 true - _CRT_SECURE_NO_WARNINGS;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + _CRT_SECURE_NO_WARNINGS;_CONSOLE;%(PreprocessorDefinitions) true + stdcpplatest + stdc17 - Windows + Console true Winhttp.lib;Dbghelp.lib;$(CoreLibraryDependencies);%(AdditionalDependencies) - mainCRTStartup - + - Level3 - true - _CRT_SECURE_NO_WARNINGS;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) - true + true + false + _DEBUG;%(PreprocessorDefinitions) - Windows - true - Winhttp.lib;Dbghelp.lib;$(CoreLibraryDependencies);%(AdditionalDependencies) - mainCRTStartup + false + false - + - Level3 true true - true - _CRT_SECURE_NO_WARNINGS;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) - true + NDEBUG;%(PreprocessorDefinitions) - Windows true true - true - Winhttp.lib;Dbghelp.lib;$(CoreLibraryDependencies);%(AdditionalDependencies) - mainCRTStartup - - - - - Level3 - true - true - true - _CRT_SECURE_NO_WARNINGS;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) - true - - - Windows - true - true - true - Winhttp.lib;Dbghelp.lib;$(CoreLibraryDependencies);%(AdditionalDependencies) - mainCRTStartup @@ -151,4 +91,4 @@ - + \ No newline at end of file From edbe21d2b838faf0eed064ace87e0822e9e689ae Mon Sep 17 00:00:00 2001 From: Philpax Date: Wed, 27 Jul 2022 00:28:55 +0200 Subject: [PATCH 51/58] Kal literally put a gun to my head and made me do this (#938) --- DalamudCrashHandler/DalamudCrashHandler.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DalamudCrashHandler/DalamudCrashHandler.cpp b/DalamudCrashHandler/DalamudCrashHandler.cpp index 390b5085e..f0be81a73 100644 --- a/DalamudCrashHandler/DalamudCrashHandler.cpp +++ b/DalamudCrashHandler/DalamudCrashHandler.cpp @@ -536,7 +536,7 @@ int main() { const TASKDIALOG_BUTTON radios[]{ {IdRadioRestartNormal, L"Restart"}, {IdRadioRestartWithout3pPlugins, L"Restart without 3rd party plugins"}, - {IdRadioRestartWithoutPlugins, L"Restart without any plugin"}, + {IdRadioRestartWithoutPlugins, L"Restart without any plugins"}, {IdRadioRestartWithoutDalamud, L"Restart without Dalamud"}, }; From a3a9f9e624b0423983a582c42ff2e8b2a067ee4f Mon Sep 17 00:00:00 2001 From: Aireil <33433913+Aireil@users.noreply.github.com> Date: Mon, 1 Aug 2022 16:24:23 +0200 Subject: [PATCH 52/58] fix: use effective version in changelog (#947) --- .../Internal/Windows/PluginInstaller/PluginChangelogEntry.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/Dalamud/Interface/Internal/Windows/PluginInstaller/PluginChangelogEntry.cs b/Dalamud/Interface/Internal/Windows/PluginInstaller/PluginChangelogEntry.cs index 0326081f5..1133090e0 100644 --- a/Dalamud/Interface/Internal/Windows/PluginInstaller/PluginChangelogEntry.cs +++ b/Dalamud/Interface/Internal/Windows/PluginInstaller/PluginChangelogEntry.cs @@ -21,10 +21,7 @@ namespace Dalamud.Interface.Internal.Windows.PluginInstaller if (plugin.Manifest.Changelog.IsNullOrEmpty()) throw new ArgumentException("Manifest has no changelog."); - var version = plugin.AssemblyName?.Version; - version ??= plugin.Manifest.Testing - ? plugin.Manifest.TestingAssemblyVersion - : plugin.Manifest.AssemblyVersion; + var version = plugin.Manifest.EffectiveVersion; this.Version = version!.ToString(); } From b96c212f76932e3a9c7dfed20e1640b363020911 Mon Sep 17 00:00:00 2001 From: Aireil <33433913+Aireil@users.noreply.github.com> Date: Wed, 3 Aug 2022 19:05:12 +0200 Subject: [PATCH 53/58] Installer spacing + reduce search bar length (#953) --- .../PluginInstaller/PluginInstallerWindow.cs | 32 +++++++++++-------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/Dalamud/Interface/Internal/Windows/PluginInstaller/PluginInstallerWindow.cs b/Dalamud/Interface/Internal/Windows/PluginInstaller/PluginInstallerWindow.cs index 3ec49aa66..0a5308054 100644 --- a/Dalamud/Interface/Internal/Windows/PluginInstaller/PluginInstallerWindow.cs +++ b/Dalamud/Interface/Internal/Windows/PluginInstaller/PluginInstallerWindow.cs @@ -235,16 +235,19 @@ namespace Dalamud.Interface.Internal.Windows.PluginInstaller ImGui.SetCursorPos(Vector2.Zero); var windowSize = ImGui.GetWindowSize(); + var titleHeight = ImGui.GetFontSize() + (ImGui.GetStyle().FramePadding.Y * 2); - ImGui.PushStyleVar(ImGuiStyleVar.WindowPadding, Vector2.Zero); - ImGui.PushStyleVar(ImGuiStyleVar.FramePadding, Vector2.Zero); - ImGui.PushStyleVar(ImGuiStyleVar.CellPadding, Vector2.Zero);; - ImGui.PushStyleVar(ImGuiStyleVar.ChildBorderSize, 0); - ImGui.PushStyleVar(ImGuiStyleVar.ChildRounding, 0); - - ImGui.SetNextWindowBgAlpha(0.8f); if (ImGui.BeginChild("###installerLoadingFrame", new Vector2(-1, -1), false)) { + ImGui.GetWindowDrawList().PushClipRectFullScreen(); + ImGui.GetWindowDrawList().AddRectFilled( + ImGui.GetWindowPos() + new Vector2(0, titleHeight), + ImGui.GetWindowPos() + windowSize, + 0xCC000000, + ImGui.GetStyle().WindowRounding, + ImDrawFlags.RoundCornersBottom); + ImGui.PopClipRect(); + ImGui.SetCursorPosY(windowSize.Y / 2); switch (this.loadingIndicatorKind) @@ -303,6 +306,7 @@ namespace Dalamud.Interface.Internal.Windows.PluginInstaller if (currentProgress != total) { + ImGui.SetCursorPosX(windowSize.X / 3); ImGui.ProgressBar(currentProgress / (float)total, new Vector2(windowSize.X / 3, 50)); } } @@ -314,8 +318,6 @@ namespace Dalamud.Interface.Internal.Windows.PluginInstaller ImGui.EndChild(); } - - ImGui.PopStyleVar(5); } private void DrawHeader() @@ -325,8 +327,8 @@ namespace Dalamud.Interface.Internal.Windows.PluginInstaller ImGui.SetCursorPosY(ImGui.GetCursorPosY() - (5 * ImGuiHelpers.GlobalScale)); - var searchInputWidth = 240 * ImGuiHelpers.GlobalScale; - var searchClearButtonWidth = 40 * ImGuiHelpers.GlobalScale; + var searchInputWidth = 180 * ImGuiHelpers.GlobalScale; + var searchClearButtonWidth = 25 * ImGuiHelpers.GlobalScale; var sortByText = Locs.SortBy_Label; var sortByTextWidth = ImGui.CalcTextSize(sortByText).X; @@ -349,9 +351,10 @@ namespace Dalamud.Interface.Internal.Windows.PluginInstaller ImGui.SameLine(); // Shift down a little to align with the middle of the header text - ImGui.SetCursorPosY(ImGui.GetCursorPosY() + (headerTextSize.Y / 4) - 2); + var downShift = ImGui.GetCursorPosY() + (headerTextSize.Y / 4) - 2; + ImGui.SetCursorPosY(downShift); - ImGui.SetCursorPosX(windowSize.X - sortSelectWidth - style.ItemSpacing.X - searchInputWidth - searchClearButtonWidth); + ImGui.SetCursorPosX(windowSize.X - sortSelectWidth - (style.ItemSpacing.X * 2) - searchInputWidth - searchClearButtonWidth); var searchTextChanged = false; ImGui.SetNextItemWidth(searchInputWidth); @@ -362,6 +365,7 @@ namespace Dalamud.Interface.Internal.Windows.PluginInstaller 100); ImGui.SameLine(); + ImGui.SetCursorPosY(downShift); ImGui.SetNextItemWidth(searchClearButtonWidth); if (ImGuiComponents.IconButton(FontAwesomeIcon.Times)) @@ -374,7 +378,7 @@ namespace Dalamud.Interface.Internal.Windows.PluginInstaller this.UpdateCategoriesOnSearchChange(); ImGui.SameLine(); - ImGui.SetCursorPosX(windowSize.X - sortSelectWidth); + ImGui.SetCursorPosY(downShift); ImGui.SetNextItemWidth(selectableWidth); if (ImGui.BeginCombo(sortByText, this.filterText, ImGuiComboFlags.NoArrowButton)) { From 3e58c29612d21c0760acba1d4a894823437897f2 Mon Sep 17 00:00:00 2001 From: Aireil <33433913+Aireil@users.noreply.github.com> Date: Wed, 3 Aug 2022 19:26:27 +0200 Subject: [PATCH 54/58] Add plugin changelogs button (#948) Co-authored-by: goat --- Dalamud/Game/ChatHandlers.cs | 2 +- .../Interface/Internal/DalamudInterface.cs | 5 +++ .../PluginInstaller/PluginInstallerWindow.cs | 12 ++++++- Dalamud/Plugin/Internal/PluginManager.cs | 31 ++++++++++++++++--- .../Internal/Types/PluginUpdateStatus.cs | 5 +++ 5 files changed, 49 insertions(+), 6 deletions(-) diff --git a/Dalamud/Game/ChatHandlers.cs b/Dalamud/Game/ChatHandlers.cs index 91467bfc9..3e489af82 100644 --- a/Dalamud/Game/ChatHandlers.cs +++ b/Dalamud/Game/ChatHandlers.cs @@ -305,7 +305,7 @@ namespace Dalamud.Game { if (this.configuration.AutoUpdatePlugins) { - PluginManager.PrintUpdatedPlugins(updatedPlugins, Loc.Localize("DalamudPluginAutoUpdate", "Auto-update:")); + Service.Get().PrintUpdatedPlugins(updatedPlugins, Loc.Localize("DalamudPluginAutoUpdate", "Auto-update:")); notifications.AddNotification(Loc.Localize("NotificationUpdatedPlugins", "{0} of your plugins were updated.").Format(updatedPlugins.Count), Loc.Localize("NotificationAutoUpdate", "Auto-Update"), NotificationType.Info); } else diff --git a/Dalamud/Interface/Internal/DalamudInterface.cs b/Dalamud/Interface/Internal/DalamudInterface.cs index fe81caf81..b985522a9 100644 --- a/Dalamud/Interface/Internal/DalamudInterface.cs +++ b/Dalamud/Interface/Internal/DalamudInterface.cs @@ -250,6 +250,11 @@ namespace Dalamud.Interface.Internal /// public void OpenPluginInstaller() => this.pluginWindow.IsOpen = true; + /// + /// Opens the on the plugin changelogs. + /// + public void OpenPluginInstallerPluginChangelogs() => this.pluginWindow.OpenPluginChangelogs(); + /// /// Opens the . /// diff --git a/Dalamud/Interface/Internal/Windows/PluginInstaller/PluginInstallerWindow.cs b/Dalamud/Interface/Internal/Windows/PluginInstaller/PluginInstallerWindow.cs index 0a5308054..c7d8a2790 100644 --- a/Dalamud/Interface/Internal/Windows/PluginInstaller/PluginInstallerWindow.cs +++ b/Dalamud/Interface/Internal/Windows/PluginInstaller/PluginInstallerWindow.cs @@ -217,6 +217,16 @@ namespace Dalamud.Interface.Internal.Windows.PluginInstaller this.imageCache.ClearIconCache(); } + /// + /// Open the window on the plugin changelogs. + /// + public void OpenPluginChangelogs() + { + this.categoryManager.CurrentGroupIdx = 3; + this.categoryManager.CurrentCategoryIdx = 2; + this.IsOpen = true; + } + private void DrawProgressOverlay() { var pluginManager = Service.Get(); @@ -501,7 +511,7 @@ namespace Dalamud.Interface.Internal.Windows.PluginInstaller if (this.updatePluginCount > 0) { - PluginManager.PrintUpdatedPlugins(this.updatedPlugins, Locs.PluginUpdateHeader_Chatbox); + Service.Get().PrintUpdatedPlugins(this.updatedPlugins, Locs.PluginUpdateHeader_Chatbox); notifications.AddNotification(Locs.Notifications_UpdatesInstalled(this.updatePluginCount), Locs.Notifications_UpdatesInstalledTitle, NotificationType.Success); var installedGroupIdx = this.categoryManager.GroupList.TakeWhile( diff --git a/Dalamud/Plugin/Internal/PluginManager.cs b/Dalamud/Plugin/Internal/PluginManager.cs index 003636cff..1889cbd50 100644 --- a/Dalamud/Plugin/Internal/PluginManager.cs +++ b/Dalamud/Plugin/Internal/PluginManager.cs @@ -17,6 +17,8 @@ using Dalamud.Game; using Dalamud.Game.Gui; using Dalamud.Game.Gui.Dtr; using Dalamud.Game.Text; +using Dalamud.Game.Text.SeStringHandling; +using Dalamud.Game.Text.SeStringHandling.Payloads; using Dalamud.Interface.Internal; using Dalamud.Logging.Internal; using Dalamud.Plugin.Internal.Exceptions; @@ -50,6 +52,8 @@ internal partial class PluginManager : IDisposable, IServiceType private readonly DirectoryInfo devPluginDirectory; private readonly BannedPlugin[]? bannedPlugins; + private readonly DalamudLinkPayload openInstallerWindowPluginChangelogsLink; + [ServiceManager.ServiceDependency] private readonly DalamudConfiguration configuration = Service.Get(); @@ -101,6 +105,11 @@ internal partial class PluginManager : IDisposable, IServiceType throw new InvalidDataException("Couldn't deserialize banned plugins manifest."); } + this.openInstallerWindowPluginChangelogsLink = Service.Get().AddChatLinkHandler("Dalamud", 1003, (i, m) => + { + Service.GetNullable()?.OpenPluginInstallerPluginChangelogs(); + }); + this.ApplyPatches(); } @@ -169,25 +178,38 @@ internal partial class PluginManager : IDisposable, IServiceType /// /// The list of updated plugin metadata. /// The header text to send to chat prior to any update info. - public static void PrintUpdatedPlugins(List? updateMetadata, string header) + public void PrintUpdatedPlugins(List? updateMetadata, string header) { var chatGui = Service.Get(); if (updateMetadata is { Count: > 0 }) { - chatGui.Print(header); + chatGui.PrintChat(new XivChatEntry + { + Message = new SeString(new List() + { + new TextPayload(header), + new TextPayload(" ["), + new UIForegroundPayload(500), + this.openInstallerWindowPluginChangelogsLink, + new TextPayload(Loc.Localize("DalamudInstallerPluginChangelogHelp", "Open plugin changelogs") + " "), + RawPayload.LinkTerminator, + new UIForegroundPayload(0), + new TextPayload("]"), + }), + }); foreach (var metadata in updateMetadata) { if (metadata.WasUpdated) { - chatGui.Print(Locs.DalamudPluginUpdateSuccessful(metadata.Name, metadata.Version)); + chatGui.Print(Locs.DalamudPluginUpdateSuccessful(metadata.Name, metadata.Version) + (metadata.HasChangelog ? " " : string.Empty)); } else { chatGui.PrintChat(new XivChatEntry { - Message = Locs.DalamudPluginUpdateFailed(metadata.Name, metadata.Version), + Message = Locs.DalamudPluginUpdateFailed(metadata.Name, metadata.Version) + (metadata.HasChangelog ? " " : string.Empty), Type = XivChatType.Urgent, }); } @@ -975,6 +997,7 @@ internal partial class PluginManager : IDisposable, IServiceType ? metadata.UpdateManifest.TestingAssemblyVersion : metadata.UpdateManifest.AssemblyVersion)!, WasUpdated = true, + HasChangelog = !metadata.UpdateManifest.Changelog.IsNullOrWhitespace(), }; if (!dryRun) diff --git a/Dalamud/Plugin/Internal/Types/PluginUpdateStatus.cs b/Dalamud/Plugin/Internal/Types/PluginUpdateStatus.cs index 02eba7ea7..24ca5fe0f 100644 --- a/Dalamud/Plugin/Internal/Types/PluginUpdateStatus.cs +++ b/Dalamud/Plugin/Internal/Types/PluginUpdateStatus.cs @@ -26,4 +26,9 @@ internal class PluginUpdateStatus /// Gets or sets a value indicating whether the plugin was updated. /// public bool WasUpdated { get; set; } + + /// + /// Gets a value indicating whether the plugin has a changelog if it was updated. + /// + public bool HasChangelog { get; init; } } From e9952cd99b7720738479f7314307308563921503 Mon Sep 17 00:00:00 2001 From: Aireil <33433913+Aireil@users.noreply.github.com> Date: Thu, 4 Aug 2022 22:53:36 +0200 Subject: [PATCH 55/58] Allow dev plugin reload if an error occurred during loading (#952) --- Dalamud/Plugin/Internal/Types/LocalDevPlugin.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Dalamud/Plugin/Internal/Types/LocalDevPlugin.cs b/Dalamud/Plugin/Internal/Types/LocalDevPlugin.cs index 2043b8c6b..e32f0f4c7 100644 --- a/Dalamud/Plugin/Internal/Types/LocalDevPlugin.cs +++ b/Dalamud/Plugin/Internal/Types/LocalDevPlugin.cs @@ -138,9 +138,9 @@ internal class LocalDevPlugin : LocalPlugin, IDisposable return; } - if (this.State != PluginState.Loaded) + if (this.State != PluginState.Loaded && this.State != PluginState.LoadError) { - Log.Debug($"Skipping reload of {this.Name}, state ({this.State}) is not {PluginState.Loaded}."); + Log.Debug($"Skipping reload of {this.Name}, state ({this.State}) is not {PluginState.Loaded} nor {PluginState.LoadError}."); return; } From 63e4490194f8f59183a314f821842f2826c9c7dc Mon Sep 17 00:00:00 2001 From: Aireil <33433913+Aireil@users.noreply.github.com> Date: Thu, 4 Aug 2022 22:54:09 +0200 Subject: [PATCH 56/58] Do not try to dispose undisposable plugins (#949) --- Dalamud/Plugin/Internal/PluginManager.cs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/Dalamud/Plugin/Internal/PluginManager.cs b/Dalamud/Plugin/Internal/PluginManager.cs index 1889cbd50..b19f89bbb 100644 --- a/Dalamud/Plugin/Internal/PluginManager.cs +++ b/Dalamud/Plugin/Internal/PluginManager.cs @@ -265,10 +265,12 @@ internal partial class PluginManager : IDisposable, IServiceType /// public void Dispose() { - if (this.InstalledPlugins.Any()) + var disposablePlugins = + this.InstalledPlugins.Where(plugin => plugin.State is PluginState.Loaded or PluginState.LoadError).ToArray(); + if (disposablePlugins.Any()) { // Unload them first, just in case some of plugin codes are still running via callbacks initiated externally. - foreach (var plugin in this.InstalledPlugins.Where(plugin => !plugin.Manifest.CanUnloadAsync)) + foreach (var plugin in disposablePlugins.Where(plugin => !plugin.Manifest.CanUnloadAsync)) { try { @@ -280,7 +282,7 @@ internal partial class PluginManager : IDisposable, IServiceType } } - Task.WaitAll(this.InstalledPlugins + Task.WaitAll(disposablePlugins .Where(plugin => plugin.Manifest.CanUnloadAsync) .Select(plugin => Task.Run(async () => { @@ -300,7 +302,7 @@ internal partial class PluginManager : IDisposable, IServiceType // Now that we've waited enough, dispose the whole plugin. // Since plugins should have been unloaded above, this should be done quickly. - foreach (var plugin in this.InstalledPlugins) + foreach (var plugin in disposablePlugins) plugin.ExplicitDisposeIgnoreExceptions($"Error disposing {plugin.Name}", Log); } From 866daff035c1988d747ff0f0f0f33988c0dd4d61 Mon Sep 17 00:00:00 2001 From: Aireil <33433913+Aireil@users.noreply.github.com> Date: Thu, 4 Aug 2022 22:54:28 +0200 Subject: [PATCH 57/58] Remove outdated dev warning and add dev plugin tag (#951) --- .../PluginInstaller/PluginInstallerWindow.cs | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/Dalamud/Interface/Internal/Windows/PluginInstaller/PluginInstallerWindow.cs b/Dalamud/Interface/Internal/Windows/PluginInstaller/PluginInstallerWindow.cs index c7d8a2790..4a08ffe06 100644 --- a/Dalamud/Interface/Internal/Windows/PluginInstaller/PluginInstallerWindow.cs +++ b/Dalamud/Interface/Internal/Windows/PluginInstaller/PluginInstallerWindow.cs @@ -1688,6 +1688,12 @@ namespace Dalamud.Interface.Internal.Windows.PluginInstaller // Name var label = plugin.Manifest.Name; + // Dev + if (plugin.IsDev) + { + label += Locs.PluginTitleMod_DevPlugin; + } + // Testing if (plugin.Manifest.Testing) { @@ -1855,12 +1861,6 @@ namespace Dalamud.Interface.Internal.Windows.PluginInstaller ImGui.SameLine(); ImGui.TextColored(ImGuiColors.DalamudGrey3, $" v{plugin.Manifest.EffectiveVersion}"); - if (plugin.IsDev) - { - ImGui.SameLine(); - ImGui.TextColored(ImGuiColors.DalamudRed, Locs.PluginBody_DeleteDevPlugin); - } - ImGuiHelpers.ScaledDummy(5); if (this.DrawPluginImages(plugin, manifest, isThirdParty, index)) @@ -2571,6 +2571,8 @@ namespace Dalamud.Interface.Internal.Windows.PluginInstaller public static string PluginTitleMod_TestingVersion => Loc.Localize("InstallerTestingVersion", " (testing version)"); + public static string PluginTitleMod_DevPlugin => Loc.Localize("InstallerDevPlugin", " (dev plugin)"); + public static string PluginTitleMod_UpdateFailed => Loc.Localize("InstallerUpdateFailed", " (update failed)"); public static string PluginTitleMod_LoadError => Loc.Localize("InstallerLoadError", " (load error)"); @@ -2614,9 +2616,6 @@ namespace Dalamud.Interface.Internal.Windows.PluginInstaller public static string PluginBody_Plugin3rdPartyRepo(string url) => Loc.Localize("InstallerPlugin3rdPartyRepo", "From custom plugin repository {0}").Format(url); public static string PluginBody_AvailableDevPlugin => Loc.Localize("InstallerDevPlugin", " This plugin is available in one of your repos, please remove it from the devPlugins folder."); - - public static string PluginBody_DeleteDevPlugin => Loc.Localize("InstallerDeleteDevPlugin ", " To delete this plugin, please remove it from the devPlugins folder."); - public static string PluginBody_Outdated => Loc.Localize("InstallerOutdatedPluginBody ", "This plugin is outdated and incompatible at the moment. Please wait for it to be updated by its author."); public static string PluginBody_Orphaned => Loc.Localize("InstallerOrphanedPluginBody ", "This plugin's source repository is no longer available. You may need to reinstall it from its repository, or re-add the repository."); From 530f2a8b66bcb72fbcb74dbd10a93e3bd71cd927 Mon Sep 17 00:00:00 2001 From: goat Date: Fri, 5 Aug 2022 20:00:45 +0200 Subject: [PATCH 58/58] chore: temporary workaround for DIP17 plugins --- .../Interface/Internal/Windows/PluginImageCache.cs | 13 ++++++++++++- Dalamud/Plugin/Internal/Types/PluginManifest.cs | 13 +++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/Dalamud/Interface/Internal/Windows/PluginImageCache.cs b/Dalamud/Interface/Internal/Windows/PluginImageCache.cs index e8826e333..43661c9ab 100644 --- a/Dalamud/Interface/Internal/Windows/PluginImageCache.cs +++ b/Dalamud/Interface/Internal/Windows/PluginImageCache.cs @@ -43,6 +43,7 @@ namespace Dalamud.Interface.Internal.Windows public const int PluginIconHeight = 512; private const string MainRepoImageUrl = "https://raw.githubusercontent.com/goatcorp/DalamudPlugins/api6/{0}/{1}/images/{2}"; + private const string MainRepoDip17ImageUrl = "https://raw.githubusercontent.com/goatcorp/PluginDistD17/main/{0}/{1}/images/{2}"; private readonly BlockingCollection>> downloadQueue = new(); private readonly BlockingCollection> loadQueue = new(); @@ -653,6 +654,9 @@ namespace Dalamud.Interface.Internal.Windows if (isThirdParty) return manifest.IconUrl; + if (manifest.IsDip17Plugin) + return MainRepoDip17ImageUrl.Format(manifest.Dip17Channel!, manifest.InternalName, "icon.png"); + return MainRepoImageUrl.Format(isTesting ? "testing" : "plugins", manifest.InternalName, "icon.png"); } @@ -672,7 +676,14 @@ namespace Dalamud.Interface.Internal.Windows var output = new List(); for (var i = 1; i <= 5; i++) { - output.Add(MainRepoImageUrl.Format(isTesting ? "testing" : "plugins", manifest.InternalName, $"image{i}.png")); + if (manifest.IsDip17Plugin) + { + output.Add(MainRepoDip17ImageUrl.Format(manifest.Dip17Channel!, manifest.InternalName, $"image{i}.png")); + } + else + { + output.Add(MainRepoImageUrl.Format(isTesting ? "testing" : "plugins", manifest.InternalName, $"image{i}.png")); + } } return output; diff --git a/Dalamud/Plugin/Internal/Types/PluginManifest.cs b/Dalamud/Plugin/Internal/Types/PluginManifest.cs index c4d712a90..a1b0e6a71 100644 --- a/Dalamud/Plugin/Internal/Types/PluginManifest.cs +++ b/Dalamud/Plugin/Internal/Types/PluginManifest.cs @@ -181,4 +181,17 @@ internal record PluginManifest /// Gets a message that is shown to users when sending feedback. /// public string? FeedbackMessage { get; init; } + + /// + /// Gets a value indicating whether this plugin is DIP17. + /// To be removed. + /// + [JsonProperty("_isDip17Plugin")] + public bool IsDip17Plugin { get; init; } = false; + + /// + /// Gets the DIP17 channel name. + /// + [JsonProperty("_Dip17Channel")] + public string? Dip17Channel { get; init; } }