Merge branch 'net5'

This commit is contained in:
goaaats 2022-06-26 18:50:56 +02:00
commit d29400437e
No known key found for this signature in database
GPG key ID: 49E2AA8C6A76498B
52 changed files with 274 additions and 273 deletions

View file

@ -84,6 +84,7 @@
<!-- This prevents us from having to include Dalamud itself as a dependency --> <!-- This prevents us from having to include Dalamud itself as a dependency -->
<!-- If the files move just update the paths here --> <!-- If the files move just update the paths here -->
<Compile Include="..\Dalamud\ClientLanguage.cs" Link="Included\%(Filename)%(Extension)" /> <Compile Include="..\Dalamud\ClientLanguage.cs" Link="Included\%(Filename)%(Extension)" />
<Compile Include="..\Dalamud\IServiceType.cs" Link="Included\%(Filename)%(Extension)" />
<Compile Include="..\Dalamud\DalamudStartInfo.cs" Link="Included\%(Filename)%(Extension)" /> <Compile Include="..\Dalamud\DalamudStartInfo.cs" Link="Included\%(Filename)%(Extension)" />
<Compile Include="..\Dalamud\Game\GameVersion.cs" Link="Included\Game\%(Filename)%(Extension)" /> <Compile Include="..\Dalamud\Game\GameVersion.cs" Link="Included\Game\%(Filename)%(Extension)" />
<Compile Include="..\Dalamud\Game\GameVersionConverter.cs" Link="Included\Game\%(Filename)%(Extension)" /> <Compile Include="..\Dalamud\Game\GameVersionConverter.cs" Link="Included\Game\%(Filename)%(Extension)" />

View file

