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);
}
}
}