diff --git a/Dalamud.Boot/veh.cpp b/Dalamud.Boot/veh.cpp index bb3f37869..c43c000a5 100644 --- a/Dalamud.Boot/veh.cpp +++ b/Dalamud.Boot/veh.cpp @@ -24,9 +24,12 @@ PVOID g_veh_handle = nullptr; bool g_veh_do_full_dump = false; + HANDLE g_crashhandler_process = nullptr; HANDLE g_crashhandler_pipe_write = nullptr; +std::recursive_mutex g_exception_handler_mutex; + std::chrono::time_point g_time_start; bool is_whitelist_exception(const DWORD code) @@ -126,8 +129,6 @@ static void append_injector_launch_args(std::vector& args) LONG exception_handler(EXCEPTION_POINTERS* ex) { - static std::recursive_mutex s_exception_handler_mutex; - if (ex->ExceptionRecord->ExceptionCode == 0x12345678) { // pass @@ -143,7 +144,7 @@ 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); + const auto lock = std::lock_guard(g_exception_handler_mutex); exception_info exinfo{}; exinfo.pExceptionPointers = ex; diff --git a/Dalamud/Dalamud.csproj b/Dalamud/Dalamud.csproj index 619713a4c..8b44450aa 100644 --- a/Dalamud/Dalamud.csproj +++ b/Dalamud/Dalamud.csproj @@ -8,7 +8,7 @@ - 6.4.0.42 + 6.4.0.43 XIV Launcher addon framework $(DalamudVersion) $(DalamudVersion) diff --git a/Dalamud/Interface/Internal/Windows/PluginInstaller/PluginInstallerWindow.cs b/Dalamud/Interface/Internal/Windows/PluginInstaller/PluginInstallerWindow.cs index 4a08ffe06..b658d3304 100644 --- a/Dalamud/Interface/Internal/Windows/PluginInstaller/PluginInstallerWindow.cs +++ b/Dalamud/Interface/Internal/Windows/PluginInstaller/PluginInstallerWindow.cs @@ -44,6 +44,8 @@ namespace Dalamud.Interface.Internal.Windows.PluginInstaller private readonly List openPluginCollapsibles = new(); + private readonly DateTime timeLoaded; + #region Image Tester State private string[] testerImagePaths = new string[5]; @@ -128,6 +130,8 @@ namespace Dalamud.Interface.Internal.Windows.PluginInstaller this.testerImagePaths[i] = string.Empty; } }); + + this.timeLoaded = DateTime.Now; } private enum OperationStatus @@ -326,6 +330,15 @@ namespace Dalamud.Interface.Internal.Windows.PluginInstaller throw new ArgumentOutOfRangeException(); } + if (DateTime.Now - this.timeLoaded > TimeSpan.FromSeconds(90) && isWaitingManager) + { + ImGui.PushStyleColor(ImGuiCol.Text, ImGuiColors.DalamudRed); + ImGuiHelpers.CenteredText("This is embarrassing, but..."); + ImGuiHelpers.CenteredText("one of your plugins may be blocking the installer."); + ImGuiHelpers.CenteredText("You should tell us about this, please keep this window open."); + ImGui.PopStyleColor(); + } + ImGui.EndChild(); } } @@ -968,6 +981,27 @@ namespace Dalamud.Interface.Internal.Windows.PluginInstaller return; } + var pm = Service.Get(); + if (pm.SafeMode) + { + ImGuiHelpers.ScaledDummy(10); + + ImGui.PushStyleColor(ImGuiCol.Text, ImGuiColors.DalamudOrange); + ImGui.PushFont(InterfaceManager.IconFont); + ImGuiHelpers.CenteredText(FontAwesomeIcon.ExclamationTriangle.ToIconString()); + ImGui.PopFont(); + ImGui.PopStyleColor(); + + var lines = Locs.SafeModeDisclaimer.Split('\n'); + foreach (var line in lines) + { + ImGuiHelpers.CenteredText(line); + } + + ImGuiHelpers.ScaledDummy(10); + ImGui.Separator(); + } + ImGui.PushStyleVar(ImGuiStyleVar.ItemSpacing, ImGuiHelpers.ScaledVector2(1, 3)); var groupInfo = this.categoryManager.GroupList[this.categoryManager.CurrentGroupIdx]; @@ -2804,6 +2838,12 @@ namespace Dalamud.Interface.Internal.Windows.PluginInstaller public static string ErrorModalButton_Ok => Loc.Localize("OK", "OK"); #endregion + + #region Other + + public static string SafeModeDisclaimer => Loc.Localize("SafeModeDisclaimer", "You enabled safe mode, no plugins will be loaded.\nYou may delete plugins from the \"Installed plugins\" tab.\nSimply restart your game to disable safe mode."); + + #endregion } } } diff --git a/Dalamud/Plugin/Internal/PluginManager.cs b/Dalamud/Plugin/Internal/PluginManager.cs index b19f89bbb..8051cc081 100644 --- a/Dalamud/Plugin/Internal/PluginManager.cs +++ b/Dalamud/Plugin/Internal/PluginManager.cs @@ -429,84 +429,133 @@ internal partial class PluginManager : IDisposable, IServiceType } } - void LoadPluginsSync(string logPrefix, IEnumerable pluginDefsList) + async Task LoadPluginsSync(string logPrefix, IEnumerable pluginDefsList) { + Log.Information("============= LoadPluginsSync START ============="); + foreach (var pluginDef in pluginDefsList) - LoadPluginOnBoot(logPrefix, pluginDef).Wait(); + await LoadPluginOnBoot(logPrefix, pluginDef); + + Log.Information("============= LoadPluginsSync END ============="); } Task LoadPluginsAsync(string logPrefix, IEnumerable pluginDefsList) { + Log.Information("============= LoadPluginsAsync START ============="); return Task.WhenAll( pluginDefsList .Select(pluginDef => Task.Run(Timings.AttachTimingHandle( () => LoadPluginOnBoot(logPrefix, pluginDef)))) - .ToArray()); + .ToArray()).ContinueWith(t => Log.Information($"============= LoadPluginsAsync END {t.IsCompletedSuccessfully} =============")); } var syncPlugins = pluginDefs.Where(def => def.Manifest?.LoadSync == true).ToList(); var asyncPlugins = pluginDefs.Where(def => def.Manifest?.LoadSync != true).ToList(); var loadTasks = new List(); + var tokenSource = new CancellationTokenSource(TimeSpan.FromMinutes(5)); + // Load plugins that can be loaded anytime LoadPluginsSync( "AnytimeSync", - syncPlugins.Where(def => def.Manifest?.LoadRequiredState == 2)); + syncPlugins.Where(def => def.Manifest?.LoadRequiredState == 2)).GetAwaiter().GetResult(); loadTasks.Add( - LoadPluginsAsync( - "AnytimeAsync", - asyncPlugins.Where(def => def.Manifest?.LoadRequiredState == 2))); + Task.Run( + () => LoadPluginsAsync( + "AnytimeAsync", + asyncPlugins.Where(def => def.Manifest?.LoadRequiredState == 2)), + tokenSource.Token)); // Load plugins that want to be loaded during Framework.Tick loadTasks.Add( - Service - .GetAsync() - .ContinueWith( - x => x.Result.RunOnTick( - () => LoadPluginsSync( - "FrameworkTickSync", - syncPlugins.Where(def => def.Manifest?.LoadRequiredState == 1))), - TaskContinuationOptions.RunContinuationsAsynchronously) - .Unwrap() - .ContinueWith( - _ => LoadPluginsAsync( - "FrameworkTickAsync", - asyncPlugins.Where(def => def.Manifest?.LoadRequiredState == 1)), - TaskContinuationOptions.RunContinuationsAsynchronously) - .Unwrap()); + Task.Run( + () => Service + .GetAsync() + .ContinueWith( + x => x.Result.RunOnTick( + () => LoadPluginsSync( + "FrameworkTickSync", + syncPlugins.Where(def => def.Manifest?.LoadRequiredState == 1)), + cancellationToken: tokenSource.Token), + tokenSource.Token, + TaskContinuationOptions.RunContinuationsAsynchronously, + TaskScheduler.Default) + .Unwrap() + .ContinueWith( + _ => LoadPluginsAsync( + "FrameworkTickAsync", + asyncPlugins.Where(def => def.Manifest?.LoadRequiredState == 1)), + tokenSource.Token, + TaskContinuationOptions.RunContinuationsAsynchronously, + TaskScheduler.Default) + .Unwrap(), + tokenSource.Token)); // Load plugins that want to be loaded during Framework.Tick, when drawing facilities are available loadTasks.Add( - Service + Task.Run( + () => Service .GetAsync() .ContinueWith( _ => Service.Get().RunOnTick( () => LoadPluginsSync( "DrawAvailableSync", - syncPlugins.Where(def => def.Manifest?.LoadRequiredState is 0 or null)))) + syncPlugins.Where(def => def.Manifest?.LoadRequiredState is 0 or null)), + cancellationToken: tokenSource.Token), + tokenSource.Token) .Unwrap() .ContinueWith( _ => LoadPluginsAsync( - "DrawAvailableAsync", - asyncPlugins.Where(def => def.Manifest?.LoadRequiredState is 0 or null))) - .Unwrap()); + "DrawAvailableAsync", + asyncPlugins.Where(def => def.Manifest?.LoadRequiredState is 0 or null)), + tokenSource.Token) + .Unwrap(), + tokenSource.Token)); // Save signatures when all plugins are done loading, successful or not. _ = Task .WhenAll(loadTasks) .ContinueWith( - _ => Service.GetAsync(), - TaskContinuationOptions.RunContinuationsAsynchronously) + t => + { + Log.Information("Task.WhenAll continuing"); + if (!t.IsCompletedSuccessfully) + { + foreach (var loadTask in loadTasks) + { + if (!loadTask.IsCompletedSuccessfully) + { + if (loadTask.Exception != null) + { + Log.Error(loadTask.Exception, " => Exception during load"); + } + else + { + Log.Error($" => Task failed, canceled: {t.IsCanceled} faulted: {t.IsFaulted}"); + } + } + } + } + + return Service.GetAsync(); + }, + tokenSource.Token, + TaskContinuationOptions.RunContinuationsAsynchronously, + TaskScheduler.Default) .Unwrap() .ContinueWith( sigScannerTask => { + Log.Information("sigScannerTask continuing"); + this.PluginsReady = true; this.NotifyInstalledPluginsChanged(); sigScannerTask.Result.Save(); }, - TaskContinuationOptions.RunContinuationsAsynchronously) + tokenSource.Token, + TaskContinuationOptions.RunContinuationsAsynchronously, + TaskScheduler.Default) .ConfigureAwait(false); } diff --git a/DalamudCrashHandler/DalamudCrashHandler.cpp b/DalamudCrashHandler/DalamudCrashHandler.cpp index f0be81a73..e9e9deb48 100644 --- a/DalamudCrashHandler/DalamudCrashHandler.cpp +++ b/DalamudCrashHandler/DalamudCrashHandler.cpp @@ -564,10 +564,17 @@ int main() { config.cRadioButtons = ARRAYSIZE(radios); config.nDefaultRadioButton = IdRadioRestartNormal; config.cxWidth = 300; + +#if _DEBUG config.pszFooter = (L"" R"aa(Help | Open log directory | Open log file | Attempt to resume)aa" ); - +#else + config.pszFooter = (L"" + R"aa(Help | Open log directory | Open log file)aa" + ); +#endif + // Can't do this, xiv stops pumping messages here //config.hwndParent = FindWindowA("FFXIVGAME", NULL); diff --git a/DalamudCrashHandler/DalamudCrashHandler.vcxproj b/DalamudCrashHandler/DalamudCrashHandler.vcxproj index 8aa951a6d..4541462b7 100644 --- a/DalamudCrashHandler/DalamudCrashHandler.vcxproj +++ b/DalamudCrashHandler/DalamudCrashHandler.vcxproj @@ -58,6 +58,7 @@ true false _DEBUG;%(PreprocessorDefinitions) + MultiThreadedDebug false @@ -69,6 +70,7 @@ true true NDEBUG;%(PreprocessorDefinitions) + MultiThreaded true @@ -91,4 +93,4 @@ - \ No newline at end of file +