diff --git a/Dalamud.Boot/DalamudStartInfo.cpp b/Dalamud.Boot/DalamudStartInfo.cpp index 1dddce4d3..6176c9468 100644 --- a/Dalamud.Boot/DalamudStartInfo.cpp +++ b/Dalamud.Boot/DalamudStartInfo.cpp @@ -21,6 +21,8 @@ void from_json(const nlohmann::json& json, DalamudStartInfo::WaitMessageboxFlags value = static_cast(static_cast(value) | static_cast(DalamudStartInfo::WaitMessageboxFlags::BeforeInitialize)); else if (item == "beforedalamudentrypoint") value = static_cast(static_cast(value) | static_cast(DalamudStartInfo::WaitMessageboxFlags::BeforeDalamudEntrypoint)); + else if (item == "beforedalamudconstruct") + value = static_cast(static_cast(value) | static_cast(DalamudStartInfo::WaitMessageboxFlags::BeforeDalamudConstruct)); } } diff --git a/Dalamud.Boot/DalamudStartInfo.h b/Dalamud.Boot/DalamudStartInfo.h index 5477844a1..4bd377811 100644 --- a/Dalamud.Boot/DalamudStartInfo.h +++ b/Dalamud.Boot/DalamudStartInfo.h @@ -5,6 +5,7 @@ struct DalamudStartInfo { None = 0, BeforeInitialize = 1 << 0, BeforeDalamudEntrypoint = 1 << 1, + BeforeDalamudConstruct = 1 << 2, }; friend void from_json(const nlohmann::json&, WaitMessageboxFlags&); diff --git a/Dalamud.Boot/dllmain.cpp b/Dalamud.Boot/dllmain.cpp index bae192641..83e678f08 100644 --- a/Dalamud.Boot/dllmain.cpp +++ b/Dalamud.Boot/dllmain.cpp @@ -9,7 +9,7 @@ HMODULE g_hModule; HINSTANCE g_hGameInstance = GetModuleHandleW(nullptr); -DllExport DWORD WINAPI Initialize(LPVOID lpParam, HANDLE hMainThreadContinue) { +DWORD WINAPI InitializeImpl(LPVOID lpParam, HANDLE hMainThreadContinue) { g_startInfo.from_envvars(); std::string jsonParseError; @@ -149,11 +149,6 @@ DllExport DWORD WINAPI Initialize(LPVOID lpParam, HANDLE hMainThreadContinue) { if (static_cast(g_startInfo.BootWaitMessageBox) & static_cast(DalamudStartInfo::WaitMessageboxFlags::BeforeDalamudEntrypoint)) MessageBoxW(nullptr, L"Press OK to continue (BeforeDalamudEntrypoint)", L"Dalamud Boot", MB_OK); - if (hMainThreadContinue) { - // Let the game initialize. - SetEvent(hMainThreadContinue); - } - // We don't need to do this anymore, Dalamud now loads without needing the window to be there. Speed! // utils::wait_for_game_window(); @@ -164,6 +159,10 @@ DllExport DWORD WINAPI Initialize(LPVOID lpParam, HANDLE hMainThreadContinue) { return 0; } +DllExport DWORD WINAPI Initialize(LPVOID lpParam) { + return InitializeImpl(lpParam, CreateEvent(nullptr, TRUE, FALSE, nullptr)); +} + BOOL APIENTRY DllMain(const HMODULE hModule, const DWORD dwReason, LPVOID lpReserved) { DisableThreadLibraryCalls(hModule); diff --git a/Dalamud.Boot/rewrite_entrypoint.cpp b/Dalamud.Boot/rewrite_entrypoint.cpp index 5a585da7a..85a3a950b 100644 --- a/Dalamud.Boot/rewrite_entrypoint.cpp +++ b/Dalamud.Boot/rewrite_entrypoint.cpp @@ -2,7 +2,7 @@ #include "logging.h" -DllExport DWORD WINAPI Initialize(LPVOID lpParam, HANDLE hMainThreadContinue); +DWORD WINAPI InitializeImpl(LPVOID lpParam, HANDLE hMainThreadContinue); struct RewrittenEntryPointParameters { void* pAllocation; @@ -368,7 +368,7 @@ DllExport void WINAPI RewrittenEntryPoint(RewrittenEntryPointParameters& params) loadInfo = params.pLoadInfo; } - Initialize(&loadInfo[0], params.hMainThreadContinue); + InitializeImpl(&loadInfo[0], params.hMainThreadContinue); return 0; } catch (const std::exception& e) { MessageBoxA(nullptr, std::format("Failed to load Dalamud.\n\nError: {}", e.what()).c_str(), "Dalamud.Boot", MB_OK | MB_ICONERROR); @@ -380,6 +380,5 @@ DllExport void WINAPI RewrittenEntryPoint(RewrittenEntryPointParameters& params) CloseHandle(params.hMainThread); WaitForSingleObject(params.hMainThreadContinue, INFINITE); - CloseHandle(params.hMainThreadContinue); VirtualFree(params.pAllocation, 0, MEM_RELEASE); } diff --git a/Dalamud.Injector/Dalamud.Injector.csproj b/Dalamud.Injector/Dalamud.Injector.csproj index 2b2138cb4..99eae66e2 100644 --- a/Dalamud.Injector/Dalamud.Injector.csproj +++ b/Dalamud.Injector/Dalamud.Injector.csproj @@ -61,6 +61,7 @@ + diff --git a/Dalamud.Injector/EntryPoint.cs b/Dalamud.Injector/EntryPoint.cs index f0e42aade..46957908b 100644 --- a/Dalamud.Injector/EntryPoint.cs +++ b/Dalamud.Injector/EntryPoint.cs @@ -83,8 +83,14 @@ namespace Dalamud.Injector } startInfo = ExtractAndInitializeStartInfoFromArguments(startInfo, args); - args.Remove("--console"); // Remove "console" flag, already handled - args.Remove("--etw"); // Remove "etw" flag, already handled + // Remove already handled arguments + args.Remove("--console"); + args.Remove("--msgbox1"); + args.Remove("--msgbox2"); + args.Remove("--msgbox3"); + args.Remove("--etw"); + args.Remove("--veh"); + args.Remove("--veh-full"); var mainCommand = args[1].ToLowerInvariant(); if (mainCommand.Length > 0 && mainCommand.Length <= 6 && "inject"[..mainCommand.Length] == mainCommand) @@ -311,7 +317,11 @@ namespace Dalamud.Injector startInfo.BootLogPath = GetLogPath("dalamud.boot"); startInfo.BootEnabledGameFixes = new List { "prevent_devicechange_crashes", "disable_game_openprocess_access_check", "redirect_openprocess" }; startInfo.BootDotnetOpenProcessHookMode = 0; - // startInfo.BootWaitMessageBox = 2; + 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.BootVehFull = args.Contains("--veh-full"); // startInfo.BootUnhookDlls = new List() { "kernel32.dll", "ntdll.dll", "user32.dll" }; return startInfo; @@ -338,6 +348,7 @@ namespace Dalamud.Injector Console.WriteLine("{0} [-m entrypoint|inject] [--mode=entrypoint|inject]", exeSpaces); Console.WriteLine("{0} [--handle-owner=inherited-handle-value]", exeSpaces); Console.WriteLine("{0} [--without-dalamud] [--no-fix-acl]", exeSpaces); + Console.WriteLine("{0} [--no-wait]", exeSpaces); Console.WriteLine("{0} [-- game_arg1=value1 game_arg2=value2 ...]", exeSpaces); } @@ -349,6 +360,8 @@ namespace Dalamud.Injector Console.WriteLine("Verbose logging:\t[-v]"); Console.WriteLine("Show Console:\t[--console]"); Console.WriteLine("Enable ETW:\t[--etw]"); + Console.WriteLine("Enable VEH:\t[--veh], [--veh-full]"); + Console.WriteLine("Show messagebox:\t[--msgbox1], [--msgbox2], [--msgbox3]"); return 0; } @@ -464,6 +477,7 @@ namespace Dalamud.Injector var handleOwner = IntPtr.Zero; var withoutDalamud = false; var noFixAcl = false; + var waitForGameWindow = true; var parsingGameArgument = false; for (var i = 2; i < args.Count; i++) @@ -480,6 +494,8 @@ namespace Dalamud.Injector useFakeArguments = true; else if (args[i] == "--without-dalamud") withoutDalamud = true; + else if (args[i] == "--no-wait") + waitForGameWindow = false; else if (args[i] == "--no-fix-acl" || args[i] == "--no-acl-fix") noFixAcl = true; else if (args[i] == "-g") @@ -596,7 +612,7 @@ namespace Dalamud.Injector Log.Verbose("RewriteRemoteEntryPointW called!"); } - }); + }, waitForGameWindow); Log.Verbose("Game process started with PID {0}", process.Id); diff --git a/Dalamud/Configuration/Internal/DalamudConfiguration.cs b/Dalamud/Configuration/Internal/DalamudConfiguration.cs index 55e831b42..40bb48a85 100644 --- a/Dalamud/Configuration/Internal/DalamudConfiguration.cs +++ b/Dalamud/Configuration/Internal/DalamudConfiguration.cs @@ -235,6 +235,11 @@ namespace Dalamud.Configuration.Internal /// public bool IsAntiAntiDebugEnabled { get; set; } = false; + /// + /// Gets or sets a value indicating whether to resume game main thread after plugins load. + /// + public bool IsResumeGameAfterPluginLoad { get; set; } = false; + /// /// Gets or sets the kind of beta to download when matches the server value. /// diff --git a/Dalamud/Dalamud.cs b/Dalamud/Dalamud.cs index 9288a0384..7584861e4 100644 --- a/Dalamud/Dalamud.cs +++ b/Dalamud/Dalamud.cs @@ -4,7 +4,7 @@ using System.IO; using System.Reflection; using System.Runtime.CompilerServices; using System.Threading; - +using System.Threading.Tasks; using Dalamud.Configuration.Internal; using Dalamud.Data; using Dalamud.Game; @@ -49,7 +49,6 @@ namespace Dalamud private readonly ManualResetEvent unloadSignal; private readonly ManualResetEvent finishUnloadSignal; - private readonly IntPtr mainThreadContinueEvent; private MonoMod.RuntimeDetour.Hook processMonoHook; private bool hasDisposedPlugins = false; @@ -65,10 +64,6 @@ namespace Dalamud /// Event used to signal the main thread to continue. public Dalamud(DalamudStartInfo info, LoggingLevelSwitch loggingLevelSwitch, ManualResetEvent finishSignal, DalamudConfiguration configuration, IntPtr mainThreadContinueEvent) { - Service.Set(this); - Service.Set(info); - Service.Set(configuration); - this.LogLevelSwitch = loggingLevelSwitch; this.unloadSignal = new ManualResetEvent(false); @@ -77,7 +72,55 @@ namespace Dalamud this.finishUnloadSignal = finishSignal; this.finishUnloadSignal.Reset(); - this.mainThreadContinueEvent = mainThreadContinueEvent; + SerilogEventSink.Instance.LogLine += SerilogOnLogLine; + + Service.Provide(this); + Service.Provide(info); + Service.Provide(configuration); + + if (!configuration.IsResumeGameAfterPluginLoad) + { + NativeFunctions.SetEvent(mainThreadContinueEvent); + _ = ServiceManager.InitializeEarlyLoadableServices(); + } + else + { + Task.Run(async () => + { + try + { + var tasks = new[] + { + ServiceManager.InitializeEarlyLoadableServices(), + ServiceManager.BlockingResolved, + }; + + await Task.WhenAny(tasks); + foreach (var task in tasks) + { + if (task.IsFaulted) + throw task.Exception!; + } + + NativeFunctions.SetEvent(mainThreadContinueEvent); + + await Task.WhenAll(tasks); + foreach (var task in tasks) + { + if (task.IsFaulted) + throw task.Exception!; + } + } + catch (Exception e) + { + Log.Error(e, "Service initialization failure"); + } + finally + { + NativeFunctions.SetEvent(mainThreadContinueEvent); + } + }); + } } /// @@ -88,262 +131,7 @@ namespace Dalamud /// /// Gets location of stored assets. /// - internal DirectoryInfo AssetDirectory => new(Service.Get().AssetDirectory); - - /// - /// Runs tier 1 of the Dalamud initialization process. - /// - public void LoadTier1() - { - using var tier1Timing = Timings.Start("Tier 1 Init"); - - try - { - SerilogEventSink.Instance.LogLine += SerilogOnLogLine; - - Service.Set(); - - // Initialize the process information. - var info = Service.Get(); - var cacheDir = new DirectoryInfo(Path.Combine(info.WorkingDirectory!, "cachedSigs")); - if (!cacheDir.Exists) - cacheDir.Create(); - - Service.Set( - new SigScanner(true, new FileInfo(Path.Combine(cacheDir.FullName, $"{info.GameVersion}.json")))); - Service.Set(); - - // Initialize FFXIVClientStructs function resolver - using (Timings.Start("CS Resolver Init")) - { - FFXIVClientStructs.Resolver.InitializeParallel(new FileInfo(Path.Combine(cacheDir.FullName, $"{info.GameVersion}_cs.json"))); - Log.Information("[T1] FFXIVClientStructs initialized!"); - } - - // Initialize game subsystem - var framework = Service.Set(); - Log.Information("[T1] Framework OK!"); - -#if DEBUG - Service.Set(); - Log.Information("[T1] TaskTracker OK!"); -#endif - Service.Set(); - Service.Set(); - - framework.Enable(); - - Log.Information("[T1] Load complete!"); - } - catch (Exception ex) - { - Log.Error(ex, "Tier 1 load failed"); - this.Unload(); - } - finally - { - // Signal the main game thread to continue - // TODO: This is done in rewrite_entrypoint.cpp again to avoid a race condition. Should be fixed! - // NativeFunctions.SetEvent(this.mainThreadContinueEvent); - - // Timings.Event("Game kickoff"); - // Log.Information("[T1] Game thread continued!"); - } - } - - /// - /// Runs tier 2 of the Dalamud initialization process. - /// - /// Whether or not the load succeeded. - public bool LoadTier2() - { - // This marks the first time we are actually on the game's main thread - ThreadSafety.MarkMainThread(); - - using var tier2Timing = Timings.Start("Tier 2 Init"); - - try - { - var configuration = Service.Get(); - - var antiDebug = Service.Set(); - if (!antiDebug.IsEnabled) - { -#if DEBUG - antiDebug.Enable(); -#else - if (configuration.IsAntiAntiDebugEnabled) - antiDebug.Enable(); -#endif - } - - Log.Information("[T2] AntiDebug OK!"); - - Service.Set(); - Log.Information("[T2] WinSock OK!"); - - Service.Set(); - Log.Information("[T2] NH OK!"); - - using (Timings.Start("DM Init")) - { - try - { - Service.Set().Initialize(this.AssetDirectory.FullName); - } - catch (Exception e) - { - Log.Error(e, "Could not initialize DataManager"); - this.Unload(); - return false; - } - } - - Log.Information("[T2] Data OK!"); - - using (Timings.Start("CS Init")) - { - 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!"); - - // This is enabled in ImGuiScene setup - using (Timings.Start("IME Init")) - { - Service.Set(); - Log.Information("[T2] IME OK!"); - } - - using (Timings.Start("IM Enable")) - { - Service.Set().Enable(); - Log.Information("[T2] IM OK!"); - } - - using (Timings.Start("GFM Init")) - { - Service.Set(); - Log.Information("[T2] GFM OK!"); - } - -#pragma warning disable CS0618 // Type or member is obsolete - Service.Set(); -#pragma warning restore CS0618 // Type or member is obsolete - - Log.Information("[T2] SeString OK!"); - - using (Timings.Start("CM Init")) - { - Service.Set(); - Service.Set().SetupCommands(); - Log.Information("[T2] CM OK!"); - } - - Service.Set(); - Log.Information("[T2] CH OK!"); - - using (Timings.Start("CS Enable")) - { - Service.Get().Enable(); - Log.Information("[T2] CS ENABLE!"); - } - - Service.Set().Enable(); - Log.Information("[T2] ATKTWEAKS ENABLE!"); - - Log.Information("[T2] Load complete!"); - } - catch (Exception ex) - { - Log.Error(ex, "Tier 2 load failed"); - this.Unload(); - return false; - } - - return true; - } - - /// - /// Runs tier 3 of the Dalamud initialization process. - /// - /// Whether or not the load succeeded. - public bool LoadTier3() - { - using var tier3Timing = Timings.Start("Tier 3 Init"); - - ThreadSafety.AssertMainThread(); - - try - { - Log.Information("[T3] START!"); - - Service.Set(); - - PluginManager pluginManager; - using (Timings.Start("PM Init")) - { - pluginManager = Service.Set(); - Service.Set(); - Log.Information("[T3] PM OK!"); - } - - Service.Set(); - Log.Information("[T3] DUI OK!"); - - try - { - using (Timings.Start("PM Load Plugin Repos")) - { - _ = pluginManager.SetPluginReposFromConfigAsync(false); - pluginManager.OnInstalledPluginsChanged += Troubleshooting.LogTroubleshooting; - - Log.Information("[T3] PM repos OK!"); - } - - using (Timings.Start("PM Cleanup Plugins")) - { - pluginManager.CleanupPlugins(); - Log.Information("[T3] PMC OK!"); - } - - using (Timings.Start("PM Load Sync Plugins")) - { - pluginManager.LoadAllPlugins(); - Log.Information("[T3] PML OK!"); - } - } - catch (Exception ex) - { - Log.Error(ex, "Plugin load failed"); - } - - Troubleshooting.LogTroubleshooting(); - - Log.Information("Dalamud is ready"); - Timings.Event("Dalamud ready"); - } - catch (Exception ex) - { - Log.Error(ex, "Tier 3 load failed"); - this.Unload(); - - return false; - } - - return true; - } + internal DirectoryInfo AssetDirectory => new(Service.Get().AssetDirectory!); /// /// Queue an unload of Dalamud when it gets the chance. diff --git a/Dalamud/DalamudStartInfo.cs b/Dalamud/DalamudStartInfo.cs index 5ba919828..2fd47e759 100644 --- a/Dalamud/DalamudStartInfo.cs +++ b/Dalamud/DalamudStartInfo.cs @@ -86,12 +86,12 @@ namespace Dalamud /// /// Gets or sets a value that specifies how much to wait before a new Dalamud session. /// - public int DelayInitializeMs { get; set; } = 0; + public int DelayInitializeMs { get; set; } /// /// Gets or sets the path the boot log file is supposed to be written to. /// - public string BootLogPath { get; set; } + public string? BootLogPath { get; set; } /// /// Gets or sets a value indicating whether a Boot console should be shown. diff --git a/Dalamud/Data/DataManager.cs b/Dalamud/Data/DataManager.cs index 8daa7cdf5..b3d90a3a3 100644 --- a/Dalamud/Data/DataManager.cs +++ b/Dalamud/Data/DataManager.cs @@ -26,22 +26,91 @@ namespace Dalamud.Data /// [PluginInterface] [InterfaceVersion("1.0")] + [ServiceManager.BlockingEarlyLoadedService] public sealed class DataManager : IDisposable { private const string IconFileFormat = "ui/icon/{0:D3}000/{1}{2:D6}.tex"; - private Thread luminaResourceThread; - private CancellationTokenSource luminaCancellationTokenSource; + private readonly Thread luminaResourceThread; + private readonly CancellationTokenSource luminaCancellationTokenSource; - /// - /// Initializes a new instance of the class. - /// - internal DataManager() + [ServiceManager.ServiceConstructor] + private DataManager(DalamudStartInfo dalamudStartInfo, Dalamud dalamud) { - this.Language = Service.Get().Language; + this.Language = dalamudStartInfo.Language; // Set up default values so plugins do not null-reference when data is being loaded. this.ClientOpCodes = this.ServerOpCodes = new ReadOnlyDictionary(new Dictionary()); + + var baseDir = dalamud.AssetDirectory.FullName; + try + { + Log.Verbose("Starting data load..."); + + var zoneOpCodeDict = JsonConvert.DeserializeObject>( + File.ReadAllText(Path.Combine(baseDir, "UIRes", "serveropcode.json")))!; + this.ServerOpCodes = new ReadOnlyDictionary(zoneOpCodeDict); + + Log.Verbose("Loaded {0} ServerOpCodes.", zoneOpCodeDict.Count); + + var clientOpCodeDict = JsonConvert.DeserializeObject>( + File.ReadAllText(Path.Combine(baseDir, "UIRes", "clientopcode.json")))!; + this.ClientOpCodes = new ReadOnlyDictionary(clientOpCodeDict); + + Log.Verbose("Loaded {0} ClientOpCodes.", clientOpCodeDict.Count); + + using (Timings.Start("Lumina Init")) + { + var luminaOptions = new LuminaOptions + { + LoadMultithreaded = true, + CacheFileResources = true, +#if DEBUG + PanicOnSheetChecksumMismatch = true, +#else + PanicOnSheetChecksumMismatch = false, +#endif + DefaultExcelLanguage = this.Language.ToLumina(), + }; + + var processModule = Process.GetCurrentProcess().MainModule; + if (processModule != null) + { + this.GameData = new GameData(Path.Combine(Path.GetDirectoryName(processModule.FileName)!, "sqpack"), luminaOptions); + } + else + { + throw new Exception("Could not main module."); + } + + Log.Information("Lumina is ready: {0}", this.GameData.DataPath); + } + + this.IsDataReady = true; + + this.luminaCancellationTokenSource = new(); + + var luminaCancellationToken = this.luminaCancellationTokenSource.Token; + this.luminaResourceThread = new(() => + { + while (!luminaCancellationToken.IsCancellationRequested) + { + if (this.GameData.FileHandleManager.HasPendingFileLoads) + { + this.GameData.ProcessFileHandleQueue(); + } + else + { + Thread.Sleep(5); + } + } + }); + this.luminaResourceThread.Start(); + } + catch (Exception ex) + { + Log.Error(ex, "Could not download data."); + } } /// @@ -68,7 +137,7 @@ namespace Dalamud.Data /// /// Gets an object which gives access to any of the game's sheet data. /// - public ExcelModule Excel => this.GameData?.Excel; + public ExcelModule Excel => this.GameData.Excel; /// /// Gets a value indicating whether Game Data is ready to be read. @@ -180,7 +249,7 @@ namespace Dalamud.Data /// The type of the icon (e.g. 'hq' to get the HQ variant of an item icon). /// The icon ID. /// The containing the icon. - public TexFile? GetIcon(string type, uint iconId) + public TexFile? GetIcon(string? type, uint iconId) { type ??= string.Empty; if (type.Length > 0 && !type.EndsWith("/")) @@ -276,81 +345,5 @@ namespace Dalamud.Data { this.luminaCancellationTokenSource.Cancel(); } - - /// - /// Initialize this data manager. - /// - /// The directory to load data from. - internal void Initialize(string baseDir) - { - try - { - Log.Verbose("Starting data load..."); - - var zoneOpCodeDict = JsonConvert.DeserializeObject>( - File.ReadAllText(Path.Combine(baseDir, "UIRes", "serveropcode.json"))); - this.ServerOpCodes = new ReadOnlyDictionary(zoneOpCodeDict); - - Log.Verbose("Loaded {0} ServerOpCodes.", zoneOpCodeDict.Count); - - var clientOpCodeDict = JsonConvert.DeserializeObject>( - File.ReadAllText(Path.Combine(baseDir, "UIRes", "clientopcode.json"))); - this.ClientOpCodes = new ReadOnlyDictionary(clientOpCodeDict); - - Log.Verbose("Loaded {0} ClientOpCodes.", clientOpCodeDict.Count); - - using (Timings.Start("Lumina Init")) - { - var luminaOptions = new LuminaOptions - { - LoadMultithreaded = true, - CacheFileResources = true, -#if DEBUG - PanicOnSheetChecksumMismatch = true, -#else - PanicOnSheetChecksumMismatch = false, -#endif - DefaultExcelLanguage = this.Language.ToLumina(), - }; - - var processModule = Process.GetCurrentProcess().MainModule; - if (processModule != null) - { - this.GameData = new GameData(Path.Combine(Path.GetDirectoryName(processModule.FileName), "sqpack"), luminaOptions); - } - else - { - throw new Exception("Could not main module."); - } - - Log.Information("Lumina is ready: {0}", this.GameData.DataPath); - } - - this.IsDataReady = true; - - this.luminaCancellationTokenSource = new(); - - var luminaCancellationToken = this.luminaCancellationTokenSource.Token; - this.luminaResourceThread = new(() => - { - while (!luminaCancellationToken.IsCancellationRequested) - { - if (this.GameData.FileHandleManager.HasPendingFileLoads) - { - this.GameData.ProcessFileHandleQueue(); - } - else - { - Thread.Sleep(5); - } - } - }); - this.luminaResourceThread.Start(); - } - catch (Exception ex) - { - Log.Error(ex, "Could not download data."); - } - } } } diff --git a/Dalamud/EntryPoint.cs b/Dalamud/EntryPoint.cs index 7036d0116..1f16e2da9 100644 --- a/Dalamud/EntryPoint.cs +++ b/Dalamud/EntryPoint.cs @@ -47,8 +47,11 @@ namespace Dalamud /// Event used to signal the main thread to continue. public static void Initialize(IntPtr infoPtr, IntPtr mainThreadContinueEvent) { - var infoStr = Marshal.PtrToStringUTF8(infoPtr); - var info = JsonConvert.DeserializeObject(infoStr); + var infoStr = Marshal.PtrToStringUTF8(infoPtr)!; + var info = JsonConvert.DeserializeObject(infoStr)!; + + if ((info.BootWaitMessageBox & 4) != 0) + MessageBoxW(IntPtr.Zero, "Press OK to continue (BeforeDalamudConstruct)", "Dalamud Boot", MessageBoxType.Ok); new Thread(() => RunThread(info, mainThreadContinueEvent)).Start(); } @@ -148,9 +151,6 @@ namespace Dalamud var dalamud = new Dalamud(info, levelSwitch, finishSignal, configuration, mainThreadContinueEvent); Log.Information("This is Dalamud - Core: {GitHash}, CS: {CsGitHash}", Util.GetGitHash(), Util.GetGitHashClientStructs()); - // Run session - dalamud.LoadTier1(); - dalamud.WaitForUnload(); dalamud.Dispose(); diff --git a/Dalamud/Game/BaseAddressResolver.cs b/Dalamud/Game/BaseAddressResolver.cs index 4528b138e..52b7031fe 100644 --- a/Dalamud/Game/BaseAddressResolver.cs +++ b/Dalamud/Game/BaseAddressResolver.cs @@ -20,15 +20,6 @@ namespace Dalamud.Game /// protected bool IsResolved { get; set; } - /// - /// Setup the resolver, calling the appopriate method based on the process architecture. - /// - public void Setup() - { - var scanner = Service.Get(); - this.Setup(scanner); - } - /// /// Setup the resolver, calling the appopriate method based on the process architecture. /// @@ -55,11 +46,13 @@ namespace Dalamud.Game this.SetupInternal(scanner); var className = this.GetType().Name; - DebugScannedValues[className] = new List<(string, IntPtr)>(); + var list = new List<(string, IntPtr)>(); + lock (DebugScannedValues) + DebugScannedValues[className] = list; foreach (var property in this.GetType().GetProperties().Where(x => x.PropertyType == typeof(IntPtr))) { - DebugScannedValues[className].Add((property.Name, (IntPtr)property.GetValue(this))); + list.Add((property.Name, (IntPtr)property.GetValue(this))); } this.IsResolved = true; diff --git a/Dalamud/Game/ChatHandlers.cs b/Dalamud/Game/ChatHandlers.cs index ceecf7638..1468dbafb 100644 --- a/Dalamud/Game/ChatHandlers.cs +++ b/Dalamud/Game/ChatHandlers.cs @@ -28,6 +28,7 @@ namespace Dalamud.Game /// [PluginInterface] [InterfaceVersion("1.0")] + [ServiceManager.BlockingEarlyLoadedService] public class ChatHandlers { // private static readonly Dictionary UnicodeToDiscordEmojiDict = new() @@ -109,13 +110,9 @@ namespace Dalamud.Game private bool hasSeenLoadingMsg; private bool hasAutoUpdatedPlugins; - /// - /// Initializes a new instance of the class. - /// - internal ChatHandlers() + [ServiceManager.ServiceConstructor] + private ChatHandlers(ChatGui chatGui) { - var chatGui = Service.Get(); - chatGui.CheckMessageHandled += this.OnCheckMessageHandled; chatGui.ChatMessage += this.OnChatMessage; diff --git a/Dalamud/Game/ClientState/Aetherytes/AetheryteList.cs b/Dalamud/Game/ClientState/Aetherytes/AetheryteList.cs index 0f03338d2..e8f4e9da3 100644 --- a/Dalamud/Game/ClientState/Aetherytes/AetheryteList.cs +++ b/Dalamud/Game/ClientState/Aetherytes/AetheryteList.cs @@ -14,18 +14,18 @@ namespace Dalamud.Game.ClientState.Aetherytes /// [PluginInterface] [InterfaceVersion("1.0")] + [ServiceManager.BlockingEarlyLoadedService] public sealed partial class AetheryteList { + [ServiceManager.ServiceDependency] + private readonly ClientState clientState = Service.Get(); private readonly ClientStateAddressResolver address; private readonly UpdateAetheryteListDelegate updateAetheryteListFunc; - /// - /// Initializes a new instance of the class. - /// - /// Client state address resolver. - internal AetheryteList(ClientStateAddressResolver addressResolver) + [ServiceManager.ServiceConstructor] + private AetheryteList() { - this.address = addressResolver; + this.address = this.clientState.AddressResolver; this.updateAetheryteListFunc = Marshal.GetDelegateForFunctionPointer(this.address.UpdateAetheryteList); Log.Verbose($"Teleport address 0x{this.address.Telepo.ToInt64():X}"); @@ -40,9 +40,7 @@ namespace Dalamud.Game.ClientState.Aetherytes { get { - var clientState = Service.Get(); - - if (clientState.LocalPlayer == null) + if (this.clientState.LocalPlayer == null) return 0; this.Update(); @@ -70,9 +68,7 @@ namespace Dalamud.Game.ClientState.Aetherytes return null; } - var clientState = Service.Get(); - - if (clientState.LocalPlayer == null) + if (this.clientState.LocalPlayer == null) return null; return new AetheryteEntry(TelepoStruct->TeleportList.Get((ulong)index)); @@ -81,10 +77,8 @@ namespace Dalamud.Game.ClientState.Aetherytes private void Update() { - var clientState = Service.Get(); - // this is very very important as otherwise it crashes - if (clientState.LocalPlayer == null) + if (this.clientState.LocalPlayer == null) return; this.updateAetheryteListFunc(this.address.Telepo, 0); diff --git a/Dalamud/Game/ClientState/Buddy/BuddyList.cs b/Dalamud/Game/ClientState/Buddy/BuddyList.cs index 21a31d868..85706de6d 100644 --- a/Dalamud/Game/ClientState/Buddy/BuddyList.cs +++ b/Dalamud/Game/ClientState/Buddy/BuddyList.cs @@ -15,19 +15,17 @@ namespace Dalamud.Game.ClientState.Buddy /// [PluginInterface] [InterfaceVersion("1.0")] + [ServiceManager.BlockingEarlyLoadedService] public sealed partial class BuddyList { private const uint InvalidObjectID = 0xE0000000; private readonly ClientStateAddressResolver address; - /// - /// Initializes a new instance of the class. - /// - /// Client state address resolver. - internal BuddyList(ClientStateAddressResolver addressResolver) + [ServiceManager.ServiceConstructor] + private BuddyList(ClientState clientState) { - this.address = addressResolver; + this.address = clientState.AddressResolver; Log.Verbose($"Buddy list address 0x{this.address.BuddyList.ToInt64():X}"); } diff --git a/Dalamud/Game/ClientState/ClientState.cs b/Dalamud/Game/ClientState/ClientState.cs index ce816c994..bafb4cdf8 100644 --- a/Dalamud/Game/ClientState/ClientState.cs +++ b/Dalamud/Game/ClientState/ClientState.cs @@ -27,6 +27,7 @@ namespace Dalamud.Game.ClientState /// [PluginInterface] [InterfaceVersion("1.0")] + [ServiceManager.BlockingEarlyLoadedService] public sealed class ClientState : IDisposable { private readonly ClientStateAddressResolver address; @@ -36,46 +37,26 @@ namespace Dalamud.Game.ClientState private bool lastFramePvP = false; /// - /// Initializes a new instance of the class. - /// Set up client state access. + /// Gets client state address resolver. /// - internal ClientState() + internal ClientStateAddressResolver AddressResolver => this.address; + + [ServiceManager.ServiceConstructor] + private ClientState(Framework framework, NetworkHandlers networkHandlers, SigScanner sigScanner, DalamudStartInfo startInfo) { this.address = new ClientStateAddressResolver(); - this.address.Setup(); + this.address.Setup(sigScanner); Log.Verbose("===== C L I E N T S T A T E ====="); - this.ClientLanguage = Service.Get().Language; - - Service.Set(this.address); - - Service.Set(this.address); - - Service.Set(this.address); - - Service.Set(this.address); - - Service.Set(this.address); - - Service.Set(this.address); - - Service.Set(this.address); - - Service.Set(this.address); - - Service.Set(this.address); - - Service.Set(this.address); + this.ClientLanguage = startInfo.Language; Log.Verbose($"SetupTerritoryType address 0x{this.address.SetupTerritoryType.ToInt64():X}"); this.setupTerritoryTypeHook = new Hook(this.address.SetupTerritoryType, this.SetupTerritoryTypeDetour); - var framework = Service.Get(); framework.Update += this.FrameworkOnOnUpdateEvent; - var networkHandlers = Service.Get(); networkHandlers.CfPop += this.NetworkHandlersOnCfPop; } diff --git a/Dalamud/Game/ClientState/Conditions/Condition.cs b/Dalamud/Game/ClientState/Conditions/Condition.cs index 7c78bb17c..0bfe44145 100644 --- a/Dalamud/Game/ClientState/Conditions/Condition.cs +++ b/Dalamud/Game/ClientState/Conditions/Condition.cs @@ -11,6 +11,7 @@ namespace Dalamud.Game.ClientState.Conditions /// [PluginInterface] [InterfaceVersion("1.0")] + [ServiceManager.BlockingEarlyLoadedService] public sealed partial class Condition { /// @@ -20,12 +21,10 @@ namespace Dalamud.Game.ClientState.Conditions private readonly bool[] cache = new bool[MaxConditionEntries]; - /// - /// Initializes a new instance of the class. - /// - /// The ClientStateAddressResolver instance. - internal Condition(ClientStateAddressResolver resolver) + [ServiceManager.ServiceConstructor] + private Condition(ClientState clientState) { + var resolver = clientState.AddressResolver; this.Address = resolver.ConditionFlags; } diff --git a/Dalamud/Game/ClientState/Fates/FateTable.cs b/Dalamud/Game/ClientState/Fates/FateTable.cs index afe36aada..b1a2d9c22 100644 --- a/Dalamud/Game/ClientState/Fates/FateTable.cs +++ b/Dalamud/Game/ClientState/Fates/FateTable.cs @@ -13,17 +13,15 @@ namespace Dalamud.Game.ClientState.Fates /// [PluginInterface] [InterfaceVersion("1.0")] + [ServiceManager.BlockingEarlyLoadedService] public sealed partial class FateTable { private readonly ClientStateAddressResolver address; - /// - /// Initializes a new instance of the class. - /// - /// Client state address resolver. - internal FateTable(ClientStateAddressResolver addressResolver) + [ServiceManager.ServiceConstructor] + private FateTable(ClientState clientState) { - this.address = addressResolver; + this.address = clientState.AddressResolver; Log.Verbose($"Fate table address 0x{this.address.FateTablePtr.ToInt64():X}"); } diff --git a/Dalamud/Game/ClientState/GamePad/GamepadState.cs b/Dalamud/Game/ClientState/GamePad/GamepadState.cs index bcd100120..ce452bcfa 100644 --- a/Dalamud/Game/ClientState/GamePad/GamepadState.cs +++ b/Dalamud/Game/ClientState/GamePad/GamepadState.cs @@ -15,6 +15,7 @@ namespace Dalamud.Game.ClientState.GamePad /// [PluginInterface] [InterfaceVersion("1.0.0")] + [ServiceManager.BlockingEarlyLoadedService] public unsafe class GamepadState : IDisposable { private readonly Hook gamepadPoll; @@ -26,12 +27,10 @@ namespace Dalamud.Game.ClientState.GamePad private int rightStickX; private int rightStickY; - /// - /// Initializes a new instance of the class. - /// - /// Resolver knowing the pointer to the GamepadPoll function. - public GamepadState(ClientStateAddressResolver resolver) + [ServiceManager.ServiceConstructor] + private GamepadState(ClientState clientState) { + var resolver = clientState.AddressResolver; Log.Verbose($"GamepadPoll address 0x{resolver.GamepadPoll.ToInt64():X}"); this.gamepadPoll = new Hook(resolver.GamepadPoll, this.GamepadPollDetour); } diff --git a/Dalamud/Game/ClientState/JobGauge/JobGauges.cs b/Dalamud/Game/ClientState/JobGauge/JobGauges.cs index 958f78b1b..dba74ab67 100644 --- a/Dalamud/Game/ClientState/JobGauge/JobGauges.cs +++ b/Dalamud/Game/ClientState/JobGauge/JobGauges.cs @@ -14,17 +14,15 @@ namespace Dalamud.Game.ClientState.JobGauge /// [PluginInterface] [InterfaceVersion("1.0")] + [ServiceManager.BlockingEarlyLoadedService] public class JobGauges { private Dictionary cache = new(); - /// - /// Initializes a new instance of the class. - /// - /// Address resolver with the JobGauge memory location(s). - public JobGauges(ClientStateAddressResolver addressResolver) + [ServiceManager.ServiceConstructor] + private JobGauges(ClientState clientState) { - this.Address = addressResolver.JobGaugeData; + this.Address = clientState.AddressResolver.JobGaugeData; Log.Verbose($"JobGaugeData address 0x{this.Address.ToInt64():X}"); } diff --git a/Dalamud/Game/ClientState/Keys/KeyState.cs b/Dalamud/Game/ClientState/Keys/KeyState.cs index 0476eee89..90c3ad872 100644 --- a/Dalamud/Game/ClientState/Keys/KeyState.cs +++ b/Dalamud/Game/ClientState/Keys/KeyState.cs @@ -22,6 +22,7 @@ namespace Dalamud.Game.ClientState.Keys /// [PluginInterface] [InterfaceVersion("1.0")] + [ServiceManager.BlockingEarlyLoadedService] public class KeyState { // The array is accessed in a way that this limit doesn't appear to exist @@ -32,14 +33,11 @@ namespace Dalamud.Game.ClientState.Keys private readonly IntPtr indexBase; private VirtualKey[] validVirtualKeyCache = null; - /// - /// Initializes a new instance of the class. - /// - /// The ClientStateAddressResolver instance. - public KeyState(ClientStateAddressResolver addressResolver) + [ServiceManager.ServiceConstructor] + private KeyState(SigScanner sigScanner, ClientState clientState) { - var moduleBaseAddress = Service.Get().Module.BaseAddress; - + var moduleBaseAddress = sigScanner.Module.BaseAddress; + var addressResolver = clientState.AddressResolver; this.bufferBase = moduleBaseAddress + Marshal.ReadInt32(addressResolver.KeyboardState); this.indexBase = moduleBaseAddress + Marshal.ReadInt32(addressResolver.KeyboardStateIndexArray); diff --git a/Dalamud/Game/ClientState/Objects/ObjectTable.cs b/Dalamud/Game/ClientState/Objects/ObjectTable.cs index df045be1c..5500aa533 100644 --- a/Dalamud/Game/ClientState/Objects/ObjectTable.cs +++ b/Dalamud/Game/ClientState/Objects/ObjectTable.cs @@ -16,19 +16,17 @@ namespace Dalamud.Game.ClientState.Objects /// [PluginInterface] [InterfaceVersion("1.0")] + [ServiceManager.BlockingEarlyLoadedService] public sealed partial class ObjectTable { private const int ObjectTableLength = 424; private readonly ClientStateAddressResolver address; - /// - /// Initializes a new instance of the class. - /// - /// Client state address resolver. - internal ObjectTable(ClientStateAddressResolver addressResolver) + [ServiceManager.ServiceConstructor] + private ObjectTable(ClientState clientState) { - this.address = addressResolver; + this.address = clientState.AddressResolver; Log.Verbose($"Object table address 0x{this.address.ObjectTable.ToInt64():X}"); } diff --git a/Dalamud/Game/ClientState/Objects/TargetManager.cs b/Dalamud/Game/ClientState/Objects/TargetManager.cs index d9372674c..881f77c54 100644 --- a/Dalamud/Game/ClientState/Objects/TargetManager.cs +++ b/Dalamud/Game/ClientState/Objects/TargetManager.cs @@ -11,17 +11,15 @@ namespace Dalamud.Game.ClientState.Objects /// [PluginInterface] [InterfaceVersion("1.0")] + [ServiceManager.BlockingEarlyLoadedService] public sealed unsafe class TargetManager { private readonly ClientStateAddressResolver address; - /// - /// Initializes a new instance of the class. - /// - /// The ClientStateAddressResolver instance. - internal TargetManager(ClientStateAddressResolver addressResolver) + [ServiceManager.ServiceConstructor] + private TargetManager(ClientState clientState) { - this.address = addressResolver; + this.address = clientState.AddressResolver; } /// diff --git a/Dalamud/Game/ClientState/Party/PartyList.cs b/Dalamud/Game/ClientState/Party/PartyList.cs index 047ea6e3e..aee27160c 100644 --- a/Dalamud/Game/ClientState/Party/PartyList.cs +++ b/Dalamud/Game/ClientState/Party/PartyList.cs @@ -14,6 +14,7 @@ namespace Dalamud.Game.ClientState.Party /// [PluginInterface] [InterfaceVersion("1.0")] + [ServiceManager.BlockingEarlyLoadedService] public sealed unsafe partial class PartyList { private const int GroupLength = 8; @@ -21,13 +22,10 @@ namespace Dalamud.Game.ClientState.Party private readonly ClientStateAddressResolver address; - /// - /// Initializes a new instance of the class. - /// - /// Client state address resolver. - internal PartyList(ClientStateAddressResolver addressResolver) + [ServiceManager.ServiceConstructor] + private PartyList(ClientState clientState) { - this.address = addressResolver; + this.address = clientState.AddressResolver; Log.Verbose($"Group manager address 0x{this.address.GroupManager.ToInt64():X}"); } diff --git a/Dalamud/Game/Command/CommandManager.cs b/Dalamud/Game/Command/CommandManager.cs index 708bd6bcf..1b83cd3b1 100644 --- a/Dalamud/Game/Command/CommandManager.cs +++ b/Dalamud/Game/Command/CommandManager.cs @@ -17,6 +17,7 @@ namespace Dalamud.Game.Command /// [PluginInterface] [InterfaceVersion("1.0")] + [ServiceManager.BlockingEarlyLoadedService] public sealed class CommandManager { private readonly Dictionary commandMap = new(); @@ -27,13 +28,9 @@ namespace Dalamud.Game.Command private readonly Regex commandRegexCn = new(@"^^(“|「)(?.+)(”|」)(出现问题:该命令不存在|出現問題:該命令不存在)。$", RegexOptions.Compiled); private readonly Regex currentLangCommandRegex; - /// - /// Initializes a new instance of the class. - /// - internal CommandManager() + [ServiceManager.ServiceConstructor] + private CommandManager(DalamudStartInfo startInfo) { - var startInfo = Service.Get(); - this.currentLangCommandRegex = startInfo.Language switch { ClientLanguage.Japanese => this.commandRegexJp, diff --git a/Dalamud/Game/Framework.cs b/Dalamud/Game/Framework.cs index 16b742eec..e9cdc18ac 100644 --- a/Dalamud/Game/Framework.cs +++ b/Dalamud/Game/Framework.cs @@ -24,6 +24,7 @@ namespace Dalamud.Game /// [PluginInterface] [InterfaceVersion("1.0")] + [ServiceManager.BlockingEarlyLoadedService] public sealed class Framework : IDisposable { private static Stopwatch statsStopwatch = new(); @@ -31,27 +32,28 @@ namespace Dalamud.Game private readonly List runOnNextTickTaskList = new(); private readonly Stopwatch updateStopwatch = new(); - private bool tier2Initialized = false; - private bool tier3Initialized = false; - private bool tierInitError = false; - private Hook updateHook; private Hook freeHook; private Hook destroyHook; private Thread? frameworkUpdateThread; - /// - /// Initializes a new instance of the class. - /// - internal Framework() + [ServiceManager.ServiceConstructor] + private Framework(GameGui gameGui, GameNetwork gameNetwork, SigScanner sigScanner) { this.Address = new FrameworkAddressResolver(); - this.Address.Setup(); + this.Address.Setup(sigScanner); this.updateHook = new Hook(this.Address.TickAddress, this.HandleFrameworkUpdate); this.freeHook = new Hook(this.Address.FreeAddress, this.HandleFrameworkFree); this.destroyHook = new Hook(this.Address.DestroyAddress, this.HandleFrameworkDestroy); + + gameGui.Enable(); + gameNetwork.Enable(); + + this.updateHook.Enable(); + this.freeHook.Enable(); + this.destroyHook.Enable(); } /// @@ -123,20 +125,6 @@ namespace Dalamud.Game /// internal bool DispatchUpdateEvents { get; set; } = true; - /// - /// Enable this module. - /// - public void Enable() - { - Service.Set(); - Service.Get().Enable(); - Service.Get().Enable(); - - this.updateHook.Enable(); - this.freeHook.Enable(); - this.destroyHook.Enable(); - } - /// /// Run given function right away if this function has been called from game's Framework.Update thread, or otherwise run on next Framework.Update call. /// @@ -238,39 +226,21 @@ namespace Dalamud.Game private bool HandleFrameworkUpdate(IntPtr framework) { - // If any of the tier loads failed, just go to the original code. - if (this.tierInitError) - goto original; - this.frameworkUpdateThread ??= Thread.CurrentThread; - var dalamud = Service.Get(); - - // If this is the first time we are running this loop, we need to init Dalamud subsystems synchronously - if (!this.tier2Initialized) - { - this.tier2Initialized = dalamud.LoadTier2(); - if (!this.tier2Initialized) - this.tierInitError = true; - - goto original; - } - - // Plugins expect the interface to be available and ready, so we need to wait with plugins until we have init'd ImGui - if (!this.tier3Initialized && Service.GetNullable()?.IsReady == true) - { - this.tier3Initialized = dalamud.LoadTier3(); - if (!this.tier3Initialized) - this.tierInitError = true; - - goto original; - } + ThreadSafety.MarkMainThread(); try { - Service.Get().UpdateQueue(); - Service.Get().UpdateQueue(); - Service.Get().UpdateQueue(); + var chatGui = Service.GetNullable(); + var toastGui = Service.GetNullable(); + var gameNetwork = Service.GetNullable(); + if (chatGui == null || toastGui == null || gameNetwork == null) + goto original; + + chatGui.UpdateQueue(); + toastGui.UpdateQueue(); + gameNetwork.UpdateQueue(); } catch (Exception ex) { diff --git a/Dalamud/Game/Gui/ChatGui.cs b/Dalamud/Game/Gui/ChatGui.cs index 58b9dfebd..1b798a6e5 100644 --- a/Dalamud/Game/Gui/ChatGui.cs +++ b/Dalamud/Game/Gui/ChatGui.cs @@ -21,6 +21,7 @@ namespace Dalamud.Game.Gui /// [PluginInterface] [InterfaceVersion("1.0")] + [ServiceManager.BlockingEarlyLoadedService] public sealed class ChatGui : IDisposable { private readonly ChatGuiAddressResolver address; @@ -34,14 +35,11 @@ namespace Dalamud.Game.Gui private IntPtr baseAddress = IntPtr.Zero; - /// - /// Initializes a new instance of the class. - /// - /// The base address of the ChatManager. - internal ChatGui() + [ServiceManager.ServiceConstructor] + private ChatGui(SigScanner sigScanner) { this.address = new ChatGuiAddressResolver(); - this.address.Setup(); + this.address.Setup(sigScanner); this.printMessageHook = new Hook(this.address.PrintMessage, this.HandlePrintMessageDetour); this.populateItemLinkHook = new Hook(this.address.PopulateItemLinkObject, this.HandlePopulateItemLinkDetour); diff --git a/Dalamud/Game/Gui/ContextMenus/ContextMenu.cs b/Dalamud/Game/Gui/ContextMenus/ContextMenu.cs index 90917fd0d..c8a8a8756 100644 --- a/Dalamud/Game/Gui/ContextMenus/ContextMenu.cs +++ b/Dalamud/Game/Gui/ContextMenus/ContextMenu.cs @@ -24,6 +24,7 @@ namespace Dalamud.Game.Gui.ContextMenus /// [PluginInterface] [InterfaceVersion("1.0")] + [ServiceManager.BlockingEarlyLoadedService] public sealed class ContextMenu : IDisposable { private const int MaxContextMenuItemsPerContextMenu = 32; @@ -47,13 +48,11 @@ namespace Dalamud.Game.Gui.ContextMenus private OpenSubContextMenuItem? selectedOpenSubContextMenuItem; private ContextMenuOpenedArgs? currentContextMenuOpenedArgs; - /// - /// Initializes a new instance of the class. - /// - public ContextMenu() + [ServiceManager.ServiceConstructor] + private ContextMenu(SigScanner sigScanner) { this.Address = new ContextMenuAddressResolver(); - this.Address.Setup(); + this.Address.Setup(sigScanner); unsafe { diff --git a/Dalamud/Game/Gui/Dtr/DtrBar.cs b/Dalamud/Game/Gui/Dtr/DtrBar.cs index fce98c8d0..13c9c3324 100644 --- a/Dalamud/Game/Gui/Dtr/DtrBar.cs +++ b/Dalamud/Game/Gui/Dtr/DtrBar.cs @@ -17,6 +17,7 @@ namespace Dalamud.Game.Gui.Dtr /// [PluginInterface] [InterfaceVersion("1.0")] + [ServiceManager.BlockingEarlyLoadedService] public sealed unsafe class DtrBar : IDisposable { private const uint BaseNodeId = 1000; @@ -24,13 +25,10 @@ namespace Dalamud.Game.Gui.Dtr private List entries = new(); private uint runningNodeIds = BaseNodeId; - /// - /// Initializes a new instance of the class. - /// - internal DtrBar() + [ServiceManager.ServiceConstructor] + private DtrBar(DalamudConfiguration configuration) { Service.Get().Update += this.Update; - var configuration = Service.Get(); configuration.DtrOrder ??= new List(); configuration.DtrIgnore ??= new List(); diff --git a/Dalamud/Game/Gui/FlyText/FlyTextGui.cs b/Dalamud/Game/Gui/FlyText/FlyTextGui.cs index 779e6c66f..ba70e891e 100644 --- a/Dalamud/Game/Gui/FlyText/FlyTextGui.cs +++ b/Dalamud/Game/Gui/FlyText/FlyTextGui.cs @@ -16,6 +16,7 @@ namespace Dalamud.Game.Gui.FlyText /// [PluginInterface] [InterfaceVersion("1.0")] + [ServiceManager.BlockingEarlyLoadedService] public sealed class FlyTextGui : IDisposable { /// @@ -28,13 +29,11 @@ namespace Dalamud.Game.Gui.FlyText /// private readonly Hook createFlyTextHook; - /// - /// Initializes a new instance of the class. - /// - internal FlyTextGui() + [ServiceManager.ServiceConstructor] + private FlyTextGui(SigScanner sigScanner) { this.Address = new FlyTextGuiAddressResolver(); - this.Address.Setup(); + this.Address.Setup(sigScanner); this.addFlyTextNative = Marshal.GetDelegateForFunctionPointer(this.Address.AddFlyText); this.createFlyTextHook = new Hook(this.Address.CreateFlyText, this.CreateFlyTextDetour); diff --git a/Dalamud/Game/Gui/GameGui.cs b/Dalamud/Game/Gui/GameGui.cs index 43a372528..1d7a09861 100644 --- a/Dalamud/Game/Gui/GameGui.cs +++ b/Dalamud/Game/Gui/GameGui.cs @@ -27,6 +27,7 @@ namespace Dalamud.Game.Gui /// [PluginInterface] [InterfaceVersion("1.0")] + [ServiceManager.BlockingEarlyLoadedService] public sealed unsafe class GameGui : IDisposable { private readonly GameGuiAddressResolver address; @@ -46,14 +47,11 @@ namespace Dalamud.Game.Gui private GetUIMapObjectDelegate getUIMapObject; private OpenMapWithFlagDelegate openMapWithFlag; - /// - /// Initializes a new instance of the class. - /// This class is responsible for many aspects of interacting with the native game UI. - /// - internal GameGui() + [ServiceManager.ServiceConstructor] + private GameGui(SigScanner sigScanner) { this.address = new GameGuiAddressResolver(); - this.address.Setup(); + this.address.Setup(sigScanner); Log.Verbose("===== G A M E G U I ====="); Log.Verbose($"GameGuiManager address 0x{this.address.BaseAddress.ToInt64():X}"); @@ -62,13 +60,6 @@ namespace Dalamud.Game.Gui Log.Verbose($"HandleItemOut address 0x{this.address.HandleItemOut.ToInt64():X}"); Log.Verbose($"HandleImm address 0x{this.address.HandleImm.ToInt64():X}"); - Service.Set(); - Service.Set(); - Service.Set(); - Service.Set(); - Service.Set(); - Service.Set(); - this.setGlobalBgmHook = new Hook(this.address.SetGlobalBgm, this.HandleSetGlobalBgmDetour); this.handleItemHoverHook = new Hook(this.address.HandleItemHover, this.HandleItemHoverDetour); diff --git a/Dalamud/Game/Gui/Internal/DalamudIME.cs b/Dalamud/Game/Gui/Internal/DalamudIME.cs index 419b01de9..e0301b8b8 100644 --- a/Dalamud/Game/Gui/Internal/DalamudIME.cs +++ b/Dalamud/Game/Gui/Internal/DalamudIME.cs @@ -18,6 +18,7 @@ namespace Dalamud.Game.Gui.Internal /// /// This class handles IME for non-English users. /// + [ServiceManager.EarlyLoadedService] internal unsafe class DalamudIME : IDisposable { private static readonly ModuleLog Log = new("IME"); @@ -32,7 +33,9 @@ namespace Dalamud.Game.Gui.Internal /// /// Initializes a new instance of the class. /// - internal DalamudIME() + /// Tag. + [ServiceManager.ServiceConstructor] + private DalamudIME() { } diff --git a/Dalamud/Game/Gui/PartyFinder/PartyFinderGui.cs b/Dalamud/Game/Gui/PartyFinder/PartyFinderGui.cs index 140256b4c..5b85588cc 100644 --- a/Dalamud/Game/Gui/PartyFinder/PartyFinderGui.cs +++ b/Dalamud/Game/Gui/PartyFinder/PartyFinderGui.cs @@ -15,6 +15,7 @@ namespace Dalamud.Game.Gui.PartyFinder /// [PluginInterface] [InterfaceVersion("1.0")] + [ServiceManager.BlockingEarlyLoadedService] public sealed class PartyFinderGui : IDisposable { private readonly PartyFinderAddressResolver address; @@ -25,10 +26,12 @@ namespace Dalamud.Game.Gui.PartyFinder /// /// Initializes a new instance of the class. /// - internal PartyFinderGui() + /// Tag. + [ServiceManager.ServiceConstructor] + private PartyFinderGui(SigScanner sigScanner) { this.address = new PartyFinderAddressResolver(); - this.address.Setup(); + this.address.Setup(sigScanner); this.memory = Marshal.AllocHGlobal(PartyFinderPacket.PacketSize); diff --git a/Dalamud/Game/Gui/Toast/ToastGui.cs b/Dalamud/Game/Gui/Toast/ToastGui.cs index 1f773def8..3cafffe32 100644 --- a/Dalamud/Game/Gui/Toast/ToastGui.cs +++ b/Dalamud/Game/Gui/Toast/ToastGui.cs @@ -14,6 +14,7 @@ namespace Dalamud.Game.Gui.Toast /// [PluginInterface] [InterfaceVersion("1.0")] + [ServiceManager.BlockingEarlyLoadedService] public sealed partial class ToastGui : IDisposable { private const uint QuestToastCheckmarkMagic = 60081; @@ -31,10 +32,12 @@ namespace Dalamud.Game.Gui.Toast /// /// Initializes a new instance of the class. /// - internal ToastGui() + /// Tag. + [ServiceManager.ServiceConstructor] + private ToastGui(SigScanner sigScanner) { this.address = new ToastGuiAddressResolver(); - this.address.Setup(); + this.address.Setup(sigScanner); this.showNormalToastHook = new Hook(this.address.ShowNormalToast, new ShowNormalToastDelegate(this.HandleNormalToastDetour)); this.showQuestToastHook = new Hook(this.address.ShowQuestToast, new ShowQuestToastDelegate(this.HandleQuestToastDetour)); diff --git a/Dalamud/Game/Internal/AntiDebug.cs b/Dalamud/Game/Internal/AntiDebug.cs index 7cac7b7dc..284732a26 100644 --- a/Dalamud/Game/Internal/AntiDebug.cs +++ b/Dalamud/Game/Internal/AntiDebug.cs @@ -1,6 +1,6 @@ using System; using System.Collections.Generic; - +using Dalamud.Configuration.Internal; using Serilog; namespace Dalamud.Game.Internal @@ -8,22 +8,19 @@ namespace Dalamud.Game.Internal /// /// This class disables anti-debug functionality in the game client. /// + [ServiceManager.EarlyLoadedService] internal sealed partial class AntiDebug { private readonly byte[] nop = new byte[] { 0x31, 0xC0, 0x90, 0x90, 0x90, 0x90 }; private byte[] original; private IntPtr debugCheckAddress; - /// - /// Initializes a new instance of the class. - /// - public AntiDebug() + [ServiceManager.ServiceConstructor] + private AntiDebug(SigScanner sigScanner) { - var scanner = Service.Get(); - try { - this.debugCheckAddress = scanner.ScanText("FF 15 ?? ?? ?? ?? 85 C0 74 11 41"); + this.debugCheckAddress = sigScanner.ScanText("FF 15 ?? ?? ?? ?? 85 C0 74 11 41"); } catch (KeyNotFoundException) { @@ -31,6 +28,16 @@ namespace Dalamud.Game.Internal } Log.Verbose($"Debug check address 0x{this.debugCheckAddress.ToInt64():X}"); + + if (!this.IsEnabled) + { +#if DEBUG + this.Enable(); +#else + if (Service.Get().IsAntiAntiDebugEnabled) + this.Enable(); +#endif + } } /// diff --git a/Dalamud/Game/Internal/DXGI/SwapChainVtableResolver.cs b/Dalamud/Game/Internal/DXGI/SwapChainVtableResolver.cs index 78abae0f0..502390ca7 100644 --- a/Dalamud/Game/Internal/DXGI/SwapChainVtableResolver.cs +++ b/Dalamud/Game/Internal/DXGI/SwapChainVtableResolver.cs @@ -4,6 +4,7 @@ using System.Diagnostics; using System.Runtime.InteropServices; using Dalamud.Game.Internal.DXGI.Definitions; +using FFXIVClientStructs.FFXIV.Client.Graphics.Kernel; using Serilog; namespace Dalamud.Game.Internal.DXGI @@ -30,9 +31,28 @@ namespace Dalamud.Game.Internal.DXGI /// protected override unsafe void Setup64Bit(SigScanner sig) { - var kernelDev = FFXIVClientStructs.FFXIV.Client.Graphics.Kernel.Device.Instance(); + Device* kernelDev; + SwapChain* swapChain; + void* dxgiSwapChain; - var scVtbl = GetVTblAddresses(new IntPtr(kernelDev->SwapChain->DXGISwapChain), Enum.GetValues(typeof(IDXGISwapChainVtbl)).Length); + while (true) + { + kernelDev = Device.Instance(); + if (kernelDev == null) + continue; + + swapChain = kernelDev->SwapChain; + if (swapChain == null) + continue; + + dxgiSwapChain = swapChain->DXGISwapChain; + if (dxgiSwapChain == null) + continue; + + break; + } + + var scVtbl = GetVTblAddresses(new IntPtr(dxgiSwapChain), Enum.GetValues(typeof(IDXGISwapChainVtbl)).Length); this.Present = scVtbl[(int)IDXGISwapChainVtbl.Present]; diff --git a/Dalamud/Game/Internal/DalamudAtkTweaks.cs b/Dalamud/Game/Internal/DalamudAtkTweaks.cs index 0fede34c9..8a0c1603e 100644 --- a/Dalamud/Game/Internal/DalamudAtkTweaks.cs +++ b/Dalamud/Game/Internal/DalamudAtkTweaks.cs @@ -23,6 +23,7 @@ namespace Dalamud.Game.Internal /// /// This class implements in-game Dalamud options in the in-game System menu. /// + [ServiceManager.EarlyLoadedService] internal sealed unsafe partial class DalamudAtkTweaks { private readonly AtkValueChangeType atkValueChangeType; @@ -37,13 +38,9 @@ namespace Dalamud.Game.Internal private readonly string locDalamudPlugins; private readonly string locDalamudSettings; - /// - /// Initializes a new instance of the class. - /// - public DalamudAtkTweaks() + [ServiceManager.ServiceConstructor] + private DalamudAtkTweaks(SigScanner sigScanner) { - var sigScanner = Service.Get(); - var openSystemMenuAddress = sigScanner.ScanText("E8 ?? ?? ?? ?? 32 C0 4C 8B AC 24 ?? ?? ?? ?? 48 8B 8D ?? ?? ?? ??"); this.hookAgentHudOpenSystemMenu = new Hook(openSystemMenuAddress, this.AgentHudOpenSystemMenuDetour); diff --git a/Dalamud/Game/Libc/LibcFunction.cs b/Dalamud/Game/Libc/LibcFunction.cs index 3c2361277..b51277232 100644 --- a/Dalamud/Game/Libc/LibcFunction.cs +++ b/Dalamud/Game/Libc/LibcFunction.cs @@ -12,19 +12,18 @@ namespace Dalamud.Game.Libc /// [PluginInterface] [InterfaceVersion("1.0")] + [ServiceManager.BlockingEarlyLoadedService] public sealed class LibcFunction { private readonly LibcFunctionAddressResolver address; private readonly StdStringFromCStringDelegate stdStringCtorCString; private readonly StdStringDeallocateDelegate stdStringDeallocate; - /// - /// Initializes a new instance of the class. - /// - public LibcFunction() + [ServiceManager.ServiceConstructor] + private LibcFunction(SigScanner sigScanner) { this.address = new LibcFunctionAddressResolver(); - this.address.Setup(); + this.address.Setup(sigScanner); this.stdStringCtorCString = Marshal.GetDelegateForFunctionPointer(this.address.StdStringFromCstring); this.stdStringDeallocate = Marshal.GetDelegateForFunctionPointer(this.address.StdStringDeallocate); diff --git a/Dalamud/Game/Network/GameNetwork.cs b/Dalamud/Game/Network/GameNetwork.cs index 039fce90c..3c22d3077 100644 --- a/Dalamud/Game/Network/GameNetwork.cs +++ b/Dalamud/Game/Network/GameNetwork.cs @@ -14,6 +14,7 @@ namespace Dalamud.Game.Network /// [PluginInterface] [InterfaceVersion("1.0")] + [ServiceManager.BlockingEarlyLoadedService] public sealed class GameNetwork : IDisposable { private readonly GameNetworkAddressResolver address; @@ -23,13 +24,11 @@ namespace Dalamud.Game.Network private IntPtr baseAddress; - /// - /// Initializes a new instance of the class. - /// - internal GameNetwork() + [ServiceManager.ServiceConstructor] + private GameNetwork(SigScanner sigScanner) { this.address = new GameNetworkAddressResolver(); - this.address.Setup(); + this.address.Setup(sigScanner); Log.Verbose("===== G A M E N E T W O R K ====="); Log.Verbose($"ProcessZonePacketDown address 0x{this.address.ProcessZonePacketDown.ToInt64():X}"); diff --git a/Dalamud/Game/Network/Internal/NetworkHandlers.cs b/Dalamud/Game/Network/Internal/NetworkHandlers.cs index e93d1164b..e39c3b9af 100644 --- a/Dalamud/Game/Network/Internal/NetworkHandlers.cs +++ b/Dalamud/Game/Network/Internal/NetworkHandlers.cs @@ -21,6 +21,7 @@ namespace Dalamud.Game.Network.Internal /// /// This class handles network notifications and uploading market board data. /// + [ServiceManager.EarlyLoadedService] internal class NetworkHandlers { private readonly List marketBoardRequests = new(); @@ -29,14 +30,12 @@ namespace Dalamud.Game.Network.Internal private MarketBoardPurchaseHandler marketBoardPurchaseHandler; - /// - /// Initializes a new instance of the class. - /// - public NetworkHandlers() + [ServiceManager.ServiceConstructor] + private NetworkHandlers(GameNetwork gameNetwork) { this.uploader = new UniversalisMarketBoardUploader(); - Service.Get().NetworkMessage += this.OnNetworkMessage; + gameNetwork.NetworkMessage += this.OnNetworkMessage; } /// diff --git a/Dalamud/Game/Network/Internal/WinSockHandlers.cs b/Dalamud/Game/Network/Internal/WinSockHandlers.cs index 2abe4a326..e2ab0c9bb 100644 --- a/Dalamud/Game/Network/Internal/WinSockHandlers.cs +++ b/Dalamud/Game/Network/Internal/WinSockHandlers.cs @@ -9,14 +9,13 @@ namespace Dalamud.Game.Network.Internal /// /// This class enables TCP optimizations in the game socket for better performance. /// + [ServiceManager.EarlyLoadedService] internal sealed class WinSockHandlers : IDisposable { private Hook ws2SocketHook; - /// - /// Initializes a new instance of the class. - /// - public WinSockHandlers() + [ServiceManager.ServiceConstructor] + private WinSockHandlers() { this.ws2SocketHook = Hook.FromSymbol("ws2_32.dll", "socket", this.OnSocket, true); this.ws2SocketHook?.Enable(); diff --git a/Dalamud/Game/SigScanner.cs b/Dalamud/Game/SigScanner.cs index 7064d211e..130e9e488 100644 --- a/Dalamud/Game/SigScanner.cs +++ b/Dalamud/Game/SigScanner.cs @@ -8,6 +8,7 @@ using System.Runtime.InteropServices; using Dalamud.IoC; using Dalamud.IoC.Internal; +using Dalamud.Utility.Timing; using Newtonsoft.Json; using Serilog; @@ -18,6 +19,7 @@ namespace Dalamud.Game /// [PluginInterface] [InterfaceVersion("1.0")] + [ServiceManager.BlockingEarlyLoadedService] public class SigScanner : IDisposable { private readonly FileInfo? cacheFile; @@ -27,6 +29,38 @@ namespace Dalamud.Game private Dictionary? textCache; + [ServiceManager.ServiceConstructor] + private SigScanner(DalamudStartInfo startInfo) + { + // Initialize the process information. + var cacheDir = new DirectoryInfo(Path.Combine(startInfo.WorkingDirectory!, "cachedSigs")); + if (!cacheDir.Exists) + cacheDir.Create(); + + this.cacheFile = new FileInfo(Path.Combine(cacheDir.FullName, $"{startInfo.GameVersion}.json")); + this.Module = Process.GetCurrentProcess().MainModule!; + this.Is32BitProcess = !Environment.Is64BitProcess; + this.IsCopy = true; + + // Limit the search space to .text section. + this.SetupSearchSpace(this.Module); + + if (this.IsCopy) + this.SetupCopiedSegments(); + + Log.Verbose($"Module base: 0x{this.TextSectionBase.ToInt64():X}"); + Log.Verbose($"Module size: 0x{this.TextSectionSize:X}"); + + if (this.cacheFile != null) + this.Load(); + + // Initialize FFXIVClientStructs function resolver + using (Timings.Start("CS Resolver Init")) + { + FFXIVClientStructs.Resolver.InitializeParallel(new FileInfo(Path.Combine(cacheDir.FullName, $"{startInfo.GameVersion}_cs.json"))); + } + } + /// /// Initializes a new instance of the class using the main module of the current process. /// diff --git a/Dalamud/Game/Text/SeStringHandling/SeStringManager.cs b/Dalamud/Game/Text/SeStringHandling/SeStringManager.cs index 68403b39e..85431e06c 100644 --- a/Dalamud/Game/Text/SeStringHandling/SeStringManager.cs +++ b/Dalamud/Game/Text/SeStringHandling/SeStringManager.cs @@ -12,13 +12,12 @@ namespace Dalamud.Game.Text.SeStringHandling /// [PluginInterface] [InterfaceVersion("1.0")] + [ServiceManager.BlockingEarlyLoadedService] [Obsolete("This class is obsolete. Please use the static methods on SeString instead.")] public sealed class SeStringManager { - /// - /// Initializes a new instance of the class. - /// - internal SeStringManager() + [ServiceManager.ServiceConstructor] + private SeStringManager() { } diff --git a/Dalamud/Hooking/Internal/HookManager.cs b/Dalamud/Hooking/Internal/HookManager.cs index 9cc73da3b..989425949 100644 --- a/Dalamud/Hooking/Internal/HookManager.cs +++ b/Dalamud/Hooking/Internal/HookManager.cs @@ -13,14 +13,13 @@ namespace Dalamud.Hooking.Internal /// /// This class manages the final disposition of hooks, cleaning up any that have not reverted their changes. /// + [ServiceManager.EarlyLoadedService] internal class HookManager : IDisposable { private static readonly ModuleLog Log = new("HM"); - /// - /// Initializes a new instance of the class. - /// - public HookManager() + [ServiceManager.ServiceConstructor] + private HookManager() { } diff --git a/Dalamud/Interface/GameFonts/GameFontManager.cs b/Dalamud/Interface/GameFonts/GameFontManager.cs index 7dc392717..544b15123 100644 --- a/Dalamud/Interface/GameFonts/GameFontManager.cs +++ b/Dalamud/Interface/GameFonts/GameFontManager.cs @@ -4,7 +4,7 @@ using System.Linq; using System.Numerics; using System.Runtime.InteropServices; using System.Text; - +using System.Threading.Tasks; using Dalamud.Data; using Dalamud.Interface.Internal; using Dalamud.Utility.Timing; @@ -17,9 +17,10 @@ namespace Dalamud.Interface.GameFonts /// /// Loads game font for use in ImGui. /// + [ServiceManager.EarlyLoadedService] internal class GameFontManager : IDisposable { - private static readonly string[] FontNames = + private static readonly string?[] FontNames = { null, "AXIS_96", "AXIS_12", "AXIS_14", "AXIS_18", "AXIS_36", @@ -31,8 +32,6 @@ namespace Dalamud.Interface.GameFonts private readonly object syncRoot = new(); - private readonly InterfaceManager interfaceManager; - private readonly FdtReader?[] fdts; private readonly List texturePixels; private readonly Dictionary fonts = new(); @@ -42,40 +41,28 @@ namespace Dalamud.Interface.GameFonts private bool isBetweenBuildFontsAndRightAfterImGuiIoFontsBuild = false; private bool isBuildingAsFallbackFontMode = false; - /// - /// Initializes a new instance of the class. - /// - public GameFontManager() + [ServiceManager.ServiceConstructor] + private GameFontManager(DataManager dataManager) { - var dataManager = Service.Get(); - - using (Timings.Start("Load FDTs")) + using (Timings.Start("Getting fdt data")) { - this.fdts = FontNames.Select(fontName => - { - var fileName = $"common/font/{fontName}.fdt"; - using (Timings.Start($"Loading FDT: {fileName}")) - { - var file = fontName == null ? null : dataManager.GetFile(fileName); - return file == null ? null : new FdtReader(file!.Data); - } - }).ToArray(); + this.fdts = FontNames.Select(fontName => fontName == null ? null : new FdtReader(dataManager.GetFile($"common/font/{fontName}.fdt")!.Data)).ToArray(); } using (Timings.Start("Getting texture data")) { - this.texturePixels = Enumerable.Range(1, 1 + this.fdts.Where(x => x != null).Select(x => x.Glyphs.Select(x => x.TextureFileIndex).Max()).Max()).Select( - x => - { - var fileName = $"common/font/font{x}.tex"; - using (Timings.Start($"Get tex: {fileName}")) - { - return dataManager.GameData.GetFile(fileName)!.ImageData; - } - }).ToList(); + var texTasks = Enumerable + .Range(1, 1 + this.fdts + .Where(x => x != null) + .Select(x => x.Glyphs.Select(y => y.TextureFileIndex).Max()) + .Max()) + .Select(x => dataManager.GetFile($"common/font/font{x}.tex")!) + .Select(x => new Task(() => x.ImageData!)) + .ToArray(); + foreach (var task in texTasks) + task.Start(); + this.texturePixels = texTasks.Select(x => x.GetAwaiter().GetResult()).ToList(); } - - this.interfaceManager = Service.Get(); } /// @@ -183,6 +170,7 @@ namespace Dalamud.Interface.GameFonts /// Handle to game font that may or may not be ready yet. public GameFontHandle NewFontRef(GameFontStyle style) { + var interfaceManager = Service.Get(); var needRebuild = false; lock (this.syncRoot) @@ -193,7 +181,7 @@ namespace Dalamud.Interface.GameFonts needRebuild = !this.fonts.ContainsKey(style); if (needRebuild) { - if (Service.Get().IsBuildingFontsBeforeAtlasBuild && this.isBetweenBuildFontsAndRightAfterImGuiIoFontsBuild) + 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); @@ -201,7 +189,7 @@ namespace Dalamud.Interface.GameFonts else { Log.Information("[GameFontManager] NewFontRef: Calling RebuildFonts because {0} has been requested.", style.ToString()); - this.interfaceManager.RebuildFonts(); + interfaceManager.RebuildFonts(); } } @@ -294,10 +282,11 @@ namespace Dalamud.Interface.GameFonts /// public unsafe void AfterBuildFonts() { + var interfaceManager = Service.Get(); var ioFonts = ImGui.GetIO().Fonts; ioFonts.GetTexDataAsRGBA32(out byte* pixels8, out var width, out var height); var pixels32 = (uint*)pixels8; - var fontGamma = this.interfaceManager.FontGamma; + var fontGamma = interfaceManager.FontGamma; foreach (var (style, font) in this.fonts) { diff --git a/Dalamud/Interface/Internal/DalamudCommands.cs b/Dalamud/Interface/Internal/DalamudCommands.cs index e81d9f5a4..86132eecb 100644 --- a/Dalamud/Interface/Internal/DalamudCommands.cs +++ b/Dalamud/Interface/Internal/DalamudCommands.cs @@ -18,22 +18,12 @@ namespace Dalamud.Interface.Internal /// /// Class handling Dalamud core commands. /// + [ServiceManager.EarlyLoadedService] internal class DalamudCommands { - /// - /// Initializes a new instance of the class. - /// - public DalamudCommands() + [ServiceManager.ServiceConstructor] + private DalamudCommands(CommandManager commandManager) { - } - - /// - /// Register all command handlers with the Dalamud instance. - /// - public void SetupCommands() - { - var commandManager = Service.Get(); - commandManager.AddHandler("/xldclose", new CommandInfo(this.OnUnloadCommand) { HelpMessage = Loc.Localize("DalamudUnloadHelp", "Unloads XIVLauncher in-game addon."), diff --git a/Dalamud/Interface/Internal/DalamudInterface.cs b/Dalamud/Interface/Internal/DalamudInterface.cs index cd314a756..afe106367 100644 --- a/Dalamud/Interface/Internal/DalamudInterface.cs +++ b/Dalamud/Interface/Internal/DalamudInterface.cs @@ -35,6 +35,7 @@ namespace Dalamud.Interface.Internal /// /// This plugin implements all of the Dalamud interface separately, to allow for reloading of the interface and rapid prototyping. /// + [ServiceManager.EarlyLoadedService] internal class DalamudInterface : IDisposable { private static readonly ModuleLog Log = new("DUI"); @@ -75,14 +76,9 @@ namespace Dalamud.Interface.Internal private bool isImGuiTestWindowsInMonospace = false; private bool isImGuiDrawMetricsWindow = false; - /// - /// Initializes a new instance of the class. - /// - public DalamudInterface() + [ServiceManager.ServiceConstructor] + private DalamudInterface(Dalamud dalamud, DalamudConfiguration configuration, InterfaceManager interfaceManager) { - var configuration = Service.Get(); - var interfaceManager = Service.Get(); - this.WindowSystem = new WindowSystem("DalamudCore"); this.changelogWindow = new ChangelogWindow() { IsOpen = false }; @@ -123,7 +119,6 @@ namespace Dalamud.Interface.Internal this.isImGuiDrawDevMenu = this.isImGuiDrawDevMenu || configuration.DevBarOpenAtStartup; interfaceManager.Draw += this.OnDraw; - var dalamud = Service.Get(); var logoTex = interfaceManager.LoadImage(Path.Combine(dalamud.AssetDirectory.FullName, "UIRes", "logo.png")); diff --git a/Dalamud/Interface/Internal/InterfaceManager.cs b/Dalamud/Interface/Internal/InterfaceManager.cs index a4a477f25..2eab121c9 100644 --- a/Dalamud/Interface/Internal/InterfaceManager.cs +++ b/Dalamud/Interface/Internal/InterfaceManager.cs @@ -7,7 +7,7 @@ using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Text; using System.Threading; - +using System.Threading.Tasks; using Dalamud.Configuration.Internal; using Dalamud.Game; using Dalamud.Game.ClientState.GamePad; @@ -44,6 +44,7 @@ namespace Dalamud.Interface.Internal /// /// This class manages interaction with the ImGui interface. /// + [ServiceManager.BlockingEarlyLoadedService] internal class InterfaceManager : IDisposable { private const float MinimumFallbackFontSizePt = 9.6f; // Game's minimum AXIS font size @@ -58,33 +59,27 @@ namespace Dalamud.Interface.Internal private readonly HashSet glyphRequests = new(); private readonly Dictionary loadedFontInfo = new(); - private readonly Hook presentHook; - private readonly Hook resizeBuffersHook; - private readonly Hook setCursorHook; - private readonly ManualResetEvent fontBuildSignal; private readonly SwapChainVtableResolver address; + private readonly TaskCompletionSource sceneInitializeTaskCompletionSource = new(); private RawDX11Scene? scene; + private Hook? presentHook; + private Hook? resizeBuffersHook; + private Hook? setCursorHook; + // can't access imgui IO before first present call private bool lastWantCapture = false; private bool isRebuildingFonts = false; private bool isFallbackFontMode = false; - /// - /// Initializes a new instance of the class. - /// - public InterfaceManager() + [ServiceManager.ServiceConstructor] + private InterfaceManager() { - Service.Set(); - - var scanner = Service.Get(); - this.fontBuildSignal = new ManualResetEvent(false); this.address = new SwapChainVtableResolver(); - this.address.Setup(scanner); try { @@ -106,16 +101,12 @@ namespace Dalamud.Interface.Internal Log.Error(e, "RTSS Free failed"); } - this.setCursorHook = Hook.FromSymbol("user32.dll", "SetCursor", this.SetCursorDetour, true); - this.presentHook = new Hook(this.address.Present, this.PresentDetour); - this.resizeBuffersHook = new Hook(this.address.ResizeBuffers, this.ResizeBuffersDetour); - - var setCursorAddress = this.setCursorHook?.Address ?? IntPtr.Zero; - - Log.Verbose("===== S W A P C H A I N ====="); - Log.Verbose($"SetCursor address 0x{setCursorAddress.ToInt64():X}"); - Log.Verbose($"Present address 0x{this.presentHook.Address.ToInt64():X}"); - Log.Verbose($"ResizeBuffers address 0x{this.resizeBuffersHook.Address.ToInt64():X}"); + Task.Run(async () => + { + var framework = await Service.GetAsync(); + var sigScanner = await Service.GetAsync(); + await framework.RunOnFrameworkThread(() => this.Enable(sigScanner)); + }); } [UnmanagedFunctionPointer(CallingConvention.ThisCall)] @@ -129,6 +120,11 @@ namespace Dalamud.Interface.Internal private delegate void InstallRTSSHook(); + /// + /// Gets a task that gets completed when scene gets initialized. + /// + public Task SceneInitializeTask => this.sceneInitializeTaskCompletionSource.Task; + /// /// This event gets called each frame to facilitate ImGui drawing. /// @@ -259,34 +255,6 @@ namespace Dalamud.Interface.Internal /// public bool IsBuildingFontsBeforeAtlasBuild => this.isRebuildingFonts && !this.fontBuildSignal.WaitOne(0); - /// - /// Enable this module. - /// - public void Enable() - { - this.setCursorHook?.Enable(); - this.presentHook.Enable(); - this.resizeBuffersHook.Enable(); - - try - { - if (!string.IsNullOrEmpty(this.rtssPath)) - { - NativeFunctions.LoadLibraryW(this.rtssPath); - var rtssModule = NativeFunctions.GetModuleHandleW("RTSSHooks64.dll"); - var installAddr = NativeFunctions.GetProcAddress(rtssModule, "InstallRTSSHook"); - - Log.Debug("Installing RTSS hook"); - Marshal.GetDelegateForFunctionPointer(installAddr).Invoke(); - Log.Debug("RTSS hook OK!"); - } - } - catch (Exception ex) - { - Log.Error(ex, "Could not reload RTSS"); - } - } - /// /// Dispose of managed and unmanaged resources. /// @@ -303,8 +271,8 @@ namespace Dalamud.Interface.Internal this.scene?.Dispose(); this.setCursorHook?.Dispose(); - this.presentHook.Dispose(); - this.resizeBuffersHook.Dispose(); + this.presentHook?.Dispose(); + this.resizeBuffersHook?.Dispose(); } #nullable enable @@ -480,9 +448,11 @@ namespace Dalamud.Interface.Internal try { this.scene = new RawDX11Scene(swapChain); + this.sceneInitializeTaskCompletionSource.SetResult(); } catch (DllNotFoundException ex) { + this.sceneInitializeTaskCompletionSource.SetException(ex); Log.Error(ex, "Could not load ImGui dependencies."); var res = PInvoke.User32.MessageBox( @@ -1003,6 +973,43 @@ namespace Dalamud.Interface.Internal } } + private void Enable(SigScanner sigScanner) + { + this.address.Setup(sigScanner); + this.setCursorHook = Hook.FromSymbol("user32.dll", "SetCursor", this.SetCursorDetour, true); + this.presentHook = new Hook(this.address.Present, this.PresentDetour); + this.resizeBuffersHook = new Hook(this.address.ResizeBuffers, this.ResizeBuffersDetour); + + var setCursorAddress = this.setCursorHook?.Address ?? IntPtr.Zero; + + Log.Verbose("===== S W A P C H A I N ====="); + Log.Verbose($"SetCursor address 0x{setCursorAddress.ToInt64():X}"); + Log.Verbose($"Present address 0x{this.presentHook.Address.ToInt64():X}"); + Log.Verbose($"ResizeBuffers address 0x{this.resizeBuffersHook.Address.ToInt64():X}"); + + this.setCursorHook?.Enable(); + this.presentHook.Enable(); + this.resizeBuffersHook.Enable(); + + try + { + if (!string.IsNullOrEmpty(this.rtssPath)) + { + NativeFunctions.LoadLibraryW(this.rtssPath); + var rtssModule = NativeFunctions.GetModuleHandleW("RTSSHooks64.dll"); + var installAddr = NativeFunctions.GetProcAddress(rtssModule, "InstallRTSSHook"); + + Log.Debug("Installing RTSS hook"); + Marshal.GetDelegateForFunctionPointer(installAddr).Invoke(); + Log.Debug("RTSS hook OK!"); + } + } + catch (Exception ex) + { + Log.Error(ex, "Could not reload RTSS"); + } + } + private void Disable() { this.setCursorHook?.Disable(); @@ -1094,6 +1101,9 @@ namespace Dalamud.Interface.Internal var gamepadState = Service.GetNullable(); var keyState = Service.GetNullable(); + if (dalamudInterface == null || gamepadState == null || keyState == null) + return; + // fix for keys in game getting stuck, if you were holding a game key (like run) // and then clicked on an imgui textbox - imgui would swallow the keyup event, // so the game would think the key remained pressed continuously until you left diff --git a/Dalamud/Interface/Internal/Notifications/NotificationManager.cs b/Dalamud/Interface/Internal/Notifications/NotificationManager.cs index aeef3c934..c68017caf 100644 --- a/Dalamud/Interface/Internal/Notifications/NotificationManager.cs +++ b/Dalamud/Interface/Internal/Notifications/NotificationManager.cs @@ -13,6 +13,7 @@ namespace Dalamud.Interface.Internal.Notifications /// Class handling notifications/toasts in ImGui. /// Ported from https://github.com/patrickcjk/imgui-notify. /// + [ServiceManager.EarlyLoadedService] internal class NotificationManager { /// @@ -54,6 +55,11 @@ namespace Dalamud.Interface.Internal.Notifications private readonly List notifications = new(); + [ServiceManager.ServiceConstructor] + private NotificationManager() + { + } + /// /// Add a notification to the notification queue. /// diff --git a/Dalamud/Interface/Internal/Windows/DataWindow.cs b/Dalamud/Interface/Internal/Windows/DataWindow.cs index bf1a13d01..c9e76e18a 100644 --- a/Dalamud/Interface/Internal/Windows/DataWindow.cs +++ b/Dalamud/Interface/Internal/Windows/DataWindow.cs @@ -1485,7 +1485,7 @@ namespace Dalamud.Interface.Internal.Windows ImGuiHelpers.ScaledDummy(20); // Needed to init the task tracker, if we're not on a debug build - var tracker = Service.GetNullable() ?? Service.Set(); + Service.Get().Enable(); for (var i = 0; i < TaskTracker.Tasks.Count; i++) { diff --git a/Dalamud/Interface/Internal/Windows/PluginImageCache.cs b/Dalamud/Interface/Internal/Windows/PluginImageCache.cs index 2e714a8c8..d1081600c 100644 --- a/Dalamud/Interface/Internal/Windows/PluginImageCache.cs +++ b/Dalamud/Interface/Internal/Windows/PluginImageCache.cs @@ -60,6 +60,8 @@ namespace Dalamud.Interface.Internal.Windows var dalamud = Service.Get(); var interfaceManager = Service.Get(); + interfaceManager.SceneInitializeTask.Wait(); + this.DefaultIcon = interfaceManager.LoadImage(Path.Combine(dalamud.AssetDirectory.FullName, "UIRes", "defaultIcon.png"))!; this.TroubleIcon = interfaceManager.LoadImage(Path.Combine(dalamud.AssetDirectory.FullName, "UIRes", "troubleIcon.png"))!; this.UpdateIcon = interfaceManager.LoadImage(Path.Combine(dalamud.AssetDirectory.FullName, "UIRes", "updateIcon.png"))!; diff --git a/Dalamud/Interface/Internal/Windows/SettingsWindow.cs b/Dalamud/Interface/Internal/Windows/SettingsWindow.cs index 2dd5b2c57..86152d1e4 100644 --- a/Dalamud/Interface/Internal/Windows/SettingsWindow.cs +++ b/Dalamud/Interface/Internal/Windows/SettingsWindow.cs @@ -33,6 +33,7 @@ namespace Dalamud.Interface.Internal.Windows private XivChatType dalamudMessagesChatType; + private bool doWaitForPluginsOnStartup; private bool doCfTaskBarFlash; private bool doCfChatMessage; private bool doMbCollect; @@ -90,6 +91,7 @@ namespace Dalamud.Interface.Internal.Windows this.dalamudMessagesChatType = configuration.GeneralChatType; + this.doWaitForPluginsOnStartup = configuration.IsResumeGameAfterPluginLoad; this.doCfTaskBarFlash = configuration.DutyFinderTaskbarFlash; this.doCfChatMessage = configuration.DutyFinderChatMessage; this.doMbCollect = configuration.IsMbCollect; @@ -261,6 +263,9 @@ namespace Dalamud.Interface.Internal.Windows ImGuiHelpers.ScaledDummy(5); + ImGui.Checkbox(Loc.Localize("DalamudSettingsWaitForPluginsOnStartup", "Wait for plugins before game loads"), ref this.doWaitForPluginsOnStartup); + ImGui.TextColored(ImGuiColors.DalamudGrey, Loc.Localize("DalamudSettingsWaitForPluginsOnStartupHint", "Do not let the game load, until plugins are loaded.")); + ImGui.Checkbox(Loc.Localize("DalamudSettingsFlash", "Flash FFXIV window on duty pop"), ref this.doCfTaskBarFlash); ImGui.TextColored(ImGuiColors.DalamudGrey, Loc.Localize("DalamudSettingsFlashHint", "Flash the FFXIV window in your task bar when a duty is ready.")); @@ -916,6 +921,7 @@ namespace Dalamud.Interface.Internal.Windows configuration.GeneralChatType = this.dalamudMessagesChatType; + configuration.IsResumeGameAfterPluginLoad = this.doWaitForPluginsOnStartup; configuration.DutyFinderTaskbarFlash = this.doCfTaskBarFlash; configuration.DutyFinderChatMessage = this.doCfChatMessage; configuration.IsMbCollect = this.doMbCollect; diff --git a/Dalamud/Interface/TitleScreenMenu.cs b/Dalamud/Interface/TitleScreenMenu.cs index 03418d2de..8e00cea3b 100644 --- a/Dalamud/Interface/TitleScreenMenu.cs +++ b/Dalamud/Interface/TitleScreenMenu.cs @@ -12,6 +12,7 @@ namespace Dalamud.Interface /// [PluginInterface] [InterfaceVersion("1.0")] + [ServiceManager.BlockingEarlyLoadedService] public class TitleScreenMenu { /// @@ -21,6 +22,11 @@ namespace Dalamud.Interface private readonly List entries = new(); + [ServiceManager.ServiceConstructor] + private TitleScreenMenu() + { + } + /// /// Gets the list of entries in the title screen menu. /// diff --git a/Dalamud/IoC/Internal/ObjectInstance.cs b/Dalamud/IoC/Internal/ObjectInstance.cs index 7475fcfaf..e92af8c5b 100644 --- a/Dalamud/IoC/Internal/ObjectInstance.cs +++ b/Dalamud/IoC/Internal/ObjectInstance.cs @@ -1,5 +1,6 @@ using System; using System.Reflection; +using System.Threading.Tasks; namespace Dalamud.IoC.Internal { @@ -11,11 +12,12 @@ namespace Dalamud.IoC.Internal /// /// Initializes a new instance of the class. /// - /// The underlying instance. - public ObjectInstance(object instance) + /// Weak reference to the underlying instance. + /// Type of the underlying instance. + public ObjectInstance(Task instanceTask, Type type) { - this.Instance = new WeakReference(instance); - this.Version = instance.GetType().GetCustomAttribute(); + this.InstanceTask = instanceTask; + this.Version = type.GetCustomAttribute(); } /// @@ -26,6 +28,7 @@ namespace Dalamud.IoC.Internal /// /// Gets a reference to the underlying instance. /// - public WeakReference Instance { get; } + /// The underlying instance. + public Task InstanceTask { get; } } } diff --git a/Dalamud/IoC/Internal/ServiceContainer.cs b/Dalamud/IoC/Internal/ServiceContainer.cs index a211107bb..bfc92d271 100644 --- a/Dalamud/IoC/Internal/ServiceContainer.cs +++ b/Dalamud/IoC/Internal/ServiceContainer.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Runtime.Serialization; - +using System.Threading.Tasks; using Dalamud.Logging.Internal; namespace Dalamud.IoC.Internal @@ -17,19 +17,26 @@ namespace Dalamud.IoC.Internal private readonly Dictionary instances = new(); + /// + /// Initializes a new instance of the class. + /// + public ServiceContainer() + { + } + /// /// Register a singleton object of any type into the current IOC container. /// /// The existing instance to register in the container. /// The interface to register. - public void RegisterSingleton(T instance) + public void RegisterSingleton(Task instance) { if (instance == null) { throw new ArgumentNullException(nameof(instance)); } - this.instances[typeof(T)] = new(instance); + this.instances[typeof(T)] = new(instance.ContinueWith(x => new WeakReference(x.Result)), typeof(T)); } /// @@ -38,12 +45,12 @@ namespace Dalamud.IoC.Internal /// The type of object to create. /// Scoped objects to be included in the constructor. /// The created object. - public object? Create(Type objectType, params object[] scopedObjects) + public async Task Create(Type objectType, params object[] scopedObjects) { var ctor = this.FindApplicableCtor(objectType, scopedObjects); if (ctor == null) { - Log.Error("Failed to create {TypeName}, an eligible ctor with satisfiable services could not be found", objectType.FullName); + Log.Error("Failed to create {TypeName}, an eligible ctor with satisfiable services could not be found", objectType.FullName!); return null; } @@ -59,36 +66,37 @@ namespace Dalamud.IoC.Internal if (!versionCheck) { - Log.Error("Failed to create {TypeName}, a RequestedVersion could not be satisfied", objectType.FullName); + Log.Error("Failed to create {TypeName}, a RequestedVersion could not be satisfied", objectType.FullName!); return null; } - var resolvedParams = parameters - .Select(p => - { - var service = this.GetService(p.parameterType, scopedObjects); + var resolvedParams = + await Task.WhenAll( + parameters + .Select(async p => + { + var service = await this.GetService(p.parameterType, scopedObjects); - if (service == null) - { - Log.Error("Requested service type {TypeName} was not available (null)", p.parameterType.FullName); - } + if (service == null) + { + Log.Error("Requested service type {TypeName} was not available (null)", p.parameterType.FullName!); + } - return service; - }) - .ToArray(); + return service; + })); var hasNull = resolvedParams.Any(p => p == null); if (hasNull) { - Log.Error("Failed to create {TypeName}, a requested service type could not be satisfied", objectType.FullName); + Log.Error("Failed to create {TypeName}, a requested service type could not be satisfied", objectType.FullName!); return null; } var instance = FormatterServices.GetUninitializedObject(objectType); - if (!this.InjectProperties(instance, scopedObjects)) + if (!await this.InjectProperties(instance, scopedObjects)) { - Log.Error("Failed to create {TypeName}, a requested property service type could not be satisfied", objectType.FullName); + Log.Error("Failed to create {TypeName}, a requested property service type could not be satisfied", objectType.FullName!); return null; } @@ -105,7 +113,7 @@ namespace Dalamud.IoC.Internal /// The object instance. /// Scoped objects. /// Whether or not the injection was successful. - public bool InjectProperties(object instance, params object[] scopedObjects) + public async Task InjectProperties(object instance, params object[] scopedObjects) { var objectType = instance.GetType(); @@ -121,17 +129,17 @@ namespace Dalamud.IoC.Internal if (!versionCheck) { - Log.Error("Failed to create {TypeName}, a RequestedVersion could not be satisfied", objectType.FullName); + Log.Error("Failed to create {TypeName}, a RequestedVersion could not be satisfied", objectType.FullName!); return false; } foreach (var prop in props) { - var service = this.GetService(prop.propertyInfo.PropertyType, scopedObjects); + var service = await this.GetService(prop.propertyInfo.PropertyType, scopedObjects); if (service == null) { - Log.Error("Requested service type {TypeName} was not available (null)", prop.propertyInfo.PropertyType.FullName); + Log.Error("Requested service type {TypeName} was not available (null)", prop.propertyInfo.PropertyType.FullName!); return false; } @@ -162,14 +170,14 @@ namespace Dalamud.IoC.Internal "Requested version {ReqVersion} does not match the implemented version {ImplVersion} for param type {ParamType}", requiredVersion.Version, declVersion.Version, - parameterType.FullName); + parameterType.FullName!); return false; } - private object? GetService(Type serviceType, object[] scopedObjects) + private async Task GetService(Type serviceType, object[] scopedObjects) { - var singletonService = this.GetService(serviceType); + var singletonService = await this.GetService(serviceType); if (singletonService != null) { return singletonService; @@ -185,15 +193,13 @@ namespace Dalamud.IoC.Internal return scoped; } - private object? GetService(Type serviceType) + private async Task GetService(Type serviceType) { - var hasInstance = this.instances.TryGetValue(serviceType, out var service); - if (hasInstance && service.Instance.IsAlive) - { - return service.Instance.Target; - } + if (!this.instances.TryGetValue(serviceType, out var service)) + return null; - return null; + var instance = await service.InstanceTask; + return instance.Target; } private ConstructorInfo? FindApplicableCtor(Type type, object[] scopedObjects) @@ -224,7 +230,7 @@ namespace Dalamud.IoC.Internal var contains = types.Contains(parameter.ParameterType); if (!contains) { - Log.Error("Failed to validate {TypeName}, unable to find any services that satisfy the type", parameter.ParameterType.FullName); + Log.Error("Failed to validate {TypeName}, unable to find any services that satisfy the type", parameter.ParameterType.FullName!); return false; } } diff --git a/Dalamud/Localization.cs b/Dalamud/Localization.cs index 1d3624bb5..0db7088dc 100644 --- a/Dalamud/Localization.cs +++ b/Dalamud/Localization.cs @@ -5,6 +5,7 @@ using System.Linq; using System.Reflection; using CheapLoc; +using Dalamud.Configuration.Internal; using Serilog; namespace Dalamud @@ -12,6 +13,7 @@ namespace Dalamud /// /// Class handling localization. /// + [ServiceManager.EarlyLoadedService] public class Localization { /// @@ -40,6 +42,16 @@ namespace Dalamud this.assembly = Assembly.GetCallingAssembly(); } + [ServiceManager.ServiceConstructor] + private Localization(Dalamud dalamud, DalamudConfiguration configuration) + : this(Path.Combine(dalamud.AssetDirectory.FullName, "UIRes", "loc", "dalamud"), "dalamud_") + { + if (!string.IsNullOrEmpty(configuration.LanguageOverride)) + this.SetupWithLangCode(configuration.LanguageOverride); + else + this.SetupWithUiCulture(); + } + /// /// Delegate for the event that occurs when the language is changed. /// diff --git a/Dalamud/Logging/Internal/ModuleLog.cs b/Dalamud/Logging/Internal/ModuleLog.cs index 9ec951cad..2a0dd018d 100644 --- a/Dalamud/Logging/Internal/ModuleLog.cs +++ b/Dalamud/Logging/Internal/ModuleLog.cs @@ -5,7 +5,7 @@ namespace Dalamud.Logging.Internal /// /// Class offering various methods to allow for logging in Dalamud modules. /// - internal class ModuleLog + public class ModuleLog { private readonly string moduleName; diff --git a/Dalamud/Logging/Internal/TaskTracker.cs b/Dalamud/Logging/Internal/TaskTracker.cs index 1f465b4a5..354decc55 100644 --- a/Dalamud/Logging/Internal/TaskTracker.cs +++ b/Dalamud/Logging/Internal/TaskTracker.cs @@ -12,6 +12,7 @@ namespace Dalamud.Logging.Internal /// /// Class responsible for tracking asynchronous tasks. /// + [ServiceManager.EarlyLoadedService] internal class TaskTracker : IDisposable { private static readonly ModuleLog Log = new("TT"); @@ -20,16 +21,14 @@ namespace Dalamud.Logging.Internal private static bool clearRequested = false; private MonoMod.RuntimeDetour.Hook? scheduleAndStartHook; + private bool enabled = false; - /// - /// Initializes a new instance of the class. - /// - public TaskTracker() + [ServiceManager.ServiceConstructor] + private TaskTracker() { - this.ApplyPatch(); - - var framework = Service.Get(); - framework.Update += this.FrameworkOnUpdate; +#if DEBUG + this.Enable(); +#endif } /// @@ -102,6 +101,21 @@ namespace Dalamud.Logging.Internal } } + /// + /// Enables TaskTracker. + /// + public void Enable() + { + if (this.enabled) + return; + + this.ApplyPatch(); + + var framework = Service.Get(); + framework.Update += this.FrameworkOnUpdate; + this.enabled = true; + } + /// public void Dispose() { diff --git a/Dalamud/Plugin/DalamudPluginInterface.cs b/Dalamud/Plugin/DalamudPluginInterface.cs index b973ea296..22d1ccd1f 100644 --- a/Dalamud/Plugin/DalamudPluginInterface.cs +++ b/Dalamud/Plugin/DalamudPluginInterface.cs @@ -378,7 +378,7 @@ namespace Dalamud.Plugin realScopedObjects[0] = this; Array.Copy(scopedObjects, 0, realScopedObjects, 1, scopedObjects.Length); - return svcContainer.InjectProperties(instance, realScopedObjects); + return svcContainer.InjectProperties(instance, realScopedObjects).GetAwaiter().GetResult(); } #endregion diff --git a/Dalamud/Plugin/Internal/PluginManager.cs b/Dalamud/Plugin/Internal/PluginManager.cs index d8595f5d5..518c691bd 100644 --- a/Dalamud/Plugin/Internal/PluginManager.cs +++ b/Dalamud/Plugin/Internal/PluginManager.cs @@ -27,6 +27,7 @@ namespace Dalamud.Plugin.Internal; /// /// Class responsible for loading and unloading plugins. /// +[ServiceManager.EarlyLoadedService] internal partial class PluginManager : IDisposable { /// @@ -40,16 +41,17 @@ internal partial class PluginManager : IDisposable private readonly DirectoryInfo devPluginDirectory; private readonly BannedPlugin[] bannedPlugins; - /// - /// Initializes a new instance of the class. - /// - public PluginManager() - { - var startInfo = Service.Get(); - var configuration = Service.Get(); + [ServiceManager.ServiceDependency] + private readonly DalamudConfiguration configuration = Service.Get(); - this.pluginDirectory = new DirectoryInfo(startInfo.PluginDirectory); - this.devPluginDirectory = new DirectoryInfo(startInfo.DefaultPluginDirectory); + [ServiceManager.ServiceDependency] + private readonly DalamudStartInfo startInfo = Service.Get(); + + [ServiceManager.ServiceConstructor] + private PluginManager() + { + this.pluginDirectory = new DirectoryInfo(this.startInfo.PluginDirectory!); + this.devPluginDirectory = new DirectoryInfo(this.startInfo.DefaultPluginDirectory!); if (!this.pluginDirectory.Exists) this.pluginDirectory.Create(); @@ -57,16 +59,16 @@ internal partial class PluginManager : IDisposable if (!this.devPluginDirectory.Exists) this.devPluginDirectory.Create(); - this.SafeMode = EnvironmentConfiguration.DalamudNoPlugins || configuration.PluginSafeMode; + this.SafeMode = EnvironmentConfiguration.DalamudNoPlugins || this.configuration.PluginSafeMode; if (this.SafeMode) { - configuration.PluginSafeMode = false; - configuration.Save(); + this.configuration.PluginSafeMode = false; + this.configuration.Save(); } - this.PluginConfigs = new PluginConfigurations(Path.Combine(Path.GetDirectoryName(startInfo.ConfigurationPath) ?? string.Empty, "pluginConfigs")); + this.PluginConfigs = new PluginConfigurations(Path.Combine(Path.GetDirectoryName(this.startInfo.ConfigurationPath) ?? string.Empty, "pluginConfigs")); - var bannedPluginsJson = File.ReadAllText(Path.Combine(startInfo.AssetDirectory, "UIRes", "bannedplugin.json")); + var bannedPluginsJson = File.ReadAllText(Path.Combine(this.startInfo.AssetDirectory!, "UIRes", "bannedplugin.json")); this.bannedPlugins = JsonConvert.DeserializeObject(bannedPluginsJson) ?? Array.Empty(); this.ApplyPatches(); @@ -225,12 +227,10 @@ internal partial class PluginManager : IDisposable /// A representing the asynchronous operation. public async Task SetPluginReposFromConfigAsync(bool notify) { - var configuration = Service.Get(); - var repos = new List() { PluginRepository.MainRepo }; - repos.AddRange(configuration.ThirdRepoList - .Where(repo => repo.IsEnabled) - .Select(repo => new PluginRepository(repo.Url, repo.IsEnabled))); + repos.AddRange(this.configuration.ThirdRepoList + .Where(repo => repo.IsEnabled) + .Select(repo => new PluginRepository(repo.Url, repo.IsEnabled))); this.Repos = repos; await this.ReloadPluginMastersAsync(notify); @@ -251,8 +251,6 @@ internal partial class PluginManager : IDisposable return; } - var configuration = Service.Get(); - var pluginDefs = new List(); var devPluginDefs = new List(); @@ -282,7 +280,7 @@ internal partial class PluginManager : IDisposable // devPlugins are more freeform. Look for any dll and hope to get lucky. var devDllFiles = this.devPluginDirectory.GetFiles("*.dll", SearchOption.AllDirectories).ToList(); - foreach (var setting in configuration.DevPluginLoadLocations) + foreach (var setting in this.configuration.DevPluginLoadLocations) { if (!setting.IsEnabled) continue; @@ -420,15 +418,13 @@ internal partial class PluginManager : IDisposable return; } - var configuration = Service.Get(); - if (!this.devPluginDirectory.Exists) this.devPluginDirectory.Create(); // devPlugins are more freeform. Look for any dll and hope to get lucky. var devDllFiles = this.devPluginDirectory.GetFiles("*.dll", SearchOption.AllDirectories).ToList(); - foreach (var setting in configuration.DevPluginLoadLocations) + foreach (var setting in this.configuration.DevPluginLoadLocations) { if (!setting.IsEnabled) continue; @@ -682,9 +678,6 @@ internal partial class PluginManager : IDisposable /// public void CleanupPlugins() { - var configuration = Service.Get(); - var startInfo = Service.Get(); - foreach (var pluginDir in this.pluginDirectory.GetDirectories()) { try @@ -748,7 +741,7 @@ internal partial class PluginManager : IDisposable continue; } - if (manifest.ApplicableVersion < startInfo.GameVersion) + if (manifest.ApplicableVersion If the manifest is eligible. public bool IsManifestEligible(PluginManifest manifest) { - var configuration = Service.Get(); - var startInfo = Service.Get(); - // Testing exclusive if (manifest.IsTestingExclusive && !configuration.DoPluginTest) return false; // Applicable version - if (manifest.ApplicableVersion < startInfo.GameVersion) + if (manifest.ApplicableVersion A value indicating whether the plugin/manifest has been banned. public bool IsManifestBanned(PluginManifest manifest) { - var configuration = Service.Get(); return !configuration.LoadBannedPlugins && this.bannedPlugins.Any(ban => (ban.Name == manifest.InternalName || ban.Name == Hash.GetStringSha256Hash(manifest.InternalName)) && ban.AssemblyVersion >= manifest.AssemblyVersion); } diff --git a/Dalamud/Plugin/Internal/StartupPluginLoader.cs b/Dalamud/Plugin/Internal/StartupPluginLoader.cs new file mode 100644 index 000000000..8a9063747 --- /dev/null +++ b/Dalamud/Plugin/Internal/StartupPluginLoader.cs @@ -0,0 +1,49 @@ +using System; +using System.Threading.Tasks; +using Dalamud.Logging.Internal; +using Dalamud.Support; +using Dalamud.Utility.Timing; + +namespace Dalamud.Plugin.Internal; + +/// +/// Class responsible for loading plugins on startup. +/// +[ServiceManager.BlockingEarlyLoadedService] +public class StartupPluginLoader +{ + private static readonly ModuleLog Log = new("SPL"); + + [ServiceManager.ServiceConstructor] + private StartupPluginLoader(PluginManager pluginManager) + { + try + { + using (Timings.Start("PM Load Plugin Repos")) + { + _ = pluginManager.SetPluginReposFromConfigAsync(false); + pluginManager.OnInstalledPluginsChanged += () => Task.Run(Troubleshooting.LogTroubleshooting); + + Log.Information("[T3] PM repos OK!"); + } + + using (Timings.Start("PM Cleanup Plugins")) + { + pluginManager.CleanupPlugins(); + Log.Information("[T3] PMC OK!"); + } + + using (Timings.Start("PM Load Sync Plugins")) + { + pluginManager.LoadAllPlugins(); + Log.Information("[T3] PML OK!"); + } + + Task.Run(Troubleshooting.LogTroubleshooting); + } + catch (Exception ex) + { + Log.Error(ex, "Plugin load failed"); + } + } +} diff --git a/Dalamud/Plugin/Internal/Types/LocalPlugin.cs b/Dalamud/Plugin/Internal/Types/LocalPlugin.cs index 1ac413ca1..b5776f4ca 100644 --- a/Dalamud/Plugin/Internal/Types/LocalPlugin.cs +++ b/Dalamud/Plugin/Internal/Types/LocalPlugin.cs @@ -334,7 +334,7 @@ internal class LocalPlugin : IDisposable this.DalamudInterface = new DalamudPluginInterface(this.pluginAssembly.GetName().Name!, this.DllFile, reason, this.IsDev); var ioc = Service.Get(); - this.instance = ioc.Create(this.pluginType, this.DalamudInterface) as IDalamudPlugin; + this.instance = ioc.Create(this.pluginType, this.DalamudInterface).GetAwaiter().GetResult() as IDalamudPlugin; if (this.instance == null) { this.State = PluginState.LoadError; diff --git a/Dalamud/Plugin/Ipc/Internal/CallGate.cs b/Dalamud/Plugin/Ipc/Internal/CallGate.cs index dd89b02bc..5d283672c 100644 --- a/Dalamud/Plugin/Ipc/Internal/CallGate.cs +++ b/Dalamud/Plugin/Ipc/Internal/CallGate.cs @@ -5,14 +5,13 @@ namespace Dalamud.Plugin.Ipc.Internal /// /// This class facilitates inter-plugin communication. /// + [ServiceManager.EarlyLoadedService] internal class CallGate { private readonly Dictionary gates = new(); - /// - /// Initializes a new instance of the class. - /// - internal CallGate() + [ServiceManager.ServiceConstructor] + private CallGate() { } diff --git a/Dalamud/ServiceManager.cs b/Dalamud/ServiceManager.cs new file mode 100644 index 000000000..8a2303d5d --- /dev/null +++ b/Dalamud/ServiceManager.cs @@ -0,0 +1,177 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Threading.Tasks; +using Dalamud.IoC.Internal; +using Dalamud.Logging.Internal; +using JetBrains.Annotations; + +namespace Dalamud +{ + /// + /// Class to initialize Service<T>s. + /// + internal static class ServiceManager + { + /// + /// Static log facility for Service{T}, to avoid duplicate instances for different types. + /// + public static readonly ModuleLog Log = new("SVC"); + + private static readonly TaskCompletionSource BlockingServicesLoadedTaskCompletionSource = new(); + + /// + /// Gets task that gets completed when all blocking early loading services are done loading. + /// + public static Task BlockingResolved { get; } = BlockingServicesLoadedTaskCompletionSource.Task; + + /// + /// Kicks off construction of services that can handle early loading. + /// + /// Task for initializing all services. + public static async Task InitializeEarlyLoadableServices() + { + Service.Provide(new ServiceContainer()); + + var service = typeof(Service<>); + var blockingEarlyLoadingServices = new List(); + + var dependencyServicesMap = new Dictionary>(); + var getAsyncTaskMap = new Dictionary(); + + foreach (var serviceType in Assembly.GetExecutingAssembly().GetTypes()) + { + var attr = serviceType.GetCustomAttribute(true)?.GetType(); + if (attr?.IsAssignableTo(typeof(EarlyLoadedService)) != true) + continue; + + var getTask = (Task)service.MakeGenericType(serviceType).InvokeMember( + "GetAsync", + BindingFlags.InvokeMethod | BindingFlags.Static | BindingFlags.Public, + null, + null, + null); + if (attr.IsAssignableTo(typeof(BlockingEarlyLoadedService))) + { + getAsyncTaskMap[serviceType] = getTask; + blockingEarlyLoadingServices.Add(getTask); + } + + dependencyServicesMap[serviceType] = + (List)service + .MakeGenericType(serviceType) + .InvokeMember( + "GetDependencyServices", + BindingFlags.InvokeMethod | BindingFlags.Static | BindingFlags.Public, + null, + null, + null); + } + + _ = Task.WhenAll(blockingEarlyLoadingServices).ContinueWith(x => + { + try + { + if (x.IsFaulted) + BlockingServicesLoadedTaskCompletionSource.SetException(x.Exception!); + else + BlockingServicesLoadedTaskCompletionSource.SetResult(); + } + catch (Exception) + { + // don't care, as this means task result/exception has already been set + } + }).ConfigureAwait(false); + + try + { + var tasks = new List(); + while (dependencyServicesMap.Any()) + { + tasks.Clear(); + foreach (var (serviceType, dependencies) in dependencyServicesMap.ToList()) + { + if (!dependencies.All( + x => !getAsyncTaskMap.ContainsKey(x) || getAsyncTaskMap[x].IsCompleted)) + continue; + + tasks.Add((Task)service.MakeGenericType(serviceType).InvokeMember( + "StartLoader", + BindingFlags.InvokeMethod | BindingFlags.Static | BindingFlags.Public, + null, + null, + null)); + dependencyServicesMap.Remove(serviceType); + } + + if (!tasks.Any()) + throw new InvalidOperationException("Unresolvable dependency cycle detected"); + + await Task.WhenAll(tasks); + foreach (var task in tasks) + { + if (task.IsFaulted) + throw task.Exception!; + } + } + } + catch (Exception e) + { + Log.Error(e, "Failed resolving services"); + try + { + BlockingServicesLoadedTaskCompletionSource.SetException(e); + } + catch (Exception) + { + // don't care, as this means task result/exception has already been set + } + + throw; + } + } + + /// + /// Indicates that this constructor will be called for early initialization. + /// + [AttributeUsage(AttributeTargets.Constructor)] + [MeansImplicitUse(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)] + public class ServiceConstructor : Attribute + { + } + + /// + /// Indicates that the field is a service that should be loaded before constructing the class. + /// + [AttributeUsage(AttributeTargets.Field)] + public class ServiceDependency : Attribute + { + } + + /// + /// Indicates that the class is a service. + /// + [AttributeUsage(AttributeTargets.Class)] + public class Service : Attribute + { + } + + /// + /// Indicates that the class is a service, and will be instantiated automatically on startup. + /// + [AttributeUsage(AttributeTargets.Class)] + public class EarlyLoadedService : Service + { + } + + /// + /// Indicates that the class is a service, and will be instantiated automatically on startup, + /// blocking game main thread until it completes. + /// + [AttributeUsage(AttributeTargets.Class)] + public class BlockingEarlyLoadedService : EarlyLoadedService + { + } + } +} diff --git a/Dalamud/Service{T}.cs b/Dalamud/Service{T}.cs index 203b9f286..99dacc993 100644 --- a/Dalamud/Service{T}.cs +++ b/Dalamud/Service{T}.cs @@ -1,9 +1,13 @@ using System; +using System.Collections.Generic; +using System.Linq; using System.Reflection; - +using System.Threading; +using System.Threading.Tasks; using Dalamud.IoC; using Dalamud.IoC.Internal; -using Dalamud.Logging.Internal; +using Dalamud.Utility.Timing; +using JetBrains.Annotations; namespace Dalamud { @@ -14,112 +18,154 @@ namespace Dalamud /// Only used internally within Dalamud, if plugins need access to things it should be _only_ via DI. /// /// The class you want to store in the service locator. - internal static class Service where T : class + internal static class Service { - private static readonly ModuleLog Log = new("SVC"); + // ReSharper disable once StaticMemberInGenericType + private static readonly TaskCompletionSource InstanceTcs = new(); - private static T? instance; + // ReSharper disable once StaticMemberInGenericType + private static bool startLoaderInvoked = false; static Service() { + var exposeToPlugins = typeof(T).GetCustomAttribute() != null; + if (exposeToPlugins) + ServiceManager.Log.Debug("Service<{0}>: Static ctor called; will be exposed to plugins", typeof(T).Name); + else + ServiceManager.Log.Debug("Service<{0}>: Static ctor called", typeof(T).Name); + + if (exposeToPlugins) + Service.Get().RegisterSingleton(InstanceTcs.Task); + } + + /// + /// Initializes the service. + /// + /// The object. + [UsedImplicitly] + public static Task StartLoader() + { + if (startLoaderInvoked) + throw new InvalidOperationException("StartLoader has already been called."); + + var attr = typeof(T).GetCustomAttribute(true)?.GetType(); + if (attr?.IsAssignableTo(typeof(ServiceManager.EarlyLoadedService)) != true) + throw new InvalidOperationException($"{typeof(T).Name} is not an EarlyLoadedService"); + + startLoaderInvoked = true; + return Task.Run(async () => + { + using (Timings.Start($"{typeof(T).Namespace} Enable")) + { + if (attr?.IsAssignableTo(typeof(ServiceManager.BlockingEarlyLoadedService)) == true) + ServiceManager.Log.Debug("Service<{0}>: Begin construction", typeof(T).Name); + try + { + var x = await ConstructObject(); + if (attr?.IsAssignableTo(typeof(ServiceManager.BlockingEarlyLoadedService)) == true) + ServiceManager.Log.Debug("Service<{0}>: Construction complete", typeof(T).Name); + InstanceTcs.SetResult(x); + return x; + } + catch (Exception e) + { + InstanceTcs.SetException(e); + if (attr?.IsAssignableTo(typeof(ServiceManager.BlockingEarlyLoadedService)) == true) + ServiceManager.Log.Error(e, "Service<{0}>: Construction failure", typeof(T).Name); + throw; + } + } + }); } /// /// Sets the type in the service locator to the given object. /// /// Object to set. - /// The set object. - public static T Set(T obj) + public static void Provide(T obj) { - SetInstanceObject(obj); - - return instance!; + InstanceTcs!.SetResult(obj); + ServiceManager.Log.Debug("Service<{0}>: Provided", typeof(T).Name); } /// - /// Sets the type in the service locator via the default parameterless constructor. + /// Pull the instance out of the service locator, waiting if necessary. /// - /// The set object. - public static T Set() - { - if (instance != null) - throw new Exception($"Service {typeof(T).FullName} was set twice"); - - var obj = (T?)Activator.CreateInstance(typeof(T), true); - - SetInstanceObject(obj); - - return instance!; - } - - /// - /// Sets a type in the service locator via a constructor with the given parameter types. - /// - /// Constructor arguments. - /// The set object. - public static T Set(params object[] args) - { - if (args == null) - { - throw new ArgumentNullException(nameof(args), $"Service locator was passed a null for type {typeof(T).FullName} parameterized constructor "); - } - - var flags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.CreateInstance | BindingFlags.OptionalParamBinding; - var obj = (T?)Activator.CreateInstance(typeof(T), flags, null, args, null, null); - - SetInstanceObject(obj); - - return obj; - } - - /// - /// Attempt to pull the instance out of the service locator. - /// - /// The object if registered. - /// Thrown when the object instance is not present in the service locator. + /// The object. public static T Get() { - return instance ?? throw new InvalidOperationException($"{typeof(T).FullName} has not been registered in the service locator!"); + if (!InstanceTcs.Task.IsCompleted) + InstanceTcs.Task.Wait(); + return InstanceTcs.Task.Result; } + /// + /// Pull the instance out of the service locator, waiting if necessary. + /// + /// The object. + [UsedImplicitly] + public static Task GetAsync() => InstanceTcs.Task; + /// /// Attempt to pull the instance out of the service locator. /// /// The object if registered, null otherwise. - public static T? GetNullable() + public static T? GetNullable() => InstanceTcs.Task.IsCompleted ? InstanceTcs.Task.Result : default; + + /// + /// Gets an enumerable containing Service<T>s that are required for this Service to initialize without blocking. + /// + /// List of dependency services. + public static List GetDependencyServices() { - return instance; + var res = new List(); + res.AddRange(GetServiceConstructor() + .GetParameters() + .Select(x => x.ParameterType)); + res.AddRange(typeof(T) + .GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) + .Select(x => x.FieldType) + .Where(x => x.GetCustomAttribute(true) != null)); + return res + .Distinct() + .Select(x => typeof(Service<>).MakeGenericType(x)) + .ToList(); } - private static void SetInstanceObject(T instance) + private static async Task GetServiceObjectConstructArgument(Type type) { - Service.instance = instance ?? throw new ArgumentNullException(nameof(instance), $"Service locator received a null for type {typeof(T).FullName}"); - - var availableToPlugins = RegisterInIoCContainer(instance); - - if (availableToPlugins) - Log.Information($"Registered {typeof(T).FullName} into service locator and exposed to plugins"); - else - Log.Information($"Registered {typeof(T).FullName} into service locator privately"); + var task = (Task)typeof(Service<>) + .MakeGenericType(type) + .InvokeMember( + "GetAsync", + BindingFlags.InvokeMethod | + BindingFlags.Static | + BindingFlags.Public, + null, + null, + null)!; + await task; + return typeof(Task<>).MakeGenericType(type) + .GetProperty("Result", BindingFlags.Instance | BindingFlags.Public)! + .GetValue(task); } - private static bool RegisterInIoCContainer(T instance) + private static ConstructorInfo GetServiceConstructor() { - var attr = typeof(T).GetCustomAttribute(); - if (attr == null) - { - return false; - } + const BindingFlags ctorBindingFlags = + BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | + BindingFlags.CreateInstance | BindingFlags.OptionalParamBinding; + return typeof(T) + .GetConstructors(ctorBindingFlags) + .Single(x => x.GetCustomAttributes(typeof(ServiceManager.ServiceConstructor), true).Any()); + } - var ioc = Service.GetNullable(); - if (ioc == null) - { - return false; - } - - ioc.RegisterSingleton(instance); - - return true; + private static async Task ConstructObject() + { + var ctor = GetServiceConstructor(); + var args = await Task.WhenAll( + ctor.GetParameters().Select(x => GetServiceObjectConstructArgument(x.ParameterType))); + return (T)ctor.Invoke(args)!; } } } diff --git a/Dalamud/Utility/ThreadSafety.cs b/Dalamud/Utility/ThreadSafety.cs index b7cdebb40..7c4b0dfcb 100644 --- a/Dalamud/Utility/ThreadSafety.cs +++ b/Dalamud/Utility/ThreadSafety.cs @@ -8,12 +8,12 @@ namespace Dalamud.Utility; public static class ThreadSafety { [ThreadStatic] - private static bool isMainThread; + private static bool threadStaticIsMainThread; /// /// Gets a value indicating whether the current thread is the main thread. /// - public static bool IsMainThread => isMainThread; + public static bool IsMainThread => threadStaticIsMainThread; /// /// Throws an exception when the current thread is not the main thread. @@ -21,7 +21,7 @@ public static class ThreadSafety /// Thrown when the current thread is not the main thread. public static void AssertMainThread() { - if (!isMainThread) + if (!threadStaticIsMainThread) { throw new InvalidOperationException("Not on main thread!"); } @@ -33,7 +33,7 @@ public static class ThreadSafety /// Thrown when the current thread is the main thread. public static void AssertNotMainThread() { - if (isMainThread) + if (threadStaticIsMainThread) { throw new InvalidOperationException("On main thread!"); } @@ -44,6 +44,6 @@ public static class ThreadSafety /// internal static void MarkMainThread() { - isMainThread = true; + threadStaticIsMainThread = true; } }