using System; using System.IO; using System.Runtime.CompilerServices; using System.Threading; using Dalamud.Configuration.Internal; using Dalamud.Data; using Dalamud.Game; using Dalamud.Game.ClientState; using Dalamud.Game.Command; using Dalamud.Game.Gui; using Dalamud.Game.Gui.Internal; using Dalamud.Game.Internal; using Dalamud.Game.Network; using Dalamud.Game.Network.Internal; using Dalamud.Game.Text.SeStringHandling; using Dalamud.Hooking.Internal; using Dalamud.Interface.Internal; using Dalamud.IoC.Internal; using Dalamud.Plugin.Internal; using Dalamud.Plugin.Ipc.Internal; using Serilog; using Serilog.Core; #if DEBUG [assembly: InternalsVisibleTo("Dalamud.CorePlugin")] #endif [assembly: InternalsVisibleTo("Dalamud.Test")] namespace Dalamud { /// /// The main Dalamud class containing all subsystems. /// internal sealed class Dalamud : IDisposable { #region Internals private readonly ManualResetEvent unloadSignal; private readonly ManualResetEvent finishUnloadSignal; private bool hasDisposedPlugins = false; #endregion /// /// Initializes a new instance of the class. /// /// DalamudStartInfo instance. /// LoggingLevelSwitch to control Serilog level. /// Signal signalling shutdown. /// The Dalamud configuration. public Dalamud(DalamudStartInfo info, LoggingLevelSwitch loggingLevelSwitch, ManualResetEvent finishSignal, DalamudConfiguration configuration) { Service.Set(this); Service.Set(info); Service.Set(configuration); this.LogLevelSwitch = loggingLevelSwitch; this.unloadSignal = new ManualResetEvent(false); this.unloadSignal.Reset(); this.finishUnloadSignal = finishSignal; this.finishUnloadSignal.Reset(); } /// /// Gets LoggingLevelSwitch for Dalamud and Plugin logs. /// internal LoggingLevelSwitch LogLevelSwitch { get; private set; } /// /// Gets a value indicating whether Dalamud was successfully loaded. /// internal bool IsReady { get; private set; } /// /// Gets a value indicating whether the plugin system is loaded. /// internal bool IsLoadedPluginSystem => Service.GetNullable() != null; /// /// Gets location of stored assets. /// internal DirectoryInfo AssetDirectory => new(Service.Get().AssetDirectory); /// /// Runs tier 1 of the Dalamud initialization process. /// public void LoadTier1() { try { Service.Set(); // Initialize the process information. Service.Set(new SigScanner(true)); Service.Set(); // Initialize FFXIVClientStructs function resolver FFXIVClientStructs.Resolver.Initialize(); Log.Information("[T1] FFXIVClientStructs initialized!"); // Initialize game subsystem var framework = Service.Set(); Log.Information("[T1] Framework OK!"); Service.Set(); Service.Set(); framework.Enable(); Log.Information("[T1] Framework ENABLE!"); } catch (Exception ex) { Log.Error(ex, "Tier 1 load failed."); this.Unload(); } } /// /// Runs tier 2 of the Dalamud initialization process. /// public void LoadTier2() { try { var configuration = Service.Get(); var antiDebug = Service.Set(); if (configuration.IsAntiAntiDebugEnabled) antiDebug.Enable(); #if DEBUG if (!antiDebug.IsEnabled) antiDebug.Enable(); #endif Log.Information("[T2] AntiDebug OK!"); Service.Set(); Log.Information("[T2] WinSock OK!"); Service.Set(); Log.Information("[T2] NH OK!"); var clientState = Service.Set(); Log.Information("[T2] CS OK!"); var localization = Service.Set(new Localization(Path.Combine(this.AssetDirectory.FullName, "UIRes", "loc", "dalamud"), "dalamud_")); if (!string.IsNullOrEmpty(configuration.LanguageOverride)) { localization.SetupWithLangCode(configuration.LanguageOverride); } else { localization.SetupWithUiCulture(); } Log.Information("[T2] LOC OK!"); if (!bool.Parse(Environment.GetEnvironmentVariable("DALAMUD_NOT_HAVE_INTERFACE") ?? "false")) { try { Service.Set().Enable(); Log.Information("[T2] IM OK!"); } catch (Exception e) { Log.Information(e, "Could not init interface."); } } try { Service.Set(); Log.Information("[T2] IME OK!"); } catch (Exception e) { Log.Information(e, "Could not init IME."); } try { Service.Set().Initialize(this.AssetDirectory.FullName); } catch (Exception e) { Log.Error(e, "Could not initialize DataManager."); this.Unload(); return; } Log.Information("[T2] Data OK!"); Service.Set(); Log.Information("[T2] SeString OK!"); // Initialize managers. Basically handlers for the logic Service.Set(); Service.Set().SetupCommands(); Log.Information("[T2] CM OK!"); Service.Set(); Log.Information("[T2] CH OK!"); clientState.Enable(); Log.Information("[T2] CS ENABLE!"); Service.Set().Enable(); this.IsReady = true; } catch (Exception ex) { Log.Error(ex, "Tier 2 load failed."); this.Unload(); } } /// /// Runs tier 3 of the Dalamud initialization process. /// public void LoadTier3() { try { Log.Information("[T3] START!"); if (!bool.Parse(Environment.GetEnvironmentVariable("DALAMUD_NOT_HAVE_PLUGINS") ?? "false")) { try { Service.Set(); var pluginManager = Service.Set(); pluginManager.OnInstalledPluginsChanged += () => Troubleshooting.LogTroubleshooting(); Log.Information("[T3] PM OK!"); pluginManager.CleanupPlugins(); Log.Information("[T3] PMC OK!"); pluginManager.LoadAllPlugins(); Log.Information("[T3] PML OK!"); } catch (Exception ex) { Log.Error(ex, "Plugin load failed."); } } Service.Set(); Log.Information("[T3] DUI OK!"); Troubleshooting.LogTroubleshooting(); Log.Information("Dalamud is ready."); } catch (Exception ex) { Log.Error(ex, "Tier 3 load failed."); this.Unload(); } } /// /// Queue an unload of Dalamud when it gets the chance. /// public void Unload() { Log.Information("Trigger unload"); this.unloadSignal.Set(); } /// /// Wait for an unload request to start. /// public void WaitForUnload() { this.unloadSignal.WaitOne(); } /// /// Wait for a queued unload to be finalized. /// public void WaitForUnloadFinish() { this.finishUnloadSignal?.WaitOne(); } /// /// Dispose subsystems related to plugin handling. /// 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 Service.GetNullable()?.Dispose(); // this must be done before unloading plugins, or it can cause a race condition // due to rendering happening on another thread, where a plugin might receive // a render call after it has been disposed, which can crash if it attempts to // use any resources that it freed in its own Dispose method Service.GetNullable()?.Dispose(); Service.GetNullable()?.Dispose(); Service.GetNullable()?.Dispose(); } /// /// Dispose Dalamud subsystems. /// public void Dispose() { try { if (!this.hasDisposedPlugins) { this.DisposePlugins(); Thread.Sleep(100); } Service.GetNullable()?.Dispose(); Service.GetNullable()?.Dispose(); this.unloadSignal?.Dispose(); Service.GetNullable()?.Dispose(); Service.GetNullable()?.Dispose(); Service.GetNullable()?.Dispose(); Service.GetNullable()?.Dispose(); Service.GetNullable()?.Dispose(); Service.GetNullable()?.Dispose(); Log.Debug("Dalamud::Dispose() OK!"); } catch (Exception ex) { Log.Error(ex, "Dalamud::Dispose() failed."); } } /// /// Replace the built-in exception handler with a debug one. /// internal void ReplaceExceptionHandler() { var releaseSig = "40 55 53 56 48 8D AC 24 ?? ?? ?? ?? B8 ?? ?? ?? ?? E8 ?? ?? ?? ?? 48 2B E0 48 8B 05 ?? ?? ?? ?? 48 33 C4 48 89 85 ?? ?? ?? ?? 48 83 3D ?? ?? ?? ?? ??"; var releaseFilter = Service.Get().ScanText(releaseSig); Log.Debug($"SE debug filter at {releaseFilter.ToInt64():X}"); var oldFilter = NativeFunctions.SetUnhandledExceptionFilter(releaseFilter); Log.Debug("Reset ExceptionFilter, old: {0}", oldFilter); } } }