@ -16,7 +16,7 @@ namespace Dalamud.Configuration.Internal
/// Class containing Dalamud settings. /// Class containing Dalamud settings.
/// </summary> /// </summary>
[Serializable] [Serializable]
internal sealed class DalamudConfiguration internal sealed class DalamudConfiguration : IServiceType
{ {
/// <summary> /// <summary>
/// Currently used beta key for Dalamud staging builds. /// Currently used beta key for Dalamud staging builds.

View file

@ -33,7 +33,7 @@ namespace Dalamud
/// <summary> /// <summary>
/// The main Dalamud class containing all subsystems. /// The main Dalamud class containing all subsystems.
/// </summary> /// </summary>
internal sealed class Dalamud : IDisposable internal sealed class Dalamud : IDisposable, IServiceType
{ {
#region Internals #region Internals

View file

@ -10,7 +10,7 @@ namespace Dalamud
/// Struct containing information needed to initialize Dalamud. /// Struct containing information needed to initialize Dalamud.
/// </summary> /// </summary>
[Serializable] [Serializable]
public record DalamudStartInfo public record DalamudStartInfo : IServiceType
{ {
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="DalamudStartInfo"/> class. /// Initializes a new instance of the <see cref="DalamudStartInfo"/> class.

View file

@ -27,7 +27,7 @@ namespace Dalamud.Data
[PluginInterface] [PluginInterface]
[InterfaceVersion("1.0")] [InterfaceVersion("1.0")]
[ServiceManager.BlockingEarlyLoadedService] [ServiceManager.BlockingEarlyLoadedService]
public sealed class DataManager : IDisposable public sealed class DataManager : IDisposable, IServiceType
{ {
private const string IconFileFormat = "ui/icon/{0:D3}000/{1}{2:D6}.tex"; private const string IconFileFormat = "ui/icon/{0:D3}000/{1}{2:D6}.tex";

View file

@ -29,7 +29,7 @@ namespace Dalamud.Game
[PluginInterface] [PluginInterface]
[InterfaceVersion("1.0")] [InterfaceVersion("1.0")]
[ServiceManager.BlockingEarlyLoadedService] [ServiceManager.BlockingEarlyLoadedService]
public class ChatHandlers public class ChatHandlers : IServiceType
{ {
// private static readonly Dictionary<string, string> UnicodeToDiscordEmojiDict = new() // private static readonly Dictionary<string, string> UnicodeToDiscordEmojiDict = new()
// { // {

View file

@ -15,7 +15,7 @@ namespace Dalamud.Game.ClientState.Aetherytes
[PluginInterface] [PluginInterface]
[InterfaceVersion("1.0")] [InterfaceVersion("1.0")]
[ServiceManager.BlockingEarlyLoadedService] [ServiceManager.BlockingEarlyLoadedService]
public sealed partial class AetheryteList public sealed partial class AetheryteList : IServiceType
{ {
[ServiceManager.ServiceDependency] [ServiceManager.ServiceDependency]
private readonly ClientState clientState = Service<ClientState>.Get(); private readonly ClientState clientState = Service<ClientState>.Get();

View file

@ -16,7 +16,7 @@ namespace Dalamud.Game.ClientState.Buddy
[PluginInterface] [PluginInterface]
[InterfaceVersion("1.0")] [InterfaceVersion("1.0")]
[ServiceManager.BlockingEarlyLoadedService] [ServiceManager.BlockingEarlyLoadedService]
public sealed partial class BuddyList public sealed partial class BuddyList : IServiceType
{ {
private const uint InvalidObjectID = 0xE0000000; private const uint InvalidObjectID = 0xE0000000;

View file

@ -28,7 +28,7 @@ namespace Dalamud.Game.ClientState
[PluginInterface] [PluginInterface]
[InterfaceVersion("1.0")] [InterfaceVersion("1.0")]
[ServiceManager.BlockingEarlyLoadedService] [ServiceManager.BlockingEarlyLoadedService]
public sealed class ClientState : IDisposable public sealed class ClientState : IDisposable, IServiceType
{ {
private readonly ClientStateAddressResolver address; private readonly ClientStateAddressResolver address;
private readonly Hook<SetupTerritoryTypeDelegate> setupTerritoryTypeHook; private readonly Hook<SetupTerritoryTypeDelegate> setupTerritoryTypeHook;
@ -128,16 +128,6 @@ namespace Dalamud.Game.ClientState
/// </summary> /// </summary>
public bool IsPvPExcludingDen { get; private set; } public bool IsPvPExcludingDen { get; private set; }
/// <summary>
/// Enable this module.
/// </summary>
public void Enable()
{
Service<Conditions.Condition>.Get().Enable();
Service<GamepadState>.Get().Enable();
this.setupTerritoryTypeHook.Enable();
}
/// <summary> /// <summary>
/// Dispose of managed and unmanaged resources. /// Dispose of managed and unmanaged resources.
/// </summary> /// </summary>
@ -150,6 +140,12 @@ namespace Dalamud.Game.ClientState
Service<NetworkHandlers>.Get().CfPop -= this.NetworkHandlersOnCfPop; Service<NetworkHandlers>.Get().CfPop -= this.NetworkHandlersOnCfPop;
} }
[ServiceManager.CallWhenServicesReady]
private void ContinueConstruction()
{
this.setupTerritoryTypeHook.Enable();
}
private IntPtr SetupTerritoryTypeDetour(IntPtr manager, ushort terriType) private IntPtr SetupTerritoryTypeDetour(IntPtr manager, ushort terriType)
{ {
this.TerritoryType = terriType; this.TerritoryType = terriType;

View file

@ -12,7 +12,7 @@ namespace Dalamud.Game.ClientState.Conditions
[PluginInterface] [PluginInterface]
[InterfaceVersion("1.0")] [InterfaceVersion("1.0")]
[ServiceManager.BlockingEarlyLoadedService] [ServiceManager.BlockingEarlyLoadedService]
public sealed partial class Condition public sealed partial class Condition : IServiceType
{ {
/// <summary> /// <summary>
/// The current max number of conditions. You can get this just by looking at the condition sheet and how many rows it has. /// The current max number of conditions. You can get this just by looking at the condition sheet and how many rows it has.
@ -82,16 +82,14 @@ namespace Dalamud.Game.ClientState.Conditions
return false; return false;
} }
/// <summary> [ServiceManager.CallWhenServicesReady]
/// Enables the hooks of the Condition class function. private void ContinueConstruction(Framework framework)
/// </summary>
public void Enable()
{ {
// Initialization // Initialization
for (var i = 0; i < MaxConditionEntries; i++) for (var i = 0; i < MaxConditionEntries; i++)
this.cache[i] = this[i]; this.cache[i] = this[i];
Service<Framework>.Get().Update += this.FrameworkUpdate; framework.Update += this.FrameworkUpdate;
} }
private void FrameworkUpdate(Framework framework) private void FrameworkUpdate(Framework framework)

View file

@ -14,7 +14,7 @@ namespace Dalamud.Game.ClientState.Fates
[PluginInterface] [PluginInterface]
[InterfaceVersion("1.0")] [InterfaceVersion("1.0")]
[ServiceManager.BlockingEarlyLoadedService] [ServiceManager.BlockingEarlyLoadedService]
public sealed partial class FateTable public sealed partial class FateTable : IServiceType
{ {
private readonly ClientStateAddressResolver address; private readonly ClientStateAddressResolver address;

View file

@ -16,7 +16,7 @@ namespace Dalamud.Game.ClientState.GamePad
[PluginInterface] [PluginInterface]
[InterfaceVersion("1.0.0")] [InterfaceVersion("1.0.0")]
[ServiceManager.BlockingEarlyLoadedService] [ServiceManager.BlockingEarlyLoadedService]
public unsafe class GamepadState : IDisposable public unsafe class GamepadState : IDisposable, IServiceType
{ {
private readonly Hook<ControllerPoll> gamepadPoll; private readonly Hook<ControllerPoll> gamepadPoll;
@ -168,10 +168,8 @@ namespace Dalamud.Game.ClientState.GamePad
GC.SuppressFinalize(this); GC.SuppressFinalize(this);
} }
/// <summary> [ServiceManager.CallWhenServicesReady]
/// Enables the hook of the GamepadPoll function. private void ContinueConstruction()
/// </summary>
internal void Enable()
{ {
this.gamepadPoll.Enable(); this.gamepadPoll.Enable();
} }

View file

@ -15,7 +15,7 @@ namespace Dalamud.Game.ClientState.JobGauge
[PluginInterface] [PluginInterface]
[InterfaceVersion("1.0")] [InterfaceVersion("1.0")]
[ServiceManager.BlockingEarlyLoadedService] [ServiceManager.BlockingEarlyLoadedService]
public class JobGauges public class JobGauges : IServiceType
{ {
private Dictionary<Type, JobGaugeBase> cache = new(); private Dictionary<Type, JobGaugeBase> cache = new();

View file

@ -23,7 +23,7 @@ namespace Dalamud.Game.ClientState.Keys
[PluginInterface] [PluginInterface]
[InterfaceVersion("1.0")] [InterfaceVersion("1.0")]
[ServiceManager.BlockingEarlyLoadedService] [ServiceManager.BlockingEarlyLoadedService]
public class KeyState public class KeyState : IServiceType
{ {
// The array is accessed in a way that this limit doesn't appear to exist // The array is accessed in a way that this limit doesn't appear to exist
// but there is other state data past this point, and keys beyond here aren't // but there is other state data past this point, and keys beyond here aren't

View file

@ -17,7 +17,7 @@ namespace Dalamud.Game.ClientState.Objects
[PluginInterface] [PluginInterface]
[InterfaceVersion("1.0")] [InterfaceVersion("1.0")]
[ServiceManager.BlockingEarlyLoadedService] [ServiceManager.BlockingEarlyLoadedService]
public sealed partial class ObjectTable public sealed partial class ObjectTable : IServiceType
{ {
private const int ObjectTableLength = 424; private const int ObjectTableLength = 424;

View file

@ -12,7 +12,7 @@ namespace Dalamud.Game.ClientState.Objects
[PluginInterface] [PluginInterface]
[InterfaceVersion("1.0")] [InterfaceVersion("1.0")]
[ServiceManager.BlockingEarlyLoadedService] [ServiceManager.BlockingEarlyLoadedService]
public sealed unsafe class TargetManager public sealed unsafe class TargetManager : IServiceType
{ {
private readonly ClientStateAddressResolver address; private readonly ClientStateAddressResolver address;

View file

@ -15,7 +15,7 @@ namespace Dalamud.Game.ClientState.Party
[PluginInterface] [PluginInterface]
[InterfaceVersion("1.0")] [InterfaceVersion("1.0")]
[ServiceManager.BlockingEarlyLoadedService] [ServiceManager.BlockingEarlyLoadedService]
public sealed unsafe partial class PartyList public sealed unsafe partial class PartyList : IServiceType
{ {
private const int GroupLength = 8; private const int GroupLength = 8;
private const int AllianceLength = 20; private const int AllianceLength = 20;

View file

@ -18,7 +18,7 @@ namespace Dalamud.Game.Command
[PluginInterface] [PluginInterface]
[InterfaceVersion("1.0")] [InterfaceVersion("1.0")]
[ServiceManager.BlockingEarlyLoadedService] [ServiceManager.BlockingEarlyLoadedService]
public sealed class CommandManager public sealed class CommandManager : IServiceType
{ {
private readonly Dictionary<string, CommandInfo> commandMap = new(); private readonly Dictionary<string, CommandInfo> commandMap = new();
private readonly Regex commandRegexEn = new(@"^The command (?<command>.+) does not exist\.$", RegexOptions.Compiled); private readonly Regex commandRegexEn = new(@"^The command (?<command>.+) does not exist\.$", RegexOptions.Compiled);

View file

@ -25,7 +25,7 @@ namespace Dalamud.Game
[PluginInterface] [PluginInterface]
[InterfaceVersion("1.0")] [InterfaceVersion("1.0")]
[ServiceManager.BlockingEarlyLoadedService] [ServiceManager.BlockingEarlyLoadedService]
public sealed class Framework : IDisposable public sealed class Framework : IDisposable, IServiceType
{ {
private static Stopwatch statsStopwatch = new(); private static Stopwatch statsStopwatch = new();
@ -47,13 +47,6 @@ namespace Dalamud.Game
this.updateHook = new Hook<OnUpdateDetour>(this.Address.TickAddress, this.HandleFrameworkUpdate); this.updateHook = new Hook<OnUpdateDetour>(this.Address.TickAddress, this.HandleFrameworkUpdate);
this.freeHook = new Hook<OnDestroyDetour>(this.Address.FreeAddress, this.HandleFrameworkFree); this.freeHook = new Hook<OnDestroyDetour>(this.Address.FreeAddress, this.HandleFrameworkFree);
this.destroyHook = new Hook<OnRealDestroyDelegate>(this.Address.DestroyAddress, this.HandleFrameworkDestroy); this.destroyHook = new Hook<OnRealDestroyDelegate>(this.Address.DestroyAddress, this.HandleFrameworkDestroy);
gameGui.Enable();
gameNetwork.Enable();
this.updateHook.Enable();
this.freeHook.Enable();
this.destroyHook.Enable();
} }
/// <summary> /// <summary>
@ -224,6 +217,14 @@ namespace Dalamud.Game
statsStopwatch.Reset(); statsStopwatch.Reset();
} }
[ServiceManager.CallWhenServicesReady]
private void ContinueConstruction()
{
this.updateHook.Enable();
this.freeHook.Enable();
this.destroyHook.Enable();
}
private bool HandleFrameworkUpdate(IntPtr framework) private bool HandleFrameworkUpdate(IntPtr framework)
{ {
this.frameworkUpdateThread ??= Thread.CurrentThread; this.frameworkUpdateThread ??= Thread.CurrentThread;

View file

@ -22,7 +22,7 @@ namespace Dalamud.Game.Gui
[PluginInterface] [PluginInterface]
[InterfaceVersion("1.0")] [InterfaceVersion("1.0")]
[ServiceManager.BlockingEarlyLoadedService] [ServiceManager.BlockingEarlyLoadedService]
public sealed class ChatGui : IDisposable public sealed class ChatGui : IDisposable, IServiceType
{ {
private readonly ChatGuiAddressResolver address; private readonly ChatGuiAddressResolver address;
@ -123,16 +123,6 @@ namespace Dalamud.Game.Gui
/// </summary> /// </summary>
public byte LastLinkedItemFlags { get; private set; } public byte LastLinkedItemFlags { get; private set; }
/// <summary>
/// Enables this module.
/// </summary>
public void Enable()
{
this.printMessageHook.Enable();
this.populateItemLinkHook.Enable();
this.interactableLinkClickedHook.Enable();
}
/// <summary> /// <summary>
/// Dispose of managed and unmanaged resources. /// Dispose of managed and unmanaged resources.
/// </summary> /// </summary>
@ -280,6 +270,14 @@ namespace Dalamud.Game.Gui
} }
} }
[ServiceManager.CallWhenServicesReady]
private void ContinueConstruction(GameGui gameGui, LibcFunction libcFunction)
{
this.printMessageHook.Enable();
this.populateItemLinkHook.Enable();
this.interactableLinkClickedHook.Enable();
}
private void HandlePopulateItemLinkDetour(IntPtr linkObjectPtr, IntPtr itemInfoPtr) private void HandlePopulateItemLinkDetour(IntPtr linkObjectPtr, IntPtr itemInfoPtr)
{ {
try try

View file

@ -2,7 +2,7 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using Dalamud.Configuration.Internal;
using Dalamud.Game.Gui.ContextMenus.OldStructs; using Dalamud.Game.Gui.ContextMenus.OldStructs;
using Dalamud.Hooking; using Dalamud.Hooking;
using Dalamud.IoC; using Dalamud.IoC;
@ -25,7 +25,7 @@ namespace Dalamud.Game.Gui.ContextMenus
[PluginInterface] [PluginInterface]
[InterfaceVersion("1.0")] [InterfaceVersion("1.0")]
[ServiceManager.BlockingEarlyLoadedService] [ServiceManager.BlockingEarlyLoadedService]
public sealed class ContextMenu : IDisposable public sealed class ContextMenu : IDisposable, IServiceType
{ {
private const int MaxContextMenuItemsPerContextMenu = 32; private const int MaxContextMenuItemsPerContextMenu = 32;
@ -97,18 +97,6 @@ namespace Dalamud.Game.Gui.ContextMenus
this.contextMenuOpeningHook.Disable(); this.contextMenuOpeningHook.Disable();
} }
/// <summary>
/// Enable this subsystem.
/// </summary>
internal void Enable()
{
this.contextMenuOpeningHook.Enable();
this.contextMenuOpenedHook.Enable();
this.contextMenuItemSelectedHook.Enable();
this.subContextMenuOpeningHook.Enable();
this.subContextMenuOpenedHook.Enable();
}
private static unsafe bool IsInventoryContext(OldAgentContextInterface* agentContextInterface) private static unsafe bool IsInventoryContext(OldAgentContextInterface* agentContextInterface)
{ {
return agentContextInterface == AgentInventoryContext.Instance(); return agentContextInterface == AgentInventoryContext.Instance();
@ -122,6 +110,19 @@ namespace Dalamud.Game.Gui.ContextMenus
} }
} }
[ServiceManager.CallWhenServicesReady]
private void ContinueConstruction(GameGui gameGui)
{
if (!EnvironmentConfiguration.DalamudDoContextMenu)
return;
this.contextMenuOpeningHook.Enable();
this.contextMenuOpenedHook.Enable();
this.contextMenuItemSelectedHook.Enable();
this.subContextMenuOpeningHook.Enable();
this.subContextMenuOpenedHook.Enable();
}
private unsafe IntPtr ContextMenuOpeningDetour(IntPtr a1, IntPtr a2, IntPtr a3, uint a4, IntPtr a5, OldAgentContextInterface* agentContextInterface, IntPtr a7, ushort a8) private unsafe IntPtr ContextMenuOpeningDetour(IntPtr a1, IntPtr a2, IntPtr a3, uint a4, IntPtr a5, OldAgentContextInterface* agentContextInterface, IntPtr a7, ushort a8)
{ {
this.currentAgentContextInterface = agentContextInterface; this.currentAgentContextInterface = agentContextInterface;

View file

@ -18,7 +18,7 @@ namespace Dalamud.Game.Gui.Dtr
[PluginInterface] [PluginInterface]
[InterfaceVersion("1.0")] [InterfaceVersion("1.0")]
[ServiceManager.BlockingEarlyLoadedService] [ServiceManager.BlockingEarlyLoadedService]
public sealed unsafe class DtrBar : IDisposable public sealed unsafe class DtrBar : IDisposable, IServiceType
{ {
private const uint BaseNodeId = 1000; private const uint BaseNodeId = 1000;

View file

@ -17,7 +17,7 @@ namespace Dalamud.Game.Gui.FlyText
[PluginInterface] [PluginInterface]
[InterfaceVersion("1.0")] [InterfaceVersion("1.0")]
[ServiceManager.BlockingEarlyLoadedService] [ServiceManager.BlockingEarlyLoadedService]
public sealed class FlyTextGui : IDisposable public sealed class FlyTextGui : IDisposable, IServiceType
{ {
/// <summary> /// <summary>
/// The native function responsible for adding fly text to the UI. See <see cref="FlyTextGuiAddressResolver.AddFlyText"/>. /// The native function responsible for adding fly text to the UI. See <see cref="FlyTextGuiAddressResolver.AddFlyText"/>.
@ -129,7 +129,10 @@ namespace Dalamud.Game.Gui.FlyText
var strOffset = 28u; var strOffset = 28u;
// Get the UI module and flytext addon pointers // Get the UI module and flytext addon pointers
var gameGui = Service<GameGui>.Get(); var gameGui = Service<GameGui>.GetNullable();
if (gameGui == null)
return;
var ui = (FFXIVClientStructs.FFXIV.Client.UI.UIModule*)gameGui.GetUIModule(); var ui = (FFXIVClientStructs.FFXIV.Client.UI.UIModule*)gameGui.GetUIModule();
var flytext = gameGui.GetAddonByName("_FlyText", 1); var flytext = gameGui.GetAddonByName("_FlyText", 1);
@ -174,10 +177,8 @@ namespace Dalamud.Game.Gui.FlyText
} }
} }
/// <summary> [ServiceManager.CallWhenServicesReady]
/// Enables this module. private void ContinueConstruction(GameGui gameGui)
/// </summary>
internal void Enable()
{ {
this.createFlyTextHook.Enable(); this.createFlyTextHook.Enable();
} }

View file

@ -28,7 +28,7 @@ namespace Dalamud.Game.Gui
[PluginInterface] [PluginInterface]
[InterfaceVersion("1.0")] [InterfaceVersion("1.0")]
[ServiceManager.BlockingEarlyLoadedService] [ServiceManager.BlockingEarlyLoadedService]
public sealed unsafe class GameGui : IDisposable public sealed unsafe class GameGui : IDisposable, IServiceType
{ {
private readonly GameGuiAddressResolver address; private readonly GameGuiAddressResolver address;
@ -418,19 +418,9 @@ namespace Dalamud.Game.Gui
/// <param name="bgmKey">The background music key.</param> /// <param name="bgmKey">The background music key.</param>
public void SetBgm(ushort bgmKey) => this.setGlobalBgmHook.Original(bgmKey, 0, 0, 0, 0, 0); public void SetBgm(ushort bgmKey) => this.setGlobalBgmHook.Original(bgmKey, 0, 0, 0, 0, 0);
/// <summary> [ServiceManager.CallWhenServicesReady]
/// Enables the hooks and submodules of this module. private void ContinueConstruction()
/// </summary>
public void Enable()
{ {
Service<ChatGui>.GetAsync().ContinueWith(x => x.Result.Enable());
Service<ToastGui>.GetAsync().ContinueWith(x => x.Result.Enable());
Service<FlyTextGui>.GetAsync().ContinueWith(x => x.Result.Enable());
Service<PartyFinderGui>.GetAsync().ContinueWith(x => x.Result.Enable());
if (EnvironmentConfiguration.DalamudDoContextMenu)
Service<ContextMenu>.GetAsync().ContinueWith(x => x.Result.Enable());
this.setGlobalBgmHook.Enable(); this.setGlobalBgmHook.Enable();
this.handleItemHoverHook.Enable(); this.handleItemHoverHook.Enable();
this.handleItemOutHook.Enable(); this.handleItemOutHook.Enable();

View file

@ -19,7 +19,7 @@ namespace Dalamud.Game.Gui.Internal
/// This class handles IME for non-English users. /// This class handles IME for non-English users.
/// </summary> /// </summary>
[ServiceManager.EarlyLoadedService] [ServiceManager.EarlyLoadedService]
internal unsafe class DalamudIME : IDisposable internal unsafe class DalamudIME : IDisposable, IServiceType
{ {
private static readonly ModuleLog Log = new("IME"); private static readonly ModuleLog Log = new("IME");
@ -83,15 +83,13 @@ namespace Dalamud.Game.Gui.Internal
return new Vector2(this.cursorPos->X, this.cursorPos->Y); return new Vector2(this.cursorPos->X, this.cursorPos->Y);
} }
/// <summary> [ServiceManager.CallWhenServicesReady]
/// Enables the IME module. private void ContinueConstruction(InterfaceManager.InterfaceManagerWithScene interfaceManagerWithScene)
/// </summary>
internal void Enable()
{ {
try try
{ {
this.wndProcDelegate = this.WndProcDetour; this.wndProcDelegate = this.WndProcDetour;
this.interfaceHandle = Service<InterfaceManager>.Get().WindowHandlePtr; this.interfaceHandle = interfaceManagerWithScene.Manager.WindowHandlePtr;
this.wndProcPtr = Marshal.GetFunctionPointerForDelegate(this.wndProcDelegate); this.wndProcPtr = Marshal.GetFunctionPointerForDelegate(this.wndProcDelegate);
this.oldWndProcPtr = SetWindowLongPtrW(this.interfaceHandle, WindowLongType.WndProc, this.wndProcPtr); this.oldWndProcPtr = SetWindowLongPtrW(this.interfaceHandle, WindowLongType.WndProc, this.wndProcPtr);

View file

@ -16,7 +16,7 @@ namespace Dalamud.Game.Gui.PartyFinder
[PluginInterface] [PluginInterface]
[InterfaceVersion("1.0")] [InterfaceVersion("1.0")]
[ServiceManager.BlockingEarlyLoadedService] [ServiceManager.BlockingEarlyLoadedService]
public sealed class PartyFinderGui : IDisposable public sealed class PartyFinderGui : IDisposable, IServiceType
{ {
private readonly PartyFinderAddressResolver address; private readonly PartyFinderAddressResolver address;
private readonly IntPtr memory; private readonly IntPtr memory;
@ -55,14 +55,6 @@ namespace Dalamud.Game.Gui.PartyFinder
/// </summary> /// </summary>
public event PartyFinderListingEventDelegate ReceiveListing; public event PartyFinderListingEventDelegate ReceiveListing;
/// <summary>
/// Enables this module.
/// </summary>
public void Enable()
{
this.receiveListingHook.Enable();
}
/// <summary> /// <summary>
/// Dispose of managed and unmanaged resources. /// Dispose of managed and unmanaged resources.
/// </summary> /// </summary>
@ -80,6 +72,12 @@ namespace Dalamud.Game.Gui.PartyFinder
} }
} }
[ServiceManager.CallWhenServicesReady]
private void ContinueConstruction(GameGui gameGui)
{
this.receiveListingHook.Enable();
}
private void HandleReceiveListingDetour(IntPtr managerPtr, IntPtr data) private void HandleReceiveListingDetour(IntPtr managerPtr, IntPtr data)
{ {
try try

View file

@ -15,7 +15,7 @@ namespace Dalamud.Game.Gui.Toast
[PluginInterface] [PluginInterface]
[InterfaceVersion("1.0")] [InterfaceVersion("1.0")]
[ServiceManager.BlockingEarlyLoadedService] [ServiceManager.BlockingEarlyLoadedService]
public sealed partial class ToastGui : IDisposable public sealed partial class ToastGui : IDisposable, IServiceType
{ {
private const uint QuestToastCheckmarkMagic = 60081; private const uint QuestToastCheckmarkMagic = 60081;
@ -100,16 +100,6 @@ namespace Dalamud.Game.Gui.Toast
#endregion #endregion
/// <summary>
/// Enables this module.
/// </summary>
public void Enable()
{
this.showNormalToastHook.Enable();
this.showQuestToastHook.Enable();
this.showErrorToastHook.Enable();
}
/// <summary> /// <summary>
/// Disposes of managed and unmanaged resources. /// Disposes of managed and unmanaged resources.
/// </summary> /// </summary>
@ -153,6 +143,14 @@ namespace Dalamud.Game.Gui.Toast
return terminated; return terminated;
} }
[ServiceManager.CallWhenServicesReady]
private void ContinueConstruction(GameGui gameGui)
{
this.showNormalToastHook.Enable();
this.showQuestToastHook.Enable();
this.showErrorToastHook.Enable();
}
private SeString ParseString(IntPtr text) private SeString ParseString(IntPtr text)
{ {
var bytes = new List<byte>(); var bytes = new List<byte>();
@ -202,7 +200,9 @@ namespace Dalamud.Game.Gui.Toast
{ {
options ??= new ToastOptions(); options ??= new ToastOptions();
var manager = Service<GameGui>.Get().GetUIModule(); var manager = Service<GameGui>.GetNullable()?.GetUIModule();
if (manager == null)
return;
// terminate the string // terminate the string
var terminated = Terminate(bytes); var terminated = Terminate(bytes);
@ -211,7 +211,7 @@ namespace Dalamud.Game.Gui.Toast
{ {
fixed (byte* ptr = terminated) fixed (byte* ptr = terminated)
{ {
this.HandleNormalToastDetour(manager, (IntPtr)ptr, 5, (byte)options.Position, (byte)options.Speed, 0); this.HandleNormalToastDetour(manager!.Value, (IntPtr)ptr, 5, (byte)options.Position, (byte)options.Speed, 0);
} }
} }
} }
@ -283,7 +283,9 @@ namespace Dalamud.Game.Gui.Toast
{ {
options ??= new QuestToastOptions(); options ??= new QuestToastOptions();
var manager = Service<GameGui>.Get().GetUIModule(); var manager = Service<GameGui>.GetNullable()?.GetUIModule();
if (manager == null)
return;
// terminate the string // terminate the string
var terminated = Terminate(bytes); var terminated = Terminate(bytes);
@ -295,7 +297,7 @@ namespace Dalamud.Game.Gui.Toast
fixed (byte* ptr = terminated) fixed (byte* ptr = terminated)
{ {
this.HandleQuestToastDetour( this.HandleQuestToastDetour(
manager, manager!.Value,
(int)options.Position, (int)options.Position,
(IntPtr)ptr, (IntPtr)ptr,
ioc1, ioc1,
@ -385,7 +387,9 @@ namespace Dalamud.Game.Gui.Toast
private void ShowError(byte[] bytes) private void ShowError(byte[] bytes)
{ {
var manager = Service<GameGui>.Get().GetUIModule(); var manager = Service<GameGui>.GetNullable()?.GetUIModule();
if (manager == null)
return;
// terminate the string // terminate the string
var terminated = Terminate(bytes); var terminated = Terminate(bytes);
@ -394,7 +398,7 @@ namespace Dalamud.Game.Gui.Toast
{ {
fixed (byte* ptr = terminated) fixed (byte* ptr = terminated)
{ {
this.HandleErrorToastDetour(manager, (IntPtr)ptr, 0); this.HandleErrorToastDetour(manager!.Value, (IntPtr)ptr, 0);
} }
} }
} }

View file

@ -9,7 +9,7 @@ namespace Dalamud.Game.Internal
/// This class disables anti-debug functionality in the game client. /// This class disables anti-debug functionality in the game client.
/// </summary> /// </summary>
[ServiceManager.EarlyLoadedService] [ServiceManager.EarlyLoadedService]
internal sealed partial class AntiDebug internal sealed partial class AntiDebug : IServiceType
{ {
private readonly byte[] nop = new byte[] { 0x31, 0xC0, 0x90, 0x90, 0x90, 0x90 }; private readonly byte[] nop = new byte[] { 0x31, 0xC0, 0x90, 0x90, 0x90, 0x90 };
private byte[] original; private byte[] original;

View file

@ -24,7 +24,7 @@ namespace Dalamud.Game.Internal
/// This class implements in-game Dalamud options in the in-game System menu. /// This class implements in-game Dalamud options in the in-game System menu.
/// </summary> /// </summary>
[ServiceManager.EarlyLoadedService] [ServiceManager.EarlyLoadedService]
internal sealed unsafe partial class DalamudAtkTweaks internal sealed unsafe partial class DalamudAtkTweaks : IServiceType
{ {
private readonly AtkValueChangeType atkValueChangeType; private readonly AtkValueChangeType atkValueChangeType;
private readonly AtkValueSetString atkValueSetString; private readonly AtkValueSetString atkValueSetString;
@ -39,7 +39,7 @@ namespace Dalamud.Game.Internal
private readonly string locDalamudSettings; private readonly string locDalamudSettings;
[ServiceManager.ServiceConstructor] [ServiceManager.ServiceConstructor]
private DalamudAtkTweaks(SigScanner sigScanner) private DalamudAtkTweaks(SigScanner sigScanner, ContextMenu contextMenu)
{ {
var openSystemMenuAddress = sigScanner.ScanText("E8 ?? ?? ?? ?? 32 C0 4C 8B AC 24 ?? ?? ?? ?? 48 8B 8D ?? ?? ?? ??"); var openSystemMenuAddress = sigScanner.ScanText("E8 ?? ?? ?? ?? 32 C0 4C 8B AC 24 ?? ?? ?? ?? 48 8B 8D ?? ?? ?? ??");
@ -60,10 +60,7 @@ namespace Dalamud.Game.Internal
this.locDalamudPlugins = Loc.Localize("SystemMenuPlugins", "Dalamud Plugins"); this.locDalamudPlugins = Loc.Localize("SystemMenuPlugins", "Dalamud Plugins");
this.locDalamudSettings = Loc.Localize("SystemMenuSettings", "Dalamud Settings"); this.locDalamudSettings = Loc.Localize("SystemMenuSettings", "Dalamud Settings");
var contextMenu = Service<ContextMenu>.Get();
contextMenu.ContextMenuOpened += this.ContextMenuOnContextMenuOpened; contextMenu.ContextMenuOpened += this.ContextMenuOnContextMenuOpened;
this.Enable();
} }
private delegate void AgentHudOpenSystemMenuPrototype(void* thisPtr, AtkValue* atkValueArgs, uint menuSize); private delegate void AgentHudOpenSystemMenuPrototype(void* thisPtr, AtkValue* atkValueArgs, uint menuSize);
@ -76,10 +73,8 @@ namespace Dalamud.Game.Internal
private delegate IntPtr AtkUnitBaseReceiveGlobalEvent(AtkUnitBase* thisPtr, ushort cmd, uint a3, IntPtr a4, uint* a5); private delegate IntPtr AtkUnitBaseReceiveGlobalEvent(AtkUnitBase* thisPtr, ushort cmd, uint a3, IntPtr a4, uint* a5);
/// <summary> [ServiceManager.CallWhenServicesReady]
/// Enables the <see cref="DalamudAtkTweaks"/>. private void ContinueConstruction(DalamudInterface dalamudInterface)
/// </summary>
public void Enable()
{ {
this.hookAgentHudOpenSystemMenu.Enable(); this.hookAgentHudOpenSystemMenu.Enable();
this.hookUiModuleRequestMainCommand.Enable(); this.hookUiModuleRequestMainCommand.Enable();

View file

@ -13,7 +13,7 @@ namespace Dalamud.Game.Libc
[PluginInterface] [PluginInterface]
[InterfaceVersion("1.0")] [InterfaceVersion("1.0")]
[ServiceManager.BlockingEarlyLoadedService] [ServiceManager.BlockingEarlyLoadedService]
public sealed class LibcFunction public sealed class LibcFunction : IServiceType
{ {
private readonly LibcFunctionAddressResolver address; private readonly LibcFunctionAddressResolver address;
private readonly StdStringFromCStringDelegate stdStringCtorCString; private readonly StdStringFromCStringDelegate stdStringCtorCString;

View file

@ -15,7 +15,7 @@ namespace Dalamud.Game.Network
[PluginInterface] [PluginInterface]
[InterfaceVersion("1.0")] [InterfaceVersion("1.0")]
[ServiceManager.BlockingEarlyLoadedService] [ServiceManager.BlockingEarlyLoadedService]
public sealed class GameNetwork : IDisposable public sealed class GameNetwork : IDisposable, IServiceType
{ {
private readonly GameNetworkAddressResolver address; private readonly GameNetworkAddressResolver address;
private readonly Hook<ProcessZonePacketDownDelegate> processZonePacketDownHook; private readonly Hook<ProcessZonePacketDownDelegate> processZonePacketDownHook;
@ -59,15 +59,6 @@ namespace Dalamud.Game.Network
/// </summary> /// </summary>
public event OnNetworkMessageDelegate NetworkMessage; public event OnNetworkMessageDelegate NetworkMessage;
/// <summary>
/// Enable this module.
/// </summary>
public void Enable()
{
this.processZonePacketDownHook.Enable();
this.processZonePacketUpHook.Enable();
}
/// <summary> /// <summary>
/// Dispose of managed and unmanaged resources. /// Dispose of managed and unmanaged resources.
/// </summary> /// </summary>
@ -98,6 +89,13 @@ namespace Dalamud.Game.Network
} }
} }
[ServiceManager.CallWhenServicesReady]
private void ContinueConstruction()
{
this.processZonePacketDownHook.Enable();
this.processZonePacketUpHook.Enable();
}
private void ProcessZonePacketDownDetour(IntPtr a, uint targetId, IntPtr dataPtr) private void ProcessZonePacketDownDetour(IntPtr a, uint targetId, IntPtr dataPtr)
{ {
this.baseAddress = a; this.baseAddress = a;

View file

@ -22,7 +22,7 @@ namespace Dalamud.Game.Network.Internal
/// This class handles network notifications and uploading market board data. /// This class handles network notifications and uploading market board data.
/// </summary> /// </summary>
[ServiceManager.EarlyLoadedService] [ServiceManager.EarlyLoadedService]
internal class NetworkHandlers internal class NetworkHandlers : IServiceType
{ {
private readonly List<MarketBoardItemRequest> marketBoardRequests = new(); private readonly List<MarketBoardItemRequest> marketBoardRequests = new();

View file

@ -10,7 +10,7 @@ namespace Dalamud.Game.Network.Internal
/// This class enables TCP optimizations in the game socket for better performance. /// This class enables TCP optimizations in the game socket for better performance.
/// </summary> /// </summary>
[ServiceManager.EarlyLoadedService] [ServiceManager.EarlyLoadedService]
internal sealed class WinSockHandlers : IDisposable internal sealed class WinSockHandlers : IDisposable, IServiceType
{ {
private Hook<SocketDelegate> ws2SocketHook; private Hook<SocketDelegate> ws2SocketHook;

View file

@ -19,7 +19,7 @@ namespace Dalamud.Game
/// </summary> /// </summary>
[PluginInterface] [PluginInterface]
[InterfaceVersion("1.0")] [InterfaceVersion("1.0")]
public class SigScanner : IDisposable public class SigScanner : IDisposable, IServiceType
{ {
private readonly FileInfo? cacheFile; private readonly FileInfo? cacheFile;

View file

@ -14,7 +14,7 @@ namespace Dalamud.Game.Text.SeStringHandling
[InterfaceVersion("1.0")] [InterfaceVersion("1.0")]
[ServiceManager.BlockingEarlyLoadedService] [ServiceManager.BlockingEarlyLoadedService]
[Obsolete("This class is obsolete. Please use the static methods on SeString instead.")] [Obsolete("This class is obsolete. Please use the static methods on SeString instead.")]
public sealed class SeStringManager public sealed class SeStringManager : IServiceType
{ {
[ServiceManager.ServiceConstructor] [ServiceManager.ServiceConstructor]
private SeStringManager() private SeStringManager()

View file

@ -14,7 +14,7 @@ namespace Dalamud.Hooking.Internal
/// This class manages the final disposition of hooks, cleaning up any that have not reverted their changes. /// This class manages the final disposition of hooks, cleaning up any that have not reverted their changes.
/// </summary> /// </summary>
[ServiceManager.EarlyLoadedService] [ServiceManager.EarlyLoadedService]
internal class HookManager : IDisposable internal class HookManager : IDisposable, IServiceType
{ {
private static readonly ModuleLog Log = new("HM"); private static readonly ModuleLog Log = new("HM");

8
Dalamud/IServiceType.cs Normal file
View file

@ -0,0 +1,8 @@
namespace Dalamud;
/// <summary>
/// Marker class for service types.
/// </summary>
public interface IServiceType
{
}

View file

@ -18,7 +18,7 @@ namespace Dalamud.Interface.GameFonts
/// Loads game font for use in ImGui. /// Loads game font for use in ImGui.
/// </summary> /// </summary>
[ServiceManager.EarlyLoadedService] [ServiceManager.EarlyLoadedService]
internal class GameFontManager : IDisposable internal class GameFontManager : IDisposable, IServiceType
{ {
private static readonly string?[] FontNames = private static readonly string?[] FontNames =
{ {

View file

@ -19,7 +19,7 @@ namespace Dalamud.Interface.Internal
/// Class handling Dalamud core commands. /// Class handling Dalamud core commands.
/// </summary> /// </summary>
[ServiceManager.EarlyLoadedService] [ServiceManager.EarlyLoadedService]
internal class DalamudCommands internal class DalamudCommands : IServiceType
{ {
[ServiceManager.ServiceConstructor] [ServiceManager.ServiceConstructor]
private DalamudCommands(CommandManager commandManager) private DalamudCommands(CommandManager commandManager)

View file

@ -35,8 +35,8 @@ namespace Dalamud.Interface.Internal
/// <summary> /// <summary>
/// This plugin implements all of the Dalamud interface separately, to allow for reloading of the interface and rapid prototyping. /// This plugin implements all of the Dalamud interface separately, to allow for reloading of the interface and rapid prototyping.
/// </summary> /// </summary>
[ServiceManager.AfterDrawingEarlyLoadedService] [ServiceManager.EarlyLoadedService]
internal class DalamudInterface : IDisposable internal class DalamudInterface : IDisposable, IServiceType
{ {
private static readonly ModuleLog Log = new("DUI"); private static readonly ModuleLog Log = new("DUI");
@ -77,8 +77,12 @@ namespace Dalamud.Interface.Internal
private bool isImGuiDrawMetricsWindow = false; private bool isImGuiDrawMetricsWindow = false;
[ServiceManager.ServiceConstructor] [ServiceManager.ServiceConstructor]
private DalamudInterface(Dalamud dalamud, DalamudConfiguration configuration, InterfaceManager interfaceManager) private DalamudInterface(
Dalamud dalamud,
DalamudConfiguration configuration,
InterfaceManager.InterfaceManagerWithScene interfaceManagerWithScene)
{ {
var interfaceManager = interfaceManagerWithScene.Manager;
this.WindowSystem = new WindowSystem("DalamudCore"); this.WindowSystem = new WindowSystem("DalamudCore");
this.changelogWindow = new ChangelogWindow() { IsOpen = false }; this.changelogWindow = new ChangelogWindow() { IsOpen = false };

View file

@ -7,12 +7,10 @@ using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Text; using System.Text;
using System.Threading; using System.Threading;
using System.Threading.Tasks;
using Dalamud.Configuration.Internal; using Dalamud.Configuration.Internal;
using Dalamud.Game; using Dalamud.Game;
using Dalamud.Game.ClientState.GamePad; using Dalamud.Game.ClientState.GamePad;
using Dalamud.Game.ClientState.Keys; using Dalamud.Game.ClientState.Keys;
using Dalamud.Game.Gui.Internal;
using Dalamud.Game.Internal.DXGI; using Dalamud.Game.Internal.DXGI;
using Dalamud.Hooking; using Dalamud.Hooking;
using Dalamud.Interface.GameFonts; using Dalamud.Interface.GameFonts;
@ -45,7 +43,7 @@ namespace Dalamud.Interface.Internal
/// This class manages interaction with the ImGui interface. /// This class manages interaction with the ImGui interface.
/// </summary> /// </summary>
[ServiceManager.BlockingEarlyLoadedService] [ServiceManager.BlockingEarlyLoadedService]
internal class InterfaceManager : IDisposable internal class InterfaceManager : IDisposable, IServiceType
{ {
private const float MinimumFallbackFontSizePt = 9.6f; // Game's minimum AXIS font size private const float MinimumFallbackFontSizePt = 9.6f; // Game's minimum AXIS font size
private const float MinimumFallbackFontSizePx = MinimumFallbackFontSizePt * 4.0f / 3.0f; private const float MinimumFallbackFontSizePx = MinimumFallbackFontSizePt * 4.0f / 3.0f;
@ -61,7 +59,6 @@ namespace Dalamud.Interface.Internal
private readonly ManualResetEvent fontBuildSignal; private readonly ManualResetEvent fontBuildSignal;
private readonly SwapChainVtableResolver address; private readonly SwapChainVtableResolver address;
private readonly TaskCompletionSource sceneInitializeTaskCompletionSource = new();
private RawDX11Scene? scene; private RawDX11Scene? scene;
private Hook<PresentDelegate>? presentHook; private Hook<PresentDelegate>? presentHook;
@ -71,7 +68,7 @@ namespace Dalamud.Interface.Internal
// can't access imgui IO before first present call // can't access imgui IO before first present call
private bool lastWantCapture = false; private bool lastWantCapture = false;
private bool isRebuildingFonts = false; private bool isRebuildingFonts = false;
private bool isOverrideGameCursor = false;
private bool isFallbackFontMode = false; private bool isFallbackFontMode = false;
[ServiceManager.ServiceConstructor] [ServiceManager.ServiceConstructor]
@ -100,13 +97,6 @@ namespace Dalamud.Interface.Internal
{ {
Log.Error(e, "RTSS Free failed"); Log.Error(e, "RTSS Free failed");
} }
Task.Run(async () =>
{
var framework = await Service<Framework>.GetAsync();
var sigScanner = await Service<SigScanner>.GetAsync();
await framework.RunOnFrameworkThread(() => this.Enable(sigScanner));
});
} }
[UnmanagedFunctionPointer(CallingConvention.ThisCall)] [UnmanagedFunctionPointer(CallingConvention.ThisCall)]
@ -160,11 +150,6 @@ namespace Dalamud.Interface.Internal
/// </summary> /// </summary>
public static ImFontPtr MonoFont { get; private set; } public static ImFontPtr MonoFont { get; private set; }
/// <summary>
/// Gets a task that gets completed when scene gets initialized.
/// </summary>
public Task SceneInitializeTask => this.sceneInitializeTaskCompletionSource.Task;
/// <summary> /// <summary>
/// Gets or sets the pointer to ImGui.IO(), when it was last used. /// Gets or sets the pointer to ImGui.IO(), when it was last used.
/// </summary> /// </summary>
@ -178,15 +163,20 @@ namespace Dalamud.Interface.Internal
/// <summary> /// <summary>
/// Gets the address handle to the main process window. /// Gets the address handle to the main process window.
/// </summary> /// </summary>
public IntPtr WindowHandlePtr => this.scene.WindowHandlePtr; public IntPtr WindowHandlePtr => this.scene?.WindowHandlePtr ?? IntPtr.Zero;
/// <summary> /// <summary>
/// Gets or sets a value indicating whether or not the game's cursor should be overridden with the ImGui cursor. /// Gets or sets a value indicating whether or not the game's cursor should be overridden with the ImGui cursor.
/// </summary> /// </summary>
public bool OverrideGameCursor public bool OverrideGameCursor
{ {
get => this.scene.UpdateCursor; get => this.scene?.UpdateCursor ?? this.isOverrideGameCursor;
set => this.scene.UpdateCursor = value; set
{
this.isOverrideGameCursor = value;
if (this.scene != null)
this.scene.UpdateCursor = value;
}
} }
/// <summary> /// <summary>
@ -353,6 +343,12 @@ namespace Dalamud.Interface.Internal
/// </summary> /// </summary>
public void RebuildFonts() public void RebuildFonts()
{ {
if (this.scene == null)
{
Log.Verbose("[FONT] RebuildFonts(): scene not ready, doing nothing");
return;
}
Log.Verbose("[FONT] RebuildFonts() called"); Log.Verbose("[FONT] RebuildFonts() called");
// don't invoke this multiple times per frame, in case multiple plugins call it // don't invoke this multiple times per frame, in case multiple plugins call it
@ -448,7 +444,7 @@ namespace Dalamud.Interface.Internal
private IntPtr PresentDetour(IntPtr swapChain, uint syncInterval, uint presentFlags) private IntPtr PresentDetour(IntPtr swapChain, uint syncInterval, uint presentFlags)
{ {
if (this.scene != null && swapChain != this.scene.SwapChain.NativePointer) if (this.scene != null && swapChain != this.scene.SwapChain.NativePointer)
return this.presentHook.Original(swapChain, syncInterval, presentFlags); return this.presentHook!.Original(swapChain, syncInterval, presentFlags);
if (this.scene == null) if (this.scene == null)
{ {
@ -460,7 +456,7 @@ namespace Dalamud.Interface.Internal
} }
catch (DllNotFoundException ex) catch (DllNotFoundException ex)
{ {
this.sceneInitializeTaskCompletionSource.SetException(ex); Service<InterfaceManagerWithScene>.ProvideException(ex);
Log.Error(ex, "Could not load ImGui dependencies."); Log.Error(ex, "Could not load ImGui dependencies.");
var res = PInvoke.User32.MessageBox( var res = PInvoke.User32.MessageBox(
@ -501,6 +497,7 @@ namespace Dalamud.Interface.Internal
Log.Error(ex, "Could not delete dalamudUI.ini"); Log.Error(ex, "Could not delete dalamudUI.ini");
} }
this.scene.UpdateCursor = this.isOverrideGameCursor;
this.scene.ImGuiIniPath = iniFileInfo.FullName; this.scene.ImGuiIniPath = iniFileInfo.FullName;
this.scene.OnBuildUI += this.Display; this.scene.OnBuildUI += this.Display;
this.scene.OnNewInputFrame += this.OnNewInputFrame; this.scene.OnNewInputFrame += this.OnNewInputFrame;
@ -566,7 +563,7 @@ namespace Dalamud.Interface.Internal
Log.Information("[IM] Scene & ImGui setup OK!"); Log.Information("[IM] Scene & ImGui setup OK!");
Service<DalamudIME>.Get().Enable(); Service<Framework>.Get().RunOnFrameworkThread(() => Service<InterfaceManagerWithScene>.Provide(new(this)));
} }
} }
@ -576,17 +573,11 @@ namespace Dalamud.Interface.Internal
this.RenderImGui(); this.RenderImGui();
if (!this.SceneInitializeTask.IsCompleted)
this.sceneInitializeTaskCompletionSource.SetResult();
return pRes; return pRes;
} }
this.RenderImGui(); this.RenderImGui();
if (!this.SceneInitializeTask.IsCompleted)
this.sceneInitializeTaskCompletionSource.SetResult();
return this.presentHook.Original(swapChain, syncInterval, presentFlags); return this.presentHook.Original(swapChain, syncInterval, presentFlags);
} }
@ -987,21 +978,20 @@ namespace Dalamud.Interface.Internal
} }
} }
private void Enable(SigScanner sigScanner) [ServiceManager.CallWhenServicesReady]
private void ContinueConstruction(SigScanner sigScanner)
{ {
this.address.Setup(sigScanner); this.address.Setup(sigScanner);
this.setCursorHook = Hook<SetCursorDelegate>.FromSymbol("user32.dll", "SetCursor", this.SetCursorDetour, true); this.setCursorHook = Hook<SetCursorDelegate>.FromSymbol("user32.dll", "SetCursor", this.SetCursorDetour, true)!;
this.presentHook = new Hook<PresentDelegate>(this.address.Present, this.PresentDetour); this.presentHook = new Hook<PresentDelegate>(this.address.Present, this.PresentDetour);
this.resizeBuffersHook = new Hook<ResizeBuffersDelegate>(this.address.ResizeBuffers, this.ResizeBuffersDetour); this.resizeBuffersHook = new Hook<ResizeBuffersDelegate>(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("===== S W A P C H A I N =====");
Log.Verbose($"SetCursor address 0x{setCursorAddress.ToInt64():X}"); Log.Verbose($"SetCursor address 0x{this.setCursorHook!.Address.ToInt64():X}");
Log.Verbose($"Present address 0x{this.presentHook.Address.ToInt64():X}"); Log.Verbose($"Present address 0x{this.presentHook!.Address.ToInt64():X}");
Log.Verbose($"ResizeBuffers address 0x{this.resizeBuffersHook.Address.ToInt64():X}"); Log.Verbose($"ResizeBuffers address 0x{this.resizeBuffersHook!.Address.ToInt64():X}");
this.setCursorHook?.Enable(); this.setCursorHook.Enable();
this.presentHook.Enable(); this.presentHook.Enable();
this.resizeBuffersHook.Enable(); this.resizeBuffersHook.Enable();
@ -1027,8 +1017,8 @@ namespace Dalamud.Interface.Internal
private void Disable() private void Disable()
{ {
this.setCursorHook?.Disable(); this.setCursorHook?.Disable();
this.presentHook.Disable(); this.presentHook?.Disable();
this.resizeBuffersHook.Disable(); this.resizeBuffersHook?.Disable();
} }
// This is intended to only be called as a handler attached to scene.OnNewRenderFrame // This is intended to only be called as a handler attached to scene.OnNewRenderFrame
@ -1038,7 +1028,7 @@ namespace Dalamud.Interface.Internal
this.SetupFonts(); this.SetupFonts();
Log.Verbose("[FONT] RebuildFontsInternal() detaching"); Log.Verbose("[FONT] RebuildFontsInternal() detaching");
this.scene.OnNewRenderFrame -= this.RebuildFontsInternal; this.scene!.OnNewRenderFrame -= this.RebuildFontsInternal;
Log.Verbose("[FONT] Calling InvalidateFonts"); Log.Verbose("[FONT] Calling InvalidateFonts");
try try
@ -1086,11 +1076,11 @@ namespace Dalamud.Interface.Internal
// We have to ensure we're working with the main swapchain, // We have to ensure we're working with the main swapchain,
// as viewports might be resizing as well // as viewports might be resizing as well
if (this.scene == null || swapChain != this.scene.SwapChain.NativePointer) if (this.scene == null || swapChain != this.scene.SwapChain.NativePointer)
return this.resizeBuffersHook.Original(swapChain, bufferCount, width, height, newFormat, swapChainFlags); return this.resizeBuffersHook!.Original(swapChain, bufferCount, width, height, newFormat, swapChainFlags);
this.scene?.OnPreResize(); this.scene?.OnPreResize();
var ret = this.resizeBuffersHook.Original(swapChain, bufferCount, width, height, newFormat, swapChainFlags); var ret = this.resizeBuffersHook!.Original(swapChain, bufferCount, width, height, newFormat, swapChainFlags);
if (ret.ToInt64() == 0x887A0001) if (ret.ToInt64() == 0x887A0001)
{ {
Log.Error("invalid call to resizeBuffers"); Log.Error("invalid call to resizeBuffers");
@ -1106,7 +1096,7 @@ namespace Dalamud.Interface.Internal
if (this.lastWantCapture == true && (!this.scene?.IsImGuiCursor(hCursor) ?? false) && this.OverrideGameCursor) if (this.lastWantCapture == true && (!this.scene?.IsImGuiCursor(hCursor) ?? false) && this.OverrideGameCursor)
return IntPtr.Zero; return IntPtr.Zero;
return this.setCursorHook.Original(hCursor); return this.setCursorHook!.Original(hCursor);
} }
private void OnNewInputFrame() private void OnNewInputFrame()
@ -1194,6 +1184,26 @@ namespace Dalamud.Interface.Internal
Service<NotificationManager>.Get().Draw(); Service<NotificationManager>.Get().Draw();
} }
/// <summary>
/// Represents an instance of InstanceManager with scene ready for use.
/// </summary>
public class InterfaceManagerWithScene : IServiceType
{
/// <summary>
/// Initializes a new instance of the <see cref="InterfaceManagerWithScene"/> class.
/// </summary>
/// <param name="interfaceManager">An instance of <see cref="InterfaceManager"/>.</param>
internal InterfaceManagerWithScene(InterfaceManager interfaceManager)
{
this.Manager = interfaceManager;
}
/// <summary>
/// Associated InterfaceManager.
/// </summary>
public InterfaceManager Manager { get; init; }
}
/// <summary> /// <summary>
/// Represents a glyph request. /// Represents a glyph request.
/// </summary> /// </summary>

View file

@ -14,7 +14,7 @@ namespace Dalamud.Interface.Internal.Notifications
/// Ported from https://github.com/patrickcjk/imgui-notify. /// Ported from https://github.com/patrickcjk/imgui-notify.
/// </summary> /// </summary>
[ServiceManager.EarlyLoadedService] [ServiceManager.EarlyLoadedService]
internal class NotificationManager internal class NotificationManager : IServiceType
{ {
/// <summary> /// <summary>
/// Value indicating the bottom-left X padding. /// Value indicating the bottom-left X padding.

View file

@ -13,7 +13,7 @@ namespace Dalamud.Interface
[PluginInterface] [PluginInterface]
[InterfaceVersion("1.0")] [InterfaceVersion("1.0")]
[ServiceManager.BlockingEarlyLoadedService] [ServiceManager.BlockingEarlyLoadedService]
public class TitleScreenMenu public class TitleScreenMenu : IServiceType
{ {
/// <summary> /// <summary>
/// Gets the texture size needed for title screen menu logos. /// Gets the texture size needed for title screen menu logos.

View file

@ -109,12 +109,12 @@ namespace Dalamud.Interface
/// <summary> /// <summary>
/// Gets the game's active Direct3D device. /// Gets the game's active Direct3D device.
/// </summary> /// </summary>
public Device Device => Service<InterfaceManager>.Get().Device; public Device Device => Service<InterfaceManager.InterfaceManagerWithScene>.Get().Manager.Device!;
/// <summary> /// <summary>
/// Gets the game's main window handle. /// Gets the game's main window handle.
/// </summary> /// </summary>
public IntPtr WindowHandlePtr => Service<InterfaceManager>.Get().WindowHandlePtr; public IntPtr WindowHandlePtr => Service<InterfaceManager.InterfaceManagerWithScene>.Get().Manager.WindowHandlePtr;
/// <summary> /// <summary>
/// Gets or sets a value indicating whether this plugin should hide its UI automatically when the game's UI is hidden. /// Gets or sets a value indicating whether this plugin should hide its UI automatically when the game's UI is hidden.
@ -215,7 +215,7 @@ namespace Dalamud.Interface
/// <param name="filePath">The full filepath to the image.</param> /// <param name="filePath">The full filepath to the image.</param>
/// <returns>A <see cref="TextureWrap"/> object wrapping the created image. Use <see cref="TextureWrap.ImGuiHandle"/> inside ImGui.Image().</returns> /// <returns>A <see cref="TextureWrap"/> object wrapping the created image. Use <see cref="TextureWrap.ImGuiHandle"/> inside ImGui.Image().</returns>
public TextureWrap LoadImage(string filePath) public TextureWrap LoadImage(string filePath)
=> Service<InterfaceManager>.Get().LoadImage(filePath); => Service<InterfaceManager.InterfaceManagerWithScene>.Get().Manager.LoadImage(filePath);
/// <summary> /// <summary>
/// Loads an image from a byte stream, such as a png downloaded into memory. /// Loads an image from a byte stream, such as a png downloaded into memory.
@ -223,7 +223,7 @@ namespace Dalamud.Interface
/// <param name="imageData">A byte array containing the raw image data.</param> /// <param name="imageData">A byte array containing the raw image data.</param>
/// <returns>A <see cref="TextureWrap"/> object wrapping the created image. Use <see cref="TextureWrap.ImGuiHandle"/> inside ImGui.Image().</returns> /// <returns>A <see cref="TextureWrap"/> object wrapping the created image. Use <see cref="TextureWrap.ImGuiHandle"/> inside ImGui.Image().</returns>
public TextureWrap LoadImage(byte[] imageData) public TextureWrap LoadImage(byte[] imageData)
=> Service<InterfaceManager>.Get().LoadImage(imageData); => Service<InterfaceManager.InterfaceManagerWithScene>.Get().Manager.LoadImage(imageData);
/// <summary> /// <summary>
/// Loads an image from raw unformatted pixel data, with no type or header information. To load formatted data, use <see cref="LoadImage(byte[])"/>. /// Loads an image from raw unformatted pixel data, with no type or header information. To load formatted data, use <see cref="LoadImage(byte[])"/>.
@ -234,7 +234,7 @@ namespace Dalamud.Interface
/// <param name="numChannels">The number of channels (bytes per pixel) of the image contained in <paramref name="imageData"/>. This should usually be 4.</param> /// <param name="numChannels">The number of channels (bytes per pixel) of the image contained in <paramref name="imageData"/>. This should usually be 4.</param>
/// <returns>A <see cref="TextureWrap"/> object wrapping the created image. Use <see cref="TextureWrap.ImGuiHandle"/> inside ImGui.Image().</returns> /// <returns>A <see cref="TextureWrap"/> object wrapping the created image. Use <see cref="TextureWrap.ImGuiHandle"/> inside ImGui.Image().</returns>
public TextureWrap LoadImageRaw(byte[] imageData, int width, int height, int numChannels) public TextureWrap LoadImageRaw(byte[] imageData, int width, int height, int numChannels)
=> Service<InterfaceManager>.Get().LoadImageRaw(imageData, width, height, numChannels); => Service<InterfaceManager.InterfaceManagerWithScene>.Get().Manager.LoadImageRaw(imageData, width, height, numChannels);
/// <summary> /// <summary>
/// Gets a game font. /// Gets a game font.

View file

@ -12,7 +12,7 @@ namespace Dalamud.IoC.Internal
/// <summary> /// <summary>
/// A simple singleton-only IOC container that provides (optional) version-based dependency resolution. /// A simple singleton-only IOC container that provides (optional) version-based dependency resolution.
/// </summary> /// </summary>
internal class ServiceContainer : IServiceProvider internal class ServiceContainer : IServiceProvider, IServiceType
{ {
private static readonly ModuleLog Log = new("SERVICECONTAINER"); private static readonly ModuleLog Log = new("SERVICECONTAINER");

View file

@ -14,7 +14,7 @@ namespace Dalamud
/// Class handling localization. /// Class handling localization.
/// </summary> /// </summary>
[ServiceManager.EarlyLoadedService] [ServiceManager.EarlyLoadedService]
public class Localization public class Localization : IServiceType
{ {
/// <summary> /// <summary>
/// Array of language codes which have a valid translation in Dalamud. /// Array of language codes which have a valid translation in Dalamud.

View file

@ -13,7 +13,7 @@ namespace Dalamud.Logging.Internal
/// Class responsible for tracking asynchronous tasks. /// Class responsible for tracking asynchronous tasks.
/// </summary> /// </summary>
[ServiceManager.EarlyLoadedService] [ServiceManager.EarlyLoadedService]
internal class TaskTracker : IDisposable internal class TaskTracker : IDisposable, IServiceType
{ {
private static readonly ModuleLog Log = new("TT"); private static readonly ModuleLog Log = new("TT");
private static readonly List<TaskInfo> TrackedTasksInternal = new(); private static readonly List<TaskInfo> TrackedTasksInternal = new();

View file

@ -31,7 +31,7 @@ namespace Dalamud.Plugin.Internal;
/// Class responsible for loading and unloading plugins. /// Class responsible for loading and unloading plugins.
/// </summary> /// </summary>
[ServiceManager.EarlyLoadedService] [ServiceManager.EarlyLoadedService]
internal partial class PluginManager : IDisposable internal partial class PluginManager : IDisposable, IServiceType
{ {
/// <summary> /// <summary>
/// The current Dalamud API level, used to handle breaking changes. Only plugins with this level will be loaded. /// The current Dalamud API level, used to handle breaking changes. Only plugins with this level will be loaded.
@ -386,12 +386,8 @@ internal partial class PluginManager : IDisposable
// Load plugins that want to be loaded during Framework.Tick, when drawing facilities are available // Load plugins that want to be loaded during Framework.Tick, when drawing facilities are available
loadTasks.Add( loadTasks.Add(
Service<InterfaceManager> Service<InterfaceManager.InterfaceManagerWithScene>
.GetAsync() .GetAsync()
.ContinueWith(
x => x.Result.SceneInitializeTask,
TaskContinuationOptions.RunContinuationsAsynchronously)
.Unwrap()
.ContinueWith( .ContinueWith(
_ => Service<Framework>.Get().RunOnTick( _ => Service<Framework>.Get().RunOnTick(
() => LoadPluginsSync( () => LoadPluginsSync(

View file

@ -10,7 +10,7 @@ namespace Dalamud.Plugin.Internal;
/// Class responsible for loading plugins on startup. /// Class responsible for loading plugins on startup.
/// </summary> /// </summary>
[ServiceManager.BlockingEarlyLoadedService] [ServiceManager.BlockingEarlyLoadedService]
public class StartupPluginLoader public class StartupPluginLoader : IServiceType
{ {
private static readonly ModuleLog Log = new("SPL"); private static readonly ModuleLog Log = new("SPL");

View file

@ -6,7 +6,7 @@ namespace Dalamud.Plugin.Ipc.Internal
/// This class facilitates inter-plugin communication. /// This class facilitates inter-plugin communication.
/// </summary> /// </summary>
[ServiceManager.EarlyLoadedService] [ServiceManager.EarlyLoadedService]
internal class CallGate internal class CallGate : IServiceType
{ {
private readonly Dictionary<string, CallGateChannel> gates = new(); private readonly Dictionary<string, CallGateChannel> gates = new();

View file

@ -6,7 +6,6 @@ using System.Reflection;
using System.Threading.Tasks; using System.Threading.Tasks;
using Dalamud.Configuration.Internal; using Dalamud.Configuration.Internal;
using Dalamud.Game; using Dalamud.Game;
using Dalamud.Interface.Internal;
using Dalamud.IoC.Internal; using Dalamud.IoC.Internal;
using Dalamud.Logging.Internal; using Dalamud.Logging.Internal;
using Dalamud.Utility.Timing; using Dalamud.Utility.Timing;
@ -67,7 +66,6 @@ namespace Dalamud
var earlyLoadingServices = new HashSet<Type>(); var earlyLoadingServices = new HashSet<Type>();
var blockingEarlyLoadingServices = new HashSet<Type>(); var blockingEarlyLoadingServices = new HashSet<Type>();
var afterDrawingEarlyLoadedServices = new HashSet<Type>();
var dependencyServicesMap = new Dictionary<Type, List<Type>>(); var dependencyServicesMap = new Dictionary<Type, List<Type>>();
var getAsyncTaskMap = new Dictionary<Type, Task>(); var getAsyncTaskMap = new Dictionary<Type, Task>();
@ -90,10 +88,6 @@ namespace Dalamud
getAsyncTaskMap[serviceType] = getTask; getAsyncTaskMap[serviceType] = getTask;
blockingEarlyLoadingServices.Add(serviceType); blockingEarlyLoadingServices.Add(serviceType);
} }
else if (attr.IsAssignableTo(typeof(AfterDrawingEarlyLoadedService)))
{
afterDrawingEarlyLoadedServices.Add(serviceType);
}
else else
{ {
earlyLoadingServices.Add(serviceType); earlyLoadingServices.Add(serviceType);
@ -126,55 +120,44 @@ namespace Dalamud
try try
{ {
for (var i = 0; i < 2; i++) var tasks = new List<Task>();
var servicesToLoad = new HashSet<Type>();
servicesToLoad.UnionWith(earlyLoadingServices);
servicesToLoad.UnionWith(blockingEarlyLoadingServices);
while (servicesToLoad.Any())
{ {
var tasks = new List<Task>(); foreach (var serviceType in servicesToLoad)
var servicesToLoad = new HashSet<Type>();
if (i == 0)
{ {
servicesToLoad.UnionWith(earlyLoadingServices); if (dependencyServicesMap[serviceType].Any(
servicesToLoad.UnionWith(blockingEarlyLoadingServices); x => getAsyncTaskMap.GetValueOrDefault(x)?.IsCompleted == false))
continue;
tasks.Add((Task)service.MakeGenericType(serviceType).InvokeMember(
"StartLoader",
BindingFlags.InvokeMethod | BindingFlags.Static | BindingFlags.Public,
null,
null,
null));
servicesToLoad.Remove(serviceType);
}
if (!tasks.Any())
throw new InvalidOperationException("Unresolvable dependency cycle detected");
if (servicesToLoad.Any())
{
await Task.WhenAny(tasks);
var faultedTasks = tasks.Where(x => x.IsFaulted).Select(x => (Exception)x.Exception!).ToArray();
if (faultedTasks.Any())
throw new AggregateException(faultedTasks);
} }
else else
{ {
servicesToLoad.UnionWith(afterDrawingEarlyLoadedServices); await Task.WhenAll(tasks);
await (await Service<InterfaceManager>.GetAsync()).SceneInitializeTask;
} }
while (servicesToLoad.Any()) tasks.RemoveAll(x => x.IsCompleted);
{
foreach (var serviceType in servicesToLoad)
{
if (dependencyServicesMap[serviceType].Any(
x => getAsyncTaskMap.GetValueOrDefault(x)?.IsCompleted == false))
continue;
tasks.Add((Task)service.MakeGenericType(serviceType).InvokeMember(
"StartLoader",
BindingFlags.InvokeMethod | BindingFlags.Static | BindingFlags.Public,
null,
null,
null));
servicesToLoad.Remove(serviceType);
}
if (!tasks.Any())
throw new InvalidOperationException("Unresolvable dependency cycle detected");
if (servicesToLoad.Any())
{
await Task.WhenAny(tasks);
var faultedTasks = tasks.Where(x => x.IsFaulted).Select(x => (Exception)x.Exception!).ToArray();
if (faultedTasks.Any())
throw new AggregateException(faultedTasks);
}
else
{
await Task.WhenAll(tasks);
}
tasks.RemoveAll(x => x.IsCompleted);
}
} }
} }
catch (Exception e) catch (Exception e)
@ -236,11 +219,11 @@ namespace Dalamud
} }
/// <summary> /// <summary>
/// Indicates that the class is a service, and will be instantiated automatically on startup, /// Indicates that the method should be called when the services given in the constructor are ready.
/// when drawing becomes available.
/// </summary> /// </summary>
[AttributeUsage(AttributeTargets.Class)] [AttributeUsage(AttributeTargets.Method)]
public class AfterDrawingEarlyLoadedService : EarlyLoadedService [MeansImplicitUse]
public class CallWhenServicesReady : Attribute
{ {
} }
} }

View file

@ -2,7 +2,6 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Dalamud.IoC; using Dalamud.IoC;
using Dalamud.IoC.Internal; using Dalamud.IoC.Internal;
@ -18,7 +17,7 @@ namespace Dalamud
/// Only used internally within Dalamud, if plugins need access to things it should be _only_ via DI. /// Only used internally within Dalamud, if plugins need access to things it should be _only_ via DI.
/// </remarks> /// </remarks>
/// <typeparam name="T">The class you want to store in the service locator.</typeparam> /// <typeparam name="T">The class you want to store in the service locator.</typeparam>
internal static class Service<T> internal static class Service<T> where T : IServiceType
{ {
// ReSharper disable once StaticMemberInGenericType // ReSharper disable once StaticMemberInGenericType
private static readonly TaskCompletionSource<T> InstanceTcs = new(); private static readonly TaskCompletionSource<T> InstanceTcs = new();
@ -51,10 +50,23 @@ namespace Dalamud
ServiceManager.Log.Debug("Service<{0}>: Begin construction", typeof(T).Name); ServiceManager.Log.Debug("Service<{0}>: Begin construction", typeof(T).Name);
try try
{ {
var x = await ConstructObject(); var instance = await ConstructObject();
InstanceTcs.SetResult(instance);
foreach (var method in typeof(T).GetMethods(
BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic))
{
if (method.GetCustomAttribute<ServiceManager.CallWhenServicesReady>(true) == null)
continue;
ServiceManager.Log.Debug("Service<{0}>: Calling {1}", typeof(T).Name, method.Name);
var args = await Task.WhenAll(method.GetParameters().Select(
x => ResolveServiceFromTypeAsync(x.ParameterType)));
method.Invoke(instance, args);
}
ServiceManager.Log.Debug("Service<{0}>: Construction complete", typeof(T).Name); ServiceManager.Log.Debug("Service<{0}>: Construction complete", typeof(T).Name);
InstanceTcs.SetResult(x); return instance;
return x;
} }
catch (Exception e) catch (Exception e)
{ {
@ -71,10 +83,20 @@ namespace Dalamud
/// <param name="obj">Object to set.</param> /// <param name="obj">Object to set.</param>
public static void Provide(T obj) public static void Provide(T obj)
{ {
InstanceTcs!.SetResult(obj); InstanceTcs.SetResult(obj);
ServiceManager.Log.Debug("Service<{0}>: Provided", typeof(T).Name); ServiceManager.Log.Debug("Service<{0}>: Provided", typeof(T).Name);
} }
/// <summary>
/// Sets the service load state to failure.
/// </summary>
/// <param name="exception">The exception.</param>
public static void ProvideException(Exception exception)
{
ServiceManager.Log.Error(exception, "Service<{0}>: Error", typeof(T).Name);
InstanceTcs.SetException(exception);
}
/// <summary> /// <summary>
/// Pull the instance out of the service locator, waiting if necessary. /// Pull the instance out of the service locator, waiting if necessary.
/// </summary> /// </summary>
@ -103,6 +125,7 @@ namespace Dalamud
/// Gets an enumerable containing Service&lt;T&gt;s that are required for this Service to initialize without blocking. /// Gets an enumerable containing Service&lt;T&gt;s that are required for this Service to initialize without blocking.
/// </summary> /// </summary>
/// <returns>List of dependency services.</returns> /// <returns>List of dependency services.</returns>
[UsedImplicitly]
public static List<Type> GetDependencyServices() public static List<Type> GetDependencyServices()
{ {
var res = new List<Type>(); var res = new List<Type>();
@ -119,7 +142,7 @@ namespace Dalamud
.ToList(); .ToList();
} }
private static async Task<object?> GetServiceObjectConstructArgument(Type type) private static async Task<object?> ResolveServiceFromTypeAsync(Type type)
{ {
var task = (Task)typeof(Service<>) var task = (Task)typeof(Service<>)
.MakeGenericType(type) .MakeGenericType(type)
@ -151,7 +174,7 @@ namespace Dalamud
{ {
var ctor = GetServiceConstructor(); var ctor = GetServiceConstructor();
var args = await Task.WhenAll( var args = await Task.WhenAll(
ctor.GetParameters().Select(x => GetServiceObjectConstructArgument(x.ParameterType))); ctor.GetParameters().Select(x => ResolveServiceFromTypeAsync(x.ParameterType)));
using (Timings.Start($"{typeof(T).Name} Construct")) using (Timings.Start($"{typeof(T).Name} Construct"))
{ {
return (T)ctor.Invoke(args)!; return (T)ctor.Invoke(args)!;