mirror of
https://github.com/goatcorp/Dalamud.git
synced 2025-12-12 10:17:22 +01:00
Load services asynchronously whenever possible (#893)
This commit is contained in:
parent
fba8c7163c
commit
8e7f370ddd
66 changed files with 959 additions and 899 deletions
|
|
@ -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));
|
||||
else if (item == "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));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ struct DalamudStartInfo {
|
|||
None = 0,
|
||||
BeforeInitialize = 1 << 0,
|
||||
BeforeDalamudEntrypoint = 1 << 1,
|
||||
BeforeDalamudConstruct = 1 << 2,
|
||||
};
|
||||
friend void from_json(const nlohmann::json&, WaitMessageboxFlags&);
|
||||
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@
|
|||
HMODULE g_hModule;
|
||||
HINSTANCE g_hGameInstance = GetModuleHandleW(nullptr);
|
||||
|
||||
DllExport DWORD WINAPI Initialize(LPVOID lpParam, HANDLE hMainThreadContinue) {
|
||||
DWORD WINAPI InitializeImpl(LPVOID lpParam, HANDLE hMainThreadContinue) {
|
||||
g_startInfo.from_envvars();
|
||||
|
||||
std::string jsonParseError;
|
||||
|
|
@ -149,11 +149,6 @@ DllExport DWORD WINAPI Initialize(LPVOID lpParam, HANDLE hMainThreadContinue) {
|
|||
if (static_cast<int>(g_startInfo.BootWaitMessageBox) & static_cast<int>(DalamudStartInfo::WaitMessageboxFlags::BeforeDalamudEntrypoint))
|
||||
MessageBoxW(nullptr, L"Press OK to continue (BeforeDalamudEntrypoint)", L"Dalamud Boot", MB_OK);
|
||||
|
||||
if (hMainThreadContinue) {
|
||||
// Let the game initialize.
|
||||
SetEvent(hMainThreadContinue);
|
||||
}
|
||||
|
||||
// We don't need to do this anymore, Dalamud now loads without needing the window to be there. Speed!
|
||||
// utils::wait_for_game_window();
|
||||
|
||||
|
|
@ -164,6 +159,10 @@ DllExport DWORD WINAPI Initialize(LPVOID lpParam, HANDLE hMainThreadContinue) {
|
|||
return 0;
|
||||
}
|
||||
|
||||
DllExport DWORD WINAPI Initialize(LPVOID lpParam) {
|
||||
return InitializeImpl(lpParam, CreateEvent(nullptr, TRUE, FALSE, nullptr));
|
||||
}
|
||||
|
||||
BOOL APIENTRY DllMain(const HMODULE hModule, const DWORD dwReason, LPVOID lpReserved) {
|
||||
DisableThreadLibraryCalls(hModule);
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
#include "logging.h"
|
||||
|
||||
DllExport DWORD WINAPI Initialize(LPVOID lpParam, HANDLE hMainThreadContinue);
|
||||
DWORD WINAPI InitializeImpl(LPVOID lpParam, HANDLE hMainThreadContinue);
|
||||
|
||||
struct RewrittenEntryPointParameters {
|
||||
void* pAllocation;
|
||||
|
|
@ -368,7 +368,7 @@ DllExport void WINAPI RewrittenEntryPoint(RewrittenEntryPointParameters& params)
|
|||
loadInfo = params.pLoadInfo;
|
||||
}
|
||||
|
||||
Initialize(&loadInfo[0], params.hMainThreadContinue);
|
||||
InitializeImpl(&loadInfo[0], params.hMainThreadContinue);
|
||||
return 0;
|
||||
} catch (const std::exception& e) {
|
||||
MessageBoxA(nullptr, std::format("Failed to load Dalamud.\n\nError: {}", e.what()).c_str(), "Dalamud.Boot", MB_OK | MB_ICONERROR);
|
||||
|
|
@ -380,6 +380,5 @@ DllExport void WINAPI RewrittenEntryPoint(RewrittenEntryPointParameters& params)
|
|||
|
||||
CloseHandle(params.hMainThread);
|
||||
WaitForSingleObject(params.hMainThreadContinue, INFINITE);
|
||||
CloseHandle(params.hMainThreadContinue);
|
||||
VirtualFree(params.pAllocation, 0, MEM_RELEASE);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -61,6 +61,7 @@
|
|||
|
||||
<ItemGroup>
|
||||
<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="PeNet" Version="2.6.4" />
|
||||
<PackageReference Include="Reloaded.Memory" Version="4.1.1" />
|
||||
|
|
|
|||
|
|
@ -83,8 +83,12 @@ namespace Dalamud.Injector
|
|||
}
|
||||
|
||||
startInfo = ExtractAndInitializeStartInfoFromArguments(startInfo, args);
|
||||
args.Remove("--console"); // Remove "console" flag, already handled
|
||||
args.Remove("--etw"); // Remove "etw" flag, already handled
|
||||
// Remove already handled arguments
|
||||
args.Remove("--console");
|
||||
args.Remove("--msgbox1");
|
||||
args.Remove("--msgbox2");
|
||||
args.Remove("--msgbox3");
|
||||
args.Remove("--etw");
|
||||
|
||||
var mainCommand = args[1].ToLowerInvariant();
|
||||
if (mainCommand.Length > 0 && mainCommand.Length <= 6 && "inject"[..mainCommand.Length] == mainCommand)
|
||||
|
|
@ -311,7 +315,9 @@ namespace Dalamud.Injector
|
|||
startInfo.BootLogPath = GetLogPath("dalamud.boot");
|
||||
startInfo.BootEnabledGameFixes = new List<string> { "prevent_devicechange_crashes", "disable_game_openprocess_access_check", "redirect_openprocess" };
|
||||
startInfo.BootDotnetOpenProcessHookMode = 0;
|
||||
// startInfo.BootWaitMessageBox = 2;
|
||||
startInfo.BootWaitMessageBox |= args.Contains("--msgbox1") ? 1 : 0;
|
||||
startInfo.BootWaitMessageBox |= args.Contains("--msgbox2") ? 2 : 0;
|
||||
startInfo.BootWaitMessageBox |= args.Contains("--msgbox3") ? 4 : 0;
|
||||
// startInfo.BootUnhookDlls = new List<string>() { "kernel32.dll", "ntdll.dll", "user32.dll" };
|
||||
|
||||
return startInfo;
|
||||
|
|
@ -338,6 +344,7 @@ namespace Dalamud.Injector
|
|||
Console.WriteLine("{0} [-m entrypoint|inject] [--mode=entrypoint|inject]", exeSpaces);
|
||||
Console.WriteLine("{0} [--handle-owner=inherited-handle-value]", exeSpaces);
|
||||
Console.WriteLine("{0} [--without-dalamud] [--no-fix-acl]", exeSpaces);
|
||||
Console.WriteLine("{0} [--no-wait]", exeSpaces);
|
||||
Console.WriteLine("{0} [-- game_arg1=value1 game_arg2=value2 ...]", exeSpaces);
|
||||
}
|
||||
|
||||
|
|
@ -349,6 +356,7 @@ namespace Dalamud.Injector
|
|||
Console.WriteLine("Verbose logging:\t[-v]");
|
||||
Console.WriteLine("Show Console:\t[--console]");
|
||||
Console.WriteLine("Enable ETW:\t[--etw]");
|
||||
Console.WriteLine("Show messagebox:\t[--msgbox1], [--msgbox2]");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -464,6 +472,7 @@ namespace Dalamud.Injector
|
|||
var handleOwner = IntPtr.Zero;
|
||||
var withoutDalamud = false;
|
||||
var noFixAcl = false;
|
||||
var waitForGameWindow = true;
|
||||
|
||||
var parsingGameArgument = false;
|
||||
for (var i = 2; i < args.Count; i++)
|
||||
|
|
@ -480,6 +489,8 @@ namespace Dalamud.Injector
|
|||
useFakeArguments = true;
|
||||
else if (args[i] == "--without-dalamud")
|
||||
withoutDalamud = true;
|
||||
else if (args[i] == "--no-wait")
|
||||
waitForGameWindow = false;
|
||||
else if (args[i] == "--no-fix-acl" || args[i] == "--no-acl-fix")
|
||||
noFixAcl = true;
|
||||
else if (args[i] == "-g")
|
||||
|
|
@ -596,7 +607,7 @@ namespace Dalamud.Injector
|
|||
|
||||
Log.Verbose("RewriteRemoteEntryPointW called!");
|
||||
}
|
||||
});
|
||||
}, waitForGameWindow);
|
||||
|
||||
Log.Verbose("Game process started with PID {0}", process.Id);
|
||||
|
||||
|
|
|
|||
|
|
@ -235,6 +235,11 @@ namespace Dalamud.Configuration.Internal
|
|||
/// </summary>
|
||||
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>
|
||||
/// Gets or sets the kind of beta to download when <see cref="DalamudBetaKey"/> matches the server value.
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ using System.IO;
|
|||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading;
|
||||
|
||||
using System.Threading.Tasks;
|
||||
using Dalamud.Configuration.Internal;
|
||||
using Dalamud.Data;
|
||||
using Dalamud.Game;
|
||||
|
|
@ -49,7 +49,6 @@ namespace Dalamud
|
|||
|
||||
private readonly ManualResetEvent unloadSignal;
|
||||
private readonly ManualResetEvent finishUnloadSignal;
|
||||
private readonly IntPtr mainThreadContinueEvent;
|
||||
private MonoMod.RuntimeDetour.Hook processMonoHook;
|
||||
private bool hasDisposedPlugins = false;
|
||||
|
||||
|
|
@ -65,10 +64,6 @@ namespace Dalamud
|
|||
/// <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)
|
||||
{
|
||||
Service<Dalamud>.Set(this);
|
||||
Service<DalamudStartInfo>.Set(info);
|
||||
Service<DalamudConfiguration>.Set(configuration);
|
||||
|
||||
this.LogLevelSwitch = loggingLevelSwitch;
|
||||
|
||||
this.unloadSignal = new ManualResetEvent(false);
|
||||
|
|
@ -77,7 +72,55 @@ namespace Dalamud
|
|||
this.finishUnloadSignal = finishSignal;
|
||||
this.finishUnloadSignal.Reset();
|
||||
|
||||
this.mainThreadContinueEvent = mainThreadContinueEvent;
|
||||
SerilogEventSink.Instance.LogLine += SerilogOnLogLine;
|
||||
|
||||
Service<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>
|
||||
|
|
@ -88,262 +131,7 @@ namespace Dalamud
|
|||
/// <summary>
|
||||
/// Gets location of stored assets.
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
internal DirectoryInfo AssetDirectory => new(Service<DalamudStartInfo>.Get().AssetDirectory!);
|
||||
|
||||
/// <summary>
|
||||
/// Queue an unload of Dalamud when it gets the chance.
|
||||
|
|
|
|||
|
|
@ -86,12 +86,12 @@ namespace Dalamud
|
|||
/// <summary>
|
||||
/// Gets or sets a value that specifies how much to wait before a new Dalamud session.
|
||||
/// </summary>
|
||||
public int DelayInitializeMs { get; set; } = 0;
|
||||
public int DelayInitializeMs { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the path the boot log file is supposed to be written to.
|
||||
/// </summary>
|
||||
public string BootLogPath { get; set; }
|
||||
public string? BootLogPath { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether a Boot console should be shown.
|
||||
|
|
|
|||
|
|
@ -26,22 +26,91 @@ namespace Dalamud.Data
|
|||
/// </summary>
|
||||
[PluginInterface]
|
||||
[InterfaceVersion("1.0")]
|
||||
[ServiceManager.BlockingEarlyLoadedService]
|
||||
public sealed class DataManager : IDisposable
|
||||
{
|
||||
private const string IconFileFormat = "ui/icon/{0:D3}000/{1}{2:D6}.tex";
|
||||
|
||||
private Thread luminaResourceThread;
|
||||
private CancellationTokenSource luminaCancellationTokenSource;
|
||||
private readonly Thread luminaResourceThread;
|
||||
private readonly CancellationTokenSource luminaCancellationTokenSource;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="DataManager"/> class.
|
||||
/// </summary>
|
||||
internal DataManager()
|
||||
[ServiceManager.ServiceConstructor]
|
||||
private DataManager(DalamudStartInfo dalamudStartInfo, Dalamud dalamud)
|
||||
{
|
||||
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.
|
||||
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>
|
||||
|
|
@ -68,7 +137,7 @@ namespace Dalamud.Data
|
|||
/// <summary>
|
||||
/// Gets an <see cref="ExcelModule"/> object which gives access to any of the game's sheet data.
|
||||
/// </summary>
|
||||
public ExcelModule Excel => this.GameData?.Excel;
|
||||
public ExcelModule Excel => this.GameData.Excel;
|
||||
|
||||
/// <summary>
|
||||
/// 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="iconId">The icon ID.</param>
|
||||
/// <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;
|
||||
if (type.Length > 0 && !type.EndsWith("/"))
|
||||
|
|
@ -276,81 +345,5 @@ namespace Dalamud.Data
|
|||
{
|
||||
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.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -47,8 +47,11 @@ namespace Dalamud
|
|||
/// <param name="mainThreadContinueEvent">Event used to signal the main thread to continue.</param>
|
||||
public static void Initialize(IntPtr infoPtr, IntPtr mainThreadContinueEvent)
|
||||
{
|
||||
var infoStr = Marshal.PtrToStringUTF8(infoPtr);
|
||||
var info = JsonConvert.DeserializeObject<DalamudStartInfo>(infoStr);
|
||||
var infoStr = Marshal.PtrToStringUTF8(infoPtr)!;
|
||||
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();
|
||||
}
|
||||
|
|
@ -148,9 +151,6 @@ namespace Dalamud
|
|||
var dalamud = new Dalamud(info, levelSwitch, finishSignal, configuration, mainThreadContinueEvent);
|
||||
Log.Information("This is Dalamud - Core: {GitHash}, CS: {CsGitHash}", Util.GetGitHash(), Util.GetGitHashClientStructs());
|
||||
|
||||
// Run session
|
||||
dalamud.LoadTier1();
|
||||
|
||||
dalamud.WaitForUnload();
|
||||
|
||||
dalamud.Dispose();
|
||||
|
|
|
|||
|
|
@ -20,15 +20,6 @@ namespace Dalamud.Game
|
|||
/// </summary>
|
||||
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>
|
||||
/// Setup the resolver, calling the appopriate method based on the process architecture.
|
||||
/// </summary>
|
||||
|
|
@ -55,11 +46,13 @@ namespace Dalamud.Game
|
|||
this.SetupInternal(scanner);
|
||||
|
||||
var className = this.GetType().Name;
|
||||
DebugScannedValues[className] = new List<(string, IntPtr)>();
|
||||
var list = new List<(string, IntPtr)>();
|
||||
lock (DebugScannedValues)
|
||||
DebugScannedValues[className] = list;
|
||||
|
||||
foreach (var property in this.GetType().GetProperties().Where(x => x.PropertyType == typeof(IntPtr)))
|
||||
{
|
||||
DebugScannedValues[className].Add((property.Name, (IntPtr)property.GetValue(this)));
|
||||
list.Add((property.Name, (IntPtr)property.GetValue(this)));
|
||||
}
|
||||
|
||||
this.IsResolved = true;
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ namespace Dalamud.Game
|
|||
/// </summary>
|
||||
[PluginInterface]
|
||||
[InterfaceVersion("1.0")]
|
||||
[ServiceManager.BlockingEarlyLoadedService]
|
||||
public class ChatHandlers
|
||||
{
|
||||
// private static readonly Dictionary<string, string> UnicodeToDiscordEmojiDict = new()
|
||||
|
|
@ -109,13 +110,9 @@ namespace Dalamud.Game
|
|||
private bool hasSeenLoadingMsg;
|
||||
private bool hasAutoUpdatedPlugins;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ChatHandlers"/> class.
|
||||
/// </summary>
|
||||
internal ChatHandlers()
|
||||
[ServiceManager.ServiceConstructor]
|
||||
private ChatHandlers(ChatGui chatGui)
|
||||
{
|
||||
var chatGui = Service<ChatGui>.Get();
|
||||
|
||||
chatGui.CheckMessageHandled += this.OnCheckMessageHandled;
|
||||
chatGui.ChatMessage += this.OnChatMessage;
|
||||
|
||||
|
|
|
|||
|
|
@ -14,18 +14,18 @@ namespace Dalamud.Game.ClientState.Aetherytes
|
|||
/// </summary>
|
||||
[PluginInterface]
|
||||
[InterfaceVersion("1.0")]
|
||||
[ServiceManager.BlockingEarlyLoadedService]
|
||||
public sealed partial class AetheryteList
|
||||
{
|
||||
[ServiceManager.ServiceDependency]
|
||||
private readonly ClientState clientState = Service<ClientState>.Get();
|
||||
private readonly ClientStateAddressResolver address;
|
||||
private readonly UpdateAetheryteListDelegate updateAetheryteListFunc;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AetheryteList"/> class.
|
||||
/// </summary>
|
||||
/// <param name="addressResolver">Client state address resolver.</param>
|
||||
internal AetheryteList(ClientStateAddressResolver addressResolver)
|
||||
[ServiceManager.ServiceConstructor]
|
||||
private AetheryteList()
|
||||
{
|
||||
this.address = addressResolver;
|
||||
this.address = this.clientState.AddressResolver;
|
||||
this.updateAetheryteListFunc = Marshal.GetDelegateForFunctionPointer<UpdateAetheryteListDelegate>(this.address.UpdateAetheryteList);
|
||||
|
||||
Log.Verbose($"Teleport address 0x{this.address.Telepo.ToInt64():X}");
|
||||
|
|
@ -40,9 +40,7 @@ namespace Dalamud.Game.ClientState.Aetherytes
|
|||
{
|
||||
get
|
||||
{
|
||||
var clientState = Service<ClientState>.Get();
|
||||
|
||||
if (clientState.LocalPlayer == null)
|
||||
if (this.clientState.LocalPlayer == null)
|
||||
return 0;
|
||||
|
||||
this.Update();
|
||||
|
|
@ -70,9 +68,7 @@ namespace Dalamud.Game.ClientState.Aetherytes
|
|||
return null;
|
||||
}
|
||||
|
||||
var clientState = Service<ClientState>.Get();
|
||||
|
||||
if (clientState.LocalPlayer == null)
|
||||
if (this.clientState.LocalPlayer == null)
|
||||
return null;
|
||||
|
||||
return new AetheryteEntry(TelepoStruct->TeleportList.Get((ulong)index));
|
||||
|
|
@ -81,10 +77,8 @@ namespace Dalamud.Game.ClientState.Aetherytes
|
|||
|
||||
private void Update()
|
||||
{
|
||||
var clientState = Service<ClientState>.Get();
|
||||
|
||||
// this is very very important as otherwise it crashes
|
||||
if (clientState.LocalPlayer == null)
|
||||
if (this.clientState.LocalPlayer == null)
|
||||
return;
|
||||
|
||||
this.updateAetheryteListFunc(this.address.Telepo, 0);
|
||||
|
|
|
|||
|
|
@ -15,19 +15,17 @@ namespace Dalamud.Game.ClientState.Buddy
|
|||
/// </summary>
|
||||
[PluginInterface]
|
||||
[InterfaceVersion("1.0")]
|
||||
[ServiceManager.BlockingEarlyLoadedService]
|
||||
public sealed partial class BuddyList
|
||||
{
|
||||
private const uint InvalidObjectID = 0xE0000000;
|
||||
|
||||
private readonly ClientStateAddressResolver address;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="BuddyList"/> class.
|
||||
/// </summary>
|
||||
/// <param name="addressResolver">Client state address resolver.</param>
|
||||
internal BuddyList(ClientStateAddressResolver addressResolver)
|
||||
[ServiceManager.ServiceConstructor]
|
||||
private BuddyList(ClientState clientState)
|
||||
{
|
||||
this.address = addressResolver;
|
||||
this.address = clientState.AddressResolver;
|
||||
|
||||
Log.Verbose($"Buddy list address 0x{this.address.BuddyList.ToInt64():X}");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ namespace Dalamud.Game.ClientState
|
|||
/// </summary>
|
||||
[PluginInterface]
|
||||
[InterfaceVersion("1.0")]
|
||||
[ServiceManager.BlockingEarlyLoadedService]
|
||||
public sealed class ClientState : IDisposable
|
||||
{
|
||||
private readonly ClientStateAddressResolver address;
|
||||
|
|
@ -36,46 +37,26 @@ namespace Dalamud.Game.ClientState
|
|||
private bool lastFramePvP = false;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ClientState"/> class.
|
||||
/// Set up client state access.
|
||||
/// Gets client state address resolver.
|
||||
/// </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.Setup();
|
||||
this.address.Setup(sigScanner);
|
||||
|
||||
Log.Verbose("===== C L I E N T S T A T E =====");
|
||||
|
||||
this.ClientLanguage = Service<DalamudStartInfo>.Get().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);
|
||||
this.ClientLanguage = startInfo.Language;
|
||||
|
||||
Log.Verbose($"SetupTerritoryType address 0x{this.address.SetupTerritoryType.ToInt64():X}");
|
||||
|
||||
this.setupTerritoryTypeHook = new Hook<SetupTerritoryTypeDelegate>(this.address.SetupTerritoryType, this.SetupTerritoryTypeDetour);
|
||||
|
||||
var framework = Service<Framework>.Get();
|
||||
framework.Update += this.FrameworkOnOnUpdateEvent;
|
||||
|
||||
var networkHandlers = Service<NetworkHandlers>.Get();
|
||||
networkHandlers.CfPop += this.NetworkHandlersOnCfPop;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ namespace Dalamud.Game.ClientState.Conditions
|
|||
/// </summary>
|
||||
[PluginInterface]
|
||||
[InterfaceVersion("1.0")]
|
||||
[ServiceManager.BlockingEarlyLoadedService]
|
||||
public sealed partial class Condition
|
||||
{
|
||||
/// <summary>
|
||||
|
|
@ -20,12 +21,10 @@ namespace Dalamud.Game.ClientState.Conditions
|
|||
|
||||
private readonly bool[] cache = new bool[MaxConditionEntries];
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Condition"/> class.
|
||||
/// </summary>
|
||||
/// <param name="resolver">The ClientStateAddressResolver instance.</param>
|
||||
internal Condition(ClientStateAddressResolver resolver)
|
||||
[ServiceManager.ServiceConstructor]
|
||||
private Condition(ClientState clientState)
|
||||
{
|
||||
var resolver = clientState.AddressResolver;
|
||||
this.Address = resolver.ConditionFlags;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -13,17 +13,15 @@ namespace Dalamud.Game.ClientState.Fates
|
|||
/// </summary>
|
||||
[PluginInterface]
|
||||
[InterfaceVersion("1.0")]
|
||||
[ServiceManager.BlockingEarlyLoadedService]
|
||||
public sealed partial class FateTable
|
||||
{
|
||||
private readonly ClientStateAddressResolver address;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="FateTable"/> class.
|
||||
/// </summary>
|
||||
/// <param name="addressResolver">Client state address resolver.</param>
|
||||
internal FateTable(ClientStateAddressResolver addressResolver)
|
||||
[ServiceManager.ServiceConstructor]
|
||||
private FateTable(ClientState clientState)
|
||||
{
|
||||
this.address = addressResolver;
|
||||
this.address = clientState.AddressResolver;
|
||||
|
||||
Log.Verbose($"Fate table address 0x{this.address.FateTablePtr.ToInt64():X}");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ namespace Dalamud.Game.ClientState.GamePad
|
|||
/// </summary>
|
||||
[PluginInterface]
|
||||
[InterfaceVersion("1.0.0")]
|
||||
[ServiceManager.BlockingEarlyLoadedService]
|
||||
public unsafe class GamepadState : IDisposable
|
||||
{
|
||||
private readonly Hook<ControllerPoll> gamepadPoll;
|
||||
|
|
@ -26,12 +27,10 @@ namespace Dalamud.Game.ClientState.GamePad
|
|||
private int rightStickX;
|
||||
private int rightStickY;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="GamepadState" /> class.
|
||||
/// </summary>
|
||||
/// <param name="resolver">Resolver knowing the pointer to the GamepadPoll function.</param>
|
||||
public GamepadState(ClientStateAddressResolver resolver)
|
||||
[ServiceManager.ServiceConstructor]
|
||||
private GamepadState(ClientState clientState)
|
||||
{
|
||||
var resolver = clientState.AddressResolver;
|
||||
Log.Verbose($"GamepadPoll address 0x{resolver.GamepadPoll.ToInt64():X}");
|
||||
this.gamepadPoll = new Hook<ControllerPoll>(resolver.GamepadPoll, this.GamepadPollDetour);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,17 +14,15 @@ namespace Dalamud.Game.ClientState.JobGauge
|
|||
/// </summary>
|
||||
[PluginInterface]
|
||||
[InterfaceVersion("1.0")]
|
||||
[ServiceManager.BlockingEarlyLoadedService]
|
||||
public class JobGauges
|
||||
{
|
||||
private Dictionary<Type, JobGaugeBase> cache = new();
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="JobGauges"/> class.
|
||||
/// </summary>
|
||||
/// <param name="addressResolver">Address resolver with the JobGauge memory location(s).</param>
|
||||
public JobGauges(ClientStateAddressResolver addressResolver)
|
||||
[ServiceManager.ServiceConstructor]
|
||||
private JobGauges(ClientState clientState)
|
||||
{
|
||||
this.Address = addressResolver.JobGaugeData;
|
||||
this.Address = clientState.AddressResolver.JobGaugeData;
|
||||
|
||||
Log.Verbose($"JobGaugeData address 0x{this.Address.ToInt64():X}");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ namespace Dalamud.Game.ClientState.Keys
|
|||
/// </remarks>
|
||||
[PluginInterface]
|
||||
[InterfaceVersion("1.0")]
|
||||
[ServiceManager.BlockingEarlyLoadedService]
|
||||
public class KeyState
|
||||
{
|
||||
// The array is accessed in a way that this limit doesn't appear to exist
|
||||
|
|
@ -32,14 +33,11 @@ namespace Dalamud.Game.ClientState.Keys
|
|||
private readonly IntPtr indexBase;
|
||||
private VirtualKey[] validVirtualKeyCache = null;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="KeyState"/> class.
|
||||
/// </summary>
|
||||
/// <param name="addressResolver">The ClientStateAddressResolver instance.</param>
|
||||
public KeyState(ClientStateAddressResolver addressResolver)
|
||||
[ServiceManager.ServiceConstructor]
|
||||
private KeyState(SigScanner sigScanner, ClientState clientState)
|
||||
{
|
||||
var moduleBaseAddress = Service<SigScanner>.Get().Module.BaseAddress;
|
||||
|
||||
var moduleBaseAddress = sigScanner.Module.BaseAddress;
|
||||
var addressResolver = clientState.AddressResolver;
|
||||
this.bufferBase = moduleBaseAddress + Marshal.ReadInt32(addressResolver.KeyboardState);
|
||||
this.indexBase = moduleBaseAddress + Marshal.ReadInt32(addressResolver.KeyboardStateIndexArray);
|
||||
|
||||
|
|
|
|||
|
|
@ -16,19 +16,17 @@ namespace Dalamud.Game.ClientState.Objects
|
|||
/// </summary>
|
||||
[PluginInterface]
|
||||
[InterfaceVersion("1.0")]
|
||||
[ServiceManager.BlockingEarlyLoadedService]
|
||||
public sealed partial class ObjectTable
|
||||
{
|
||||
private const int ObjectTableLength = 424;
|
||||
|
||||
private readonly ClientStateAddressResolver address;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ObjectTable"/> class.
|
||||
/// </summary>
|
||||
/// <param name="addressResolver">Client state address resolver.</param>
|
||||
internal ObjectTable(ClientStateAddressResolver addressResolver)
|
||||
[ServiceManager.ServiceConstructor]
|
||||
private ObjectTable(ClientState clientState)
|
||||
{
|
||||
this.address = addressResolver;
|
||||
this.address = clientState.AddressResolver;
|
||||
|
||||
Log.Verbose($"Object table address 0x{this.address.ObjectTable.ToInt64():X}");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,17 +11,15 @@ namespace Dalamud.Game.ClientState.Objects
|
|||
/// </summary>
|
||||
[PluginInterface]
|
||||
[InterfaceVersion("1.0")]
|
||||
[ServiceManager.BlockingEarlyLoadedService]
|
||||
public sealed unsafe class TargetManager
|
||||
{
|
||||
private readonly ClientStateAddressResolver address;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="TargetManager"/> class.
|
||||
/// </summary>
|
||||
/// <param name="addressResolver">The ClientStateAddressResolver instance.</param>
|
||||
internal TargetManager(ClientStateAddressResolver addressResolver)
|
||||
[ServiceManager.ServiceConstructor]
|
||||
private TargetManager(ClientState clientState)
|
||||
{
|
||||
this.address = addressResolver;
|
||||
this.address = clientState.AddressResolver;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ namespace Dalamud.Game.ClientState.Party
|
|||
/// </summary>
|
||||
[PluginInterface]
|
||||
[InterfaceVersion("1.0")]
|
||||
[ServiceManager.BlockingEarlyLoadedService]
|
||||
public sealed unsafe partial class PartyList
|
||||
{
|
||||
private const int GroupLength = 8;
|
||||
|
|
@ -21,13 +22,10 @@ namespace Dalamud.Game.ClientState.Party
|
|||
|
||||
private readonly ClientStateAddressResolver address;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="PartyList"/> class.
|
||||
/// </summary>
|
||||
/// <param name="addressResolver">Client state address resolver.</param>
|
||||
internal PartyList(ClientStateAddressResolver addressResolver)
|
||||
[ServiceManager.ServiceConstructor]
|
||||
private PartyList(ClientState clientState)
|
||||
{
|
||||
this.address = addressResolver;
|
||||
this.address = clientState.AddressResolver;
|
||||
|
||||
Log.Verbose($"Group manager address 0x{this.address.GroupManager.ToInt64():X}");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ namespace Dalamud.Game.Command
|
|||
/// </summary>
|
||||
[PluginInterface]
|
||||
[InterfaceVersion("1.0")]
|
||||
[ServiceManager.BlockingEarlyLoadedService]
|
||||
public sealed class CommandManager
|
||||
{
|
||||
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 currentLangCommandRegex;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="CommandManager"/> class.
|
||||
/// </summary>
|
||||
internal CommandManager()
|
||||
[ServiceManager.ServiceConstructor]
|
||||
private CommandManager(DalamudStartInfo startInfo)
|
||||
{
|
||||
var startInfo = Service<DalamudStartInfo>.Get();
|
||||
|
||||
this.currentLangCommandRegex = startInfo.Language switch
|
||||
{
|
||||
ClientLanguage.Japanese => this.commandRegexJp,
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ namespace Dalamud.Game
|
|||
/// </summary>
|
||||
[PluginInterface]
|
||||
[InterfaceVersion("1.0")]
|
||||
[ServiceManager.BlockingEarlyLoadedService]
|
||||
public sealed class Framework : IDisposable
|
||||
{
|
||||
private static Stopwatch statsStopwatch = new();
|
||||
|
|
@ -31,27 +32,28 @@ namespace Dalamud.Game
|
|||
private readonly List<RunOnNextTickTaskBase> runOnNextTickTaskList = new();
|
||||
private readonly Stopwatch updateStopwatch = new();
|
||||
|
||||
private bool tier2Initialized = false;
|
||||
private bool tier3Initialized = false;
|
||||
private bool tierInitError = false;
|
||||
|
||||
private Hook<OnUpdateDetour> updateHook;
|
||||
private Hook<OnDestroyDetour> freeHook;
|
||||
private Hook<OnRealDestroyDelegate> destroyHook;
|
||||
|
||||
private Thread? frameworkUpdateThread;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Framework"/> class.
|
||||
/// </summary>
|
||||
internal Framework()
|
||||
[ServiceManager.ServiceConstructor]
|
||||
private Framework(GameGui gameGui, GameNetwork gameNetwork, SigScanner sigScanner)
|
||||
{
|
||||
this.Address = new FrameworkAddressResolver();
|
||||
this.Address.Setup();
|
||||
this.Address.Setup(sigScanner);
|
||||
|
||||
this.updateHook = new Hook<OnUpdateDetour>(this.Address.TickAddress, this.HandleFrameworkUpdate);
|
||||
this.freeHook = new Hook<OnDestroyDetour>(this.Address.FreeAddress, this.HandleFrameworkFree);
|
||||
this.destroyHook = new Hook<OnRealDestroyDelegate>(this.Address.DestroyAddress, this.HandleFrameworkDestroy);
|
||||
|
||||
gameGui.Enable();
|
||||
gameNetwork.Enable();
|
||||
|
||||
this.updateHook.Enable();
|
||||
this.freeHook.Enable();
|
||||
this.destroyHook.Enable();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -123,20 +125,6 @@ namespace Dalamud.Game
|
|||
/// </summary>
|
||||
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>
|
||||
/// 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>
|
||||
|
|
@ -238,39 +226,21 @@ namespace Dalamud.Game
|
|||
|
||||
private bool HandleFrameworkUpdate(IntPtr framework)
|
||||
{
|
||||
// If any of the tier loads failed, just go to the original code.
|
||||
if (this.tierInitError)
|
||||
goto original;
|
||||
|
||||
this.frameworkUpdateThread ??= Thread.CurrentThread;
|
||||
|
||||
var dalamud = Service<Dalamud>.Get();
|
||||
|
||||
// If this is the first time we are running this loop, we need to init Dalamud subsystems synchronously
|
||||
if (!this.tier2Initialized)
|
||||
{
|
||||
this.tier2Initialized = dalamud.LoadTier2();
|
||||
if (!this.tier2Initialized)
|
||||
this.tierInitError = true;
|
||||
|
||||
goto original;
|
||||
}
|
||||
|
||||
// Plugins expect the interface to be available and ready, so we need to wait with plugins until we have init'd ImGui
|
||||
if (!this.tier3Initialized && Service<InterfaceManager>.GetNullable()?.IsReady == true)
|
||||
{
|
||||
this.tier3Initialized = dalamud.LoadTier3();
|
||||
if (!this.tier3Initialized)
|
||||
this.tierInitError = true;
|
||||
|
||||
goto original;
|
||||
}
|
||||
ThreadSafety.MarkMainThread();
|
||||
|
||||
try
|
||||
{
|
||||
Service<ChatGui>.Get().UpdateQueue();
|
||||
Service<ToastGui>.Get().UpdateQueue();
|
||||
Service<GameNetwork>.Get().UpdateQueue();
|
||||
var chatGui = Service<ChatGui>.GetNullable();
|
||||
var toastGui = Service<ToastGui>.GetNullable();
|
||||
var gameNetwork = Service<GameNetwork>.GetNullable();
|
||||
if (chatGui == null || toastGui == null || gameNetwork == null)
|
||||
goto original;
|
||||
|
||||
chatGui.UpdateQueue();
|
||||
toastGui.UpdateQueue();
|
||||
gameNetwork.UpdateQueue();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ namespace Dalamud.Game.Gui
|
|||
/// </summary>
|
||||
[PluginInterface]
|
||||
[InterfaceVersion("1.0")]
|
||||
[ServiceManager.BlockingEarlyLoadedService]
|
||||
public sealed class ChatGui : IDisposable
|
||||
{
|
||||
private readonly ChatGuiAddressResolver address;
|
||||
|
|
@ -34,14 +35,11 @@ namespace Dalamud.Game.Gui
|
|||
|
||||
private IntPtr baseAddress = IntPtr.Zero;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ChatGui"/> class.
|
||||
/// </summary>
|
||||
/// <param name="baseAddress">The base address of the ChatManager.</param>
|
||||
internal ChatGui()
|
||||
[ServiceManager.ServiceConstructor]
|
||||
private ChatGui(SigScanner sigScanner)
|
||||
{
|
||||
this.address = new ChatGuiAddressResolver();
|
||||
this.address.Setup();
|
||||
this.address.Setup(sigScanner);
|
||||
|
||||
this.printMessageHook = new Hook<PrintMessageDelegate>(this.address.PrintMessage, this.HandlePrintMessageDetour);
|
||||
this.populateItemLinkHook = new Hook<PopulateItemLinkDelegate>(this.address.PopulateItemLinkObject, this.HandlePopulateItemLinkDetour);
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ namespace Dalamud.Game.Gui.ContextMenus
|
|||
/// </summary>
|
||||
[PluginInterface]
|
||||
[InterfaceVersion("1.0")]
|
||||
[ServiceManager.BlockingEarlyLoadedService]
|
||||
public sealed class ContextMenu : IDisposable
|
||||
{
|
||||
private const int MaxContextMenuItemsPerContextMenu = 32;
|
||||
|
|
@ -47,13 +48,11 @@ namespace Dalamud.Game.Gui.ContextMenus
|
|||
private OpenSubContextMenuItem? selectedOpenSubContextMenuItem;
|
||||
private ContextMenuOpenedArgs? currentContextMenuOpenedArgs;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ContextMenu"/> class.
|
||||
/// </summary>
|
||||
public ContextMenu()
|
||||
[ServiceManager.ServiceConstructor]
|
||||
private ContextMenu(SigScanner sigScanner)
|
||||
{
|
||||
this.Address = new ContextMenuAddressResolver();
|
||||
this.Address.Setup();
|
||||
this.Address.Setup(sigScanner);
|
||||
|
||||
unsafe
|
||||
{
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ namespace Dalamud.Game.Gui.Dtr
|
|||
/// </summary>
|
||||
[PluginInterface]
|
||||
[InterfaceVersion("1.0")]
|
||||
[ServiceManager.BlockingEarlyLoadedService]
|
||||
public sealed unsafe class DtrBar : IDisposable
|
||||
{
|
||||
private const uint BaseNodeId = 1000;
|
||||
|
|
@ -24,13 +25,10 @@ namespace Dalamud.Game.Gui.Dtr
|
|||
private List<DtrBarEntry> entries = new();
|
||||
private uint runningNodeIds = BaseNodeId;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="DtrBar"/> class.
|
||||
/// </summary>
|
||||
internal DtrBar()
|
||||
[ServiceManager.ServiceConstructor]
|
||||
private DtrBar(DalamudConfiguration configuration)
|
||||
{
|
||||
Service<Framework>.Get().Update += this.Update;
|
||||
var configuration = Service<DalamudConfiguration>.Get();
|
||||
|
||||
configuration.DtrOrder ??= new List<string>();
|
||||
configuration.DtrIgnore ??= new List<string>();
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ namespace Dalamud.Game.Gui.FlyText
|
|||
/// </summary>
|
||||
[PluginInterface]
|
||||
[InterfaceVersion("1.0")]
|
||||
[ServiceManager.BlockingEarlyLoadedService]
|
||||
public sealed class FlyTextGui : IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
|
|
@ -28,13 +29,11 @@ namespace Dalamud.Game.Gui.FlyText
|
|||
/// </summary>
|
||||
private readonly Hook<CreateFlyTextDelegate> createFlyTextHook;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="FlyTextGui"/> class.
|
||||
/// </summary>
|
||||
internal FlyTextGui()
|
||||
[ServiceManager.ServiceConstructor]
|
||||
private FlyTextGui(SigScanner sigScanner)
|
||||
{
|
||||
this.Address = new FlyTextGuiAddressResolver();
|
||||
this.Address.Setup();
|
||||
this.Address.Setup(sigScanner);
|
||||
|
||||
this.addFlyTextNative = Marshal.GetDelegateForFunctionPointer<AddFlyTextDelegate>(this.Address.AddFlyText);
|
||||
this.createFlyTextHook = new Hook<CreateFlyTextDelegate>(this.Address.CreateFlyText, this.CreateFlyTextDetour);
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ namespace Dalamud.Game.Gui
|
|||
/// </summary>
|
||||
[PluginInterface]
|
||||
[InterfaceVersion("1.0")]
|
||||
[ServiceManager.BlockingEarlyLoadedService]
|
||||
public sealed unsafe class GameGui : IDisposable
|
||||
{
|
||||
private readonly GameGuiAddressResolver address;
|
||||
|
|
@ -46,14 +47,11 @@ namespace Dalamud.Game.Gui
|
|||
private GetUIMapObjectDelegate getUIMapObject;
|
||||
private OpenMapWithFlagDelegate openMapWithFlag;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="GameGui"/> class.
|
||||
/// This class is responsible for many aspects of interacting with the native game UI.
|
||||
/// </summary>
|
||||
internal GameGui()
|
||||
[ServiceManager.ServiceConstructor]
|
||||
private GameGui(SigScanner sigScanner)
|
||||
{
|
||||
this.address = new GameGuiAddressResolver();
|
||||
this.address.Setup();
|
||||
this.address.Setup(sigScanner);
|
||||
|
||||
Log.Verbose("===== G A M E G U I =====");
|
||||
Log.Verbose($"GameGuiManager address 0x{this.address.BaseAddress.ToInt64():X}");
|
||||
|
|
@ -62,13 +60,6 @@ namespace Dalamud.Game.Gui
|
|||
Log.Verbose($"HandleItemOut address 0x{this.address.HandleItemOut.ToInt64():X}");
|
||||
Log.Verbose($"HandleImm address 0x{this.address.HandleImm.ToInt64():X}");
|
||||
|
||||
Service<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.handleItemHoverHook = new Hook<HandleItemHoverDelegate>(this.address.HandleItemHover, this.HandleItemHoverDetour);
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ namespace Dalamud.Game.Gui.Internal
|
|||
/// <summary>
|
||||
/// This class handles IME for non-English users.
|
||||
/// </summary>
|
||||
[ServiceManager.EarlyLoadedService]
|
||||
internal unsafe class DalamudIME : IDisposable
|
||||
{
|
||||
private static readonly ModuleLog Log = new("IME");
|
||||
|
|
@ -32,7 +33,9 @@ namespace Dalamud.Game.Gui.Internal
|
|||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="DalamudIME"/> class.
|
||||
/// </summary>
|
||||
internal DalamudIME()
|
||||
/// <param name="tag">Tag.</param>
|
||||
[ServiceManager.ServiceConstructor]
|
||||
private DalamudIME()
|
||||
{
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ namespace Dalamud.Game.Gui.PartyFinder
|
|||
/// </summary>
|
||||
[PluginInterface]
|
||||
[InterfaceVersion("1.0")]
|
||||
[ServiceManager.BlockingEarlyLoadedService]
|
||||
public sealed class PartyFinderGui : IDisposable
|
||||
{
|
||||
private readonly PartyFinderAddressResolver address;
|
||||
|
|
@ -25,10 +26,12 @@ namespace Dalamud.Game.Gui.PartyFinder
|
|||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="PartyFinderGui"/> class.
|
||||
/// </summary>
|
||||
internal PartyFinderGui()
|
||||
/// <param name="tag">Tag.</param>
|
||||
[ServiceManager.ServiceConstructor]
|
||||
private PartyFinderGui(SigScanner sigScanner)
|
||||
{
|
||||
this.address = new PartyFinderAddressResolver();
|
||||
this.address.Setup();
|
||||
this.address.Setup(sigScanner);
|
||||
|
||||
this.memory = Marshal.AllocHGlobal(PartyFinderPacket.PacketSize);
|
||||
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ namespace Dalamud.Game.Gui.Toast
|
|||
/// </summary>
|
||||
[PluginInterface]
|
||||
[InterfaceVersion("1.0")]
|
||||
[ServiceManager.BlockingEarlyLoadedService]
|
||||
public sealed partial class ToastGui : IDisposable
|
||||
{
|
||||
private const uint QuestToastCheckmarkMagic = 60081;
|
||||
|
|
@ -31,10 +32,12 @@ namespace Dalamud.Game.Gui.Toast
|
|||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ToastGui"/> class.
|
||||
/// </summary>
|
||||
internal ToastGui()
|
||||
/// <param name="tag">Tag.</param>
|
||||
[ServiceManager.ServiceConstructor]
|
||||
private ToastGui(SigScanner sigScanner)
|
||||
{
|
||||
this.address = new ToastGuiAddressResolver();
|
||||
this.address.Setup();
|
||||
this.address.Setup(sigScanner);
|
||||
|
||||
this.showNormalToastHook = new Hook<ShowNormalToastDelegate>(this.address.ShowNormalToast, new ShowNormalToastDelegate(this.HandleNormalToastDetour));
|
||||
this.showQuestToastHook = new Hook<ShowQuestToastDelegate>(this.address.ShowQuestToast, new ShowQuestToastDelegate(this.HandleQuestToastDetour));
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
using Dalamud.Configuration.Internal;
|
||||
using Serilog;
|
||||
|
||||
namespace Dalamud.Game.Internal
|
||||
|
|
@ -8,22 +8,19 @@ namespace Dalamud.Game.Internal
|
|||
/// <summary>
|
||||
/// This class disables anti-debug functionality in the game client.
|
||||
/// </summary>
|
||||
[ServiceManager.EarlyLoadedService]
|
||||
internal sealed partial class AntiDebug
|
||||
{
|
||||
private readonly byte[] nop = new byte[] { 0x31, 0xC0, 0x90, 0x90, 0x90, 0x90 };
|
||||
private byte[] original;
|
||||
private IntPtr debugCheckAddress;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AntiDebug"/> class.
|
||||
/// </summary>
|
||||
public AntiDebug()
|
||||
[ServiceManager.ServiceConstructor]
|
||||
private AntiDebug(SigScanner sigScanner)
|
||||
{
|
||||
var scanner = Service<SigScanner>.Get();
|
||||
|
||||
try
|
||||
{
|
||||
this.debugCheckAddress = scanner.ScanText("FF 15 ?? ?? ?? ?? 85 C0 74 11 41");
|
||||
this.debugCheckAddress = sigScanner.ScanText("FF 15 ?? ?? ?? ?? 85 C0 74 11 41");
|
||||
}
|
||||
catch (KeyNotFoundException)
|
||||
{
|
||||
|
|
@ -31,6 +28,16 @@ namespace Dalamud.Game.Internal
|
|||
}
|
||||
|
||||
Log.Verbose($"Debug check address 0x{this.debugCheckAddress.ToInt64():X}");
|
||||
|
||||
if (!this.IsEnabled)
|
||||
{
|
||||
#if DEBUG
|
||||
this.Enable();
|
||||
#else
|
||||
if (Service<DalamudConfiguration>.Get().IsAntiAntiDebugEnabled)
|
||||
this.Enable();
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ using System.Diagnostics;
|
|||
using System.Runtime.InteropServices;
|
||||
|
||||
using Dalamud.Game.Internal.DXGI.Definitions;
|
||||
using FFXIVClientStructs.FFXIV.Client.Graphics.Kernel;
|
||||
using Serilog;
|
||||
|
||||
namespace Dalamud.Game.Internal.DXGI
|
||||
|
|
@ -30,9 +31,28 @@ namespace Dalamud.Game.Internal.DXGI
|
|||
/// <inheritdoc/>
|
||||
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];
|
||||
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ namespace Dalamud.Game.Internal
|
|||
/// <summary>
|
||||
/// This class implements in-game Dalamud options in the in-game System menu.
|
||||
/// </summary>
|
||||
[ServiceManager.EarlyLoadedService]
|
||||
internal sealed unsafe partial class DalamudAtkTweaks
|
||||
{
|
||||
private readonly AtkValueChangeType atkValueChangeType;
|
||||
|
|
@ -37,13 +38,9 @@ namespace Dalamud.Game.Internal
|
|||
private readonly string locDalamudPlugins;
|
||||
private readonly string locDalamudSettings;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="DalamudAtkTweaks"/> class.
|
||||
/// </summary>
|
||||
public DalamudAtkTweaks()
|
||||
[ServiceManager.ServiceConstructor]
|
||||
private DalamudAtkTweaks(SigScanner sigScanner)
|
||||
{
|
||||
var sigScanner = Service<SigScanner>.Get();
|
||||
|
||||
var openSystemMenuAddress = sigScanner.ScanText("E8 ?? ?? ?? ?? 32 C0 4C 8B AC 24 ?? ?? ?? ?? 48 8B 8D ?? ?? ?? ??");
|
||||
|
||||
this.hookAgentHudOpenSystemMenu = new Hook<AgentHudOpenSystemMenuPrototype>(openSystemMenuAddress, this.AgentHudOpenSystemMenuDetour);
|
||||
|
|
|
|||
|
|
@ -12,19 +12,18 @@ namespace Dalamud.Game.Libc
|
|||
/// </summary>
|
||||
[PluginInterface]
|
||||
[InterfaceVersion("1.0")]
|
||||
[ServiceManager.BlockingEarlyLoadedService]
|
||||
public sealed class LibcFunction
|
||||
{
|
||||
private readonly LibcFunctionAddressResolver address;
|
||||
private readonly StdStringFromCStringDelegate stdStringCtorCString;
|
||||
private readonly StdStringDeallocateDelegate stdStringDeallocate;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="LibcFunction"/> class.
|
||||
/// </summary>
|
||||
public LibcFunction()
|
||||
[ServiceManager.ServiceConstructor]
|
||||
private LibcFunction(SigScanner sigScanner)
|
||||
{
|
||||
this.address = new LibcFunctionAddressResolver();
|
||||
this.address.Setup();
|
||||
this.address.Setup(sigScanner);
|
||||
|
||||
this.stdStringCtorCString = Marshal.GetDelegateForFunctionPointer<StdStringFromCStringDelegate>(this.address.StdStringFromCstring);
|
||||
this.stdStringDeallocate = Marshal.GetDelegateForFunctionPointer<StdStringDeallocateDelegate>(this.address.StdStringDeallocate);
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ namespace Dalamud.Game.Network
|
|||
/// </summary>
|
||||
[PluginInterface]
|
||||
[InterfaceVersion("1.0")]
|
||||
[ServiceManager.BlockingEarlyLoadedService]
|
||||
public sealed class GameNetwork : IDisposable
|
||||
{
|
||||
private readonly GameNetworkAddressResolver address;
|
||||
|
|
@ -23,13 +24,11 @@ namespace Dalamud.Game.Network
|
|||
|
||||
private IntPtr baseAddress;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="GameNetwork"/> class.
|
||||
/// </summary>
|
||||
internal GameNetwork()
|
||||
[ServiceManager.ServiceConstructor]
|
||||
private GameNetwork(SigScanner sigScanner)
|
||||
{
|
||||
this.address = new GameNetworkAddressResolver();
|
||||
this.address.Setup();
|
||||
this.address.Setup(sigScanner);
|
||||
|
||||
Log.Verbose("===== G A M E N E T W O R K =====");
|
||||
Log.Verbose($"ProcessZonePacketDown address 0x{this.address.ProcessZonePacketDown.ToInt64():X}");
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ namespace Dalamud.Game.Network.Internal
|
|||
/// <summary>
|
||||
/// This class handles network notifications and uploading market board data.
|
||||
/// </summary>
|
||||
[ServiceManager.EarlyLoadedService]
|
||||
internal class NetworkHandlers
|
||||
{
|
||||
private readonly List<MarketBoardItemRequest> marketBoardRequests = new();
|
||||
|
|
@ -29,14 +30,12 @@ namespace Dalamud.Game.Network.Internal
|
|||
|
||||
private MarketBoardPurchaseHandler marketBoardPurchaseHandler;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="NetworkHandlers"/> class.
|
||||
/// </summary>
|
||||
public NetworkHandlers()
|
||||
[ServiceManager.ServiceConstructor]
|
||||
private NetworkHandlers(GameNetwork gameNetwork)
|
||||
{
|
||||
this.uploader = new UniversalisMarketBoardUploader();
|
||||
|
||||
Service<GameNetwork>.Get().NetworkMessage += this.OnNetworkMessage;
|
||||
gameNetwork.NetworkMessage += this.OnNetworkMessage;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
|||
|
|
@ -9,14 +9,13 @@ namespace Dalamud.Game.Network.Internal
|
|||
/// <summary>
|
||||
/// This class enables TCP optimizations in the game socket for better performance.
|
||||
/// </summary>
|
||||
[ServiceManager.EarlyLoadedService]
|
||||
internal sealed class WinSockHandlers : IDisposable
|
||||
{
|
||||
private Hook<SocketDelegate> ws2SocketHook;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="WinSockHandlers"/> class.
|
||||
/// </summary>
|
||||
public WinSockHandlers()
|
||||
[ServiceManager.ServiceConstructor]
|
||||
private WinSockHandlers()
|
||||
{
|
||||
this.ws2SocketHook = Hook<SocketDelegate>.FromSymbol("ws2_32.dll", "socket", this.OnSocket, true);
|
||||
this.ws2SocketHook?.Enable();
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ using System.Runtime.InteropServices;
|
|||
|
||||
using Dalamud.IoC;
|
||||
using Dalamud.IoC.Internal;
|
||||
using Dalamud.Utility.Timing;
|
||||
using Newtonsoft.Json;
|
||||
using Serilog;
|
||||
|
||||
|
|
@ -18,6 +19,7 @@ namespace Dalamud.Game
|
|||
/// </summary>
|
||||
[PluginInterface]
|
||||
[InterfaceVersion("1.0")]
|
||||
[ServiceManager.BlockingEarlyLoadedService]
|
||||
public class SigScanner : IDisposable
|
||||
{
|
||||
private readonly FileInfo? cacheFile;
|
||||
|
|
@ -27,6 +29,38 @@ namespace Dalamud.Game
|
|||
|
||||
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>
|
||||
/// Initializes a new instance of the <see cref="SigScanner"/> class using the main module of the current process.
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -12,13 +12,12 @@ namespace Dalamud.Game.Text.SeStringHandling
|
|||
/// </summary>
|
||||
[PluginInterface]
|
||||
[InterfaceVersion("1.0")]
|
||||
[ServiceManager.BlockingEarlyLoadedService]
|
||||
[Obsolete("This class is obsolete. Please use the static methods on SeString instead.")]
|
||||
public sealed class SeStringManager
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="SeStringManager"/> class.
|
||||
/// </summary>
|
||||
internal SeStringManager()
|
||||
[ServiceManager.ServiceConstructor]
|
||||
private SeStringManager()
|
||||
{
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -13,14 +13,13 @@ namespace Dalamud.Hooking.Internal
|
|||
/// <summary>
|
||||
/// This class manages the final disposition of hooks, cleaning up any that have not reverted their changes.
|
||||
/// </summary>
|
||||
[ServiceManager.EarlyLoadedService]
|
||||
internal class HookManager : IDisposable
|
||||
{
|
||||
private static readonly ModuleLog Log = new("HM");
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="HookManager"/> class.
|
||||
/// </summary>
|
||||
public HookManager()
|
||||
[ServiceManager.ServiceConstructor]
|
||||
private HookManager()
|
||||
{
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ using System.Linq;
|
|||
using System.Numerics;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
|
||||
using System.Threading.Tasks;
|
||||
using Dalamud.Data;
|
||||
using Dalamud.Interface.Internal;
|
||||
using Dalamud.Utility.Timing;
|
||||
|
|
@ -17,9 +17,10 @@ namespace Dalamud.Interface.GameFonts
|
|||
/// <summary>
|
||||
/// Loads game font for use in ImGui.
|
||||
/// </summary>
|
||||
[ServiceManager.EarlyLoadedService]
|
||||
internal class GameFontManager : IDisposable
|
||||
{
|
||||
private static readonly string[] FontNames =
|
||||
private static readonly string?[] FontNames =
|
||||
{
|
||||
null,
|
||||
"AXIS_96", "AXIS_12", "AXIS_14", "AXIS_18", "AXIS_36",
|
||||
|
|
@ -31,8 +32,6 @@ namespace Dalamud.Interface.GameFonts
|
|||
|
||||
private readonly object syncRoot = new();
|
||||
|
||||
private readonly InterfaceManager interfaceManager;
|
||||
|
||||
private readonly FdtReader?[] fdts;
|
||||
private readonly List<byte[]> texturePixels;
|
||||
private readonly Dictionary<GameFontStyle, ImFontPtr> fonts = new();
|
||||
|
|
@ -42,40 +41,28 @@ namespace Dalamud.Interface.GameFonts
|
|||
private bool isBetweenBuildFontsAndRightAfterImGuiIoFontsBuild = false;
|
||||
private bool isBuildingAsFallbackFontMode = false;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="GameFontManager"/> class.
|
||||
/// </summary>
|
||||
public GameFontManager()
|
||||
[ServiceManager.ServiceConstructor]
|
||||
private GameFontManager(DataManager dataManager)
|
||||
{
|
||||
var dataManager = Service<DataManager>.Get();
|
||||
|
||||
using (Timings.Start("Load FDTs"))
|
||||
using (Timings.Start("Getting fdt data"))
|
||||
{
|
||||
this.fdts = FontNames.Select(fontName =>
|
||||
{
|
||||
var fileName = $"common/font/{fontName}.fdt";
|
||||
using (Timings.Start($"Loading FDT: {fileName}"))
|
||||
{
|
||||
var file = fontName == null ? null : dataManager.GetFile(fileName);
|
||||
return file == null ? null : new FdtReader(file!.Data);
|
||||
}
|
||||
}).ToArray();
|
||||
this.fdts = FontNames.Select(fontName => fontName == null ? null : new FdtReader(dataManager.GetFile($"common/font/{fontName}.fdt")!.Data)).ToArray();
|
||||
}
|
||||
|
||||
using (Timings.Start("Getting texture data"))
|
||||
{
|
||||
this.texturePixels = Enumerable.Range(1, 1 + this.fdts.Where(x => x != null).Select(x => x.Glyphs.Select(x => x.TextureFileIndex).Max()).Max()).Select(
|
||||
x =>
|
||||
{
|
||||
var fileName = $"common/font/font{x}.tex";
|
||||
using (Timings.Start($"Get tex: {fileName}"))
|
||||
{
|
||||
return dataManager.GameData.GetFile<TexFile>(fileName)!.ImageData;
|
||||
var texTasks = Enumerable
|
||||
.Range(1, 1 + this.fdts
|
||||
.Where(x => x != null)
|
||||
.Select(x => x.Glyphs.Select(y => y.TextureFileIndex).Max())
|
||||
.Max())
|
||||
.Select(x => dataManager.GetFile<TexFile>($"common/font/font{x}.tex")!)
|
||||
.Select(x => new Task<byte[]>(() => x.ImageData!))
|
||||
.ToArray();
|
||||
foreach (var task in texTasks)
|
||||
task.Start();
|
||||
this.texturePixels = texTasks.Select(x => x.GetAwaiter().GetResult()).ToList();
|
||||
}
|
||||
}).ToList();
|
||||
}
|
||||
|
||||
this.interfaceManager = Service<InterfaceManager>.Get();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -183,6 +170,7 @@ namespace Dalamud.Interface.GameFonts
|
|||
/// <returns>Handle to game font that may or may not be ready yet.</returns>
|
||||
public GameFontHandle NewFontRef(GameFontStyle style)
|
||||
{
|
||||
var interfaceManager = Service<InterfaceManager>.Get();
|
||||
var needRebuild = false;
|
||||
|
||||
lock (this.syncRoot)
|
||||
|
|
@ -193,7 +181,7 @@ namespace Dalamud.Interface.GameFonts
|
|||
needRebuild = !this.fonts.ContainsKey(style);
|
||||
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());
|
||||
this.EnsureFont(style);
|
||||
|
|
@ -201,7 +189,7 @@ namespace Dalamud.Interface.GameFonts
|
|||
else
|
||||
{
|
||||
Log.Information("[GameFontManager] NewFontRef: Calling RebuildFonts because {0} has been requested.", style.ToString());
|
||||
this.interfaceManager.RebuildFonts();
|
||||
interfaceManager.RebuildFonts();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -294,10 +282,11 @@ namespace Dalamud.Interface.GameFonts
|
|||
/// </summary>
|
||||
public unsafe void AfterBuildFonts()
|
||||
{
|
||||
var interfaceManager = Service<InterfaceManager>.Get();
|
||||
var ioFonts = ImGui.GetIO().Fonts;
|
||||
ioFonts.GetTexDataAsRGBA32(out byte* pixels8, out var width, out var height);
|
||||
var pixels32 = (uint*)pixels8;
|
||||
var fontGamma = this.interfaceManager.FontGamma;
|
||||
var fontGamma = interfaceManager.FontGamma;
|
||||
|
||||
foreach (var (style, font) in this.fonts)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -18,22 +18,12 @@ namespace Dalamud.Interface.Internal
|
|||
/// <summary>
|
||||
/// Class handling Dalamud core commands.
|
||||
/// </summary>
|
||||
[ServiceManager.EarlyLoadedService]
|
||||
internal class DalamudCommands
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="DalamudCommands"/> class.
|
||||
/// </summary>
|
||||
public DalamudCommands()
|
||||
[ServiceManager.ServiceConstructor]
|
||||
private DalamudCommands(CommandManager commandManager)
|
||||
{
|
||||
}
|
||||
|
||||
/// <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)
|
||||
{
|
||||
HelpMessage = Loc.Localize("DalamudUnloadHelp", "Unloads XIVLauncher in-game addon."),
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@ namespace Dalamud.Interface.Internal
|
|||
/// <summary>
|
||||
/// This plugin implements all of the Dalamud interface separately, to allow for reloading of the interface and rapid prototyping.
|
||||
/// </summary>
|
||||
[ServiceManager.EarlyLoadedService]
|
||||
internal class DalamudInterface : IDisposable
|
||||
{
|
||||
private static readonly ModuleLog Log = new("DUI");
|
||||
|
|
@ -75,14 +76,9 @@ namespace Dalamud.Interface.Internal
|
|||
private bool isImGuiTestWindowsInMonospace = false;
|
||||
private bool isImGuiDrawMetricsWindow = false;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="DalamudInterface"/> class.
|
||||
/// </summary>
|
||||
public DalamudInterface()
|
||||
[ServiceManager.ServiceConstructor]
|
||||
private DalamudInterface(Dalamud dalamud, DalamudConfiguration configuration, InterfaceManager interfaceManager)
|
||||
{
|
||||
var configuration = Service<DalamudConfiguration>.Get();
|
||||
var interfaceManager = Service<InterfaceManager>.Get();
|
||||
|
||||
this.WindowSystem = new WindowSystem("DalamudCore");
|
||||
|
||||
this.changelogWindow = new ChangelogWindow() { IsOpen = false };
|
||||
|
|
@ -123,7 +119,6 @@ namespace Dalamud.Interface.Internal
|
|||
this.isImGuiDrawDevMenu = this.isImGuiDrawDevMenu || configuration.DevBarOpenAtStartup;
|
||||
|
||||
interfaceManager.Draw += this.OnDraw;
|
||||
var dalamud = Service<Dalamud>.Get();
|
||||
|
||||
var logoTex =
|
||||
interfaceManager.LoadImage(Path.Combine(dalamud.AssetDirectory.FullName, "UIRes", "logo.png"));
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ using System.Runtime.CompilerServices;
|
|||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
|
||||
using System.Threading.Tasks;
|
||||
using Dalamud.Configuration.Internal;
|
||||
using Dalamud.Game;
|
||||
using Dalamud.Game.ClientState.GamePad;
|
||||
|
|
@ -44,6 +44,7 @@ namespace Dalamud.Interface.Internal
|
|||
/// <summary>
|
||||
/// This class manages interaction with the ImGui interface.
|
||||
/// </summary>
|
||||
[ServiceManager.BlockingEarlyLoadedService]
|
||||
internal class InterfaceManager : IDisposable
|
||||
{
|
||||
private const float MinimumFallbackFontSizePt = 9.6f; // Game's minimum AXIS font size
|
||||
|
|
@ -58,33 +59,27 @@ namespace Dalamud.Interface.Internal
|
|||
private readonly HashSet<SpecialGlyphRequest> glyphRequests = 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 SwapChainVtableResolver address;
|
||||
private readonly TaskCompletionSource sceneInitializeTaskCompletionSource = new();
|
||||
private RawDX11Scene? scene;
|
||||
|
||||
private Hook<PresentDelegate>? presentHook;
|
||||
private Hook<ResizeBuffersDelegate>? resizeBuffersHook;
|
||||
private Hook<SetCursorDelegate>? setCursorHook;
|
||||
|
||||
// can't access imgui IO before first present call
|
||||
private bool lastWantCapture = false;
|
||||
private bool isRebuildingFonts = false;
|
||||
|
||||
private bool isFallbackFontMode = false;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="InterfaceManager"/> class.
|
||||
/// </summary>
|
||||
public InterfaceManager()
|
||||
[ServiceManager.ServiceConstructor]
|
||||
private InterfaceManager()
|
||||
{
|
||||
Service<NotificationManager>.Set();
|
||||
|
||||
var scanner = Service<SigScanner>.Get();
|
||||
|
||||
this.fontBuildSignal = new ManualResetEvent(false);
|
||||
|
||||
this.address = new SwapChainVtableResolver();
|
||||
this.address.Setup(scanner);
|
||||
|
||||
try
|
||||
{
|
||||
|
|
@ -106,16 +101,12 @@ namespace Dalamud.Interface.Internal
|
|||
Log.Error(e, "RTSS Free failed");
|
||||
}
|
||||
|
||||
this.setCursorHook = Hook<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}");
|
||||
Task.Run(async () =>
|
||||
{
|
||||
var framework = await Service<Framework>.GetAsync();
|
||||
var sigScanner = await Service<SigScanner>.GetAsync();
|
||||
await framework.RunOnFrameworkThread(() => this.Enable(sigScanner));
|
||||
});
|
||||
}
|
||||
|
||||
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
|
||||
|
|
@ -129,6 +120,11 @@ namespace Dalamud.Interface.Internal
|
|||
|
||||
private delegate void InstallRTSSHook();
|
||||
|
||||
/// <summary>
|
||||
/// Gets a task that gets completed when scene gets initialized.
|
||||
/// </summary>
|
||||
public Task SceneInitializeTask => this.sceneInitializeTaskCompletionSource.Task;
|
||||
|
||||
/// <summary>
|
||||
/// This event gets called each frame to facilitate ImGui drawing.
|
||||
/// </summary>
|
||||
|
|
@ -259,34 +255,6 @@ namespace Dalamud.Interface.Internal
|
|||
/// </summary>
|
||||
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>
|
||||
/// Dispose of managed and unmanaged resources.
|
||||
/// </summary>
|
||||
|
|
@ -303,8 +271,8 @@ namespace Dalamud.Interface.Internal
|
|||
|
||||
this.scene?.Dispose();
|
||||
this.setCursorHook?.Dispose();
|
||||
this.presentHook.Dispose();
|
||||
this.resizeBuffersHook.Dispose();
|
||||
this.presentHook?.Dispose();
|
||||
this.resizeBuffersHook?.Dispose();
|
||||
}
|
||||
|
||||
#nullable enable
|
||||
|
|
@ -480,9 +448,11 @@ namespace Dalamud.Interface.Internal
|
|||
try
|
||||
{
|
||||
this.scene = new RawDX11Scene(swapChain);
|
||||
this.sceneInitializeTaskCompletionSource.SetResult();
|
||||
}
|
||||
catch (DllNotFoundException ex)
|
||||
{
|
||||
this.sceneInitializeTaskCompletionSource.SetException(ex);
|
||||
Log.Error(ex, "Could not load ImGui dependencies.");
|
||||
|
||||
var res = PInvoke.User32.MessageBox(
|
||||
|
|
@ -1003,6 +973,43 @@ namespace Dalamud.Interface.Internal
|
|||
}
|
||||
}
|
||||
|
||||
private void Enable(SigScanner sigScanner)
|
||||
{
|
||||
this.address.Setup(sigScanner);
|
||||
this.setCursorHook = Hook<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()
|
||||
{
|
||||
this.setCursorHook?.Disable();
|
||||
|
|
@ -1094,6 +1101,9 @@ namespace Dalamud.Interface.Internal
|
|||
var gamepadState = Service<GamepadState>.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)
|
||||
// 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
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ namespace Dalamud.Interface.Internal.Notifications
|
|||
/// Class handling notifications/toasts in ImGui.
|
||||
/// Ported from https://github.com/patrickcjk/imgui-notify.
|
||||
/// </summary>
|
||||
[ServiceManager.EarlyLoadedService]
|
||||
internal class NotificationManager
|
||||
{
|
||||
/// <summary>
|
||||
|
|
@ -54,6 +55,11 @@ namespace Dalamud.Interface.Internal.Notifications
|
|||
|
||||
private readonly List<Notification> notifications = new();
|
||||
|
||||
[ServiceManager.ServiceConstructor]
|
||||
private NotificationManager()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add a notification to the notification queue.
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -1485,7 +1485,7 @@ namespace Dalamud.Interface.Internal.Windows
|
|||
ImGuiHelpers.ScaledDummy(20);
|
||||
|
||||
// Needed to init the task tracker, if we're not on a debug build
|
||||
var tracker = Service<TaskTracker>.GetNullable() ?? Service<TaskTracker>.Set();
|
||||
Service<TaskTracker>.Get().Enable();
|
||||
|
||||
for (var i = 0; i < TaskTracker.Tasks.Count; i++)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -60,6 +60,8 @@ namespace Dalamud.Interface.Internal.Windows
|
|||
var dalamud = Service<Dalamud>.Get();
|
||||
var interfaceManager = Service<InterfaceManager>.Get();
|
||||
|
||||
interfaceManager.SceneInitializeTask.Wait();
|
||||
|
||||
this.DefaultIcon = interfaceManager.LoadImage(Path.Combine(dalamud.AssetDirectory.FullName, "UIRes", "defaultIcon.png"))!;
|
||||
this.TroubleIcon = interfaceManager.LoadImage(Path.Combine(dalamud.AssetDirectory.FullName, "UIRes", "troubleIcon.png"))!;
|
||||
this.UpdateIcon = interfaceManager.LoadImage(Path.Combine(dalamud.AssetDirectory.FullName, "UIRes", "updateIcon.png"))!;
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ namespace Dalamud.Interface.Internal.Windows
|
|||
|
||||
private XivChatType dalamudMessagesChatType;
|
||||
|
||||
private bool doWaitForPluginsOnStartup;
|
||||
private bool doCfTaskBarFlash;
|
||||
private bool doCfChatMessage;
|
||||
private bool doMbCollect;
|
||||
|
|
@ -90,6 +91,7 @@ namespace Dalamud.Interface.Internal.Windows
|
|||
|
||||
this.dalamudMessagesChatType = configuration.GeneralChatType;
|
||||
|
||||
this.doWaitForPluginsOnStartup = configuration.IsResumeGameAfterPluginLoad;
|
||||
this.doCfTaskBarFlash = configuration.DutyFinderTaskbarFlash;
|
||||
this.doCfChatMessage = configuration.DutyFinderChatMessage;
|
||||
this.doMbCollect = configuration.IsMbCollect;
|
||||
|
|
@ -261,6 +263,9 @@ namespace Dalamud.Interface.Internal.Windows
|
|||
|
||||
ImGuiHelpers.ScaledDummy(5);
|
||||
|
||||
ImGui.Checkbox(Loc.Localize("DalamudSettingsWaitForPluginsOnStartup", "Wait for plugins before game loads"), ref this.doWaitForPluginsOnStartup);
|
||||
ImGui.TextColored(ImGuiColors.DalamudGrey, Loc.Localize("DalamudSettingsWaitForPluginsOnStartupHint", "Do not let the game load, until plugins are loaded."));
|
||||
|
||||
ImGui.Checkbox(Loc.Localize("DalamudSettingsFlash", "Flash FFXIV window on duty pop"), ref this.doCfTaskBarFlash);
|
||||
ImGui.TextColored(ImGuiColors.DalamudGrey, Loc.Localize("DalamudSettingsFlashHint", "Flash the FFXIV window in your task bar when a duty is ready."));
|
||||
|
||||
|
|
@ -916,6 +921,7 @@ namespace Dalamud.Interface.Internal.Windows
|
|||
|
||||
configuration.GeneralChatType = this.dalamudMessagesChatType;
|
||||
|
||||
configuration.IsResumeGameAfterPluginLoad = this.doWaitForPluginsOnStartup;
|
||||
configuration.DutyFinderTaskbarFlash = this.doCfTaskBarFlash;
|
||||
configuration.DutyFinderChatMessage = this.doCfChatMessage;
|
||||
configuration.IsMbCollect = this.doMbCollect;
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ namespace Dalamud.Interface
|
|||
/// </summary>
|
||||
[PluginInterface]
|
||||
[InterfaceVersion("1.0")]
|
||||
[ServiceManager.BlockingEarlyLoadedService]
|
||||
public class TitleScreenMenu
|
||||
{
|
||||
/// <summary>
|
||||
|
|
@ -21,6 +22,11 @@ namespace Dalamud.Interface
|
|||
|
||||
private readonly List<TitleScreenMenuEntry> entries = new();
|
||||
|
||||
[ServiceManager.ServiceConstructor]
|
||||
private TitleScreenMenu()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the list of entries in the title screen menu.
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
using System;
|
||||
using System.Reflection;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Dalamud.IoC.Internal
|
||||
{
|
||||
|
|
@ -11,11 +12,12 @@ namespace Dalamud.IoC.Internal
|
|||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ObjectInstance"/> class.
|
||||
/// </summary>
|
||||
/// <param name="instance">The underlying instance.</param>
|
||||
public ObjectInstance(object instance)
|
||||
/// <param name="instanceTask">Weak reference to the underlying instance.</param>
|
||||
/// <param name="type">Type of the underlying instance.</param>
|
||||
public ObjectInstance(Task<WeakReference> instanceTask, Type type)
|
||||
{
|
||||
this.Instance = new WeakReference(instance);
|
||||
this.Version = instance.GetType().GetCustomAttribute<InterfaceVersionAttribute>();
|
||||
this.InstanceTask = instanceTask;
|
||||
this.Version = type.GetCustomAttribute<InterfaceVersionAttribute>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -26,6 +28,7 @@ namespace Dalamud.IoC.Internal
|
|||
/// <summary>
|
||||
/// Gets a reference to the underlying instance.
|
||||
/// </summary>
|
||||
public WeakReference Instance { get; }
|
||||
/// <returns>The underlying instance.</returns>
|
||||
public Task<WeakReference> InstanceTask { get; }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ using System.Collections.Generic;
|
|||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
using System.Threading.Tasks;
|
||||
using Dalamud.Logging.Internal;
|
||||
|
||||
namespace Dalamud.IoC.Internal
|
||||
|
|
@ -17,19 +17,26 @@ namespace Dalamud.IoC.Internal
|
|||
|
||||
private readonly Dictionary<Type, ObjectInstance> instances = new();
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ServiceContainer"/> class.
|
||||
/// </summary>
|
||||
public ServiceContainer()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Register a singleton object of any type into the current IOC container.
|
||||
/// </summary>
|
||||
/// <param name="instance">The existing instance to register in the container.</param>
|
||||
/// <typeparam name="T">The interface to register.</typeparam>
|
||||
public void RegisterSingleton<T>(T instance)
|
||||
public void RegisterSingleton<T>(Task<T> instance)
|
||||
{
|
||||
if (instance == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(instance));
|
||||
}
|
||||
|
||||
this.instances[typeof(T)] = new(instance);
|
||||
this.instances[typeof(T)] = new(instance.ContinueWith(x => new WeakReference(x.Result)), typeof(T));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -38,12 +45,12 @@ namespace Dalamud.IoC.Internal
|
|||
/// <param name="objectType">The type of object to create.</param>
|
||||
/// <param name="scopedObjects">Scoped objects to be included in the constructor.</param>
|
||||
/// <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);
|
||||
if (ctor == null)
|
||||
{
|
||||
Log.Error("Failed to create {TypeName}, an eligible ctor with satisfiable services could not be found", objectType.FullName);
|
||||
Log.Error("Failed to create {TypeName}, an eligible ctor with satisfiable services could not be found", objectType.FullName!);
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
@ -59,36 +66,37 @@ namespace Dalamud.IoC.Internal
|
|||
|
||||
if (!versionCheck)
|
||||
{
|
||||
Log.Error("Failed to create {TypeName}, a RequestedVersion could not be satisfied", objectType.FullName);
|
||||
Log.Error("Failed to create {TypeName}, a RequestedVersion could not be satisfied", objectType.FullName!);
|
||||
return null;
|
||||
}
|
||||
|
||||
var resolvedParams = parameters
|
||||
.Select(p =>
|
||||
var resolvedParams =
|
||||
await Task.WhenAll(
|
||||
parameters
|
||||
.Select(async p =>
|
||||
{
|
||||
var service = this.GetService(p.parameterType, scopedObjects);
|
||||
var service = await this.GetService(p.parameterType, scopedObjects);
|
||||
|
||||
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;
|
||||
})
|
||||
.ToArray();
|
||||
}));
|
||||
|
||||
var hasNull = resolvedParams.Any(p => p == null);
|
||||
if (hasNull)
|
||||
{
|
||||
Log.Error("Failed to create {TypeName}, a requested service type could not be satisfied", objectType.FullName);
|
||||
Log.Error("Failed to create {TypeName}, a requested service type could not be satisfied", objectType.FullName!);
|
||||
return null;
|
||||
}
|
||||
|
||||
var instance = FormatterServices.GetUninitializedObject(objectType);
|
||||
|
||||
if (!this.InjectProperties(instance, scopedObjects))
|
||||
if (!await this.InjectProperties(instance, scopedObjects))
|
||||
{
|
||||
Log.Error("Failed to create {TypeName}, a requested property service type could not be satisfied", objectType.FullName);
|
||||
Log.Error("Failed to create {TypeName}, a requested property service type could not be satisfied", objectType.FullName!);
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
@ -105,7 +113,7 @@ namespace Dalamud.IoC.Internal
|
|||
/// <param name="instance">The object instance.</param>
|
||||
/// <param name="scopedObjects">Scoped objects.</param>
|
||||
/// <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();
|
||||
|
||||
|
|
@ -121,17 +129,17 @@ namespace Dalamud.IoC.Internal
|
|||
|
||||
if (!versionCheck)
|
||||
{
|
||||
Log.Error("Failed to create {TypeName}, a RequestedVersion could not be satisfied", objectType.FullName);
|
||||
Log.Error("Failed to create {TypeName}, a RequestedVersion could not be satisfied", objectType.FullName!);
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach (var prop in props)
|
||||
{
|
||||
var service = this.GetService(prop.propertyInfo.PropertyType, scopedObjects);
|
||||
var service = await this.GetService(prop.propertyInfo.PropertyType, scopedObjects);
|
||||
|
||||
if (service == null)
|
||||
{
|
||||
Log.Error("Requested service type {TypeName} was not available (null)", prop.propertyInfo.PropertyType.FullName);
|
||||
Log.Error("Requested service type {TypeName} was not available (null)", prop.propertyInfo.PropertyType.FullName!);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -162,14 +170,14 @@ namespace Dalamud.IoC.Internal
|
|||
"Requested version {ReqVersion} does not match the implemented version {ImplVersion} for param type {ParamType}",
|
||||
requiredVersion.Version,
|
||||
declVersion.Version,
|
||||
parameterType.FullName);
|
||||
parameterType.FullName!);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private object? GetService(Type serviceType, object[] scopedObjects)
|
||||
private async Task<object?> GetService(Type serviceType, object[] scopedObjects)
|
||||
{
|
||||
var singletonService = this.GetService(serviceType);
|
||||
var singletonService = await this.GetService(serviceType);
|
||||
if (singletonService != null)
|
||||
{
|
||||
return singletonService;
|
||||
|
|
@ -185,15 +193,13 @@ namespace Dalamud.IoC.Internal
|
|||
return scoped;
|
||||
}
|
||||
|
||||
private object? GetService(Type serviceType)
|
||||
private async Task<object?> GetService(Type serviceType)
|
||||
{
|
||||
var hasInstance = this.instances.TryGetValue(serviceType, out var service);
|
||||
if (hasInstance && service.Instance.IsAlive)
|
||||
{
|
||||
return service.Instance.Target;
|
||||
}
|
||||
|
||||
if (!this.instances.TryGetValue(serviceType, out var service))
|
||||
return null;
|
||||
|
||||
var instance = await service.InstanceTask;
|
||||
return instance.Target;
|
||||
}
|
||||
|
||||
private ConstructorInfo? FindApplicableCtor(Type type, object[] scopedObjects)
|
||||
|
|
@ -224,7 +230,7 @@ namespace Dalamud.IoC.Internal
|
|||
var contains = types.Contains(parameter.ParameterType);
|
||||
if (!contains)
|
||||
{
|
||||
Log.Error("Failed to validate {TypeName}, unable to find any services that satisfy the type", parameter.ParameterType.FullName);
|
||||
Log.Error("Failed to validate {TypeName}, unable to find any services that satisfy the type", parameter.ParameterType.FullName!);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ using System.Linq;
|
|||
using System.Reflection;
|
||||
|
||||
using CheapLoc;
|
||||
using Dalamud.Configuration.Internal;
|
||||
using Serilog;
|
||||
|
||||
namespace Dalamud
|
||||
|
|
@ -12,6 +13,7 @@ namespace Dalamud
|
|||
/// <summary>
|
||||
/// Class handling localization.
|
||||
/// </summary>
|
||||
[ServiceManager.EarlyLoadedService]
|
||||
public class Localization
|
||||
{
|
||||
/// <summary>
|
||||
|
|
@ -40,6 +42,16 @@ namespace Dalamud
|
|||
this.assembly = Assembly.GetCallingAssembly();
|
||||
}
|
||||
|
||||
[ServiceManager.ServiceConstructor]
|
||||
private Localization(Dalamud dalamud, DalamudConfiguration configuration)
|
||||
: this(Path.Combine(dalamud.AssetDirectory.FullName, "UIRes", "loc", "dalamud"), "dalamud_")
|
||||
{
|
||||
if (!string.IsNullOrEmpty(configuration.LanguageOverride))
|
||||
this.SetupWithLangCode(configuration.LanguageOverride);
|
||||
else
|
||||
this.SetupWithUiCulture();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Delegate for the <see cref="Localization.LocalizationChanged"/> event that occurs when the language is changed.
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ namespace Dalamud.Logging.Internal
|
|||
/// <summary>
|
||||
/// Class offering various methods to allow for logging in Dalamud modules.
|
||||
/// </summary>
|
||||
internal class ModuleLog
|
||||
public class ModuleLog
|
||||
{
|
||||
private readonly string moduleName;
|
||||
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ namespace Dalamud.Logging.Internal
|
|||
/// <summary>
|
||||
/// Class responsible for tracking asynchronous tasks.
|
||||
/// </summary>
|
||||
[ServiceManager.EarlyLoadedService]
|
||||
internal class TaskTracker : IDisposable
|
||||
{
|
||||
private static readonly ModuleLog Log = new("TT");
|
||||
|
|
@ -20,16 +21,14 @@ namespace Dalamud.Logging.Internal
|
|||
private static bool clearRequested = false;
|
||||
|
||||
private MonoMod.RuntimeDetour.Hook? scheduleAndStartHook;
|
||||
private bool enabled = false;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="TaskTracker"/> class.
|
||||
/// </summary>
|
||||
public TaskTracker()
|
||||
[ServiceManager.ServiceConstructor]
|
||||
private TaskTracker()
|
||||
{
|
||||
this.ApplyPatch();
|
||||
|
||||
var framework = Service<Framework>.Get();
|
||||
framework.Update += this.FrameworkOnUpdate;
|
||||
#if DEBUG
|
||||
this.Enable();
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <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/>
|
||||
public void Dispose()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -378,7 +378,7 @@ namespace Dalamud.Plugin
|
|||
realScopedObjects[0] = this;
|
||||
Array.Copy(scopedObjects, 0, realScopedObjects, 1, scopedObjects.Length);
|
||||
|
||||
return svcContainer.InjectProperties(instance, realScopedObjects);
|
||||
return svcContainer.InjectProperties(instance, realScopedObjects).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ namespace Dalamud.Plugin.Internal;
|
|||
/// <summary>
|
||||
/// Class responsible for loading and unloading plugins.
|
||||
/// </summary>
|
||||
[ServiceManager.EarlyLoadedService]
|
||||
internal partial class PluginManager : IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
|
|
@ -40,16 +41,17 @@ internal partial class PluginManager : IDisposable
|
|||
private readonly DirectoryInfo devPluginDirectory;
|
||||
private readonly BannedPlugin[] bannedPlugins;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="PluginManager"/> class.
|
||||
/// </summary>
|
||||
public PluginManager()
|
||||
{
|
||||
var startInfo = Service<DalamudStartInfo>.Get();
|
||||
var configuration = Service<DalamudConfiguration>.Get();
|
||||
[ServiceManager.ServiceDependency]
|
||||
private readonly DalamudConfiguration configuration = Service<DalamudConfiguration>.Get();
|
||||
|
||||
this.pluginDirectory = new DirectoryInfo(startInfo.PluginDirectory);
|
||||
this.devPluginDirectory = new DirectoryInfo(startInfo.DefaultPluginDirectory);
|
||||
[ServiceManager.ServiceDependency]
|
||||
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)
|
||||
this.pluginDirectory.Create();
|
||||
|
|
@ -57,16 +59,16 @@ internal partial class PluginManager : IDisposable
|
|||
if (!this.devPluginDirectory.Exists)
|
||||
this.devPluginDirectory.Create();
|
||||
|
||||
this.SafeMode = EnvironmentConfiguration.DalamudNoPlugins || configuration.PluginSafeMode;
|
||||
this.SafeMode = EnvironmentConfiguration.DalamudNoPlugins || this.configuration.PluginSafeMode;
|
||||
if (this.SafeMode)
|
||||
{
|
||||
configuration.PluginSafeMode = false;
|
||||
configuration.Save();
|
||||
this.configuration.PluginSafeMode = false;
|
||||
this.configuration.Save();
|
||||
}
|
||||
|
||||
this.PluginConfigs = new PluginConfigurations(Path.Combine(Path.GetDirectoryName(startInfo.ConfigurationPath) ?? string.Empty, "pluginConfigs"));
|
||||
this.PluginConfigs = new PluginConfigurations(Path.Combine(Path.GetDirectoryName(this.startInfo.ConfigurationPath) ?? string.Empty, "pluginConfigs"));
|
||||
|
||||
var bannedPluginsJson = File.ReadAllText(Path.Combine(startInfo.AssetDirectory, "UIRes", "bannedplugin.json"));
|
||||
var bannedPluginsJson = File.ReadAllText(Path.Combine(this.startInfo.AssetDirectory!, "UIRes", "bannedplugin.json"));
|
||||
this.bannedPlugins = JsonConvert.DeserializeObject<BannedPlugin[]>(bannedPluginsJson) ?? Array.Empty<BannedPlugin>();
|
||||
|
||||
this.ApplyPatches();
|
||||
|
|
@ -225,10 +227,8 @@ internal partial class PluginManager : IDisposable
|
|||
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
|
||||
public async Task SetPluginReposFromConfigAsync(bool notify)
|
||||
{
|
||||
var configuration = Service<DalamudConfiguration>.Get();
|
||||
|
||||
var repos = new List<PluginRepository>() { PluginRepository.MainRepo };
|
||||
repos.AddRange(configuration.ThirdRepoList
|
||||
repos.AddRange(this.configuration.ThirdRepoList
|
||||
.Where(repo => repo.IsEnabled)
|
||||
.Select(repo => new PluginRepository(repo.Url, repo.IsEnabled)));
|
||||
|
||||
|
|
@ -251,8 +251,6 @@ internal partial class PluginManager : IDisposable
|
|||
return;
|
||||
}
|
||||
|
||||
var configuration = Service<DalamudConfiguration>.Get();
|
||||
|
||||
var pluginDefs = 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.
|
||||
var devDllFiles = this.devPluginDirectory.GetFiles("*.dll", SearchOption.AllDirectories).ToList();
|
||||
|
||||
foreach (var setting in configuration.DevPluginLoadLocations)
|
||||
foreach (var setting in this.configuration.DevPluginLoadLocations)
|
||||
{
|
||||
if (!setting.IsEnabled)
|
||||
continue;
|
||||
|
|
@ -420,15 +418,13 @@ internal partial class PluginManager : IDisposable
|
|||
return;
|
||||
}
|
||||
|
||||
var configuration = Service<DalamudConfiguration>.Get();
|
||||
|
||||
if (!this.devPluginDirectory.Exists)
|
||||
this.devPluginDirectory.Create();
|
||||
|
||||
// devPlugins are more freeform. Look for any dll and hope to get lucky.
|
||||
var devDllFiles = this.devPluginDirectory.GetFiles("*.dll", SearchOption.AllDirectories).ToList();
|
||||
|
||||
foreach (var setting in configuration.DevPluginLoadLocations)
|
||||
foreach (var setting in this.configuration.DevPluginLoadLocations)
|
||||
{
|
||||
if (!setting.IsEnabled)
|
||||
continue;
|
||||
|
|
@ -682,9 +678,6 @@ internal partial class PluginManager : IDisposable
|
|||
/// </summary>
|
||||
public void CleanupPlugins()
|
||||
{
|
||||
var configuration = Service<DalamudConfiguration>.Get();
|
||||
var startInfo = Service<DalamudStartInfo>.Get();
|
||||
|
||||
foreach (var pluginDir in this.pluginDirectory.GetDirectories())
|
||||
{
|
||||
try
|
||||
|
|
@ -748,7 +741,7 @@ internal partial class PluginManager : IDisposable
|
|||
continue;
|
||||
}
|
||||
|
||||
if (manifest.ApplicableVersion < startInfo.GameVersion)
|
||||
if (manifest.ApplicableVersion <this.startInfo.GameVersion)
|
||||
{
|
||||
Log.Information($"Inapplicable version: cleaning up {versionDir.FullName}");
|
||||
versionDir.Delete(true);
|
||||
|
|
@ -919,15 +912,12 @@ internal partial class PluginManager : IDisposable
|
|||
/// <returns>If the manifest is eligible.</returns>
|
||||
public bool IsManifestEligible(PluginManifest manifest)
|
||||
{
|
||||
var configuration = Service<DalamudConfiguration>.Get();
|
||||
var startInfo = Service<DalamudStartInfo>.Get();
|
||||
|
||||
// Testing exclusive
|
||||
if (manifest.IsTestingExclusive && !configuration.DoPluginTest)
|
||||
return false;
|
||||
|
||||
// Applicable version
|
||||
if (manifest.ApplicableVersion < startInfo.GameVersion)
|
||||
if (manifest.ApplicableVersion <this.startInfo.GameVersion)
|
||||
return false;
|
||||
|
||||
// API level
|
||||
|
|
@ -945,7 +935,6 @@ internal partial class PluginManager : IDisposable
|
|||
/// <returns>A value indicating whether the plugin/manifest has been banned.</returns>
|
||||
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))
|
||||
&& ban.AssemblyVersion >= manifest.AssemblyVersion);
|
||||
}
|
||||
|
|
|
|||
49
Dalamud/Plugin/Internal/StartupPluginLoader.cs
Normal file
49
Dalamud/Plugin/Internal/StartupPluginLoader.cs
Normal 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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -334,7 +334,7 @@ internal class LocalPlugin : IDisposable
|
|||
this.DalamudInterface = new DalamudPluginInterface(this.pluginAssembly.GetName().Name!, this.DllFile, reason, this.IsDev);
|
||||
|
||||
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)
|
||||
{
|
||||
this.State = PluginState.LoadError;
|
||||
|
|
|
|||
|
|
@ -5,14 +5,13 @@ namespace Dalamud.Plugin.Ipc.Internal
|
|||
/// <summary>
|
||||
/// This class facilitates inter-plugin communication.
|
||||
/// </summary>
|
||||
[ServiceManager.EarlyLoadedService]
|
||||
internal class CallGate
|
||||
{
|
||||
private readonly Dictionary<string, CallGateChannel> gates = new();
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="CallGate"/> class.
|
||||
/// </summary>
|
||||
internal CallGate()
|
||||
[ServiceManager.ServiceConstructor]
|
||||
private CallGate()
|
||||
{
|
||||
}
|
||||
|
||||
|
|
|
|||
177
Dalamud/ServiceManager.cs
Normal file
177
Dalamud/ServiceManager.cs
Normal 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<T>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
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,9 +1,13 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Dalamud.IoC;
|
||||
using Dalamud.IoC.Internal;
|
||||
using Dalamud.Logging.Internal;
|
||||
using Dalamud.Utility.Timing;
|
||||
using JetBrains.Annotations;
|
||||
|
||||
namespace Dalamud
|
||||
{
|
||||
|
|
@ -14,112 +18,154 @@ namespace Dalamud
|
|||
/// Only used internally within Dalamud, if plugins need access to things it should be _only_ via DI.
|
||||
/// </remarks>
|
||||
/// <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()
|
||||
{
|
||||
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>
|
||||
/// Sets the type in the service locator to the given object.
|
||||
/// </summary>
|
||||
/// <param name="obj">Object to set.</param>
|
||||
/// <returns>The set object.</returns>
|
||||
public static T Set(T obj)
|
||||
public static void Provide(T obj)
|
||||
{
|
||||
SetInstanceObject(obj);
|
||||
|
||||
return instance!;
|
||||
InstanceTcs!.SetResult(obj);
|
||||
ServiceManager.Log.Debug("Service<{0}>: Provided", typeof(T).Name);
|
||||
}
|
||||
|
||||
/// <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>
|
||||
/// <returns>The set 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>
|
||||
/// <returns>The object.</returns>
|
||||
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>
|
||||
/// Attempt to pull the instance out of the service locator.
|
||||
/// </summary>
|
||||
/// <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<T>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 availableToPlugins = RegisterInIoCContainer(instance);
|
||||
|
||||
if (availableToPlugins)
|
||||
Log.Information($"Registered {typeof(T).FullName} into service locator and exposed to plugins");
|
||||
else
|
||||
Log.Information($"Registered {typeof(T).FullName} into service locator privately");
|
||||
var task = (Task)typeof(Service<>)
|
||||
.MakeGenericType(type)
|
||||
.InvokeMember(
|
||||
"GetAsync",
|
||||
BindingFlags.InvokeMethod |
|
||||
BindingFlags.Static |
|
||||
BindingFlags.Public,
|
||||
null,
|
||||
null,
|
||||
null)!;
|
||||
await task;
|
||||
return typeof(Task<>).MakeGenericType(type)
|
||||
.GetProperty("Result", BindingFlags.Instance | BindingFlags.Public)!
|
||||
.GetValue(task);
|
||||
}
|
||||
|
||||
private static bool RegisterInIoCContainer(T instance)
|
||||
private static ConstructorInfo GetServiceConstructor()
|
||||
{
|
||||
var attr = typeof(T).GetCustomAttribute<PluginInterfaceAttribute>();
|
||||
if (attr == null)
|
||||
{
|
||||
return false;
|
||||
const BindingFlags ctorBindingFlags =
|
||||
BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic |
|
||||
BindingFlags.CreateInstance | BindingFlags.OptionalParamBinding;
|
||||
return typeof(T)
|
||||
.GetConstructors(ctorBindingFlags)
|
||||
.Single(x => x.GetCustomAttributes(typeof(ServiceManager.ServiceConstructor), true).Any());
|
||||
}
|
||||
|
||||
var ioc = Service<ServiceContainer>.GetNullable();
|
||||
if (ioc == null)
|
||||
private static async Task<T> ConstructObject()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
ioc.RegisterSingleton(instance);
|
||||
|
||||
return true;
|
||||
var ctor = GetServiceConstructor();
|
||||
var args = await Task.WhenAll(
|
||||
ctor.GetParameters().Select(x => GetServiceObjectConstructArgument(x.ParameterType)));
|
||||
return (T)ctor.Invoke(args)!;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,12 +8,12 @@ namespace Dalamud.Utility;
|
|||
public static class ThreadSafety
|
||||
{
|
||||
[ThreadStatic]
|
||||
private static bool isMainThread;
|
||||
private static bool threadStaticIsMainThread;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the current thread is the main thread.
|
||||
/// </summary>
|
||||
public static bool IsMainThread => isMainThread;
|
||||
public static bool IsMainThread => threadStaticIsMainThread;
|
||||
|
||||
/// <summary>
|
||||
/// 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>
|
||||
public static void AssertMainThread()
|
||||
{
|
||||
if (!isMainThread)
|
||||
if (!threadStaticIsMainThread)
|
||||
{
|
||||
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>
|
||||
public static void AssertNotMainThread()
|
||||
{
|
||||
if (isMainThread)
|
||||
if (threadStaticIsMainThread)
|
||||
{
|
||||
throw new InvalidOperationException("On main thread!");
|
||||
}
|
||||
|
|
@ -44,6 +44,6 @@ public static class ThreadSafety
|
|||
/// </summary>
|
||||
internal static void MarkMainThread()
|
||||
{
|
||||
isMainThread = true;
|
||||
threadStaticIsMainThread = true;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue