using System.Diagnostics; using System.IO; using System.Linq; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Threading; using System.Threading.Tasks; using Dalamud.Common; using Dalamud.Configuration.Internal; using Dalamud.Game; using Dalamud.Plugin.Internal; using Dalamud.Storage; using Dalamud.Utility; using Dalamud.Utility.Timing; using Serilog; using Windows.Win32.Foundation; using Windows.Win32.Security; #if DEBUG [assembly: InternalsVisibleTo("Dalamud.CorePlugin")] #endif [assembly: InternalsVisibleTo("Dalamud.Test")] [assembly: InternalsVisibleTo("Dalamud.DevHelpers")] namespace Dalamud; /// /// The main Dalamud class containing all subsystems. /// [ServiceManager.ProvidedService] internal sealed unsafe class Dalamud : IServiceType { #region Internals private static int shownServiceError = 0; private readonly ManualResetEvent unloadSignal; #endregion /// /// Initializes a new instance of the class. /// /// DalamudStartInfo instance. /// ReliableFileStorage instance. /// The Dalamud configuration. /// Event used to signal the main thread to continue. public Dalamud(DalamudStartInfo info, ReliableFileStorage fs, DalamudConfiguration configuration, IntPtr mainThreadContinueEvent) { this.StartInfo = info; this.unloadSignal = new ManualResetEvent(false); this.unloadSignal.Reset(); // Directory resolved signatures(CS, our own) will be cached in var cacheDir = new DirectoryInfo(Path.Combine(this.StartInfo.WorkingDirectory!, "cachedSigs")); if (!cacheDir.Exists) cacheDir.Create(); // Set up the SigScanner for our target module TargetSigScanner scanner; using (Timings.Start("SigScanner Init")) { scanner = new TargetSigScanner( true, new FileInfo(Path.Combine(cacheDir.FullName, $"{this.StartInfo.GameVersion}.json"))); } ServiceManager.InitializeProvidedServices( this, fs, configuration, scanner, Localization.FromAssets(info.AssetDirectory!, configuration.LanguageOverride)); // Set up FFXIVClientStructs this.SetupClientStructsResolver(cacheDir); void KickoffGameThread() { Log.Verbose("=============== GAME THREAD KICKOFF ==============="); Timings.Event("Game thread kickoff"); Windows.Win32.PInvoke.SetEvent(new HANDLE(mainThreadContinueEvent)); } void HandleServiceInitFailure(Task t) { Log.Error(t.Exception!, "Service initialization failure"); if (Interlocked.CompareExchange(ref shownServiceError, 1, 0) != 0) return; Util.Fatal( $"Dalamud failed to load all necessary services.\nThe game will continue, but you may not be able to use plugins.\n\n{t.Exception}", "Dalamud", false); } ServiceManager.InitializeEarlyLoadableServices() .ContinueWith( t => { if (t.IsCompletedSuccessfully) return; HandleServiceInitFailure(t); }); ServiceManager.BlockingResolved.ContinueWith( t => { if (t.IsCompletedSuccessfully) { KickoffGameThread(); return; } HandleServiceInitFailure(t); }); this.DefaultExceptionFilter = SetExceptionHandler(nint.Zero); SetExceptionHandler(this.DefaultExceptionFilter); Log.Debug($"SE default exception filter at {new IntPtr(this.DefaultExceptionFilter):X}"); var debugSig = "40 55 53 57 48 8D AC 24 70 AD FF FF"; this.DebugExceptionFilter = Service.Get().ScanText(debugSig); Log.Debug($"SE debug exception filter at {this.DebugExceptionFilter.ToInt64():X}"); } /// /// Gets the start information for this Dalamud instance. /// internal DalamudStartInfo StartInfo { get; private set; } /// /// Gets location of stored assets. /// internal DirectoryInfo AssetDirectory => new(this.StartInfo.AssetDirectory!); /// /// Gets the in-game default exception filter. /// private nint DefaultExceptionFilter { get; } /// /// Gets the in-game debug exception filter. /// private nint DebugExceptionFilter { get; } /// /// Signal to the crash handler process that we should restart the game. /// public static void RestartGame() { [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(); } /// /// Queue an unload of Dalamud when it gets the chance. /// public void Unload() { Log.Information("Trigger unload"); var reportCrashesSetting = Service.GetNullable()?.ReportShutdownCrashes ?? true; var pmHasDevPlugins = Service.GetNullable()?.InstalledPlugins.Any(x => x.IsDev) ?? false; if (!reportCrashesSetting && !pmHasDevPlugins) { // Leaking on purpose for now var attribs = default(SECURITY_ATTRIBUTES); attribs.nLength = (uint)Unsafe.SizeOf(); Windows.Win32.PInvoke.CreateMutex(attribs, false, "DALAMUD_CRASHES_NO_MORE"); } this.unloadSignal.Set(); } /// /// Wait for an unload request to start. /// public void WaitForUnload() { this.unloadSignal.WaitOne(); } /// /// Replace the current exception handler with the default one. /// internal void UseDefaultExceptionHandler() => SetExceptionHandler(this.DefaultExceptionFilter); /// /// Replace the current exception handler with a debug one. /// internal void UseDebugExceptionHandler() => SetExceptionHandler(this.DebugExceptionFilter); /// /// Disable the current exception handler. /// internal void UseNoExceptionHandler() => SetExceptionHandler(nint.Zero); /// /// Helper function to set the exception handler. /// private static nint SetExceptionHandler(nint newFilter) { var oldFilter = Windows.Win32.PInvoke.SetUnhandledExceptionFilter((delegate* unmanaged[Stdcall])newFilter); Log.Debug("Set ExceptionFilter to {0}, old: {1}", newFilter, (nint)oldFilter); return (nint)oldFilter; } private void SetupClientStructsResolver(DirectoryInfo cacheDir) { using (Timings.Start("CS Resolver Init")) { // the resolver tracks version as a field in the json InteropGenerator.Runtime.Resolver.GetInstance.Setup(Service.Get().SearchBase, $"{this.StartInfo.GameVersion}", new FileInfo(Path.Combine(cacheDir.FullName, "cs.json"))); FFXIVClientStructs.Interop.Generated.Addresses.Register(); InteropGenerator.Runtime.Resolver.GetInstance.Resolve(); } } }