mirror of
https://github.com/goatcorp/Dalamud.git
synced 2025-12-12 10:17:22 +01:00
Improvements (#903)
This commit is contained in:
parent
e9cd7e0273
commit
716736f022
55 changed files with 1809 additions and 872 deletions
|
|
@ -403,6 +403,96 @@ void xivfixes::redirect_openprocess(bool bApply) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void xivfixes::backup_userdata_save(bool bApply) {
|
||||||
|
static const char* LogTag = "[xivfixes:backup_userdata_save]";
|
||||||
|
static std::optional<hooks::import_hook<decltype(CreateFileW)>> s_hookCreateFileW;
|
||||||
|
static std::optional<hooks::import_hook<decltype(CloseHandle)>> s_hookCloseHandle;
|
||||||
|
static std::map<HANDLE, std::pair<std::filesystem::path, std::filesystem::path>> s_handles;
|
||||||
|
static std::mutex s_mtx;
|
||||||
|
|
||||||
|
if (bApply) {
|
||||||
|
if (!g_startInfo.BootEnabledGameFixes.contains("backup_userdata_save")) {
|
||||||
|
logging::I("{} Turned off via environment variable.", LogTag);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
s_hookCreateFileW.emplace("kernel32.dll!CreateFileW (import, backup_userdata_save)", "kernel32.dll", "CreateFileW", 0);
|
||||||
|
s_hookCloseHandle.emplace("kernel32.dll!CloseHandle (import, backup_userdata_save)", "kernel32.dll", "CloseHandle", 0);
|
||||||
|
|
||||||
|
s_hookCreateFileW->set_detour([](LPCWSTR lpFileName,
|
||||||
|
DWORD dwDesiredAccess,
|
||||||
|
DWORD dwShareMode,
|
||||||
|
LPSECURITY_ATTRIBUTES lpSecurityAttributes,
|
||||||
|
DWORD dwCreationDisposition,
|
||||||
|
DWORD dwFlagsAndAttributes,
|
||||||
|
HANDLE hTemplateFile)->HANDLE {
|
||||||
|
if (dwDesiredAccess != GENERIC_WRITE)
|
||||||
|
return s_hookCreateFileW->call_original(lpFileName, dwDesiredAccess, dwShareMode, lpSecurityAttributes, dwCreationDisposition, dwFlagsAndAttributes, hTemplateFile);
|
||||||
|
|
||||||
|
auto path = std::filesystem::path(lpFileName);
|
||||||
|
const auto ext = unicode::convert<std::string>(path.extension().wstring(), &unicode::lower);
|
||||||
|
if (ext != ".dat" && ext != ".cfg")
|
||||||
|
return s_hookCreateFileW->call_original(lpFileName, dwDesiredAccess, dwShareMode, lpSecurityAttributes, dwCreationDisposition, dwFlagsAndAttributes, hTemplateFile);
|
||||||
|
|
||||||
|
std::filesystem::path temporaryPath = path;
|
||||||
|
temporaryPath.replace_extension(path.extension().wstring() + L".new");
|
||||||
|
const auto handle = s_hookCreateFileW->call_original(temporaryPath.c_str(), GENERIC_READ | GENERIC_WRITE | DELETE, dwShareMode, lpSecurityAttributes, dwCreationDisposition, dwFlagsAndAttributes, hTemplateFile);
|
||||||
|
if (handle == INVALID_HANDLE_VALUE)
|
||||||
|
return handle;
|
||||||
|
|
||||||
|
const auto lock = std::lock_guard(s_mtx);
|
||||||
|
s_handles.try_emplace(handle, std::move(temporaryPath), std::move(path));
|
||||||
|
|
||||||
|
return handle;
|
||||||
|
});
|
||||||
|
|
||||||
|
s_hookCloseHandle->set_detour([](HANDLE handle) {
|
||||||
|
const auto lock = std::lock_guard(s_mtx);
|
||||||
|
if (const auto it = s_handles.find(handle); it != s_handles.end()) {
|
||||||
|
std::filesystem::path tempPath(std::move(it->second.first));
|
||||||
|
std::filesystem::path finalPath(std::move(it->second.second));
|
||||||
|
s_handles.erase(it);
|
||||||
|
|
||||||
|
if (exists(finalPath)) {
|
||||||
|
std::filesystem::path oldPath = finalPath;
|
||||||
|
oldPath.replace_extension(finalPath.extension().wstring() + L".old");
|
||||||
|
try {
|
||||||
|
rename(finalPath, oldPath);
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
logging::E("{0} Failed to rename {1} to {2}: {3}",
|
||||||
|
LogTag,
|
||||||
|
unicode::convert<std::string>(finalPath.c_str()),
|
||||||
|
unicode::convert<std::string>(oldPath.c_str()),
|
||||||
|
e.what());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto pathwstr = finalPath.wstring();
|
||||||
|
std::vector<char> renameInfoBuf(sizeof(FILE_RENAME_INFO) + sizeof(wchar_t) * pathwstr.size() + 2);
|
||||||
|
auto& renameInfo = *reinterpret_cast<FILE_RENAME_INFO*>(&renameInfoBuf[0]);
|
||||||
|
renameInfo.ReplaceIfExists = true;
|
||||||
|
renameInfo.FileNameLength = static_cast<DWORD>(pathwstr.size() * 2);
|
||||||
|
memcpy(renameInfo.FileName, &pathwstr[0], renameInfo.FileNameLength);
|
||||||
|
if (!SetFileInformationByHandle(handle, FileRenameInfo, &renameInfoBuf[0], static_cast<DWORD>(renameInfoBuf.size()))) {
|
||||||
|
logging::E("{0} Failed to rename {1} to {2}: Win32 error {3}(0x{3})",
|
||||||
|
LogTag,
|
||||||
|
unicode::convert<std::string>(tempPath.c_str()),
|
||||||
|
unicode::convert<std::string>(finalPath.c_str()),
|
||||||
|
GetLastError());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return s_hookCloseHandle->call_original(handle);
|
||||||
|
});
|
||||||
|
|
||||||
|
logging::I("{} Enable", LogTag);
|
||||||
|
} else {
|
||||||
|
if (s_hookCreateFileW) {
|
||||||
|
logging::I("{} Disable OpenProcess", LogTag);
|
||||||
|
s_hookCreateFileW.reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void xivfixes::apply_all(bool bApply) {
|
void xivfixes::apply_all(bool bApply) {
|
||||||
for (const auto& [taskName, taskFunction] : std::initializer_list<std::pair<const char*, void(*)(bool)>>
|
for (const auto& [taskName, taskFunction] : std::initializer_list<std::pair<const char*, void(*)(bool)>>
|
||||||
{
|
{
|
||||||
|
|
@ -410,6 +500,7 @@ void xivfixes::apply_all(bool bApply) {
|
||||||
{ "prevent_devicechange_crashes", &prevent_devicechange_crashes },
|
{ "prevent_devicechange_crashes", &prevent_devicechange_crashes },
|
||||||
{ "disable_game_openprocess_access_check", &disable_game_openprocess_access_check },
|
{ "disable_game_openprocess_access_check", &disable_game_openprocess_access_check },
|
||||||
{ "redirect_openprocess", &redirect_openprocess },
|
{ "redirect_openprocess", &redirect_openprocess },
|
||||||
|
{ "backup_userdata_save", &backup_userdata_save },
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
try {
|
try {
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ namespace xivfixes {
|
||||||
void prevent_devicechange_crashes(bool bApply);
|
void prevent_devicechange_crashes(bool bApply);
|
||||||
void disable_game_openprocess_access_check(bool bApply);
|
void disable_game_openprocess_access_check(bool bApply);
|
||||||
void redirect_openprocess(bool bApply);
|
void redirect_openprocess(bool bApply);
|
||||||
|
void backup_userdata_save(bool bApply);
|
||||||
|
|
||||||
void apply_all(bool bApply);
|
void apply_all(bool bApply);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -315,7 +315,7 @@ namespace Dalamud.Injector
|
||||||
startInfo.BootShowConsole = args.Contains("--console");
|
startInfo.BootShowConsole = args.Contains("--console");
|
||||||
startInfo.BootEnableEtw = args.Contains("--etw");
|
startInfo.BootEnableEtw = args.Contains("--etw");
|
||||||
startInfo.BootLogPath = GetLogPath("dalamud.boot");
|
startInfo.BootLogPath = GetLogPath("dalamud.boot");
|
||||||
startInfo.BootEnabledGameFixes = new List<string> { "prevent_devicechange_crashes", "disable_game_openprocess_access_check", "redirect_openprocess" };
|
startInfo.BootEnabledGameFixes = new List<string> { "prevent_devicechange_crashes", "disable_game_openprocess_access_check", "redirect_openprocess", "backup_userdata_save" };
|
||||||
startInfo.BootDotnetOpenProcessHookMode = 0;
|
startInfo.BootDotnetOpenProcessHookMode = 0;
|
||||||
startInfo.BootWaitMessageBox |= args.Contains("--msgbox1") ? 1 : 0;
|
startInfo.BootWaitMessageBox |= args.Contains("--msgbox1") ? 1 : 0;
|
||||||
startInfo.BootWaitMessageBox |= args.Contains("--msgbox2") ? 2 : 0;
|
startInfo.BootWaitMessageBox |= args.Contains("--msgbox2") ? 2 : 0;
|
||||||
|
|
|
||||||
|
|
@ -190,6 +190,11 @@ namespace Dalamud.Configuration.Internal
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public LogEventLevel LogLevel { get; set; } = LogEventLevel.Information;
|
public LogEventLevel LogLevel { get; set; } = LogEventLevel.Information;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a value indicating whether to write to log files synchronously.
|
||||||
|
/// </summary>
|
||||||
|
public bool LogSynchronously { get; set; } = false;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets a value indicating whether or not the debug log should scroll automatically.
|
/// Gets or sets a value indicating whether or not the debug log should scroll automatically.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -261,6 +266,12 @@ namespace Dalamud.Configuration.Internal
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool PluginSafeMode { get; set; }
|
public bool PluginSafeMode { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a value indicating the wait time between plugin unload and plugin assembly unload.
|
||||||
|
/// Uses default value that may change between versions if set to null.
|
||||||
|
/// </summary>
|
||||||
|
public int? PluginWaitBeforeFree { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets a list of saved styles.
|
/// Gets or sets a list of saved styles.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
||||||
|
|
@ -5,21 +5,11 @@ using System.Runtime.CompilerServices;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Dalamud.Configuration.Internal;
|
using Dalamud.Configuration.Internal;
|
||||||
using Dalamud.Data;
|
|
||||||
using Dalamud.Game;
|
using Dalamud.Game;
|
||||||
using Dalamud.Game.ClientState;
|
|
||||||
using Dalamud.Game.Gui.Internal;
|
using Dalamud.Game.Gui.Internal;
|
||||||
using Dalamud.Game.Internal;
|
|
||||||
using Dalamud.Game.Network.Internal;
|
|
||||||
using Dalamud.Hooking.Internal;
|
|
||||||
using Dalamud.Interface.Internal;
|
using Dalamud.Interface.Internal;
|
||||||
using Dalamud.Logging.Internal;
|
|
||||||
using Dalamud.Plugin.Internal;
|
using Dalamud.Plugin.Internal;
|
||||||
using Dalamud.Support;
|
|
||||||
using Dalamud.Utility;
|
|
||||||
using Serilog;
|
using Serilog;
|
||||||
using Serilog.Core;
|
|
||||||
using Serilog.Events;
|
|
||||||
|
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
[assembly: InternalsVisibleTo("Dalamud.CorePlugin")]
|
[assembly: InternalsVisibleTo("Dalamud.CorePlugin")]
|
||||||
|
|
@ -33,13 +23,11 @@ namespace Dalamud
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The main Dalamud class containing all subsystems.
|
/// The main Dalamud class containing all subsystems.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal sealed class Dalamud : IDisposable, IServiceType
|
internal sealed class Dalamud : IServiceType
|
||||||
{
|
{
|
||||||
#region Internals
|
#region Internals
|
||||||
|
|
||||||
private readonly ManualResetEvent unloadSignal;
|
private readonly ManualResetEvent unloadSignal;
|
||||||
private readonly ManualResetEvent finishUnloadSignal;
|
|
||||||
private MonoMod.RuntimeDetour.Hook processMonoHook;
|
|
||||||
private bool hasDisposedPlugins = false;
|
private bool hasDisposedPlugins = false;
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
@ -48,22 +36,13 @@ namespace Dalamud
|
||||||
/// Initializes a new instance of the <see cref="Dalamud"/> class.
|
/// Initializes a new instance of the <see cref="Dalamud"/> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="info">DalamudStartInfo instance.</param>
|
/// <param name="info">DalamudStartInfo instance.</param>
|
||||||
/// <param name="loggingLevelSwitch">LoggingLevelSwitch to control Serilog level.</param>
|
|
||||||
/// <param name="finishSignal">Signal signalling shutdown.</param>
|
|
||||||
/// <param name="configuration">The Dalamud configuration.</param>
|
/// <param name="configuration">The Dalamud configuration.</param>
|
||||||
/// <param name="mainThreadContinueEvent">Event used to signal the main thread to continue.</param>
|
/// <param name="mainThreadContinueEvent">Event used to signal the main thread to continue.</param>
|
||||||
public Dalamud(DalamudStartInfo info, LoggingLevelSwitch loggingLevelSwitch, ManualResetEvent finishSignal, DalamudConfiguration configuration, IntPtr mainThreadContinueEvent)
|
public Dalamud(DalamudStartInfo info, DalamudConfiguration configuration, IntPtr mainThreadContinueEvent)
|
||||||
{
|
{
|
||||||
this.LogLevelSwitch = loggingLevelSwitch;
|
|
||||||
|
|
||||||
this.unloadSignal = new ManualResetEvent(false);
|
this.unloadSignal = new ManualResetEvent(false);
|
||||||
this.unloadSignal.Reset();
|
this.unloadSignal.Reset();
|
||||||
|
|
||||||
this.finishUnloadSignal = finishSignal;
|
|
||||||
this.finishUnloadSignal.Reset();
|
|
||||||
|
|
||||||
SerilogEventSink.Instance.LogLine += SerilogOnLogLine;
|
|
||||||
|
|
||||||
ServiceManager.InitializeProvidedServicesAndClientStructs(this, info, configuration);
|
ServiceManager.InitializeProvidedServicesAndClientStructs(this, info, configuration);
|
||||||
|
|
||||||
if (!configuration.IsResumeGameAfterPluginLoad)
|
if (!configuration.IsResumeGameAfterPluginLoad)
|
||||||
|
|
@ -111,11 +90,6 @@ namespace Dalamud
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets LoggingLevelSwitch for Dalamud and Plugin logs.
|
|
||||||
/// </summary>
|
|
||||||
internal LoggingLevelSwitch LogLevelSwitch { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets location of stored assets.
|
/// Gets location of stored assets.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -138,14 +112,6 @@ namespace Dalamud
|
||||||
this.unloadSignal.WaitOne();
|
this.unloadSignal.WaitOne();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Wait for a queued unload to be finalized.
|
|
||||||
/// </summary>
|
|
||||||
public void WaitForUnloadFinish()
|
|
||||||
{
|
|
||||||
this.finishUnloadSignal?.WaitOne();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Dispose subsystems related to plugin handling.
|
/// Dispose subsystems related to plugin handling.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -169,46 +135,6 @@ namespace Dalamud
|
||||||
Service<PluginManager>.GetNullable()?.Dispose();
|
Service<PluginManager>.GetNullable()?.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Dispose Dalamud subsystems.
|
|
||||||
/// </summary>
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (!this.hasDisposedPlugins)
|
|
||||||
{
|
|
||||||
this.DisposePlugins();
|
|
||||||
Thread.Sleep(100);
|
|
||||||
}
|
|
||||||
|
|
||||||
Service<Framework>.GetNullable()?.ExplicitDispose();
|
|
||||||
Service<ClientState>.GetNullable()?.ExplicitDispose();
|
|
||||||
|
|
||||||
this.unloadSignal?.Dispose();
|
|
||||||
|
|
||||||
Service<WinSockHandlers>.GetNullable()?.Dispose();
|
|
||||||
Service<DataManager>.GetNullable()?.ExplicitDispose();
|
|
||||||
Service<AntiDebug>.GetNullable()?.Dispose();
|
|
||||||
Service<DalamudAtkTweaks>.GetNullable()?.Dispose();
|
|
||||||
Service<HookManager>.GetNullable()?.Dispose();
|
|
||||||
|
|
||||||
var sigScanner = Service<SigScanner>.Get();
|
|
||||||
sigScanner.Save();
|
|
||||||
sigScanner.Dispose();
|
|
||||||
|
|
||||||
SerilogEventSink.Instance.LogLine -= SerilogOnLogLine;
|
|
||||||
|
|
||||||
this.processMonoHook?.Dispose();
|
|
||||||
|
|
||||||
Log.Debug("Dalamud::Dispose() OK!");
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Log.Error(ex, "Dalamud::Dispose() failed.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Replace the built-in exception handler with a debug one.
|
/// Replace the built-in exception handler with a debug one.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -221,13 +147,5 @@ namespace Dalamud
|
||||||
var oldFilter = NativeFunctions.SetUnhandledExceptionFilter(releaseFilter);
|
var oldFilter = NativeFunctions.SetUnhandledExceptionFilter(releaseFilter);
|
||||||
Log.Debug("Reset ExceptionFilter, old: {0}", oldFilter);
|
Log.Debug("Reset ExceptionFilter, old: {0}", oldFilter);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void SerilogOnLogLine(object? sender, (string Line, LogEventLevel Level, DateTimeOffset TimeStamp, Exception? Exception) e)
|
|
||||||
{
|
|
||||||
if (e.Exception == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
Troubleshooting.LogException(e.Exception, e.Line);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,11 @@ namespace Dalamud
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed class EntryPoint
|
public sealed class EntryPoint
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Log level switch for runtime log level change.
|
||||||
|
/// </summary>
|
||||||
|
public static readonly LoggingLevelSwitch LogLevelSwitch = new(LogEventLevel.Verbose);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A delegate used during initialization of the CLR from Dalamud.Boot.
|
/// A delegate used during initialization of the CLR from Dalamud.Boot.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -107,6 +112,49 @@ namespace Dalamud
|
||||||
msgThread.Join();
|
msgThread.Join();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets up logging.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="baseDirectory">Base directory.</param>
|
||||||
|
/// <param name="logConsole">Whether to log to console.</param>
|
||||||
|
/// <param name="logSynchronously">Log synchronously.</param>
|
||||||
|
internal static void InitLogging(string baseDirectory, bool logConsole, bool logSynchronously)
|
||||||
|
{
|
||||||
|
#if DEBUG
|
||||||
|
var logPath = Path.Combine(baseDirectory, "dalamud.log");
|
||||||
|
var oldPath = Path.Combine(baseDirectory, "dalamud.log.old");
|
||||||
|
#else
|
||||||
|
var logPath = Path.Combine(baseDirectory, "..", "..", "..", "dalamud.log");
|
||||||
|
var oldPath = Path.Combine(baseDirectory, "..", "..", "..", "dalamud.log.old");
|
||||||
|
#endif
|
||||||
|
Log.CloseAndFlush();
|
||||||
|
|
||||||
|
CullLogFile(logPath, oldPath, 1 * 1024 * 1024);
|
||||||
|
CullLogFile(oldPath, null, 10 * 1024 * 1024);
|
||||||
|
|
||||||
|
var config = new LoggerConfiguration()
|
||||||
|
.WriteTo.Sink(SerilogEventSink.Instance)
|
||||||
|
.MinimumLevel.ControlledBy(LogLevelSwitch);
|
||||||
|
|
||||||
|
if (logSynchronously)
|
||||||
|
{
|
||||||
|
config = config.WriteTo.File(logPath, fileSizeLimitBytes: null);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
config = config.WriteTo.Async(a => a.File(
|
||||||
|
logPath,
|
||||||
|
fileSizeLimitBytes: null,
|
||||||
|
buffered: false,
|
||||||
|
flushToDiskInterval: TimeSpan.FromSeconds(1)));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (logConsole)
|
||||||
|
config = config.WriteTo.Console();
|
||||||
|
|
||||||
|
Log.Logger = config.CreateLogger();
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initialize all Dalamud subsystems and start running on the main thread.
|
/// Initialize all Dalamud subsystems and start running on the main thread.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -115,22 +163,23 @@ namespace Dalamud
|
||||||
private static void RunThread(DalamudStartInfo info, IntPtr mainThreadContinueEvent)
|
private static void RunThread(DalamudStartInfo info, IntPtr mainThreadContinueEvent)
|
||||||
{
|
{
|
||||||
// Setup logger
|
// Setup logger
|
||||||
var levelSwitch = InitLogging(info.WorkingDirectory, info.BootShowConsole);
|
InitLogging(info.WorkingDirectory!, info.BootShowConsole, true);
|
||||||
|
SerilogEventSink.Instance.LogLine += SerilogOnLogLine;
|
||||||
|
|
||||||
// Load configuration first to get some early persistent state, like log level
|
// Load configuration first to get some early persistent state, like log level
|
||||||
var configuration = DalamudConfiguration.Load(info.ConfigurationPath);
|
var configuration = DalamudConfiguration.Load(info.ConfigurationPath!);
|
||||||
|
|
||||||
// Set the appropriate logging level from the configuration
|
// Set the appropriate logging level from the configuration
|
||||||
#if !DEBUG
|
#if !DEBUG
|
||||||
levelSwitch.MinimumLevel = configuration.LogLevel;
|
if (!configuration.LogSynchronously)
|
||||||
|
InitLogging(info.WorkingDirectory!, info.BootShowConsole, configuration.LogSynchronously);
|
||||||
|
LogLevelSwitch.MinimumLevel = configuration.LogLevel;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Log any unhandled exception.
|
// Log any unhandled exception.
|
||||||
AppDomain.CurrentDomain.UnhandledException += OnUnhandledException;
|
AppDomain.CurrentDomain.UnhandledException += OnUnhandledException;
|
||||||
TaskScheduler.UnobservedTaskException += OnUnobservedTaskException;
|
TaskScheduler.UnobservedTaskException += OnUnobservedTaskException;
|
||||||
|
|
||||||
var finishSignal = new ManualResetEvent(false);
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (info.DelayInitializeMs > 0)
|
if (info.DelayInitializeMs > 0)
|
||||||
|
|
@ -148,12 +197,12 @@ namespace Dalamud
|
||||||
if (!Util.IsLinux())
|
if (!Util.IsLinux())
|
||||||
InitSymbolHandler(info);
|
InitSymbolHandler(info);
|
||||||
|
|
||||||
var dalamud = new Dalamud(info, levelSwitch, finishSignal, configuration, mainThreadContinueEvent);
|
var dalamud = new Dalamud(info, configuration, mainThreadContinueEvent);
|
||||||
Log.Information("This is Dalamud - Core: {GitHash}, CS: {CsGitHash}", Util.GetGitHash(), Util.GetGitHashClientStructs());
|
Log.Information("This is Dalamud - Core: {GitHash}, CS: {CsGitHash}", Util.GetGitHash(), Util.GetGitHashClientStructs());
|
||||||
|
|
||||||
dalamud.WaitForUnload();
|
dalamud.WaitForUnload();
|
||||||
|
|
||||||
dalamud.Dispose();
|
ServiceManager.UnloadAllServices();
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
|
@ -166,11 +215,18 @@ namespace Dalamud
|
||||||
|
|
||||||
Log.Information("Session has ended.");
|
Log.Information("Session has ended.");
|
||||||
Log.CloseAndFlush();
|
Log.CloseAndFlush();
|
||||||
|
SerilogEventSink.Instance.LogLine -= SerilogOnLogLine;
|
||||||
finishSignal.Set();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void SerilogOnLogLine(object? sender, (string Line, LogEventLevel Level, DateTimeOffset TimeStamp, Exception? Exception) e)
|
||||||
|
{
|
||||||
|
if (e.Exception == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
Troubleshooting.LogException(e.Exception, e.Line);
|
||||||
|
}
|
||||||
|
|
||||||
private static void InitSymbolHandler(DalamudStartInfo info)
|
private static void InitSymbolHandler(DalamudStartInfo info)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
|
|
@ -193,33 +249,6 @@ namespace Dalamud
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static LoggingLevelSwitch InitLogging(string baseDirectory, bool logConsole)
|
|
||||||
{
|
|
||||||
#if DEBUG
|
|
||||||
var logPath = Path.Combine(baseDirectory, "dalamud.log");
|
|
||||||
var oldPath = Path.Combine(baseDirectory, "dalamud.log.old");
|
|
||||||
#else
|
|
||||||
var logPath = Path.Combine(baseDirectory, "..", "..", "..", "dalamud.log");
|
|
||||||
var oldPath = Path.Combine(baseDirectory, "..", "..", "..", "dalamud.log.old");
|
|
||||||
#endif
|
|
||||||
|
|
||||||
CullLogFile(logPath, oldPath, 1 * 1024 * 1024);
|
|
||||||
CullLogFile(oldPath, null, 10 * 1024 * 1024);
|
|
||||||
|
|
||||||
var levelSwitch = new LoggingLevelSwitch(LogEventLevel.Verbose);
|
|
||||||
var config = new LoggerConfiguration()
|
|
||||||
.WriteTo.Async(a => a.File(logPath, fileSizeLimitBytes: null, buffered: false, flushToDiskInterval: TimeSpan.FromSeconds(1)))
|
|
||||||
.WriteTo.Sink(SerilogEventSink.Instance)
|
|
||||||
.MinimumLevel.ControlledBy(levelSwitch);
|
|
||||||
|
|
||||||
if (logConsole)
|
|
||||||
config = config.WriteTo.Console();
|
|
||||||
|
|
||||||
Log.Logger = config.CreateLogger();
|
|
||||||
|
|
||||||
return levelSwitch;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void CullLogFile(string logPath, string? oldPath, int cullingFileSize)
|
private static void CullLogFile(string logPath, string? oldPath, int cullingFileSize)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
|
|
|
||||||
|
|
@ -107,6 +107,9 @@ namespace Dalamud.Game
|
||||||
|
|
||||||
private readonly DalamudLinkPayload openInstallerWindowLink;
|
private readonly DalamudLinkPayload openInstallerWindowLink;
|
||||||
|
|
||||||
|
[ServiceManager.ServiceDependency]
|
||||||
|
private readonly DalamudConfiguration configuration = Service<DalamudConfiguration>.Get();
|
||||||
|
|
||||||
private bool hasSeenLoadingMsg;
|
private bool hasSeenLoadingMsg;
|
||||||
private bool hasAutoUpdatedPlugins;
|
private bool hasAutoUpdatedPlugins;
|
||||||
|
|
||||||
|
|
@ -118,7 +121,7 @@ namespace Dalamud.Game
|
||||||
|
|
||||||
this.openInstallerWindowLink = chatGui.AddChatLinkHandler("Dalamud", 1001, (i, m) =>
|
this.openInstallerWindowLink = chatGui.AddChatLinkHandler("Dalamud", 1001, (i, m) =>
|
||||||
{
|
{
|
||||||
Service<DalamudInterface>.Get().OpenPluginInstaller();
|
Service<DalamudInterface>.GetNullable()?.OpenPluginInstaller();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -145,11 +148,9 @@ namespace Dalamud.Game
|
||||||
|
|
||||||
private void OnCheckMessageHandled(XivChatType type, uint senderid, ref SeString sender, ref SeString message, ref bool isHandled)
|
private void OnCheckMessageHandled(XivChatType type, uint senderid, ref SeString sender, ref SeString message, ref bool isHandled)
|
||||||
{
|
{
|
||||||
var configuration = Service<DalamudConfiguration>.Get();
|
|
||||||
|
|
||||||
var textVal = message.TextValue;
|
var textVal = message.TextValue;
|
||||||
|
|
||||||
if (!configuration.DisableRmtFiltering)
|
if (!this.configuration.DisableRmtFiltering)
|
||||||
{
|
{
|
||||||
var matched = this.rmtRegex.IsMatch(textVal);
|
var matched = this.rmtRegex.IsMatch(textVal);
|
||||||
if (matched)
|
if (matched)
|
||||||
|
|
@ -161,8 +162,8 @@ namespace Dalamud.Game
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (configuration.BadWords != null &&
|
if (this.configuration.BadWords != null &&
|
||||||
configuration.BadWords.Any(x => !string.IsNullOrEmpty(x) && textVal.Contains(x)))
|
this.configuration.BadWords.Any(x => !string.IsNullOrEmpty(x) && textVal.Contains(x)))
|
||||||
{
|
{
|
||||||
// This seems to be in the user block list - let's not show it
|
// This seems to be in the user block list - let's not show it
|
||||||
Log.Debug("Blocklist triggered");
|
Log.Debug("Blocklist triggered");
|
||||||
|
|
@ -174,7 +175,9 @@ namespace Dalamud.Game
|
||||||
private void OnChatMessage(XivChatType type, uint senderId, ref SeString sender, ref SeString message, ref bool isHandled)
|
private void OnChatMessage(XivChatType type, uint senderId, ref SeString sender, ref SeString message, ref bool isHandled)
|
||||||
{
|
{
|
||||||
var startInfo = Service<DalamudStartInfo>.Get();
|
var startInfo = Service<DalamudStartInfo>.Get();
|
||||||
var clientState = Service<ClientState.ClientState>.Get();
|
var clientState = Service<ClientState.ClientState>.GetNullable();
|
||||||
|
if (clientState == null)
|
||||||
|
return;
|
||||||
|
|
||||||
if (type == XivChatType.Notice && !this.hasSeenLoadingMsg)
|
if (type == XivChatType.Notice && !this.hasSeenLoadingMsg)
|
||||||
this.PrintWelcomeMessage();
|
this.PrintWelcomeMessage();
|
||||||
|
|
@ -232,17 +235,19 @@ namespace Dalamud.Game
|
||||||
|
|
||||||
private void PrintWelcomeMessage()
|
private void PrintWelcomeMessage()
|
||||||
{
|
{
|
||||||
var chatGui = Service<ChatGui>.Get();
|
var chatGui = Service<ChatGui>.GetNullable();
|
||||||
var configuration = Service<DalamudConfiguration>.Get();
|
var pluginManager = Service<PluginManager>.GetNullable();
|
||||||
var pluginManager = Service<PluginManager>.Get();
|
var dalamudInterface = Service<DalamudInterface>.GetNullable();
|
||||||
var dalamudInterface = Service<DalamudInterface>.Get();
|
|
||||||
|
if (chatGui == null || pluginManager == null || dalamudInterface == null)
|
||||||
|
return;
|
||||||
|
|
||||||
var assemblyVersion = Assembly.GetAssembly(typeof(ChatHandlers)).GetName().Version.ToString();
|
var assemblyVersion = Assembly.GetAssembly(typeof(ChatHandlers)).GetName().Version.ToString();
|
||||||
|
|
||||||
chatGui.Print(string.Format(Loc.Localize("DalamudWelcome", "Dalamud vD{0} loaded."), assemblyVersion)
|
chatGui.Print(string.Format(Loc.Localize("DalamudWelcome", "Dalamud vD{0} loaded."), assemblyVersion)
|
||||||
+ string.Format(Loc.Localize("PluginsWelcome", " {0} plugin(s) loaded."), pluginManager.InstalledPlugins.Count(x => x.IsLoaded)));
|
+ string.Format(Loc.Localize("PluginsWelcome", " {0} plugin(s) loaded."), pluginManager.InstalledPlugins.Count(x => x.IsLoaded)));
|
||||||
|
|
||||||
if (configuration.PrintPluginsWelcomeMsg)
|
if (this.configuration.PrintPluginsWelcomeMsg)
|
||||||
{
|
{
|
||||||
foreach (var plugin in pluginManager.InstalledPlugins.OrderBy(plugin => plugin.Name).Where(x => x.IsLoaded))
|
foreach (var plugin in pluginManager.InstalledPlugins.OrderBy(plugin => plugin.Name).Where(x => x.IsLoaded))
|
||||||
{
|
{
|
||||||
|
|
@ -250,7 +255,7 @@ namespace Dalamud.Game
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(configuration.LastVersion) || !assemblyVersion.StartsWith(configuration.LastVersion))
|
if (string.IsNullOrEmpty(this.configuration.LastVersion) || !assemblyVersion.StartsWith(this.configuration.LastVersion))
|
||||||
{
|
{
|
||||||
chatGui.PrintChat(new XivChatEntry
|
chatGui.PrintChat(new XivChatEntry
|
||||||
{
|
{
|
||||||
|
|
@ -258,14 +263,14 @@ namespace Dalamud.Game
|
||||||
Type = XivChatType.Notice,
|
Type = XivChatType.Notice,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(configuration.LastChangelogMajorMinor) || (!ChangelogWindow.WarrantsChangelogForMajorMinor.StartsWith(configuration.LastChangelogMajorMinor) && assemblyVersion.StartsWith(ChangelogWindow.WarrantsChangelogForMajorMinor)))
|
if (string.IsNullOrEmpty(this.configuration.LastChangelogMajorMinor) || (!ChangelogWindow.WarrantsChangelogForMajorMinor.StartsWith(this.configuration.LastChangelogMajorMinor) && assemblyVersion.StartsWith(ChangelogWindow.WarrantsChangelogForMajorMinor)))
|
||||||
{
|
{
|
||||||
dalamudInterface.OpenChangelogWindow();
|
dalamudInterface.OpenChangelogWindow();
|
||||||
configuration.LastChangelogMajorMinor = ChangelogWindow.WarrantsChangelogForMajorMinor;
|
this.configuration.LastChangelogMajorMinor = ChangelogWindow.WarrantsChangelogForMajorMinor;
|
||||||
}
|
}
|
||||||
|
|
||||||
configuration.LastVersion = assemblyVersion;
|
this.configuration.LastVersion = assemblyVersion;
|
||||||
configuration.Save();
|
this.configuration.Save();
|
||||||
}
|
}
|
||||||
|
|
||||||
this.hasSeenLoadingMsg = true;
|
this.hasSeenLoadingMsg = true;
|
||||||
|
|
@ -273,10 +278,12 @@ namespace Dalamud.Game
|
||||||
|
|
||||||
private void AutoUpdatePlugins()
|
private void AutoUpdatePlugins()
|
||||||
{
|
{
|
||||||
var chatGui = Service<ChatGui>.Get();
|
var chatGui = Service<ChatGui>.GetNullable();
|
||||||
var configuration = Service<DalamudConfiguration>.Get();
|
var pluginManager = Service<PluginManager>.GetNullable();
|
||||||
var pluginManager = Service<PluginManager>.Get();
|
var notifications = Service<NotificationManager>.GetNullable();
|
||||||
var notifications = Service<NotificationManager>.Get();
|
|
||||||
|
if (chatGui == null || pluginManager == null || notifications == null)
|
||||||
|
return;
|
||||||
|
|
||||||
if (!pluginManager.ReposReady || pluginManager.InstalledPlugins.Count == 0 || pluginManager.AvailablePlugins.Count == 0)
|
if (!pluginManager.ReposReady || pluginManager.InstalledPlugins.Count == 0 || pluginManager.AvailablePlugins.Count == 0)
|
||||||
{
|
{
|
||||||
|
|
@ -286,7 +293,7 @@ namespace Dalamud.Game
|
||||||
|
|
||||||
this.hasAutoUpdatedPlugins = true;
|
this.hasAutoUpdatedPlugins = true;
|
||||||
|
|
||||||
Task.Run(() => pluginManager.UpdatePluginsAsync(!configuration.AutoUpdatePlugins)).ContinueWith(task =>
|
Task.Run(() => pluginManager.UpdatePluginsAsync(!this.configuration.AutoUpdatePlugins)).ContinueWith(task =>
|
||||||
{
|
{
|
||||||
if (task.IsFaulted)
|
if (task.IsFaulted)
|
||||||
{
|
{
|
||||||
|
|
@ -297,15 +304,13 @@ namespace Dalamud.Game
|
||||||
var updatedPlugins = task.Result;
|
var updatedPlugins = task.Result;
|
||||||
if (updatedPlugins != null && updatedPlugins.Any())
|
if (updatedPlugins != null && updatedPlugins.Any())
|
||||||
{
|
{
|
||||||
if (configuration.AutoUpdatePlugins)
|
if (this.configuration.AutoUpdatePlugins)
|
||||||
{
|
{
|
||||||
PluginManager.PrintUpdatedPlugins(updatedPlugins, Loc.Localize("DalamudPluginAutoUpdate", "Auto-update:"));
|
PluginManager.PrintUpdatedPlugins(updatedPlugins, Loc.Localize("DalamudPluginAutoUpdate", "Auto-update:"));
|
||||||
notifications.AddNotification(Loc.Localize("NotificationUpdatedPlugins", "{0} of your plugins were updated.").Format(updatedPlugins.Count), Loc.Localize("NotificationAutoUpdate", "Auto-Update"), NotificationType.Info);
|
notifications.AddNotification(Loc.Localize("NotificationUpdatedPlugins", "{0} of your plugins were updated.").Format(updatedPlugins.Count), Loc.Localize("NotificationAutoUpdate", "Auto-Update"), NotificationType.Info);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var data = Service<DataManager>.Get();
|
|
||||||
|
|
||||||
chatGui.PrintChat(new XivChatEntry
|
chatGui.PrintChat(new XivChatEntry
|
||||||
{
|
{
|
||||||
Message = new SeString(new List<Payload>()
|
Message = new SeString(new List<Payload>()
|
||||||
|
|
|
||||||
|
|
@ -20,12 +20,15 @@ namespace Dalamud.Game.ClientState.Buddy
|
||||||
{
|
{
|
||||||
private const uint InvalidObjectID = 0xE0000000;
|
private const uint InvalidObjectID = 0xE0000000;
|
||||||
|
|
||||||
|
[ServiceManager.ServiceDependency]
|
||||||
|
private readonly ClientState clientState = Service<ClientState>.Get();
|
||||||
|
|
||||||
private readonly ClientStateAddressResolver address;
|
private readonly ClientStateAddressResolver address;
|
||||||
|
|
||||||
[ServiceManager.ServiceConstructor]
|
[ServiceManager.ServiceConstructor]
|
||||||
private BuddyList(ClientState clientState)
|
private BuddyList()
|
||||||
{
|
{
|
||||||
this.address = clientState.AddressResolver;
|
this.address = this.clientState.AddressResolver;
|
||||||
|
|
||||||
Log.Verbose($"Buddy list address 0x{this.address.BuddyList.ToInt64():X}");
|
Log.Verbose($"Buddy list address 0x{this.address.BuddyList.ToInt64():X}");
|
||||||
}
|
}
|
||||||
|
|
@ -145,9 +148,7 @@ namespace Dalamud.Game.ClientState.Buddy
|
||||||
/// <returns><see cref="BuddyMember"/> object containing the requested data.</returns>
|
/// <returns><see cref="BuddyMember"/> object containing the requested data.</returns>
|
||||||
public BuddyMember? CreateBuddyMemberReference(IntPtr address)
|
public BuddyMember? CreateBuddyMemberReference(IntPtr address)
|
||||||
{
|
{
|
||||||
var clientState = Service<ClientState>.Get();
|
if (this.clientState.LocalContentId == 0)
|
||||||
|
|
||||||
if (clientState.LocalContentId == 0)
|
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
if (address == IntPtr.Zero)
|
if (address == IntPtr.Zero)
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,9 @@ namespace Dalamud.Game.ClientState.Buddy
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public unsafe class BuddyMember
|
public unsafe class BuddyMember
|
||||||
{
|
{
|
||||||
|
[ServiceManager.ServiceDependency]
|
||||||
|
private readonly ObjectTable objectTable = Service<ObjectTable>.Get();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="BuddyMember"/> class.
|
/// Initializes a new instance of the <see cref="BuddyMember"/> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -36,7 +39,7 @@ namespace Dalamud.Game.ClientState.Buddy
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// This iterates the actor table, it should be used with care.
|
/// This iterates the actor table, it should be used with care.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
public GameObject? GameObject => Service<ObjectTable>.Get().SearchById(this.ObjectId);
|
public GameObject? GameObject => this.objectTable.SearchById(this.ObjectId);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the current health of this buddy.
|
/// Gets the current health of this buddy.
|
||||||
|
|
|
||||||
|
|
@ -1,22 +1,15 @@
|
||||||
using System;
|
using System;
|
||||||
|
using System.Linq;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
using Dalamud.Data;
|
using Dalamud.Data;
|
||||||
using Dalamud.Game.ClientState.Aetherytes;
|
|
||||||
using Dalamud.Game.ClientState.Buddy;
|
|
||||||
using Dalamud.Game.ClientState.Fates;
|
|
||||||
using Dalamud.Game.ClientState.GamePad;
|
|
||||||
using Dalamud.Game.ClientState.JobGauge;
|
|
||||||
using Dalamud.Game.ClientState.Keys;
|
|
||||||
using Dalamud.Game.ClientState.Objects;
|
using Dalamud.Game.ClientState.Objects;
|
||||||
using Dalamud.Game.ClientState.Objects.SubKinds;
|
using Dalamud.Game.ClientState.Objects.SubKinds;
|
||||||
using Dalamud.Game.ClientState.Party;
|
|
||||||
using Dalamud.Game.Gui;
|
using Dalamud.Game.Gui;
|
||||||
using Dalamud.Game.Network.Internal;
|
using Dalamud.Game.Network.Internal;
|
||||||
using Dalamud.Hooking;
|
using Dalamud.Hooking;
|
||||||
using Dalamud.IoC;
|
using Dalamud.IoC;
|
||||||
using Dalamud.IoC.Internal;
|
using Dalamud.IoC.Internal;
|
||||||
using Dalamud.Utility;
|
|
||||||
using FFXIVClientStructs.FFXIV.Client.Game;
|
using FFXIVClientStructs.FFXIV.Client.Game;
|
||||||
using Serilog;
|
using Serilog;
|
||||||
|
|
||||||
|
|
@ -33,6 +26,12 @@ namespace Dalamud.Game.ClientState
|
||||||
private readonly ClientStateAddressResolver address;
|
private readonly ClientStateAddressResolver address;
|
||||||
private readonly Hook<SetupTerritoryTypeDelegate> setupTerritoryTypeHook;
|
private readonly Hook<SetupTerritoryTypeDelegate> setupTerritoryTypeHook;
|
||||||
|
|
||||||
|
[ServiceManager.ServiceDependency]
|
||||||
|
private readonly Framework framework = Service<Framework>.Get();
|
||||||
|
|
||||||
|
[ServiceManager.ServiceDependency]
|
||||||
|
private readonly NetworkHandlers networkHandlers = Service<NetworkHandlers>.Get();
|
||||||
|
|
||||||
private bool lastConditionNone = true;
|
private bool lastConditionNone = true;
|
||||||
private bool lastFramePvP = false;
|
private bool lastFramePvP = false;
|
||||||
|
|
||||||
|
|
@ -42,7 +41,7 @@ namespace Dalamud.Game.ClientState
|
||||||
internal ClientStateAddressResolver AddressResolver => this.address;
|
internal ClientStateAddressResolver AddressResolver => this.address;
|
||||||
|
|
||||||
[ServiceManager.ServiceConstructor]
|
[ServiceManager.ServiceConstructor]
|
||||||
private ClientState(Framework framework, NetworkHandlers networkHandlers, SigScanner sigScanner, DalamudStartInfo startInfo)
|
private ClientState(SigScanner sigScanner, DalamudStartInfo startInfo)
|
||||||
{
|
{
|
||||||
this.address = new ClientStateAddressResolver();
|
this.address = new ClientStateAddressResolver();
|
||||||
this.address.Setup(sigScanner);
|
this.address.Setup(sigScanner);
|
||||||
|
|
@ -53,11 +52,11 @@ namespace Dalamud.Game.ClientState
|
||||||
|
|
||||||
Log.Verbose($"SetupTerritoryType address 0x{this.address.SetupTerritoryType.ToInt64():X}");
|
Log.Verbose($"SetupTerritoryType address 0x{this.address.SetupTerritoryType.ToInt64():X}");
|
||||||
|
|
||||||
this.setupTerritoryTypeHook = new Hook<SetupTerritoryTypeDelegate>(this.address.SetupTerritoryType, this.SetupTerritoryTypeDetour);
|
this.setupTerritoryTypeHook = Hook<SetupTerritoryTypeDelegate>.FromAddress(this.address.SetupTerritoryType, this.SetupTerritoryTypeDetour);
|
||||||
|
|
||||||
framework.Update += this.FrameworkOnOnUpdateEvent;
|
this.framework.Update += this.FrameworkOnOnUpdateEvent;
|
||||||
|
|
||||||
networkHandlers.CfPop += this.NetworkHandlersOnCfPop;
|
this.networkHandlers.CfPop += this.NetworkHandlersOnCfPop;
|
||||||
}
|
}
|
||||||
|
|
||||||
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
|
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
|
||||||
|
|
@ -106,7 +105,7 @@ namespace Dalamud.Game.ClientState
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the local player character, if one is present.
|
/// Gets the local player character, if one is present.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public PlayerCharacter? LocalPlayer => Service<ObjectTable>.Get()[0] as PlayerCharacter;
|
public PlayerCharacter? LocalPlayer => Service<ObjectTable>.GetNullable()?.FirstOrDefault() as PlayerCharacter;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the content ID of the local character.
|
/// Gets the content ID of the local character.
|
||||||
|
|
@ -134,10 +133,8 @@ namespace Dalamud.Game.ClientState
|
||||||
void IDisposable.Dispose()
|
void IDisposable.Dispose()
|
||||||
{
|
{
|
||||||
this.setupTerritoryTypeHook.Dispose();
|
this.setupTerritoryTypeHook.Dispose();
|
||||||
Service<Conditions.Condition>.Get().ExplicitDispose();
|
this.framework.Update -= this.FrameworkOnOnUpdateEvent;
|
||||||
Service<GamepadState>.Get().ExplicitDispose();
|
this.networkHandlers.CfPop -= this.NetworkHandlersOnCfPop;
|
||||||
Service<Framework>.Get().Update -= this.FrameworkOnOnUpdateEvent;
|
|
||||||
Service<NetworkHandlers>.Get().CfPop -= this.NetworkHandlersOnCfPop;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[ServiceManager.CallWhenServicesReady]
|
[ServiceManager.CallWhenServicesReady]
|
||||||
|
|
@ -161,11 +158,14 @@ namespace Dalamud.Game.ClientState
|
||||||
this.CfPop?.Invoke(this, e);
|
this.CfPop?.Invoke(this, e);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void FrameworkOnOnUpdateEvent(Framework framework)
|
private void FrameworkOnOnUpdateEvent(Framework framework1)
|
||||||
{
|
{
|
||||||
var condition = Service<Conditions.Condition>.Get();
|
var condition = Service<Conditions.Condition>.GetNullable();
|
||||||
var gameGui = Service<GameGui>.Get();
|
var gameGui = Service<GameGui>.GetNullable();
|
||||||
var data = Service<DataManager>.Get();
|
var data = Service<DataManager>.GetNullable();
|
||||||
|
|
||||||
|
if (condition == null || gameGui == null || data == null)
|
||||||
|
return;
|
||||||
|
|
||||||
if (condition.Any() && this.lastConditionNone == true)
|
if (condition.Any() && this.lastConditionNone == true)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -46,9 +46,9 @@ namespace Dalamud.Game.ClientState.Fates
|
||||||
/// <returns>True or false.</returns>
|
/// <returns>True or false.</returns>
|
||||||
public static bool IsValid(Fate fate)
|
public static bool IsValid(Fate fate)
|
||||||
{
|
{
|
||||||
var clientState = Service<ClientState>.Get();
|
var clientState = Service<ClientState>.GetNullable();
|
||||||
|
|
||||||
if (fate == null)
|
if (fate == null || clientState == null)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if (clientState.LocalContentId == 0)
|
if (clientState.LocalContentId == 0)
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,7 @@ namespace Dalamud.Game.ClientState.GamePad
|
||||||
{
|
{
|
||||||
var resolver = clientState.AddressResolver;
|
var resolver = clientState.AddressResolver;
|
||||||
Log.Verbose($"GamepadPoll address 0x{resolver.GamepadPoll.ToInt64():X}");
|
Log.Verbose($"GamepadPoll address 0x{resolver.GamepadPoll.ToInt64():X}");
|
||||||
this.gamepadPoll = new Hook<ControllerPoll>(resolver.GamepadPoll, this.GamepadPollDetour);
|
this.gamepadPoll = Hook<ControllerPoll>.FromAddress(resolver.GamepadPoll, this.GamepadPollDetour);
|
||||||
}
|
}
|
||||||
|
|
||||||
private delegate int ControllerPoll(IntPtr controllerInput);
|
private delegate int ControllerPoll(IntPtr controllerInput);
|
||||||
|
|
|
||||||
|
|
@ -97,9 +97,9 @@ namespace Dalamud.Game.ClientState.Objects
|
||||||
/// <returns><see cref="GameObject"/> object or inheritor containing the requested data.</returns>
|
/// <returns><see cref="GameObject"/> object or inheritor containing the requested data.</returns>
|
||||||
public unsafe GameObject? CreateObjectReference(IntPtr address)
|
public unsafe GameObject? CreateObjectReference(IntPtr address)
|
||||||
{
|
{
|
||||||
var clientState = Service<ClientState>.Get();
|
var clientState = Service<ClientState>.GetNullable();
|
||||||
|
|
||||||
if (clientState.LocalContentId == 0)
|
if (clientState == null || clientState.LocalContentId == 0)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
if (address == IntPtr.Zero)
|
if (address == IntPtr.Zero)
|
||||||
|
|
|
||||||
|
|
@ -14,12 +14,18 @@ namespace Dalamud.Game.ClientState.Objects
|
||||||
[ServiceManager.BlockingEarlyLoadedService]
|
[ServiceManager.BlockingEarlyLoadedService]
|
||||||
public sealed unsafe class TargetManager : IServiceType
|
public sealed unsafe class TargetManager : IServiceType
|
||||||
{
|
{
|
||||||
|
[ServiceManager.ServiceDependency]
|
||||||
|
private readonly ClientState clientState = Service<ClientState>.Get();
|
||||||
|
|
||||||
|
[ServiceManager.ServiceDependency]
|
||||||
|
private readonly ObjectTable objectTable = Service<ObjectTable>.Get();
|
||||||
|
|
||||||
private readonly ClientStateAddressResolver address;
|
private readonly ClientStateAddressResolver address;
|
||||||
|
|
||||||
[ServiceManager.ServiceConstructor]
|
[ServiceManager.ServiceConstructor]
|
||||||
private TargetManager(ClientState clientState)
|
private TargetManager()
|
||||||
{
|
{
|
||||||
this.address = clientState.AddressResolver;
|
this.address = this.clientState.AddressResolver;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -32,7 +38,7 @@ namespace Dalamud.Game.ClientState.Objects
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public GameObject? Target
|
public GameObject? Target
|
||||||
{
|
{
|
||||||
get => Service<ObjectTable>.Get().CreateObjectReference((IntPtr)Struct->Target);
|
get => this.objectTable.CreateObjectReference((IntPtr)Struct->Target);
|
||||||
set => this.SetTarget(value);
|
set => this.SetTarget(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -41,7 +47,7 @@ namespace Dalamud.Game.ClientState.Objects
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public GameObject? MouseOverTarget
|
public GameObject? MouseOverTarget
|
||||||
{
|
{
|
||||||
get => Service<ObjectTable>.Get().CreateObjectReference((IntPtr)Struct->MouseOverTarget);
|
get => this.objectTable.CreateObjectReference((IntPtr)Struct->MouseOverTarget);
|
||||||
set => this.SetMouseOverTarget(value);
|
set => this.SetMouseOverTarget(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -50,7 +56,7 @@ namespace Dalamud.Game.ClientState.Objects
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public GameObject? FocusTarget
|
public GameObject? FocusTarget
|
||||||
{
|
{
|
||||||
get => Service<ObjectTable>.Get().CreateObjectReference((IntPtr)Struct->FocusTarget);
|
get => this.objectTable.CreateObjectReference((IntPtr)Struct->FocusTarget);
|
||||||
set => this.SetFocusTarget(value);
|
set => this.SetFocusTarget(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -59,7 +65,7 @@ namespace Dalamud.Game.ClientState.Objects
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public GameObject? PreviousTarget
|
public GameObject? PreviousTarget
|
||||||
{
|
{
|
||||||
get => Service<ObjectTable>.Get().CreateObjectReference((IntPtr)Struct->PreviousTarget);
|
get => this.objectTable.CreateObjectReference((IntPtr)Struct->PreviousTarget);
|
||||||
set => this.SetPreviousTarget(value);
|
set => this.SetPreviousTarget(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -68,7 +74,7 @@ namespace Dalamud.Game.ClientState.Objects
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public GameObject? SoftTarget
|
public GameObject? SoftTarget
|
||||||
{
|
{
|
||||||
get => Service<ObjectTable>.Get().CreateObjectReference((IntPtr)Struct->SoftTarget);
|
get => this.objectTable.CreateObjectReference((IntPtr)Struct->SoftTarget);
|
||||||
set => this.SetSoftTarget(value);
|
set => this.SetSoftTarget(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -61,9 +61,9 @@ namespace Dalamud.Game.ClientState.Objects.Types
|
||||||
/// <returns>True or false.</returns>
|
/// <returns>True or false.</returns>
|
||||||
public static bool IsValid(GameObject? actor)
|
public static bool IsValid(GameObject? actor)
|
||||||
{
|
{
|
||||||
var clientState = Service<ClientState>.Get();
|
var clientState = Service<ClientState>.GetNullable();
|
||||||
|
|
||||||
if (actor is null)
|
if (actor is null || clientState == null)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if (clientState.LocalContentId == 0)
|
if (clientState.LocalContentId == 0)
|
||||||
|
|
|
||||||
|
|
@ -20,12 +20,15 @@ namespace Dalamud.Game.ClientState.Party
|
||||||
private const int GroupLength = 8;
|
private const int GroupLength = 8;
|
||||||
private const int AllianceLength = 20;
|
private const int AllianceLength = 20;
|
||||||
|
|
||||||
|
[ServiceManager.ServiceDependency]
|
||||||
|
private readonly ClientState clientState = Service<ClientState>.Get();
|
||||||
|
|
||||||
private readonly ClientStateAddressResolver address;
|
private readonly ClientStateAddressResolver address;
|
||||||
|
|
||||||
[ServiceManager.ServiceConstructor]
|
[ServiceManager.ServiceConstructor]
|
||||||
private PartyList(ClientState clientState)
|
private PartyList()
|
||||||
{
|
{
|
||||||
this.address = clientState.AddressResolver;
|
this.address = this.clientState.AddressResolver;
|
||||||
|
|
||||||
Log.Verbose($"Group manager address 0x{this.address.GroupManager.ToInt64():X}");
|
Log.Verbose($"Group manager address 0x{this.address.GroupManager.ToInt64():X}");
|
||||||
}
|
}
|
||||||
|
|
@ -115,9 +118,7 @@ namespace Dalamud.Game.ClientState.Party
|
||||||
/// <returns>The party member object containing the requested data.</returns>
|
/// <returns>The party member object containing the requested data.</returns>
|
||||||
public PartyMember? CreatePartyMemberReference(IntPtr address)
|
public PartyMember? CreatePartyMemberReference(IntPtr address)
|
||||||
{
|
{
|
||||||
var clientState = Service<ClientState>.Get();
|
if (this.clientState.LocalContentId == 0)
|
||||||
|
|
||||||
if (clientState.LocalContentId == 0)
|
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
if (address == IntPtr.Zero)
|
if (address == IntPtr.Zero)
|
||||||
|
|
@ -146,9 +147,7 @@ namespace Dalamud.Game.ClientState.Party
|
||||||
/// <returns>The party member object containing the requested data.</returns>
|
/// <returns>The party member object containing the requested data.</returns>
|
||||||
public PartyMember? CreateAllianceMemberReference(IntPtr address)
|
public PartyMember? CreateAllianceMemberReference(IntPtr address)
|
||||||
{
|
{
|
||||||
var clientState = Service<ClientState>.Get();
|
if (this.clientState.LocalContentId == 0)
|
||||||
|
|
||||||
if (clientState.LocalContentId == 0)
|
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
if (address == IntPtr.Zero)
|
if (address == IntPtr.Zero)
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ namespace Dalamud.Game.Command
|
||||||
[PluginInterface]
|
[PluginInterface]
|
||||||
[InterfaceVersion("1.0")]
|
[InterfaceVersion("1.0")]
|
||||||
[ServiceManager.BlockingEarlyLoadedService]
|
[ServiceManager.BlockingEarlyLoadedService]
|
||||||
public sealed class CommandManager : IServiceType
|
public sealed class CommandManager : IServiceType, IDisposable
|
||||||
{
|
{
|
||||||
private readonly Dictionary<string, CommandInfo> commandMap = new();
|
private readonly Dictionary<string, CommandInfo> commandMap = new();
|
||||||
private readonly Regex commandRegexEn = new(@"^The command (?<command>.+) does not exist\.$", RegexOptions.Compiled);
|
private readonly Regex commandRegexEn = new(@"^The command (?<command>.+) does not exist\.$", RegexOptions.Compiled);
|
||||||
|
|
@ -28,6 +28,9 @@ namespace Dalamud.Game.Command
|
||||||
private readonly Regex commandRegexCn = new(@"^^(“|「)(?<command>.+)(”|」)(出现问题:该命令不存在|出現問題:該命令不存在)。$", RegexOptions.Compiled);
|
private readonly Regex commandRegexCn = new(@"^^(“|「)(?<command>.+)(”|」)(出现问题:该命令不存在|出現問題:該命令不存在)。$", RegexOptions.Compiled);
|
||||||
private readonly Regex currentLangCommandRegex;
|
private readonly Regex currentLangCommandRegex;
|
||||||
|
|
||||||
|
[ServiceManager.ServiceDependency]
|
||||||
|
private readonly ChatGui chatGui = Service<ChatGui>.Get();
|
||||||
|
|
||||||
[ServiceManager.ServiceConstructor]
|
[ServiceManager.ServiceConstructor]
|
||||||
private CommandManager(DalamudStartInfo startInfo)
|
private CommandManager(DalamudStartInfo startInfo)
|
||||||
{
|
{
|
||||||
|
|
@ -40,7 +43,7 @@ namespace Dalamud.Game.Command
|
||||||
_ => this.currentLangCommandRegex,
|
_ => this.currentLangCommandRegex,
|
||||||
};
|
};
|
||||||
|
|
||||||
Service<ChatGui>.Get().CheckMessageHandled += this.OnCheckMessageHandled;
|
this.chatGui.CheckMessageHandled += this.OnCheckMessageHandled;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -170,5 +173,10 @@ namespace Dalamud.Game.Command
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
this.chatGui.CheckMessageHandled -= this.OnCheckMessageHandled;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,10 +8,8 @@ using System.Threading.Tasks;
|
||||||
|
|
||||||
using Dalamud.Game.Gui;
|
using Dalamud.Game.Gui;
|
||||||
using Dalamud.Game.Gui.Toast;
|
using Dalamud.Game.Gui.Toast;
|
||||||
using Dalamud.Game.Libc;
|
|
||||||
using Dalamud.Game.Network;
|
using Dalamud.Game.Network;
|
||||||
using Dalamud.Hooking;
|
using Dalamud.Hooking;
|
||||||
using Dalamud.Interface.Internal;
|
|
||||||
using Dalamud.IoC;
|
using Dalamud.IoC;
|
||||||
using Dalamud.IoC.Internal;
|
using Dalamud.IoC.Internal;
|
||||||
using Dalamud.Utility;
|
using Dalamud.Utility;
|
||||||
|
|
@ -32,21 +30,19 @@ namespace Dalamud.Game
|
||||||
private readonly List<RunOnNextTickTaskBase> runOnNextTickTaskList = new();
|
private readonly List<RunOnNextTickTaskBase> runOnNextTickTaskList = new();
|
||||||
private readonly Stopwatch updateStopwatch = new();
|
private readonly Stopwatch updateStopwatch = new();
|
||||||
|
|
||||||
private Hook<OnUpdateDetour> updateHook;
|
private readonly Hook<OnUpdateDetour> updateHook;
|
||||||
private Hook<OnDestroyDetour> freeHook;
|
private readonly Hook<OnRealDestroyDelegate> destroyHook;
|
||||||
private Hook<OnRealDestroyDelegate> destroyHook;
|
|
||||||
|
|
||||||
private Thread? frameworkUpdateThread;
|
private Thread? frameworkUpdateThread;
|
||||||
|
|
||||||
[ServiceManager.ServiceConstructor]
|
[ServiceManager.ServiceConstructor]
|
||||||
private Framework(GameGui gameGui, GameNetwork gameNetwork, SigScanner sigScanner)
|
private Framework(SigScanner sigScanner)
|
||||||
{
|
{
|
||||||
this.Address = new FrameworkAddressResolver();
|
this.Address = new FrameworkAddressResolver();
|
||||||
this.Address.Setup(sigScanner);
|
this.Address.Setup(sigScanner);
|
||||||
|
|
||||||
this.updateHook = new Hook<OnUpdateDetour>(this.Address.TickAddress, this.HandleFrameworkUpdate);
|
this.updateHook = Hook<OnUpdateDetour>.FromAddress(this.Address.TickAddress, this.HandleFrameworkUpdate);
|
||||||
this.freeHook = new Hook<OnDestroyDetour>(this.Address.FreeAddress, this.HandleFrameworkFree);
|
this.destroyHook = Hook<OnRealDestroyDelegate>.FromAddress(this.Address.DestroyAddress, this.HandleFrameworkDestroy);
|
||||||
this.destroyHook = new Hook<OnRealDestroyDelegate>(this.Address.DestroyAddress, this.HandleFrameworkDestroy);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -113,6 +109,11 @@ namespace Dalamud.Game
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool IsInFrameworkUpdateThread => Thread.CurrentThread == this.frameworkUpdateThread;
|
public bool IsInFrameworkUpdateThread => Thread.CurrentThread == this.frameworkUpdateThread;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a value indicating whether game Framework is unloading.
|
||||||
|
/// </summary>
|
||||||
|
public bool IsFrameworkUnloading { get; internal set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets a value indicating whether to dispatch update events.
|
/// Gets or sets a value indicating whether to dispatch update events.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -124,7 +125,8 @@ namespace Dalamud.Game
|
||||||
/// <typeparam name="T">Return type.</typeparam>
|
/// <typeparam name="T">Return type.</typeparam>
|
||||||
/// <param name="func">Function to call.</param>
|
/// <param name="func">Function to call.</param>
|
||||||
/// <returns>Task representing the pending or already completed function.</returns>
|
/// <returns>Task representing the pending or already completed function.</returns>
|
||||||
public Task<T> RunOnFrameworkThread<T>(Func<T> func) => this.IsInFrameworkUpdateThread ? Task.FromResult(func()) : this.RunOnTick(func);
|
public Task<T> RunOnFrameworkThread<T>(Func<T> func) =>
|
||||||
|
this.IsInFrameworkUpdateThread || this.IsFrameworkUnloading ? Task.FromResult(func()) : this.RunOnTick(func);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Run given function right away if this function has been called from game's Framework.Update thread, or otherwise run on next Framework.Update call.
|
/// Run given function right away if this function has been called from game's Framework.Update thread, or otherwise run on next Framework.Update call.
|
||||||
|
|
@ -133,7 +135,7 @@ namespace Dalamud.Game
|
||||||
/// <returns>Task representing the pending or already completed function.</returns>
|
/// <returns>Task representing the pending or already completed function.</returns>
|
||||||
public Task RunOnFrameworkThread(Action action)
|
public Task RunOnFrameworkThread(Action action)
|
||||||
{
|
{
|
||||||
if (this.IsInFrameworkUpdateThread)
|
if (this.IsInFrameworkUpdateThread || this.IsFrameworkUnloading)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
|
@ -151,6 +153,24 @@ namespace Dalamud.Game
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <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>
|
||||||
|
/// <typeparam name="T">Return type.</typeparam>
|
||||||
|
/// <param name="func">Function to call.</param>
|
||||||
|
/// <returns>Task representing the pending or already completed function.</returns>
|
||||||
|
public Task<T> RunOnFrameworkThread<T>(Func<Task<T>> func) =>
|
||||||
|
this.IsInFrameworkUpdateThread || this.IsFrameworkUnloading ? func() : this.RunOnTick(func);
|
||||||
|
|
||||||
|
/// <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>
|
||||||
|
/// <typeparam name="T">Return type.</typeparam>
|
||||||
|
/// <param name="func">Function to call.</param>
|
||||||
|
/// <returns>Task representing the pending or already completed function.</returns>
|
||||||
|
public Task RunOnFrameworkThread(Func<Task> func) =>
|
||||||
|
this.IsInFrameworkUpdateThread || this.IsFrameworkUnloading ? func() : this.RunOnTick(func);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Run given function in upcoming Framework.Tick call.
|
/// Run given function in upcoming Framework.Tick call.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -162,6 +182,16 @@ namespace Dalamud.Game
|
||||||
/// <returns>Task representing the pending function.</returns>
|
/// <returns>Task representing the pending function.</returns>
|
||||||
public Task<T> RunOnTick<T>(Func<T> func, TimeSpan delay = default, int delayTicks = default, CancellationToken cancellationToken = default)
|
public Task<T> RunOnTick<T>(Func<T> func, TimeSpan delay = default, int delayTicks = default, CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
|
if (this.IsFrameworkUnloading)
|
||||||
|
{
|
||||||
|
if (delay == default && delayTicks == default)
|
||||||
|
return this.RunOnFrameworkThread(func);
|
||||||
|
|
||||||
|
var cts = new CancellationTokenSource();
|
||||||
|
cts.Cancel();
|
||||||
|
return Task.FromCanceled<T>(cts.Token);
|
||||||
|
}
|
||||||
|
|
||||||
var tcs = new TaskCompletionSource<T>();
|
var tcs = new TaskCompletionSource<T>();
|
||||||
this.runOnNextTickTaskList.Add(new RunOnNextTickTaskFunc<T>()
|
this.runOnNextTickTaskList.Add(new RunOnNextTickTaskFunc<T>()
|
||||||
{
|
{
|
||||||
|
|
@ -184,6 +214,16 @@ namespace Dalamud.Game
|
||||||
/// <returns>Task representing the pending function.</returns>
|
/// <returns>Task representing the pending function.</returns>
|
||||||
public Task RunOnTick(Action action, TimeSpan delay = default, int delayTicks = default, CancellationToken cancellationToken = default)
|
public Task RunOnTick(Action action, TimeSpan delay = default, int delayTicks = default, CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
|
if (this.IsFrameworkUnloading)
|
||||||
|
{
|
||||||
|
if (delay == default && delayTicks == default)
|
||||||
|
return this.RunOnFrameworkThread(action);
|
||||||
|
|
||||||
|
var cts = new CancellationTokenSource();
|
||||||
|
cts.Cancel();
|
||||||
|
return Task.FromCanceled(cts.Token);
|
||||||
|
}
|
||||||
|
|
||||||
var tcs = new TaskCompletionSource();
|
var tcs = new TaskCompletionSource();
|
||||||
this.runOnNextTickTaskList.Add(new RunOnNextTickTaskAction()
|
this.runOnNextTickTaskList.Add(new RunOnNextTickTaskAction()
|
||||||
{
|
{
|
||||||
|
|
@ -196,22 +236,88 @@ namespace Dalamud.Game
|
||||||
return tcs.Task;
|
return tcs.Task;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Run given function in upcoming Framework.Tick call.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">Return type.</typeparam>
|
||||||
|
/// <param name="func">Function to call.</param>
|
||||||
|
/// <param name="delay">Wait for given timespan before calling this function.</param>
|
||||||
|
/// <param name="delayTicks">Count given number of Framework.Tick calls before calling this function. This takes precedence over delay parameter.</param>
|
||||||
|
/// <param name="cancellationToken">Cancellation token which will prevent the execution of this function if wait conditions are not met.</param>
|
||||||
|
/// <returns>Task representing the pending function.</returns>
|
||||||
|
public Task<T> RunOnTick<T>(Func<Task<T>> func, TimeSpan delay = default, int delayTicks = default, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
if (this.IsFrameworkUnloading)
|
||||||
|
{
|
||||||
|
if (delay == default && delayTicks == default)
|
||||||
|
return this.RunOnFrameworkThread(func);
|
||||||
|
|
||||||
|
var cts = new CancellationTokenSource();
|
||||||
|
cts.Cancel();
|
||||||
|
return Task.FromCanceled<T>(cts.Token);
|
||||||
|
}
|
||||||
|
|
||||||
|
var tcs = new TaskCompletionSource<Task<T>>();
|
||||||
|
this.runOnNextTickTaskList.Add(new RunOnNextTickTaskFunc<Task<T>>()
|
||||||
|
{
|
||||||
|
RemainingTicks = delayTicks,
|
||||||
|
RunAfterTickCount = Environment.TickCount64 + (long)Math.Ceiling(delay.TotalMilliseconds),
|
||||||
|
CancellationToken = cancellationToken,
|
||||||
|
TaskCompletionSource = tcs,
|
||||||
|
Func = func,
|
||||||
|
});
|
||||||
|
return tcs.Task.ContinueWith(x => x.Result, cancellationToken).Unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Run given function in upcoming Framework.Tick call.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">Return type.</typeparam>
|
||||||
|
/// <param name="func">Function to call.</param>
|
||||||
|
/// <param name="delay">Wait for given timespan before calling this function.</param>
|
||||||
|
/// <param name="delayTicks">Count given number of Framework.Tick calls before calling this function. This takes precedence over delay parameter.</param>
|
||||||
|
/// <param name="cancellationToken">Cancellation token which will prevent the execution of this function if wait conditions are not met.</param>
|
||||||
|
/// <returns>Task representing the pending function.</returns>
|
||||||
|
public Task RunOnTick(Func<Task> func, TimeSpan delay = default, int delayTicks = default, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
if (this.IsFrameworkUnloading)
|
||||||
|
{
|
||||||
|
if (delay == default && delayTicks == default)
|
||||||
|
return this.RunOnFrameworkThread(func);
|
||||||
|
|
||||||
|
var cts = new CancellationTokenSource();
|
||||||
|
cts.Cancel();
|
||||||
|
return Task.FromCanceled(cts.Token);
|
||||||
|
}
|
||||||
|
|
||||||
|
var tcs = new TaskCompletionSource<Task>();
|
||||||
|
this.runOnNextTickTaskList.Add(new RunOnNextTickTaskFunc<Task>()
|
||||||
|
{
|
||||||
|
RemainingTicks = delayTicks,
|
||||||
|
RunAfterTickCount = Environment.TickCount64 + (long)Math.Ceiling(delay.TotalMilliseconds),
|
||||||
|
CancellationToken = cancellationToken,
|
||||||
|
TaskCompletionSource = tcs,
|
||||||
|
Func = func,
|
||||||
|
});
|
||||||
|
return tcs.Task.ContinueWith(x => x.Result, cancellationToken).Unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Dispose of managed and unmanaged resources.
|
/// Dispose of managed and unmanaged resources.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
void IDisposable.Dispose()
|
void IDisposable.Dispose()
|
||||||
{
|
{
|
||||||
Service<GameGui>.GetNullable()?.ExplicitDispose();
|
this.RunOnFrameworkThread(() =>
|
||||||
Service<GameNetwork>.GetNullable()?.ExplicitDispose();
|
{
|
||||||
|
// ReSharper disable once AccessToDisposedClosure
|
||||||
|
this.updateHook.Disable();
|
||||||
|
|
||||||
this.updateHook?.Disable();
|
// ReSharper disable once AccessToDisposedClosure
|
||||||
this.freeHook?.Disable();
|
this.destroyHook.Disable();
|
||||||
this.destroyHook?.Disable();
|
}).Wait();
|
||||||
Thread.Sleep(500);
|
|
||||||
|
|
||||||
this.updateHook?.Dispose();
|
this.updateHook.Dispose();
|
||||||
this.freeHook?.Dispose();
|
this.destroyHook.Dispose();
|
||||||
this.destroyHook?.Dispose();
|
|
||||||
|
|
||||||
this.updateStopwatch.Reset();
|
this.updateStopwatch.Reset();
|
||||||
statsStopwatch.Reset();
|
statsStopwatch.Reset();
|
||||||
|
|
@ -221,7 +327,6 @@ namespace Dalamud.Game
|
||||||
private void ContinueConstruction()
|
private void ContinueConstruction()
|
||||||
{
|
{
|
||||||
this.updateHook.Enable();
|
this.updateHook.Enable();
|
||||||
this.freeHook.Enable();
|
|
||||||
this.destroyHook.Enable();
|
this.destroyHook.Enable();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -314,41 +419,21 @@ namespace Dalamud.Game
|
||||||
}
|
}
|
||||||
|
|
||||||
original:
|
original:
|
||||||
return this.updateHook.Original(framework);
|
return this.updateHook.OriginalDisposeSafe(framework);
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool HandleFrameworkDestroy(IntPtr framework)
|
private bool HandleFrameworkDestroy(IntPtr framework)
|
||||||
{
|
{
|
||||||
if (this.DispatchUpdateEvents)
|
this.IsFrameworkUnloading = true;
|
||||||
{
|
|
||||||
Log.Information("Framework::Destroy!");
|
|
||||||
|
|
||||||
var dalamud = Service<Dalamud>.Get();
|
|
||||||
dalamud.DisposePlugins();
|
|
||||||
|
|
||||||
Log.Information("Framework::Destroy OK!");
|
|
||||||
}
|
|
||||||
|
|
||||||
this.DispatchUpdateEvents = false;
|
this.DispatchUpdateEvents = false;
|
||||||
|
|
||||||
return this.destroyHook.Original(framework);
|
Log.Information("Framework::Destroy!");
|
||||||
}
|
Service<Dalamud>.Get().Unload();
|
||||||
|
this.runOnNextTickTaskList.RemoveAll(x => x.Run());
|
||||||
|
ServiceManager.UnloadAllServices();
|
||||||
|
Log.Information("Framework::Destroy OK!");
|
||||||
|
|
||||||
private IntPtr HandleFrameworkFree()
|
return this.destroyHook.OriginalDisposeSafe(framework);
|
||||||
{
|
|
||||||
Log.Information("Framework::Free!");
|
|
||||||
|
|
||||||
// Store the pointer to the original trampoline location
|
|
||||||
var originalPtr = Marshal.GetFunctionPointerForDelegate(this.freeHook.Original);
|
|
||||||
|
|
||||||
var dalamud = Service<Dalamud>.Get();
|
|
||||||
dalamud.Unload();
|
|
||||||
dalamud.WaitForUnloadFinish();
|
|
||||||
|
|
||||||
Log.Information("Framework::Free OK!");
|
|
||||||
|
|
||||||
// Return the original trampoline location to cleanly exit
|
|
||||||
return originalPtr;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private abstract class RunOnNextTickTaskBase
|
private abstract class RunOnNextTickTaskBase
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,12 @@ namespace Dalamud.Game.Gui
|
||||||
private readonly Hook<PopulateItemLinkDelegate> populateItemLinkHook;
|
private readonly Hook<PopulateItemLinkDelegate> populateItemLinkHook;
|
||||||
private readonly Hook<InteractableLinkClickedDelegate> interactableLinkClickedHook;
|
private readonly Hook<InteractableLinkClickedDelegate> interactableLinkClickedHook;
|
||||||
|
|
||||||
|
[ServiceManager.ServiceDependency]
|
||||||
|
private readonly DalamudConfiguration configuration = Service<DalamudConfiguration>.Get();
|
||||||
|
|
||||||
|
[ServiceManager.ServiceDependency]
|
||||||
|
private readonly LibcFunction libcFunction = Service<LibcFunction>.Get();
|
||||||
|
|
||||||
private IntPtr baseAddress = IntPtr.Zero;
|
private IntPtr baseAddress = IntPtr.Zero;
|
||||||
|
|
||||||
[ServiceManager.ServiceConstructor]
|
[ServiceManager.ServiceConstructor]
|
||||||
|
|
@ -41,9 +47,9 @@ namespace Dalamud.Game.Gui
|
||||||
this.address = new ChatGuiAddressResolver();
|
this.address = new ChatGuiAddressResolver();
|
||||||
this.address.Setup(sigScanner);
|
this.address.Setup(sigScanner);
|
||||||
|
|
||||||
this.printMessageHook = new Hook<PrintMessageDelegate>(this.address.PrintMessage, this.HandlePrintMessageDetour);
|
this.printMessageHook = Hook<PrintMessageDelegate>.FromAddress(this.address.PrintMessage, this.HandlePrintMessageDetour);
|
||||||
this.populateItemLinkHook = new Hook<PopulateItemLinkDelegate>(this.address.PopulateItemLinkObject, this.HandlePopulateItemLinkDetour);
|
this.populateItemLinkHook = Hook<PopulateItemLinkDelegate>.FromAddress(this.address.PopulateItemLinkObject, this.HandlePopulateItemLinkDetour);
|
||||||
this.interactableLinkClickedHook = new Hook<InteractableLinkClickedDelegate>(this.address.InteractableLinkClicked, this.InteractableLinkClickedDetour);
|
this.interactableLinkClickedHook = Hook<InteractableLinkClickedDelegate>.FromAddress(this.address.InteractableLinkClicked, this.InteractableLinkClickedDetour);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -150,13 +156,11 @@ namespace Dalamud.Game.Gui
|
||||||
/// <param name="message">A message to send.</param>
|
/// <param name="message">A message to send.</param>
|
||||||
public void Print(string message)
|
public void Print(string message)
|
||||||
{
|
{
|
||||||
var configuration = Service<DalamudConfiguration>.Get();
|
|
||||||
|
|
||||||
// Log.Verbose("[CHATGUI PRINT REGULAR]{0}", message);
|
// Log.Verbose("[CHATGUI PRINT REGULAR]{0}", message);
|
||||||
this.PrintChat(new XivChatEntry
|
this.PrintChat(new XivChatEntry
|
||||||
{
|
{
|
||||||
Message = message,
|
Message = message,
|
||||||
Type = configuration.GeneralChatType,
|
Type = this.configuration.GeneralChatType,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -167,13 +171,11 @@ namespace Dalamud.Game.Gui
|
||||||
/// <param name="message">A message to send.</param>
|
/// <param name="message">A message to send.</param>
|
||||||
public void Print(SeString message)
|
public void Print(SeString message)
|
||||||
{
|
{
|
||||||
var configuration = Service<DalamudConfiguration>.Get();
|
|
||||||
|
|
||||||
// Log.Verbose("[CHATGUI PRINT SESTRING]{0}", message.TextValue);
|
// Log.Verbose("[CHATGUI PRINT SESTRING]{0}", message.TextValue);
|
||||||
this.PrintChat(new XivChatEntry
|
this.PrintChat(new XivChatEntry
|
||||||
{
|
{
|
||||||
Message = message,
|
Message = message,
|
||||||
Type = configuration.GeneralChatType,
|
Type = this.configuration.GeneralChatType,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -222,10 +224,10 @@ namespace Dalamud.Game.Gui
|
||||||
}
|
}
|
||||||
|
|
||||||
var senderRaw = (chat.Name ?? string.Empty).Encode();
|
var senderRaw = (chat.Name ?? string.Empty).Encode();
|
||||||
using var senderOwned = Service<LibcFunction>.Get().NewString(senderRaw);
|
using var senderOwned = this.libcFunction.NewString(senderRaw);
|
||||||
|
|
||||||
var messageRaw = (chat.Message ?? string.Empty).Encode();
|
var messageRaw = (chat.Message ?? string.Empty).Encode();
|
||||||
using var messageOwned = Service<LibcFunction>.Get().NewString(messageRaw);
|
using var messageOwned = this.libcFunction.NewString(messageRaw);
|
||||||
|
|
||||||
this.HandlePrintMessageDetour(this.baseAddress, chat.Type, senderOwned.Address, messageOwned.Address, chat.SenderId, chat.Parameters);
|
this.HandlePrintMessageDetour(this.baseAddress, chat.Type, senderOwned.Address, messageOwned.Address, chat.SenderId, chat.Parameters);
|
||||||
}
|
}
|
||||||
|
|
@ -364,7 +366,7 @@ namespace Dalamud.Game.Gui
|
||||||
|
|
||||||
if (!Util.FastByteArrayCompare(originalMessageData, message.RawData))
|
if (!Util.FastByteArrayCompare(originalMessageData, message.RawData))
|
||||||
{
|
{
|
||||||
allocatedString = Service<LibcFunction>.Get().NewString(message.RawData);
|
allocatedString = this.libcFunction.NewString(message.RawData);
|
||||||
Log.Debug($"HandlePrintMessageDetour String modified: {originalMessageData}({messagePtr}) -> {message}({allocatedString.Address})");
|
Log.Debug($"HandlePrintMessageDetour String modified: {originalMessageData}({messagePtr}) -> {message}({allocatedString.Address})");
|
||||||
messagePtr = allocatedString.Address;
|
messagePtr = allocatedString.Address;
|
||||||
}
|
}
|
||||||
|
|
@ -379,7 +381,7 @@ namespace Dalamud.Game.Gui
|
||||||
|
|
||||||
if (!Util.FastByteArrayCompare(originalSenderData, sender.RawData))
|
if (!Util.FastByteArrayCompare(originalSenderData, sender.RawData))
|
||||||
{
|
{
|
||||||
allocatedStringSender = Service<LibcFunction>.Get().NewString(sender.RawData);
|
allocatedStringSender = this.libcFunction.NewString(sender.RawData);
|
||||||
Log.Debug(
|
Log.Debug(
|
||||||
$"HandlePrintMessageDetour Sender modified: {originalSenderData}({senderPtr}) -> {sender}({allocatedStringSender.Address})");
|
$"HandlePrintMessageDetour Sender modified: {originalSenderData}({senderPtr}) -> {sender}({allocatedStringSender.Address})");
|
||||||
senderPtr = allocatedStringSender.Address;
|
senderPtr = allocatedStringSender.Address;
|
||||||
|
|
|
||||||
|
|
@ -58,11 +58,11 @@ namespace Dalamud.Game.Gui.ContextMenus
|
||||||
{
|
{
|
||||||
this.openSubContextMenu = Marshal.GetDelegateForFunctionPointer<OpenSubContextMenuDelegate>(this.Address.OpenSubContextMenuPtr);
|
this.openSubContextMenu = Marshal.GetDelegateForFunctionPointer<OpenSubContextMenuDelegate>(this.Address.OpenSubContextMenuPtr);
|
||||||
|
|
||||||
this.contextMenuOpeningHook = new Hook<ContextMenuOpeningDelegate>(this.Address.ContextMenuOpeningPtr, this.ContextMenuOpeningDetour);
|
this.contextMenuOpeningHook = Hook<ContextMenuOpeningDelegate>.FromAddress(this.Address.ContextMenuOpeningPtr, this.ContextMenuOpeningDetour);
|
||||||
this.contextMenuOpenedHook = new Hook<ContextMenuOpenedDelegate>(this.Address.ContextMenuOpenedPtr, this.ContextMenuOpenedDetour);
|
this.contextMenuOpenedHook = Hook<ContextMenuOpenedDelegate>.FromAddress(this.Address.ContextMenuOpenedPtr, this.ContextMenuOpenedDetour);
|
||||||
this.contextMenuItemSelectedHook = new Hook<ContextMenuItemSelectedDelegate>(this.Address.ContextMenuItemSelectedPtr, this.ContextMenuItemSelectedDetour);
|
this.contextMenuItemSelectedHook = Hook<ContextMenuItemSelectedDelegate>.FromAddress(this.Address.ContextMenuItemSelectedPtr, this.ContextMenuItemSelectedDetour);
|
||||||
this.subContextMenuOpeningHook = new Hook<SubContextMenuOpeningDelegate>(this.Address.SubContextMenuOpeningPtr, this.SubContextMenuOpeningDetour);
|
this.subContextMenuOpeningHook = Hook<SubContextMenuOpeningDelegate>.FromAddress(this.Address.SubContextMenuOpeningPtr, this.SubContextMenuOpeningDetour);
|
||||||
this.subContextMenuOpenedHook = new Hook<ContextMenuOpenedDelegate>(this.Address.SubContextMenuOpenedPtr, this.SubContextMenuOpenedDetour);
|
this.subContextMenuOpenedHook = Hook<ContextMenuOpenedDelegate>.FromAddress(this.Address.SubContextMenuOpenedPtr, this.SubContextMenuOpenedDetour);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -22,17 +22,26 @@ namespace Dalamud.Game.Gui.Dtr
|
||||||
{
|
{
|
||||||
private const uint BaseNodeId = 1000;
|
private const uint BaseNodeId = 1000;
|
||||||
|
|
||||||
|
[ServiceManager.ServiceDependency]
|
||||||
|
private readonly Framework framework = Service<Framework>.Get();
|
||||||
|
|
||||||
|
[ServiceManager.ServiceDependency]
|
||||||
|
private readonly GameGui gameGui = Service<GameGui>.Get();
|
||||||
|
|
||||||
|
[ServiceManager.ServiceDependency]
|
||||||
|
private readonly DalamudConfiguration configuration = Service<DalamudConfiguration>.Get();
|
||||||
|
|
||||||
private List<DtrBarEntry> entries = new();
|
private List<DtrBarEntry> entries = new();
|
||||||
private uint runningNodeIds = BaseNodeId;
|
private uint runningNodeIds = BaseNodeId;
|
||||||
|
|
||||||
[ServiceManager.ServiceConstructor]
|
[ServiceManager.ServiceConstructor]
|
||||||
private DtrBar(DalamudConfiguration configuration, Framework framework)
|
private DtrBar()
|
||||||
{
|
{
|
||||||
framework.Update += this.Update;
|
this.framework.Update += this.Update;
|
||||||
|
|
||||||
configuration.DtrOrder ??= new List<string>();
|
this.configuration.DtrOrder ??= new List<string>();
|
||||||
configuration.DtrIgnore ??= new List<string>();
|
this.configuration.DtrIgnore ??= new List<string>();
|
||||||
configuration.Save();
|
this.configuration.Save();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -48,14 +57,13 @@ namespace Dalamud.Game.Gui.Dtr
|
||||||
if (this.entries.Any(x => x.Title == title))
|
if (this.entries.Any(x => x.Title == title))
|
||||||
throw new ArgumentException("An entry with the same title already exists.");
|
throw new ArgumentException("An entry with the same title already exists.");
|
||||||
|
|
||||||
var configuration = Service<DalamudConfiguration>.Get();
|
|
||||||
var node = this.MakeNode(++this.runningNodeIds);
|
var node = this.MakeNode(++this.runningNodeIds);
|
||||||
var entry = new DtrBarEntry(title, node);
|
var entry = new DtrBarEntry(title, node);
|
||||||
entry.Text = text;
|
entry.Text = text;
|
||||||
|
|
||||||
// Add the entry to the end of the order list, if it's not there already.
|
// Add the entry to the end of the order list, if it's not there already.
|
||||||
if (!configuration.DtrOrder!.Contains(title))
|
if (!this.configuration.DtrOrder!.Contains(title))
|
||||||
configuration.DtrOrder!.Add(title);
|
this.configuration.DtrOrder!.Add(title);
|
||||||
this.entries.Add(entry);
|
this.entries.Add(entry);
|
||||||
this.ApplySort();
|
this.ApplySort();
|
||||||
|
|
||||||
|
|
@ -69,7 +77,7 @@ namespace Dalamud.Game.Gui.Dtr
|
||||||
this.RemoveNode(entry.TextNode);
|
this.RemoveNode(entry.TextNode);
|
||||||
|
|
||||||
this.entries.Clear();
|
this.entries.Clear();
|
||||||
Service<Framework>.Get().Update -= this.Update;
|
this.framework.Update -= this.Update;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -112,12 +120,11 @@ namespace Dalamud.Game.Gui.Dtr
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal void ApplySort()
|
internal void ApplySort()
|
||||||
{
|
{
|
||||||
var configuration = Service<DalamudConfiguration>.Get();
|
|
||||||
|
|
||||||
// Sort the current entry list, based on the order in the configuration.
|
// Sort the current entry list, based on the order in the configuration.
|
||||||
var positions = configuration.DtrOrder!
|
var positions = this.configuration
|
||||||
.Select(entry => (entry, index: configuration.DtrOrder!.IndexOf(entry)))
|
.DtrOrder!
|
||||||
.ToDictionary(x => x.entry, x => x.index);
|
.Select(entry => (entry, index: this.configuration.DtrOrder!.IndexOf(entry)))
|
||||||
|
.ToDictionary(x => x.entry, x => x.index);
|
||||||
|
|
||||||
this.entries.Sort((x, y) =>
|
this.entries.Sort((x, y) =>
|
||||||
{
|
{
|
||||||
|
|
@ -127,13 +134,13 @@ namespace Dalamud.Game.Gui.Dtr
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private static AtkUnitBase* GetDtr() => (AtkUnitBase*)Service<GameGui>.Get().GetAddonByName("_DTR", 1).ToPointer();
|
private AtkUnitBase* GetDtr() => (AtkUnitBase*)this.gameGui.GetAddonByName("_DTR", 1).ToPointer();
|
||||||
|
|
||||||
private void Update(Framework unused)
|
private void Update(Framework unused)
|
||||||
{
|
{
|
||||||
this.HandleRemovedNodes();
|
this.HandleRemovedNodes();
|
||||||
|
|
||||||
var dtr = GetDtr();
|
var dtr = this.GetDtr();
|
||||||
if (dtr == null) return;
|
if (dtr == null) return;
|
||||||
|
|
||||||
// The collision node on the DTR element is always the width of its content
|
// The collision node on the DTR element is always the width of its content
|
||||||
|
|
@ -147,16 +154,16 @@ namespace Dalamud.Game.Gui.Dtr
|
||||||
var collisionNode = dtr->UldManager.NodeList[1];
|
var collisionNode = dtr->UldManager.NodeList[1];
|
||||||
if (collisionNode == null) return;
|
if (collisionNode == null) return;
|
||||||
|
|
||||||
var configuration = Service<DalamudConfiguration>.Get();
|
|
||||||
|
|
||||||
// If we are drawing backwards, we should start from the right side of the collision node. That is,
|
// If we are drawing backwards, we should start from the right side of the collision node. That is,
|
||||||
// collisionNode->X + collisionNode->Width.
|
// collisionNode->X + collisionNode->Width.
|
||||||
var runningXPos = configuration.DtrSwapDirection ? collisionNode->X + collisionNode->Width : collisionNode->X;
|
var runningXPos = this.configuration.DtrSwapDirection
|
||||||
|
? collisionNode->X + collisionNode->Width
|
||||||
|
: collisionNode->X;
|
||||||
|
|
||||||
for (var i = 0; i < this.entries.Count; i++)
|
for (var i = 0; i < this.entries.Count; i++)
|
||||||
{
|
{
|
||||||
var data = this.entries[i];
|
var data = this.entries[i];
|
||||||
var isHide = configuration.DtrIgnore!.Any(x => x == data.Title) || !data.Shown;
|
var isHide = this.configuration.DtrIgnore!.Any(x => x == data.Title) || !data.Shown;
|
||||||
|
|
||||||
if (data.Dirty && data.Added && data.Text != null && data.TextNode != null)
|
if (data.Dirty && data.Added && data.Text != null && data.TextNode != null)
|
||||||
{
|
{
|
||||||
|
|
@ -185,9 +192,9 @@ namespace Dalamud.Game.Gui.Dtr
|
||||||
|
|
||||||
if (!isHide)
|
if (!isHide)
|
||||||
{
|
{
|
||||||
var elementWidth = data.TextNode->AtkResNode.Width + configuration.DtrSpacing;
|
var elementWidth = data.TextNode->AtkResNode.Width + this.configuration.DtrSpacing;
|
||||||
|
|
||||||
if (configuration.DtrSwapDirection)
|
if (this.configuration.DtrSwapDirection)
|
||||||
{
|
{
|
||||||
data.TextNode->AtkResNode.SetPositionFloat(runningXPos, 2);
|
data.TextNode->AtkResNode.SetPositionFloat(runningXPos, 2);
|
||||||
runningXPos += elementWidth;
|
runningXPos += elementWidth;
|
||||||
|
|
@ -209,7 +216,7 @@ namespace Dalamud.Game.Gui.Dtr
|
||||||
/// <returns>True if there are nodes with an ID > 1000.</returns>
|
/// <returns>True if there are nodes with an ID > 1000.</returns>
|
||||||
private bool CheckForDalamudNodes()
|
private bool CheckForDalamudNodes()
|
||||||
{
|
{
|
||||||
var dtr = GetDtr();
|
var dtr = this.GetDtr();
|
||||||
if (dtr == null || dtr->RootNode == null) return false;
|
if (dtr == null || dtr->RootNode == null) return false;
|
||||||
|
|
||||||
for (var i = 0; i < dtr->UldManager.NodeListCount; i++)
|
for (var i = 0; i < dtr->UldManager.NodeListCount; i++)
|
||||||
|
|
@ -233,7 +240,7 @@ namespace Dalamud.Game.Gui.Dtr
|
||||||
|
|
||||||
private bool AddNode(AtkTextNode* node)
|
private bool AddNode(AtkTextNode* node)
|
||||||
{
|
{
|
||||||
var dtr = GetDtr();
|
var dtr = this.GetDtr();
|
||||||
if (dtr == null || dtr->RootNode == null || dtr->UldManager.NodeList == null || node == null) return false;
|
if (dtr == null || dtr->RootNode == null || dtr->UldManager.NodeList == null || node == null) return false;
|
||||||
|
|
||||||
var lastChild = dtr->RootNode->ChildNode;
|
var lastChild = dtr->RootNode->ChildNode;
|
||||||
|
|
@ -253,7 +260,7 @@ namespace Dalamud.Game.Gui.Dtr
|
||||||
|
|
||||||
private bool RemoveNode(AtkTextNode* node)
|
private bool RemoveNode(AtkTextNode* node)
|
||||||
{
|
{
|
||||||
var dtr = GetDtr();
|
var dtr = this.GetDtr();
|
||||||
if (dtr == null || dtr->RootNode == null || dtr->UldManager.NodeList == null || node == null) return false;
|
if (dtr == null || dtr->RootNode == null || dtr->UldManager.NodeList == null || node == null) return false;
|
||||||
|
|
||||||
var tmpPrevNode = node->AtkResNode.PrevSiblingNode;
|
var tmpPrevNode = node->AtkResNode.PrevSiblingNode;
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,7 @@ namespace Dalamud.Game.Gui.FlyText
|
||||||
this.Address.Setup(sigScanner);
|
this.Address.Setup(sigScanner);
|
||||||
|
|
||||||
this.addFlyTextNative = Marshal.GetDelegateForFunctionPointer<AddFlyTextDelegate>(this.Address.AddFlyText);
|
this.addFlyTextNative = Marshal.GetDelegateForFunctionPointer<AddFlyTextDelegate>(this.Address.AddFlyText);
|
||||||
this.createFlyTextHook = new Hook<CreateFlyTextDelegate>(this.Address.CreateFlyText, this.CreateFlyTextDetour);
|
this.createFlyTextHook = Hook<CreateFlyTextDelegate>.FromAddress(this.Address.CreateFlyText, this.CreateFlyTextDetour);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
||||||
|
|
@ -60,23 +60,23 @@ namespace Dalamud.Game.Gui
|
||||||
Log.Verbose($"HandleItemOut address 0x{this.address.HandleItemOut.ToInt64():X}");
|
Log.Verbose($"HandleItemOut address 0x{this.address.HandleItemOut.ToInt64():X}");
|
||||||
Log.Verbose($"HandleImm address 0x{this.address.HandleImm.ToInt64():X}");
|
Log.Verbose($"HandleImm address 0x{this.address.HandleImm.ToInt64():X}");
|
||||||
|
|
||||||
this.setGlobalBgmHook = new Hook<SetGlobalBgmDelegate>(this.address.SetGlobalBgm, this.HandleSetGlobalBgmDetour);
|
this.setGlobalBgmHook = Hook<SetGlobalBgmDelegate>.FromAddress(this.address.SetGlobalBgm, this.HandleSetGlobalBgmDetour);
|
||||||
|
|
||||||
this.handleItemHoverHook = new Hook<HandleItemHoverDelegate>(this.address.HandleItemHover, this.HandleItemHoverDetour);
|
this.handleItemHoverHook = Hook<HandleItemHoverDelegate>.FromAddress(this.address.HandleItemHover, this.HandleItemHoverDetour);
|
||||||
this.handleItemOutHook = new Hook<HandleItemOutDelegate>(this.address.HandleItemOut, this.HandleItemOutDetour);
|
this.handleItemOutHook = Hook<HandleItemOutDelegate>.FromAddress(this.address.HandleItemOut, this.HandleItemOutDetour);
|
||||||
|
|
||||||
this.handleActionHoverHook = new Hook<HandleActionHoverDelegate>(this.address.HandleActionHover, this.HandleActionHoverDetour);
|
this.handleActionHoverHook = Hook<HandleActionHoverDelegate>.FromAddress(this.address.HandleActionHover, this.HandleActionHoverDetour);
|
||||||
this.handleActionOutHook = new Hook<HandleActionOutDelegate>(this.address.HandleActionOut, this.HandleActionOutDetour);
|
this.handleActionOutHook = Hook<HandleActionOutDelegate>.FromAddress(this.address.HandleActionOut, this.HandleActionOutDetour);
|
||||||
|
|
||||||
this.handleImmHook = new Hook<HandleImmDelegate>(this.address.HandleImm, this.HandleImmDetour);
|
this.handleImmHook = Hook<HandleImmDelegate>.FromAddress(this.address.HandleImm, this.HandleImmDetour);
|
||||||
|
|
||||||
this.getMatrixSingleton = Marshal.GetDelegateForFunctionPointer<GetMatrixSingletonDelegate>(this.address.GetMatrixSingleton);
|
this.getMatrixSingleton = Marshal.GetDelegateForFunctionPointer<GetMatrixSingletonDelegate>(this.address.GetMatrixSingleton);
|
||||||
|
|
||||||
this.screenToWorldNative = Marshal.GetDelegateForFunctionPointer<ScreenToWorldNativeDelegate>(this.address.ScreenToWorld);
|
this.screenToWorldNative = Marshal.GetDelegateForFunctionPointer<ScreenToWorldNativeDelegate>(this.address.ScreenToWorld);
|
||||||
|
|
||||||
this.toggleUiHideHook = new Hook<ToggleUiHideDelegate>(this.address.ToggleUiHide, this.ToggleUiHideDetour);
|
this.toggleUiHideHook = Hook<ToggleUiHideDelegate>.FromAddress(this.address.ToggleUiHide, this.ToggleUiHideDetour);
|
||||||
|
|
||||||
this.utf8StringFromSequenceHook = new Hook<Utf8StringFromSequenceDelegate>(this.address.Utf8StringFromSequence, this.Utf8StringFromSequenceDetour);
|
this.utf8StringFromSequenceHook = Hook<Utf8StringFromSequenceDelegate>.FromAddress(this.address.Utf8StringFromSequence, this.Utf8StringFromSequenceDetour);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Marshaled delegates
|
// Marshaled delegates
|
||||||
|
|
@ -436,12 +436,6 @@ namespace Dalamud.Game.Gui
|
||||||
/// </summary>
|
/// </summary>
|
||||||
void IDisposable.Dispose()
|
void IDisposable.Dispose()
|
||||||
{
|
{
|
||||||
Service<ChatGui>.Get().ExplicitDispose();
|
|
||||||
Service<ToastGui>.Get().ExplicitDispose();
|
|
||||||
Service<FlyTextGui>.Get().ExplicitDispose();
|
|
||||||
Service<PartyFinderGui>.Get().ExplicitDispose();
|
|
||||||
Service<ContextMenu>.Get().ExplicitDispose();
|
|
||||||
Service<DtrBar>.Get().ExplicitDispose();
|
|
||||||
this.setGlobalBgmHook.Dispose();
|
this.setGlobalBgmHook.Dispose();
|
||||||
this.handleItemHoverHook.Dispose();
|
this.handleItemHoverHook.Dispose();
|
||||||
this.handleItemOutHook.Dispose();
|
this.handleItemOutHook.Dispose();
|
||||||
|
|
|
||||||
|
|
@ -266,9 +266,9 @@ namespace Dalamud.Game.Gui.Internal
|
||||||
private void ToggleWindow(bool visible)
|
private void ToggleWindow(bool visible)
|
||||||
{
|
{
|
||||||
if (visible)
|
if (visible)
|
||||||
Service<DalamudInterface>.Get().OpenImeWindow();
|
Service<DalamudInterface>.GetNullable()?.OpenImeWindow();
|
||||||
else
|
else
|
||||||
Service<DalamudInterface>.Get().CloseImeWindow();
|
Service<DalamudInterface>.GetNullable()?.CloseImeWindow();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -35,7 +35,7 @@ namespace Dalamud.Game.Gui.PartyFinder
|
||||||
|
|
||||||
this.memory = Marshal.AllocHGlobal(PartyFinderPacket.PacketSize);
|
this.memory = Marshal.AllocHGlobal(PartyFinderPacket.PacketSize);
|
||||||
|
|
||||||
this.receiveListingHook = new Hook<ReceiveListingDelegate>(this.address.ReceiveListing, new ReceiveListingDelegate(this.HandleReceiveListingDetour));
|
this.receiveListingHook = Hook<ReceiveListingDelegate>.FromAddress(this.address.ReceiveListing, new ReceiveListingDelegate(this.HandleReceiveListingDetour));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
||||||
|
|
@ -39,9 +39,9 @@ namespace Dalamud.Game.Gui.Toast
|
||||||
this.address = new ToastGuiAddressResolver();
|
this.address = new ToastGuiAddressResolver();
|
||||||
this.address.Setup(sigScanner);
|
this.address.Setup(sigScanner);
|
||||||
|
|
||||||
this.showNormalToastHook = new Hook<ShowNormalToastDelegate>(this.address.ShowNormalToast, new ShowNormalToastDelegate(this.HandleNormalToastDetour));
|
this.showNormalToastHook = Hook<ShowNormalToastDelegate>.FromAddress(this.address.ShowNormalToast, new ShowNormalToastDelegate(this.HandleNormalToastDetour));
|
||||||
this.showQuestToastHook = new Hook<ShowQuestToastDelegate>(this.address.ShowQuestToast, new ShowQuestToastDelegate(this.HandleQuestToastDetour));
|
this.showQuestToastHook = Hook<ShowQuestToastDelegate>.FromAddress(this.address.ShowQuestToast, new ShowQuestToastDelegate(this.HandleQuestToastDetour));
|
||||||
this.showErrorToastHook = new Hook<ShowErrorToastDelegate>(this.address.ShowErrorToast, new ShowErrorToastDelegate(this.HandleErrorToastDetour));
|
this.showErrorToastHook = Hook<ShowErrorToastDelegate>.FromAddress(this.address.ShowErrorToast, new ShowErrorToastDelegate(this.HandleErrorToastDetour));
|
||||||
}
|
}
|
||||||
|
|
||||||
#region Event delegates
|
#region Event delegates
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,6 @@ using Dalamud.Game.Text;
|
||||||
using Dalamud.Game.Text.SeStringHandling;
|
using Dalamud.Game.Text.SeStringHandling;
|
||||||
using Dalamud.Game.Text.SeStringHandling.Payloads;
|
using Dalamud.Game.Text.SeStringHandling.Payloads;
|
||||||
using Dalamud.Hooking;
|
using Dalamud.Hooking;
|
||||||
using Dalamud.Interface;
|
|
||||||
using Dalamud.Interface.Internal;
|
using Dalamud.Interface.Internal;
|
||||||
using Dalamud.Interface.Windowing;
|
using Dalamud.Interface.Windowing;
|
||||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||||
|
|
@ -35,15 +34,21 @@ namespace Dalamud.Game.Internal
|
||||||
|
|
||||||
private readonly Hook<AtkUnitBaseReceiveGlobalEvent> hookAtkUnitBaseReceiveGlobalEvent;
|
private readonly Hook<AtkUnitBaseReceiveGlobalEvent> hookAtkUnitBaseReceiveGlobalEvent;
|
||||||
|
|
||||||
|
[ServiceManager.ServiceDependency]
|
||||||
|
private readonly DalamudConfiguration configuration = Service<DalamudConfiguration>.Get();
|
||||||
|
|
||||||
|
[ServiceManager.ServiceDependency]
|
||||||
|
private readonly ContextMenu contextMenu = Service<ContextMenu>.Get();
|
||||||
|
|
||||||
private readonly string locDalamudPlugins;
|
private readonly string locDalamudPlugins;
|
||||||
private readonly string locDalamudSettings;
|
private readonly string locDalamudSettings;
|
||||||
|
|
||||||
[ServiceManager.ServiceConstructor]
|
[ServiceManager.ServiceConstructor]
|
||||||
private DalamudAtkTweaks(SigScanner sigScanner, ContextMenu contextMenu)
|
private DalamudAtkTweaks(SigScanner sigScanner)
|
||||||
{
|
{
|
||||||
var openSystemMenuAddress = sigScanner.ScanText("E8 ?? ?? ?? ?? 32 C0 4C 8B AC 24 ?? ?? ?? ?? 48 8B 8D ?? ?? ?? ??");
|
var openSystemMenuAddress = sigScanner.ScanText("E8 ?? ?? ?? ?? 32 C0 4C 8B AC 24 ?? ?? ?? ?? 48 8B 8D ?? ?? ?? ??");
|
||||||
|
|
||||||
this.hookAgentHudOpenSystemMenu = new Hook<AgentHudOpenSystemMenuPrototype>(openSystemMenuAddress, this.AgentHudOpenSystemMenuDetour);
|
this.hookAgentHudOpenSystemMenu = Hook<AgentHudOpenSystemMenuPrototype>.FromAddress(openSystemMenuAddress, this.AgentHudOpenSystemMenuDetour);
|
||||||
|
|
||||||
var atkValueChangeTypeAddress = sigScanner.ScanText("E8 ?? ?? ?? ?? 45 84 F6 48 8D 4C 24 ??");
|
var atkValueChangeTypeAddress = sigScanner.ScanText("E8 ?? ?? ?? ?? 45 84 F6 48 8D 4C 24 ??");
|
||||||
this.atkValueChangeType = Marshal.GetDelegateForFunctionPointer<AtkValueChangeType>(atkValueChangeTypeAddress);
|
this.atkValueChangeType = Marshal.GetDelegateForFunctionPointer<AtkValueChangeType>(atkValueChangeTypeAddress);
|
||||||
|
|
@ -52,15 +57,15 @@ namespace Dalamud.Game.Internal
|
||||||
this.atkValueSetString = Marshal.GetDelegateForFunctionPointer<AtkValueSetString>(atkValueSetStringAddress);
|
this.atkValueSetString = Marshal.GetDelegateForFunctionPointer<AtkValueSetString>(atkValueSetStringAddress);
|
||||||
|
|
||||||
var uiModuleRequestMainCommandAddress = sigScanner.ScanText("40 53 56 48 81 EC ?? ?? ?? ?? 48 8B 05 ?? ?? ?? ?? 48 33 C4 48 89 84 24 ?? ?? ?? ?? 48 8B 01 8B DA 48 8B F1 FF 90 ?? ?? ?? ??");
|
var uiModuleRequestMainCommandAddress = sigScanner.ScanText("40 53 56 48 81 EC ?? ?? ?? ?? 48 8B 05 ?? ?? ?? ?? 48 33 C4 48 89 84 24 ?? ?? ?? ?? 48 8B 01 8B DA 48 8B F1 FF 90 ?? ?? ?? ??");
|
||||||
this.hookUiModuleRequestMainCommand = new Hook<UiModuleRequestMainCommand>(uiModuleRequestMainCommandAddress, this.UiModuleRequestMainCommandDetour);
|
this.hookUiModuleRequestMainCommand = Hook<UiModuleRequestMainCommand>.FromAddress(uiModuleRequestMainCommandAddress, this.UiModuleRequestMainCommandDetour);
|
||||||
|
|
||||||
var atkUnitBaseReceiveGlobalEventAddress = sigScanner.ScanText("48 89 5C 24 ?? 48 89 7C 24 ?? 55 41 56 41 57 48 8B EC 48 83 EC 50 44 0F B7 F2 ");
|
var atkUnitBaseReceiveGlobalEventAddress = sigScanner.ScanText("48 89 5C 24 ?? 48 89 7C 24 ?? 55 41 56 41 57 48 8B EC 48 83 EC 50 44 0F B7 F2 ");
|
||||||
this.hookAtkUnitBaseReceiveGlobalEvent = new Hook<AtkUnitBaseReceiveGlobalEvent>(atkUnitBaseReceiveGlobalEventAddress, this.AtkUnitBaseReceiveGlobalEventDetour);
|
this.hookAtkUnitBaseReceiveGlobalEvent = Hook<AtkUnitBaseReceiveGlobalEvent>.FromAddress(atkUnitBaseReceiveGlobalEventAddress, this.AtkUnitBaseReceiveGlobalEventDetour);
|
||||||
|
|
||||||
this.locDalamudPlugins = Loc.Localize("SystemMenuPlugins", "Dalamud Plugins");
|
this.locDalamudPlugins = Loc.Localize("SystemMenuPlugins", "Dalamud Plugins");
|
||||||
this.locDalamudSettings = Loc.Localize("SystemMenuSettings", "Dalamud Settings");
|
this.locDalamudSettings = Loc.Localize("SystemMenuSettings", "Dalamud Settings");
|
||||||
|
|
||||||
contextMenu.ContextMenuOpened += this.ContextMenuOnContextMenuOpened;
|
this.contextMenu.ContextMenuOpened += this.ContextMenuOnContextMenuOpened;
|
||||||
}
|
}
|
||||||
|
|
||||||
private delegate void AgentHudOpenSystemMenuPrototype(void* thisPtr, AtkValue* atkValueArgs, uint menuSize);
|
private delegate void AgentHudOpenSystemMenuPrototype(void* thisPtr, AtkValue* atkValueArgs, uint menuSize);
|
||||||
|
|
@ -83,11 +88,13 @@ namespace Dalamud.Game.Internal
|
||||||
|
|
||||||
private void ContextMenuOnContextMenuOpened(ContextMenuOpenedArgs args)
|
private void ContextMenuOnContextMenuOpened(ContextMenuOpenedArgs args)
|
||||||
{
|
{
|
||||||
var systemText = Service<DataManager>.Get().GetExcelSheet<Addon>()!.GetRow(1059)!.Text.RawString; // "System"
|
var systemText = Service<DataManager>.GetNullable()?.GetExcelSheet<Addon>()?.GetRow(1059)?.Text?.RawString; // "System"
|
||||||
var configuration = Service<DalamudConfiguration>.Get();
|
var interfaceManager = Service<InterfaceManager>.GetNullable();
|
||||||
var interfaceManager = Service<InterfaceManager>.Get();
|
|
||||||
|
|
||||||
if (args.Title == systemText && configuration.DoButtonsSystemMenu && interfaceManager.IsDispatchingEvents)
|
if (systemText == null || interfaceManager == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (args.Title == systemText && this.configuration.DoButtonsSystemMenu && interfaceManager.IsDispatchingEvents)
|
||||||
{
|
{
|
||||||
var dalamudInterface = Service<DalamudInterface>.Get();
|
var dalamudInterface = Service<DalamudInterface>.Get();
|
||||||
|
|
||||||
|
|
@ -109,7 +116,7 @@ namespace Dalamud.Game.Internal
|
||||||
|
|
||||||
// "SendHotkey"
|
// "SendHotkey"
|
||||||
// 3 == Close
|
// 3 == Close
|
||||||
if (cmd == 12 && WindowSystem.HasAnyWindowSystemFocus && *arg == 3 && Service<DalamudConfiguration>.Get().IsFocusManagementEnabled)
|
if (cmd == 12 && WindowSystem.HasAnyWindowSystemFocus && *arg == 3 && this.configuration.IsFocusManagementEnabled)
|
||||||
{
|
{
|
||||||
Log.Verbose($"Cancelling global event SendHotkey command due to WindowSystem {WindowSystem.FocusedWindowSystemNamespace}");
|
Log.Verbose($"Cancelling global event SendHotkey command due to WindowSystem {WindowSystem.FocusedWindowSystemNamespace}");
|
||||||
return IntPtr.Zero;
|
return IntPtr.Zero;
|
||||||
|
|
@ -120,14 +127,18 @@ namespace Dalamud.Game.Internal
|
||||||
|
|
||||||
private void AgentHudOpenSystemMenuDetour(void* thisPtr, AtkValue* atkValueArgs, uint menuSize)
|
private void AgentHudOpenSystemMenuDetour(void* thisPtr, AtkValue* atkValueArgs, uint menuSize)
|
||||||
{
|
{
|
||||||
if (WindowSystem.HasAnyWindowSystemFocus && Service<DalamudConfiguration>.Get().IsFocusManagementEnabled)
|
if (WindowSystem.HasAnyWindowSystemFocus && this.configuration.IsFocusManagementEnabled)
|
||||||
{
|
{
|
||||||
Log.Verbose($"Cancelling OpenSystemMenu due to WindowSystem {WindowSystem.FocusedWindowSystemNamespace}");
|
Log.Verbose($"Cancelling OpenSystemMenu due to WindowSystem {WindowSystem.FocusedWindowSystemNamespace}");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var configuration = Service<DalamudConfiguration>.Get();
|
var interfaceManager = Service<InterfaceManager>.GetNullable();
|
||||||
var interfaceManager = Service<InterfaceManager>.Get();
|
if (interfaceManager == null)
|
||||||
|
{
|
||||||
|
this.hookAgentHudOpenSystemMenu.Original(thisPtr, atkValueArgs, menuSize);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (!configuration.DoButtonsSystemMenu || !interfaceManager.IsDispatchingEvents)
|
if (!configuration.DoButtonsSystemMenu || !interfaceManager.IsDispatchingEvents)
|
||||||
{
|
{
|
||||||
|
|
@ -207,15 +218,15 @@ namespace Dalamud.Game.Internal
|
||||||
|
|
||||||
private void UiModuleRequestMainCommandDetour(void* thisPtr, int commandId)
|
private void UiModuleRequestMainCommandDetour(void* thisPtr, int commandId)
|
||||||
{
|
{
|
||||||
var dalamudInterface = Service<DalamudInterface>.Get();
|
var dalamudInterface = Service<DalamudInterface>.GetNullable();
|
||||||
|
|
||||||
switch (commandId)
|
switch (commandId)
|
||||||
{
|
{
|
||||||
case 69420:
|
case 69420:
|
||||||
dalamudInterface.TogglePluginInstallerWindow();
|
dalamudInterface?.TogglePluginInstallerWindow();
|
||||||
break;
|
break;
|
||||||
case 69421:
|
case 69421:
|
||||||
dalamudInterface.ToggleSettingsWindow();
|
dalamudInterface?.ToggleSettingsWindow();
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
this.hookUiModuleRequestMainCommand.Original(thisPtr, commandId);
|
this.hookUiModuleRequestMainCommand.Original(thisPtr, commandId);
|
||||||
|
|
@ -259,7 +270,7 @@ namespace Dalamud.Game.Internal
|
||||||
this.hookUiModuleRequestMainCommand.Dispose();
|
this.hookUiModuleRequestMainCommand.Dispose();
|
||||||
this.hookAtkUnitBaseReceiveGlobalEvent.Dispose();
|
this.hookAtkUnitBaseReceiveGlobalEvent.Dispose();
|
||||||
|
|
||||||
Service<ContextMenu>.Get().ContextMenuOpened -= this.ContextMenuOnContextMenuOpened;
|
this.contextMenu.ContextMenuOpened -= this.ContextMenuOnContextMenuOpened;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.disposed = true;
|
this.disposed = true;
|
||||||
|
|
|
||||||
|
|
@ -34,8 +34,8 @@ namespace Dalamud.Game.Network
|
||||||
Log.Verbose($"ProcessZonePacketDown address 0x{this.address.ProcessZonePacketDown.ToInt64():X}");
|
Log.Verbose($"ProcessZonePacketDown address 0x{this.address.ProcessZonePacketDown.ToInt64():X}");
|
||||||
Log.Verbose($"ProcessZonePacketUp address 0x{this.address.ProcessZonePacketUp.ToInt64():X}");
|
Log.Verbose($"ProcessZonePacketUp address 0x{this.address.ProcessZonePacketUp.ToInt64():X}");
|
||||||
|
|
||||||
this.processZonePacketDownHook = new Hook<ProcessZonePacketDownDelegate>(this.address.ProcessZonePacketDown, this.ProcessZonePacketDownDetour);
|
this.processZonePacketDownHook = Hook<ProcessZonePacketDownDelegate>.FromAddress(this.address.ProcessZonePacketDown, this.ProcessZonePacketDownDetour);
|
||||||
this.processZonePacketUpHook = new Hook<ProcessZonePacketUpDelegate>(this.address.ProcessZonePacketUp, this.ProcessZonePacketUpDetour);
|
this.processZonePacketUpHook = Hook<ProcessZonePacketUpDelegate>.FromAddress(this.address.ProcessZonePacketUp, this.ProcessZonePacketUpDetour);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,9 @@ namespace Dalamud.Game.Network.Internal.MarketBoardUploaders.Universalis
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public async Task Upload(MarketBoardItemRequest request)
|
public async Task Upload(MarketBoardItemRequest request)
|
||||||
{
|
{
|
||||||
var clientState = Service<ClientState.ClientState>.Get();
|
var clientState = Service<ClientState.ClientState>.GetNullable();
|
||||||
|
if (clientState == null)
|
||||||
|
return;
|
||||||
|
|
||||||
Log.Verbose("Starting Universalis upload.");
|
Log.Verbose("Starting Universalis upload.");
|
||||||
var uploader = clientState.LocalContentId;
|
var uploader = clientState.LocalContentId;
|
||||||
|
|
@ -118,7 +120,9 @@ namespace Dalamud.Game.Network.Internal.MarketBoardUploaders.Universalis
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public async Task UploadTax(MarketTaxRates taxRates)
|
public async Task UploadTax(MarketTaxRates taxRates)
|
||||||
{
|
{
|
||||||
var clientState = Service<ClientState.ClientState>.Get();
|
var clientState = Service<ClientState.ClientState>.GetNullable();
|
||||||
|
if (clientState == null)
|
||||||
|
return;
|
||||||
|
|
||||||
// ====================================================================================
|
// ====================================================================================
|
||||||
|
|
||||||
|
|
@ -157,7 +161,9 @@ namespace Dalamud.Game.Network.Internal.MarketBoardUploaders.Universalis
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
public async Task UploadPurchase(MarketBoardPurchaseHandler purchaseHandler)
|
public async Task UploadPurchase(MarketBoardPurchaseHandler purchaseHandler)
|
||||||
{
|
{
|
||||||
var clientState = Service<ClientState.ClientState>.Get();
|
var clientState = Service<ClientState.ClientState>.GetNullable();
|
||||||
|
if (clientState == null)
|
||||||
|
return;
|
||||||
|
|
||||||
var itemId = purchaseHandler.CatalogId;
|
var itemId = purchaseHandler.CatalogId;
|
||||||
var worldId = clientState.LocalPlayer?.CurrentWorld.Id ?? 0;
|
var worldId = clientState.LocalPlayer?.CurrentWorld.Id ?? 0;
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,9 @@ namespace Dalamud.Game.Network.Internal
|
||||||
|
|
||||||
private readonly IMarketBoardUploader uploader;
|
private readonly IMarketBoardUploader uploader;
|
||||||
|
|
||||||
|
[ServiceManager.ServiceDependency]
|
||||||
|
private readonly DalamudConfiguration configuration = Service<DalamudConfiguration>.Get();
|
||||||
|
|
||||||
private MarketBoardPurchaseHandler marketBoardPurchaseHandler;
|
private MarketBoardPurchaseHandler marketBoardPurchaseHandler;
|
||||||
|
|
||||||
[ServiceManager.ServiceConstructor]
|
[ServiceManager.ServiceConstructor]
|
||||||
|
|
@ -47,14 +50,12 @@ namespace Dalamud.Game.Network.Internal
|
||||||
{
|
{
|
||||||
var dataManager = Service<DataManager>.GetNullable();
|
var dataManager = Service<DataManager>.GetNullable();
|
||||||
|
|
||||||
if (dataManager?.IsDataReady == false)
|
if (dataManager?.IsDataReady != true)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var configuration = Service<DalamudConfiguration>.Get();
|
|
||||||
|
|
||||||
if (direction == NetworkMessageDirection.ZoneUp)
|
if (direction == NetworkMessageDirection.ZoneUp)
|
||||||
{
|
{
|
||||||
if (configuration.IsMbCollect)
|
if (this.configuration.IsMbCollect)
|
||||||
{
|
{
|
||||||
if (opCode == dataManager.ClientOpCodes["MarketBoardPurchaseHandler"])
|
if (opCode == dataManager.ClientOpCodes["MarketBoardPurchaseHandler"])
|
||||||
{
|
{
|
||||||
|
|
@ -72,7 +73,7 @@ namespace Dalamud.Game.Network.Internal
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (configuration.IsMbCollect)
|
if (this.configuration.IsMbCollect)
|
||||||
{
|
{
|
||||||
if (opCode == dataManager.ServerOpCodes["MarketBoardItemRequestStart"])
|
if (opCode == dataManager.ServerOpCodes["MarketBoardItemRequestStart"])
|
||||||
{
|
{
|
||||||
|
|
@ -236,8 +237,9 @@ namespace Dalamud.Game.Network.Internal
|
||||||
|
|
||||||
private unsafe void HandleCfPop(IntPtr dataPtr)
|
private unsafe void HandleCfPop(IntPtr dataPtr)
|
||||||
{
|
{
|
||||||
var dataManager = Service<DataManager>.Get();
|
var dataManager = Service<DataManager>.GetNullable();
|
||||||
var configuration = Service<DalamudConfiguration>.Get();
|
if (dataManager == null)
|
||||||
|
return;
|
||||||
|
|
||||||
using var stream = new UnmanagedMemoryStream((byte*)dataPtr.ToPointer(), 64);
|
using var stream = new UnmanagedMemoryStream((byte*)dataPtr.ToPointer(), 64);
|
||||||
using var reader = new BinaryReader(stream);
|
using var reader = new BinaryReader(stream);
|
||||||
|
|
@ -266,7 +268,7 @@ namespace Dalamud.Game.Network.Internal
|
||||||
}
|
}
|
||||||
|
|
||||||
// Flash window
|
// Flash window
|
||||||
if (configuration.DutyFinderTaskbarFlash && !NativeFunctions.ApplicationIsActivated())
|
if (this.configuration.DutyFinderTaskbarFlash && !NativeFunctions.ApplicationIsActivated())
|
||||||
{
|
{
|
||||||
var flashInfo = new NativeFunctions.FlashWindowInfo
|
var flashInfo = new NativeFunctions.FlashWindowInfo
|
||||||
{
|
{
|
||||||
|
|
@ -281,9 +283,9 @@ namespace Dalamud.Game.Network.Internal
|
||||||
|
|
||||||
Task.Run(() =>
|
Task.Run(() =>
|
||||||
{
|
{
|
||||||
if (configuration.DutyFinderChatMessage)
|
if (this.configuration.DutyFinderChatMessage)
|
||||||
{
|
{
|
||||||
Service<ChatGui>.Get().Print($"Duty pop: {cfcName}");
|
Service<ChatGui>.GetNullable()?.Print($"Duty pop: {cfcName}");
|
||||||
}
|
}
|
||||||
|
|
||||||
this.CfPop?.Invoke(this, cfCondition);
|
this.CfPop?.Invoke(this, cfCondition);
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ namespace Dalamud.Game.Network.Internal
|
||||||
[ServiceManager.ServiceConstructor]
|
[ServiceManager.ServiceConstructor]
|
||||||
private WinSockHandlers()
|
private WinSockHandlers()
|
||||||
{
|
{
|
||||||
this.ws2SocketHook = Hook<SocketDelegate>.FromImport(Process.GetCurrentProcess().MainModule, "ws2_32.dll", "socket", 23, this.OnSocket);
|
this.ws2SocketHook = Hook<SocketDelegate>.FromImport(null, "ws2_32.dll", "socket", 23, this.OnSocket);
|
||||||
this.ws2SocketHook?.Enable();
|
this.ws2SocketHook?.Enable();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -359,6 +359,7 @@ namespace Dalamud.Game
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
|
this.Save();
|
||||||
Marshal.FreeHGlobal(this.moduleCopyPtr);
|
Marshal.FreeHGlobal(this.moduleCopyPtr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -370,7 +371,14 @@ namespace Dalamud.Game
|
||||||
if (this.cacheFile == null)
|
if (this.cacheFile == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
File.WriteAllText(this.cacheFile.FullName, JsonConvert.SerializeObject(this.textCache));
|
try
|
||||||
|
{
|
||||||
|
File.WriteAllText(this.cacheFile.FullName, JsonConvert.SerializeObject(this.textCache));
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.Warning(e, "Failed to save cache to {0}", this.cacheFile);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
||||||
|
|
@ -88,6 +88,22 @@ namespace Dalamud.Hooking
|
||||||
/// <exception cref="ObjectDisposedException">Hook is already disposed.</exception>
|
/// <exception cref="ObjectDisposedException">Hook is already disposed.</exception>
|
||||||
public virtual T Original => this.compatHookImpl != null ? this.compatHookImpl!.Original : throw new NotImplementedException();
|
public virtual T Original => this.compatHookImpl != null ? this.compatHookImpl!.Original : throw new NotImplementedException();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a delegate function that can be used to call the actual function as if function is not hooked yet.
|
||||||
|
/// This can be called even after Dispose.
|
||||||
|
/// </summary>
|
||||||
|
public T OriginalDisposeSafe
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (this.compatHookImpl != null)
|
||||||
|
return this.compatHookImpl!.OriginalDisposeSafe;
|
||||||
|
if (this.IsDisposed)
|
||||||
|
return Marshal.GetDelegateForFunctionPointer<T>(this.address);
|
||||||
|
return this.Original;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets a value indicating whether or not the hook is enabled.
|
/// Gets a value indicating whether or not the hook is enabled.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -115,14 +131,17 @@ namespace Dalamud.Hooking
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a hook by rewriting import table address.
|
/// Creates a hook by rewriting import table address.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="module">Module to check for.</param>
|
/// <param name="module">Module to check for. Current process' main module if null.</param>
|
||||||
/// <param name="moduleName">Name of the DLL, including the extension.</param>
|
/// <param name="moduleName">Name of the DLL, including the extension.</param>
|
||||||
/// <param name="functionName">Decorated name of the function.</param>
|
/// <param name="functionName">Decorated name of the function.</param>
|
||||||
/// <param name="hintOrOrdinal">Hint or ordinal. 0 to unspecify.</param>
|
/// <param name="hintOrOrdinal">Hint or ordinal. 0 to unspecify.</param>
|
||||||
/// <param name="detour">Callback function. Delegate must have a same original function prototype.</param>
|
/// <param name="detour">Callback function. Delegate must have a same original function prototype.</param>
|
||||||
/// <returns>The hook with the supplied parameters.</returns>
|
/// <returns>The hook with the supplied parameters.</returns>
|
||||||
public static unsafe Hook<T> FromImport(ProcessModule module, string moduleName, string functionName, uint hintOrOrdinal, T detour)
|
public static unsafe Hook<T> FromImport(ProcessModule? module, string moduleName, string functionName, uint hintOrOrdinal, T detour)
|
||||||
{
|
{
|
||||||
|
module ??= Process.GetCurrentProcess().MainModule;
|
||||||
|
if (module == null)
|
||||||
|
throw new InvalidOperationException("Current module is null?");
|
||||||
var pDos = (PeHeader.IMAGE_DOS_HEADER*)module.BaseAddress;
|
var pDos = (PeHeader.IMAGE_DOS_HEADER*)module.BaseAddress;
|
||||||
var pNt = (PeHeader.IMAGE_FILE_HEADER*)(module.BaseAddress + (int)pDos->e_lfanew + 4);
|
var pNt = (PeHeader.IMAGE_FILE_HEADER*)(module.BaseAddress + (int)pDos->e_lfanew + 4);
|
||||||
var isPe64 = pNt->SizeOfOptionalHeader == Marshal.SizeOf<PeHeader.IMAGE_OPTIONAL_HEADER64>();
|
var isPe64 = pNt->SizeOfOptionalHeader == Marshal.SizeOf<PeHeader.IMAGE_OPTIONAL_HEADER64>();
|
||||||
|
|
|
||||||
|
|
@ -27,24 +27,27 @@ namespace Dalamud.Hooking.Internal
|
||||||
internal FunctionPointerVariableHook(IntPtr address, T detour, Assembly callingAssembly)
|
internal FunctionPointerVariableHook(IntPtr address, T detour, Assembly callingAssembly)
|
||||||
: base(address)
|
: base(address)
|
||||||
{
|
{
|
||||||
var hasOtherHooks = HookManager.Originals.ContainsKey(this.Address);
|
lock (HookManager.HookEnableSyncRoot)
|
||||||
if (!hasOtherHooks)
|
|
||||||
{
|
{
|
||||||
MemoryHelper.ReadRaw(this.Address, 0x32, out var original);
|
var hasOtherHooks = HookManager.Originals.ContainsKey(this.Address);
|
||||||
HookManager.Originals[this.Address] = original;
|
if (!hasOtherHooks)
|
||||||
|
{
|
||||||
|
MemoryHelper.ReadRaw(this.Address, 0x32, out var original);
|
||||||
|
HookManager.Originals[this.Address] = original;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!HookManager.MultiHookTracker.TryGetValue(this.Address, out var indexList))
|
||||||
|
indexList = HookManager.MultiHookTracker[this.Address] = new();
|
||||||
|
|
||||||
|
this.pfnOriginal = Marshal.ReadIntPtr(this.Address);
|
||||||
|
this.originalDelegate = Marshal.GetDelegateForFunctionPointer<T>(this.pfnOriginal);
|
||||||
|
this.detourDelegate = detour;
|
||||||
|
|
||||||
|
// Add afterwards, so the hookIdent starts at 0.
|
||||||
|
indexList.Add(this);
|
||||||
|
|
||||||
|
HookManager.TrackedHooks.TryAdd(Guid.NewGuid(), new HookInfo(this, detour, callingAssembly));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!HookManager.MultiHookTracker.TryGetValue(this.Address, out var indexList))
|
|
||||||
indexList = HookManager.MultiHookTracker[this.Address] = new();
|
|
||||||
|
|
||||||
this.pfnOriginal = Marshal.ReadIntPtr(this.Address);
|
|
||||||
this.originalDelegate = Marshal.GetDelegateForFunctionPointer<T>(this.pfnOriginal);
|
|
||||||
this.detourDelegate = detour;
|
|
||||||
|
|
||||||
// Add afterwards, so the hookIdent starts at 0.
|
|
||||||
indexList.Add(this);
|
|
||||||
|
|
||||||
HookManager.TrackedHooks.TryAdd(Guid.NewGuid(), new HookInfo(this, detour, callingAssembly));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
|
|
@ -91,11 +94,15 @@ namespace Dalamud.Hooking.Internal
|
||||||
|
|
||||||
if (!this.enabled)
|
if (!this.enabled)
|
||||||
{
|
{
|
||||||
if (!NativeFunctions.VirtualProtect(this.Address, (UIntPtr)Marshal.SizeOf<IntPtr>(), MemoryProtection.ExecuteReadWrite, out var oldProtect))
|
lock (HookManager.HookEnableSyncRoot)
|
||||||
throw new Win32Exception(Marshal.GetLastWin32Error());
|
{
|
||||||
|
if (!NativeFunctions.VirtualProtect(this.Address, (UIntPtr)Marshal.SizeOf<IntPtr>(),
|
||||||
|
MemoryProtection.ExecuteReadWrite, out var oldProtect))
|
||||||
|
throw new Win32Exception(Marshal.GetLastWin32Error());
|
||||||
|
|
||||||
Marshal.WriteIntPtr(this.Address, Marshal.GetFunctionPointerForDelegate(this.detourDelegate));
|
Marshal.WriteIntPtr(this.Address, Marshal.GetFunctionPointerForDelegate(this.detourDelegate));
|
||||||
NativeFunctions.VirtualProtect(this.Address, (UIntPtr)Marshal.SizeOf<IntPtr>(), oldProtect, out _);
|
NativeFunctions.VirtualProtect(this.Address, (UIntPtr)Marshal.SizeOf<IntPtr>(), oldProtect, out _);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -106,11 +113,15 @@ namespace Dalamud.Hooking.Internal
|
||||||
|
|
||||||
if (this.enabled)
|
if (this.enabled)
|
||||||
{
|
{
|
||||||
if (!NativeFunctions.VirtualProtect(this.Address, (UIntPtr)Marshal.SizeOf<IntPtr>(), MemoryProtection.ExecuteReadWrite, out var oldProtect))
|
lock (HookManager.HookEnableSyncRoot)
|
||||||
throw new Win32Exception(Marshal.GetLastWin32Error());
|
{
|
||||||
|
if (!NativeFunctions.VirtualProtect(this.Address, (UIntPtr)Marshal.SizeOf<IntPtr>(),
|
||||||
|
MemoryProtection.ExecuteReadWrite, out var oldProtect))
|
||||||
|
throw new Win32Exception(Marshal.GetLastWin32Error());
|
||||||
|
|
||||||
Marshal.WriteIntPtr(this.Address, this.pfnOriginal);
|
Marshal.WriteIntPtr(this.Address, this.pfnOriginal);
|
||||||
NativeFunctions.VirtualProtect(this.Address, (UIntPtr)Marshal.SizeOf<IntPtr>(), oldProtect, out _);
|
NativeFunctions.VirtualProtect(this.Address, (UIntPtr)Marshal.SizeOf<IntPtr>(), oldProtect, out _);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,11 @@ namespace Dalamud.Hooking.Internal
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets sync root object for hook enabling/disabling.
|
||||||
|
/// </summary>
|
||||||
|
internal static object HookEnableSyncRoot { get; } = new();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets a static list of tracked and registered hooks.
|
/// Gets a static list of tracked and registered hooks.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
||||||
|
|
@ -22,24 +22,27 @@ namespace Dalamud.Hooking.Internal
|
||||||
internal MinHookHook(IntPtr address, T detour, Assembly callingAssembly)
|
internal MinHookHook(IntPtr address, T detour, Assembly callingAssembly)
|
||||||
: base(address)
|
: base(address)
|
||||||
{
|
{
|
||||||
var hasOtherHooks = HookManager.Originals.ContainsKey(this.Address);
|
lock (HookManager.HookEnableSyncRoot)
|
||||||
if (!hasOtherHooks)
|
|
||||||
{
|
{
|
||||||
MemoryHelper.ReadRaw(this.Address, 0x32, out var original);
|
var hasOtherHooks = HookManager.Originals.ContainsKey(this.Address);
|
||||||
HookManager.Originals[this.Address] = original;
|
if (!hasOtherHooks)
|
||||||
|
{
|
||||||
|
MemoryHelper.ReadRaw(this.Address, 0x32, out var original);
|
||||||
|
HookManager.Originals[this.Address] = original;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!HookManager.MultiHookTracker.TryGetValue(this.Address, out var indexList))
|
||||||
|
indexList = HookManager.MultiHookTracker[this.Address] = new();
|
||||||
|
|
||||||
|
var index = (ulong)indexList.Count;
|
||||||
|
|
||||||
|
this.minHookImpl = new MinSharp.Hook<T>(this.Address, detour, index);
|
||||||
|
|
||||||
|
// Add afterwards, so the hookIdent starts at 0.
|
||||||
|
indexList.Add(this);
|
||||||
|
|
||||||
|
HookManager.TrackedHooks.TryAdd(Guid.NewGuid(), new HookInfo(this, detour, callingAssembly));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!HookManager.MultiHookTracker.TryGetValue(this.Address, out var indexList))
|
|
||||||
indexList = HookManager.MultiHookTracker[this.Address] = new();
|
|
||||||
|
|
||||||
var index = (ulong)indexList.Count;
|
|
||||||
|
|
||||||
this.minHookImpl = new MinSharp.Hook<T>(this.Address, detour, index);
|
|
||||||
|
|
||||||
// Add afterwards, so the hookIdent starts at 0.
|
|
||||||
indexList.Add(this);
|
|
||||||
|
|
||||||
HookManager.TrackedHooks.TryAdd(Guid.NewGuid(), new HookInfo(this, detour, callingAssembly));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
|
|
@ -71,10 +74,13 @@ namespace Dalamud.Hooking.Internal
|
||||||
if (this.IsDisposed)
|
if (this.IsDisposed)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
this.minHookImpl.Dispose();
|
lock (HookManager.HookEnableSyncRoot)
|
||||||
|
{
|
||||||
|
this.minHookImpl.Dispose();
|
||||||
|
|
||||||
var index = HookManager.MultiHookTracker[this.Address].IndexOf(this);
|
var index = HookManager.MultiHookTracker[this.Address].IndexOf(this);
|
||||||
HookManager.MultiHookTracker[this.Address][index] = null;
|
HookManager.MultiHookTracker[this.Address][index] = null;
|
||||||
|
}
|
||||||
|
|
||||||
base.Dispose();
|
base.Dispose();
|
||||||
}
|
}
|
||||||
|
|
@ -86,7 +92,10 @@ namespace Dalamud.Hooking.Internal
|
||||||
|
|
||||||
if (!this.minHookImpl.Enabled)
|
if (!this.minHookImpl.Enabled)
|
||||||
{
|
{
|
||||||
this.minHookImpl.Enable();
|
lock (HookManager.HookEnableSyncRoot)
|
||||||
|
{
|
||||||
|
this.minHookImpl.Enable();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -97,7 +106,10 @@ namespace Dalamud.Hooking.Internal
|
||||||
|
|
||||||
if (this.minHookImpl.Enabled)
|
if (this.minHookImpl.Enabled)
|
||||||
{
|
{
|
||||||
this.minHookImpl.Disable();
|
lock (HookManager.HookEnableSyncRoot)
|
||||||
|
{
|
||||||
|
this.minHookImpl.Disable();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,16 +19,19 @@ namespace Dalamud.Hooking.Internal
|
||||||
internal ReloadedHook(IntPtr address, T detour, Assembly callingAssembly)
|
internal ReloadedHook(IntPtr address, T detour, Assembly callingAssembly)
|
||||||
: base(address)
|
: base(address)
|
||||||
{
|
{
|
||||||
var hasOtherHooks = HookManager.Originals.ContainsKey(this.Address);
|
lock (HookManager.HookEnableSyncRoot)
|
||||||
if (!hasOtherHooks)
|
|
||||||
{
|
{
|
||||||
MemoryHelper.ReadRaw(this.Address, 0x32, out var original);
|
var hasOtherHooks = HookManager.Originals.ContainsKey(this.Address);
|
||||||
HookManager.Originals[this.Address] = original;
|
if (!hasOtherHooks)
|
||||||
|
{
|
||||||
|
MemoryHelper.ReadRaw(this.Address, 0x32, out var original);
|
||||||
|
HookManager.Originals[this.Address] = original;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.hookImpl = ReloadedHooks.Instance.CreateHook<T>(detour, address.ToInt64());
|
||||||
|
|
||||||
|
HookManager.TrackedHooks.TryAdd(Guid.NewGuid(), new HookInfo(this, detour, callingAssembly));
|
||||||
}
|
}
|
||||||
|
|
||||||
this.hookImpl = ReloadedHooks.Instance.CreateHook<T>(detour, address.ToInt64());
|
|
||||||
|
|
||||||
HookManager.TrackedHooks.TryAdd(Guid.NewGuid(), new HookInfo(this, detour, callingAssembly));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
|
|
@ -70,11 +73,14 @@ namespace Dalamud.Hooking.Internal
|
||||||
{
|
{
|
||||||
this.CheckDisposed();
|
this.CheckDisposed();
|
||||||
|
|
||||||
if (!this.hookImpl.IsHookActivated)
|
lock (HookManager.HookEnableSyncRoot)
|
||||||
this.hookImpl.Activate();
|
{
|
||||||
|
if (!this.hookImpl.IsHookActivated)
|
||||||
|
this.hookImpl.Activate();
|
||||||
|
|
||||||
if (!this.hookImpl.IsHookEnabled)
|
if (!this.hookImpl.IsHookEnabled)
|
||||||
this.hookImpl.Enable();
|
this.hookImpl.Enable();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
|
|
@ -82,11 +88,14 @@ namespace Dalamud.Hooking.Internal
|
||||||
{
|
{
|
||||||
this.CheckDisposed();
|
this.CheckDisposed();
|
||||||
|
|
||||||
if (!this.hookImpl.IsHookActivated)
|
lock (HookManager.HookEnableSyncRoot)
|
||||||
return;
|
{
|
||||||
|
if (!this.hookImpl.IsHookActivated)
|
||||||
|
return;
|
||||||
|
|
||||||
if (this.hookImpl.IsHookEnabled)
|
if (this.hookImpl.IsHookEnabled)
|
||||||
this.hookImpl.Disable();
|
this.hookImpl.Disable();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ namespace Dalamud.Interface.GameFonts
|
||||||
/// Loads game font for use in ImGui.
|
/// Loads game font for use in ImGui.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[ServiceManager.EarlyLoadedService]
|
[ServiceManager.EarlyLoadedService]
|
||||||
internal class GameFontManager : IDisposable, IServiceType
|
internal class GameFontManager : IServiceType
|
||||||
{
|
{
|
||||||
private static readonly string?[] FontNames =
|
private static readonly string?[] FontNames =
|
||||||
{
|
{
|
||||||
|
|
@ -158,11 +158,6 @@ namespace Dalamud.Interface.GameFonts
|
||||||
fontPtr.BuildLookupTable();
|
fontPtr.BuildLookupTable();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a new GameFontHandle, and increases internal font reference counter, and if it's first time use, then the font will be loaded on next font building process.
|
/// Creates a new GameFontHandle, and increases internal font reference counter, and if it's first time use, then the font will be loaded on next font building process.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
||||||
|
|
@ -60,8 +60,6 @@ namespace Dalamud.Interface.Internal
|
||||||
private readonly TextureWrap logoTexture;
|
private readonly TextureWrap logoTexture;
|
||||||
private readonly TextureWrap tsmLogoTexture;
|
private readonly TextureWrap tsmLogoTexture;
|
||||||
|
|
||||||
private ulong frameCount = 0;
|
|
||||||
|
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
private bool isImGuiDrawDevMenu = true;
|
private bool isImGuiDrawDevMenu = true;
|
||||||
#else
|
#else
|
||||||
|
|
@ -80,7 +78,8 @@ namespace Dalamud.Interface.Internal
|
||||||
private DalamudInterface(
|
private DalamudInterface(
|
||||||
Dalamud dalamud,
|
Dalamud dalamud,
|
||||||
DalamudConfiguration configuration,
|
DalamudConfiguration configuration,
|
||||||
InterfaceManager.InterfaceManagerWithScene interfaceManagerWithScene)
|
InterfaceManager.InterfaceManagerWithScene interfaceManagerWithScene,
|
||||||
|
PluginImageCache pluginImageCache)
|
||||||
{
|
{
|
||||||
var interfaceManager = interfaceManagerWithScene.Manager;
|
var interfaceManager = interfaceManagerWithScene.Manager;
|
||||||
this.WindowSystem = new WindowSystem("DalamudCore");
|
this.WindowSystem = new WindowSystem("DalamudCore");
|
||||||
|
|
@ -94,7 +93,7 @@ namespace Dalamud.Interface.Internal
|
||||||
this.imeWindow = new IMEWindow() { IsOpen = false };
|
this.imeWindow = new IMEWindow() { IsOpen = false };
|
||||||
this.consoleWindow = new ConsoleWindow() { IsOpen = configuration.LogOpenAtStartup };
|
this.consoleWindow = new ConsoleWindow() { IsOpen = configuration.LogOpenAtStartup };
|
||||||
this.pluginStatWindow = new PluginStatWindow() { IsOpen = false };
|
this.pluginStatWindow = new PluginStatWindow() { IsOpen = false };
|
||||||
this.pluginWindow = new PluginInstallerWindow() { IsOpen = false };
|
this.pluginWindow = new PluginInstallerWindow(pluginImageCache) { IsOpen = false };
|
||||||
this.settingsWindow = new SettingsWindow() { IsOpen = false };
|
this.settingsWindow = new SettingsWindow() { IsOpen = false };
|
||||||
this.selfTestWindow = new SelfTestWindow() { IsOpen = false };
|
this.selfTestWindow = new SelfTestWindow() { IsOpen = false };
|
||||||
this.styleEditorWindow = new StyleEditorWindow() { IsOpen = false };
|
this.styleEditorWindow = new StyleEditorWindow() { IsOpen = false };
|
||||||
|
|
@ -138,15 +137,20 @@ namespace Dalamud.Interface.Internal
|
||||||
this.tsmLogoTexture = tsmLogoTex;
|
this.tsmLogoTexture = tsmLogoTex;
|
||||||
|
|
||||||
var tsm = Service<TitleScreenMenu>.Get();
|
var tsm = Service<TitleScreenMenu>.Get();
|
||||||
tsm.AddEntry(Loc.Localize("TSMDalamudPlugins", "Plugin Installer"), this.tsmLogoTexture, () => this.pluginWindow.IsOpen = true);
|
tsm.AddEntryCore(Loc.Localize("TSMDalamudPlugins", "Plugin Installer"), this.tsmLogoTexture, () => this.pluginWindow.IsOpen = true);
|
||||||
tsm.AddEntry(Loc.Localize("TSMDalamudSettings", "Dalamud Settings"), this.tsmLogoTexture, () => this.settingsWindow.IsOpen = true);
|
tsm.AddEntryCore(Loc.Localize("TSMDalamudSettings", "Dalamud Settings"), this.tsmLogoTexture, () => this.settingsWindow.IsOpen = true);
|
||||||
|
|
||||||
if (configuration.IsConventionalStaging)
|
if (configuration.IsConventionalStaging)
|
||||||
{
|
{
|
||||||
tsm.AddEntry(Loc.Localize("TSMDalamudDevMenu", "Developer Menu"), this.tsmLogoTexture, () => this.isImGuiDrawDevMenu = true);
|
tsm.AddEntryCore(Loc.Localize("TSMDalamudDevMenu", "Developer Menu"), this.tsmLogoTexture, () => this.isImGuiDrawDevMenu = true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the number of frames since Dalamud has loaded.
|
||||||
|
/// </summary>
|
||||||
|
public ulong FrameCount { get; private set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the <see cref="WindowSystem"/> controlling all Dalamud-internal windows.
|
/// Gets the <see cref="WindowSystem"/> controlling all Dalamud-internal windows.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -373,7 +377,7 @@ namespace Dalamud.Interface.Internal
|
||||||
|
|
||||||
private void OnDraw()
|
private void OnDraw()
|
||||||
{
|
{
|
||||||
this.frameCount++;
|
this.FrameCount++;
|
||||||
|
|
||||||
#if BOOT_AGING
|
#if BOOT_AGING
|
||||||
if (this.frameCount > 500 && !this.signaledBoot)
|
if (this.frameCount > 500 && !this.signaledBoot)
|
||||||
|
|
@ -494,9 +498,9 @@ namespace Dalamud.Interface.Internal
|
||||||
{
|
{
|
||||||
foreach (var logLevel in Enum.GetValues(typeof(LogEventLevel)).Cast<LogEventLevel>())
|
foreach (var logLevel in Enum.GetValues(typeof(LogEventLevel)).Cast<LogEventLevel>())
|
||||||
{
|
{
|
||||||
if (ImGui.MenuItem(logLevel + "##logLevelSwitch", string.Empty, dalamud.LogLevelSwitch.MinimumLevel == logLevel))
|
if (ImGui.MenuItem(logLevel + "##logLevelSwitch", string.Empty, EntryPoint.LogLevelSwitch.MinimumLevel == logLevel))
|
||||||
{
|
{
|
||||||
dalamud.LogLevelSwitch.MinimumLevel = logLevel;
|
EntryPoint.LogLevelSwitch.MinimumLevel = logLevel;
|
||||||
configuration.LogLevel = logLevel;
|
configuration.LogLevel = logLevel;
|
||||||
configuration.Save();
|
configuration.Save();
|
||||||
}
|
}
|
||||||
|
|
@ -505,6 +509,19 @@ namespace Dalamud.Interface.Internal
|
||||||
ImGui.EndMenu();
|
ImGui.EndMenu();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var logSynchronously = configuration.LogSynchronously;
|
||||||
|
if (ImGui.MenuItem("Log Synchronously", null, ref logSynchronously))
|
||||||
|
{
|
||||||
|
configuration.LogSynchronously = logSynchronously;
|
||||||
|
configuration.Save();
|
||||||
|
|
||||||
|
var startupInfo = Service<DalamudStartInfo>.Get();
|
||||||
|
EntryPoint.InitLogging(
|
||||||
|
startupInfo.WorkingDirectory!,
|
||||||
|
startupInfo.BootShowConsole,
|
||||||
|
configuration.LogSynchronously);
|
||||||
|
}
|
||||||
|
|
||||||
var antiDebug = Service<AntiDebug>.Get();
|
var antiDebug = Service<AntiDebug>.Get();
|
||||||
if (ImGui.MenuItem("Enable AntiDebug", null, antiDebug.IsEnabled))
|
if (ImGui.MenuItem("Enable AntiDebug", null, antiDebug.IsEnabled))
|
||||||
{
|
{
|
||||||
|
|
@ -584,7 +601,7 @@ namespace Dalamud.Interface.Internal
|
||||||
Marshal.ReadByte(IntPtr.Zero);
|
Marshal.ReadByte(IntPtr.Zero);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ImGui.MenuItem("Crash game"))
|
if (ImGui.MenuItem("Crash game (nullptr)"))
|
||||||
{
|
{
|
||||||
unsafe
|
unsafe
|
||||||
{
|
{
|
||||||
|
|
@ -593,6 +610,15 @@ namespace Dalamud.Interface.Internal
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (ImGui.MenuItem("Crash game (non-nullptr)"))
|
||||||
|
{
|
||||||
|
unsafe
|
||||||
|
{
|
||||||
|
var framework = Framework.Instance();
|
||||||
|
framework->UIModule = (UIModule*)0x12345678;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ImGui.Separator();
|
ImGui.Separator();
|
||||||
|
|
||||||
var isBeta = configuration.DalamudBetaKey == DalamudConfiguration.DalamudCurrentBetaKey;
|
var isBeta = configuration.DalamudBetaKey == DalamudConfiguration.DalamudCurrentBetaKey;
|
||||||
|
|
@ -795,7 +821,7 @@ namespace Dalamud.Interface.Internal
|
||||||
ImGui.PushFont(InterfaceManager.MonoFont);
|
ImGui.PushFont(InterfaceManager.MonoFont);
|
||||||
|
|
||||||
ImGui.BeginMenu(Util.GetGitHash(), false);
|
ImGui.BeginMenu(Util.GetGitHash(), false);
|
||||||
ImGui.BeginMenu(this.frameCount.ToString("000000"), false);
|
ImGui.BeginMenu(this.FrameCount.ToString("000000"), false);
|
||||||
ImGui.BeginMenu(ImGui.GetIO().Framerate.ToString("000"), false);
|
ImGui.BeginMenu(ImGui.GetIO().Framerate.ToString("000"), false);
|
||||||
ImGui.BeginMenu($"{Util.FormatBytes(GC.GetTotalMemory(false))}", false);
|
ImGui.BeginMenu($"{Util.FormatBytes(GC.GetTotalMemory(false))}", false);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -57,6 +57,9 @@ namespace Dalamud.Interface.Internal
|
||||||
private readonly HashSet<SpecialGlyphRequest> glyphRequests = new();
|
private readonly HashSet<SpecialGlyphRequest> glyphRequests = new();
|
||||||
private readonly Dictionary<ImFontPtr, TargetFontModification> loadedFontInfo = new();
|
private readonly Dictionary<ImFontPtr, TargetFontModification> loadedFontInfo = new();
|
||||||
|
|
||||||
|
[ServiceManager.ServiceDependency]
|
||||||
|
private readonly Framework framework = Service<Framework>.Get();
|
||||||
|
|
||||||
private readonly ManualResetEvent fontBuildSignal;
|
private readonly ManualResetEvent fontBuildSignal;
|
||||||
private readonly SwapChainVtableResolver address;
|
private readonly SwapChainVtableResolver address;
|
||||||
private readonly Hook<DispatchMessageWDelegate> dispatchMessageWHook;
|
private readonly Hook<DispatchMessageWDelegate> dispatchMessageWHook;
|
||||||
|
|
@ -73,12 +76,12 @@ namespace Dalamud.Interface.Internal
|
||||||
private bool isOverrideGameCursor = true;
|
private bool isOverrideGameCursor = true;
|
||||||
|
|
||||||
[ServiceManager.ServiceConstructor]
|
[ServiceManager.ServiceConstructor]
|
||||||
private InterfaceManager(SigScanner sigScanner)
|
private InterfaceManager()
|
||||||
{
|
{
|
||||||
this.dispatchMessageWHook = Hook<DispatchMessageWDelegate>.FromImport(
|
this.dispatchMessageWHook = Hook<DispatchMessageWDelegate>.FromImport(
|
||||||
Process.GetCurrentProcess().MainModule, "user32.dll", "DispatchMessageW", 0, this.DispatchMessageWDetour);
|
null, "user32.dll", "DispatchMessageW", 0, this.DispatchMessageWDetour);
|
||||||
this.setCursorHook = Hook<SetCursorDelegate>.FromImport(
|
this.setCursorHook = Hook<SetCursorDelegate>.FromImport(
|
||||||
Process.GetCurrentProcess().MainModule, "user32.dll", "SetCursor", 0, this.SetCursorDetour);
|
null, "user32.dll", "SetCursor", 0, this.SetCursorDetour);
|
||||||
|
|
||||||
this.fontBuildSignal = new ManualResetEvent(false);
|
this.fontBuildSignal = new ManualResetEvent(false);
|
||||||
|
|
||||||
|
|
@ -91,12 +94,6 @@ namespace Dalamud.Interface.Internal
|
||||||
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
|
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
|
||||||
private delegate IntPtr ResizeBuffersDelegate(IntPtr swapChain, uint bufferCount, uint width, uint height, uint newFormat, uint swapChainFlags);
|
private delegate IntPtr ResizeBuffersDelegate(IntPtr swapChain, uint bufferCount, uint width, uint height, uint newFormat, uint swapChainFlags);
|
||||||
|
|
||||||
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
|
|
||||||
private delegate int CreateDXGIFactoryDelegate(Guid riid, out IntPtr ppFactory);
|
|
||||||
|
|
||||||
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
|
|
||||||
private delegate int IDXGIFactory_CreateSwapChainDelegate(IntPtr pFactory, IntPtr pDevice, IntPtr pDesc, out IntPtr ppSwapChain);
|
|
||||||
|
|
||||||
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
|
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
|
||||||
private delegate IntPtr SetCursorDelegate(IntPtr hCursor);
|
private delegate IntPtr SetCursorDelegate(IntPtr hCursor);
|
||||||
|
|
||||||
|
|
@ -248,20 +245,15 @@ namespace Dalamud.Interface.Internal
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
// HACK: this is usually called on a separate thread from PresentDetour (likely on a dedicated render thread)
|
this.framework.RunOnFrameworkThread(() =>
|
||||||
// and if we aren't already disabled, disposing of the scene and hook can frequently crash due to the hook
|
{
|
||||||
// being disposed of in this thread while it is actively in use in the render thread.
|
this.setCursorHook.Dispose();
|
||||||
// This is a terrible way to prevent issues, but should basically always work to ensure that all outstanding
|
this.presentHook?.Dispose();
|
||||||
// calls to PresentDetour have finished (and Disable means no new ones will start), before we try to cleanup
|
this.resizeBuffersHook?.Dispose();
|
||||||
// So... not great, but much better than constantly crashing on unload
|
this.dispatchMessageWHook.Dispose();
|
||||||
this.Disable();
|
}).Wait();
|
||||||
Thread.Sleep(500);
|
|
||||||
|
|
||||||
this.scene?.Dispose();
|
this.scene?.Dispose();
|
||||||
this.setCursorHook.Dispose();
|
|
||||||
this.presentHook?.Dispose();
|
|
||||||
this.resizeBuffersHook?.Dispose();
|
|
||||||
this.dispatchMessageWHook.Dispose();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#nullable enable
|
#nullable enable
|
||||||
|
|
@ -990,8 +982,8 @@ namespace Dalamud.Interface.Internal
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.presentHook = new Hook<PresentDelegate>(this.address.Present, this.PresentDetour);
|
this.presentHook = Hook<PresentDelegate>.FromAddress(this.address.Present, this.PresentDetour);
|
||||||
this.resizeBuffersHook = new Hook<ResizeBuffersDelegate>(this.address.ResizeBuffers, this.ResizeBuffersDetour);
|
this.resizeBuffersHook = Hook<ResizeBuffersDelegate>.FromAddress(this.address.ResizeBuffers, this.ResizeBuffersDetour);
|
||||||
|
|
||||||
Log.Verbose("===== S W A P C H A I N =====");
|
Log.Verbose("===== S W A P C H A I N =====");
|
||||||
Log.Verbose($"Present address 0x{this.presentHook!.Address.ToInt64():X}");
|
Log.Verbose($"Present address 0x{this.presentHook!.Address.ToInt64():X}");
|
||||||
|
|
@ -1004,14 +996,6 @@ namespace Dalamud.Interface.Internal
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Disable()
|
|
||||||
{
|
|
||||||
this.setCursorHook.Disable();
|
|
||||||
this.presentHook?.Disable();
|
|
||||||
this.resizeBuffersHook?.Disable();
|
|
||||||
this.dispatchMessageWHook.Disable();
|
|
||||||
}
|
|
||||||
|
|
||||||
// This is intended to only be called as a handler attached to scene.OnNewRenderFrame
|
// This is intended to only be called as a handler attached to scene.OnNewRenderFrame
|
||||||
private void RebuildFontsInternal()
|
private void RebuildFontsInternal()
|
||||||
{
|
{
|
||||||
|
|
@ -1104,7 +1088,7 @@ namespace Dalamud.Interface.Internal
|
||||||
if (this.lastWantCapture == true && (!this.scene?.IsImGuiCursor(hCursor) ?? false) && this.OverrideGameCursor)
|
if (this.lastWantCapture == true && (!this.scene?.IsImGuiCursor(hCursor) ?? false) && this.OverrideGameCursor)
|
||||||
return IntPtr.Zero;
|
return IntPtr.Zero;
|
||||||
|
|
||||||
return this.setCursorHook!.Original(hCursor);
|
return this.setCursorHook.IsDisposed ? User32.SetCursor(new User32.SafeCursorHandle(hCursor, false)).DangerousGetHandle() : this.setCursorHook.Original(hCursor);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnNewInputFrame()
|
private void OnNewInputFrame()
|
||||||
|
|
|
||||||
|
|
@ -123,7 +123,6 @@ namespace Dalamud.Interface.Internal.Windows
|
||||||
// Options menu
|
// Options menu
|
||||||
if (ImGui.BeginPopup("Options"))
|
if (ImGui.BeginPopup("Options"))
|
||||||
{
|
{
|
||||||
var dalamud = Service<Dalamud>.Get();
|
|
||||||
var configuration = Service<DalamudConfiguration>.Get();
|
var configuration = Service<DalamudConfiguration>.Get();
|
||||||
|
|
||||||
if (ImGui.Checkbox("Auto-scroll", ref this.autoScroll))
|
if (ImGui.Checkbox("Auto-scroll", ref this.autoScroll))
|
||||||
|
|
@ -138,10 +137,10 @@ namespace Dalamud.Interface.Internal.Windows
|
||||||
configuration.Save();
|
configuration.Save();
|
||||||
}
|
}
|
||||||
|
|
||||||
var prevLevel = (int)dalamud.LogLevelSwitch.MinimumLevel;
|
var prevLevel = (int)EntryPoint.LogLevelSwitch.MinimumLevel;
|
||||||
if (ImGui.Combo("Log Level", ref prevLevel, Enum.GetValues(typeof(LogEventLevel)).Cast<LogEventLevel>().Select(x => x.ToString()).ToArray(), 6))
|
if (ImGui.Combo("Log Level", ref prevLevel, Enum.GetValues(typeof(LogEventLevel)).Cast<LogEventLevel>().Select(x => x.ToString()).ToArray(), 6))
|
||||||
{
|
{
|
||||||
dalamud.LogLevelSwitch.MinimumLevel = (LogEventLevel)prevLevel;
|
EntryPoint.LogLevelSwitch.MinimumLevel = (LogEventLevel)prevLevel;
|
||||||
configuration.LogLevel = (LogEventLevel)prevLevel;
|
configuration.LogLevel = (LogEventLevel)prevLevel;
|
||||||
configuration.Save();
|
configuration.Save();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,6 @@ using System.Net;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
using Dalamud.Game;
|
using Dalamud.Game;
|
||||||
using Dalamud.Plugin.Internal;
|
using Dalamud.Plugin.Internal;
|
||||||
using Dalamud.Plugin.Internal.Types;
|
using Dalamud.Plugin.Internal.Types;
|
||||||
|
|
@ -20,7 +19,8 @@ namespace Dalamud.Interface.Internal.Windows
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A cache for plugin icons and images.
|
/// A cache for plugin icons and images.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal class PluginImageCache : IDisposable
|
[ServiceManager.EarlyLoadedService]
|
||||||
|
internal class PluginImageCache : IDisposable, IServiceType
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Maximum plugin image width.
|
/// Maximum plugin image width.
|
||||||
|
|
@ -44,102 +44,133 @@ namespace Dalamud.Interface.Internal.Windows
|
||||||
|
|
||||||
private const string MainRepoImageUrl = "https://raw.githubusercontent.com/goatcorp/DalamudPlugins/api6/{0}/{1}/images/{2}";
|
private const string MainRepoImageUrl = "https://raw.githubusercontent.com/goatcorp/DalamudPlugins/api6/{0}/{1}/images/{2}";
|
||||||
|
|
||||||
private BlockingCollection<Func<Task>> downloadQueue = new();
|
private readonly BlockingCollection<Tuple<ulong, Func<Task>>> downloadQueue = new();
|
||||||
private BlockingCollection<Action> loadQueue = new();
|
private readonly BlockingCollection<Func<Task>> loadQueue = new();
|
||||||
private CancellationTokenSource downloadToken = new();
|
private readonly CancellationTokenSource cancelToken = new();
|
||||||
private Thread downloadThread;
|
private readonly Task downloadTask;
|
||||||
|
private readonly Task loadTask;
|
||||||
|
|
||||||
private Dictionary<string, TextureWrap?> pluginIconMap = new();
|
private readonly Dictionary<string, TextureWrap?> pluginIconMap = new();
|
||||||
private Dictionary<string, TextureWrap?[]> pluginImagesMap = new();
|
private readonly Dictionary<string, TextureWrap?[]> pluginImagesMap = new();
|
||||||
|
|
||||||
|
private readonly Task<TextureWrap> emptyTextureTask;
|
||||||
|
private readonly Task<TextureWrap> defaultIconTask;
|
||||||
|
private readonly Task<TextureWrap> troubleIconTask;
|
||||||
|
private readonly Task<TextureWrap> updateIconTask;
|
||||||
|
private readonly Task<TextureWrap> installedIconTask;
|
||||||
|
private readonly Task<TextureWrap> thirdIconTask;
|
||||||
|
private readonly Task<TextureWrap> thirdInstalledIconTask;
|
||||||
|
private readonly Task<TextureWrap> corePluginIconTask;
|
||||||
|
|
||||||
|
[ServiceManager.ServiceConstructor]
|
||||||
|
private PluginImageCache(Dalamud dalamud)
|
||||||
|
{
|
||||||
|
var imwst = Service<InterfaceManager.InterfaceManagerWithScene>.GetAsync();
|
||||||
|
|
||||||
|
Task<TextureWrap>? TaskWrapIfNonNull(TextureWrap? tw) => tw == null ? null : Task.FromResult(tw!);
|
||||||
|
|
||||||
|
this.emptyTextureTask = imwst.ContinueWith(task => task.Result.Manager.LoadImageRaw(new byte[64], 8, 8, 4)!);
|
||||||
|
this.defaultIconTask = imwst.ContinueWith(task => TaskWrapIfNonNull(task.Result.Manager.LoadImage(Path.Combine(dalamud.AssetDirectory.FullName, "UIRes", "defaultIcon.png"))) ?? this.emptyTextureTask).Unwrap();
|
||||||
|
this.troubleIconTask = imwst.ContinueWith(task => TaskWrapIfNonNull(task.Result.Manager.LoadImage(Path.Combine(dalamud.AssetDirectory.FullName, "UIRes", "troubleIcon.png"))) ?? this.emptyTextureTask).Unwrap();
|
||||||
|
this.updateIconTask = imwst.ContinueWith(task => TaskWrapIfNonNull(task.Result.Manager.LoadImage(Path.Combine(dalamud.AssetDirectory.FullName, "UIRes", "updateIcon.png"))) ?? this.emptyTextureTask).Unwrap();
|
||||||
|
this.installedIconTask = imwst.ContinueWith(task => TaskWrapIfNonNull(task.Result.Manager.LoadImage(Path.Combine(dalamud.AssetDirectory.FullName, "UIRes", "installedIcon.png"))) ?? this.emptyTextureTask).Unwrap();
|
||||||
|
this.thirdIconTask = imwst.ContinueWith(task => TaskWrapIfNonNull(task.Result.Manager.LoadImage(Path.Combine(dalamud.AssetDirectory.FullName, "UIRes", "thirdIcon.png"))) ?? this.emptyTextureTask).Unwrap();
|
||||||
|
this.thirdInstalledIconTask = imwst.ContinueWith(task => TaskWrapIfNonNull(task.Result.Manager.LoadImage(Path.Combine(dalamud.AssetDirectory.FullName, "UIRes", "thirdInstalledIcon.png"))) ?? this.emptyTextureTask).Unwrap();
|
||||||
|
this.corePluginIconTask = imwst.ContinueWith(task => TaskWrapIfNonNull(task.Result.Manager.LoadImage(Path.Combine(dalamud.AssetDirectory.FullName, "UIRes", "tsmLogo.png"))) ?? this.emptyTextureTask).Unwrap();
|
||||||
|
|
||||||
|
this.downloadTask = Task.Factory.StartNew(
|
||||||
|
() => this.DownloadTask(8), TaskCreationOptions.LongRunning);
|
||||||
|
this.loadTask = Task.Factory.StartNew(
|
||||||
|
() => this.LoadTask(Environment.ProcessorCount), TaskCreationOptions.LongRunning);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="PluginImageCache"/> class.
|
/// Gets the fallback empty texture.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public PluginImageCache()
|
public TextureWrap EmptyTexture => this.emptyTextureTask.IsCompleted
|
||||||
{
|
? this.emptyTextureTask.Result
|
||||||
var dalamud = Service<Dalamud>.Get();
|
: this.emptyTextureTask.GetAwaiter().GetResult();
|
||||||
var interfaceManager = Service<InterfaceManager>.Get();
|
|
||||||
|
|
||||||
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"))!;
|
|
||||||
this.InstalledIcon = interfaceManager.LoadImage(Path.Combine(dalamud.AssetDirectory.FullName, "UIRes", "installedIcon.png"))!;
|
|
||||||
this.ThirdIcon = interfaceManager.LoadImage(Path.Combine(dalamud.AssetDirectory.FullName, "UIRes", "thirdIcon.png"))!;
|
|
||||||
this.ThirdInstalledIcon = interfaceManager.LoadImage(Path.Combine(dalamud.AssetDirectory.FullName, "UIRes", "thirdInstalledIcon.png"))!;
|
|
||||||
this.CorePluginIcon = interfaceManager.LoadImage(Path.Combine(dalamud.AssetDirectory.FullName, "UIRes", "tsmLogo.png"))!;
|
|
||||||
|
|
||||||
if (this.DefaultIcon == null || this.TroubleIcon == null || this.UpdateIcon == null || this.InstalledIcon == null ||
|
|
||||||
this.ThirdIcon == null || this.ThirdInstalledIcon == null || this.CorePluginIcon == null)
|
|
||||||
{
|
|
||||||
throw new Exception("Plugin overlay images could not be loaded.");
|
|
||||||
}
|
|
||||||
|
|
||||||
this.downloadThread = new Thread(this.DownloadTask);
|
|
||||||
this.downloadThread.Start();
|
|
||||||
|
|
||||||
var framework = Service<Framework>.Get();
|
|
||||||
framework.Update += this.FrameworkOnUpdate;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the default plugin icon.
|
/// Gets the default plugin icon.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public TextureWrap DefaultIcon { get; }
|
public TextureWrap DefaultIcon => this.defaultIconTask.IsCompleted
|
||||||
|
? this.defaultIconTask.Result
|
||||||
|
: this.defaultIconTask.GetAwaiter().GetResult();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the plugin trouble icon overlay.
|
/// Gets the plugin trouble icon overlay.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public TextureWrap TroubleIcon { get; }
|
public TextureWrap TroubleIcon => this.troubleIconTask.IsCompleted
|
||||||
|
? this.troubleIconTask.Result
|
||||||
|
: this.troubleIconTask.GetAwaiter().GetResult();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the plugin update icon overlay.
|
/// Gets the plugin update icon overlay.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public TextureWrap UpdateIcon { get; }
|
public TextureWrap UpdateIcon => this.updateIconTask.IsCompleted
|
||||||
|
? this.updateIconTask.Result
|
||||||
|
: this.updateIconTask.GetAwaiter().GetResult();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the plugin installed icon overlay.
|
/// Gets the plugin installed icon overlay.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public TextureWrap InstalledIcon { get; }
|
public TextureWrap InstalledIcon => this.installedIconTask.IsCompleted
|
||||||
|
? this.installedIconTask.Result
|
||||||
|
: this.installedIconTask.GetAwaiter().GetResult();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the third party plugin icon overlay.
|
/// Gets the third party plugin icon overlay.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public TextureWrap ThirdIcon { get; }
|
public TextureWrap ThirdIcon => this.thirdIconTask.IsCompleted
|
||||||
|
? this.thirdIconTask.Result
|
||||||
|
: this.thirdIconTask.GetAwaiter().GetResult();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the installed third party plugin icon overlay.
|
/// Gets the installed third party plugin icon overlay.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public TextureWrap ThirdInstalledIcon { get; }
|
public TextureWrap ThirdInstalledIcon => this.thirdInstalledIconTask.IsCompleted
|
||||||
|
? this.thirdInstalledIconTask.Result
|
||||||
|
: this.thirdInstalledIconTask.GetAwaiter().GetResult();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the core plugin icon.
|
/// Gets the core plugin icon.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public TextureWrap CorePluginIcon { get; }
|
public TextureWrap CorePluginIcon => this.corePluginIconTask.IsCompleted
|
||||||
|
? this.corePluginIconTask.Result
|
||||||
|
: this.corePluginIconTask.GetAwaiter().GetResult();
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
var framework = Service<Framework>.Get();
|
this.cancelToken.Cancel();
|
||||||
framework.Update -= this.FrameworkOnUpdate;
|
this.downloadQueue.CompleteAdding();
|
||||||
|
this.loadQueue.CompleteAdding();
|
||||||
|
|
||||||
this.DefaultIcon?.Dispose();
|
if (!Task.WaitAll(new[] { this.loadTask, this.downloadTask }, 4000))
|
||||||
this.TroubleIcon?.Dispose();
|
|
||||||
this.UpdateIcon?.Dispose();
|
|
||||||
this.InstalledIcon?.Dispose();
|
|
||||||
this.ThirdIcon?.Dispose();
|
|
||||||
this.ThirdInstalledIcon?.Dispose();
|
|
||||||
this.CorePluginIcon?.Dispose();
|
|
||||||
|
|
||||||
this.downloadToken?.Cancel();
|
|
||||||
|
|
||||||
if (!this.downloadThread.Join(4000))
|
|
||||||
{
|
{
|
||||||
Log.Error("Plugin Image Download thread has not cancelled in time");
|
Log.Error("Plugin Image download/load thread has not cancelled in time");
|
||||||
}
|
}
|
||||||
|
|
||||||
this.downloadToken?.Dispose();
|
this.cancelToken.Dispose();
|
||||||
this.downloadQueue?.CompleteAdding();
|
this.downloadQueue.Dispose();
|
||||||
this.downloadQueue?.Dispose();
|
this.loadQueue.Dispose();
|
||||||
|
|
||||||
|
foreach (var task in new[]
|
||||||
|
{
|
||||||
|
this.defaultIconTask,
|
||||||
|
this.troubleIconTask,
|
||||||
|
this.updateIconTask,
|
||||||
|
this.installedIconTask,
|
||||||
|
this.thirdIconTask,
|
||||||
|
this.thirdInstalledIconTask,
|
||||||
|
this.corePluginIconTask,
|
||||||
|
})
|
||||||
|
{
|
||||||
|
task.Wait();
|
||||||
|
if (task.IsCompletedSuccessfully)
|
||||||
|
task.Result.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
foreach (var icon in this.pluginIconMap.Values)
|
foreach (var icon in this.pluginIconMap.Values)
|
||||||
{
|
{
|
||||||
|
|
@ -181,12 +212,22 @@ namespace Dalamud.Interface.Internal.Windows
|
||||||
if (this.pluginIconMap.TryGetValue(manifest.InternalName, out iconTexture))
|
if (this.pluginIconMap.TryGetValue(manifest.InternalName, out iconTexture))
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
iconTexture = null;
|
this.pluginIconMap.Add(manifest.InternalName, null);
|
||||||
this.pluginIconMap.Add(manifest.InternalName, iconTexture);
|
|
||||||
|
|
||||||
if (!this.downloadQueue.IsCompleted)
|
try
|
||||||
{
|
{
|
||||||
this.downloadQueue.Add(async () => await this.DownloadPluginIconAsync(plugin, manifest, isThirdParty));
|
if (!this.downloadQueue.IsCompleted)
|
||||||
|
{
|
||||||
|
this.downloadQueue.Add(
|
||||||
|
Tuple.Create(
|
||||||
|
Service<DalamudInterface>.GetNullable()?.FrameCount ?? 0,
|
||||||
|
() => this.DownloadPluginIconAsync(plugin, manifest, isThirdParty)),
|
||||||
|
this.cancelToken.Token);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (ObjectDisposedException)
|
||||||
|
{
|
||||||
|
// pass
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
|
|
@ -209,39 +250,120 @@ namespace Dalamud.Interface.Internal.Windows
|
||||||
imageTextures = Array.Empty<TextureWrap>();
|
imageTextures = Array.Empty<TextureWrap>();
|
||||||
this.pluginImagesMap.Add(manifest.InternalName, imageTextures);
|
this.pluginImagesMap.Add(manifest.InternalName, imageTextures);
|
||||||
|
|
||||||
if (!this.downloadQueue.IsCompleted)
|
try
|
||||||
{
|
{
|
||||||
this.downloadQueue.Add(async () => await this.DownloadPluginImagesAsync(plugin, manifest, isThirdParty));
|
if (!this.downloadQueue.IsCompleted)
|
||||||
|
{
|
||||||
|
this.downloadQueue.Add(
|
||||||
|
Tuple.Create(
|
||||||
|
Service<DalamudInterface>.GetNullable()?.FrameCount ?? 0,
|
||||||
|
() => this.DownloadPluginImagesAsync(plugin, manifest, isThirdParty)),
|
||||||
|
this.cancelToken.Token);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (ObjectDisposedException)
|
||||||
|
{
|
||||||
|
// pass
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void FrameworkOnUpdate(Framework framework)
|
private static async Task<TextureWrap?> TryLoadIcon(
|
||||||
|
byte[] bytes,
|
||||||
|
string name,
|
||||||
|
string? loc,
|
||||||
|
PluginManifest manifest,
|
||||||
|
int maxWidth,
|
||||||
|
int maxHeight,
|
||||||
|
bool requireSquare)
|
||||||
{
|
{
|
||||||
|
var interfaceManager = (await Service<InterfaceManager.InterfaceManagerWithScene>.GetAsync()).Manager;
|
||||||
|
var framework = await Service<Framework>.GetAsync();
|
||||||
|
|
||||||
|
TextureWrap? icon;
|
||||||
|
// FIXME(goat): This is a hack around this call failing randomly in certain situations. Might be related to not being called on the main thread.
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (!this.loadQueue.TryTake(out var loadAction, 0, this.downloadToken.Token))
|
icon = interfaceManager.LoadImage(bytes);
|
||||||
return;
|
|
||||||
|
|
||||||
loadAction.Invoke();
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Log.Error(ex, "An unhandled exception occurred in image loader framework dispatcher");
|
Log.Error(ex, "Access violation during load plugin {name} from {Loc} (Async Thread)", name, loc);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
icon = await framework.RunOnFrameworkThread(() => interfaceManager.LoadImage(bytes));
|
||||||
|
}
|
||||||
|
catch (Exception ex2)
|
||||||
|
{
|
||||||
|
Log.Error(ex2, "Access violation during load plugin {name} from {Loc} (Framework Thread)", name, loc);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (icon == null)
|
||||||
|
{
|
||||||
|
Log.Error($"Could not load {name} for {manifest.InternalName} at {loc}");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (icon.Width > maxWidth || icon.Height > maxHeight)
|
||||||
|
{
|
||||||
|
Log.Error($"Plugin {name} for {manifest.InternalName} at {loc} was larger than the maximum allowed resolution ({maxWidth}x{maxHeight}).");
|
||||||
|
icon.Dispose();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (requireSquare && icon.Height != icon.Width)
|
||||||
|
{
|
||||||
|
Log.Error($"Plugin {name} for {manifest.InternalName} at {loc} was not square.");
|
||||||
|
icon.Dispose();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return icon!;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async void DownloadTask()
|
private async Task DownloadTask(int concurrency)
|
||||||
{
|
{
|
||||||
while (!this.downloadToken.Token.IsCancellationRequested)
|
var token = this.cancelToken.Token;
|
||||||
|
var runningTasks = new List<Task>();
|
||||||
|
var pendingFuncs = new List<Tuple<ulong, Func<Task>>>();
|
||||||
|
while (true)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (!this.downloadQueue.TryTake(out var task, -1, this.downloadToken.Token))
|
token.ThrowIfCancellationRequested();
|
||||||
return;
|
if (!pendingFuncs.Any())
|
||||||
|
{
|
||||||
|
if (!this.downloadQueue.TryTake(out var taskTuple, -1, token))
|
||||||
|
return;
|
||||||
|
|
||||||
await task.Invoke();
|
pendingFuncs.Add(taskTuple);
|
||||||
|
}
|
||||||
|
|
||||||
|
token.ThrowIfCancellationRequested();
|
||||||
|
while (this.downloadQueue.TryTake(out var taskTuple, 0, token))
|
||||||
|
pendingFuncs.Add(taskTuple);
|
||||||
|
|
||||||
|
// Process most recently requested items first in terms of frame index.
|
||||||
|
pendingFuncs = pendingFuncs.OrderBy(x => x.Item1).ToList();
|
||||||
|
|
||||||
|
var item1 = pendingFuncs.Last().Item1;
|
||||||
|
while (pendingFuncs.Any() && pendingFuncs.Last().Item1 == item1)
|
||||||
|
{
|
||||||
|
token.ThrowIfCancellationRequested();
|
||||||
|
while (runningTasks.Count >= concurrency)
|
||||||
|
{
|
||||||
|
await Task.WhenAny(runningTasks);
|
||||||
|
runningTasks.RemoveAll(task => task.IsCompleted);
|
||||||
|
}
|
||||||
|
|
||||||
|
token.ThrowIfCancellationRequested();
|
||||||
|
runningTasks.Add(Task.Run(pendingFuncs.Last().Item2, token));
|
||||||
|
pendingFuncs.RemoveAt(pendingFuncs.Count - 1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch (OperationCanceledException)
|
catch (OperationCanceledException)
|
||||||
{
|
{
|
||||||
|
|
@ -252,52 +374,56 @@ namespace Dalamud.Interface.Internal.Windows
|
||||||
{
|
{
|
||||||
Log.Error(ex, "An unhandled exception occurred in the plugin image downloader");
|
Log.Error(ex, "An unhandled exception occurred in the plugin image downloader");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
while (runningTasks.Count >= concurrency)
|
||||||
|
{
|
||||||
|
await Task.WhenAny(runningTasks);
|
||||||
|
runningTasks.RemoveAll(task => task.IsCompleted);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await Task.WhenAll(runningTasks);
|
||||||
Log.Debug("Plugin image downloader has shutdown");
|
Log.Debug("Plugin image downloader has shutdown");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task LoadTask(int concurrency)
|
||||||
|
{
|
||||||
|
await Service<InterfaceManager.InterfaceManagerWithScene>.GetAsync();
|
||||||
|
|
||||||
|
var token = this.cancelToken.Token;
|
||||||
|
var runningTasks = new List<Task>();
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
token.ThrowIfCancellationRequested();
|
||||||
|
while (runningTasks.Count >= concurrency)
|
||||||
|
{
|
||||||
|
await Task.WhenAny(runningTasks);
|
||||||
|
runningTasks.RemoveAll(task => task.IsCompleted);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.loadQueue.TryTake(out var func, -1, token))
|
||||||
|
return;
|
||||||
|
runningTasks.Add(Task.Run(func, token));
|
||||||
|
}
|
||||||
|
catch (OperationCanceledException)
|
||||||
|
{
|
||||||
|
// Shutdown signal.
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Log.Error(ex, "An unhandled exception occurred in the plugin image loader");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await Task.WhenAll(runningTasks);
|
||||||
|
Log.Debug("Plugin image loader has shutdown");
|
||||||
|
}
|
||||||
|
|
||||||
private async Task DownloadPluginIconAsync(LocalPlugin? plugin, PluginManifest manifest, bool isThirdParty)
|
private async Task DownloadPluginIconAsync(LocalPlugin? plugin, PluginManifest manifest, bool isThirdParty)
|
||||||
{
|
{
|
||||||
var interfaceManager = Service<InterfaceManager>.Get();
|
|
||||||
var pluginManager = Service<PluginManager>.Get();
|
|
||||||
|
|
||||||
static bool TryLoadIcon(byte[] bytes, string? loc, PluginManifest manifest, InterfaceManager interfaceManager, out TextureWrap? icon)
|
|
||||||
{
|
|
||||||
// FIXME(goat): This is a hack around this call failing randomly in certain situations. Might be related to not being called on the main thread.
|
|
||||||
try
|
|
||||||
{
|
|
||||||
icon = interfaceManager.LoadImage(bytes);
|
|
||||||
}
|
|
||||||
catch (AccessViolationException ex)
|
|
||||||
{
|
|
||||||
Log.Error(ex, "Access violation during load plugin icon from {Loc}", loc);
|
|
||||||
|
|
||||||
icon = null;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (icon == null)
|
|
||||||
{
|
|
||||||
Log.Error($"Could not load icon for {manifest.InternalName} at {loc}");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (icon.Width > PluginIconWidth || icon.Height > PluginIconHeight)
|
|
||||||
{
|
|
||||||
Log.Error($"Icon for {manifest.InternalName} at {loc} was larger than the maximum allowed resolution ({PluginIconWidth}x{PluginIconHeight}).");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (icon.Height != icon.Width)
|
|
||||||
{
|
|
||||||
Log.Error($"Icon for {manifest.InternalName} at {loc} was not square.");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (plugin != null && plugin.IsDev)
|
if (plugin != null && plugin.IsDev)
|
||||||
{
|
{
|
||||||
var file = this.GetPluginIconFileInfo(plugin);
|
var file = this.GetPluginIconFileInfo(plugin);
|
||||||
|
|
@ -306,7 +432,8 @@ namespace Dalamud.Interface.Internal.Windows
|
||||||
Log.Verbose($"Fetching icon for {manifest.InternalName} from {file.FullName}");
|
Log.Verbose($"Fetching icon for {manifest.InternalName} from {file.FullName}");
|
||||||
|
|
||||||
var bytes = await File.ReadAllBytesAsync(file.FullName);
|
var bytes = await File.ReadAllBytesAsync(file.FullName);
|
||||||
if (!TryLoadIcon(bytes, file.FullName, manifest, interfaceManager, out var icon))
|
var icon = await TryLoadIcon(bytes, "icon", file.FullName, manifest, PluginIconWidth, PluginIconHeight, true);
|
||||||
|
if (icon == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
this.pluginIconMap[manifest.InternalName] = icon;
|
this.pluginIconMap[manifest.InternalName] = icon;
|
||||||
|
|
@ -349,9 +476,10 @@ namespace Dalamud.Interface.Internal.Windows
|
||||||
data.EnsureSuccessStatusCode();
|
data.EnsureSuccessStatusCode();
|
||||||
|
|
||||||
var bytes = await data.Content.ReadAsByteArrayAsync();
|
var bytes = await data.Content.ReadAsByteArrayAsync();
|
||||||
this.loadQueue.Add(() =>
|
this.loadQueue.Add(async () =>
|
||||||
{
|
{
|
||||||
if (!TryLoadIcon(bytes, url, manifest, interfaceManager, out var icon))
|
var icon = await TryLoadIcon(bytes, "icon", url, manifest, PluginIconWidth, PluginIconHeight, true);
|
||||||
|
if (icon == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
this.pluginIconMap[manifest.InternalName] = icon;
|
this.pluginIconMap[manifest.InternalName] = icon;
|
||||||
|
|
@ -366,39 +494,6 @@ namespace Dalamud.Interface.Internal.Windows
|
||||||
|
|
||||||
private async Task DownloadPluginImagesAsync(LocalPlugin? plugin, PluginManifest manifest, bool isThirdParty)
|
private async Task DownloadPluginImagesAsync(LocalPlugin? plugin, PluginManifest manifest, bool isThirdParty)
|
||||||
{
|
{
|
||||||
var interfaceManager = Service<InterfaceManager>.Get();
|
|
||||||
var pluginManager = Service<PluginManager>.Get();
|
|
||||||
|
|
||||||
static bool TryLoadImage(int i, byte[] bytes, string loc, PluginManifest manifest, InterfaceManager interfaceManager, out TextureWrap? image)
|
|
||||||
{
|
|
||||||
// FIXME(goat): This is a hack around this call failing randomly in certain situations. Might be related to not being called on the main thread.
|
|
||||||
try
|
|
||||||
{
|
|
||||||
image = interfaceManager.LoadImage(bytes);
|
|
||||||
}
|
|
||||||
catch (AccessViolationException ex)
|
|
||||||
{
|
|
||||||
Log.Error(ex, "Access violation during load plugin image from {Loc}", loc);
|
|
||||||
|
|
||||||
image = null;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (image == null)
|
|
||||||
{
|
|
||||||
Log.Error($"Could not load image{i + 1} for {manifest.InternalName} at {loc}");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (image.Width > PluginImageWidth || image.Height > PluginImageHeight)
|
|
||||||
{
|
|
||||||
Log.Error($"Plugin image{i + 1} for {manifest.InternalName} at {loc} was larger than the maximum allowed resolution ({PluginImageWidth}x{PluginImageHeight}).");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (plugin is { IsDev: true })
|
if (plugin is { IsDev: true })
|
||||||
{
|
{
|
||||||
var files = this.GetPluginImageFileInfos(plugin);
|
var files = this.GetPluginImageFileInfos(plugin);
|
||||||
|
|
@ -415,7 +510,8 @@ namespace Dalamud.Interface.Internal.Windows
|
||||||
Log.Verbose($"Loading image{i + 1} for {manifest.InternalName} from {file.FullName}");
|
Log.Verbose($"Loading image{i + 1} for {manifest.InternalName} from {file.FullName}");
|
||||||
var bytes = await File.ReadAllBytesAsync(file.FullName);
|
var bytes = await File.ReadAllBytesAsync(file.FullName);
|
||||||
|
|
||||||
if (!TryLoadImage(i, bytes, file.FullName, manifest, interfaceManager, out var image))
|
var image = await TryLoadIcon(bytes, $"image{i + 1}", file.FullName, manifest, PluginImageWidth, PluginImageHeight, true);
|
||||||
|
if (image == null)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
Log.Verbose($"Plugin image{i + 1} for {manifest.InternalName} loaded from disk");
|
Log.Verbose($"Plugin image{i + 1} for {manifest.InternalName} loaded from disk");
|
||||||
|
|
@ -490,17 +586,16 @@ namespace Dalamud.Interface.Internal.Windows
|
||||||
|
|
||||||
if (didAny)
|
if (didAny)
|
||||||
{
|
{
|
||||||
this.loadQueue.Add(() =>
|
this.loadQueue.Add(async () =>
|
||||||
{
|
{
|
||||||
var pluginImages = new TextureWrap[urls.Count];
|
var pluginImages = new TextureWrap[urls.Count];
|
||||||
|
|
||||||
for (var i = 0; i < imageBytes.Length; i++)
|
for (var i = 0; i < imageBytes.Length; i++)
|
||||||
{
|
{
|
||||||
var bytes = imageBytes[i];
|
var bytes = imageBytes[i];
|
||||||
if (bytes == null)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
if (!TryLoadImage(i, bytes, "queue", manifest, interfaceManager, out var image))
|
var image = await TryLoadIcon(bytes, $"image{i + 1}", "queue", manifest, PluginImageWidth, PluginImageHeight, true);
|
||||||
|
if (image == null)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
pluginImages[i] = image;
|
pluginImages[i] = image;
|
||||||
|
|
@ -551,7 +646,9 @@ namespace Dalamud.Interface.Internal.Windows
|
||||||
|
|
||||||
private FileInfo? GetPluginIconFileInfo(LocalPlugin? plugin)
|
private FileInfo? GetPluginIconFileInfo(LocalPlugin? plugin)
|
||||||
{
|
{
|
||||||
var pluginDir = plugin.DllFile.Directory;
|
var pluginDir = plugin?.DllFile.Directory;
|
||||||
|
if (pluginDir == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
var devUrl = new FileInfo(Path.Combine(pluginDir.FullName, "images", "icon.png"));
|
var devUrl = new FileInfo(Path.Combine(pluginDir.FullName, "images", "icon.png"));
|
||||||
if (devUrl.Exists)
|
if (devUrl.Exists)
|
||||||
|
|
@ -562,8 +659,12 @@ namespace Dalamud.Interface.Internal.Windows
|
||||||
|
|
||||||
private List<FileInfo?> GetPluginImageFileInfos(LocalPlugin? plugin)
|
private List<FileInfo?> GetPluginImageFileInfos(LocalPlugin? plugin)
|
||||||
{
|
{
|
||||||
var pluginDir = plugin.DllFile.Directory;
|
|
||||||
var output = new List<FileInfo>();
|
var output = new List<FileInfo>();
|
||||||
|
|
||||||
|
var pluginDir = plugin?.DllFile.Directory;
|
||||||
|
if (pluginDir == null)
|
||||||
|
return output;
|
||||||
|
|
||||||
for (var i = 1; i <= 5; i++)
|
for (var i = 1; i <= 5; i++)
|
||||||
{
|
{
|
||||||
var devUrl = new FileInfo(Path.Combine(pluginDir.FullName, "images", $"image{i}.png"));
|
var devUrl = new FileInfo(Path.Combine(pluginDir.FullName, "images", $"image{i}.png"));
|
||||||
|
|
|
||||||
|
|
@ -37,8 +37,8 @@ namespace Dalamud.Interface.Internal.Windows.PluginInstaller
|
||||||
private readonly Vector4 changelogBgColor = new(0.114f, 0.584f, 0.192f, 0.678f);
|
private readonly Vector4 changelogBgColor = new(0.114f, 0.584f, 0.192f, 0.678f);
|
||||||
private readonly Vector4 changelogTextColor = new(0.812f, 1.000f, 0.816f, 1.000f);
|
private readonly Vector4 changelogTextColor = new(0.812f, 1.000f, 0.816f, 1.000f);
|
||||||
|
|
||||||
|
private readonly PluginImageCache imageCache;
|
||||||
private readonly PluginCategoryManager categoryManager = new();
|
private readonly PluginCategoryManager categoryManager = new();
|
||||||
private readonly PluginImageCache imageCache = new();
|
|
||||||
private readonly DalamudChangelogManager dalamudChangelogManager = new();
|
private readonly DalamudChangelogManager dalamudChangelogManager = new();
|
||||||
|
|
||||||
private readonly List<int> openPluginCollapsibles = new();
|
private readonly List<int> openPluginCollapsibles = new();
|
||||||
|
|
@ -87,12 +87,14 @@ namespace Dalamud.Interface.Internal.Windows.PluginInstaller
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="PluginInstallerWindow"/> class.
|
/// Initializes a new instance of the <see cref="PluginInstallerWindow"/> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public PluginInstallerWindow()
|
/// <param name="imageCache">An instance of <see cref="PluginImageCache"/> class.</param>
|
||||||
|
public PluginInstallerWindow(PluginImageCache imageCache)
|
||||||
: base(
|
: base(
|
||||||
Locs.WindowTitle + (Service<DalamudConfiguration>.Get().DoPluginTest ? Locs.WindowTitleMod_Testing : string.Empty) + "###XlPluginInstaller",
|
Locs.WindowTitle + (Service<DalamudConfiguration>.Get().DoPluginTest ? Locs.WindowTitleMod_Testing : string.Empty) + "###XlPluginInstaller",
|
||||||
ImGuiWindowFlags.NoCollapse | ImGuiWindowFlags.NoScrollbar)
|
ImGuiWindowFlags.NoCollapse | ImGuiWindowFlags.NoScrollbar)
|
||||||
{
|
{
|
||||||
this.IsOpen = true;
|
this.IsOpen = true;
|
||||||
|
this.imageCache = imageCache;
|
||||||
|
|
||||||
this.Size = new Vector2(830, 570);
|
this.Size = new Vector2(830, 570);
|
||||||
this.SizeCondition = ImGuiCond.FirstUseEver;
|
this.SizeCondition = ImGuiCond.FirstUseEver;
|
||||||
|
|
@ -200,6 +202,7 @@ namespace Dalamud.Interface.Internal.Windows.PluginInstaller
|
||||||
ImGui.SetCursorPosY(ImGui.GetCursorPosY() - (5 * ImGuiHelpers.GlobalScale));
|
ImGui.SetCursorPosY(ImGui.GetCursorPosY() - (5 * ImGuiHelpers.GlobalScale));
|
||||||
|
|
||||||
var searchInputWidth = 240 * ImGuiHelpers.GlobalScale;
|
var searchInputWidth = 240 * ImGuiHelpers.GlobalScale;
|
||||||
|
var searchClearButtonWidth = 40 * ImGuiHelpers.GlobalScale;
|
||||||
|
|
||||||
var sortByText = Locs.SortBy_Label;
|
var sortByText = Locs.SortBy_Label;
|
||||||
var sortByTextWidth = ImGui.CalcTextSize(sortByText).X;
|
var sortByTextWidth = ImGui.CalcTextSize(sortByText).X;
|
||||||
|
|
@ -224,13 +227,28 @@ namespace Dalamud.Interface.Internal.Windows.PluginInstaller
|
||||||
// Shift down a little to align with the middle of the header text
|
// Shift down a little to align with the middle of the header text
|
||||||
ImGui.SetCursorPosY(ImGui.GetCursorPosY() + (headerTextSize.Y / 4) - 2);
|
ImGui.SetCursorPosY(ImGui.GetCursorPosY() + (headerTextSize.Y / 4) - 2);
|
||||||
|
|
||||||
ImGui.SetCursorPosX(windowSize.X - sortSelectWidth - style.ItemSpacing.X - searchInputWidth);
|
ImGui.SetCursorPosX(windowSize.X - sortSelectWidth - style.ItemSpacing.X - searchInputWidth - searchClearButtonWidth);
|
||||||
|
|
||||||
|
var searchTextChanged = false;
|
||||||
ImGui.SetNextItemWidth(searchInputWidth);
|
ImGui.SetNextItemWidth(searchInputWidth);
|
||||||
if (ImGui.InputTextWithHint("###XlPluginInstaller_Search", Locs.Header_SearchPlaceholder, ref this.searchText, 100))
|
searchTextChanged |= ImGui.InputTextWithHint(
|
||||||
|
"###XlPluginInstaller_Search",
|
||||||
|
Locs.Header_SearchPlaceholder,
|
||||||
|
ref this.searchText,
|
||||||
|
100);
|
||||||
|
|
||||||
|
ImGui.SameLine();
|
||||||
|
|
||||||
|
ImGui.SetNextItemWidth(searchClearButtonWidth);
|
||||||
|
if (ImGuiComponents.IconButton(FontAwesomeIcon.Times))
|
||||||
{
|
{
|
||||||
this.UpdateCategoriesOnSearchChange();
|
this.searchText = string.Empty;
|
||||||
|
searchTextChanged = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (searchTextChanged)
|
||||||
|
this.UpdateCategoriesOnSearchChange();
|
||||||
|
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
ImGui.SetCursorPosX(windowSize.X - sortSelectWidth);
|
ImGui.SetCursorPosX(windowSize.X - sortSelectWidth);
|
||||||
ImGui.SetNextItemWidth(selectableWidth);
|
ImGui.SetNextItemWidth(selectableWidth);
|
||||||
|
|
@ -552,7 +570,8 @@ namespace Dalamud.Interface.Internal.Windows.PluginInstaller
|
||||||
var i = 0;
|
var i = 0;
|
||||||
foreach (var manifest in categoryManifestsList)
|
foreach (var manifest in categoryManifestsList)
|
||||||
{
|
{
|
||||||
var remoteManifest = manifest as RemotePluginManifest;
|
if (manifest is not RemotePluginManifest remoteManifest)
|
||||||
|
continue;
|
||||||
var (isInstalled, plugin) = this.IsManifestInstalled(remoteManifest);
|
var (isInstalled, plugin) = this.IsManifestInstalled(remoteManifest);
|
||||||
|
|
||||||
ImGui.PushID($"{manifest.InternalName}{manifest.AssemblyVersion}");
|
ImGui.PushID($"{manifest.InternalName}{manifest.AssemblyVersion}");
|
||||||
|
|
@ -1087,51 +1106,38 @@ namespace Dalamud.Interface.Internal.Windows.PluginInstaller
|
||||||
|
|
||||||
ImGui.SetCursorPos(startCursor);
|
ImGui.SetCursorPos(startCursor);
|
||||||
|
|
||||||
var iconTex = this.imageCache.DefaultIcon;
|
|
||||||
var hasIcon = this.imageCache.TryGetIcon(plugin, manifest, isThirdParty, out var cachedIconTex);
|
|
||||||
if (hasIcon && cachedIconTex != null)
|
|
||||||
{
|
|
||||||
iconTex = cachedIconTex;
|
|
||||||
}
|
|
||||||
|
|
||||||
var iconSize = ImGuiHelpers.ScaledVector2(64, 64);
|
var iconSize = ImGuiHelpers.ScaledVector2(64, 64);
|
||||||
|
|
||||||
var cursorBeforeImage = ImGui.GetCursorPos();
|
var cursorBeforeImage = ImGui.GetCursorPos();
|
||||||
ImGui.Image(iconTex.ImGuiHandle, iconSize);
|
var rectOffset = ImGui.GetWindowContentRegionMin() + ImGui.GetWindowPos();
|
||||||
ImGui.SameLine();
|
if (ImGui.IsRectVisible(rectOffset + cursorBeforeImage, rectOffset + cursorBeforeImage + iconSize))
|
||||||
|
{
|
||||||
|
var iconTex = this.imageCache.DefaultIcon;
|
||||||
|
var hasIcon = this.imageCache.TryGetIcon(plugin, manifest, isThirdParty, out var cachedIconTex);
|
||||||
|
if (hasIcon && cachedIconTex != null)
|
||||||
|
{
|
||||||
|
iconTex = cachedIconTex;
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.Image(iconTex.ImGuiHandle, iconSize);
|
||||||
|
ImGui.SameLine();
|
||||||
|
ImGui.SetCursorPos(cursorBeforeImage);
|
||||||
|
}
|
||||||
|
|
||||||
var isLoaded = plugin is { IsLoaded: true };
|
var isLoaded = plugin is { IsLoaded: true };
|
||||||
|
|
||||||
if (updateAvailable)
|
if (updateAvailable)
|
||||||
{
|
|
||||||
ImGui.SetCursorPos(cursorBeforeImage);
|
|
||||||
ImGui.Image(this.imageCache.UpdateIcon.ImGuiHandle, iconSize);
|
ImGui.Image(this.imageCache.UpdateIcon.ImGuiHandle, iconSize);
|
||||||
ImGui.SameLine();
|
|
||||||
}
|
|
||||||
else if (trouble)
|
else if (trouble)
|
||||||
{
|
|
||||||
ImGui.SetCursorPos(cursorBeforeImage);
|
|
||||||
ImGui.Image(this.imageCache.TroubleIcon.ImGuiHandle, iconSize);
|
ImGui.Image(this.imageCache.TroubleIcon.ImGuiHandle, iconSize);
|
||||||
ImGui.SameLine();
|
|
||||||
}
|
|
||||||
else if (isLoaded && isThirdParty)
|
else if (isLoaded && isThirdParty)
|
||||||
{
|
|
||||||
ImGui.SetCursorPos(cursorBeforeImage);
|
|
||||||
ImGui.Image(this.imageCache.ThirdInstalledIcon.ImGuiHandle, iconSize);
|
ImGui.Image(this.imageCache.ThirdInstalledIcon.ImGuiHandle, iconSize);
|
||||||
ImGui.SameLine();
|
|
||||||
}
|
|
||||||
else if (isThirdParty)
|
else if (isThirdParty)
|
||||||
{
|
|
||||||
ImGui.SetCursorPos(cursorBeforeImage);
|
|
||||||
ImGui.Image(this.imageCache.ThirdIcon.ImGuiHandle, iconSize);
|
ImGui.Image(this.imageCache.ThirdIcon.ImGuiHandle, iconSize);
|
||||||
ImGui.SameLine();
|
|
||||||
}
|
|
||||||
else if (isLoaded)
|
else if (isLoaded)
|
||||||
{
|
|
||||||
ImGui.SetCursorPos(cursorBeforeImage);
|
|
||||||
ImGui.Image(this.imageCache.InstalledIcon.ImGuiHandle, iconSize);
|
ImGui.Image(this.imageCache.InstalledIcon.ImGuiHandle, iconSize);
|
||||||
ImGui.SameLine();
|
else
|
||||||
}
|
ImGui.Dummy(iconSize);
|
||||||
|
ImGui.SameLine();
|
||||||
|
|
||||||
ImGuiHelpers.ScaledDummy(5);
|
ImGuiHelpers.ScaledDummy(5);
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
|
|
@ -1208,23 +1214,32 @@ namespace Dalamud.Interface.Internal.Windows.PluginInstaller
|
||||||
var startCursor = ImGui.GetCursorPos();
|
var startCursor = ImGui.GetCursorPos();
|
||||||
|
|
||||||
var iconSize = ImGuiHelpers.ScaledVector2(64, 64);
|
var iconSize = ImGuiHelpers.ScaledVector2(64, 64);
|
||||||
|
var cursorBeforeImage = ImGui.GetCursorPos();
|
||||||
TextureWrap icon;
|
var rectOffset = ImGui.GetWindowContentRegionMin() + ImGui.GetWindowPos();
|
||||||
if (log is PluginChangelogEntry pluginLog)
|
if (ImGui.IsRectVisible(rectOffset + cursorBeforeImage, rectOffset + cursorBeforeImage + iconSize))
|
||||||
{
|
{
|
||||||
icon = this.imageCache.DefaultIcon;
|
TextureWrap icon;
|
||||||
var hasIcon = this.imageCache.TryGetIcon(pluginLog.Plugin, pluginLog.Plugin.Manifest, pluginLog.Plugin.Manifest.IsThirdParty, out var cachedIconTex);
|
if (log is PluginChangelogEntry pluginLog)
|
||||||
if (hasIcon && cachedIconTex != null)
|
|
||||||
{
|
{
|
||||||
icon = cachedIconTex;
|
icon = this.imageCache.DefaultIcon;
|
||||||
|
var hasIcon = this.imageCache.TryGetIcon(pluginLog.Plugin, pluginLog.Plugin.Manifest, pluginLog.Plugin.Manifest.IsThirdParty, out var cachedIconTex);
|
||||||
|
if (hasIcon && cachedIconTex != null)
|
||||||
|
{
|
||||||
|
icon = cachedIconTex;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
icon = this.imageCache.CorePluginIcon;
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.Image(icon.ImGuiHandle, iconSize);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
icon = this.imageCache.CorePluginIcon;
|
ImGui.Dummy(iconSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui.Image(icon.ImGuiHandle, iconSize);
|
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
|
|
||||||
ImGuiHelpers.ScaledDummy(5);
|
ImGuiHelpers.ScaledDummy(5);
|
||||||
|
|
@ -1644,7 +1659,7 @@ namespace Dalamud.Interface.Internal.Windows.PluginInstaller
|
||||||
|
|
||||||
this.installStatus = OperationStatus.InProgress;
|
this.installStatus = OperationStatus.InProgress;
|
||||||
|
|
||||||
Task.Run(() => pluginManager.DeleteConfiguration(plugin))
|
Task.Run(() => pluginManager.DeleteConfigurationAsync(plugin))
|
||||||
.ContinueWith(task =>
|
.ContinueWith(task =>
|
||||||
{
|
{
|
||||||
this.installStatus = OperationStatus.Idle;
|
this.installStatus = OperationStatus.Idle;
|
||||||
|
|
@ -1670,7 +1685,7 @@ namespace Dalamud.Interface.Internal.Windows.PluginInstaller
|
||||||
// Disable everything if the plugin is outdated
|
// Disable everything if the plugin is outdated
|
||||||
disabled = disabled || (plugin.IsOutdated && !configuration.LoadAllApiLevels) || plugin.IsBanned;
|
disabled = disabled || (plugin.IsOutdated && !configuration.LoadAllApiLevels) || plugin.IsBanned;
|
||||||
|
|
||||||
if (plugin.State == PluginState.InProgress)
|
if (plugin.State == PluginState.Loading || plugin.State == PluginState.Unloading)
|
||||||
{
|
{
|
||||||
ImGuiComponents.DisabledButton(Locs.PluginButton_Working);
|
ImGuiComponents.DisabledButton(Locs.PluginButton_Working);
|
||||||
}
|
}
|
||||||
|
|
@ -1686,7 +1701,7 @@ namespace Dalamud.Interface.Internal.Windows.PluginInstaller
|
||||||
{
|
{
|
||||||
Task.Run(() =>
|
Task.Run(() =>
|
||||||
{
|
{
|
||||||
var unloadTask = Task.Run(() => plugin.Unload())
|
var unloadTask = Task.Run(() => plugin.UnloadAsync())
|
||||||
.ContinueWith(this.DisplayErrorContinuation, Locs.ErrorModal_UnloadFail(plugin.Name));
|
.ContinueWith(this.DisplayErrorContinuation, Locs.ErrorModal_UnloadFail(plugin.Name));
|
||||||
|
|
||||||
unloadTask.Wait();
|
unloadTask.Wait();
|
||||||
|
|
|
||||||
|
|
@ -57,6 +57,8 @@ namespace Dalamud.Interface.Internal.Windows
|
||||||
private int dtrSpacing;
|
private int dtrSpacing;
|
||||||
private bool dtrSwapDirection;
|
private bool dtrSwapDirection;
|
||||||
|
|
||||||
|
private int? pluginWaitBeforeFree;
|
||||||
|
|
||||||
private List<ThirdPartyRepoSettings> thirdRepoList;
|
private List<ThirdPartyRepoSettings> thirdRepoList;
|
||||||
private bool thirdRepoListChanged;
|
private bool thirdRepoListChanged;
|
||||||
private string thirdRepoTempUrl = string.Empty;
|
private string thirdRepoTempUrl = string.Empty;
|
||||||
|
|
@ -113,6 +115,8 @@ namespace Dalamud.Interface.Internal.Windows
|
||||||
this.dtrSpacing = configuration.DtrSpacing;
|
this.dtrSpacing = configuration.DtrSpacing;
|
||||||
this.dtrSwapDirection = configuration.DtrSwapDirection;
|
this.dtrSwapDirection = configuration.DtrSwapDirection;
|
||||||
|
|
||||||
|
this.pluginWaitBeforeFree = configuration.PluginWaitBeforeFree;
|
||||||
|
|
||||||
this.doPluginTest = configuration.DoPluginTest;
|
this.doPluginTest = configuration.DoPluginTest;
|
||||||
this.thirdRepoList = configuration.ThirdRepoList.Select(x => x.Clone()).ToList();
|
this.thirdRepoList = configuration.ThirdRepoList.Select(x => x.Clone()).ToList();
|
||||||
this.devPluginLocations = configuration.DevPluginLoadLocations.Select(x => x.Clone()).ToList();
|
this.devPluginLocations = configuration.DevPluginLoadLocations.Select(x => x.Clone()).ToList();
|
||||||
|
|
@ -561,6 +565,34 @@ namespace Dalamud.Interface.Internal.Windows
|
||||||
var configuration = Service<DalamudConfiguration>.Get();
|
var configuration = Service<DalamudConfiguration>.Get();
|
||||||
var pluginManager = Service<PluginManager>.Get();
|
var pluginManager = Service<PluginManager>.Get();
|
||||||
|
|
||||||
|
var useCustomPluginWaitBeforeFree = this.pluginWaitBeforeFree.HasValue;
|
||||||
|
if (ImGui.Checkbox(
|
||||||
|
Loc.Localize("DalamudSettingsPluginCustomizeWaitTime", "Customize wait time for plugin unload"),
|
||||||
|
ref useCustomPluginWaitBeforeFree))
|
||||||
|
{
|
||||||
|
if (!useCustomPluginWaitBeforeFree)
|
||||||
|
this.pluginWaitBeforeFree = null;
|
||||||
|
else
|
||||||
|
this.pluginWaitBeforeFree = PluginManager.PluginWaitBeforeFreeDefault;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (useCustomPluginWaitBeforeFree)
|
||||||
|
{
|
||||||
|
var waitTime = this.pluginWaitBeforeFree ?? PluginManager.PluginWaitBeforeFreeDefault;
|
||||||
|
if (ImGui.SliderInt(
|
||||||
|
"Wait time###DalamudSettingsPluginCustomizeWaitTimeSlider",
|
||||||
|
ref waitTime,
|
||||||
|
0,
|
||||||
|
5000))
|
||||||
|
{
|
||||||
|
this.pluginWaitBeforeFree = waitTime;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.TextColored(ImGuiColors.DalamudGrey, Loc.Localize("DalamudSettingsPluginCustomizeWaitTimeHint", "Configure the wait time between stopping plugin and completely unloading plugin. If you are experiencing crashes when exiting the game, try increasing this value."));
|
||||||
|
|
||||||
|
ImGuiHelpers.ScaledDummy(12);
|
||||||
|
|
||||||
#region Plugin testing
|
#region Plugin testing
|
||||||
|
|
||||||
ImGui.Checkbox(Loc.Localize("DalamudSettingsPluginTest", "Get plugin testing builds"), ref this.doPluginTest);
|
ImGui.Checkbox(Loc.Localize("DalamudSettingsPluginTest", "Get plugin testing builds"), ref this.doPluginTest);
|
||||||
|
|
@ -974,6 +1006,8 @@ namespace Dalamud.Interface.Internal.Windows
|
||||||
configuration.DtrSpacing = this.dtrSpacing;
|
configuration.DtrSpacing = this.dtrSpacing;
|
||||||
configuration.DtrSwapDirection = this.dtrSwapDirection;
|
configuration.DtrSwapDirection = this.dtrSwapDirection;
|
||||||
|
|
||||||
|
configuration.PluginWaitBeforeFree = this.pluginWaitBeforeFree;
|
||||||
|
|
||||||
configuration.DoPluginTest = this.doPluginTest;
|
configuration.DoPluginTest = this.doPluginTest;
|
||||||
configuration.ThirdRepoList = this.thirdRepoList.Select(x => x.Clone()).ToList();
|
configuration.ThirdRepoList = this.thirdRepoList.Select(x => x.Clone()).ToList();
|
||||||
configuration.DevPluginLoadLocations = this.devPluginLocations.Select(x => x.Clone()).ToList();
|
configuration.DevPluginLoadLocations = this.devPluginLocations.Select(x => x.Clone()).ToList();
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
using Dalamud.IoC;
|
using Dalamud.IoC;
|
||||||
using Dalamud.IoC.Internal;
|
using Dalamud.IoC.Internal;
|
||||||
using ImGuiScene;
|
using ImGuiScene;
|
||||||
|
|
@ -47,37 +48,140 @@ namespace Dalamud.Interface
|
||||||
throw new ArgumentException("Texture must be 64x64");
|
throw new ArgumentException("Texture must be 64x64");
|
||||||
}
|
}
|
||||||
|
|
||||||
var entry = new TitleScreenMenuEntry(text, texture, onTriggered);
|
lock (this.entries)
|
||||||
this.entries.Add(entry);
|
{
|
||||||
return entry;
|
var entriesOfAssembly = this.entries.Where(x => x.CallingAssembly == Assembly.GetCallingAssembly()).ToList();
|
||||||
|
var priority = entriesOfAssembly.Any()
|
||||||
|
? unchecked(entriesOfAssembly.Select(x => x.Priority).Max() + 1)
|
||||||
|
: 0;
|
||||||
|
var entry = new TitleScreenMenuEntry(Assembly.GetCallingAssembly(), priority, text, texture, onTriggered);
|
||||||
|
var i = this.entries.BinarySearch(entry);
|
||||||
|
if (i < 0)
|
||||||
|
i = ~i;
|
||||||
|
this.entries.Insert(i, entry);
|
||||||
|
return entry;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds a new entry to the title screen menu.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="priority">Priority of the entry.</param>
|
||||||
|
/// <param name="text">The text to show.</param>
|
||||||
|
/// <param name="texture">The texture to show.</param>
|
||||||
|
/// <param name="onTriggered">The action to execute when the option is selected.</param>
|
||||||
|
/// <returns>A <see cref="TitleScreenMenu"/> object that can be used to manage the entry.</returns>
|
||||||
|
/// <exception cref="ArgumentException">Thrown when the texture provided does not match the required resolution(64x64).</exception>
|
||||||
|
public TitleScreenMenuEntry AddEntry(ulong priority, string text, TextureWrap texture, Action onTriggered)
|
||||||
|
{
|
||||||
|
if (texture.Height != TextureSize || texture.Width != TextureSize)
|
||||||
|
{
|
||||||
|
throw new ArgumentException("Texture must be 64x64");
|
||||||
|
}
|
||||||
|
|
||||||
|
lock (this.entries)
|
||||||
|
{
|
||||||
|
var entry = new TitleScreenMenuEntry(Assembly.GetCallingAssembly(), priority, text, texture, onTriggered);
|
||||||
|
var i = this.entries.BinarySearch(entry);
|
||||||
|
if (i < 0)
|
||||||
|
i = ~i;
|
||||||
|
this.entries.Insert(i, entry);
|
||||||
|
return entry;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Remove an entry from the title screen menu.
|
/// Remove an entry from the title screen menu.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="entry">The entry to remove.</param>
|
/// <param name="entry">The entry to remove.</param>
|
||||||
public void RemoveEntry(TitleScreenMenuEntry entry) => this.entries.Remove(entry);
|
public void RemoveEntry(TitleScreenMenuEntry entry)
|
||||||
|
{
|
||||||
|
lock (this.entries)
|
||||||
|
{
|
||||||
|
this.entries.Remove(entry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds a new entry to the title screen menu.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="priority">Priority of the entry.</param>
|
||||||
|
/// <param name="text">The text to show.</param>
|
||||||
|
/// <param name="texture">The texture to show.</param>
|
||||||
|
/// <param name="onTriggered">The action to execute when the option is selected.</param>
|
||||||
|
/// <returns>A <see cref="TitleScreenMenu"/> object that can be used to manage the entry.</returns>
|
||||||
|
/// <exception cref="ArgumentException">Thrown when the texture provided does not match the required resolution(64x64).</exception>
|
||||||
|
internal TitleScreenMenuEntry AddEntryCore(ulong priority, string text, TextureWrap texture, Action onTriggered)
|
||||||
|
{
|
||||||
|
if (texture.Height != TextureSize || texture.Width != TextureSize)
|
||||||
|
{
|
||||||
|
throw new ArgumentException("Texture must be 64x64");
|
||||||
|
}
|
||||||
|
|
||||||
|
lock (this.entries)
|
||||||
|
{
|
||||||
|
var entry = new TitleScreenMenuEntry(null, priority, text, texture, onTriggered);
|
||||||
|
this.entries.Add(entry);
|
||||||
|
return entry;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds a new entry to the title screen menu.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="text">The text to show.</param>
|
||||||
|
/// <param name="texture">The texture to show.</param>
|
||||||
|
/// <param name="onTriggered">The action to execute when the option is selected.</param>
|
||||||
|
/// <returns>A <see cref="TitleScreenMenu"/> object that can be used to manage the entry.</returns>
|
||||||
|
/// <exception cref="ArgumentException">Thrown when the texture provided does not match the required resolution(64x64).</exception>
|
||||||
|
internal TitleScreenMenuEntry AddEntryCore(string text, TextureWrap texture, Action onTriggered)
|
||||||
|
{
|
||||||
|
if (texture.Height != TextureSize || texture.Width != TextureSize)
|
||||||
|
{
|
||||||
|
throw new ArgumentException("Texture must be 64x64");
|
||||||
|
}
|
||||||
|
|
||||||
|
lock (this.entries)
|
||||||
|
{
|
||||||
|
var entriesOfAssembly = this.entries.Where(x => x.CallingAssembly == null).ToList();
|
||||||
|
var priority = entriesOfAssembly.Any()
|
||||||
|
? unchecked(entriesOfAssembly.Select(x => x.Priority).Max() + 1)
|
||||||
|
: 0;
|
||||||
|
var entry = new TitleScreenMenuEntry(null, priority, text, texture, onTriggered);
|
||||||
|
this.entries.Add(entry);
|
||||||
|
return entry;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Class representing an entry in the title screen menu.
|
/// Class representing an entry in the title screen menu.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class TitleScreenMenuEntry
|
public class TitleScreenMenuEntry : IComparable<TitleScreenMenuEntry>
|
||||||
{
|
{
|
||||||
private readonly Action onTriggered;
|
private readonly Action onTriggered;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="TitleScreenMenuEntry"/> class.
|
/// Initializes a new instance of the <see cref="TitleScreenMenuEntry"/> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <param name="callingAssembly">The calling assembly.</param>
|
||||||
|
/// <param name="priority">The priority of this entry.</param>
|
||||||
/// <param name="text">The text to show.</param>
|
/// <param name="text">The text to show.</param>
|
||||||
/// <param name="texture">The texture to show.</param>
|
/// <param name="texture">The texture to show.</param>
|
||||||
/// <param name="onTriggered">The action to execute when the option is selected.</param>
|
/// <param name="onTriggered">The action to execute when the option is selected.</param>
|
||||||
internal TitleScreenMenuEntry(string text, TextureWrap texture, Action onTriggered)
|
internal TitleScreenMenuEntry(Assembly? callingAssembly, ulong priority, string text, TextureWrap texture, Action onTriggered)
|
||||||
{
|
{
|
||||||
|
this.CallingAssembly = callingAssembly;
|
||||||
|
this.Priority = priority;
|
||||||
this.Name = text;
|
this.Name = text;
|
||||||
this.Texture = texture;
|
this.Texture = texture;
|
||||||
this.onTriggered = onTriggered;
|
this.onTriggered = onTriggered;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the priority of this entry.
|
||||||
|
/// </summary>
|
||||||
|
public ulong Priority { get; init; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the name of this entry.
|
/// Gets or sets the name of this entry.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -88,6 +192,11 @@ namespace Dalamud.Interface
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public TextureWrap Texture { get; set; }
|
public TextureWrap Texture { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the calling assembly of this entry.
|
||||||
|
/// </summary>
|
||||||
|
internal Assembly? CallingAssembly { get; init; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the internal ID of this entry.
|
/// Gets the internal ID of this entry.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -100,6 +209,32 @@ namespace Dalamud.Interface
|
||||||
{
|
{
|
||||||
this.onTriggered();
|
this.onTriggered();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public int CompareTo(TitleScreenMenuEntry? other)
|
||||||
|
{
|
||||||
|
if (other == null)
|
||||||
|
return 1;
|
||||||
|
if (this.CallingAssembly != other.CallingAssembly)
|
||||||
|
{
|
||||||
|
if (this.CallingAssembly == null && other.CallingAssembly == null)
|
||||||
|
return 0;
|
||||||
|
if (this.CallingAssembly == null && other.CallingAssembly != null)
|
||||||
|
return -1;
|
||||||
|
if (this.CallingAssembly != null && other.CallingAssembly == null)
|
||||||
|
return 1;
|
||||||
|
return string.Compare(
|
||||||
|
this.CallingAssembly!.FullName!,
|
||||||
|
other.CallingAssembly!.FullName!,
|
||||||
|
StringComparison.CurrentCultureIgnoreCase);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.Priority != other.Priority)
|
||||||
|
return this.Priority.CompareTo(other.Priority);
|
||||||
|
if (this.Name != other.Name)
|
||||||
|
return string.Compare(this.Name, other.Name, StringComparison.InvariantCultureIgnoreCase);
|
||||||
|
return string.Compare(this.Name, other.Name, StringComparison.InvariantCulture);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,9 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using Dalamud.Configuration.Internal;
|
using Dalamud.Configuration.Internal;
|
||||||
|
using Dalamud.Game;
|
||||||
using Dalamud.Game.ClientState.Conditions;
|
using Dalamud.Game.ClientState.Conditions;
|
||||||
using Dalamud.Game.Gui;
|
using Dalamud.Game.Gui;
|
||||||
using Dalamud.Interface.GameFonts;
|
using Dalamud.Interface.GameFonts;
|
||||||
|
|
@ -24,6 +25,8 @@ namespace Dalamud.Interface
|
||||||
{
|
{
|
||||||
private readonly Stopwatch stopwatch;
|
private readonly Stopwatch stopwatch;
|
||||||
private readonly string namespaceName;
|
private readonly string namespaceName;
|
||||||
|
private readonly InterfaceManager interfaceManager = Service<InterfaceManager>.Get();
|
||||||
|
private readonly GameFontManager gameFontManager = Service<GameFontManager>.Get();
|
||||||
|
|
||||||
private bool hasErrorWindow = false;
|
private bool hasErrorWindow = false;
|
||||||
private bool lastFrameUiHideState = false;
|
private bool lastFrameUiHideState = false;
|
||||||
|
|
@ -38,11 +41,10 @@ namespace Dalamud.Interface
|
||||||
this.stopwatch = new Stopwatch();
|
this.stopwatch = new Stopwatch();
|
||||||
this.namespaceName = namespaceName;
|
this.namespaceName = namespaceName;
|
||||||
|
|
||||||
var interfaceManager = Service<InterfaceManager>.Get();
|
this.interfaceManager.Draw += this.OnDraw;
|
||||||
interfaceManager.Draw += this.OnDraw;
|
this.interfaceManager.BuildFonts += this.OnBuildFonts;
|
||||||
interfaceManager.BuildFonts += this.OnBuildFonts;
|
this.interfaceManager.AfterBuildFonts += this.OnAfterBuildFonts;
|
||||||
interfaceManager.AfterBuildFonts += this.OnAfterBuildFonts;
|
this.interfaceManager.ResizeBuffers += this.OnResizeBuffers;
|
||||||
interfaceManager.ResizeBuffers += this.OnResizeBuffers;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -109,12 +111,12 @@ namespace Dalamud.Interface
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the game's active Direct3D device.
|
/// Gets the game's active Direct3D device.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Device Device => Service<InterfaceManager.InterfaceManagerWithScene>.Get().Manager.Device!;
|
public Device Device => this.InterfaceManagerWithScene.Device!;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the game's main window handle.
|
/// Gets the game's main window handle.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public IntPtr WindowHandlePtr => Service<InterfaceManager.InterfaceManagerWithScene>.Get().Manager.WindowHandlePtr;
|
public IntPtr WindowHandlePtr => this.InterfaceManagerWithScene.WindowHandlePtr;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets a value indicating whether this plugin should hide its UI automatically when the game's UI is hidden.
|
/// Gets or sets a value indicating whether this plugin should hide its UI automatically when the game's UI is hidden.
|
||||||
|
|
@ -141,8 +143,8 @@ namespace Dalamud.Interface
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool OverrideGameCursor
|
public bool OverrideGameCursor
|
||||||
{
|
{
|
||||||
get => Service<InterfaceManager>.Get().OverrideGameCursor;
|
get => this.interfaceManager.OverrideGameCursor;
|
||||||
set => Service<InterfaceManager>.Get().OverrideGameCursor = value;
|
set => this.interfaceManager.OverrideGameCursor = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -157,7 +159,9 @@ namespace Dalamud.Interface
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
var condition = Service<Condition>.Get();
|
var condition = Service<Condition>.GetNullable();
|
||||||
|
if (condition == null)
|
||||||
|
return false;
|
||||||
return condition[ConditionFlag.OccupiedInCutSceneEvent]
|
return condition[ConditionFlag.OccupiedInCutSceneEvent]
|
||||||
|| condition[ConditionFlag.WatchingCutscene78];
|
|| condition[ConditionFlag.WatchingCutscene78];
|
||||||
}
|
}
|
||||||
|
|
@ -170,7 +174,9 @@ namespace Dalamud.Interface
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
var condition = Service<Condition>.Get();
|
var condition = Service<Condition>.GetNullable();
|
||||||
|
if (condition == null)
|
||||||
|
return false;
|
||||||
return condition[ConditionFlag.WatchingCutscene];
|
return condition[ConditionFlag.WatchingCutscene];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -178,7 +184,12 @@ namespace Dalamud.Interface
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets a value indicating whether this plugin should modify the game's interface at this time.
|
/// Gets a value indicating whether this plugin should modify the game's interface at this time.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool ShouldModifyUi => Service<InterfaceManager>.GetNullable()?.IsDispatchingEvents ?? true;
|
public bool ShouldModifyUi => this.interfaceManager.IsDispatchingEvents;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a value indicating whether UI functions can be used.
|
||||||
|
/// </summary>
|
||||||
|
public bool UiPrepared => Service<InterfaceManager.InterfaceManagerWithScene>.GetNullable() != null;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets a value indicating whether statistics about UI draw time should be collected.
|
/// Gets or sets a value indicating whether statistics about UI draw time should be collected.
|
||||||
|
|
@ -209,13 +220,20 @@ namespace Dalamud.Interface
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal List<long> DrawTimeHistory { get; set; } = new List<long>();
|
internal List<long> DrawTimeHistory { get; set; } = new List<long>();
|
||||||
|
|
||||||
|
private InterfaceManager? InterfaceManagerWithScene =>
|
||||||
|
Service<InterfaceManager.InterfaceManagerWithScene>.GetNullable()?.Manager;
|
||||||
|
|
||||||
|
private Task<InterfaceManager> InterfaceManagerWithSceneAsync =>
|
||||||
|
Service<InterfaceManager.InterfaceManagerWithScene>.GetAsync().ContinueWith(task => task.Result.Manager);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Loads an image from the specified file.
|
/// Loads an image from the specified file.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="filePath">The full filepath to the image.</param>
|
/// <param name="filePath">The full filepath to the image.</param>
|
||||||
/// <returns>A <see cref="TextureWrap"/> object wrapping the created image. Use <see cref="TextureWrap.ImGuiHandle"/> inside ImGui.Image().</returns>
|
/// <returns>A <see cref="TextureWrap"/> object wrapping the created image. Use <see cref="TextureWrap.ImGuiHandle"/> inside ImGui.Image().</returns>
|
||||||
public TextureWrap LoadImage(string filePath)
|
public TextureWrap LoadImage(string filePath)
|
||||||
=> Service<InterfaceManager.InterfaceManagerWithScene>.Get().Manager.LoadImage(filePath);
|
=> this.InterfaceManagerWithScene?.LoadImage(filePath)
|
||||||
|
?? throw new InvalidOperationException("Load failed.");
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Loads an image from a byte stream, such as a png downloaded into memory.
|
/// Loads an image from a byte stream, such as a png downloaded into memory.
|
||||||
|
|
@ -223,7 +241,8 @@ namespace Dalamud.Interface
|
||||||
/// <param name="imageData">A byte array containing the raw image data.</param>
|
/// <param name="imageData">A byte array containing the raw image data.</param>
|
||||||
/// <returns>A <see cref="TextureWrap"/> object wrapping the created image. Use <see cref="TextureWrap.ImGuiHandle"/> inside ImGui.Image().</returns>
|
/// <returns>A <see cref="TextureWrap"/> object wrapping the created image. Use <see cref="TextureWrap.ImGuiHandle"/> inside ImGui.Image().</returns>
|
||||||
public TextureWrap LoadImage(byte[] imageData)
|
public TextureWrap LoadImage(byte[] imageData)
|
||||||
=> Service<InterfaceManager.InterfaceManagerWithScene>.Get().Manager.LoadImage(imageData);
|
=> this.InterfaceManagerWithScene?.LoadImage(imageData)
|
||||||
|
?? throw new InvalidOperationException("Load failed.");
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Loads an image from raw unformatted pixel data, with no type or header information. To load formatted data, use <see cref="LoadImage(byte[])"/>.
|
/// Loads an image from raw unformatted pixel data, with no type or header information. To load formatted data, use <see cref="LoadImage(byte[])"/>.
|
||||||
|
|
@ -234,14 +253,99 @@ namespace Dalamud.Interface
|
||||||
/// <param name="numChannels">The number of channels (bytes per pixel) of the image contained in <paramref name="imageData"/>. This should usually be 4.</param>
|
/// <param name="numChannels">The number of channels (bytes per pixel) of the image contained in <paramref name="imageData"/>. This should usually be 4.</param>
|
||||||
/// <returns>A <see cref="TextureWrap"/> object wrapping the created image. Use <see cref="TextureWrap.ImGuiHandle"/> inside ImGui.Image().</returns>
|
/// <returns>A <see cref="TextureWrap"/> object wrapping the created image. Use <see cref="TextureWrap.ImGuiHandle"/> inside ImGui.Image().</returns>
|
||||||
public TextureWrap LoadImageRaw(byte[] imageData, int width, int height, int numChannels)
|
public TextureWrap LoadImageRaw(byte[] imageData, int width, int height, int numChannels)
|
||||||
=> Service<InterfaceManager.InterfaceManagerWithScene>.Get().Manager.LoadImageRaw(imageData, width, height, numChannels);
|
=> this.InterfaceManagerWithScene?.LoadImageRaw(imageData, width, height, numChannels)
|
||||||
|
?? throw new InvalidOperationException("Load failed.");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Asynchronously loads an image from the specified file, when it's possible to do so.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="filePath">The full filepath to the image.</param>
|
||||||
|
/// <returns>A <see cref="TextureWrap"/> object wrapping the created image. Use <see cref="TextureWrap.ImGuiHandle"/> inside ImGui.Image().</returns>
|
||||||
|
public Task<TextureWrap> LoadImageAsync(string filePath) => Task.Run(
|
||||||
|
async () =>
|
||||||
|
(await this.InterfaceManagerWithSceneAsync).LoadImage(filePath)
|
||||||
|
?? throw new InvalidOperationException("Load failed."));
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Asynchronously loads an image from a byte stream, such as a png downloaded into memory, when it's possible to do so.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="imageData">A byte array containing the raw image data.</param>
|
||||||
|
/// <returns>A <see cref="TextureWrap"/> object wrapping the created image. Use <see cref="TextureWrap.ImGuiHandle"/> inside ImGui.Image().</returns>
|
||||||
|
public Task<TextureWrap> LoadImageAsync(byte[] imageData) => Task.Run(
|
||||||
|
async () =>
|
||||||
|
(await this.InterfaceManagerWithSceneAsync).LoadImage(imageData)
|
||||||
|
?? throw new InvalidOperationException("Load failed."));
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Asynchronously loads an image from raw unformatted pixel data, with no type or header information, when it's possible to do so. To load formatted data, use <see cref="LoadImage(byte[])"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="imageData">A byte array containing the raw pixel data.</param>
|
||||||
|
/// <param name="width">The width of the image contained in <paramref name="imageData"/>.</param>
|
||||||
|
/// <param name="height">The height of the image contained in <paramref name="imageData"/>.</param>
|
||||||
|
/// <param name="numChannels">The number of channels (bytes per pixel) of the image contained in <paramref name="imageData"/>. This should usually be 4.</param>
|
||||||
|
/// <returns>A <see cref="TextureWrap"/> object wrapping the created image. Use <see cref="TextureWrap.ImGuiHandle"/> inside ImGui.Image().</returns>
|
||||||
|
public Task<TextureWrap> LoadImageRawAsync(byte[] imageData, int width, int height, int numChannels) => Task.Run(
|
||||||
|
async () =>
|
||||||
|
(await this.InterfaceManagerWithSceneAsync).LoadImageRaw(imageData, width, height, numChannels)
|
||||||
|
?? throw new InvalidOperationException("Load failed."));
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Waits for UI to become available for use.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>A task that completes when the game's Present has been called at least once.</returns>
|
||||||
|
public Task WaitForUi() => this.InterfaceManagerWithSceneAsync;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Waits for UI to become available for use.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="func">Function to call.</param>
|
||||||
|
/// <param name="runInFrameworkThread">Specifies whether to call the function from the framework thread.</param>
|
||||||
|
/// <returns>A task that completes when the game's Present has been called at least once.</returns>
|
||||||
|
/// <typeparam name="T">Return type.</typeparam>
|
||||||
|
public Task<T> RunWhenUiPrepared<T>(Func<T> func, bool runInFrameworkThread = false)
|
||||||
|
{
|
||||||
|
if (runInFrameworkThread)
|
||||||
|
{
|
||||||
|
return this.InterfaceManagerWithSceneAsync
|
||||||
|
.ContinueWith(_ => Service<Framework>.Get().RunOnFrameworkThread(func))
|
||||||
|
.Unwrap();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return this.InterfaceManagerWithSceneAsync
|
||||||
|
.ContinueWith(_ => func());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Waits for UI to become available for use.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="func">Function to call.</param>
|
||||||
|
/// <param name="runInFrameworkThread">Specifies whether to call the function from the framework thread.</param>
|
||||||
|
/// <returns>A task that completes when the game's Present has been called at least once.</returns>
|
||||||
|
/// <typeparam name="T">Return type.</typeparam>
|
||||||
|
public Task<T> RunWhenUiPrepared<T>(Func<Task<T>> func, bool runInFrameworkThread = false)
|
||||||
|
{
|
||||||
|
if (runInFrameworkThread)
|
||||||
|
{
|
||||||
|
return this.InterfaceManagerWithSceneAsync
|
||||||
|
.ContinueWith(_ => Service<Framework>.Get().RunOnFrameworkThread(func))
|
||||||
|
.Unwrap();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return this.InterfaceManagerWithSceneAsync
|
||||||
|
.ContinueWith(_ => func())
|
||||||
|
.Unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets a game font.
|
/// Gets a game font.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="style">Font to get.</param>
|
/// <param name="style">Font to get.</param>
|
||||||
/// <returns>Handle to the game font which may or may not be available for use yet.</returns>
|
/// <returns>Handle to the game font which may or may not be available for use yet.</returns>
|
||||||
public GameFontHandle GetGameFontHandle(GameFontStyle style) => Service<GameFontManager>.Get().NewFontRef(style);
|
public GameFontHandle GetGameFontHandle(GameFontStyle style) => this.gameFontManager.NewFontRef(style);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Call this to queue a rebuild of the font atlas.<br/>
|
/// Call this to queue a rebuild of the font atlas.<br/>
|
||||||
|
|
@ -251,7 +355,7 @@ namespace Dalamud.Interface
|
||||||
public void RebuildFonts()
|
public void RebuildFonts()
|
||||||
{
|
{
|
||||||
Log.Verbose("[FONT] {0} plugin is initiating FONT REBUILD", this.namespaceName);
|
Log.Verbose("[FONT] {0} plugin is initiating FONT REBUILD", this.namespaceName);
|
||||||
Service<InterfaceManager>.Get().RebuildFonts();
|
this.interfaceManager.RebuildFonts();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -262,19 +366,25 @@ namespace Dalamud.Interface
|
||||||
/// <param name="type">The type of the notification.</param>
|
/// <param name="type">The type of the notification.</param>
|
||||||
/// <param name="msDelay">The time the notification should be displayed for.</param>
|
/// <param name="msDelay">The time the notification should be displayed for.</param>
|
||||||
public void AddNotification(
|
public void AddNotification(
|
||||||
string content, string? title = null, NotificationType type = NotificationType.None, uint msDelay = 3000) =>
|
string content, string? title = null, NotificationType type = NotificationType.None, uint msDelay = 3000)
|
||||||
Service<NotificationManager>.Get().AddNotification(content, title, type, msDelay);
|
{
|
||||||
|
Service<NotificationManager>
|
||||||
|
.GetAsync()
|
||||||
|
.ContinueWith(task =>
|
||||||
|
{
|
||||||
|
if (task.IsCompletedSuccessfully)
|
||||||
|
task.Result.AddNotification(content, title, type, msDelay);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Unregister the UiBuilder. Do not call this in plugin code.
|
/// Unregister the UiBuilder. Do not call this in plugin code.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
void IDisposable.Dispose()
|
void IDisposable.Dispose()
|
||||||
{
|
{
|
||||||
var interfaceManager = Service<InterfaceManager>.Get();
|
this.interfaceManager.Draw -= this.OnDraw;
|
||||||
|
this.interfaceManager.BuildFonts -= this.OnBuildFonts;
|
||||||
interfaceManager.Draw -= this.OnDraw;
|
this.interfaceManager.ResizeBuffers -= this.OnResizeBuffers;
|
||||||
interfaceManager.BuildFonts -= this.OnBuildFonts;
|
|
||||||
interfaceManager.ResizeBuffers -= this.OnResizeBuffers;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -304,8 +414,9 @@ namespace Dalamud.Interface
|
||||||
private void OnDraw()
|
private void OnDraw()
|
||||||
{
|
{
|
||||||
var configuration = Service<DalamudConfiguration>.Get();
|
var configuration = Service<DalamudConfiguration>.Get();
|
||||||
var gameGui = Service<GameGui>.Get();
|
var gameGui = Service<GameGui>.GetNullable();
|
||||||
var interfaceManager = Service<InterfaceManager>.Get();
|
if (gameGui == null)
|
||||||
|
return;
|
||||||
|
|
||||||
if ((gameGui.GameUiHidden && configuration.ToggleUiHide &&
|
if ((gameGui.GameUiHidden && configuration.ToggleUiHide &&
|
||||||
!(this.DisableUserUiHide || this.DisableAutomaticUiHide)) ||
|
!(this.DisableUserUiHide || this.DisableAutomaticUiHide)) ||
|
||||||
|
|
@ -329,7 +440,7 @@ namespace Dalamud.Interface
|
||||||
this.ShowUi?.Invoke();
|
this.ShowUi?.Invoke();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!interfaceManager.FontsReady)
|
if (!this.interfaceManager.FontsReady)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
ImGui.PushID(this.namespaceName);
|
ImGui.PushID(this.namespaceName);
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,9 @@ namespace Dalamud.Logging.Internal
|
||||||
private static readonly ConcurrentQueue<TaskInfo> NewlyCreatedTasks = new();
|
private static readonly ConcurrentQueue<TaskInfo> NewlyCreatedTasks = new();
|
||||||
private static bool clearRequested = false;
|
private static bool clearRequested = false;
|
||||||
|
|
||||||
|
[ServiceManager.ServiceDependency]
|
||||||
|
private readonly Framework framework = Service<Framework>.Get();
|
||||||
|
|
||||||
private MonoMod.RuntimeDetour.Hook? scheduleAndStartHook;
|
private MonoMod.RuntimeDetour.Hook? scheduleAndStartHook;
|
||||||
private bool enabled = false;
|
private bool enabled = false;
|
||||||
|
|
||||||
|
|
@ -111,8 +114,7 @@ namespace Dalamud.Logging.Internal
|
||||||
|
|
||||||
this.ApplyPatch();
|
this.ApplyPatch();
|
||||||
|
|
||||||
var framework = Service<Framework>.Get();
|
this.framework.Update += this.FrameworkOnUpdate;
|
||||||
framework.Update += this.FrameworkOnUpdate;
|
|
||||||
this.enabled = true;
|
this.enabled = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -121,8 +123,7 @@ namespace Dalamud.Logging.Internal
|
||||||
{
|
{
|
||||||
this.scheduleAndStartHook?.Dispose();
|
this.scheduleAndStartHook?.Dispose();
|
||||||
|
|
||||||
var framework = Service<Framework>.Get();
|
this.framework.Update -= this.FrameworkOnUpdate;
|
||||||
framework.Update -= this.FrameworkOnUpdate;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool AddToActiveTasksHook(Func<Task, bool> orig, Task self)
|
private static bool AddToActiveTasksHook(Func<Task, bool> orig, Task self)
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ using Dalamud.Game.Text.Sanitizer;
|
||||||
using Dalamud.Game.Text.SeStringHandling;
|
using Dalamud.Game.Text.SeStringHandling;
|
||||||
using Dalamud.Game.Text.SeStringHandling.Payloads;
|
using Dalamud.Game.Text.SeStringHandling.Payloads;
|
||||||
using Dalamud.Interface;
|
using Dalamud.Interface;
|
||||||
|
using Dalamud.Interface.GameFonts;
|
||||||
using Dalamud.Interface.Internal;
|
using Dalamud.Interface.Internal;
|
||||||
using Dalamud.Plugin.Internal;
|
using Dalamud.Plugin.Internal;
|
||||||
using Dalamud.Plugin.Ipc;
|
using Dalamud.Plugin.Ipc;
|
||||||
|
|
|
||||||
|
|
@ -38,6 +38,11 @@ internal partial class PluginManager : IDisposable, IServiceType
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public const int DalamudApiLevel = 6;
|
public const int DalamudApiLevel = 6;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Default time to wait between plugin unload and plugin assembly unload.
|
||||||
|
/// </summary>
|
||||||
|
public const int PluginWaitBeforeFreeDefault = 500;
|
||||||
|
|
||||||
private static readonly ModuleLog Log = new("PLUGINM");
|
private static readonly ModuleLog Log = new("PLUGINM");
|
||||||
|
|
||||||
private readonly object pluginListLock = new();
|
private readonly object pluginListLock = new();
|
||||||
|
|
@ -207,16 +212,43 @@ internal partial class PluginManager : IDisposable, IServiceType
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
foreach (var plugin in this.InstalledPlugins)
|
if (this.InstalledPlugins.Any())
|
||||||
{
|
{
|
||||||
try
|
// Unload them first, just in case some of plugin codes are still running via callbacks initiated externally.
|
||||||
|
foreach (var plugin in this.InstalledPlugins.Where(plugin => !plugin.Manifest.CanUnloadAsync))
|
||||||
{
|
{
|
||||||
plugin.Dispose();
|
try
|
||||||
}
|
{
|
||||||
catch (Exception ex)
|
plugin.UnloadAsync(true, false).Wait();
|
||||||
{
|
}
|
||||||
Log.Error(ex, $"Error disposing {plugin.Name}");
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Log.Error(ex, $"Error unloading {plugin.Name}");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Task.WaitAll(this.InstalledPlugins
|
||||||
|
.Where(plugin => plugin.Manifest.CanUnloadAsync)
|
||||||
|
.Select(plugin => Task.Run(async () =>
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await plugin.UnloadAsync(true, false);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Log.Error(ex, $"Error unloading {plugin.Name}");
|
||||||
|
}
|
||||||
|
})).ToArray());
|
||||||
|
|
||||||
|
// Just in case plugins still have tasks running that they didn't cancel when they should have,
|
||||||
|
// give them some time to complete it.
|
||||||
|
Thread.Sleep(this.configuration.PluginWaitBeforeFree ?? PluginWaitBeforeFreeDefault);
|
||||||
|
|
||||||
|
// Now that we've waited enough, dispose the whole plugin.
|
||||||
|
// Since plugins should have been unloaded above, this should be done quickly.
|
||||||
|
foreach (var plugin in this.InstalledPlugins)
|
||||||
|
plugin.ExplicitDisposeIgnoreExceptions($"Error disposing {plugin.Name}", Log);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.assemblyLocationMonoHook?.Dispose();
|
this.assemblyLocationMonoHook?.Dispose();
|
||||||
|
|
@ -891,7 +923,7 @@ internal partial class PluginManager : IDisposable, IServiceType
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
plugin.Unload();
|
await plugin.UnloadAsync();
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
|
@ -963,23 +995,30 @@ internal partial class PluginManager : IDisposable, IServiceType
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="plugin">The plugin.</param>
|
/// <param name="plugin">The plugin.</param>
|
||||||
/// <exception cref="Exception">Throws if the plugin is still loading/unloading.</exception>
|
/// <exception cref="Exception">Throws if the plugin is still loading/unloading.</exception>
|
||||||
public void DeleteConfiguration(LocalPlugin plugin)
|
/// <returns>The task.</returns>
|
||||||
|
public async Task DeleteConfigurationAsync(LocalPlugin plugin)
|
||||||
{
|
{
|
||||||
if (plugin.State == PluginState.InProgress)
|
if (plugin.State == PluginState.Loading || plugin.State == PluginState.Unloaded)
|
||||||
throw new Exception("Cannot delete configuration for a loading/unloading plugin");
|
throw new Exception("Cannot delete configuration for a loading/unloading plugin");
|
||||||
|
|
||||||
if (plugin.IsLoaded)
|
if (plugin.IsLoaded)
|
||||||
plugin.Unload();
|
await plugin.UnloadAsync();
|
||||||
|
|
||||||
// Let's wait so any handles on files in plugin configurations can be closed
|
for (var waitUntil = Environment.TickCount64 + 1000; Environment.TickCount64 < waitUntil;)
|
||||||
Thread.Sleep(500);
|
{
|
||||||
|
try
|
||||||
this.PluginConfigs.Delete(plugin.Name);
|
{
|
||||||
|
this.PluginConfigs.Delete(plugin.Name);
|
||||||
Thread.Sleep(500);
|
break;
|
||||||
|
}
|
||||||
|
catch (IOException)
|
||||||
|
{
|
||||||
|
await Task.Delay(100);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Let's indicate "installer" here since this is supposed to be a fresh install
|
// Let's indicate "installer" here since this is supposed to be a fresh install
|
||||||
plugin.LoadAsync(PluginLoadReason.Installer).Wait();
|
await plugin.LoadAsync(PluginLoadReason.Installer);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
||||||
|
|
@ -2,10 +2,13 @@ using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Dalamud.Configuration.Internal;
|
using Dalamud.Configuration.Internal;
|
||||||
using Dalamud.Game;
|
using Dalamud.Game;
|
||||||
using Dalamud.Game.Gui.Dtr;
|
using Dalamud.Game.Gui.Dtr;
|
||||||
|
using Dalamud.Interface.GameFonts;
|
||||||
|
using Dalamud.Interface.Internal;
|
||||||
using Dalamud.IoC.Internal;
|
using Dalamud.IoC.Internal;
|
||||||
using Dalamud.Logging.Internal;
|
using Dalamud.Logging.Internal;
|
||||||
using Dalamud.Plugin.Internal.Exceptions;
|
using Dalamud.Plugin.Internal.Exceptions;
|
||||||
|
|
@ -27,6 +30,8 @@ internal class LocalPlugin : IDisposable
|
||||||
private readonly FileInfo disabledFile;
|
private readonly FileInfo disabledFile;
|
||||||
private readonly FileInfo testingFile;
|
private readonly FileInfo testingFile;
|
||||||
|
|
||||||
|
private readonly SemaphoreSlim pluginLoadStateLock = new(1);
|
||||||
|
|
||||||
private PluginLoader? loader;
|
private PluginLoader? loader;
|
||||||
private Assembly? pluginAssembly;
|
private Assembly? pluginAssembly;
|
||||||
private Type? pluginType;
|
private Type? pluginType;
|
||||||
|
|
@ -208,8 +213,20 @@ internal class LocalPlugin : IDisposable
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
this.instance?.Dispose();
|
var framework = Service<Framework>.GetNullable();
|
||||||
this.instance = null;
|
var configuration = Service<DalamudConfiguration>.Get();
|
||||||
|
|
||||||
|
var didPluginDispose = false;
|
||||||
|
if (this.instance != null)
|
||||||
|
{
|
||||||
|
didPluginDispose = true;
|
||||||
|
if (this.Manifest.CanUnloadAsync || framework == null)
|
||||||
|
this.instance.Dispose();
|
||||||
|
else
|
||||||
|
framework.RunOnFrameworkThread(() => this.instance.Dispose()).Wait();
|
||||||
|
|
||||||
|
this.instance = null;
|
||||||
|
}
|
||||||
|
|
||||||
this.DalamudInterface?.ExplicitDispose();
|
this.DalamudInterface?.ExplicitDispose();
|
||||||
this.DalamudInterface = null;
|
this.DalamudInterface = null;
|
||||||
|
|
@ -217,6 +234,8 @@ internal class LocalPlugin : IDisposable
|
||||||
this.pluginType = null;
|
this.pluginType = null;
|
||||||
this.pluginAssembly = null;
|
this.pluginAssembly = null;
|
||||||
|
|
||||||
|
if (this.loader != null && didPluginDispose)
|
||||||
|
Thread.Sleep(configuration.PluginWaitBeforeFree ?? PluginManager.PluginWaitBeforeFreeDefault);
|
||||||
this.loader?.Dispose();
|
this.loader?.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -228,54 +247,73 @@ internal class LocalPlugin : IDisposable
|
||||||
/// <returns>A task.</returns>
|
/// <returns>A task.</returns>
|
||||||
public async Task LoadAsync(PluginLoadReason reason, bool reloading = false)
|
public async Task LoadAsync(PluginLoadReason reason, bool reloading = false)
|
||||||
{
|
{
|
||||||
var startInfo = Service<DalamudStartInfo>.Get();
|
var configuration = await Service<DalamudConfiguration>.GetAsync();
|
||||||
var configuration = Service<DalamudConfiguration>.Get();
|
var framework = await Service<Framework>.GetAsync();
|
||||||
var pluginManager = Service<PluginManager>.Get();
|
var ioc = await Service<ServiceContainer>.GetAsync();
|
||||||
|
var pluginManager = await Service<PluginManager>.GetAsync();
|
||||||
|
var startInfo = await Service<DalamudStartInfo>.GetAsync();
|
||||||
|
|
||||||
// Allowed: Unloaded
|
// UiBuilder constructor requires the following two.
|
||||||
switch (this.State)
|
await Service<InterfaceManager>.GetAsync();
|
||||||
{
|
await Service<GameFontManager>.GetAsync();
|
||||||
case PluginState.InProgress:
|
|
||||||
throw new InvalidPluginOperationException($"Unable to load {this.Name}, already working");
|
|
||||||
case PluginState.Loaded:
|
|
||||||
throw new InvalidPluginOperationException($"Unable to load {this.Name}, already loaded");
|
|
||||||
case PluginState.LoadError:
|
|
||||||
throw new InvalidPluginOperationException($"Unable to load {this.Name}, load previously faulted, unload first");
|
|
||||||
case PluginState.UnloadError:
|
|
||||||
throw new InvalidPluginOperationException($"Unable to load {this.Name}, unload previously faulted, restart Dalamud");
|
|
||||||
case PluginState.Unloaded:
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
throw new ArgumentOutOfRangeException(this.State.ToString());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (pluginManager.IsManifestBanned(this.Manifest))
|
if (this.Manifest.LoadRequiredState == 0)
|
||||||
throw new BannedPluginException($"Unable to load {this.Name}, banned");
|
_ = await Service<InterfaceManager.InterfaceManagerWithScene>.GetAsync();
|
||||||
|
|
||||||
if (this.Manifest.ApplicableVersion < startInfo.GameVersion)
|
|
||||||
throw new InvalidPluginOperationException($"Unable to load {this.Name}, no applicable version");
|
|
||||||
|
|
||||||
if (this.Manifest.DalamudApiLevel < PluginManager.DalamudApiLevel && !configuration.LoadAllApiLevels)
|
|
||||||
throw new InvalidPluginOperationException($"Unable to load {this.Name}, incompatible API level");
|
|
||||||
|
|
||||||
if (this.Manifest.Disabled)
|
|
||||||
throw new InvalidPluginOperationException($"Unable to load {this.Name}, disabled");
|
|
||||||
|
|
||||||
this.State = PluginState.InProgress;
|
|
||||||
Log.Information($"Loading {this.DllFile.Name}");
|
|
||||||
|
|
||||||
if (this.DllFile.DirectoryName != null && File.Exists(Path.Combine(this.DllFile.DirectoryName, "Dalamud.dll")))
|
|
||||||
{
|
|
||||||
Log.Error("==== IMPORTANT MESSAGE TO {0}, THE DEVELOPER OF {1} ====", this.Manifest.Author!, this.Manifest.InternalName);
|
|
||||||
Log.Error("YOU ARE INCLUDING DALAMUD DEPENDENCIES IN YOUR BUILDS!!!");
|
|
||||||
Log.Error("You may not be able to load your plugin. \"<Private>False</Private>\" needs to be set in your csproj.");
|
|
||||||
Log.Error("If you are using ILMerge, do not merge anything other than your direct dependencies.");
|
|
||||||
Log.Error("Do not merge FFXIVClientStructs.Generators.dll.");
|
|
||||||
Log.Error("Please refer to https://github.com/goatcorp/Dalamud/discussions/603 for more information.");
|
|
||||||
}
|
|
||||||
|
|
||||||
|
await this.pluginLoadStateLock.WaitAsync();
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
switch (this.State)
|
||||||
|
{
|
||||||
|
case PluginState.Loaded:
|
||||||
|
throw new InvalidPluginOperationException($"Unable to load {this.Name}, already loaded");
|
||||||
|
case PluginState.LoadError:
|
||||||
|
throw new InvalidPluginOperationException(
|
||||||
|
$"Unable to load {this.Name}, load previously faulted, unload first");
|
||||||
|
case PluginState.UnloadError:
|
||||||
|
throw new InvalidPluginOperationException(
|
||||||
|
$"Unable to load {this.Name}, unload previously faulted, restart Dalamud");
|
||||||
|
case PluginState.Unloaded:
|
||||||
|
break;
|
||||||
|
case PluginState.Loading:
|
||||||
|
case PluginState.Unloading:
|
||||||
|
default:
|
||||||
|
throw new ArgumentOutOfRangeException(this.State.ToString());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pluginManager.IsManifestBanned(this.Manifest))
|
||||||
|
throw new BannedPluginException($"Unable to load {this.Name}, banned");
|
||||||
|
|
||||||
|
if (this.Manifest.ApplicableVersion < startInfo.GameVersion)
|
||||||
|
throw new InvalidPluginOperationException($"Unable to load {this.Name}, no applicable version");
|
||||||
|
|
||||||
|
if (this.Manifest.DalamudApiLevel < PluginManager.DalamudApiLevel && !configuration.LoadAllApiLevels)
|
||||||
|
throw new InvalidPluginOperationException($"Unable to load {this.Name}, incompatible API level");
|
||||||
|
|
||||||
|
if (this.Manifest.Disabled)
|
||||||
|
throw new InvalidPluginOperationException($"Unable to load {this.Name}, disabled");
|
||||||
|
|
||||||
|
this.State = PluginState.Loading;
|
||||||
|
Log.Information($"Loading {this.DllFile.Name}");
|
||||||
|
|
||||||
|
if (this.DllFile.DirectoryName != null &&
|
||||||
|
File.Exists(Path.Combine(this.DllFile.DirectoryName, "Dalamud.dll")))
|
||||||
|
{
|
||||||
|
Log.Error(
|
||||||
|
"==== IMPORTANT MESSAGE TO {0}, THE DEVELOPER OF {1} ====",
|
||||||
|
this.Manifest.Author!,
|
||||||
|
this.Manifest.InternalName);
|
||||||
|
Log.Error(
|
||||||
|
"YOU ARE INCLUDING DALAMUD DEPENDENCIES IN YOUR BUILDS!!!");
|
||||||
|
Log.Error(
|
||||||
|
"You may not be able to load your plugin. \"<Private>False</Private>\" needs to be set in your csproj.");
|
||||||
|
Log.Error(
|
||||||
|
"If you are using ILMerge, do not merge anything other than your direct dependencies.");
|
||||||
|
Log.Error("Do not merge FFXIVClientStructs.Generators.dll.");
|
||||||
|
Log.Error(
|
||||||
|
"Please refer to https://github.com/goatcorp/Dalamud/discussions/603 for more information.");
|
||||||
|
}
|
||||||
|
|
||||||
this.loader ??= PluginLoader.CreateFromAssemblyFile(this.DllFile.FullName, SetupLoaderConfig);
|
this.loader ??= PluginLoader.CreateFromAssemblyFile(this.DllFile.FullName, SetupLoaderConfig);
|
||||||
|
|
||||||
if (reloading || this.IsDev)
|
if (reloading || this.IsDev)
|
||||||
|
|
@ -309,7 +347,8 @@ internal class LocalPlugin : IDisposable
|
||||||
this.AssemblyName = this.pluginAssembly.GetName();
|
this.AssemblyName = this.pluginAssembly.GetName();
|
||||||
|
|
||||||
// Find the plugin interface implementation. It is guaranteed to exist after checking in the ctor.
|
// Find the plugin interface implementation. It is guaranteed to exist after checking in the ctor.
|
||||||
this.pluginType ??= this.pluginAssembly.GetTypes().First(type => type.IsAssignableTo(typeof(IDalamudPlugin)));
|
this.pluginType ??= this.pluginAssembly.GetTypes()
|
||||||
|
.First(type => type.IsAssignableTo(typeof(IDalamudPlugin)));
|
||||||
|
|
||||||
// Check for any loaded plugins with the same assembly name
|
// Check for any loaded plugins with the same assembly name
|
||||||
var assemblyName = this.pluginAssembly.GetName().Name;
|
var assemblyName = this.pluginAssembly.GetName().Name;
|
||||||
|
|
@ -319,7 +358,8 @@ internal class LocalPlugin : IDisposable
|
||||||
if (otherPlugin == this || otherPlugin.instance == null)
|
if (otherPlugin == this || otherPlugin.instance == null)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
var otherPluginAssemblyName = otherPlugin.instance.GetType().Assembly.GetName().Name;
|
var otherPluginAssemblyName =
|
||||||
|
otherPlugin.instance.GetType().Assembly.GetName().Name;
|
||||||
if (otherPluginAssemblyName == assemblyName && otherPluginAssemblyName != null)
|
if (otherPluginAssemblyName == assemblyName && otherPluginAssemblyName != null)
|
||||||
{
|
{
|
||||||
this.State = PluginState.Unloaded;
|
this.State = PluginState.Unloaded;
|
||||||
|
|
@ -330,17 +370,29 @@ internal class LocalPlugin : IDisposable
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update the location for the Location and CodeBase patches
|
// Update the location for the Location and CodeBase patches
|
||||||
PluginManager.PluginLocations[this.pluginType.Assembly.FullName] = new PluginPatchData(this.DllFile);
|
PluginManager.PluginLocations[this.pluginType.Assembly.FullName] =
|
||||||
|
new PluginPatchData(this.DllFile);
|
||||||
|
|
||||||
this.DalamudInterface = new DalamudPluginInterface(this.pluginAssembly.GetName().Name!, this.DllFile, reason, this.IsDev);
|
this.DalamudInterface =
|
||||||
|
new DalamudPluginInterface(this.pluginAssembly.GetName().Name!, this.DllFile, reason, this.IsDev);
|
||||||
|
|
||||||
|
if (this.Manifest.LoadSync && this.Manifest.LoadRequiredState is 0 or 1)
|
||||||
|
{
|
||||||
|
this.instance = await framework.RunOnFrameworkThread(
|
||||||
|
() => ioc.CreateAsync(this.pluginType!, this.DalamudInterface!)) as IDalamudPlugin;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
this.instance =
|
||||||
|
await ioc.CreateAsync(this.pluginType!, this.DalamudInterface!) as IDalamudPlugin;
|
||||||
|
}
|
||||||
|
|
||||||
var ioc = Service<ServiceContainer>.Get();
|
|
||||||
this.instance = await ioc.CreateAsync(this.pluginType, this.DalamudInterface) as IDalamudPlugin;
|
|
||||||
if (this.instance == null)
|
if (this.instance == null)
|
||||||
{
|
{
|
||||||
this.State = PluginState.LoadError;
|
this.State = PluginState.LoadError;
|
||||||
this.DalamudInterface.ExplicitDispose();
|
this.DalamudInterface.ExplicitDispose();
|
||||||
Log.Error($"Error while loading {this.Name}, failed to bind and call the plugin constructor");
|
Log.Error(
|
||||||
|
$"Error while loading {this.Name}, failed to bind and call the plugin constructor");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -363,6 +415,10 @@ internal class LocalPlugin : IDisposable
|
||||||
|
|
||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
this.pluginLoadStateLock.Release();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -370,31 +426,40 @@ internal class LocalPlugin : IDisposable
|
||||||
/// in the plugin list until it has been actually disposed.
|
/// in the plugin list until it has been actually disposed.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="reloading">Unload while reloading.</param>
|
/// <param name="reloading">Unload while reloading.</param>
|
||||||
public void Unload(bool reloading = false)
|
/// <param name="waitBeforeLoaderDispose">Wait before disposing loader.</param>
|
||||||
|
/// <returns>The task.</returns>
|
||||||
|
public async Task UnloadAsync(bool reloading = false, bool waitBeforeLoaderDispose = true)
|
||||||
{
|
{
|
||||||
// Allowed: Loaded, LoadError(we are cleaning this up while we're at it)
|
var configuration = Service<DalamudConfiguration>.Get();
|
||||||
switch (this.State)
|
var framework = Service<Framework>.GetNullable();
|
||||||
{
|
|
||||||
case PluginState.InProgress:
|
|
||||||
throw new InvalidPluginOperationException($"Unable to unload {this.Name}, already working");
|
|
||||||
case PluginState.Unloaded:
|
|
||||||
throw new InvalidPluginOperationException($"Unable to unload {this.Name}, already unloaded");
|
|
||||||
case PluginState.UnloadError:
|
|
||||||
throw new InvalidPluginOperationException($"Unable to unload {this.Name}, unload previously faulted, restart Dalamud");
|
|
||||||
case PluginState.Loaded:
|
|
||||||
break;
|
|
||||||
case PluginState.LoadError:
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
throw new ArgumentOutOfRangeException(this.State.ToString());
|
|
||||||
}
|
|
||||||
|
|
||||||
|
await this.pluginLoadStateLock.WaitAsync();
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
this.State = PluginState.InProgress;
|
switch (this.State)
|
||||||
|
{
|
||||||
|
case PluginState.Unloaded:
|
||||||
|
throw new InvalidPluginOperationException($"Unable to unload {this.Name}, already unloaded");
|
||||||
|
case PluginState.UnloadError:
|
||||||
|
throw new InvalidPluginOperationException(
|
||||||
|
$"Unable to unload {this.Name}, unload previously faulted, restart Dalamud");
|
||||||
|
case PluginState.Loaded:
|
||||||
|
case PluginState.LoadError:
|
||||||
|
break;
|
||||||
|
case PluginState.Loading:
|
||||||
|
case PluginState.Unloading:
|
||||||
|
default:
|
||||||
|
throw new ArgumentOutOfRangeException(this.State.ToString());
|
||||||
|
}
|
||||||
|
|
||||||
|
this.State = PluginState.Unloading;
|
||||||
Log.Information($"Unloading {this.DllFile.Name}");
|
Log.Information($"Unloading {this.DllFile.Name}");
|
||||||
|
|
||||||
this.instance?.Dispose();
|
if (this.Manifest.CanUnloadAsync || framework == null)
|
||||||
|
this.instance?.Dispose();
|
||||||
|
else
|
||||||
|
await framework.RunOnFrameworkThread(() => this.instance?.Dispose());
|
||||||
|
|
||||||
this.instance = null;
|
this.instance = null;
|
||||||
|
|
||||||
this.DalamudInterface?.ExplicitDispose();
|
this.DalamudInterface?.ExplicitDispose();
|
||||||
|
|
@ -405,6 +470,8 @@ internal class LocalPlugin : IDisposable
|
||||||
|
|
||||||
if (!reloading)
|
if (!reloading)
|
||||||
{
|
{
|
||||||
|
if (waitBeforeLoaderDispose && this.loader != null)
|
||||||
|
await Task.Delay(configuration.PluginWaitBeforeFree ?? PluginManager.PluginWaitBeforeFreeDefault);
|
||||||
this.loader?.Dispose();
|
this.loader?.Dispose();
|
||||||
this.loader = null;
|
this.loader = null;
|
||||||
}
|
}
|
||||||
|
|
@ -419,6 +486,13 @@ internal class LocalPlugin : IDisposable
|
||||||
|
|
||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
// We need to handle removed DTR nodes here, as otherwise, plugins will not be able to re-add their bar entries after updates.
|
||||||
|
Service<DtrBar>.GetNullable()?.HandleRemovedNodes();
|
||||||
|
|
||||||
|
this.pluginLoadStateLock.Release();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -427,12 +501,7 @@ internal class LocalPlugin : IDisposable
|
||||||
/// <returns>A task.</returns>
|
/// <returns>A task.</returns>
|
||||||
public async Task ReloadAsync()
|
public async Task ReloadAsync()
|
||||||
{
|
{
|
||||||
this.Unload(true);
|
await this.UnloadAsync(true);
|
||||||
|
|
||||||
// We need to handle removed DTR nodes here, as otherwise, plugins will not be able to re-add their bar entries after updates.
|
|
||||||
var dtr = Service<DtrBar>.Get();
|
|
||||||
dtr.HandleRemovedNodes();
|
|
||||||
|
|
||||||
await this.LoadAsync(PluginLoadReason.Reload, true);
|
await this.LoadAsync(PluginLoadReason.Reload, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -444,7 +513,8 @@ internal class LocalPlugin : IDisposable
|
||||||
// Allowed: Unloaded, UnloadError
|
// Allowed: Unloaded, UnloadError
|
||||||
switch (this.State)
|
switch (this.State)
|
||||||
{
|
{
|
||||||
case PluginState.InProgress:
|
case PluginState.Loading:
|
||||||
|
case PluginState.Unloading:
|
||||||
case PluginState.Loaded:
|
case PluginState.Loaded:
|
||||||
case PluginState.LoadError:
|
case PluginState.LoadError:
|
||||||
throw new InvalidPluginOperationException($"Unable to enable {this.Name}, still loaded");
|
throw new InvalidPluginOperationException($"Unable to enable {this.Name}, still loaded");
|
||||||
|
|
@ -471,7 +541,8 @@ internal class LocalPlugin : IDisposable
|
||||||
// Allowed: Unloaded, UnloadError
|
// Allowed: Unloaded, UnloadError
|
||||||
switch (this.State)
|
switch (this.State)
|
||||||
{
|
{
|
||||||
case PluginState.InProgress:
|
case PluginState.Loading:
|
||||||
|
case PluginState.Unloading:
|
||||||
case PluginState.Loaded:
|
case PluginState.Loaded:
|
||||||
case PluginState.LoadError:
|
case PluginState.LoadError:
|
||||||
throw new InvalidPluginOperationException($"Unable to disable {this.Name}, still loaded");
|
throw new InvalidPluginOperationException($"Unable to disable {this.Name}, still loaded");
|
||||||
|
|
|
||||||
|
|
@ -156,6 +156,12 @@ internal record PluginManifest
|
||||||
[JsonProperty]
|
[JsonProperty]
|
||||||
public int LoadPriority { get; init; }
|
public int LoadPriority { get; init; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a value indicating whether the plugin can be unloaded asynchronously.
|
||||||
|
/// </summary>
|
||||||
|
[JsonProperty]
|
||||||
|
public bool CanUnloadAsync { get; init; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets a list of screenshot image URLs to show in the plugin installer.
|
/// Gets a list of screenshot image URLs to show in the plugin installer.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
||||||
|
|
@ -16,9 +16,9 @@ internal enum PluginState
|
||||||
UnloadError,
|
UnloadError,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Currently loading.
|
/// Currently unloading.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
InProgress,
|
Unloading,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Load is successful.
|
/// Load is successful.
|
||||||
|
|
@ -29,4 +29,9 @@ internal enum PluginState
|
||||||
/// Plugin has thrown an error during loading.
|
/// Plugin has thrown an error during loading.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
LoadError,
|
LoadError,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Currently loading.
|
||||||
|
/// </summary>
|
||||||
|
Loading,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,8 @@ namespace Dalamud
|
||||||
|
|
||||||
private static readonly TaskCompletionSource BlockingServicesLoadedTaskCompletionSource = new();
|
private static readonly TaskCompletionSource BlockingServicesLoadedTaskCompletionSource = new();
|
||||||
|
|
||||||
|
private static readonly List<Type> LoadedServices = new();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets task that gets completed when all blocking early loading services are done loading.
|
/// Gets task that gets completed when all blocking early loading services are done loading.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -38,20 +40,35 @@ namespace Dalamud
|
||||||
/// <param name="configuration">Instance of <see cref="DalamudConfiguration"/>.</param>
|
/// <param name="configuration">Instance of <see cref="DalamudConfiguration"/>.</param>
|
||||||
public static void InitializeProvidedServicesAndClientStructs(Dalamud dalamud, DalamudStartInfo startInfo, DalamudConfiguration configuration)
|
public static void InitializeProvidedServicesAndClientStructs(Dalamud dalamud, DalamudStartInfo startInfo, DalamudConfiguration configuration)
|
||||||
{
|
{
|
||||||
Service<Dalamud>.Provide(dalamud);
|
|
||||||
Service<DalamudStartInfo>.Provide(startInfo);
|
|
||||||
Service<DalamudConfiguration>.Provide(configuration);
|
|
||||||
Service<ServiceContainer>.Provide(new ServiceContainer());
|
|
||||||
|
|
||||||
// Initialize the process information.
|
// Initialize the process information.
|
||||||
var cacheDir = new DirectoryInfo(Path.Combine(startInfo.WorkingDirectory!, "cachedSigs"));
|
var cacheDir = new DirectoryInfo(Path.Combine(startInfo.WorkingDirectory!, "cachedSigs"));
|
||||||
if (!cacheDir.Exists)
|
if (!cacheDir.Exists)
|
||||||
cacheDir.Create();
|
cacheDir.Create();
|
||||||
Service<SigScanner>.Provide(new SigScanner(true, new FileInfo(Path.Combine(cacheDir.FullName, $"{startInfo.GameVersion}.json"))));
|
|
||||||
|
lock (LoadedServices)
|
||||||
|
{
|
||||||
|
Service<Dalamud>.Provide(dalamud);
|
||||||
|
LoadedServices.Add(typeof(Dalamud));
|
||||||
|
|
||||||
|
Service<DalamudStartInfo>.Provide(startInfo);
|
||||||
|
LoadedServices.Add(typeof(DalamudStartInfo));
|
||||||
|
|
||||||
|
Service<DalamudConfiguration>.Provide(configuration);
|
||||||
|
LoadedServices.Add(typeof(DalamudConfiguration));
|
||||||
|
|
||||||
|
Service<ServiceContainer>.Provide(new ServiceContainer());
|
||||||
|
LoadedServices.Add(typeof(ServiceContainer));
|
||||||
|
|
||||||
|
Service<SigScanner>.Provide(
|
||||||
|
new SigScanner(
|
||||||
|
true, new FileInfo(Path.Combine(cacheDir.FullName, $"{startInfo.GameVersion}.json"))));
|
||||||
|
LoadedServices.Add(typeof(SigScanner));
|
||||||
|
}
|
||||||
|
|
||||||
using (Timings.Start("CS Resolver Init"))
|
using (Timings.Start("CS Resolver Init"))
|
||||||
{
|
{
|
||||||
FFXIVClientStructs.Resolver.InitializeParallel(new FileInfo(Path.Combine(cacheDir.FullName, $"{startInfo.GameVersion}_cs.json")));
|
FFXIVClientStructs.Resolver.InitializeParallel(
|
||||||
|
new FileInfo(Path.Combine(cacheDir.FullName, $"{startInfo.GameVersion}_cs.json")));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -62,7 +79,6 @@ namespace Dalamud
|
||||||
public static async Task InitializeEarlyLoadableServices()
|
public static async Task InitializeEarlyLoadableServices()
|
||||||
{
|
{
|
||||||
using var serviceInitializeTimings = Timings.Start("Services Init");
|
using var serviceInitializeTimings = Timings.Start("Services Init");
|
||||||
var service = typeof(Service<>);
|
|
||||||
|
|
||||||
var earlyLoadingServices = new HashSet<Type>();
|
var earlyLoadingServices = new HashSet<Type>();
|
||||||
var blockingEarlyLoadingServices = new HashSet<Type>();
|
var blockingEarlyLoadingServices = new HashSet<Type>();
|
||||||
|
|
@ -76,12 +92,14 @@ namespace Dalamud
|
||||||
if (attr?.IsAssignableTo(typeof(EarlyLoadedService)) != true)
|
if (attr?.IsAssignableTo(typeof(EarlyLoadedService)) != true)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
var getTask = (Task)service.MakeGenericType(serviceType).InvokeMember(
|
var getTask = (Task)typeof(Service<>)
|
||||||
"GetAsync",
|
.MakeGenericType(serviceType)
|
||||||
BindingFlags.InvokeMethod | BindingFlags.Static | BindingFlags.Public,
|
.InvokeMember(
|
||||||
null,
|
"GetAsync",
|
||||||
null,
|
BindingFlags.InvokeMethod | BindingFlags.Static | BindingFlags.Public,
|
||||||
null);
|
null,
|
||||||
|
null,
|
||||||
|
null);
|
||||||
|
|
||||||
if (attr.IsAssignableTo(typeof(BlockingEarlyLoadedService)))
|
if (attr.IsAssignableTo(typeof(BlockingEarlyLoadedService)))
|
||||||
{
|
{
|
||||||
|
|
@ -94,7 +112,7 @@ namespace Dalamud
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencyServicesMap[serviceType] =
|
dependencyServicesMap[serviceType] =
|
||||||
(List<Type>)service
|
(List<Type>)typeof(Service<>)
|
||||||
.MakeGenericType(serviceType)
|
.MakeGenericType(serviceType)
|
||||||
.InvokeMember(
|
.InvokeMember(
|
||||||
"GetDependencyServices",
|
"GetDependencyServices",
|
||||||
|
|
@ -118,9 +136,9 @@ namespace Dalamud
|
||||||
}
|
}
|
||||||
}).ConfigureAwait(false);
|
}).ConfigureAwait(false);
|
||||||
|
|
||||||
|
var tasks = new List<Task>();
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var tasks = new List<Task>();
|
|
||||||
var servicesToLoad = new HashSet<Type>();
|
var servicesToLoad = new HashSet<Type>();
|
||||||
servicesToLoad.UnionWith(earlyLoadingServices);
|
servicesToLoad.UnionWith(earlyLoadingServices);
|
||||||
servicesToLoad.UnionWith(blockingEarlyLoadingServices);
|
servicesToLoad.UnionWith(blockingEarlyLoadingServices);
|
||||||
|
|
@ -133,13 +151,25 @@ namespace Dalamud
|
||||||
x => getAsyncTaskMap.GetValueOrDefault(x)?.IsCompleted == false))
|
x => getAsyncTaskMap.GetValueOrDefault(x)?.IsCompleted == false))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
tasks.Add((Task)service.MakeGenericType(serviceType).InvokeMember(
|
tasks.Add((Task)typeof(Service<>)
|
||||||
"StartLoader",
|
.MakeGenericType(serviceType)
|
||||||
BindingFlags.InvokeMethod | BindingFlags.Static | BindingFlags.Public,
|
.InvokeMember(
|
||||||
null,
|
"StartLoader",
|
||||||
null,
|
BindingFlags.InvokeMethod | BindingFlags.Static | BindingFlags.NonPublic,
|
||||||
null));
|
null,
|
||||||
|
null,
|
||||||
|
null));
|
||||||
servicesToLoad.Remove(serviceType);
|
servicesToLoad.Remove(serviceType);
|
||||||
|
|
||||||
|
tasks.Add(tasks.Last().ContinueWith(task =>
|
||||||
|
{
|
||||||
|
if (task.IsFaulted)
|
||||||
|
return;
|
||||||
|
lock (LoadedServices)
|
||||||
|
{
|
||||||
|
LoadedServices.Add(serviceType);
|
||||||
|
}
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!tasks.Any())
|
if (!tasks.Any())
|
||||||
|
|
@ -172,10 +202,49 @@ namespace Dalamud
|
||||||
// don't care, as this means task result/exception has already been set
|
// don't care, as this means task result/exception has already been set
|
||||||
}
|
}
|
||||||
|
|
||||||
|
while (tasks.Any())
|
||||||
|
{
|
||||||
|
await Task.WhenAny(tasks);
|
||||||
|
tasks.RemoveAll(x => x.IsCompleted);
|
||||||
|
}
|
||||||
|
|
||||||
|
UnloadAllServices();
|
||||||
|
|
||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Unloads all services, in the reverse order of load.
|
||||||
|
/// </summary>
|
||||||
|
public static void UnloadAllServices()
|
||||||
|
{
|
||||||
|
var framework = Service<Framework>.GetNullable(Service<Framework>.ExceptionPropagationMode.None);
|
||||||
|
if (framework is { IsInFrameworkUpdateThread: false, IsFrameworkUnloading: false })
|
||||||
|
{
|
||||||
|
framework.RunOnFrameworkThread(UnloadAllServices).Wait();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
lock (LoadedServices)
|
||||||
|
{
|
||||||
|
while (LoadedServices.Any())
|
||||||
|
{
|
||||||
|
var serviceType = LoadedServices.Last();
|
||||||
|
LoadedServices.RemoveAt(LoadedServices.Count - 1);
|
||||||
|
|
||||||
|
typeof(Service<>)
|
||||||
|
.MakeGenericType(serviceType)
|
||||||
|
.InvokeMember(
|
||||||
|
"Unset",
|
||||||
|
BindingFlags.InvokeMethod | BindingFlags.Static | BindingFlags.NonPublic,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Indicates that this constructor will be called for early initialization.
|
/// Indicates that this constructor will be called for early initialization.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
||||||
|
|
@ -19,8 +19,7 @@ namespace Dalamud
|
||||||
/// <typeparam name="T">The class you want to store in the service locator.</typeparam>
|
/// <typeparam name="T">The class you want to store in the service locator.</typeparam>
|
||||||
internal static class Service<T> where T : IServiceType
|
internal static class Service<T> where T : IServiceType
|
||||||
{
|
{
|
||||||
// ReSharper disable once StaticMemberInGenericType
|
private static TaskCompletionSource<T> instanceTcs = new();
|
||||||
private static readonly TaskCompletionSource<T> InstanceTcs = new();
|
|
||||||
|
|
||||||
static Service()
|
static Service()
|
||||||
{
|
{
|
||||||
|
|
@ -31,50 +30,28 @@ namespace Dalamud
|
||||||
ServiceManager.Log.Debug("Service<{0}>: Static ctor called", typeof(T).Name);
|
ServiceManager.Log.Debug("Service<{0}>: Static ctor called", typeof(T).Name);
|
||||||
|
|
||||||
if (exposeToPlugins)
|
if (exposeToPlugins)
|
||||||
Service<ServiceContainer>.Get().RegisterSingleton(InstanceTcs.Task);
|
Service<ServiceContainer>.Get().RegisterSingleton(instanceTcs.Task);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes the service.
|
/// Specifies how to handle the cases of failed services when calling <see cref="Service{T}.GetNullable"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns>The object.</returns>
|
public enum ExceptionPropagationMode
|
||||||
[UsedImplicitly]
|
|
||||||
public static Task<T> StartLoader()
|
|
||||||
{
|
{
|
||||||
var attr = typeof(T).GetCustomAttribute<ServiceManager.Service>(true)?.GetType();
|
/// <summary>
|
||||||
if (attr?.IsAssignableTo(typeof(ServiceManager.EarlyLoadedService)) != true)
|
/// Propagate all exceptions.
|
||||||
throw new InvalidOperationException($"{typeof(T).Name} is not an EarlyLoadedService");
|
/// </summary>
|
||||||
|
PropagateAll,
|
||||||
|
|
||||||
return Task.Run(Timings.AttachTimingHandle(async () =>
|
/// <summary>
|
||||||
{
|
/// Propagate all exceptions, except for <see cref="UnloadedException"/>.
|
||||||
ServiceManager.Log.Debug("Service<{0}>: Begin construction", typeof(T).Name);
|
/// </summary>
|
||||||
try
|
PropagateNonUnloaded,
|
||||||
{
|
|
||||||
var instance = await ConstructObject();
|
|
||||||
InstanceTcs.SetResult(instance);
|
|
||||||
|
|
||||||
foreach (var method in typeof(T).GetMethods(
|
/// <summary>
|
||||||
BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic))
|
/// Treat all exceptions as null.
|
||||||
{
|
/// </summary>
|
||||||
if (method.GetCustomAttribute<ServiceManager.CallWhenServicesReady>(true) == null)
|
None,
|
||||||
continue;
|
|
||||||
|
|
||||||
ServiceManager.Log.Debug("Service<{0}>: Calling {1}", typeof(T).Name, method.Name);
|
|
||||||
var args = await Task.WhenAll(method.GetParameters().Select(
|
|
||||||
x => ResolveServiceFromTypeAsync(x.ParameterType)));
|
|
||||||
method.Invoke(instance, args);
|
|
||||||
}
|
|
||||||
|
|
||||||
ServiceManager.Log.Debug("Service<{0}>: Construction complete", typeof(T).Name);
|
|
||||||
return instance;
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
ServiceManager.Log.Error(e, "Service<{0}>: Construction failure", typeof(T).Name);
|
|
||||||
InstanceTcs.SetException(e);
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -83,7 +60,7 @@ namespace Dalamud
|
||||||
/// <param name="obj">Object to set.</param>
|
/// <param name="obj">Object to set.</param>
|
||||||
public static void Provide(T obj)
|
public static void Provide(T obj)
|
||||||
{
|
{
|
||||||
InstanceTcs.SetResult(obj);
|
instanceTcs.SetResult(obj);
|
||||||
ServiceManager.Log.Debug("Service<{0}>: Provided", typeof(T).Name);
|
ServiceManager.Log.Debug("Service<{0}>: Provided", typeof(T).Name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -94,7 +71,7 @@ namespace Dalamud
|
||||||
public static void ProvideException(Exception exception)
|
public static void ProvideException(Exception exception)
|
||||||
{
|
{
|
||||||
ServiceManager.Log.Error(exception, "Service<{0}>: Error", typeof(T).Name);
|
ServiceManager.Log.Error(exception, "Service<{0}>: Error", typeof(T).Name);
|
||||||
InstanceTcs.SetException(exception);
|
instanceTcs.SetException(exception);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -103,9 +80,9 @@ namespace Dalamud
|
||||||
/// <returns>The object.</returns>
|
/// <returns>The object.</returns>
|
||||||
public static T Get()
|
public static T Get()
|
||||||
{
|
{
|
||||||
if (!InstanceTcs.Task.IsCompleted)
|
if (!instanceTcs.Task.IsCompleted)
|
||||||
InstanceTcs.Task.Wait();
|
instanceTcs.Task.Wait();
|
||||||
return InstanceTcs.Task.Result;
|
return instanceTcs.Task.Result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -113,13 +90,27 @@ namespace Dalamud
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns>The object.</returns>
|
/// <returns>The object.</returns>
|
||||||
[UsedImplicitly]
|
[UsedImplicitly]
|
||||||
public static Task<T> GetAsync() => InstanceTcs.Task;
|
public static Task<T> GetAsync() => instanceTcs.Task;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Attempt to pull the instance out of the service locator.
|
/// Attempt to pull the instance out of the service locator.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <param name="propagateException">Specifies which exceptions to propagate.</param>
|
||||||
/// <returns>The object if registered, null otherwise.</returns>
|
/// <returns>The object if registered, null otherwise.</returns>
|
||||||
public static T? GetNullable() => InstanceTcs.Task.IsCompleted ? InstanceTcs.Task.Result : default;
|
public static T? GetNullable(ExceptionPropagationMode propagateException = ExceptionPropagationMode.PropagateNonUnloaded)
|
||||||
|
{
|
||||||
|
if (instanceTcs.Task.IsCompletedSuccessfully)
|
||||||
|
return instanceTcs.Task.Result;
|
||||||
|
if (instanceTcs.Task.IsFaulted && propagateException != ExceptionPropagationMode.None)
|
||||||
|
{
|
||||||
|
if (propagateException == ExceptionPropagationMode.PropagateNonUnloaded
|
||||||
|
&& instanceTcs.Task.Exception!.InnerExceptions.FirstOrDefault() is UnloadedException)
|
||||||
|
return default;
|
||||||
|
throw instanceTcs.Task.Exception!;
|
||||||
|
}
|
||||||
|
|
||||||
|
return default;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets an enumerable containing Service<T>s that are required for this Service to initialize without blocking.
|
/// Gets an enumerable containing Service<T>s that are required for this Service to initialize without blocking.
|
||||||
|
|
@ -142,6 +133,77 @@ namespace Dalamud
|
||||||
.ToList();
|
.ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[UsedImplicitly]
|
||||||
|
private static Task<T> StartLoader()
|
||||||
|
{
|
||||||
|
if (instanceTcs.Task.IsCompleted)
|
||||||
|
throw new InvalidOperationException($"{typeof(T).Name} is already loaded or disposed.");
|
||||||
|
|
||||||
|
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");
|
||||||
|
|
||||||
|
return Task.Run(Timings.AttachTimingHandle(async () =>
|
||||||
|
{
|
||||||
|
ServiceManager.Log.Debug("Service<{0}>: Begin construction", typeof(T).Name);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var instance = await ConstructObject();
|
||||||
|
instanceTcs.SetResult(instance);
|
||||||
|
|
||||||
|
foreach (var method in typeof(T).GetMethods(
|
||||||
|
BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic))
|
||||||
|
{
|
||||||
|
if (method.GetCustomAttribute<ServiceManager.CallWhenServicesReady>(true) == null)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
ServiceManager.Log.Debug("Service<{0}>: Calling {1}", typeof(T).Name, method.Name);
|
||||||
|
var args = await Task.WhenAll(method.GetParameters().Select(
|
||||||
|
x => ResolveServiceFromTypeAsync(x.ParameterType)));
|
||||||
|
method.Invoke(instance, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
ServiceManager.Log.Debug("Service<{0}>: Construction complete", typeof(T).Name);
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
ServiceManager.Log.Error(e, "Service<{0}>: Construction failure", typeof(T).Name);
|
||||||
|
instanceTcs.SetException(e);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
[UsedImplicitly]
|
||||||
|
private static void Unset()
|
||||||
|
{
|
||||||
|
if (!instanceTcs.Task.IsCompletedSuccessfully)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var instance = instanceTcs.Task.Result;
|
||||||
|
if (instance is IDisposable disposable)
|
||||||
|
{
|
||||||
|
ServiceManager.Log.Debug("Service<{0}>: Disposing", typeof(T).Name);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
disposable.Dispose();
|
||||||
|
ServiceManager.Log.Debug("Service<{0}>: Disposed", typeof(T).Name);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
ServiceManager.Log.Warning(e, "Service<{0}>: Dispose failure", typeof(T).Name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ServiceManager.Log.Debug("Service<{0}>: Unset", typeof(T).Name);
|
||||||
|
}
|
||||||
|
|
||||||
|
instanceTcs = new TaskCompletionSource<T>();
|
||||||
|
instanceTcs.SetException(new UnloadedException());
|
||||||
|
}
|
||||||
|
|
||||||
private static async Task<object?> ResolveServiceFromTypeAsync(Type type)
|
private static async Task<object?> ResolveServiceFromTypeAsync(Type type)
|
||||||
{
|
{
|
||||||
var task = (Task)typeof(Service<>)
|
var task = (Task)typeof(Service<>)
|
||||||
|
|
@ -180,5 +242,19 @@ namespace Dalamud
|
||||||
return (T)ctor.Invoke(args)!;
|
return (T)ctor.Invoke(args)!;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Exception thrown when service is attempted to be retrieved when it's unloaded.
|
||||||
|
/// </summary>
|
||||||
|
public class UnloadedException : InvalidOperationException
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="UnloadedException"/> class.
|
||||||
|
/// </summary>
|
||||||
|
public UnloadedException()
|
||||||
|
: base("Service is unloaded.")
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ using Dalamud.Game;
|
||||||
using Dalamud.Game.ClientState.Objects.Types;
|
using Dalamud.Game.ClientState.Objects.Types;
|
||||||
using Dalamud.Interface;
|
using Dalamud.Interface;
|
||||||
using Dalamud.Interface.Colors;
|
using Dalamud.Interface.Colors;
|
||||||
|
using Dalamud.Logging.Internal;
|
||||||
using ImGuiNET;
|
using ImGuiNET;
|
||||||
using Microsoft.Win32;
|
using Microsoft.Win32;
|
||||||
using Serilog;
|
using Serilog;
|
||||||
|
|
@ -536,6 +537,31 @@ namespace Dalamud.Utility
|
||||||
obj.Dispose();
|
obj.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Dispose this object.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="obj">The object to dispose.</param>
|
||||||
|
/// <param name="logMessage">Log message to print, if specified and an error occurs.</param>
|
||||||
|
/// <param name="moduleLog">Module logger, if any.</param>
|
||||||
|
/// <typeparam name="T">The type of object to dispose.</typeparam>
|
||||||
|
internal static void ExplicitDisposeIgnoreExceptions<T>(this T obj, string? logMessage = null, ModuleLog? moduleLog = null) where T : IDisposable
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
obj.Dispose();
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
if (logMessage == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (moduleLog != null)
|
||||||
|
moduleLog.Error(e, logMessage);
|
||||||
|
else
|
||||||
|
Log.Error(e, logMessage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static unsafe void ShowValue(ulong addr, IEnumerable<string> path, Type type, object value)
|
private static unsafe void ShowValue(ulong addr, IEnumerable<string> path, Type type, object value)
|
||||||
{
|
{
|
||||||
if (type.IsPointer)
|
if (type.IsPointer)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue