Merge branch 'net5'

This commit is contained in:
goaaats 2022-06-24 22:16:24 +02:00
commit 1a2365a676
No known key found for this signature in database
GPG key ID: 49E2AA8C6A76498B
66 changed files with 964 additions and 899 deletions

View file

@ -21,6 +21,8 @@ void from_json(const nlohmann::json& json, DalamudStartInfo::WaitMessageboxFlags
value = static_cast<DalamudStartInfo::WaitMessageboxFlags>(static_cast<int>(value) | static_cast<int>(DalamudStartInfo::WaitMessageboxFlags::BeforeInitialize)); value = static_cast<DalamudStartInfo::WaitMessageboxFlags>(static_cast<int>(value) | static_cast<int>(DalamudStartInfo::WaitMessageboxFlags::BeforeInitialize));
else if (item == "beforedalamudentrypoint") else if (item == "beforedalamudentrypoint")
value = static_cast<DalamudStartInfo::WaitMessageboxFlags>(static_cast<int>(value) | static_cast<int>(DalamudStartInfo::WaitMessageboxFlags::BeforeDalamudEntrypoint)); value = static_cast<DalamudStartInfo::WaitMessageboxFlags>(static_cast<int>(value) | static_cast<int>(DalamudStartInfo::WaitMessageboxFlags::BeforeDalamudEntrypoint));
else if (item == "beforedalamudconstruct")
value = static_cast<DalamudStartInfo::WaitMessageboxFlags>(static_cast<int>(value) | static_cast<int>(DalamudStartInfo::WaitMessageboxFlags::BeforeDalamudConstruct));
} }
} }

View file

@ -5,6 +5,7 @@ struct DalamudStartInfo {
None = 0, None = 0,
BeforeInitialize = 1 << 0, BeforeInitialize = 1 << 0,
BeforeDalamudEntrypoint = 1 << 1, BeforeDalamudEntrypoint = 1 << 1,
BeforeDalamudConstruct = 1 << 2,
}; };
friend void from_json(const nlohmann::json&, WaitMessageboxFlags&); friend void from_json(const nlohmann::json&, WaitMessageboxFlags&);

View file

@ -9,7 +9,7 @@
HMODULE g_hModule; HMODULE g_hModule;
HINSTANCE g_hGameInstance = GetModuleHandleW(nullptr); HINSTANCE g_hGameInstance = GetModuleHandleW(nullptr);
DllExport DWORD WINAPI Initialize(LPVOID lpParam, HANDLE hMainThreadContinue) { DWORD WINAPI InitializeImpl(LPVOID lpParam, HANDLE hMainThreadContinue) {
g_startInfo.from_envvars(); g_startInfo.from_envvars();
std::string jsonParseError; std::string jsonParseError;
@ -149,11 +149,6 @@ DllExport DWORD WINAPI Initialize(LPVOID lpParam, HANDLE hMainThreadContinue) {
if (static_cast<int>(g_startInfo.BootWaitMessageBox) & static_cast<int>(DalamudStartInfo::WaitMessageboxFlags::BeforeDalamudEntrypoint)) if (static_cast<int>(g_startInfo.BootWaitMessageBox) & static_cast<int>(DalamudStartInfo::WaitMessageboxFlags::BeforeDalamudEntrypoint))
MessageBoxW(nullptr, L"Press OK to continue (BeforeDalamudEntrypoint)", L"Dalamud Boot", MB_OK); 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! // We don't need to do this anymore, Dalamud now loads without needing the window to be there. Speed!
// utils::wait_for_game_window(); // utils::wait_for_game_window();
@ -164,6 +159,10 @@ DllExport DWORD WINAPI Initialize(LPVOID lpParam, HANDLE hMainThreadContinue) {
return 0; 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) { BOOL APIENTRY DllMain(const HMODULE hModule, const DWORD dwReason, LPVOID lpReserved) {
DisableThreadLibraryCalls(hModule); DisableThreadLibraryCalls(hModule);

View file

@ -2,7 +2,7 @@
#include "logging.h" #include "logging.h"
DllExport DWORD WINAPI Initialize(LPVOID lpParam, HANDLE hMainThreadContinue); DWORD WINAPI InitializeImpl(LPVOID lpParam, HANDLE hMainThreadContinue);
struct RewrittenEntryPointParameters { struct RewrittenEntryPointParameters {
void* pAllocation; void* pAllocation;
@ -368,7 +368,7 @@ DllExport void WINAPI RewrittenEntryPoint(RewrittenEntryPointParameters& params)
loadInfo = params.pLoadInfo; loadInfo = params.pLoadInfo;
} }
Initialize(&loadInfo[0], params.hMainThreadContinue); InitializeImpl(&loadInfo[0], params.hMainThreadContinue);
return 0; return 0;
} catch (const std::exception& e) { } 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); 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); CloseHandle(params.hMainThread);
WaitForSingleObject(params.hMainThreadContinue, INFINITE); WaitForSingleObject(params.hMainThreadContinue, INFINITE);
CloseHandle(params.hMainThreadContinue);
VirtualFree(params.pAllocation, 0, MEM_RELEASE); VirtualFree(params.pAllocation, 0, MEM_RELEASE);
} }

View file

@ -61,6 +61,7 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="Iced" Version="1.13.0" /> <PackageReference Include="Iced" Version="1.13.0" />
<PackageReference Include="JetBrains.Annotations" Version="2022.1.0" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" /> <PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
<PackageReference Include="PeNet" Version="2.6.4" /> <PackageReference Include="PeNet" Version="2.6.4" />
<PackageReference Include="Reloaded.Memory" Version="4.1.1" /> <PackageReference Include="Reloaded.Memory" Version="4.1.1" />

View file

@ -83,8 +83,14 @@ namespace Dalamud.Injector
} }
startInfo = ExtractAndInitializeStartInfoFromArguments(startInfo, args); startInfo = ExtractAndInitializeStartInfoFromArguments(startInfo, args);
args.Remove("--console"); // Remove "console" flag, already handled // Remove already handled arguments
args.Remove("--etw"); // Remove "etw" flag, already handled 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(); var mainCommand = args[1].ToLowerInvariant();
if (mainCommand.Length > 0 && mainCommand.Length <= 6 && "inject"[..mainCommand.Length] == mainCommand) if (mainCommand.Length > 0 && mainCommand.Length <= 6 && "inject"[..mainCommand.Length] == mainCommand)
@ -311,7 +317,11 @@ namespace Dalamud.Injector
startInfo.BootLogPath = GetLogPath("dalamud.boot"); startInfo.BootLogPath = GetLogPath("dalamud.boot");
startInfo.BootEnabledGameFixes = new List<string> { "prevent_devicechange_crashes", "disable_game_openprocess_access_check", "redirect_openprocess" }; startInfo.BootEnabledGameFixes = new List<string> { "prevent_devicechange_crashes", "disable_game_openprocess_access_check", "redirect_openprocess" };
startInfo.BootDotnetOpenProcessHookMode = 0; 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<string>() { "kernel32.dll", "ntdll.dll", "user32.dll" }; // startInfo.BootUnhookDlls = new List<string>() { "kernel32.dll", "ntdll.dll", "user32.dll" };
return startInfo; return startInfo;
@ -338,6 +348,7 @@ namespace Dalamud.Injector
Console.WriteLine("{0} [-m entrypoint|inject] [--mode=entrypoint|inject]", exeSpaces); Console.WriteLine("{0} [-m entrypoint|inject] [--mode=entrypoint|inject]", exeSpaces);
Console.WriteLine("{0} [--handle-owner=inherited-handle-value]", exeSpaces); Console.WriteLine("{0} [--handle-owner=inherited-handle-value]", exeSpaces);
Console.WriteLine("{0} [--without-dalamud] [--no-fix-acl]", 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); 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("Verbose logging:\t[-v]");
Console.WriteLine("Show Console:\t[--console]"); Console.WriteLine("Show Console:\t[--console]");
Console.WriteLine("Enable ETW:\t[--etw]"); Console.WriteLine("Enable ETW:\t[--etw]");
Console.WriteLine("Enable VEH:\t[--veh], [--veh-full]");
Console.WriteLine("Show messagebox:\t[--msgbox1], [--msgbox2], [--msgbox3]");
return 0; return 0;
} }
@ -464,6 +477,7 @@ namespace Dalamud.Injector
var handleOwner = IntPtr.Zero; var handleOwner = IntPtr.Zero;
var withoutDalamud = false; var withoutDalamud = false;
var noFixAcl = false; var noFixAcl = false;
var waitForGameWindow = true;
var parsingGameArgument = false; var parsingGameArgument = false;
for (var i = 2; i < args.Count; i++) for (var i = 2; i < args.Count; i++)
@ -480,6 +494,8 @@ namespace Dalamud.Injector
useFakeArguments = true; useFakeArguments = true;
else if (args[i] == "--without-dalamud") else if (args[i] == "--without-dalamud")
withoutDalamud = true; withoutDalamud = true;
else if (args[i] == "--no-wait")
waitForGameWindow = false;
else if (args[i] == "--no-fix-acl" || args[i] == "--no-acl-fix") else if (args[i] == "--no-fix-acl" || args[i] == "--no-acl-fix")
noFixAcl = true; noFixAcl = true;
else if (args[i] == "-g") else if (args[i] == "-g")
@ -596,7 +612,7 @@ namespace Dalamud.Injector
Log.Verbose("RewriteRemoteEntryPointW called!"); Log.Verbose("RewriteRemoteEntryPointW called!");
} }
}); }, waitForGameWindow);
Log.Verbose("Game process started with PID {0}", process.Id); Log.Verbose("Game process started with PID {0}", process.Id);

View file

@ -235,6 +235,11 @@ namespace Dalamud.Configuration.Internal
/// </summary> /// </summary>
public bool IsAntiAntiDebugEnabled { get; set; } = false; public bool IsAntiAntiDebugEnabled { get; set; } = false;
/// <summary>
/// Gets or sets a value indicating whether to resume game main thread after plugins load.
/// </summary>
public bool IsResumeGameAfterPluginLoad { get; set; } = false;
/// <summary> /// <summary>
/// Gets or sets the kind of beta to download when <see cref="DalamudBetaKey"/> matches the server value. /// Gets or sets the kind of beta to download when <see cref="DalamudBetaKey"/> matches the server value.
/// </summary> /// </summary>

View file

@ -4,7 +4,7 @@ using System.IO;
using System.Reflection; using System.Reflection;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Threading; using System.Threading;
using System.Threading.Tasks;
using Dalamud.Configuration.Internal; using Dalamud.Configuration.Internal;
using Dalamud.Data; using Dalamud.Data;
using Dalamud.Game; using Dalamud.Game;
@ -49,7 +49,6 @@ namespace Dalamud
private readonly ManualResetEvent unloadSignal; private readonly ManualResetEvent unloadSignal;
private readonly ManualResetEvent finishUnloadSignal; private readonly ManualResetEvent finishUnloadSignal;
private readonly IntPtr mainThreadContinueEvent;
private MonoMod.RuntimeDetour.Hook processMonoHook; private MonoMod.RuntimeDetour.Hook processMonoHook;
private bool hasDisposedPlugins = false; private bool hasDisposedPlugins = false;
@ -65,10 +64,6 @@ namespace Dalamud
/// <param name="mainThreadContinueEvent">Event used to signal the main thread to continue.</param> /// <param name="mainThreadContinueEvent">Event used to signal the main thread to continue.</param>
public Dalamud(DalamudStartInfo info, LoggingLevelSwitch loggingLevelSwitch, ManualResetEvent finishSignal, DalamudConfiguration configuration, IntPtr mainThreadContinueEvent) public Dalamud(DalamudStartInfo info, LoggingLevelSwitch loggingLevelSwitch, ManualResetEvent finishSignal, DalamudConfiguration configuration, IntPtr mainThreadContinueEvent)
{ {
Service<Dalamud>.Set(this);
Service<DalamudStartInfo>.Set(info);
Service<DalamudConfiguration>.Set(configuration);
this.LogLevelSwitch = loggingLevelSwitch; this.LogLevelSwitch = loggingLevelSwitch;
this.unloadSignal = new ManualResetEvent(false); this.unloadSignal = new ManualResetEvent(false);
@ -77,7 +72,55 @@ namespace Dalamud
this.finishUnloadSignal = finishSignal; this.finishUnloadSignal = finishSignal;
this.finishUnloadSignal.Reset(); this.finishUnloadSignal.Reset();
this.mainThreadContinueEvent = mainThreadContinueEvent; SerilogEventSink.Instance.LogLine += SerilogOnLogLine;
Service<Dalamud>.Provide(this);
Service<DalamudStartInfo>.Provide(info);
Service<DalamudConfiguration>.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);
}
});
}
} }
/// <summary> /// <summary>
@ -88,262 +131,7 @@ namespace Dalamud
/// <summary> /// <summary>
/// Gets location of stored assets. /// Gets location of stored assets.
/// </summary> /// </summary>
internal DirectoryInfo AssetDirectory => new(Service<DalamudStartInfo>.Get().AssetDirectory); internal DirectoryInfo AssetDirectory => new(Service<DalamudStartInfo>.Get().AssetDirectory!);
/// <summary>
/// Runs tier 1 of the Dalamud initialization process.
/// </summary>
public void LoadTier1()
{
using var tier1Timing = Timings.Start("Tier 1 Init");
try
{
SerilogEventSink.Instance.LogLine += SerilogOnLogLine;
Service<ServiceContainer>.Set();
// Initialize the process information.
var info = Service<DalamudStartInfo>.Get();
var cacheDir = new DirectoryInfo(Path.Combine(info.WorkingDirectory!, "cachedSigs"));
if (!cacheDir.Exists)
cacheDir.Create();
Service<SigScanner>.Set(
new SigScanner(true, new FileInfo(Path.Combine(cacheDir.FullName, $"{info.GameVersion}.json"))));
Service<HookManager>.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<Framework>.Set();
Log.Information("[T1] Framework OK!");
#if DEBUG
Service<TaskTracker>.Set();
Log.Information("[T1] TaskTracker OK!");
#endif
Service<GameNetwork>.Set();
Service<GameGui>.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!");
}
}
/// <summary>
/// Runs tier 2 of the Dalamud initialization process.
/// </summary>
/// <returns>Whether or not the load succeeded.</returns>
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<DalamudConfiguration>.Get();
var antiDebug = Service<AntiDebug>.Set();
if (!antiDebug.IsEnabled)
{
#if DEBUG
antiDebug.Enable();
#else
if (configuration.IsAntiAntiDebugEnabled)
antiDebug.Enable();
#endif
}
Log.Information("[T2] AntiDebug OK!");
Service<WinSockHandlers>.Set();
Log.Information("[T2] WinSock OK!");
Service<NetworkHandlers>.Set();
Log.Information("[T2] NH OK!");
using (Timings.Start("DM Init"))
{
try
{
Service<DataManager>.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<ClientState>.Set();
Log.Information("[T2] CS OK!");
}
var localization = Service<Localization>.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<DalamudIME>.Set();
Log.Information("[T2] IME OK!");
}
using (Timings.Start("IM Enable"))
{
Service<InterfaceManager>.Set().Enable();
Log.Information("[T2] IM OK!");
}
using (Timings.Start("GFM Init"))
{
Service<GameFontManager>.Set();
Log.Information("[T2] GFM OK!");
}
#pragma warning disable CS0618 // Type or member is obsolete
Service<SeStringManager>.Set();
#pragma warning restore CS0618 // Type or member is obsolete
Log.Information("[T2] SeString OK!");
using (Timings.Start("CM Init"))
{
Service<CommandManager>.Set();
Service<DalamudCommands>.Set().SetupCommands();
Log.Information("[T2] CM OK!");
}
Service<ChatHandlers>.Set();
Log.Information("[T2] CH OK!");
using (Timings.Start("CS Enable"))
{
Service<ClientState>.Get().Enable();
Log.Information("[T2] CS ENABLE!");
}
Service<DalamudAtkTweaks>.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;
}
/// <summary>
/// Runs tier 3 of the Dalamud initialization process.
/// </summary>
/// <returns>Whether or not the load succeeded.</returns>
public bool LoadTier3()
{
using var tier3Timing = Timings.Start("Tier 3 Init");
ThreadSafety.AssertMainThread();
try
{
Log.Information("[T3] START!");
Service<TitleScreenMenu>.Set();
PluginManager pluginManager;
using (Timings.Start("PM Init"))
{
pluginManager = Service<PluginManager>.Set();
Service<CallGate>.Set();
Log.Information("[T3] PM OK!");
}
Service<DalamudInterface>.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;
}
/// <summary> /// <summary>
/// Queue an unload of Dalamud when it gets the chance. /// Queue an unload of Dalamud when it gets the chance.

View file

@ -86,12 +86,12 @@ namespace Dalamud
/// <summary> /// <summary>
/// Gets or sets a value that specifies how much to wait before a new Dalamud session. /// Gets or sets a value that specifies how much to wait before a new Dalamud session.
/// </summary> /// </summary>
public int DelayInitializeMs { get; set; } = 0; public int DelayInitializeMs { get; set; }
/// <summary> /// <summary>
/// Gets or sets the path the boot log file is supposed to be written to. /// Gets or sets the path the boot log file is supposed to be written to.
/// </summary> /// </summary>
public string BootLogPath { get; set; } public string? BootLogPath { get; set; }
/// <summary> /// <summary>
/// Gets or sets a value indicating whether a Boot console should be shown. /// Gets or sets a value indicating whether a Boot console should be shown.

View file

@ -26,22 +26,91 @@ namespace Dalamud.Data
/// </summary> /// </summary>
[PluginInterface] [PluginInterface]
[InterfaceVersion("1.0")] [InterfaceVersion("1.0")]
[ServiceManager.BlockingEarlyLoadedService]
public sealed class DataManager : IDisposable public sealed class DataManager : IDisposable
{ {
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";
private Thread luminaResourceThread; private readonly Thread luminaResourceThread;
private CancellationTokenSource luminaCancellationTokenSource; private readonly CancellationTokenSource luminaCancellationTokenSource;
/// <summary> [ServiceManager.ServiceConstructor]
/// Initializes a new instance of the <see cref="DataManager"/> class. private DataManager(DalamudStartInfo dalamudStartInfo, Dalamud dalamud)
/// </summary>
internal DataManager()
{ {
this.Language = Service<DalamudStartInfo>.Get().Language; this.Language = dalamudStartInfo.Language;
// Set up default values so plugins do not null-reference when data is being loaded. // Set up default values so plugins do not null-reference when data is being loaded.
this.ClientOpCodes = this.ServerOpCodes = new ReadOnlyDictionary<string, ushort>(new Dictionary<string, ushort>()); this.ClientOpCodes = this.ServerOpCodes = new ReadOnlyDictionary<string, ushort>(new Dictionary<string, ushort>());
var baseDir = dalamud.AssetDirectory.FullName;
try
{
Log.Verbose("Starting data load...");
var zoneOpCodeDict = JsonConvert.DeserializeObject<Dictionary<string, ushort>>(
File.ReadAllText(Path.Combine(baseDir, "UIRes", "serveropcode.json")))!;
this.ServerOpCodes = new ReadOnlyDictionary<string, ushort>(zoneOpCodeDict);
Log.Verbose("Loaded {0} ServerOpCodes.", zoneOpCodeDict.Count);
var clientOpCodeDict = JsonConvert.DeserializeObject<Dictionary<string, ushort>>(
File.ReadAllText(Path.Combine(baseDir, "UIRes", "clientopcode.json")))!;
this.ClientOpCodes = new ReadOnlyDictionary<string, ushort>(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.");
}
} }
/// <summary> /// <summary>
@ -68,7 +137,7 @@ namespace Dalamud.Data
/// <summary> /// <summary>
/// Gets an <see cref="ExcelModule"/> object which gives access to any of the game's sheet data. /// Gets an <see cref="ExcelModule"/> object which gives access to any of the game's sheet data.
/// </summary> /// </summary>
public ExcelModule Excel => this.GameData?.Excel; public ExcelModule Excel => this.GameData.Excel;
/// <summary> /// <summary>
/// Gets a value indicating whether Game Data is ready to be read. /// Gets a value indicating whether Game Data is ready to be read.
@ -180,7 +249,7 @@ namespace Dalamud.Data
/// <param name="type">The type of the icon (e.g. 'hq' to get the HQ variant of an item icon).</param> /// <param name="type">The type of the icon (e.g. 'hq' to get the HQ variant of an item icon).</param>
/// <param name="iconId">The icon ID.</param> /// <param name="iconId">The icon ID.</param>
/// <returns>The <see cref="TexFile"/> containing the icon.</returns> /// <returns>The <see cref="TexFile"/> containing the icon.</returns>
public TexFile? GetIcon(string type, uint iconId) public TexFile? GetIcon(string? type, uint iconId)
{ {
type ??= string.Empty; type ??= string.Empty;
if (type.Length > 0 && !type.EndsWith("/")) if (type.Length > 0 && !type.EndsWith("/"))
@ -276,81 +345,5 @@ namespace Dalamud.Data
{ {
this.luminaCancellationTokenSource.Cancel(); this.luminaCancellationTokenSource.Cancel();
} }
/// <summary>
/// Initialize this data manager.
/// </summary>
/// <param name="baseDir">The directory to load data from.</param>
internal void Initialize(string baseDir)
{
try
{
Log.Verbose("Starting data load...");
var zoneOpCodeDict = JsonConvert.DeserializeObject<Dictionary<string, ushort>>(
File.ReadAllText(Path.Combine(baseDir, "UIRes", "serveropcode.json")));
this.ServerOpCodes = new ReadOnlyDictionary<string, ushort>(zoneOpCodeDict);
Log.Verbose("Loaded {0} ServerOpCodes.", zoneOpCodeDict.Count);
var clientOpCodeDict = JsonConvert.DeserializeObject<Dictionary<string, ushort>>(
File.ReadAllText(Path.Combine(baseDir, "UIRes", "clientopcode.json")));
this.ClientOpCodes = new ReadOnlyDictionary<string, ushort>(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.");
}
}
} }
} }

View file

@ -47,8 +47,11 @@ namespace Dalamud
/// <param name="mainThreadContinueEvent">Event used to signal the main thread to continue.</param> /// <param name="mainThreadContinueEvent">Event used to signal the main thread to continue.</param>
public static void Initialize(IntPtr infoPtr, IntPtr mainThreadContinueEvent) public static void Initialize(IntPtr infoPtr, IntPtr mainThreadContinueEvent)
{ {
var infoStr = Marshal.PtrToStringUTF8(infoPtr); var infoStr = Marshal.PtrToStringUTF8(infoPtr)!;
var info = JsonConvert.DeserializeObject<DalamudStartInfo>(infoStr); var info = JsonConvert.DeserializeObject<DalamudStartInfo>(infoStr)!;
if ((info.BootWaitMessageBox & 4) != 0)
MessageBoxW(IntPtr.Zero, "Press OK to continue (BeforeDalamudConstruct)", "Dalamud Boot", MessageBoxType.Ok);
new Thread(() => RunThread(info, mainThreadContinueEvent)).Start(); new Thread(() => RunThread(info, mainThreadContinueEvent)).Start();
} }
@ -148,9 +151,6 @@ namespace Dalamud
var dalamud = new Dalamud(info, levelSwitch, finishSignal, configuration, mainThreadContinueEvent); var dalamud = new Dalamud(info, levelSwitch, finishSignal, configuration, mainThreadContinueEvent);
Log.Information("This is Dalamud - Core: {GitHash}, CS: {CsGitHash}", Util.GetGitHash(), Util.GetGitHashClientStructs()); Log.Information("This is Dalamud - Core: {GitHash}, CS: {CsGitHash}", Util.GetGitHash(), Util.GetGitHashClientStructs());
// Run session
dalamud.LoadTier1();
dalamud.WaitForUnload(); dalamud.WaitForUnload();
dalamud.Dispose(); dalamud.Dispose();

View file

@ -20,15 +20,6 @@ namespace Dalamud.Game
/// </summary> /// </summary>
protected bool IsResolved { get; set; } protected bool IsResolved { get; set; }
/// <summary>
/// Setup the resolver, calling the appopriate method based on the process architecture.
/// </summary>
public void Setup()
{
var scanner = Service<SigScanner>.Get();
this.Setup(scanner);
}
/// <summary> /// <summary>
/// Setup the resolver, calling the appopriate method based on the process architecture. /// Setup the resolver, calling the appopriate method based on the process architecture.
/// </summary> /// </summary>
@ -55,11 +46,13 @@ namespace Dalamud.Game
this.SetupInternal(scanner); this.SetupInternal(scanner);
var className = this.GetType().Name; 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))) 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; this.IsResolved = true;

View file

@ -28,6 +28,7 @@ namespace Dalamud.Game
/// </summary> /// </summary>
[PluginInterface] [PluginInterface]
[InterfaceVersion("1.0")] [InterfaceVersion("1.0")]
[ServiceManager.BlockingEarlyLoadedService]
public class ChatHandlers public class ChatHandlers
{ {
// private static readonly Dictionary<string, string> UnicodeToDiscordEmojiDict = new() // private static readonly Dictionary<string, string> UnicodeToDiscordEmojiDict = new()
@ -109,13 +110,9 @@ namespace Dalamud.Game
private bool hasSeenLoadingMsg; private bool hasSeenLoadingMsg;
private bool hasAutoUpdatedPlugins; private bool hasAutoUpdatedPlugins;
/// <summary> [ServiceManager.ServiceConstructor]
/// Initializes a new instance of the <see cref="ChatHandlers"/> class. private ChatHandlers(ChatGui chatGui)
/// </summary>
internal ChatHandlers()
{ {
var chatGui = Service<ChatGui>.Get();
chatGui.CheckMessageHandled += this.OnCheckMessageHandled; chatGui.CheckMessageHandled += this.OnCheckMessageHandled;
chatGui.ChatMessage += this.OnChatMessage; chatGui.ChatMessage += this.OnChatMessage;

View file

@ -14,18 +14,18 @@ namespace Dalamud.Game.ClientState.Aetherytes
/// </summary> /// </summary>
[PluginInterface] [PluginInterface]
[InterfaceVersion("1.0")] [InterfaceVersion("1.0")]
[ServiceManager.BlockingEarlyLoadedService]
public sealed partial class AetheryteList public sealed partial class AetheryteList
{ {
[ServiceManager.ServiceDependency]
private readonly ClientState clientState = Service<ClientState>.Get();
private readonly ClientStateAddressResolver address; private readonly ClientStateAddressResolver address;
private readonly UpdateAetheryteListDelegate updateAetheryteListFunc; private readonly UpdateAetheryteListDelegate updateAetheryteListFunc;
/// <summary> [ServiceManager.ServiceConstructor]
/// Initializes a new instance of the <see cref="AetheryteList"/> class. private AetheryteList()
/// </summary>
/// <param name="addressResolver">Client state address resolver.</param>
internal AetheryteList(ClientStateAddressResolver addressResolver)
{ {
this.address = addressResolver; this.address = this.clientState.AddressResolver;
this.updateAetheryteListFunc = Marshal.GetDelegateForFunctionPointer<UpdateAetheryteListDelegate>(this.address.UpdateAetheryteList); this.updateAetheryteListFunc = Marshal.GetDelegateForFunctionPointer<UpdateAetheryteListDelegate>(this.address.UpdateAetheryteList);
Log.Verbose($"Teleport address 0x{this.address.Telepo.ToInt64():X}"); Log.Verbose($"Teleport address 0x{this.address.Telepo.ToInt64():X}");
@ -40,9 +40,7 @@ namespace Dalamud.Game.ClientState.Aetherytes
{ {
get get
{ {
var clientState = Service<ClientState>.Get(); if (this.clientState.LocalPlayer == null)
if (clientState.LocalPlayer == null)
return 0; return 0;
this.Update(); this.Update();
@ -70,9 +68,7 @@ namespace Dalamud.Game.ClientState.Aetherytes
return null; return null;
} }
var clientState = Service<ClientState>.Get(); if (this.clientState.LocalPlayer == null)
if (clientState.LocalPlayer == null)
return null; return null;
return new AetheryteEntry(TelepoStruct->TeleportList.Get((ulong)index)); return new AetheryteEntry(TelepoStruct->TeleportList.Get((ulong)index));
@ -81,10 +77,8 @@ namespace Dalamud.Game.ClientState.Aetherytes
private void Update() private void Update()
{ {
var clientState = Service<ClientState>.Get();
// this is very very important as otherwise it crashes // this is very very important as otherwise it crashes
if (clientState.LocalPlayer == null) if (this.clientState.LocalPlayer == null)
return; return;
this.updateAetheryteListFunc(this.address.Telepo, 0); this.updateAetheryteListFunc(this.address.Telepo, 0);

View file

@ -15,19 +15,17 @@ namespace Dalamud.Game.ClientState.Buddy
/// </summary> /// </summary>
[PluginInterface] [PluginInterface]
[InterfaceVersion("1.0")] [InterfaceVersion("1.0")]
[ServiceManager.BlockingEarlyLoadedService]
public sealed partial class BuddyList public sealed partial class BuddyList
{ {
private const uint InvalidObjectID = 0xE0000000; private const uint InvalidObjectID = 0xE0000000;
private readonly ClientStateAddressResolver address; private readonly ClientStateAddressResolver address;
/// <summary> [ServiceManager.ServiceConstructor]
/// Initializes a new instance of the <see cref="BuddyList"/> class. private BuddyList(ClientState clientState)
/// </summary>
/// <param name="addressResolver">Client state address resolver.</param>
internal BuddyList(ClientStateAddressResolver addressResolver)
{ {
this.address = addressResolver; this.address = clientState.AddressResolver;
Log.Verbose($"Buddy list address 0x{this.address.BuddyList.ToInt64():X}"); Log.Verbose($"Buddy list address 0x{this.address.BuddyList.ToInt64():X}");
} }

View file

@ -27,6 +27,7 @@ namespace Dalamud.Game.ClientState
/// </summary> /// </summary>
[PluginInterface] [PluginInterface]
[InterfaceVersion("1.0")] [InterfaceVersion("1.0")]
[ServiceManager.BlockingEarlyLoadedService]
public sealed class ClientState : IDisposable public sealed class ClientState : IDisposable
{ {
private readonly ClientStateAddressResolver address; private readonly ClientStateAddressResolver address;
@ -36,46 +37,26 @@ namespace Dalamud.Game.ClientState
private bool lastFramePvP = false; private bool lastFramePvP = false;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="ClientState"/> class. /// Gets client state address resolver.
/// Set up client state access.
/// </summary> /// </summary>
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 = new ClientStateAddressResolver();
this.address.Setup(); this.address.Setup(sigScanner);
Log.Verbose("===== C L I E N T S T A T E ====="); Log.Verbose("===== C L I E N T S T A T E =====");
this.ClientLanguage = Service<DalamudStartInfo>.Get().Language; this.ClientLanguage = startInfo.Language;
Service<ObjectTable>.Set(this.address);
Service<FateTable>.Set(this.address);
Service<PartyList>.Set(this.address);
Service<BuddyList>.Set(this.address);
Service<JobGauges>.Set(this.address);
Service<KeyState>.Set(this.address);
Service<GamepadState>.Set(this.address);
Service<Conditions.Condition>.Set(this.address);
Service<TargetManager>.Set(this.address);
Service<AetheryteList>.Set(this.address);
Log.Verbose($"SetupTerritoryType address 0x{this.address.SetupTerritoryType.ToInt64():X}"); Log.Verbose($"SetupTerritoryType address 0x{this.address.SetupTerritoryType.ToInt64():X}");
this.setupTerritoryTypeHook = new Hook<SetupTerritoryTypeDelegate>(this.address.SetupTerritoryType, this.SetupTerritoryTypeDetour); this.setupTerritoryTypeHook = new Hook<SetupTerritoryTypeDelegate>(this.address.SetupTerritoryType, this.SetupTerritoryTypeDetour);
var framework = Service<Framework>.Get();
framework.Update += this.FrameworkOnOnUpdateEvent; framework.Update += this.FrameworkOnOnUpdateEvent;
var networkHandlers = Service<NetworkHandlers>.Get();
networkHandlers.CfPop += this.NetworkHandlersOnCfPop; networkHandlers.CfPop += this.NetworkHandlersOnCfPop;
} }

View file

@ -11,6 +11,7 @@ namespace Dalamud.Game.ClientState.Conditions
/// </summary> /// </summary>
[PluginInterface] [PluginInterface]
[InterfaceVersion("1.0")] [InterfaceVersion("1.0")]
[ServiceManager.BlockingEarlyLoadedService]
public sealed partial class Condition public sealed partial class Condition
{ {
/// <summary> /// <summary>
@ -20,12 +21,10 @@ namespace Dalamud.Game.ClientState.Conditions
private readonly bool[] cache = new bool[MaxConditionEntries]; private readonly bool[] cache = new bool[MaxConditionEntries];
/// <summary> [ServiceManager.ServiceConstructor]
/// Initializes a new instance of the <see cref="Condition"/> class. private Condition(ClientState clientState)
/// </summary>
/// <param name="resolver">The ClientStateAddressResolver instance.</param>
internal Condition(ClientStateAddressResolver resolver)
{ {
var resolver = clientState.AddressResolver;
this.Address = resolver.ConditionFlags; this.Address = resolver.ConditionFlags;
} }

View file

@ -13,17 +13,15 @@ namespace Dalamud.Game.ClientState.Fates
/// </summary> /// </summary>
[PluginInterface] [PluginInterface]
[InterfaceVersion("1.0")] [InterfaceVersion("1.0")]
[ServiceManager.BlockingEarlyLoadedService]
public sealed partial class FateTable public sealed partial class FateTable
{ {
private readonly ClientStateAddressResolver address; private readonly ClientStateAddressResolver address;
/// <summary> [ServiceManager.ServiceConstructor]
/// Initializes a new instance of the <see cref="FateTable"/> class. private FateTable(ClientState clientState)
/// </summary>
/// <param name="addressResolver">Client state address resolver.</param>
internal FateTable(ClientStateAddressResolver addressResolver)
{ {
this.address = addressResolver; this.address = clientState.AddressResolver;
Log.Verbose($"Fate table address 0x{this.address.FateTablePtr.ToInt64():X}"); Log.Verbose($"Fate table address 0x{this.address.FateTablePtr.ToInt64():X}");
} }

View file

@ -15,6 +15,7 @@ namespace Dalamud.Game.ClientState.GamePad
/// </summary> /// </summary>
[PluginInterface] [PluginInterface]
[InterfaceVersion("1.0.0")] [InterfaceVersion("1.0.0")]
[ServiceManager.BlockingEarlyLoadedService]
public unsafe class GamepadState : IDisposable public unsafe class GamepadState : IDisposable
{ {
private readonly Hook<ControllerPoll> gamepadPoll; private readonly Hook<ControllerPoll> gamepadPoll;
@ -26,12 +27,10 @@ namespace Dalamud.Game.ClientState.GamePad
private int rightStickX; private int rightStickX;
private int rightStickY; private int rightStickY;
/// <summary> [ServiceManager.ServiceConstructor]
/// Initializes a new instance of the <see cref="GamepadState" /> class. private GamepadState(ClientState clientState)
/// </summary>
/// <param name="resolver">Resolver knowing the pointer to the GamepadPoll function.</param>
public GamepadState(ClientStateAddressResolver resolver)
{ {
var resolver = clientState.AddressResolver;
Log.Verbose($"GamepadPoll address 0x{resolver.GamepadPoll.ToInt64():X}"); Log.Verbose($"GamepadPoll address 0x{resolver.GamepadPoll.ToInt64():X}");
this.gamepadPoll = new Hook<ControllerPoll>(resolver.GamepadPoll, this.GamepadPollDetour); this.gamepadPoll = new Hook<ControllerPoll>(resolver.GamepadPoll, this.GamepadPollDetour);
} }

View file

@ -14,17 +14,15 @@ namespace Dalamud.Game.ClientState.JobGauge
/// </summary> /// </summary>
[PluginInterface] [PluginInterface]
[InterfaceVersion("1.0")] [InterfaceVersion("1.0")]
[ServiceManager.BlockingEarlyLoadedService]
public class JobGauges public class JobGauges
{ {
private Dictionary<Type, JobGaugeBase> cache = new(); private Dictionary<Type, JobGaugeBase> cache = new();
/// <summary> [ServiceManager.ServiceConstructor]
/// Initializes a new instance of the <see cref="JobGauges"/> class. private JobGauges(ClientState clientState)
/// </summary>
/// <param name="addressResolver">Address resolver with the JobGauge memory location(s).</param>
public JobGauges(ClientStateAddressResolver addressResolver)
{ {
this.Address = addressResolver.JobGaugeData; this.Address = clientState.AddressResolver.JobGaugeData;
Log.Verbose($"JobGaugeData address 0x{this.Address.ToInt64():X}"); Log.Verbose($"JobGaugeData address 0x{this.Address.ToInt64():X}");
} }

View file

@ -22,6 +22,7 @@ namespace Dalamud.Game.ClientState.Keys
/// </remarks> /// </remarks>
[PluginInterface] [PluginInterface]
[InterfaceVersion("1.0")] [InterfaceVersion("1.0")]
[ServiceManager.BlockingEarlyLoadedService]
public class KeyState public class KeyState
{ {
// 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
@ -32,14 +33,11 @@ namespace Dalamud.Game.ClientState.Keys
private readonly IntPtr indexBase; private readonly IntPtr indexBase;
private VirtualKey[] validVirtualKeyCache = null; private VirtualKey[] validVirtualKeyCache = null;
/// <summary> [ServiceManager.ServiceConstructor]
/// Initializes a new instance of the <see cref="KeyState"/> class. private KeyState(SigScanner sigScanner, ClientState clientState)
/// </summary>
/// <param name="addressResolver">The ClientStateAddressResolver instance.</param>
public KeyState(ClientStateAddressResolver addressResolver)
{ {
var moduleBaseAddress = Service<SigScanner>.Get().Module.BaseAddress; var moduleBaseAddress = sigScanner.Module.BaseAddress;
var addressResolver = clientState.AddressResolver;
this.bufferBase = moduleBaseAddress + Marshal.ReadInt32(addressResolver.KeyboardState); this.bufferBase = moduleBaseAddress + Marshal.ReadInt32(addressResolver.KeyboardState);
this.indexBase = moduleBaseAddress + Marshal.ReadInt32(addressResolver.KeyboardStateIndexArray); this.indexBase = moduleBaseAddress + Marshal.ReadInt32(addressResolver.KeyboardStateIndexArray);

View file

@ -16,19 +16,17 @@ namespace Dalamud.Game.ClientState.Objects
/// </summary> /// </summary>
[PluginInterface] [PluginInterface]
[InterfaceVersion("1.0")] [InterfaceVersion("1.0")]
[ServiceManager.BlockingEarlyLoadedService]
public sealed partial class ObjectTable public sealed partial class ObjectTable
{ {
private const int ObjectTableLength = 424; private const int ObjectTableLength = 424;
private readonly ClientStateAddressResolver address; private readonly ClientStateAddressResolver address;
/// <summary> [ServiceManager.ServiceConstructor]
/// Initializes a new instance of the <see cref="ObjectTable"/> class. private ObjectTable(ClientState clientState)
/// </summary>
/// <param name="addressResolver">Client state address resolver.</param>
internal ObjectTable(ClientStateAddressResolver addressResolver)
{ {
this.address = addressResolver; this.address = clientState.AddressResolver;
Log.Verbose($"Object table address 0x{this.address.ObjectTable.ToInt64():X}"); Log.Verbose($"Object table address 0x{this.address.ObjectTable.ToInt64():X}");
} }

View file

@ -11,17 +11,15 @@ namespace Dalamud.Game.ClientState.Objects
/// </summary> /// </summary>
[PluginInterface] [PluginInterface]
[InterfaceVersion("1.0")] [InterfaceVersion("1.0")]
[ServiceManager.BlockingEarlyLoadedService]
public sealed unsafe class TargetManager public sealed unsafe class TargetManager
{ {
private readonly ClientStateAddressResolver address; private readonly ClientStateAddressResolver address;
/// <summary> [ServiceManager.ServiceConstructor]
/// Initializes a new instance of the <see cref="TargetManager"/> class. private TargetManager(ClientState clientState)
/// </summary>
/// <param name="addressResolver">The ClientStateAddressResolver instance.</param>
internal TargetManager(ClientStateAddressResolver addressResolver)
{ {
this.address = addressResolver; this.address = clientState.AddressResolver;
} }
/// <summary> /// <summary>

View file

@ -14,6 +14,7 @@ namespace Dalamud.Game.ClientState.Party
/// </summary> /// </summary>
[PluginInterface] [PluginInterface]
[InterfaceVersion("1.0")] [InterfaceVersion("1.0")]
[ServiceManager.BlockingEarlyLoadedService]
public sealed unsafe partial class PartyList public sealed unsafe partial class PartyList
{ {
private const int GroupLength = 8; private const int GroupLength = 8;
@ -21,13 +22,10 @@ namespace Dalamud.Game.ClientState.Party
private readonly ClientStateAddressResolver address; private readonly ClientStateAddressResolver address;
/// <summary> [ServiceManager.ServiceConstructor]
/// Initializes a new instance of the <see cref="PartyList"/> class. private PartyList(ClientState clientState)
/// </summary>
/// <param name="addressResolver">Client state address resolver.</param>
internal PartyList(ClientStateAddressResolver addressResolver)
{ {
this.address = addressResolver; this.address = clientState.AddressResolver;
Log.Verbose($"Group manager address 0x{this.address.GroupManager.ToInt64():X}"); Log.Verbose($"Group manager address 0x{this.address.GroupManager.ToInt64():X}");
} }

View file

@ -17,6 +17,7 @@ namespace Dalamud.Game.Command
/// </summary> /// </summary>
[PluginInterface] [PluginInterface]
[InterfaceVersion("1.0")] [InterfaceVersion("1.0")]
[ServiceManager.BlockingEarlyLoadedService]
public sealed class CommandManager public sealed class CommandManager
{ {
private readonly Dictionary<string, CommandInfo> commandMap = new(); private readonly Dictionary<string, CommandInfo> commandMap = new();
@ -27,13 +28,9 @@ namespace Dalamud.Game.Command
private readonly Regex commandRegexCn = new(@"^^(“|「)(?<command>.+)(”|」)(出现问题:该命令不存在|出現問題:該命令不存在)。$", RegexOptions.Compiled); private readonly Regex commandRegexCn = new(@"^^(“|「)(?<command>.+)(”|」)(出现问题:该命令不存在|出現問題:該命令不存在)。$", RegexOptions.Compiled);
private readonly Regex currentLangCommandRegex; private readonly Regex currentLangCommandRegex;
/// <summary> [ServiceManager.ServiceConstructor]
/// Initializes a new instance of the <see cref="CommandManager"/> class. private CommandManager(DalamudStartInfo startInfo)
/// </summary>
internal CommandManager()
{ {
var startInfo = Service<DalamudStartInfo>.Get();
this.currentLangCommandRegex = startInfo.Language switch this.currentLangCommandRegex = startInfo.Language switch
{ {
ClientLanguage.Japanese => this.commandRegexJp, ClientLanguage.Japanese => this.commandRegexJp,

View file

@ -24,6 +24,7 @@ namespace Dalamud.Game
/// </summary> /// </summary>
[PluginInterface] [PluginInterface]
[InterfaceVersion("1.0")] [InterfaceVersion("1.0")]
[ServiceManager.BlockingEarlyLoadedService]
public sealed class Framework : IDisposable public sealed class Framework : IDisposable
{ {
private static Stopwatch statsStopwatch = new(); private static Stopwatch statsStopwatch = new();
@ -31,27 +32,28 @@ namespace Dalamud.Game
private readonly List<RunOnNextTickTaskBase> runOnNextTickTaskList = new(); private readonly List<RunOnNextTickTaskBase> runOnNextTickTaskList = new();
private readonly Stopwatch updateStopwatch = new(); private readonly Stopwatch updateStopwatch = new();
private bool tier2Initialized = false;
private bool tier3Initialized = false;
private bool tierInitError = false;
private Hook<OnUpdateDetour> updateHook; private Hook<OnUpdateDetour> updateHook;
private Hook<OnDestroyDetour> freeHook; private Hook<OnDestroyDetour> freeHook;
private Hook<OnRealDestroyDelegate> destroyHook; private Hook<OnRealDestroyDelegate> destroyHook;
private Thread? frameworkUpdateThread; private Thread? frameworkUpdateThread;
/// <summary> [ServiceManager.ServiceConstructor]
/// Initializes a new instance of the <see cref="Framework"/> class. private Framework(GameGui gameGui, GameNetwork gameNetwork, SigScanner sigScanner)
/// </summary>
internal Framework()
{ {
this.Address = new FrameworkAddressResolver(); this.Address = new FrameworkAddressResolver();
this.Address.Setup(); this.Address.Setup(sigScanner);
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>
@ -123,20 +125,6 @@ namespace Dalamud.Game
/// </summary> /// </summary>
internal bool DispatchUpdateEvents { get; set; } = true; internal bool DispatchUpdateEvents { get; set; } = true;
/// <summary>
/// Enable this module.
/// </summary>
public void Enable()
{
Service<LibcFunction>.Set();
Service<GameGui>.Get().Enable();
Service<GameNetwork>.Get().Enable();
this.updateHook.Enable();
this.freeHook.Enable();
this.destroyHook.Enable();
}
/// <summary> /// <summary>
/// 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. /// 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.
/// </summary> /// </summary>
@ -238,39 +226,21 @@ namespace Dalamud.Game
private bool HandleFrameworkUpdate(IntPtr framework) 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; this.frameworkUpdateThread ??= Thread.CurrentThread;
var dalamud = Service<Dalamud>.Get(); ThreadSafety.MarkMainThread();
// 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<InterfaceManager>.GetNullable()?.IsReady == true)
{
this.tier3Initialized = dalamud.LoadTier3();
if (!this.tier3Initialized)
this.tierInitError = true;
goto original;
}
try try
{ {
Service<ChatGui>.Get().UpdateQueue(); var chatGui = Service<ChatGui>.GetNullable();
Service<ToastGui>.Get().UpdateQueue(); var toastGui = Service<ToastGui>.GetNullable();
Service<GameNetwork>.Get().UpdateQueue(); var gameNetwork = Service<GameNetwork>.GetNullable();
if (chatGui == null || toastGui == null || gameNetwork == null)
goto original;
chatGui.UpdateQueue();
toastGui.UpdateQueue();
gameNetwork.UpdateQueue();
} }
catch (Exception ex) catch (Exception ex)
{ {

View file

@ -21,6 +21,7 @@ namespace Dalamud.Game.Gui
/// </summary> /// </summary>
[PluginInterface] [PluginInterface]
[InterfaceVersion("1.0")] [InterfaceVersion("1.0")]
[ServiceManager.BlockingEarlyLoadedService]
public sealed class ChatGui : IDisposable public sealed class ChatGui : IDisposable
{ {
private readonly ChatGuiAddressResolver address; private readonly ChatGuiAddressResolver address;
@ -34,14 +35,11 @@ namespace Dalamud.Game.Gui
private IntPtr baseAddress = IntPtr.Zero; private IntPtr baseAddress = IntPtr.Zero;
/// <summary> [ServiceManager.ServiceConstructor]
/// Initializes a new instance of the <see cref="ChatGui"/> class. private ChatGui(SigScanner sigScanner)
/// </summary>
/// <param name="baseAddress">The base address of the ChatManager.</param>
internal ChatGui()
{ {
this.address = new ChatGuiAddressResolver(); this.address = new ChatGuiAddressResolver();
this.address.Setup(); this.address.Setup(sigScanner);
this.printMessageHook = new Hook<PrintMessageDelegate>(this.address.PrintMessage, this.HandlePrintMessageDetour); this.printMessageHook = new Hook<PrintMessageDelegate>(this.address.PrintMessage, this.HandlePrintMessageDetour);
this.populateItemLinkHook = new Hook<PopulateItemLinkDelegate>(this.address.PopulateItemLinkObject, this.HandlePopulateItemLinkDetour); this.populateItemLinkHook = new Hook<PopulateItemLinkDelegate>(this.address.PopulateItemLinkObject, this.HandlePopulateItemLinkDetour);

View file

@ -24,6 +24,7 @@ namespace Dalamud.Game.Gui.ContextMenus
/// </summary> /// </summary>
[PluginInterface] [PluginInterface]
[InterfaceVersion("1.0")] [InterfaceVersion("1.0")]
[ServiceManager.BlockingEarlyLoadedService]
public sealed class ContextMenu : IDisposable public sealed class ContextMenu : IDisposable
{ {
private const int MaxContextMenuItemsPerContextMenu = 32; private const int MaxContextMenuItemsPerContextMenu = 32;
@ -47,13 +48,11 @@ namespace Dalamud.Game.Gui.ContextMenus
private OpenSubContextMenuItem? selectedOpenSubContextMenuItem; private OpenSubContextMenuItem? selectedOpenSubContextMenuItem;
private ContextMenuOpenedArgs? currentContextMenuOpenedArgs; private ContextMenuOpenedArgs? currentContextMenuOpenedArgs;
/// <summary> [ServiceManager.ServiceConstructor]
/// Initializes a new instance of the <see cref="ContextMenu"/> class. private ContextMenu(SigScanner sigScanner)
/// </summary>
public ContextMenu()
{ {
this.Address = new ContextMenuAddressResolver(); this.Address = new ContextMenuAddressResolver();
this.Address.Setup(); this.Address.Setup(sigScanner);
unsafe unsafe
{ {

View file

@ -17,6 +17,7 @@ namespace Dalamud.Game.Gui.Dtr
/// </summary> /// </summary>
[PluginInterface] [PluginInterface]
[InterfaceVersion("1.0")] [InterfaceVersion("1.0")]
[ServiceManager.BlockingEarlyLoadedService]
public sealed unsafe class DtrBar : IDisposable public sealed unsafe class DtrBar : IDisposable
{ {
private const uint BaseNodeId = 1000; private const uint BaseNodeId = 1000;
@ -24,13 +25,10 @@ namespace Dalamud.Game.Gui.Dtr
private List<DtrBarEntry> entries = new(); private List<DtrBarEntry> entries = new();
private uint runningNodeIds = BaseNodeId; private uint runningNodeIds = BaseNodeId;
/// <summary> [ServiceManager.ServiceConstructor]
/// Initializes a new instance of the <see cref="DtrBar"/> class. private DtrBar(DalamudConfiguration configuration)
/// </summary>
internal DtrBar()
{ {
Service<Framework>.Get().Update += this.Update; Service<Framework>.Get().Update += this.Update;
var configuration = Service<DalamudConfiguration>.Get();
configuration.DtrOrder ??= new List<string>(); configuration.DtrOrder ??= new List<string>();
configuration.DtrIgnore ??= new List<string>(); configuration.DtrIgnore ??= new List<string>();

View file

@ -16,6 +16,7 @@ namespace Dalamud.Game.Gui.FlyText
/// </summary> /// </summary>
[PluginInterface] [PluginInterface]
[InterfaceVersion("1.0")] [InterfaceVersion("1.0")]
[ServiceManager.BlockingEarlyLoadedService]
public sealed class FlyTextGui : IDisposable public sealed class FlyTextGui : IDisposable
{ {
/// <summary> /// <summary>
@ -28,13 +29,11 @@ namespace Dalamud.Game.Gui.FlyText
/// </summary> /// </summary>
private readonly Hook<CreateFlyTextDelegate> createFlyTextHook; private readonly Hook<CreateFlyTextDelegate> createFlyTextHook;
/// <summary> [ServiceManager.ServiceConstructor]
/// Initializes a new instance of the <see cref="FlyTextGui"/> class. private FlyTextGui(SigScanner sigScanner)
/// </summary>
internal FlyTextGui()
{ {
this.Address = new FlyTextGuiAddressResolver(); this.Address = new FlyTextGuiAddressResolver();
this.Address.Setup(); this.Address.Setup(sigScanner);
this.addFlyTextNative = Marshal.GetDelegateForFunctionPointer<AddFlyTextDelegate>(this.Address.AddFlyText); this.addFlyTextNative = Marshal.GetDelegateForFunctionPointer<AddFlyTextDelegate>(this.Address.AddFlyText);
this.createFlyTextHook = new Hook<CreateFlyTextDelegate>(this.Address.CreateFlyText, this.CreateFlyTextDetour); this.createFlyTextHook = new Hook<CreateFlyTextDelegate>(this.Address.CreateFlyText, this.CreateFlyTextDetour);

View file

@ -27,6 +27,7 @@ namespace Dalamud.Game.Gui
/// </summary> /// </summary>
[PluginInterface] [PluginInterface]
[InterfaceVersion("1.0")] [InterfaceVersion("1.0")]
[ServiceManager.BlockingEarlyLoadedService]
public sealed unsafe class GameGui : IDisposable public sealed unsafe class GameGui : IDisposable
{ {
private readonly GameGuiAddressResolver address; private readonly GameGuiAddressResolver address;
@ -46,14 +47,11 @@ namespace Dalamud.Game.Gui
private GetUIMapObjectDelegate getUIMapObject; private GetUIMapObjectDelegate getUIMapObject;
private OpenMapWithFlagDelegate openMapWithFlag; private OpenMapWithFlagDelegate openMapWithFlag;
/// <summary> [ServiceManager.ServiceConstructor]
/// Initializes a new instance of the <see cref="GameGui"/> class. private GameGui(SigScanner sigScanner)
/// This class is responsible for many aspects of interacting with the native game UI.
/// </summary>
internal GameGui()
{ {
this.address = new GameGuiAddressResolver(); this.address = new GameGuiAddressResolver();
this.address.Setup(); this.address.Setup(sigScanner);
Log.Verbose("===== G A M E G U I ====="); Log.Verbose("===== G A M E G U I =====");
Log.Verbose($"GameGuiManager address 0x{this.address.BaseAddress.ToInt64():X}"); 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($"HandleItemOut address 0x{this.address.HandleItemOut.ToInt64():X}");
Log.Verbose($"HandleImm address 0x{this.address.HandleImm.ToInt64():X}"); Log.Verbose($"HandleImm address 0x{this.address.HandleImm.ToInt64():X}");
Service<ChatGui>.Set();
Service<PartyFinderGui>.Set();
Service<ToastGui>.Set();
Service<FlyTextGui>.Set();
Service<ContextMenu>.Set();
Service<DtrBar>.Set();
this.setGlobalBgmHook = new Hook<SetGlobalBgmDelegate>(this.address.SetGlobalBgm, this.HandleSetGlobalBgmDetour); this.setGlobalBgmHook = new Hook<SetGlobalBgmDelegate>(this.address.SetGlobalBgm, this.HandleSetGlobalBgmDetour);
this.handleItemHoverHook = new Hook<HandleItemHoverDelegate>(this.address.HandleItemHover, this.HandleItemHoverDetour); this.handleItemHoverHook = new Hook<HandleItemHoverDelegate>(this.address.HandleItemHover, this.HandleItemHoverDetour);

View file

@ -18,6 +18,7 @@ namespace Dalamud.Game.Gui.Internal
/// <summary> /// <summary>
/// This class handles IME for non-English users. /// This class handles IME for non-English users.
/// </summary> /// </summary>
[ServiceManager.EarlyLoadedService]
internal unsafe class DalamudIME : IDisposable internal unsafe class DalamudIME : IDisposable
{ {
private static readonly ModuleLog Log = new("IME"); private static readonly ModuleLog Log = new("IME");
@ -32,7 +33,9 @@ namespace Dalamud.Game.Gui.Internal
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="DalamudIME"/> class. /// Initializes a new instance of the <see cref="DalamudIME"/> class.
/// </summary> /// </summary>
internal DalamudIME() /// <param name="tag">Tag.</param>
[ServiceManager.ServiceConstructor]
private DalamudIME()
{ {
} }

View file

@ -15,6 +15,7 @@ namespace Dalamud.Game.Gui.PartyFinder
/// </summary> /// </summary>
[PluginInterface] [PluginInterface]
[InterfaceVersion("1.0")] [InterfaceVersion("1.0")]
[ServiceManager.BlockingEarlyLoadedService]
public sealed class PartyFinderGui : IDisposable public sealed class PartyFinderGui : IDisposable
{ {
private readonly PartyFinderAddressResolver address; private readonly PartyFinderAddressResolver address;
@ -25,10 +26,12 @@ namespace Dalamud.Game.Gui.PartyFinder
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="PartyFinderGui"/> class. /// Initializes a new instance of the <see cref="PartyFinderGui"/> class.
/// </summary> /// </summary>
internal PartyFinderGui() /// <param name="tag">Tag.</param>
[ServiceManager.ServiceConstructor]
private PartyFinderGui(SigScanner sigScanner)
{ {
this.address = new PartyFinderAddressResolver(); this.address = new PartyFinderAddressResolver();
this.address.Setup(); this.address.Setup(sigScanner);
this.memory = Marshal.AllocHGlobal(PartyFinderPacket.PacketSize); this.memory = Marshal.AllocHGlobal(PartyFinderPacket.PacketSize);

View file

@ -14,6 +14,7 @@ namespace Dalamud.Game.Gui.Toast
/// </summary> /// </summary>
[PluginInterface] [PluginInterface]
[InterfaceVersion("1.0")] [InterfaceVersion("1.0")]
[ServiceManager.BlockingEarlyLoadedService]
public sealed partial class ToastGui : IDisposable public sealed partial class ToastGui : IDisposable
{ {
private const uint QuestToastCheckmarkMagic = 60081; private const uint QuestToastCheckmarkMagic = 60081;
@ -31,10 +32,12 @@ namespace Dalamud.Game.Gui.Toast
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="ToastGui"/> class. /// Initializes a new instance of the <see cref="ToastGui"/> class.
/// </summary> /// </summary>
internal ToastGui() /// <param name="tag">Tag.</param>
[ServiceManager.ServiceConstructor]
private ToastGui(SigScanner sigScanner)
{ {
this.address = new ToastGuiAddressResolver(); this.address = new ToastGuiAddressResolver();
this.address.Setup(); this.address.Setup(sigScanner);
this.showNormalToastHook = new Hook<ShowNormalToastDelegate>(this.address.ShowNormalToast, new ShowNormalToastDelegate(this.HandleNormalToastDetour)); this.showNormalToastHook = new Hook<ShowNormalToastDelegate>(this.address.ShowNormalToast, new ShowNormalToastDelegate(this.HandleNormalToastDetour));
this.showQuestToastHook = new Hook<ShowQuestToastDelegate>(this.address.ShowQuestToast, new ShowQuestToastDelegate(this.HandleQuestToastDetour)); this.showQuestToastHook = new Hook<ShowQuestToastDelegate>(this.address.ShowQuestToast, new ShowQuestToastDelegate(this.HandleQuestToastDetour));

View file

@ -1,6 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using Dalamud.Configuration.Internal;
using Serilog; using Serilog;
namespace Dalamud.Game.Internal namespace Dalamud.Game.Internal
@ -8,22 +8,19 @@ namespace Dalamud.Game.Internal
/// <summary> /// <summary>
/// This class disables anti-debug functionality in the game client. /// This class disables anti-debug functionality in the game client.
/// </summary> /// </summary>
[ServiceManager.EarlyLoadedService]
internal sealed partial class AntiDebug internal sealed partial class AntiDebug
{ {
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;
private IntPtr debugCheckAddress; private IntPtr debugCheckAddress;
/// <summary> [ServiceManager.ServiceConstructor]
/// Initializes a new instance of the <see cref="AntiDebug"/> class. private AntiDebug(SigScanner sigScanner)
/// </summary>
public AntiDebug()
{ {
var scanner = Service<SigScanner>.Get();
try 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) catch (KeyNotFoundException)
{ {
@ -31,6 +28,16 @@ namespace Dalamud.Game.Internal
} }
Log.Verbose($"Debug check address 0x{this.debugCheckAddress.ToInt64():X}"); Log.Verbose($"Debug check address 0x{this.debugCheckAddress.ToInt64():X}");
if (!this.IsEnabled)
{
#if DEBUG
this.Enable();
#else
if (Service<DalamudConfiguration>.Get().IsAntiAntiDebugEnabled)
this.Enable();
#endif
}
} }
/// <summary> /// <summary>

View file

@ -4,6 +4,7 @@ using System.Diagnostics;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using Dalamud.Game.Internal.DXGI.Definitions; using Dalamud.Game.Internal.DXGI.Definitions;
using FFXIVClientStructs.FFXIV.Client.Graphics.Kernel;
using Serilog; using Serilog;
namespace Dalamud.Game.Internal.DXGI namespace Dalamud.Game.Internal.DXGI
@ -30,9 +31,28 @@ namespace Dalamud.Game.Internal.DXGI
/// <inheritdoc/> /// <inheritdoc/>
protected override unsafe void Setup64Bit(SigScanner sig) 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]; this.Present = scVtbl[(int)IDXGISwapChainVtbl.Present];

View file

@ -23,6 +23,7 @@ namespace Dalamud.Game.Internal
/// <summary> /// <summary>
/// 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]
internal sealed unsafe partial class DalamudAtkTweaks internal sealed unsafe partial class DalamudAtkTweaks
{ {
private readonly AtkValueChangeType atkValueChangeType; private readonly AtkValueChangeType atkValueChangeType;
@ -37,13 +38,9 @@ namespace Dalamud.Game.Internal
private readonly string locDalamudPlugins; private readonly string locDalamudPlugins;
private readonly string locDalamudSettings; private readonly string locDalamudSettings;
/// <summary> [ServiceManager.ServiceConstructor]
/// Initializes a new instance of the <see cref="DalamudAtkTweaks"/> class. private DalamudAtkTweaks(SigScanner sigScanner)
/// </summary>
public DalamudAtkTweaks()
{ {
var sigScanner = Service<SigScanner>.Get();
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 ?? ?? ?? ??");
this.hookAgentHudOpenSystemMenu = new Hook<AgentHudOpenSystemMenuPrototype>(openSystemMenuAddress, this.AgentHudOpenSystemMenuDetour); this.hookAgentHudOpenSystemMenu = new Hook<AgentHudOpenSystemMenuPrototype>(openSystemMenuAddress, this.AgentHudOpenSystemMenuDetour);

View file

@ -12,19 +12,18 @@ namespace Dalamud.Game.Libc
/// </summary> /// </summary>
[PluginInterface] [PluginInterface]
[InterfaceVersion("1.0")] [InterfaceVersion("1.0")]
[ServiceManager.BlockingEarlyLoadedService]
public sealed class LibcFunction public sealed class LibcFunction
{ {
private readonly LibcFunctionAddressResolver address; private readonly LibcFunctionAddressResolver address;
private readonly StdStringFromCStringDelegate stdStringCtorCString; private readonly StdStringFromCStringDelegate stdStringCtorCString;
private readonly StdStringDeallocateDelegate stdStringDeallocate; private readonly StdStringDeallocateDelegate stdStringDeallocate;
/// <summary> [ServiceManager.ServiceConstructor]
/// Initializes a new instance of the <see cref="LibcFunction"/> class. private LibcFunction(SigScanner sigScanner)
/// </summary>
public LibcFunction()
{ {
this.address = new LibcFunctionAddressResolver(); this.address = new LibcFunctionAddressResolver();
this.address.Setup(); this.address.Setup(sigScanner);
this.stdStringCtorCString = Marshal.GetDelegateForFunctionPointer<StdStringFromCStringDelegate>(this.address.StdStringFromCstring); this.stdStringCtorCString = Marshal.GetDelegateForFunctionPointer<StdStringFromCStringDelegate>(this.address.StdStringFromCstring);
this.stdStringDeallocate = Marshal.GetDelegateForFunctionPointer<StdStringDeallocateDelegate>(this.address.StdStringDeallocate); this.stdStringDeallocate = Marshal.GetDelegateForFunctionPointer<StdStringDeallocateDelegate>(this.address.StdStringDeallocate);

View file

@ -14,6 +14,7 @@ namespace Dalamud.Game.Network
/// </summary> /// </summary>
[PluginInterface] [PluginInterface]
[InterfaceVersion("1.0")] [InterfaceVersion("1.0")]
[ServiceManager.BlockingEarlyLoadedService]
public sealed class GameNetwork : IDisposable public sealed class GameNetwork : IDisposable
{ {
private readonly GameNetworkAddressResolver address; private readonly GameNetworkAddressResolver address;
@ -23,13 +24,11 @@ namespace Dalamud.Game.Network
private IntPtr baseAddress; private IntPtr baseAddress;
/// <summary> [ServiceManager.ServiceConstructor]
/// Initializes a new instance of the <see cref="GameNetwork"/> class. private GameNetwork(SigScanner sigScanner)
/// </summary>
internal GameNetwork()
{ {
this.address = new GameNetworkAddressResolver(); 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("===== G A M E N E T W O R K =====");
Log.Verbose($"ProcessZonePacketDown address 0x{this.address.ProcessZonePacketDown.ToInt64():X}"); Log.Verbose($"ProcessZonePacketDown address 0x{this.address.ProcessZonePacketDown.ToInt64():X}");

View file

@ -21,6 +21,7 @@ namespace Dalamud.Game.Network.Internal
/// <summary> /// <summary>
/// This class handles network notifications and uploading market board data. /// This class handles network notifications and uploading market board data.
/// </summary> /// </summary>
[ServiceManager.EarlyLoadedService]
internal class NetworkHandlers internal class NetworkHandlers
{ {
private readonly List<MarketBoardItemRequest> marketBoardRequests = new(); private readonly List<MarketBoardItemRequest> marketBoardRequests = new();
@ -29,14 +30,12 @@ namespace Dalamud.Game.Network.Internal
private MarketBoardPurchaseHandler marketBoardPurchaseHandler; private MarketBoardPurchaseHandler marketBoardPurchaseHandler;
/// <summary> [ServiceManager.ServiceConstructor]
/// Initializes a new instance of the <see cref="NetworkHandlers"/> class. private NetworkHandlers(GameNetwork gameNetwork)
/// </summary>
public NetworkHandlers()
{ {
this.uploader = new UniversalisMarketBoardUploader(); this.uploader = new UniversalisMarketBoardUploader();
Service<GameNetwork>.Get().NetworkMessage += this.OnNetworkMessage; gameNetwork.NetworkMessage += this.OnNetworkMessage;
} }
/// <summary> /// <summary>

View file

@ -9,14 +9,13 @@ namespace Dalamud.Game.Network.Internal
/// <summary> /// <summary>
/// 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]
internal sealed class WinSockHandlers : IDisposable internal sealed class WinSockHandlers : IDisposable
{ {
private Hook<SocketDelegate> ws2SocketHook; private Hook<SocketDelegate> ws2SocketHook;
/// <summary> [ServiceManager.ServiceConstructor]
/// Initializes a new instance of the <see cref="WinSockHandlers"/> class. private WinSockHandlers()
/// </summary>
public WinSockHandlers()
{ {
this.ws2SocketHook = Hook<SocketDelegate>.FromSymbol("ws2_32.dll", "socket", this.OnSocket, true); this.ws2SocketHook = Hook<SocketDelegate>.FromSymbol("ws2_32.dll", "socket", this.OnSocket, true);
this.ws2SocketHook?.Enable(); this.ws2SocketHook?.Enable();

View file

@ -8,6 +8,7 @@ using System.Runtime.InteropServices;
using Dalamud.IoC; using Dalamud.IoC;
using Dalamud.IoC.Internal; using Dalamud.IoC.Internal;
using Dalamud.Utility.Timing;
using Newtonsoft.Json; using Newtonsoft.Json;
using Serilog; using Serilog;
@ -18,6 +19,7 @@ namespace Dalamud.Game
/// </summary> /// </summary>
[PluginInterface] [PluginInterface]
[InterfaceVersion("1.0")] [InterfaceVersion("1.0")]
[ServiceManager.BlockingEarlyLoadedService]
public class SigScanner : IDisposable public class SigScanner : IDisposable
{ {
private readonly FileInfo? cacheFile; private readonly FileInfo? cacheFile;
@ -27,6 +29,38 @@ namespace Dalamud.Game
private Dictionary<string, long>? textCache; private Dictionary<string, long>? 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")));
}
}
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="SigScanner"/> class using the main module of the current process. /// Initializes a new instance of the <see cref="SigScanner"/> class using the main module of the current process.
/// </summary> /// </summary>

View file

@ -12,13 +12,12 @@ namespace Dalamud.Game.Text.SeStringHandling
/// </summary> /// </summary>
[PluginInterface] [PluginInterface]
[InterfaceVersion("1.0")] [InterfaceVersion("1.0")]
[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
{ {
/// <summary> [ServiceManager.ServiceConstructor]
/// Initializes a new instance of the <see cref="SeStringManager"/> class. private SeStringManager()
/// </summary>
internal SeStringManager()
{ {
} }

View file

@ -13,14 +13,13 @@ namespace Dalamud.Hooking.Internal
/// <summary> /// <summary>
/// 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]
internal class HookManager : IDisposable internal class HookManager : IDisposable
{ {
private static readonly ModuleLog Log = new("HM"); private static readonly ModuleLog Log = new("HM");
/// <summary> [ServiceManager.ServiceConstructor]
/// Initializes a new instance of the <see cref="HookManager"/> class. private HookManager()
/// </summary>
public HookManager()
{ {
} }

View file

@ -4,7 +4,7 @@ using System.Linq;
using System.Numerics; using System.Numerics;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Text; using System.Text;
using System.Threading.Tasks;
using Dalamud.Data; using Dalamud.Data;
using Dalamud.Interface.Internal; using Dalamud.Interface.Internal;
using Dalamud.Utility.Timing; using Dalamud.Utility.Timing;
@ -17,9 +17,10 @@ namespace Dalamud.Interface.GameFonts
/// <summary> /// <summary>
/// Loads game font for use in ImGui. /// Loads game font for use in ImGui.
/// </summary> /// </summary>
[ServiceManager.EarlyLoadedService]
internal class GameFontManager : IDisposable internal class GameFontManager : IDisposable
{ {
private static readonly string[] FontNames = private static readonly string?[] FontNames =
{ {
null, null,
"AXIS_96", "AXIS_12", "AXIS_14", "AXIS_18", "AXIS_36", "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 object syncRoot = new();
private readonly InterfaceManager interfaceManager;
private readonly FdtReader?[] fdts; private readonly FdtReader?[] fdts;
private readonly List<byte[]> texturePixels; private readonly List<byte[]> texturePixels;
private readonly Dictionary<GameFontStyle, ImFontPtr> fonts = new(); private readonly Dictionary<GameFontStyle, ImFontPtr> fonts = new();
@ -42,40 +41,28 @@ namespace Dalamud.Interface.GameFonts
private bool isBetweenBuildFontsAndRightAfterImGuiIoFontsBuild = false; private bool isBetweenBuildFontsAndRightAfterImGuiIoFontsBuild = false;
private bool isBuildingAsFallbackFontMode = false; private bool isBuildingAsFallbackFontMode = false;
/// <summary> [ServiceManager.ServiceConstructor]
/// Initializes a new instance of the <see cref="GameFontManager"/> class. private GameFontManager(DataManager dataManager)
/// </summary>
public GameFontManager()
{ {
var dataManager = Service<DataManager>.Get(); using (Timings.Start("Getting fdt data"))
using (Timings.Start("Load FDTs"))
{ {
this.fdts = FontNames.Select(fontName => this.fdts = FontNames.Select(fontName => fontName == null ? null : new FdtReader(dataManager.GetFile($"common/font/{fontName}.fdt")!.Data)).ToArray();
{
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();
} }
using (Timings.Start("Getting texture data")) 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( var texTasks = Enumerable
x => .Range(1, 1 + this.fdts
{ .Where(x => x != null)
var fileName = $"common/font/font{x}.tex"; .Select(x => x.Glyphs.Select(y => y.TextureFileIndex).Max())
using (Timings.Start($"Get tex: {fileName}")) .Max())
{ .Select(x => dataManager.GetFile<TexFile>($"common/font/font{x}.tex")!)
return dataManager.GameData.GetFile<TexFile>(fileName)!.ImageData; .Select(x => new Task<byte[]>(() => x.ImageData!))
} .ToArray();
}).ToList(); foreach (var task in texTasks)
task.Start();
this.texturePixels = texTasks.Select(x => x.GetAwaiter().GetResult()).ToList();
} }
this.interfaceManager = Service<InterfaceManager>.Get();
} }
/// <summary> /// <summary>
@ -183,6 +170,7 @@ namespace Dalamud.Interface.GameFonts
/// <returns>Handle to game font that may or may not be ready yet.</returns> /// <returns>Handle to game font that may or may not be ready yet.</returns>
public GameFontHandle NewFontRef(GameFontStyle style) public GameFontHandle NewFontRef(GameFontStyle style)
{ {
var interfaceManager = Service<InterfaceManager>.Get();
var needRebuild = false; var needRebuild = false;
lock (this.syncRoot) lock (this.syncRoot)
@ -193,7 +181,7 @@ namespace Dalamud.Interface.GameFonts
needRebuild = !this.fonts.ContainsKey(style); needRebuild = !this.fonts.ContainsKey(style);
if (needRebuild) if (needRebuild)
{ {
if (Service<InterfaceManager>.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()); 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); this.EnsureFont(style);
@ -201,7 +189,7 @@ namespace Dalamud.Interface.GameFonts
else else
{ {
Log.Information("[GameFontManager] NewFontRef: Calling RebuildFonts because {0} has been requested.", style.ToString()); 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
/// </summary> /// </summary>
public unsafe void AfterBuildFonts() public unsafe void AfterBuildFonts()
{ {
var interfaceManager = Service<InterfaceManager>.Get();
var ioFonts = ImGui.GetIO().Fonts; var ioFonts = ImGui.GetIO().Fonts;
ioFonts.GetTexDataAsRGBA32(out byte* pixels8, out var width, out var height); ioFonts.GetTexDataAsRGBA32(out byte* pixels8, out var width, out var height);
var pixels32 = (uint*)pixels8; var pixels32 = (uint*)pixels8;
var fontGamma = this.interfaceManager.FontGamma; var fontGamma = interfaceManager.FontGamma;
foreach (var (style, font) in this.fonts) foreach (var (style, font) in this.fonts)
{ {

View file

@ -18,22 +18,12 @@ namespace Dalamud.Interface.Internal
/// <summary> /// <summary>
/// Class handling Dalamud core commands. /// Class handling Dalamud core commands.
/// </summary> /// </summary>
[ServiceManager.EarlyLoadedService]
internal class DalamudCommands internal class DalamudCommands
{ {
/// <summary> [ServiceManager.ServiceConstructor]
/// Initializes a new instance of the <see cref="DalamudCommands"/> class. private DalamudCommands(CommandManager commandManager)
/// </summary>
public DalamudCommands()
{ {
}
/// <summary>
/// Register all command handlers with the Dalamud instance.
/// </summary>
public void SetupCommands()
{
var commandManager = Service<CommandManager>.Get();
commandManager.AddHandler("/xldclose", new CommandInfo(this.OnUnloadCommand) commandManager.AddHandler("/xldclose", new CommandInfo(this.OnUnloadCommand)
{ {
HelpMessage = Loc.Localize("DalamudUnloadHelp", "Unloads XIVLauncher in-game addon."), HelpMessage = Loc.Localize("DalamudUnloadHelp", "Unloads XIVLauncher in-game addon."),

View file

@ -35,6 +35,7 @@ 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.EarlyLoadedService]
internal class DalamudInterface : IDisposable internal class DalamudInterface : IDisposable
{ {
private static readonly ModuleLog Log = new("DUI"); private static readonly ModuleLog Log = new("DUI");
@ -75,14 +76,9 @@ namespace Dalamud.Interface.Internal
private bool isImGuiTestWindowsInMonospace = false; private bool isImGuiTestWindowsInMonospace = false;
private bool isImGuiDrawMetricsWindow = false; private bool isImGuiDrawMetricsWindow = false;
/// <summary> [ServiceManager.ServiceConstructor]
/// Initializes a new instance of the <see cref="DalamudInterface"/> class. private DalamudInterface(Dalamud dalamud, DalamudConfiguration configuration, InterfaceManager interfaceManager)
/// </summary>
public DalamudInterface()
{ {
var configuration = Service<DalamudConfiguration>.Get();
var interfaceManager = Service<InterfaceManager>.Get();
this.WindowSystem = new WindowSystem("DalamudCore"); this.WindowSystem = new WindowSystem("DalamudCore");
this.changelogWindow = new ChangelogWindow() { IsOpen = false }; this.changelogWindow = new ChangelogWindow() { IsOpen = false };
@ -123,7 +119,6 @@ namespace Dalamud.Interface.Internal
this.isImGuiDrawDevMenu = this.isImGuiDrawDevMenu || configuration.DevBarOpenAtStartup; this.isImGuiDrawDevMenu = this.isImGuiDrawDevMenu || configuration.DevBarOpenAtStartup;
interfaceManager.Draw += this.OnDraw; interfaceManager.Draw += this.OnDraw;
var dalamud = Service<Dalamud>.Get();
var logoTex = var logoTex =
interfaceManager.LoadImage(Path.Combine(dalamud.AssetDirectory.FullName, "UIRes", "logo.png")); interfaceManager.LoadImage(Path.Combine(dalamud.AssetDirectory.FullName, "UIRes", "logo.png"));

View file

@ -7,7 +7,7 @@ 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;
@ -44,6 +44,7 @@ namespace Dalamud.Interface.Internal
/// <summary> /// <summary>
/// This class manages interaction with the ImGui interface. /// This class manages interaction with the ImGui interface.
/// </summary> /// </summary>
[ServiceManager.BlockingEarlyLoadedService]
internal class InterfaceManager : IDisposable internal class InterfaceManager : IDisposable
{ {
private const float MinimumFallbackFontSizePt = 9.6f; // Game's minimum AXIS font size private const float MinimumFallbackFontSizePt = 9.6f; // Game's minimum AXIS font size
@ -58,33 +59,27 @@ namespace Dalamud.Interface.Internal
private readonly HashSet<SpecialGlyphRequest> glyphRequests = new(); private readonly HashSet<SpecialGlyphRequest> glyphRequests = new();
private readonly Dictionary<ImFontPtr, TargetFontModification> loadedFontInfo = new(); private readonly Dictionary<ImFontPtr, TargetFontModification> loadedFontInfo = new();
private readonly Hook<PresentDelegate> presentHook;
private readonly Hook<ResizeBuffersDelegate> resizeBuffersHook;
private readonly Hook<SetCursorDelegate> setCursorHook;
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<ResizeBuffersDelegate>? resizeBuffersHook;
private Hook<SetCursorDelegate>? setCursorHook;
// 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 isFallbackFontMode = false; private bool isFallbackFontMode = false;
/// <summary> [ServiceManager.ServiceConstructor]
/// Initializes a new instance of the <see cref="InterfaceManager"/> class. private InterfaceManager()
/// </summary>
public InterfaceManager()
{ {
Service<NotificationManager>.Set();
var scanner = Service<SigScanner>.Get();
this.fontBuildSignal = new ManualResetEvent(false); this.fontBuildSignal = new ManualResetEvent(false);
this.address = new SwapChainVtableResolver(); this.address = new SwapChainVtableResolver();
this.address.Setup(scanner);
try try
{ {
@ -106,16 +101,12 @@ namespace Dalamud.Interface.Internal
Log.Error(e, "RTSS Free failed"); Log.Error(e, "RTSS Free failed");
} }
this.setCursorHook = Hook<SetCursorDelegate>.FromSymbol("user32.dll", "SetCursor", this.SetCursorDetour, true); Task.Run(async () =>
this.presentHook = new Hook<PresentDelegate>(this.address.Present, this.PresentDetour); {
this.resizeBuffersHook = new Hook<ResizeBuffersDelegate>(this.address.ResizeBuffers, this.ResizeBuffersDetour); var framework = await Service<Framework>.GetAsync();
var sigScanner = await Service<SigScanner>.GetAsync();
var setCursorAddress = this.setCursorHook?.Address ?? IntPtr.Zero; await framework.RunOnFrameworkThread(() => this.Enable(sigScanner));
});
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}");
} }
[UnmanagedFunctionPointer(CallingConvention.ThisCall)] [UnmanagedFunctionPointer(CallingConvention.ThisCall)]
@ -129,6 +120,11 @@ namespace Dalamud.Interface.Internal
private delegate void InstallRTSSHook(); private delegate void InstallRTSSHook();
/// <summary>
/// Gets a task that gets completed when scene gets initialized.
/// </summary>
public Task SceneInitializeTask => this.sceneInitializeTaskCompletionSource.Task;
/// <summary> /// <summary>
/// This event gets called each frame to facilitate ImGui drawing. /// This event gets called each frame to facilitate ImGui drawing.
/// </summary> /// </summary>
@ -259,34 +255,6 @@ namespace Dalamud.Interface.Internal
/// </summary> /// </summary>
public bool IsBuildingFontsBeforeAtlasBuild => this.isRebuildingFonts && !this.fontBuildSignal.WaitOne(0); public bool IsBuildingFontsBeforeAtlasBuild => this.isRebuildingFonts && !this.fontBuildSignal.WaitOne(0);
/// <summary>
/// Enable this module.
/// </summary>
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<InstallRTSSHook>(installAddr).Invoke();
Log.Debug("RTSS hook OK!");
}
}
catch (Exception ex)
{
Log.Error(ex, "Could not reload RTSS");
}
}
/// <summary> /// <summary>
/// Dispose of managed and unmanaged resources. /// Dispose of managed and unmanaged resources.
/// </summary> /// </summary>
@ -303,8 +271,8 @@ namespace Dalamud.Interface.Internal
this.scene?.Dispose(); this.scene?.Dispose();
this.setCursorHook?.Dispose(); this.setCursorHook?.Dispose();
this.presentHook.Dispose(); this.presentHook?.Dispose();
this.resizeBuffersHook.Dispose(); this.resizeBuffersHook?.Dispose();
} }
#nullable enable #nullable enable
@ -480,9 +448,11 @@ namespace Dalamud.Interface.Internal
try try
{ {
this.scene = new RawDX11Scene(swapChain); this.scene = new RawDX11Scene(swapChain);
this.sceneInitializeTaskCompletionSource.SetResult();
} }
catch (DllNotFoundException ex) catch (DllNotFoundException ex)
{ {
this.sceneInitializeTaskCompletionSource.SetException(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(
@ -1003,6 +973,43 @@ namespace Dalamud.Interface.Internal
} }
} }
private void Enable(SigScanner sigScanner)
{
this.address.Setup(sigScanner);
this.setCursorHook = Hook<SetCursorDelegate>.FromSymbol("user32.dll", "SetCursor", this.SetCursorDetour, true);
this.presentHook = new Hook<PresentDelegate>(this.address.Present, this.PresentDetour);
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($"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<InstallRTSSHook>(installAddr).Invoke();
Log.Debug("RTSS hook OK!");
}
}
catch (Exception ex)
{
Log.Error(ex, "Could not reload RTSS");
}
}
private void Disable() private void Disable()
{ {
this.setCursorHook?.Disable(); this.setCursorHook?.Disable();
@ -1094,6 +1101,9 @@ namespace Dalamud.Interface.Internal
var gamepadState = Service<GamepadState>.GetNullable(); var gamepadState = Service<GamepadState>.GetNullable();
var keyState = Service<KeyState>.GetNullable(); var keyState = Service<KeyState>.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) // 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, // 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 // so the game would think the key remained pressed continuously until you left

View file

@ -13,6 +13,7 @@ namespace Dalamud.Interface.Internal.Notifications
/// Class handling notifications/toasts in ImGui. /// Class handling notifications/toasts in ImGui.
/// Ported from https://github.com/patrickcjk/imgui-notify. /// Ported from https://github.com/patrickcjk/imgui-notify.
/// </summary> /// </summary>
[ServiceManager.EarlyLoadedService]
internal class NotificationManager internal class NotificationManager
{ {
/// <summary> /// <summary>
@ -54,6 +55,11 @@ namespace Dalamud.Interface.Internal.Notifications
private readonly List<Notification> notifications = new(); private readonly List<Notification> notifications = new();
[ServiceManager.ServiceConstructor]
private NotificationManager()
{
}
/// <summary> /// <summary>
/// Add a notification to the notification queue. /// Add a notification to the notification queue.
/// </summary> /// </summary>

View file

@ -1485,7 +1485,7 @@ namespace Dalamud.Interface.Internal.Windows
ImGuiHelpers.ScaledDummy(20); ImGuiHelpers.ScaledDummy(20);
// Needed to init the task tracker, if we're not on a debug build // Needed to init the task tracker, if we're not on a debug build
var tracker = Service<TaskTracker>.GetNullable() ?? Service<TaskTracker>.Set(); Service<TaskTracker>.Get().Enable();
for (var i = 0; i < TaskTracker.Tasks.Count; i++) for (var i = 0; i < TaskTracker.Tasks.Count; i++)
{ {

View file

@ -60,6 +60,8 @@ namespace Dalamud.Interface.Internal.Windows
var dalamud = Service<Dalamud>.Get(); var dalamud = Service<Dalamud>.Get();
var interfaceManager = Service<InterfaceManager>.Get(); var interfaceManager = Service<InterfaceManager>.Get();
interfaceManager.SceneInitializeTask.Wait();
this.DefaultIcon = interfaceManager.LoadImage(Path.Combine(dalamud.AssetDirectory.FullName, "UIRes", "defaultIcon.png"))!; 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.TroubleIcon = interfaceManager.LoadImage(Path.Combine(dalamud.AssetDirectory.FullName, "UIRes", "troubleIcon.png"))!;
this.UpdateIcon = interfaceManager.LoadImage(Path.Combine(dalamud.AssetDirectory.FullName, "UIRes", "updateIcon.png"))!; this.UpdateIcon = interfaceManager.LoadImage(Path.Combine(dalamud.AssetDirectory.FullName, "UIRes", "updateIcon.png"))!;

View file

@ -33,6 +33,7 @@ namespace Dalamud.Interface.Internal.Windows
private XivChatType dalamudMessagesChatType; private XivChatType dalamudMessagesChatType;
private bool doWaitForPluginsOnStartup;
private bool doCfTaskBarFlash; private bool doCfTaskBarFlash;
private bool doCfChatMessage; private bool doCfChatMessage;
private bool doMbCollect; private bool doMbCollect;
@ -90,6 +91,7 @@ namespace Dalamud.Interface.Internal.Windows
this.dalamudMessagesChatType = configuration.GeneralChatType; this.dalamudMessagesChatType = configuration.GeneralChatType;
this.doWaitForPluginsOnStartup = configuration.IsResumeGameAfterPluginLoad;
this.doCfTaskBarFlash = configuration.DutyFinderTaskbarFlash; this.doCfTaskBarFlash = configuration.DutyFinderTaskbarFlash;
this.doCfChatMessage = configuration.DutyFinderChatMessage; this.doCfChatMessage = configuration.DutyFinderChatMessage;
this.doMbCollect = configuration.IsMbCollect; this.doMbCollect = configuration.IsMbCollect;
@ -261,6 +263,9 @@ namespace Dalamud.Interface.Internal.Windows
ImGuiHelpers.ScaledDummy(5); 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.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.")); 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.GeneralChatType = this.dalamudMessagesChatType;
configuration.IsResumeGameAfterPluginLoad = this.doWaitForPluginsOnStartup;
configuration.DutyFinderTaskbarFlash = this.doCfTaskBarFlash; configuration.DutyFinderTaskbarFlash = this.doCfTaskBarFlash;
configuration.DutyFinderChatMessage = this.doCfChatMessage; configuration.DutyFinderChatMessage = this.doCfChatMessage;
configuration.IsMbCollect = this.doMbCollect; configuration.IsMbCollect = this.doMbCollect;

View file

@ -12,6 +12,7 @@ namespace Dalamud.Interface
/// </summary> /// </summary>
[PluginInterface] [PluginInterface]
[InterfaceVersion("1.0")] [InterfaceVersion("1.0")]
[ServiceManager.BlockingEarlyLoadedService]
public class TitleScreenMenu public class TitleScreenMenu
{ {
/// <summary> /// <summary>
@ -21,6 +22,11 @@ namespace Dalamud.Interface
private readonly List<TitleScreenMenuEntry> entries = new(); private readonly List<TitleScreenMenuEntry> entries = new();
[ServiceManager.ServiceConstructor]
private TitleScreenMenu()
{
}
/// <summary> /// <summary>
/// Gets the list of entries in the title screen menu. /// Gets the list of entries in the title screen menu.
/// </summary> /// </summary>

View file

@ -1,5 +1,6 @@
using System; using System;
using System.Reflection; using System.Reflection;
using System.Threading.Tasks;
namespace Dalamud.IoC.Internal namespace Dalamud.IoC.Internal
{ {
@ -11,11 +12,12 @@ namespace Dalamud.IoC.Internal
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="ObjectInstance"/> class. /// Initializes a new instance of the <see cref="ObjectInstance"/> class.
/// </summary> /// </summary>
/// <param name="instance">The underlying instance.</param> /// <param name="instanceTask">Weak reference to the underlying instance.</param>
public ObjectInstance(object instance) /// <param name="type">Type of the underlying instance.</param>
public ObjectInstance(Task<WeakReference> instanceTask, Type type)
{ {
this.Instance = new WeakReference(instance); this.InstanceTask = instanceTask;
this.Version = instance.GetType().GetCustomAttribute<InterfaceVersionAttribute>(); this.Version = type.GetCustomAttribute<InterfaceVersionAttribute>();
} }
/// <summary> /// <summary>
@ -26,6 +28,7 @@ namespace Dalamud.IoC.Internal
/// <summary> /// <summary>
/// Gets a reference to the underlying instance. /// Gets a reference to the underlying instance.
/// </summary> /// </summary>
public WeakReference Instance { get; } /// <returns>The underlying instance.</returns>
public Task<WeakReference> InstanceTask { get; }
} }
} }

View file

@ -3,7 +3,7 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
using System.Runtime.Serialization; using System.Runtime.Serialization;
using System.Threading.Tasks;
using Dalamud.Logging.Internal; using Dalamud.Logging.Internal;
namespace Dalamud.IoC.Internal namespace Dalamud.IoC.Internal
@ -17,19 +17,26 @@ namespace Dalamud.IoC.Internal
private readonly Dictionary<Type, ObjectInstance> instances = new(); private readonly Dictionary<Type, ObjectInstance> instances = new();
/// <summary>
/// Initializes a new instance of the <see cref="ServiceContainer"/> class.
/// </summary>
public ServiceContainer()
{
}
/// <summary> /// <summary>
/// Register a singleton object of any type into the current IOC container. /// Register a singleton object of any type into the current IOC container.
/// </summary> /// </summary>
/// <param name="instance">The existing instance to register in the container.</param> /// <param name="instance">The existing instance to register in the container.</param>
/// <typeparam name="T">The interface to register.</typeparam> /// <typeparam name="T">The interface to register.</typeparam>
public void RegisterSingleton<T>(T instance) public void RegisterSingleton<T>(Task<T> instance)
{ {
if (instance == null) if (instance == null)
{ {
throw new ArgumentNullException(nameof(instance)); 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));
} }
/// <summary> /// <summary>
@ -38,12 +45,12 @@ namespace Dalamud.IoC.Internal
/// <param name="objectType">The type of object to create.</param> /// <param name="objectType">The type of object to create.</param>
/// <param name="scopedObjects">Scoped objects to be included in the constructor.</param> /// <param name="scopedObjects">Scoped objects to be included in the constructor.</param>
/// <returns>The created object.</returns> /// <returns>The created object.</returns>
public object? Create(Type objectType, params object[] scopedObjects) public async Task<object?> Create(Type objectType, params object[] scopedObjects)
{ {
var ctor = this.FindApplicableCtor(objectType, scopedObjects); var ctor = this.FindApplicableCtor(objectType, scopedObjects);
if (ctor == null) 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; return null;
} }
@ -59,36 +66,37 @@ namespace Dalamud.IoC.Internal
if (!versionCheck) 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; return null;
} }
var resolvedParams = parameters var resolvedParams =
.Select(p => await Task.WhenAll(
{ parameters
var service = this.GetService(p.parameterType, scopedObjects); .Select(async p =>
{
var service = await this.GetService(p.parameterType, scopedObjects);
if (service == null) if (service == null)
{ {
Log.Error("Requested service type {TypeName} was not available (null)", p.parameterType.FullName); Log.Error("Requested service type {TypeName} was not available (null)", p.parameterType.FullName!);
} }
return service; return service;
}) }));
.ToArray();
var hasNull = resolvedParams.Any(p => p == null); var hasNull = resolvedParams.Any(p => p == null);
if (hasNull) 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; return null;
} }
var instance = FormatterServices.GetUninitializedObject(objectType); 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; return null;
} }
@ -105,7 +113,7 @@ namespace Dalamud.IoC.Internal
/// <param name="instance">The object instance.</param> /// <param name="instance">The object instance.</param>
/// <param name="scopedObjects">Scoped objects.</param> /// <param name="scopedObjects">Scoped objects.</param>
/// <returns>Whether or not the injection was successful.</returns> /// <returns>Whether or not the injection was successful.</returns>
public bool InjectProperties(object instance, params object[] scopedObjects) public async Task<bool> InjectProperties(object instance, params object[] scopedObjects)
{ {
var objectType = instance.GetType(); var objectType = instance.GetType();
@ -121,17 +129,17 @@ namespace Dalamud.IoC.Internal
if (!versionCheck) 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; return false;
} }
foreach (var prop in props) 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) 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; return false;
} }
@ -162,14 +170,14 @@ namespace Dalamud.IoC.Internal
"Requested version {ReqVersion} does not match the implemented version {ImplVersion} for param type {ParamType}", "Requested version {ReqVersion} does not match the implemented version {ImplVersion} for param type {ParamType}",
requiredVersion.Version, requiredVersion.Version,
declVersion.Version, declVersion.Version,
parameterType.FullName); parameterType.FullName!);
return false; return false;
} }
private object? GetService(Type serviceType, object[] scopedObjects) private async Task<object?> GetService(Type serviceType, object[] scopedObjects)
{ {
var singletonService = this.GetService(serviceType); var singletonService = await this.GetService(serviceType);
if (singletonService != null) if (singletonService != null)
{ {
return singletonService; return singletonService;
@ -185,15 +193,13 @@ namespace Dalamud.IoC.Internal
return scoped; return scoped;
} }
private object? GetService(Type serviceType) private async Task<object?> GetService(Type serviceType)
{ {
var hasInstance = this.instances.TryGetValue(serviceType, out var service); if (!this.instances.TryGetValue(serviceType, out var service))
if (hasInstance && service.Instance.IsAlive) return null;
{
return service.Instance.Target;
}
return null; var instance = await service.InstanceTask;
return instance.Target;
} }
private ConstructorInfo? FindApplicableCtor(Type type, object[] scopedObjects) private ConstructorInfo? FindApplicableCtor(Type type, object[] scopedObjects)
@ -224,7 +230,7 @@ namespace Dalamud.IoC.Internal
var contains = types.Contains(parameter.ParameterType); var contains = types.Contains(parameter.ParameterType);
if (!contains) 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; return false;
} }
} }

View file

@ -5,6 +5,7 @@ using System.Linq;
using System.Reflection; using System.Reflection;
using CheapLoc; using CheapLoc;
using Dalamud.Configuration.Internal;
using Serilog; using Serilog;
namespace Dalamud namespace Dalamud
@ -12,6 +13,7 @@ namespace Dalamud
/// <summary> /// <summary>
/// Class handling localization. /// Class handling localization.
/// </summary> /// </summary>
[ServiceManager.EarlyLoadedService]
public class Localization public class Localization
{ {
/// <summary> /// <summary>
@ -40,6 +42,16 @@ namespace Dalamud
this.assembly = Assembly.GetCallingAssembly(); 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();
}
/// <summary> /// <summary>
/// Delegate for the <see cref="Localization.LocalizationChanged"/> event that occurs when the language is changed. /// Delegate for the <see cref="Localization.LocalizationChanged"/> event that occurs when the language is changed.
/// </summary> /// </summary>

View file

@ -5,7 +5,7 @@ namespace Dalamud.Logging.Internal
/// <summary> /// <summary>
/// Class offering various methods to allow for logging in Dalamud modules. /// Class offering various methods to allow for logging in Dalamud modules.
/// </summary> /// </summary>
internal class ModuleLog public class ModuleLog
{ {
private readonly string moduleName; private readonly string moduleName;

View file

@ -12,6 +12,7 @@ namespace Dalamud.Logging.Internal
/// <summary> /// <summary>
/// Class responsible for tracking asynchronous tasks. /// Class responsible for tracking asynchronous tasks.
/// </summary> /// </summary>
[ServiceManager.EarlyLoadedService]
internal class TaskTracker : IDisposable internal class TaskTracker : IDisposable
{ {
private static readonly ModuleLog Log = new("TT"); private static readonly ModuleLog Log = new("TT");
@ -20,16 +21,14 @@ namespace Dalamud.Logging.Internal
private static bool clearRequested = false; private static bool clearRequested = false;
private MonoMod.RuntimeDetour.Hook? scheduleAndStartHook; private MonoMod.RuntimeDetour.Hook? scheduleAndStartHook;
private bool enabled = false;
/// <summary> [ServiceManager.ServiceConstructor]
/// Initializes a new instance of the <see cref="TaskTracker"/> class. private TaskTracker()
/// </summary>
public TaskTracker()
{ {
this.ApplyPatch(); #if DEBUG
this.Enable();
var framework = Service<Framework>.Get(); #endif
framework.Update += this.FrameworkOnUpdate;
} }
/// <summary> /// <summary>
@ -102,6 +101,21 @@ namespace Dalamud.Logging.Internal
} }
} }
/// <summary>
/// Enables TaskTracker.
/// </summary>
public void Enable()
{
if (this.enabled)
return;
this.ApplyPatch();
var framework = Service<Framework>.Get();
framework.Update += this.FrameworkOnUpdate;
this.enabled = true;
}
/// <inheritdoc/> /// <inheritdoc/>
public void Dispose() public void Dispose()
{ {

View file

@ -378,7 +378,7 @@ namespace Dalamud.Plugin
realScopedObjects[0] = this; realScopedObjects[0] = this;
Array.Copy(scopedObjects, 0, realScopedObjects, 1, scopedObjects.Length); Array.Copy(scopedObjects, 0, realScopedObjects, 1, scopedObjects.Length);
return svcContainer.InjectProperties(instance, realScopedObjects); return svcContainer.InjectProperties(instance, realScopedObjects).GetAwaiter().GetResult();
} }
#endregion #endregion

View file

@ -27,6 +27,7 @@ namespace Dalamud.Plugin.Internal;
/// <summary> /// <summary>
/// Class responsible for loading and unloading plugins. /// Class responsible for loading and unloading plugins.
/// </summary> /// </summary>
[ServiceManager.EarlyLoadedService]
internal partial class PluginManager : IDisposable internal partial class PluginManager : IDisposable
{ {
/// <summary> /// <summary>
@ -40,16 +41,17 @@ internal partial class PluginManager : IDisposable
private readonly DirectoryInfo devPluginDirectory; private readonly DirectoryInfo devPluginDirectory;
private readonly BannedPlugin[] bannedPlugins; private readonly BannedPlugin[] bannedPlugins;
/// <summary> [ServiceManager.ServiceDependency]
/// Initializes a new instance of the <see cref="PluginManager"/> class. private readonly DalamudConfiguration configuration = Service<DalamudConfiguration>.Get();
/// </summary>
public PluginManager()
{
var startInfo = Service<DalamudStartInfo>.Get();
var configuration = Service<DalamudConfiguration>.Get();
this.pluginDirectory = new DirectoryInfo(startInfo.PluginDirectory); [ServiceManager.ServiceDependency]
this.devPluginDirectory = new DirectoryInfo(startInfo.DefaultPluginDirectory); private readonly DalamudStartInfo startInfo = Service<DalamudStartInfo>.Get();
[ServiceManager.ServiceConstructor]
private PluginManager()
{
this.pluginDirectory = new DirectoryInfo(this.startInfo.PluginDirectory!);
this.devPluginDirectory = new DirectoryInfo(this.startInfo.DefaultPluginDirectory!);
if (!this.pluginDirectory.Exists) if (!this.pluginDirectory.Exists)
this.pluginDirectory.Create(); this.pluginDirectory.Create();
@ -57,16 +59,16 @@ internal partial class PluginManager : IDisposable
if (!this.devPluginDirectory.Exists) if (!this.devPluginDirectory.Exists)
this.devPluginDirectory.Create(); this.devPluginDirectory.Create();
this.SafeMode = EnvironmentConfiguration.DalamudNoPlugins || configuration.PluginSafeMode; this.SafeMode = EnvironmentConfiguration.DalamudNoPlugins || this.configuration.PluginSafeMode;
if (this.SafeMode) if (this.SafeMode)
{ {
configuration.PluginSafeMode = false; this.configuration.PluginSafeMode = false;
configuration.Save(); 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<BannedPlugin[]>(bannedPluginsJson) ?? Array.Empty<BannedPlugin>(); this.bannedPlugins = JsonConvert.DeserializeObject<BannedPlugin[]>(bannedPluginsJson) ?? Array.Empty<BannedPlugin>();
this.ApplyPatches(); this.ApplyPatches();
@ -225,12 +227,10 @@ internal partial class PluginManager : IDisposable
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns> /// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
public async Task SetPluginReposFromConfigAsync(bool notify) public async Task SetPluginReposFromConfigAsync(bool notify)
{ {
var configuration = Service<DalamudConfiguration>.Get();
var repos = new List<PluginRepository>() { PluginRepository.MainRepo }; var repos = new List<PluginRepository>() { PluginRepository.MainRepo };
repos.AddRange(configuration.ThirdRepoList repos.AddRange(this.configuration.ThirdRepoList
.Where(repo => repo.IsEnabled) .Where(repo => repo.IsEnabled)
.Select(repo => new PluginRepository(repo.Url, repo.IsEnabled))); .Select(repo => new PluginRepository(repo.Url, repo.IsEnabled)));
this.Repos = repos; this.Repos = repos;
await this.ReloadPluginMastersAsync(notify); await this.ReloadPluginMastersAsync(notify);
@ -251,8 +251,6 @@ internal partial class PluginManager : IDisposable
return; return;
} }
var configuration = Service<DalamudConfiguration>.Get();
var pluginDefs = new List<PluginDef>(); var pluginDefs = new List<PluginDef>();
var devPluginDefs = new List<PluginDef>(); var devPluginDefs = new List<PluginDef>();
@ -282,7 +280,7 @@ internal partial class PluginManager : IDisposable
// devPlugins are more freeform. Look for any dll and hope to get lucky. // devPlugins are more freeform. Look for any dll and hope to get lucky.
var devDllFiles = this.devPluginDirectory.GetFiles("*.dll", SearchOption.AllDirectories).ToList(); 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) if (!setting.IsEnabled)
continue; continue;
@ -420,15 +418,13 @@ internal partial class PluginManager : IDisposable
return; return;
} }
var configuration = Service<DalamudConfiguration>.Get();
if (!this.devPluginDirectory.Exists) if (!this.devPluginDirectory.Exists)
this.devPluginDirectory.Create(); this.devPluginDirectory.Create();
// devPlugins are more freeform. Look for any dll and hope to get lucky. // devPlugins are more freeform. Look for any dll and hope to get lucky.
var devDllFiles = this.devPluginDirectory.GetFiles("*.dll", SearchOption.AllDirectories).ToList(); 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) if (!setting.IsEnabled)
continue; continue;
@ -682,9 +678,6 @@ internal partial class PluginManager : IDisposable
/// </summary> /// </summary>
public void CleanupPlugins() public void CleanupPlugins()
{ {
var configuration = Service<DalamudConfiguration>.Get();
var startInfo = Service<DalamudStartInfo>.Get();
foreach (var pluginDir in this.pluginDirectory.GetDirectories()) foreach (var pluginDir in this.pluginDirectory.GetDirectories())
{ {
try try
@ -748,7 +741,7 @@ internal partial class PluginManager : IDisposable
continue; continue;
} }
if (manifest.ApplicableVersion < startInfo.GameVersion) if (manifest.ApplicableVersion <this.startInfo.GameVersion)
{ {
Log.Information($"Inapplicable version: cleaning up {versionDir.FullName}"); Log.Information($"Inapplicable version: cleaning up {versionDir.FullName}");
versionDir.Delete(true); versionDir.Delete(true);
@ -919,15 +912,12 @@ internal partial class PluginManager : IDisposable
/// <returns>If the manifest is eligible.</returns> /// <returns>If the manifest is eligible.</returns>
public bool IsManifestEligible(PluginManifest manifest) public bool IsManifestEligible(PluginManifest manifest)
{ {
var configuration = Service<DalamudConfiguration>.Get();
var startInfo = Service<DalamudStartInfo>.Get();
// Testing exclusive // Testing exclusive
if (manifest.IsTestingExclusive && !configuration.DoPluginTest) if (manifest.IsTestingExclusive && !configuration.DoPluginTest)
return false; return false;
// Applicable version // Applicable version
if (manifest.ApplicableVersion < startInfo.GameVersion) if (manifest.ApplicableVersion <this.startInfo.GameVersion)
return false; return false;
// API level // API level
@ -945,7 +935,6 @@ internal partial class PluginManager : IDisposable
/// <returns>A value indicating whether the plugin/manifest has been banned.</returns> /// <returns>A value indicating whether the plugin/manifest has been banned.</returns>
public bool IsManifestBanned(PluginManifest manifest) public bool IsManifestBanned(PluginManifest manifest)
{ {
var configuration = Service<DalamudConfiguration>.Get();
return !configuration.LoadBannedPlugins && this.bannedPlugins.Any(ban => (ban.Name == manifest.InternalName || ban.Name == Hash.GetStringSha256Hash(manifest.InternalName)) return !configuration.LoadBannedPlugins && this.bannedPlugins.Any(ban => (ban.Name == manifest.InternalName || ban.Name == Hash.GetStringSha256Hash(manifest.InternalName))
&& ban.AssemblyVersion >= manifest.AssemblyVersion); && ban.AssemblyVersion >= manifest.AssemblyVersion);
} }

View file

@ -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;
/// <summary>
/// Class responsible for loading plugins on startup.
/// </summary>
[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");
}
}
}

View file

@ -334,7 +334,7 @@ internal class LocalPlugin : IDisposable
this.DalamudInterface = new DalamudPluginInterface(this.pluginAssembly.GetName().Name!, this.DllFile, reason, this.IsDev); this.DalamudInterface = new DalamudPluginInterface(this.pluginAssembly.GetName().Name!, this.DllFile, reason, this.IsDev);
var ioc = Service<ServiceContainer>.Get(); var ioc = Service<ServiceContainer>.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) if (this.instance == null)
{ {
this.State = PluginState.LoadError; this.State = PluginState.LoadError;

View file

@ -5,14 +5,13 @@ namespace Dalamud.Plugin.Ipc.Internal
/// <summary> /// <summary>
/// This class facilitates inter-plugin communication. /// This class facilitates inter-plugin communication.
/// </summary> /// </summary>
[ServiceManager.EarlyLoadedService]
internal class CallGate internal class CallGate
{ {
private readonly Dictionary<string, CallGateChannel> gates = new(); private readonly Dictionary<string, CallGateChannel> gates = new();
/// <summary> [ServiceManager.ServiceConstructor]
/// Initializes a new instance of the <see cref="CallGate"/> class. private CallGate()
/// </summary>
internal CallGate()
{ {
} }

177
Dalamud/ServiceManager.cs Normal file
View file

@ -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
{
/// <summary>
/// Class to initialize Service&lt;T&gt;s.
/// </summary>
internal static class ServiceManager
{
/// <summary>
/// Static log facility for Service{T}, to avoid duplicate instances for different types.
/// </summary>
public static readonly ModuleLog Log = new("SVC");
private static readonly TaskCompletionSource BlockingServicesLoadedTaskCompletionSource = new();
/// <summary>
/// Gets task that gets completed when all blocking early loading services are done loading.
/// </summary>
public static Task BlockingResolved { get; } = BlockingServicesLoadedTaskCompletionSource.Task;
/// <summary>
/// Kicks off construction of services that can handle early loading.
/// </summary>
/// <returns>Task for initializing all services.</returns>
public static async Task InitializeEarlyLoadableServices()
{
Service<ServiceContainer>.Provide(new ServiceContainer());
var service = typeof(Service<>);
var blockingEarlyLoadingServices = new List<Task>();
var dependencyServicesMap = new Dictionary<Type, List<Type>>();
var getAsyncTaskMap = new Dictionary<Type, Task>();
foreach (var serviceType in Assembly.GetExecutingAssembly().GetTypes())
{
var attr = serviceType.GetCustomAttribute<Service>(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<Type>)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<Task>();
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;
}
}
/// <summary>
/// Indicates that this constructor will be called for early initialization.
/// </summary>
[AttributeUsage(AttributeTargets.Constructor)]
[MeansImplicitUse(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)]
public class ServiceConstructor : Attribute
{
}
/// <summary>
/// Indicates that the field is a service that should be loaded before constructing the class.
/// </summary>
[AttributeUsage(AttributeTargets.Field)]
public class ServiceDependency : Attribute
{
}
/// <summary>
/// Indicates that the class is a service.
/// </summary>
[AttributeUsage(AttributeTargets.Class)]
public class Service : Attribute
{
}
/// <summary>
/// Indicates that the class is a service, and will be instantiated automatically on startup.
/// </summary>
[AttributeUsage(AttributeTargets.Class)]
public class EarlyLoadedService : Service
{
}
/// <summary>
/// Indicates that the class is a service, and will be instantiated automatically on startup,
/// blocking game main thread until it completes.
/// </summary>
[AttributeUsage(AttributeTargets.Class)]
public class BlockingEarlyLoadedService : EarlyLoadedService
{
}
}
}

View file

@ -1,9 +1,13 @@
using System; using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection; using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using Dalamud.IoC; using Dalamud.IoC;
using Dalamud.IoC.Internal; using Dalamud.IoC.Internal;
using Dalamud.Logging.Internal; using Dalamud.Utility.Timing;
using JetBrains.Annotations;
namespace Dalamud 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. /// 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> where T : class internal static class Service<T>
{ {
private static readonly ModuleLog Log = new("SVC"); // ReSharper disable once StaticMemberInGenericType
private static readonly TaskCompletionSource<T> InstanceTcs = new();
private static T? instance; // ReSharper disable once StaticMemberInGenericType
private static bool startLoaderInvoked = false;
static Service() static Service()
{ {
var exposeToPlugins = typeof(T).GetCustomAttribute<PluginInterfaceAttribute>() != 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<ServiceContainer>.Get().RegisterSingleton(InstanceTcs.Task);
}
/// <summary>
/// Initializes the service.
/// </summary>
/// <returns>The object.</returns>
[UsedImplicitly]
public static Task<T> StartLoader()
{
if (startLoaderInvoked)
throw new InvalidOperationException("StartLoader has already been called.");
var attr = typeof(T).GetCustomAttribute<ServiceManager.Service>(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;
}
}
});
} }
/// <summary> /// <summary>
/// Sets the type in the service locator to the given object. /// Sets the type in the service locator to the given object.
/// </summary> /// </summary>
/// <param name="obj">Object to set.</param> /// <param name="obj">Object to set.</param>
/// <returns>The set object.</returns> public static void Provide(T obj)
public static T Set(T obj)
{ {
SetInstanceObject(obj); InstanceTcs!.SetResult(obj);
ServiceManager.Log.Debug("Service<{0}>: Provided", typeof(T).Name);
return instance!;
} }
/// <summary> /// <summary>
/// Sets the type in the service locator via the default parameterless constructor. /// Pull the instance out of the service locator, waiting if necessary.
/// </summary> /// </summary>
/// <returns>The set object.</returns> /// <returns>The object.</returns>
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!;
}
/// <summary>
/// Sets a type in the service locator via a constructor with the given parameter types.
/// </summary>
/// <param name="args">Constructor arguments.</param>
/// <returns>The set object.</returns>
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;
}
/// <summary>
/// Attempt to pull the instance out of the service locator.
/// </summary>
/// <returns>The object if registered.</returns>
/// <exception cref="InvalidOperationException">Thrown when the object instance is not present in the service locator.</exception>
public static T Get() 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;
} }
/// <summary>
/// Pull the instance out of the service locator, waiting if necessary.
/// </summary>
/// <returns>The object.</returns>
[UsedImplicitly]
public static Task<T> GetAsync() => InstanceTcs.Task;
/// <summary> /// <summary>
/// Attempt to pull the instance out of the service locator. /// Attempt to pull the instance out of the service locator.
/// </summary> /// </summary>
/// <returns>The object if registered, null otherwise.</returns> /// <returns>The object if registered, null otherwise.</returns>
public static T? GetNullable() public static T? GetNullable() => InstanceTcs.Task.IsCompleted ? InstanceTcs.Task.Result : default;
/// <summary>
/// Gets an enumerable containing Service&lt;T&gt;s that are required for this Service to initialize without blocking.
/// </summary>
/// <returns>List of dependency services.</returns>
public static List<Type> GetDependencyServices()
{ {
return instance; var res = new List<Type>();
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<ServiceManager.ServiceDependency>(true) != null));
return res
.Distinct()
.Select(x => typeof(Service<>).MakeGenericType(x))
.ToList();
} }
private static void SetInstanceObject(T instance) private static async Task<object?> GetServiceObjectConstructArgument(Type type)
{ {
Service<T>.instance = instance ?? throw new ArgumentNullException(nameof(instance), $"Service locator received a null for type {typeof(T).FullName}"); var task = (Task)typeof(Service<>)
.MakeGenericType(type)
var availableToPlugins = RegisterInIoCContainer(instance); .InvokeMember(
"GetAsync",
if (availableToPlugins) BindingFlags.InvokeMethod |
Log.Information($"Registered {typeof(T).FullName} into service locator and exposed to plugins"); BindingFlags.Static |
else BindingFlags.Public,
Log.Information($"Registered {typeof(T).FullName} into service locator privately"); 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<PluginInterfaceAttribute>(); const BindingFlags ctorBindingFlags =
if (attr == null) BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic |
{ BindingFlags.CreateInstance | BindingFlags.OptionalParamBinding;
return false; return typeof(T)
} .GetConstructors(ctorBindingFlags)
.Single(x => x.GetCustomAttributes(typeof(ServiceManager.ServiceConstructor), true).Any());
}
var ioc = Service<ServiceContainer>.GetNullable(); private static async Task<T> ConstructObject()
if (ioc == null) {
{ var ctor = GetServiceConstructor();
return false; var args = await Task.WhenAll(
} ctor.GetParameters().Select(x => GetServiceObjectConstructArgument(x.ParameterType)));
return (T)ctor.Invoke(args)!;
ioc.RegisterSingleton(instance);
return true;
} }
} }
} }

View file

@ -8,12 +8,12 @@ namespace Dalamud.Utility;
public static class ThreadSafety public static class ThreadSafety
{ {
[ThreadStatic] [ThreadStatic]
private static bool isMainThread; private static bool threadStaticIsMainThread;
/// <summary> /// <summary>
/// Gets a value indicating whether the current thread is the main thread. /// Gets a value indicating whether the current thread is the main thread.
/// </summary> /// </summary>
public static bool IsMainThread => isMainThread; public static bool IsMainThread => threadStaticIsMainThread;
/// <summary> /// <summary>
/// Throws an exception when the current thread is not the main thread. /// Throws an exception when the current thread is not the main thread.
@ -21,7 +21,7 @@ public static class ThreadSafety
/// <exception cref="InvalidOperationException">Thrown when the current thread is not the main thread.</exception> /// <exception cref="InvalidOperationException">Thrown when the current thread is not the main thread.</exception>
public static void AssertMainThread() public static void AssertMainThread()
{ {
if (!isMainThread) if (!threadStaticIsMainThread)
{ {
throw new InvalidOperationException("Not on main thread!"); throw new InvalidOperationException("Not on main thread!");
} }
@ -33,7 +33,7 @@ public static class ThreadSafety
/// <exception cref="InvalidOperationException">Thrown when the current thread is the main thread.</exception> /// <exception cref="InvalidOperationException">Thrown when the current thread is the main thread.</exception>
public static void AssertNotMainThread() public static void AssertNotMainThread()
{ {
if (isMainThread) if (threadStaticIsMainThread)
{ {
throw new InvalidOperationException("On main thread!"); throw new InvalidOperationException("On main thread!");
} }
@ -44,6 +44,6 @@ public static class ThreadSafety
/// </summary> /// </summary>
internal static void MarkMainThread() internal static void MarkMainThread()
{ {
isMainThread = true; threadStaticIsMainThread = true;
} }
} }