mirror of
https://github.com/goatcorp/Dalamud.git
synced 2025-12-12 18:27:23 +01:00
Merge branch 'master' into v9
This commit is contained in:
commit
54f3fe7a2f
56 changed files with 3323 additions and 2250 deletions
|
|
@ -115,18 +115,18 @@ namespace Dalamud.Injector
|
|||
}
|
||||
}
|
||||
|
||||
private static string GetLogPath(string fileName, string logName)
|
||||
private static string GetLogPath(string? baseDirectory, string fileName, string? logName)
|
||||
{
|
||||
var baseDirectory = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
|
||||
baseDirectory ??= Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
|
||||
baseDirectory ??= Environment.CurrentDirectory;
|
||||
fileName = !string.IsNullOrEmpty(logName) ? $"{fileName}-{logName}.log" : $"{fileName}.log";
|
||||
|
||||
#if DEBUG
|
||||
var logPath = Path.Combine(baseDirectory, fileName);
|
||||
#else
|
||||
var logPath = Path.Combine(baseDirectory, "..", "..", "..", fileName);
|
||||
#endif
|
||||
// TODO(api9): remove
|
||||
var previousLogPath = Path.Combine(baseDirectory, "..", "..", "..", fileName);
|
||||
if (File.Exists(previousLogPath))
|
||||
File.Delete(previousLogPath);
|
||||
|
||||
return logPath;
|
||||
return Path.Combine(baseDirectory, fileName);
|
||||
}
|
||||
|
||||
private static void Init(List<string> args)
|
||||
|
|
@ -168,6 +168,7 @@ namespace Dalamud.Injector
|
|||
Log.Error("A fatal error has occurred: {Exception}", eventArgs.ExceptionObject.ToString());
|
||||
}
|
||||
|
||||
Log.CloseAndFlush();
|
||||
Environment.Exit(-1);
|
||||
};
|
||||
}
|
||||
|
|
@ -180,15 +181,18 @@ namespace Dalamud.Injector
|
|||
};
|
||||
|
||||
var logName = args.FirstOrDefault(x => x.StartsWith("--logname="))?[10..];
|
||||
var logPath = GetLogPath("dalamud.injector", logName);
|
||||
var logBaseDir = args.FirstOrDefault(x => x.StartsWith("--logpath="))?[10..];
|
||||
var logPath = GetLogPath(logBaseDir, "dalamud.injector", logName);
|
||||
|
||||
CullLogFile(logPath, 1 * 1024 * 1024);
|
||||
|
||||
Log.Logger = new LoggerConfiguration()
|
||||
.WriteTo.Console(standardErrorFromLevel: LogEventLevel.Verbose)
|
||||
.WriteTo.Async(a => a.File(logPath))
|
||||
.MinimumLevel.ControlledBy(levelSwitch)
|
||||
.CreateLogger();
|
||||
.WriteTo.File(logPath, fileSizeLimitBytes: null)
|
||||
.MinimumLevel.ControlledBy(levelSwitch)
|
||||
.CreateLogger();
|
||||
|
||||
Log.Information(new string('-', 80));
|
||||
Log.Information("Dalamud.Injector, (c) 2023 XIVLauncher Contributors");
|
||||
}
|
||||
|
||||
private static void CullLogFile(string logPath, int cullingFileSize)
|
||||
|
|
@ -199,9 +203,10 @@ namespace Dalamud.Injector
|
|||
|
||||
var logFile = new FileInfo(logPath);
|
||||
|
||||
// Leave it to serilog
|
||||
if (!logFile.Exists)
|
||||
{
|
||||
logFile.Create();
|
||||
return;
|
||||
}
|
||||
|
||||
if (logFile.Length <= cullingFileSize)
|
||||
|
|
@ -256,6 +261,7 @@ namespace Dalamud.Injector
|
|||
var assetDirectory = startInfo.AssetDirectory;
|
||||
var delayInitializeMs = startInfo.DelayInitializeMs;
|
||||
var logName = startInfo.LogName;
|
||||
var logPath = startInfo.LogPath;
|
||||
var languageStr = startInfo.Language.ToString().ToLowerInvariant();
|
||||
var troubleshootingData = "{\"empty\": true, \"description\": \"No troubleshooting data supplied.\"}";
|
||||
|
||||
|
|
@ -293,6 +299,10 @@ namespace Dalamud.Injector
|
|||
{
|
||||
logName = args[i][key.Length..];
|
||||
}
|
||||
else if (args[i].StartsWith(key = "--logpath="))
|
||||
{
|
||||
logPath = args[i][key.Length..];
|
||||
}
|
||||
else
|
||||
{
|
||||
continue;
|
||||
|
|
@ -357,11 +367,19 @@ namespace Dalamud.Injector
|
|||
startInfo.GameVersion = null;
|
||||
startInfo.TroubleshootingPackData = troubleshootingData;
|
||||
startInfo.LogName = logName;
|
||||
startInfo.LogPath = logPath;
|
||||
|
||||
// TODO: XL should set --logpath to its roaming path. We are only doing this here until that's rolled out.
|
||||
#if DEBUG
|
||||
startInfo.LogPath ??= startInfo.WorkingDirectory;
|
||||
#else
|
||||
startInfo.LogPath ??= xivlauncherDir;
|
||||
#endif
|
||||
|
||||
// Set boot defaults
|
||||
startInfo.BootShowConsole = args.Contains("--console");
|
||||
startInfo.BootEnableEtw = args.Contains("--etw");
|
||||
startInfo.BootLogPath = GetLogPath("dalamud.boot", startInfo.LogName);
|
||||
startInfo.BootLogPath = GetLogPath(startInfo.LogPath, "dalamud.boot", startInfo.LogName);
|
||||
startInfo.BootEnabledGameFixes = new List<string> { "prevent_devicechange_crashes", "disable_game_openprocess_access_check", "redirect_openprocess", "backup_userdata_save", "clr_failfast_hijack", "prevent_icmphandle_crashes" };
|
||||
startInfo.BootDotnetOpenProcessHookMode = 0;
|
||||
startInfo.BootWaitMessageBox |= args.Contains("--msgbox1") ? 1 : 0;
|
||||
|
|
@ -418,6 +436,7 @@ namespace Dalamud.Injector
|
|||
Console.WriteLine("Enable VEH:\t[--veh], [--veh-full]");
|
||||
Console.WriteLine("Show messagebox:\t[--msgbox1], [--msgbox2], [--msgbox3]");
|
||||
Console.WriteLine("No plugins:\t[--no-plugin] [--no-3rd-plugin]");
|
||||
Console.WriteLine("Logging:\t[--logname=<logfile suffix>] [--logpath=<log base directory>]");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -520,6 +539,7 @@ namespace Dalamud.Injector
|
|||
foreach (var process in processes)
|
||||
Inject(process, AdjustStartInfo(dalamudStartInfo, process.MainModule.FileName), tryFixAcl);
|
||||
|
||||
Log.CloseAndFlush();
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
@ -808,6 +828,7 @@ namespace Dalamud.Injector
|
|||
|
||||
Console.WriteLine($"{{\"pid\": {process.Id}, \"handle\": {processHandleForOwner}}}");
|
||||
|
||||
Log.CloseAndFlush();
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ public record DalamudStartInfo : IServiceType
|
|||
{
|
||||
this.WorkingDirectory = other.WorkingDirectory;
|
||||
this.ConfigurationPath = other.ConfigurationPath;
|
||||
this.LogPath = other.LogPath;
|
||||
this.LogName = other.LogName;
|
||||
this.PluginDirectory = other.PluginDirectory;
|
||||
this.AssetDirectory = other.AssetDirectory;
|
||||
|
|
@ -61,6 +62,11 @@ public record DalamudStartInfo : IServiceType
|
|||
/// </summary>
|
||||
public string? ConfigurationPath { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the path of the log files.
|
||||
/// </summary>
|
||||
public string? LogPath { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the name of the log file.
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ namespace Dalamud.Data;
|
|||
public sealed class DataManager : IDisposable, IServiceType
|
||||
{
|
||||
private const string IconFileFormat = "ui/icon/{0:D3}000/{1}{2:D6}.tex";
|
||||
private const string HighResolutionIconFileFormat = "ui/icon/{0:D3}000/{1}{2:D6}_hr1.tex";
|
||||
|
||||
private readonly Thread luminaResourceThread;
|
||||
private readonly CancellationTokenSource luminaCancellationTokenSource;
|
||||
|
|
@ -169,10 +170,8 @@ public sealed class DataManager : IDisposable, IServiceType
|
|||
/// </summary>
|
||||
/// <typeparam name="T">The excel sheet type to get.</typeparam>
|
||||
/// <returns>The <see cref="ExcelSheet{T}"/>, giving access to game rows.</returns>
|
||||
public ExcelSheet<T>? GetExcelSheet<T>() where T : ExcelRow
|
||||
{
|
||||
return this.Excel.GetSheet<T>();
|
||||
}
|
||||
public ExcelSheet<T>? GetExcelSheet<T>() where T : ExcelRow
|
||||
=> this.Excel.GetSheet<T>();
|
||||
|
||||
/// <summary>
|
||||
/// Get an <see cref="ExcelSheet{T}"/> with the given Excel sheet row type with a specified language.
|
||||
|
|
@ -180,20 +179,16 @@ public sealed class DataManager : IDisposable, IServiceType
|
|||
/// <param name="language">Language of the sheet to get.</param>
|
||||
/// <typeparam name="T">The excel sheet type to get.</typeparam>
|
||||
/// <returns>The <see cref="ExcelSheet{T}"/>, giving access to game rows.</returns>
|
||||
public ExcelSheet<T>? GetExcelSheet<T>(ClientLanguage language) where T : ExcelRow
|
||||
{
|
||||
return this.Excel.GetSheet<T>(language.ToLumina());
|
||||
}
|
||||
public ExcelSheet<T>? GetExcelSheet<T>(ClientLanguage language) where T : ExcelRow
|
||||
=> this.Excel.GetSheet<T>(language.ToLumina());
|
||||
|
||||
/// <summary>
|
||||
/// Get a <see cref="FileResource"/> with the given path.
|
||||
/// </summary>
|
||||
/// <param name="path">The path inside of the game files.</param>
|
||||
/// <returns>The <see cref="FileResource"/> of the file.</returns>
|
||||
public FileResource? GetFile(string path)
|
||||
{
|
||||
return this.GetFile<FileResource>(path);
|
||||
}
|
||||
public FileResource? GetFile(string path)
|
||||
=> this.GetFile<FileResource>(path);
|
||||
|
||||
/// <summary>
|
||||
/// Get a <see cref="FileResource"/> with the given path, of the given type.
|
||||
|
|
@ -214,21 +209,27 @@ public sealed class DataManager : IDisposable, IServiceType
|
|||
/// </summary>
|
||||
/// <param name="path">The path inside of the game files.</param>
|
||||
/// <returns>True if the file exists.</returns>
|
||||
public bool FileExists(string path)
|
||||
{
|
||||
return this.GameData.FileExists(path);
|
||||
}
|
||||
public bool FileExists(string path)
|
||||
=> this.GameData.FileExists(path);
|
||||
|
||||
/// <summary>
|
||||
/// Get a <see cref="TexFile"/> containing the icon with the given ID.
|
||||
/// </summary>
|
||||
/// <param name="iconId">The icon ID.</param>
|
||||
/// <returns>The <see cref="TexFile"/> containing the icon.</returns>
|
||||
public TexFile? GetIcon(uint iconId)
|
||||
{
|
||||
return this.GetIcon(this.Language, iconId);
|
||||
}
|
||||
/// todo: remove in api9 in favor of GetIcon(uint iconId, bool highResolution)
|
||||
public TexFile? GetIcon(uint iconId)
|
||||
=> this.GetIcon(this.Language, iconId, false);
|
||||
|
||||
/// <summary>
|
||||
/// Get a <see cref="TexFile"/> containing the icon with the given ID.
|
||||
/// </summary>
|
||||
/// <param name="iconId">The icon ID.</param>
|
||||
/// <param name="highResolution">Return high resolution version.</param>
|
||||
/// <returns>The <see cref="TexFile"/> containing the icon.</returns>
|
||||
public TexFile? GetIcon(uint iconId, bool highResolution)
|
||||
=> this.GetIcon(this.Language, iconId, highResolution);
|
||||
|
||||
/// <summary>
|
||||
/// Get a <see cref="TexFile"/> containing the icon with the given ID, of the given quality.
|
||||
/// </summary>
|
||||
|
|
@ -247,7 +248,18 @@ public sealed class DataManager : IDisposable, IServiceType
|
|||
/// <param name="iconLanguage">The requested language.</param>
|
||||
/// <param name="iconId">The icon ID.</param>
|
||||
/// <returns>The <see cref="TexFile"/> containing the icon.</returns>
|
||||
/// todo: remove in api9 in favor of GetIcon(ClientLanguage iconLanguage, uint iconId, bool highResolution)
|
||||
public TexFile? GetIcon(ClientLanguage iconLanguage, uint iconId)
|
||||
=> this.GetIcon(iconLanguage, iconId, false);
|
||||
|
||||
/// <summary>
|
||||
/// Get a <see cref="TexFile"/> containing the icon with the given ID, of the given language.
|
||||
/// </summary>
|
||||
/// <param name="iconLanguage">The requested language.</param>
|
||||
/// <param name="iconId">The icon ID.</param>
|
||||
/// <param name="highResolution">Return high resolution version.</param>
|
||||
/// <returns>The <see cref="TexFile"/> containing the icon.</returns>
|
||||
public TexFile? GetIcon(ClientLanguage iconLanguage, uint iconId, bool highResolution)
|
||||
{
|
||||
var type = iconLanguage switch
|
||||
{
|
||||
|
|
@ -258,7 +270,7 @@ public sealed class DataManager : IDisposable, IServiceType
|
|||
_ => throw new ArgumentOutOfRangeException(nameof(iconLanguage), $"Unknown Language: {iconLanguage}"),
|
||||
};
|
||||
|
||||
return this.GetIcon(type, iconId);
|
||||
return this.GetIcon(type, iconId, highResolution);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -267,20 +279,33 @@ public sealed class DataManager : IDisposable, IServiceType
|
|||
/// <param name="type">The type of the icon (e.g. 'hq' to get the HQ variant of an item icon).</param>
|
||||
/// <param name="iconId">The icon ID.</param>
|
||||
/// <returns>The <see cref="TexFile"/> containing the icon.</returns>
|
||||
/// todo: remove in api9 in favor of GetIcon(string? type, uint iconId, bool highResolution)
|
||||
public TexFile? GetIcon(string? type, uint iconId)
|
||||
=> this.GetIcon(type, iconId, false);
|
||||
|
||||
/// <summary>
|
||||
/// Get a <see cref="TexFile"/> containing the icon with the given ID, of the given type.
|
||||
/// </summary>
|
||||
/// <param name="type">The type of the icon (e.g. 'hq' to get the HQ variant of an item icon).</param>
|
||||
/// <param name="iconId">The icon ID.</param>
|
||||
/// <param name="highResolution">Return high resolution version.</param>
|
||||
/// <returns>The <see cref="TexFile"/> containing the icon.</returns>
|
||||
public TexFile? GetIcon(string? type, uint iconId, bool highResolution)
|
||||
{
|
||||
var format = highResolution ? HighResolutionIconFileFormat : IconFileFormat;
|
||||
|
||||
type ??= string.Empty;
|
||||
if (type.Length > 0 && !type.EndsWith("/"))
|
||||
type += "/";
|
||||
|
||||
var filePath = string.Format(IconFileFormat, iconId / 1000, type, iconId);
|
||||
var filePath = string.Format(format, iconId / 1000, type, iconId);
|
||||
var file = this.GetFile<TexFile>(filePath);
|
||||
|
||||
if (type == string.Empty || file != default)
|
||||
return file;
|
||||
|
||||
// Couldn't get specific type, try for generic version.
|
||||
filePath = string.Format(IconFileFormat, iconId / 1000, string.Empty, iconId);
|
||||
filePath = string.Format(format, iconId / 1000, string.Empty, iconId);
|
||||
file = this.GetFile<TexFile>(filePath);
|
||||
return file;
|
||||
}
|
||||
|
|
@ -299,9 +324,7 @@ public sealed class DataManager : IDisposable, IServiceType
|
|||
/// <param name="tex">The Lumina <see cref="TexFile"/>.</param>
|
||||
/// <returns>A <see cref="TextureWrap"/> that can be used to draw the texture.</returns>
|
||||
public TextureWrap? GetImGuiTexture(TexFile? tex)
|
||||
{
|
||||
return tex == null ? null : Service<InterfaceManager>.Get().LoadImageRaw(tex.GetRgbaImageData(), tex.Header.Width, tex.Header.Height, 4);
|
||||
}
|
||||
=> tex == null ? null : Service<InterfaceManager>.Get().LoadImageRaw(tex.GetRgbaImageData(), tex.Header.Width, tex.Header.Height, 4);
|
||||
|
||||
/// <summary>
|
||||
/// Get the passed texture path as a drawable ImGui TextureWrap.
|
||||
|
|
@ -316,8 +339,18 @@ public sealed class DataManager : IDisposable, IServiceType
|
|||
/// </summary>
|
||||
/// <param name="iconId">The icon ID.</param>
|
||||
/// <returns>The <see cref="TextureWrap"/> containing the icon.</returns>
|
||||
public TextureWrap? GetImGuiTextureIcon(uint iconId)
|
||||
=> this.GetImGuiTexture(this.GetIcon(iconId));
|
||||
/// todo: remove in api9 in favor of GetImGuiTextureIcon(uint iconId, bool highResolution)
|
||||
public TextureWrap? GetImGuiTextureIcon(uint iconId)
|
||||
=> this.GetImGuiTexture(this.GetIcon(iconId, false));
|
||||
|
||||
/// <summary>
|
||||
/// Get a <see cref="TextureWrap"/> containing the icon with the given ID.
|
||||
/// </summary>
|
||||
/// <param name="iconId">The icon ID.</param>
|
||||
/// <param name="highResolution">Return the high resolution version.</param>
|
||||
/// <returns>The <see cref="TextureWrap"/> containing the icon.</returns>
|
||||
public TextureWrap? GetImGuiTextureIcon(uint iconId, bool highResolution)
|
||||
=> this.GetImGuiTexture(this.GetIcon(iconId, highResolution));
|
||||
|
||||
/// <summary>
|
||||
/// Get a <see cref="TextureWrap"/> containing the icon with the given ID, of the given quality.
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ using System.Threading.Tasks;
|
|||
|
||||
using Dalamud.Configuration.Internal;
|
||||
using Dalamud.Logging.Internal;
|
||||
using Dalamud.Logging.Retention;
|
||||
using Dalamud.Plugin.Internal;
|
||||
using Dalamud.Support;
|
||||
using Dalamud.Utility;
|
||||
|
|
@ -87,59 +88,32 @@ public sealed class EntryPoint
|
|||
{
|
||||
var logFileName = logName.IsNullOrEmpty() ? "dalamud" : $"dalamud-{logName}";
|
||||
|
||||
#if DEBUG
|
||||
var logPath = Path.Combine(baseDirectory, $"{logFileName}.log");
|
||||
var oldPath = Path.Combine(baseDirectory, $"{logFileName}.old.log");
|
||||
var oldPathOld = Path.Combine(baseDirectory, $"{logFileName}.log.old");
|
||||
#else
|
||||
var logPath = Path.Combine(baseDirectory, "..", "..", "..", $"{logFileName}.log");
|
||||
var oldPath = Path.Combine(baseDirectory, "..", "..", "..", $"{logFileName}.old.log");
|
||||
var oldPathOld = Path.Combine(baseDirectory, "..", "..", "..", $"{logFileName}.log.old");
|
||||
#endif
|
||||
Log.CloseAndFlush();
|
||||
|
||||
#if DEBUG
|
||||
var oldFileOld = new FileInfo(oldPathOld);
|
||||
if (oldFileOld.Exists)
|
||||
{
|
||||
var oldFile = new FileInfo(oldPath);
|
||||
if (oldFile.Exists)
|
||||
oldFileOld.Delete();
|
||||
else
|
||||
oldFileOld.MoveTo(oldPath);
|
||||
}
|
||||
var logPath = new FileInfo(Path.Combine(baseDirectory, $"{logFileName}.log"));
|
||||
var oldPath = new FileInfo(Path.Combine(baseDirectory, $"{logFileName}.old.log"));
|
||||
|
||||
CullLogFile(logPath, 1 * 1024 * 1024, oldPath, 10 * 1024 * 1024);
|
||||
Log.CloseAndFlush();
|
||||
|
||||
RetentionBehaviour behaviour;
|
||||
#if DEBUG
|
||||
behaviour = new DebugRetentionBehaviour();
|
||||
#else
|
||||
try
|
||||
{
|
||||
if (File.Exists(logPath))
|
||||
File.Delete(logPath);
|
||||
|
||||
if (File.Exists(oldPath))
|
||||
File.Delete(oldPath);
|
||||
|
||||
if (File.Exists(oldPathOld))
|
||||
File.Delete(oldPathOld);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
behaviour = new ReleaseRetentionBehaviour();
|
||||
#endif
|
||||
|
||||
behaviour.Apply(logPath, oldPath);
|
||||
|
||||
var config = new LoggerConfiguration()
|
||||
.WriteTo.Sink(SerilogEventSink.Instance)
|
||||
.MinimumLevel.ControlledBy(LogLevelSwitch);
|
||||
|
||||
if (logSynchronously)
|
||||
{
|
||||
config = config.WriteTo.File(logPath, fileSizeLimitBytes: null);
|
||||
config = config.WriteTo.File(logPath.FullName, fileSizeLimitBytes: null);
|
||||
}
|
||||
else
|
||||
{
|
||||
config = config.WriteTo.Async(a => a.File(
|
||||
logPath,
|
||||
logPath.FullName,
|
||||
fileSizeLimitBytes: null,
|
||||
buffered: false,
|
||||
flushToDiskInterval: TimeSpan.FromSeconds(1)));
|
||||
|
|
@ -159,7 +133,7 @@ public sealed class EntryPoint
|
|||
private static void RunThread(DalamudStartInfo info, IntPtr mainThreadContinueEvent)
|
||||
{
|
||||
// Setup logger
|
||||
InitLogging(info.WorkingDirectory!, info.BootShowConsole, true, info.LogName);
|
||||
InitLogging(info.LogPath!, info.BootShowConsole, true, info.LogName);
|
||||
SerilogEventSink.Instance.LogLine += SerilogOnLogLine;
|
||||
|
||||
// Load configuration first to get some early persistent state, like log level
|
||||
|
|
@ -167,7 +141,7 @@ public sealed class EntryPoint
|
|||
|
||||
// Set the appropriate logging level from the configuration
|
||||
if (!configuration.LogSynchronously)
|
||||
InitLogging(info.WorkingDirectory!, info.BootShowConsole, configuration.LogSynchronously, info.LogName);
|
||||
InitLogging(info.LogPath!, info.BootShowConsole, configuration.LogSynchronously, info.LogName);
|
||||
LogLevelSwitch.MinimumLevel = configuration.LogLevel;
|
||||
|
||||
// Log any unhandled exception.
|
||||
|
|
@ -265,86 +239,6 @@ public sealed class EntryPoint
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Trim existing log file to a specified length, and optionally move the excess data to another file.
|
||||
/// </summary>
|
||||
/// <param name="logPath">Target log file to trim.</param>
|
||||
/// <param name="logMaxSize">Maximum size of target log file.</param>
|
||||
/// <param name="oldPath">.old file to move excess data to.</param>
|
||||
/// <param name="oldMaxSize">Maximum size of .old file.</param>
|
||||
private static void CullLogFile(string logPath, int logMaxSize, string oldPath, int oldMaxSize)
|
||||
{
|
||||
var logFile = new FileInfo(logPath);
|
||||
var oldFile = new FileInfo(oldPath);
|
||||
var targetFiles = new[]
|
||||
{
|
||||
(logFile, logMaxSize),
|
||||
(oldFile, oldMaxSize),
|
||||
};
|
||||
var buffer = new byte[4096];
|
||||
|
||||
try
|
||||
{
|
||||
if (!logFile.Exists)
|
||||
logFile.Create().Close();
|
||||
|
||||
// 1. Move excess data from logFile to oldFile
|
||||
if (logFile.Length > logMaxSize)
|
||||
{
|
||||
using var reader = logFile.Open(FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
|
||||
using var writer = oldFile.Open(FileMode.Append, FileAccess.Write, FileShare.ReadWrite);
|
||||
|
||||
var amountToMove = (int)Math.Min(logFile.Length - logMaxSize, oldMaxSize);
|
||||
reader.Seek(-(logMaxSize + amountToMove), SeekOrigin.End);
|
||||
|
||||
for (var i = 0; i < amountToMove; i += buffer.Length)
|
||||
writer.Write(buffer, 0, reader.Read(buffer, 0, Math.Min(buffer.Length, amountToMove - i)));
|
||||
}
|
||||
|
||||
// 2. Cull each of .log and .old files
|
||||
foreach (var (file, maxSize) in targetFiles)
|
||||
{
|
||||
if (!file.Exists || file.Length <= maxSize)
|
||||
continue;
|
||||
|
||||
using var reader = file.Open(FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
|
||||
using var writer = file.Open(FileMode.Open, FileAccess.Write, FileShare.ReadWrite);
|
||||
|
||||
reader.Seek(file.Length - maxSize, SeekOrigin.Begin);
|
||||
for (int read; (read = reader.Read(buffer, 0, buffer.Length)) > 0;)
|
||||
writer.Write(buffer, 0, read);
|
||||
|
||||
writer.SetLength(maxSize);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (ex is IOException)
|
||||
{
|
||||
foreach (var (file, _) in targetFiles)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (file.Exists)
|
||||
file.Delete();
|
||||
}
|
||||
catch (Exception ex2)
|
||||
{
|
||||
Log.Error(ex2, "Failed to delete {file}", file.FullName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Log.Error(ex, "Log cull failed");
|
||||
|
||||
/*
|
||||
var caption = "XIVLauncher Error";
|
||||
var message = $"Log cull threw an exception: {ex.Message}\n{ex.StackTrace ?? string.Empty}";
|
||||
_ = MessageBoxW(IntPtr.Zero, message, caption, MessageBoxType.IconError | MessageBoxType.Ok);
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
||||
private static void OnUnhandledException(object sender, UnhandledExceptionEventArgs args)
|
||||
{
|
||||
switch (args.ExceptionObject)
|
||||
|
|
|
|||
|
|
@ -292,7 +292,7 @@ public class ChatHandlers : IServiceType
|
|||
if (chatGui == null || pluginManager == null || notifications == null)
|
||||
return;
|
||||
|
||||
if (!pluginManager.ReposReady || pluginManager.InstalledPlugins.Count == 0 || pluginManager.AvailablePlugins.Count == 0)
|
||||
if (!pluginManager.ReposReady || !pluginManager.InstalledPlugins.Any() || !pluginManager.AvailablePlugins.Any())
|
||||
{
|
||||
// Plugins aren't ready yet.
|
||||
// TODO: We should retry. This sucks, because it means we won't ever get here again until another notice.
|
||||
|
|
@ -311,7 +311,7 @@ public class ChatHandlers : IServiceType
|
|||
return;
|
||||
}
|
||||
|
||||
var updatedPlugins = task.Result;
|
||||
var updatedPlugins = task.Result.ToList();
|
||||
if (updatedPlugins.Any())
|
||||
{
|
||||
if (this.configuration.AutoUpdatePlugins)
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ using Dalamud.Interface.Animation.EasingFunctions;
|
|||
using Dalamud.Interface.Colors;
|
||||
using Dalamud.Interface.Internal.ManagedAsserts;
|
||||
using Dalamud.Interface.Internal.Windows;
|
||||
using Dalamud.Interface.Internal.Windows.Data;
|
||||
using Dalamud.Interface.Internal.Windows.PluginInstaller;
|
||||
using Dalamud.Interface.Internal.Windows.SelfTest;
|
||||
using Dalamud.Interface.Internal.Windows.Settings;
|
||||
|
|
@ -642,7 +643,7 @@ internal class DalamudInterface : IDisposable, IServiceType
|
|||
configuration.QueueSave();
|
||||
|
||||
EntryPoint.InitLogging(
|
||||
startInfo.WorkingDirectory!,
|
||||
startInfo.LogPath!,
|
||||
startInfo.BootShowConsole,
|
||||
configuration.LogSynchronously,
|
||||
startInfo.LogName);
|
||||
|
|
@ -900,7 +901,7 @@ internal class DalamudInterface : IDisposable, IServiceType
|
|||
|
||||
ImGui.Separator();
|
||||
ImGui.MenuItem("API Level:" + PluginManager.DalamudApiLevel, false);
|
||||
ImGui.MenuItem("Loaded plugins:" + pluginManager.InstalledPlugins.Count, false);
|
||||
ImGui.MenuItem("Loaded plugins:" + pluginManager.InstalledPlugins.Count(), false);
|
||||
ImGui.EndMenu();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -53,8 +53,6 @@ internal unsafe class UiDebug
|
|||
{
|
||||
}
|
||||
|
||||
private delegate AtkStage* GetAtkStageSingleton();
|
||||
|
||||
/// <summary>
|
||||
/// Renders this window.
|
||||
/// </summary>
|
||||
|
|
@ -165,7 +163,7 @@ internal unsafe class UiDebug
|
|||
private void PrintSimpleNode(AtkResNode* node, string treePrefix)
|
||||
{
|
||||
var popped = false;
|
||||
var isVisible = (node->Flags & 0x10) == 0x10;
|
||||
var isVisible = node->NodeFlags.HasFlag(NodeFlags.Visible);
|
||||
|
||||
if (isVisible)
|
||||
ImGui.PushStyleColor(ImGuiCol.Text, new Vector4(0, 255, 0, 255));
|
||||
|
|
@ -296,7 +294,7 @@ internal unsafe class UiDebug
|
|||
var compNode = (AtkComponentNode*)node;
|
||||
|
||||
var popped = false;
|
||||
var isVisible = (node->Flags & 0x10) == 0x10;
|
||||
var isVisible = node->NodeFlags.HasFlag(NodeFlags.Visible);
|
||||
|
||||
var componentInfo = compNode->Component->UldManager;
|
||||
|
||||
|
|
@ -396,7 +394,7 @@ internal unsafe class UiDebug
|
|||
ImGui.SameLine();
|
||||
if (ImGui.SmallButton($"T:Visible##{(ulong)node:X}"))
|
||||
{
|
||||
node->Flags ^= 0x10;
|
||||
node->NodeFlags ^= NodeFlags.Visible;
|
||||
}
|
||||
|
||||
ImGui.SameLine();
|
||||
|
|
@ -573,7 +571,7 @@ internal unsafe class UiDebug
|
|||
if (node == null) return false;
|
||||
while (node != null)
|
||||
{
|
||||
if ((node->Flags & (short)NodeFlags.Visible) != (short)NodeFlags.Visible) return false;
|
||||
if (!node->NodeFlags.HasFlag(NodeFlags.Visible)) return false;
|
||||
node = node->ParentNode;
|
||||
}
|
||||
|
||||
|
|
|
|||
158
Dalamud/Interface/Internal/Windows/Data/DataKindEnum.cs
Normal file
158
Dalamud/Interface/Internal/Windows/Data/DataKindEnum.cs
Normal file
|
|
@ -0,0 +1,158 @@
|
|||
// ReSharper disable InconsistentNaming // Naming is suppressed so we can replace '_' with ' '
|
||||
namespace Dalamud.Interface.Internal.Windows;
|
||||
|
||||
/// <summary>
|
||||
/// Enum representing a DataKind for the Data Window.
|
||||
/// </summary>
|
||||
internal enum DataKind
|
||||
{
|
||||
/// <summary>
|
||||
/// Server Opcode Display.
|
||||
/// </summary>
|
||||
Server_OpCode,
|
||||
|
||||
/// <summary>
|
||||
/// Address.
|
||||
/// </summary>
|
||||
Address,
|
||||
|
||||
/// <summary>
|
||||
/// Object Table.
|
||||
/// </summary>
|
||||
Object_Table,
|
||||
|
||||
/// <summary>
|
||||
/// Fate Table.
|
||||
/// </summary>
|
||||
Fate_Table,
|
||||
|
||||
/// <summary>
|
||||
/// SE Font Test.
|
||||
/// </summary>
|
||||
SE_Font_Test,
|
||||
|
||||
/// <summary>
|
||||
/// FontAwesome Test.
|
||||
/// </summary>
|
||||
FontAwesome_Test,
|
||||
|
||||
/// <summary>
|
||||
/// Party List.
|
||||
/// </summary>
|
||||
Party_List,
|
||||
|
||||
/// <summary>
|
||||
/// Buddy List.
|
||||
/// </summary>
|
||||
Buddy_List,
|
||||
|
||||
/// <summary>
|
||||
/// Plugin IPC Test.
|
||||
/// </summary>
|
||||
Plugin_IPC,
|
||||
|
||||
/// <summary>
|
||||
/// Player Condition.
|
||||
/// </summary>
|
||||
Condition,
|
||||
|
||||
/// <summary>
|
||||
/// Gauge.
|
||||
/// </summary>
|
||||
Gauge,
|
||||
|
||||
/// <summary>
|
||||
/// Command.
|
||||
/// </summary>
|
||||
Command,
|
||||
|
||||
/// <summary>
|
||||
/// Addon.
|
||||
/// </summary>
|
||||
Addon,
|
||||
|
||||
/// <summary>
|
||||
/// Addon Inspector.
|
||||
/// </summary>
|
||||
Addon_Inspector,
|
||||
|
||||
/// <summary>
|
||||
/// AtkArrayData Browser.
|
||||
/// </summary>
|
||||
AtkArrayData_Browser,
|
||||
|
||||
/// <summary>
|
||||
/// StartInfo.
|
||||
/// </summary>
|
||||
StartInfo,
|
||||
|
||||
/// <summary>
|
||||
/// Target.
|
||||
/// </summary>
|
||||
Target,
|
||||
|
||||
/// <summary>
|
||||
/// Toast.
|
||||
/// </summary>
|
||||
Toast,
|
||||
|
||||
/// <summary>
|
||||
/// Fly Text.
|
||||
/// </summary>
|
||||
FlyText,
|
||||
|
||||
/// <summary>
|
||||
/// ImGui.
|
||||
/// </summary>
|
||||
ImGui,
|
||||
|
||||
/// <summary>
|
||||
/// Tex.
|
||||
/// </summary>
|
||||
Tex,
|
||||
|
||||
/// <summary>
|
||||
/// KeyState.
|
||||
/// </summary>
|
||||
KeyState,
|
||||
|
||||
/// <summary>
|
||||
/// GamePad.
|
||||
/// </summary>
|
||||
Gamepad,
|
||||
|
||||
/// <summary>
|
||||
/// Configuration.
|
||||
/// </summary>
|
||||
Configuration,
|
||||
|
||||
/// <summary>
|
||||
/// Task Scheduler.
|
||||
/// </summary>
|
||||
TaskSched,
|
||||
|
||||
/// <summary>
|
||||
/// Hook.
|
||||
/// </summary>
|
||||
Hook,
|
||||
|
||||
/// <summary>
|
||||
/// Aetherytes.
|
||||
/// </summary>
|
||||
Aetherytes,
|
||||
|
||||
/// <summary>
|
||||
/// DTR Bar.
|
||||
/// </summary>
|
||||
Dtr_Bar,
|
||||
|
||||
/// <summary>
|
||||
/// UIColor.
|
||||
/// </summary>
|
||||
UIColor,
|
||||
|
||||
/// <summary>
|
||||
/// Data Share.
|
||||
/// </summary>
|
||||
DataShare,
|
||||
}
|
||||
191
Dalamud/Interface/Internal/Windows/Data/DataWindow.cs
Normal file
191
Dalamud/Interface/Internal/Windows/Data/DataWindow.cs
Normal file
|
|
@ -0,0 +1,191 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
|
||||
using Dalamud.Game.Gui;
|
||||
using Dalamud.Interface.Components;
|
||||
using Dalamud.Interface.Windowing;
|
||||
using ImGuiNET;
|
||||
using Serilog;
|
||||
|
||||
namespace Dalamud.Interface.Internal.Windows.Data;
|
||||
|
||||
/// <summary>
|
||||
/// Class responsible for drawing the data/debug window.
|
||||
/// </summary>
|
||||
internal class DataWindow : Window
|
||||
{
|
||||
private readonly IDataWindowWidget[] modules =
|
||||
{
|
||||
new ServerOpcodeWidget(),
|
||||
new AddressesWidget(),
|
||||
new ObjectTableWidget(),
|
||||
new FateTableWidget(),
|
||||
new SeFontTestWidget(),
|
||||
new FontAwesomeTestWidget(),
|
||||
new PartyListWidget(),
|
||||
new BuddyListWidget(),
|
||||
new PluginIpcWidget(),
|
||||
new ConditionWidget(),
|
||||
new GaugeWidget(),
|
||||
new CommandWidget(),
|
||||
new AddonWidget(),
|
||||
new AddonInspectorWidget(),
|
||||
new AtkArrayDataBrowserWidget(),
|
||||
new StartInfoWidget(),
|
||||
new TargetWidget(),
|
||||
new ToastWidget(),
|
||||
new FlyTextWidget(),
|
||||
new ImGuiWidget(),
|
||||
new TexWidget(),
|
||||
new KeyStateWidget(),
|
||||
new GamepadWidget(),
|
||||
new ConfigurationWidget(),
|
||||
new TaskSchedulerWidget(),
|
||||
new HookWidget(),
|
||||
new AetherytesWidget(),
|
||||
new DtrBarWidget(),
|
||||
new UIColorWidget(),
|
||||
new DataShareWidget(),
|
||||
};
|
||||
|
||||
private readonly Dictionary<DataKind, string> dataKindNames = new();
|
||||
|
||||
private bool isExcept;
|
||||
private DataKind currentKind;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="DataWindow"/> class.
|
||||
/// </summary>
|
||||
public DataWindow()
|
||||
: base("Dalamud Data")
|
||||
{
|
||||
this.Size = new Vector2(500, 500);
|
||||
this.SizeCondition = ImGuiCond.FirstUseEver;
|
||||
|
||||
this.RespectCloseHotkey = false;
|
||||
|
||||
foreach (var dataKind in Enum.GetValues<DataKind>())
|
||||
{
|
||||
this.dataKindNames[dataKind] = dataKind.ToString().Replace("_", " ");
|
||||
}
|
||||
|
||||
this.Load();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void OnOpen()
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void OnClose()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set the DataKind dropdown menu.
|
||||
/// </summary>
|
||||
/// <param name="dataKind">Data kind name, can be lower and/or without spaces.</param>
|
||||
public void SetDataKind(string dataKind)
|
||||
{
|
||||
if (string.IsNullOrEmpty(dataKind))
|
||||
return;
|
||||
|
||||
dataKind = dataKind switch
|
||||
{
|
||||
"ai" => "Addon Inspector",
|
||||
"at" => "Object Table", // Actor Table
|
||||
"ot" => "Object Table",
|
||||
"uic" => "UIColor",
|
||||
_ => dataKind,
|
||||
};
|
||||
|
||||
dataKind = dataKind.Replace(" ", string.Empty).ToLower();
|
||||
|
||||
var matched = Enum
|
||||
.GetValues<DataKind>()
|
||||
.FirstOrDefault(kind => Enum.GetName(kind)?.Replace("_", string.Empty).ToLower() == dataKind);
|
||||
|
||||
if (matched != default)
|
||||
{
|
||||
this.currentKind = matched;
|
||||
}
|
||||
else
|
||||
{
|
||||
Service<ChatGui>.Get().PrintError($"/xldata: Invalid data type {dataKind}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Draw the window via ImGui.
|
||||
/// </summary>
|
||||
public override void Draw()
|
||||
{
|
||||
if (ImGuiComponents.IconButton("forceReload", FontAwesomeIcon.Sync)) this.Load();
|
||||
if (ImGui.IsItemHovered()) ImGui.SetTooltip("Force Reload");
|
||||
ImGui.SameLine();
|
||||
var copy = ImGuiComponents.IconButton("copyAll", FontAwesomeIcon.ClipboardList);
|
||||
if (ImGui.IsItemHovered()) ImGui.SetTooltip("Copy All");
|
||||
ImGui.SameLine();
|
||||
|
||||
ImGui.SetNextItemWidth(275.0f * ImGuiHelpers.GlobalScale);
|
||||
if (ImGui.BeginCombo("Data Kind", this.dataKindNames[this.currentKind]))
|
||||
{
|
||||
foreach (var module in this.modules.OrderBy(module => this.dataKindNames[module.DataKind]))
|
||||
{
|
||||
if (ImGui.Selectable(this.dataKindNames[module.DataKind], this.currentKind == module.DataKind))
|
||||
{
|
||||
this.currentKind = module.DataKind;
|
||||
}
|
||||
}
|
||||
|
||||
ImGui.EndCombo();
|
||||
}
|
||||
|
||||
ImGuiHelpers.ScaledDummy(10.0f);
|
||||
|
||||
ImGui.BeginChild("scrolling", Vector2.Zero, false, ImGuiWindowFlags.HorizontalScrollbar);
|
||||
|
||||
if (copy)
|
||||
ImGui.LogToClipboard();
|
||||
|
||||
try
|
||||
{
|
||||
var selectedWidget = this.modules.FirstOrDefault(dataWindowWidget => dataWindowWidget.DataKind == this.currentKind);
|
||||
|
||||
if (selectedWidget is { Ready: true })
|
||||
{
|
||||
selectedWidget.Draw();
|
||||
}
|
||||
else
|
||||
{
|
||||
ImGui.TextUnformatted("Data not ready.");
|
||||
}
|
||||
|
||||
this.isExcept = false;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (!this.isExcept)
|
||||
{
|
||||
Log.Error(ex, "Could not draw data");
|
||||
}
|
||||
|
||||
this.isExcept = true;
|
||||
|
||||
ImGui.TextUnformatted(ex.ToString());
|
||||
}
|
||||
|
||||
ImGui.EndChild();
|
||||
}
|
||||
|
||||
private void Load()
|
||||
{
|
||||
foreach (var widget in this.modules)
|
||||
{
|
||||
widget.Load();
|
||||
}
|
||||
}
|
||||
}
|
||||
27
Dalamud/Interface/Internal/Windows/Data/IDataWindowWidget.cs
Normal file
27
Dalamud/Interface/Internal/Windows/Data/IDataWindowWidget.cs
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
namespace Dalamud.Interface.Internal.Windows;
|
||||
|
||||
/// <summary>
|
||||
/// Class representing a date window entry.
|
||||
/// </summary>
|
||||
internal interface IDataWindowWidget
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the Data Kind for this data window module.
|
||||
/// </summary>
|
||||
DataKind DataKind { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether this data window module is ready.
|
||||
/// </summary>
|
||||
bool Ready { get; protected set; }
|
||||
|
||||
/// <summary>
|
||||
/// Loads the necessary data for this data window module.
|
||||
/// </summary>
|
||||
void Load();
|
||||
|
||||
/// <summary>
|
||||
/// Draws this data window module.
|
||||
/// </summary>
|
||||
void Draw();
|
||||
}
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
namespace Dalamud.Interface.Internal.Windows.Data;
|
||||
|
||||
/// <summary>
|
||||
/// Widget for displaying addon inspector.
|
||||
/// </summary>
|
||||
internal class AddonInspectorWidget : IDataWindowWidget
|
||||
{
|
||||
private UiDebug? addonInspector;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public DataKind DataKind { get; init; } = DataKind.Addon_Inspector;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool Ready { get; set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Load()
|
||||
{
|
||||
this.addonInspector = new UiDebug();
|
||||
|
||||
if (this.addonInspector is not null)
|
||||
{
|
||||
this.Ready = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Draw()
|
||||
{
|
||||
this.addonInspector?.Draw();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,66 @@
|
|||
using Dalamud.Game.Gui;
|
||||
using Dalamud.Memory;
|
||||
using Dalamud.Utility;
|
||||
using ImGuiNET;
|
||||
|
||||
namespace Dalamud.Interface.Internal.Windows.Data;
|
||||
|
||||
/// <summary>
|
||||
/// Widget for displaying Addon Data.
|
||||
/// </summary>
|
||||
internal unsafe class AddonWidget : IDataWindowWidget
|
||||
{
|
||||
private string inputAddonName = string.Empty;
|
||||
private int inputAddonIndex;
|
||||
private nint findAgentInterfacePtr;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public DataKind DataKind { get; init; } = DataKind.Addon;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool Ready { get; set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Load()
|
||||
{
|
||||
this.Ready = true;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Draw()
|
||||
{
|
||||
var gameGui = Service<GameGui>.Get();
|
||||
|
||||
ImGui.InputText("Addon Name", ref this.inputAddonName, 256);
|
||||
ImGui.InputInt("Addon Index", ref this.inputAddonIndex);
|
||||
|
||||
if (this.inputAddonName.IsNullOrEmpty())
|
||||
return;
|
||||
|
||||
var address = gameGui.GetAddonByName(this.inputAddonName, this.inputAddonIndex);
|
||||
|
||||
if (address == nint.Zero)
|
||||
{
|
||||
ImGui.Text("Null");
|
||||
return;
|
||||
}
|
||||
|
||||
var addon = (FFXIVClientStructs.FFXIV.Component.GUI.AtkUnitBase*)address;
|
||||
var name = MemoryHelper.ReadStringNullTerminated((nint)addon->Name);
|
||||
ImGui.TextUnformatted($"{name} - 0x{address.ToInt64():X}\n v:{addon->IsVisible} x:{addon->X} y:{addon->Y} s:{addon->Scale}, w:{addon->RootNode->Width}, h:{addon->RootNode->Height}");
|
||||
|
||||
if (ImGui.Button("Find Agent"))
|
||||
{
|
||||
this.findAgentInterfacePtr = gameGui.FindAgentInterface(address);
|
||||
}
|
||||
|
||||
if (this.findAgentInterfacePtr != nint.Zero)
|
||||
{
|
||||
ImGui.TextUnformatted($"Agent: 0x{this.findAgentInterfacePtr.ToInt64():X}");
|
||||
ImGui.SameLine();
|
||||
|
||||
if (ImGui.Button("C"))
|
||||
ImGui.SetClipboardText(this.findAgentInterfacePtr.ToInt64().ToString("X"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,64 @@
|
|||
using System.Collections.Generic;
|
||||
|
||||
using Dalamud.Game;
|
||||
using ImGuiNET;
|
||||
|
||||
namespace Dalamud.Interface.Internal.Windows.Data;
|
||||
|
||||
/// <summary>
|
||||
/// Widget to display resolved .text sigs.
|
||||
/// </summary>
|
||||
internal class AddressesWidget : IDataWindowWidget
|
||||
{
|
||||
private string inputSig = string.Empty;
|
||||
private nint sigResult = nint.Zero;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public DataKind DataKind { get; init; } = DataKind.Address;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool Ready { get; set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Load()
|
||||
{
|
||||
this.Ready = true;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Draw()
|
||||
{
|
||||
ImGui.InputText(".text sig", ref this.inputSig, 400);
|
||||
if (ImGui.Button("Resolve"))
|
||||
{
|
||||
try
|
||||
{
|
||||
var sigScanner = Service<SigScanner>.Get();
|
||||
this.sigResult = sigScanner.ScanText(this.inputSig);
|
||||
}
|
||||
catch (KeyNotFoundException)
|
||||
{
|
||||
this.sigResult = new nint(-1);
|
||||
}
|
||||
}
|
||||
|
||||
ImGui.Text($"Result: {this.sigResult.ToInt64():X}");
|
||||
ImGui.SameLine();
|
||||
if (ImGui.Button($"C##{this.sigResult.ToInt64():X}"))
|
||||
ImGui.SetClipboardText(this.sigResult.ToInt64().ToString("X"));
|
||||
|
||||
foreach (var debugScannedValue in BaseAddressResolver.DebugScannedValues)
|
||||
{
|
||||
ImGui.TextUnformatted($"{debugScannedValue.Key}");
|
||||
foreach (var valueTuple in debugScannedValue.Value)
|
||||
{
|
||||
ImGui.TextUnformatted(
|
||||
$" {valueTuple.ClassName} - 0x{valueTuple.Address.ToInt64():X}");
|
||||
ImGui.SameLine();
|
||||
|
||||
if (ImGui.Button($"C##{valueTuple.Address.ToInt64():X}"))
|
||||
ImGui.SetClipboardText(valueTuple.Address.ToInt64().ToString("X"));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,87 @@
|
|||
using Dalamud.Game.ClientState.Aetherytes;
|
||||
using ImGuiNET;
|
||||
|
||||
namespace Dalamud.Interface.Internal.Windows.Data;
|
||||
|
||||
/// <summary>
|
||||
/// Widget for displaying aetheryte table.
|
||||
/// </summary>
|
||||
internal class AetherytesWidget : IDataWindowWidget
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public DataKind DataKind { get; init; } = DataKind.Aetherytes;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool Ready { get; set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Load()
|
||||
{
|
||||
this.Ready = true;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Draw()
|
||||
{
|
||||
if (!ImGui.BeginTable("##aetheryteTable", 11, ImGuiTableFlags.ScrollY | ImGuiTableFlags.RowBg | ImGuiTableFlags.Borders))
|
||||
return;
|
||||
|
||||
ImGui.TableSetupScrollFreeze(0, 1);
|
||||
ImGui.TableSetupColumn("Idx", ImGuiTableColumnFlags.WidthFixed);
|
||||
ImGui.TableSetupColumn("Name", ImGuiTableColumnFlags.WidthFixed);
|
||||
ImGui.TableSetupColumn("ID", ImGuiTableColumnFlags.WidthFixed);
|
||||
ImGui.TableSetupColumn("Zone", ImGuiTableColumnFlags.WidthFixed);
|
||||
ImGui.TableSetupColumn("Ward", ImGuiTableColumnFlags.WidthFixed);
|
||||
ImGui.TableSetupColumn("Plot", ImGuiTableColumnFlags.WidthFixed);
|
||||
ImGui.TableSetupColumn("Sub", ImGuiTableColumnFlags.WidthFixed);
|
||||
ImGui.TableSetupColumn("Gil", ImGuiTableColumnFlags.WidthFixed);
|
||||
ImGui.TableSetupColumn("Fav", ImGuiTableColumnFlags.WidthFixed);
|
||||
ImGui.TableSetupColumn("Shared", ImGuiTableColumnFlags.WidthFixed);
|
||||
ImGui.TableSetupColumn("Apartment", ImGuiTableColumnFlags.WidthFixed);
|
||||
ImGui.TableHeadersRow();
|
||||
|
||||
var tpList = Service<AetheryteList>.Get();
|
||||
|
||||
for (var i = 0; i < tpList.Length; i++)
|
||||
{
|
||||
var info = tpList[i];
|
||||
if (info == null)
|
||||
continue;
|
||||
|
||||
ImGui.TableNextColumn(); // Idx
|
||||
ImGui.TextUnformatted($"{i}");
|
||||
|
||||
ImGui.TableNextColumn(); // Name
|
||||
ImGui.TextUnformatted($"{info.AetheryteData.GameData?.PlaceName.Value?.Name}");
|
||||
|
||||
ImGui.TableNextColumn(); // ID
|
||||
ImGui.TextUnformatted($"{info.AetheryteId}");
|
||||
|
||||
ImGui.TableNextColumn(); // Zone
|
||||
ImGui.TextUnformatted($"{info.TerritoryId}");
|
||||
|
||||
ImGui.TableNextColumn(); // Ward
|
||||
ImGui.TextUnformatted($"{info.Ward}");
|
||||
|
||||
ImGui.TableNextColumn(); // Plot
|
||||
ImGui.TextUnformatted($"{info.Plot}");
|
||||
|
||||
ImGui.TableNextColumn(); // Sub
|
||||
ImGui.TextUnformatted($"{info.SubIndex}");
|
||||
|
||||
ImGui.TableNextColumn(); // Gil
|
||||
ImGui.TextUnformatted($"{info.GilCost}");
|
||||
|
||||
ImGui.TableNextColumn(); // Favourite
|
||||
ImGui.TextUnformatted($"{info.IsFavourite}");
|
||||
|
||||
ImGui.TableNextColumn(); // Shared
|
||||
ImGui.TextUnformatted($"{info.IsSharedHouse}");
|
||||
|
||||
ImGui.TableNextColumn(); // Apartment
|
||||
ImGui.TextUnformatted($"{info.IsAppartment}");
|
||||
}
|
||||
|
||||
ImGui.EndTable();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,173 @@
|
|||
using System;
|
||||
using System.Numerics;
|
||||
|
||||
using Dalamud.Memory;
|
||||
using ImGuiNET;
|
||||
|
||||
namespace Dalamud.Interface.Internal.Windows.Data;
|
||||
|
||||
/// <summary>
|
||||
/// Widget for displaying AtkArrayData.
|
||||
/// </summary>
|
||||
internal unsafe class AtkArrayDataBrowserWidget : IDataWindowWidget
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public DataKind DataKind { get; init; } = DataKind.AtkArrayData_Browser;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool Ready { get; set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Load()
|
||||
{
|
||||
this.Ready = true;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Draw()
|
||||
{
|
||||
var fontWidth = ImGui.CalcTextSize("A").X;
|
||||
var fontHeight = ImGui.GetTextLineHeightWithSpacing();
|
||||
var uiModule = FFXIVClientStructs.FFXIV.Client.System.Framework.Framework.Instance()->GetUiModule();
|
||||
|
||||
if (uiModule == null)
|
||||
{
|
||||
ImGui.Text("UIModule unavailable.");
|
||||
return;
|
||||
}
|
||||
|
||||
var atkArrayDataHolder = &uiModule->GetRaptureAtkModule()->AtkModule.AtkArrayDataHolder;
|
||||
|
||||
if (ImGui.BeginTabBar("AtkArrayDataBrowserTabBar"))
|
||||
{
|
||||
if (ImGui.BeginTabItem($"NumberArrayData [{atkArrayDataHolder->NumberArrayCount}]"))
|
||||
{
|
||||
if (ImGui.BeginTable("NumberArrayDataTable", 3, ImGuiTableFlags.RowBg | ImGuiTableFlags.ScrollY))
|
||||
{
|
||||
ImGui.TableSetupColumn("Index", ImGuiTableColumnFlags.WidthFixed, fontWidth * 10);
|
||||
ImGui.TableSetupColumn("Size", ImGuiTableColumnFlags.WidthFixed, fontWidth * 10);
|
||||
ImGui.TableSetupColumn("Pointer", ImGuiTableColumnFlags.WidthStretch);
|
||||
ImGui.TableHeadersRow();
|
||||
for (var numberArrayIndex = 0; numberArrayIndex < atkArrayDataHolder->NumberArrayCount; numberArrayIndex++)
|
||||
{
|
||||
ImGui.TableNextRow();
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.Text($"{numberArrayIndex} [{numberArrayIndex * 8:X}]");
|
||||
ImGui.TableNextColumn();
|
||||
var numberArrayData = atkArrayDataHolder->NumberArrays[numberArrayIndex];
|
||||
if (numberArrayData != null)
|
||||
{
|
||||
ImGui.Text($"{numberArrayData->AtkArrayData.Size}");
|
||||
ImGui.TableNextColumn();
|
||||
if (ImGui.TreeNodeEx($"{(long)numberArrayData:X}###{numberArrayIndex}", ImGuiTreeNodeFlags.SpanFullWidth))
|
||||
{
|
||||
ImGui.NewLine();
|
||||
var tableHeight = Math.Min(40, numberArrayData->AtkArrayData.Size + 4);
|
||||
if (ImGui.BeginTable($"NumberArrayDataTable", 4, ImGuiTableFlags.RowBg | ImGuiTableFlags.ScrollY, new Vector2(0.0F, fontHeight * tableHeight)))
|
||||
{
|
||||
ImGui.TableSetupColumn("Index", ImGuiTableColumnFlags.WidthFixed, fontWidth * 6);
|
||||
ImGui.TableSetupColumn("Hex", ImGuiTableColumnFlags.WidthFixed, fontWidth * 9);
|
||||
ImGui.TableSetupColumn("Integer", ImGuiTableColumnFlags.WidthFixed, fontWidth * 12);
|
||||
ImGui.TableSetupColumn("Float", ImGuiTableColumnFlags.WidthFixed, fontWidth * 20);
|
||||
ImGui.TableHeadersRow();
|
||||
for (var numberIndex = 0; numberIndex < numberArrayData->AtkArrayData.Size; numberIndex++)
|
||||
{
|
||||
ImGui.TableNextRow();
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.Text($"{numberIndex}");
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.Text($"{numberArrayData->IntArray[numberIndex]:X}");
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.Text($"{numberArrayData->IntArray[numberIndex]}");
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.Text($"{*(float*)&numberArrayData->IntArray[numberIndex]}");
|
||||
}
|
||||
|
||||
ImGui.EndTable();
|
||||
}
|
||||
|
||||
ImGui.TreePop();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ImGui.TextDisabled("--");
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.TextDisabled("--");
|
||||
}
|
||||
}
|
||||
|
||||
ImGui.EndTable();
|
||||
}
|
||||
|
||||
ImGui.EndTabItem();
|
||||
}
|
||||
|
||||
if (ImGui.BeginTabItem($"StringArrayData [{atkArrayDataHolder->StringArrayCount}]"))
|
||||
{
|
||||
if (ImGui.BeginTable("StringArrayDataTable", 3, ImGuiTableFlags.RowBg | ImGuiTableFlags.ScrollY))
|
||||
{
|
||||
ImGui.TableSetupColumn("Index", ImGuiTableColumnFlags.WidthFixed, fontWidth * 10);
|
||||
ImGui.TableSetupColumn("Size", ImGuiTableColumnFlags.WidthFixed, fontWidth * 10);
|
||||
ImGui.TableSetupColumn("Pointer", ImGuiTableColumnFlags.WidthStretch);
|
||||
ImGui.TableHeadersRow();
|
||||
for (var stringArrayIndex = 0; stringArrayIndex < atkArrayDataHolder->StringArrayCount; stringArrayIndex++)
|
||||
{
|
||||
ImGui.TableNextRow();
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.Text($"{stringArrayIndex} [{stringArrayIndex * 8:X}]");
|
||||
ImGui.TableNextColumn();
|
||||
var stringArrayData = atkArrayDataHolder->StringArrays[stringArrayIndex];
|
||||
if (stringArrayData != null)
|
||||
{
|
||||
ImGui.Text($"{stringArrayData->AtkArrayData.Size}");
|
||||
ImGui.TableNextColumn();
|
||||
if (ImGui.TreeNodeEx($"{(long)stringArrayData:X}###{stringArrayIndex}", ImGuiTreeNodeFlags.SpanFullWidth))
|
||||
{
|
||||
ImGui.NewLine();
|
||||
var tableHeight = Math.Min(40, stringArrayData->AtkArrayData.Size + 4);
|
||||
if (ImGui.BeginTable($"StringArrayDataTable", 2, ImGuiTableFlags.RowBg | ImGuiTableFlags.ScrollY, new Vector2(0.0F, fontHeight * tableHeight)))
|
||||
{
|
||||
ImGui.TableSetupColumn("Index", ImGuiTableColumnFlags.WidthFixed, fontWidth * 6);
|
||||
ImGui.TableSetupColumn("String", ImGuiTableColumnFlags.WidthStretch);
|
||||
ImGui.TableHeadersRow();
|
||||
for (var stringIndex = 0; stringIndex < stringArrayData->AtkArrayData.Size; stringIndex++)
|
||||
{
|
||||
ImGui.TableNextRow();
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.Text($"{stringIndex}");
|
||||
ImGui.TableNextColumn();
|
||||
if (stringArrayData->StringArray[stringIndex] != null)
|
||||
{
|
||||
ImGui.Text($"{MemoryHelper.ReadSeStringNullTerminated(new IntPtr(stringArrayData->StringArray[stringIndex]))}");
|
||||
}
|
||||
else
|
||||
{
|
||||
ImGui.TextDisabled("--");
|
||||
}
|
||||
}
|
||||
|
||||
ImGui.EndTable();
|
||||
}
|
||||
|
||||
ImGui.TreePop();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ImGui.TextDisabled("--");
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.TextDisabled("--");
|
||||
}
|
||||
}
|
||||
|
||||
ImGui.EndTable();
|
||||
}
|
||||
|
||||
ImGui.EndTabItem();
|
||||
}
|
||||
|
||||
ImGui.EndTabBar();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,110 @@
|
|||
using Dalamud.Game.ClientState.Buddy;
|
||||
using Dalamud.Utility;
|
||||
using ImGuiNET;
|
||||
|
||||
namespace Dalamud.Interface.Internal.Windows.Data;
|
||||
|
||||
/// <summary>
|
||||
/// Widget for displaying data about the Buddy List.
|
||||
/// </summary>
|
||||
internal class BuddyListWidget : IDataWindowWidget
|
||||
{
|
||||
private bool resolveGameData;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public DataKind DataKind { get; init; } = DataKind.Buddy_List;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool Ready { get; set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Load()
|
||||
{
|
||||
this.Ready = true;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Draw()
|
||||
{
|
||||
var buddyList = Service<BuddyList>.Get();
|
||||
|
||||
ImGui.Checkbox("Resolve GameData", ref this.resolveGameData);
|
||||
|
||||
ImGui.Text($"BuddyList: {buddyList.BuddyListAddress.ToInt64():X}");
|
||||
{
|
||||
var member = buddyList.CompanionBuddy;
|
||||
if (member == null)
|
||||
{
|
||||
ImGui.Text("[Companion] null");
|
||||
}
|
||||
else
|
||||
{
|
||||
ImGui.Text($"[Companion] {member.Address.ToInt64():X} - {member.ObjectId} - {member.DataID}");
|
||||
if (this.resolveGameData)
|
||||
{
|
||||
var gameObject = member.GameObject;
|
||||
if (gameObject == null)
|
||||
{
|
||||
ImGui.Text("GameObject was null");
|
||||
}
|
||||
else
|
||||
{
|
||||
Util.PrintGameObject(gameObject, "-", this.resolveGameData);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
var member = buddyList.PetBuddy;
|
||||
if (member == null)
|
||||
{
|
||||
ImGui.Text("[Pet] null");
|
||||
}
|
||||
else
|
||||
{
|
||||
ImGui.Text($"[Pet] {member.Address.ToInt64():X} - {member.ObjectId} - {member.DataID}");
|
||||
if (this.resolveGameData)
|
||||
{
|
||||
var gameObject = member.GameObject;
|
||||
if (gameObject == null)
|
||||
{
|
||||
ImGui.Text("GameObject was null");
|
||||
}
|
||||
else
|
||||
{
|
||||
Util.PrintGameObject(gameObject, "-", this.resolveGameData);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
var count = buddyList.Length;
|
||||
if (count == 0)
|
||||
{
|
||||
ImGui.Text("[BattleBuddy] None present");
|
||||
}
|
||||
else
|
||||
{
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
var member = buddyList[i];
|
||||
ImGui.Text($"[BattleBuddy] [{i}] {member?.Address.ToInt64():X} - {member?.ObjectId} - {member?.DataID}");
|
||||
if (this.resolveGameData)
|
||||
{
|
||||
var gameObject = member?.GameObject;
|
||||
if (gameObject == null)
|
||||
{
|
||||
ImGui.Text("GameObject was null");
|
||||
}
|
||||
else
|
||||
{
|
||||
Util.PrintGameObject(gameObject, "-", this.resolveGameData);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
using Dalamud.Game.Command;
|
||||
using ImGuiNET;
|
||||
|
||||
namespace Dalamud.Interface.Internal.Windows.Data;
|
||||
|
||||
/// <summary>
|
||||
/// Widget for displaying command info.
|
||||
/// </summary>
|
||||
internal class CommandWidget : IDataWindowWidget
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public DataKind DataKind { get; init; } = DataKind.Command;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool Ready { get; set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Load()
|
||||
{
|
||||
this.Ready = true;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Draw()
|
||||
{
|
||||
var commandManager = Service<CommandManager>.Get();
|
||||
|
||||
foreach (var command in commandManager.Commands)
|
||||
{
|
||||
ImGui.Text($"{command.Key}\n -> {command.Value.HelpMessage}\n -> In help: {command.Value.ShowInHelp}\n\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
using Dalamud.Game.ClientState.Conditions;
|
||||
using ImGuiNET;
|
||||
|
||||
namespace Dalamud.Interface.Internal.Windows.Data;
|
||||
|
||||
/// <summary>
|
||||
/// Widget for displaying current character condition flags.
|
||||
/// </summary>
|
||||
internal class ConditionWidget : IDataWindowWidget
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public DataKind DataKind { get; init; } = DataKind.Condition;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool Ready { get; set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Load()
|
||||
{
|
||||
this.Ready = true;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Draw()
|
||||
{
|
||||
var condition = Service<Condition>.Get();
|
||||
|
||||
#if DEBUG
|
||||
ImGui.Text($"ptr: 0x{condition.Address.ToInt64():X}");
|
||||
#endif
|
||||
|
||||
ImGui.Text("Current Conditions:");
|
||||
ImGui.Separator();
|
||||
|
||||
var didAny = false;
|
||||
|
||||
for (var i = 0; i < Condition.MaxConditionEntries; i++)
|
||||
{
|
||||
var typedCondition = (ConditionFlag)i;
|
||||
var cond = condition[typedCondition];
|
||||
|
||||
if (!cond) continue;
|
||||
|
||||
didAny = true;
|
||||
|
||||
ImGui.Text($"ID: {i} Enum: {typedCondition}");
|
||||
}
|
||||
|
||||
if (!didAny)
|
||||
ImGui.Text("None. Talk to a shop NPC or visit a market board to find out more!");
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
using Dalamud.Configuration.Internal;
|
||||
using Dalamud.Utility;
|
||||
|
||||
namespace Dalamud.Interface.Internal.Windows.Data;
|
||||
|
||||
/// <summary>
|
||||
/// Widget for displaying configuration info.
|
||||
/// </summary>
|
||||
internal class ConfigurationWidget : IDataWindowWidget
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public DataKind DataKind { get; init; } = DataKind.Configuration;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool Ready { get; set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Load()
|
||||
{
|
||||
this.Ready = true;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Draw()
|
||||
{
|
||||
var config = Service<DalamudConfiguration>.Get();
|
||||
Util.ShowObject(config);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,53 @@
|
|||
using Dalamud.Plugin.Ipc.Internal;
|
||||
using ImGuiNET;
|
||||
|
||||
namespace Dalamud.Interface.Internal.Windows.Data;
|
||||
|
||||
/// <summary>
|
||||
/// Widget for displaying plugin data share modules.
|
||||
/// </summary>
|
||||
internal class DataShareWidget : IDataWindowWidget
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public DataKind DataKind { get; init; } = DataKind.DataShare;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool Ready { get; set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Load()
|
||||
{
|
||||
this.Ready = true;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Draw()
|
||||
{
|
||||
if (!ImGui.BeginTable("###DataShareTable", 4, ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.RowBg))
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
ImGui.TableSetupColumn("Shared Tag");
|
||||
ImGui.TableSetupColumn("Creator Assembly");
|
||||
ImGui.TableSetupColumn("#", ImGuiTableColumnFlags.WidthFixed, 30 * ImGuiHelpers.GlobalScale);
|
||||
ImGui.TableSetupColumn("Consumers");
|
||||
ImGui.TableHeadersRow();
|
||||
foreach (var share in Service<DataShare>.Get().GetAllShares())
|
||||
{
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.TextUnformatted(share.Tag);
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.TextUnformatted(share.CreatorAssembly);
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.TextUnformatted(share.Users.Length.ToString());
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.TextUnformatted(string.Join(", ", share.Users));
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
ImGui.EndTable();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,80 @@
|
|||
using Dalamud.Configuration.Internal;
|
||||
using Dalamud.Game.Gui.Dtr;
|
||||
using ImGuiNET;
|
||||
|
||||
namespace Dalamud.Interface.Internal.Windows.Data;
|
||||
|
||||
/// <summary>
|
||||
/// Widget for displaying dtr test.
|
||||
/// </summary>
|
||||
internal class DtrBarWidget : IDataWindowWidget
|
||||
{
|
||||
private DtrBarEntry? dtrTest1;
|
||||
private DtrBarEntry? dtrTest2;
|
||||
private DtrBarEntry? dtrTest3;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public DataKind DataKind { get; init; } = DataKind.Dtr_Bar;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool Ready { get; set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Load()
|
||||
{
|
||||
this.Ready = true;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Draw()
|
||||
{
|
||||
this.DrawDtrTestEntry(ref this.dtrTest1, "DTR Test #1");
|
||||
ImGui.Separator();
|
||||
this.DrawDtrTestEntry(ref this.dtrTest2, "DTR Test #2");
|
||||
ImGui.Separator();
|
||||
this.DrawDtrTestEntry(ref this.dtrTest3, "DTR Test #3");
|
||||
ImGui.Separator();
|
||||
|
||||
var configuration = Service<DalamudConfiguration>.Get();
|
||||
if (configuration.DtrOrder != null)
|
||||
{
|
||||
ImGui.Separator();
|
||||
|
||||
foreach (var order in configuration.DtrOrder)
|
||||
{
|
||||
ImGui.Text(order);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawDtrTestEntry(ref DtrBarEntry? entry, string title)
|
||||
{
|
||||
var dtrBar = Service<DtrBar>.Get();
|
||||
|
||||
if (entry != null)
|
||||
{
|
||||
ImGui.Text(title);
|
||||
|
||||
var text = entry.Text?.TextValue ?? string.Empty;
|
||||
if (ImGui.InputText($"Text###{entry.Title}t", ref text, 255))
|
||||
entry.Text = text;
|
||||
|
||||
var shown = entry.Shown;
|
||||
if (ImGui.Checkbox($"Shown###{entry.Title}s", ref shown))
|
||||
entry.Shown = shown;
|
||||
|
||||
if (ImGui.Button($"Remove###{entry.Title}r"))
|
||||
{
|
||||
entry.Remove();
|
||||
entry = null;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (ImGui.Button($"Add###{title}"))
|
||||
{
|
||||
entry = dtrBar.Get(title, title);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,68 @@
|
|||
using Dalamud.Game.ClientState.Fates;
|
||||
using ImGuiNET;
|
||||
|
||||
namespace Dalamud.Interface.Internal.Windows.Data;
|
||||
|
||||
/// <summary>
|
||||
/// Widget for displaying the Fate Table.
|
||||
/// </summary>
|
||||
internal class FateTableWidget : IDataWindowWidget
|
||||
{
|
||||
private bool resolveGameData;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public DataKind DataKind { get; init; } = DataKind.Fate_Table;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool Ready { get; set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Load()
|
||||
{
|
||||
this.Ready = true;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Draw()
|
||||
{
|
||||
ImGui.Checkbox("Resolve GameData", ref this.resolveGameData);
|
||||
|
||||
var fateTable = Service<FateTable>.Get();
|
||||
|
||||
var stateString = string.Empty;
|
||||
if (fateTable.Length == 0)
|
||||
{
|
||||
ImGui.TextUnformatted("No fates or data not ready.");
|
||||
}
|
||||
else
|
||||
{
|
||||
stateString += $"FateTableLen: {fateTable.Length}\n";
|
||||
|
||||
ImGui.TextUnformatted(stateString);
|
||||
|
||||
for (var i = 0; i < fateTable.Length; i++)
|
||||
{
|
||||
var fate = fateTable[i];
|
||||
if (fate == null)
|
||||
continue;
|
||||
|
||||
var fateString = $"{fate.Address.ToInt64():X}:[{i}]" +
|
||||
$" - Lv.{fate.Level} {fate.Name} ({fate.Progress}%)" +
|
||||
$" - X{fate.Position.X} Y{fate.Position.Y} Z{fate.Position.Z}" +
|
||||
$" - Territory {(this.resolveGameData ? (fate.TerritoryType.GameData?.Name ?? fate.TerritoryType.Id.ToString()) : fate.TerritoryType.Id.ToString())}\n";
|
||||
|
||||
fateString += $" StartTimeEpoch: {fate.StartTimeEpoch}" +
|
||||
$" - Duration: {fate.Duration}" +
|
||||
$" - State: {fate.State}" +
|
||||
$" - GameData name: {(this.resolveGameData ? (fate.GameData.Name ?? fate.FateId.ToString()) : fate.FateId.ToString())}";
|
||||
|
||||
ImGui.TextUnformatted(fateString);
|
||||
ImGui.SameLine();
|
||||
if (ImGui.Button("C"))
|
||||
{
|
||||
ImGui.SetClipboardText(fate.Address.ToString("X"));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,77 @@
|
|||
using System;
|
||||
using System.Numerics;
|
||||
|
||||
using Dalamud.Game.Gui.FlyText;
|
||||
using ImGuiNET;
|
||||
|
||||
namespace Dalamud.Interface.Internal.Windows.Data;
|
||||
|
||||
/// <summary>
|
||||
/// Widget for displaying fly text info.
|
||||
/// </summary>
|
||||
internal class FlyTextWidget : IDataWindowWidget
|
||||
{
|
||||
private int flyActor;
|
||||
private FlyTextKind flyKind;
|
||||
private int flyVal1;
|
||||
private int flyVal2;
|
||||
private string flyText1 = string.Empty;
|
||||
private string flyText2 = string.Empty;
|
||||
private int flyIcon;
|
||||
private int flyDmgIcon;
|
||||
private Vector4 flyColor = new(1, 0, 0, 1);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public DataKind DataKind { get; init; } = DataKind.FlyText;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool Ready { get; set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Load()
|
||||
{
|
||||
this.Ready = true;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Draw()
|
||||
{
|
||||
if (ImGui.BeginCombo("Kind", this.flyKind.ToString()))
|
||||
{
|
||||
var names = Enum.GetNames(typeof(FlyTextKind));
|
||||
for (var i = 0; i < names.Length; i++)
|
||||
{
|
||||
if (ImGui.Selectable($"{names[i]} ({i})"))
|
||||
this.flyKind = (FlyTextKind)i;
|
||||
}
|
||||
|
||||
ImGui.EndCombo();
|
||||
}
|
||||
|
||||
ImGui.InputText("Text1", ref this.flyText1, 200);
|
||||
ImGui.InputText("Text2", ref this.flyText2, 200);
|
||||
|
||||
ImGui.InputInt("Val1", ref this.flyVal1);
|
||||
ImGui.InputInt("Val2", ref this.flyVal2);
|
||||
|
||||
ImGui.InputInt("Icon ID", ref this.flyIcon);
|
||||
ImGui.InputInt("Damage Icon ID", ref this.flyDmgIcon);
|
||||
ImGui.ColorEdit4("Color", ref this.flyColor);
|
||||
ImGui.InputInt("Actor Index", ref this.flyActor);
|
||||
var sendColor = ImGui.ColorConvertFloat4ToU32(this.flyColor);
|
||||
|
||||
if (ImGui.Button("Send"))
|
||||
{
|
||||
Service<FlyTextGui>.Get().AddFlyText(
|
||||
this.flyKind,
|
||||
unchecked((uint)this.flyActor),
|
||||
unchecked((uint)this.flyVal1),
|
||||
unchecked((uint)this.flyVal2),
|
||||
this.flyText1,
|
||||
this.flyText2,
|
||||
sendColor,
|
||||
unchecked((uint)this.flyIcon),
|
||||
unchecked((uint)this.flyDmgIcon));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,78 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
|
||||
using ImGuiNET;
|
||||
|
||||
namespace Dalamud.Interface.Internal.Windows.Data;
|
||||
|
||||
/// <summary>
|
||||
/// Widget to display FontAwesome Symbols.
|
||||
/// </summary>
|
||||
internal class FontAwesomeTestWidget : IDataWindowWidget
|
||||
{
|
||||
private List<FontAwesomeIcon>? icons;
|
||||
private List<string>? iconNames;
|
||||
private string[]? iconCategories;
|
||||
private int selectedIconCategory;
|
||||
private string iconSearchInput = string.Empty;
|
||||
private bool iconSearchChanged = true;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public DataKind DataKind { get; init; } = DataKind.FontAwesome_Test;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool Ready { get; set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Load()
|
||||
{
|
||||
this.Ready = true;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Draw()
|
||||
{
|
||||
ImGui.PushStyleVar(ImGuiStyleVar.ItemSpacing, Vector2.Zero);
|
||||
|
||||
this.iconCategories ??= FontAwesomeHelpers.GetCategories();
|
||||
|
||||
if (this.iconSearchChanged)
|
||||
{
|
||||
this.icons = FontAwesomeHelpers.SearchIcons(this.iconSearchInput, this.iconCategories[this.selectedIconCategory]);
|
||||
this.iconNames = this.icons.Select(icon => Enum.GetName(icon)!).ToList();
|
||||
this.iconSearchChanged = false;
|
||||
}
|
||||
|
||||
ImGui.SetNextItemWidth(160f);
|
||||
var categoryIndex = this.selectedIconCategory;
|
||||
if (ImGui.Combo("####FontAwesomeCategorySearch", ref categoryIndex, this.iconCategories, this.iconCategories.Length))
|
||||
{
|
||||
this.selectedIconCategory = categoryIndex;
|
||||
this.iconSearchChanged = true;
|
||||
}
|
||||
|
||||
ImGui.SameLine(170f);
|
||||
ImGui.SetNextItemWidth(180f);
|
||||
if (ImGui.InputTextWithHint($"###FontAwesomeInputSearch", "search icons", ref this.iconSearchInput, 50))
|
||||
{
|
||||
this.iconSearchChanged = true;
|
||||
}
|
||||
|
||||
ImGuiHelpers.ScaledDummy(10f);
|
||||
for (var i = 0; i < this.icons?.Count; i++)
|
||||
{
|
||||
ImGui.Text($"0x{(int)this.icons[i].ToIconChar():X}");
|
||||
ImGuiHelpers.ScaledRelativeSameLine(50f);
|
||||
ImGui.Text($"{this.iconNames?[i]}");
|
||||
ImGuiHelpers.ScaledRelativeSameLine(280f);
|
||||
ImGui.PushFont(UiBuilder.IconFont);
|
||||
ImGui.Text(this.icons[i].ToIconString());
|
||||
ImGui.PopFont();
|
||||
ImGuiHelpers.ScaledDummy(2f);
|
||||
}
|
||||
|
||||
ImGui.PopStyleVar();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,86 @@
|
|||
using System;
|
||||
|
||||
using Dalamud.Game.ClientState.GamePad;
|
||||
using ImGuiNET;
|
||||
|
||||
namespace Dalamud.Interface.Internal.Windows.Data;
|
||||
|
||||
/// <summary>
|
||||
/// Widget for displaying gamepad info.
|
||||
/// </summary>
|
||||
internal class GamepadWidget : IDataWindowWidget
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public DataKind DataKind { get; init; } = DataKind.Gamepad;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool Ready { get; set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Load()
|
||||
{
|
||||
this.Ready = true;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Draw()
|
||||
{
|
||||
var gamepadState = Service<GamepadState>.Get();
|
||||
|
||||
static void DrawHelper(string text, uint mask, Func<GamepadButtons, float> resolve)
|
||||
{
|
||||
ImGui.Text($"{text} {mask:X4}");
|
||||
ImGui.Text($"DPadLeft {resolve(GamepadButtons.DpadLeft)} " +
|
||||
$"DPadUp {resolve(GamepadButtons.DpadUp)} " +
|
||||
$"DPadRight {resolve(GamepadButtons.DpadRight)} " +
|
||||
$"DPadDown {resolve(GamepadButtons.DpadDown)} ");
|
||||
ImGui.Text($"West {resolve(GamepadButtons.West)} " +
|
||||
$"North {resolve(GamepadButtons.North)} " +
|
||||
$"East {resolve(GamepadButtons.East)} " +
|
||||
$"South {resolve(GamepadButtons.South)} ");
|
||||
ImGui.Text($"L1 {resolve(GamepadButtons.L1)} " +
|
||||
$"L2 {resolve(GamepadButtons.L2)} " +
|
||||
$"R1 {resolve(GamepadButtons.R1)} " +
|
||||
$"R2 {resolve(GamepadButtons.R2)} ");
|
||||
ImGui.Text($"Select {resolve(GamepadButtons.Select)} " +
|
||||
$"Start {resolve(GamepadButtons.Start)} " +
|
||||
$"L3 {resolve(GamepadButtons.L3)} " +
|
||||
$"R3 {resolve(GamepadButtons.R3)} ");
|
||||
}
|
||||
|
||||
ImGui.Text($"GamepadInput 0x{gamepadState.GamepadInputAddress.ToInt64():X}");
|
||||
|
||||
#if DEBUG
|
||||
if (ImGui.IsItemHovered())
|
||||
ImGui.SetMouseCursor(ImGuiMouseCursor.Hand);
|
||||
|
||||
if (ImGui.IsItemClicked())
|
||||
ImGui.SetClipboardText($"0x{gamepadState.GamepadInputAddress.ToInt64():X}");
|
||||
#endif
|
||||
|
||||
DrawHelper(
|
||||
"Buttons Raw",
|
||||
gamepadState.ButtonsRaw,
|
||||
gamepadState.Raw);
|
||||
DrawHelper(
|
||||
"Buttons Pressed",
|
||||
gamepadState.ButtonsPressed,
|
||||
gamepadState.Pressed);
|
||||
DrawHelper(
|
||||
"Buttons Repeat",
|
||||
gamepadState.ButtonsRepeat,
|
||||
gamepadState.Repeat);
|
||||
DrawHelper(
|
||||
"Buttons Released",
|
||||
gamepadState.ButtonsReleased,
|
||||
gamepadState.Released);
|
||||
ImGui.Text($"LeftStickLeft {gamepadState.LeftStickLeft:0.00} " +
|
||||
$"LeftStickUp {gamepadState.LeftStickUp:0.00} " +
|
||||
$"LeftStickRight {gamepadState.LeftStickRight:0.00} " +
|
||||
$"LeftStickDown {gamepadState.LeftStickDown:0.00} ");
|
||||
ImGui.Text($"RightStickLeft {gamepadState.RightStickLeft:0.00} " +
|
||||
$"RightStickUp {gamepadState.RightStickUp:0.00} " +
|
||||
$"RightStickRight {gamepadState.RightStickRight:0.00} " +
|
||||
$"RightStickDown {gamepadState.RightStickDown:0.00} ");
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,72 @@
|
|||
using Dalamud.Game.ClientState;
|
||||
using Dalamud.Game.ClientState.JobGauge;
|
||||
using Dalamud.Game.ClientState.JobGauge.Types;
|
||||
using Dalamud.Utility;
|
||||
using ImGuiNET;
|
||||
|
||||
namespace Dalamud.Interface.Internal.Windows.Data;
|
||||
|
||||
/// <summary>
|
||||
/// Widget for displaying job gauge data.
|
||||
/// </summary>
|
||||
internal class GaugeWidget : IDataWindowWidget
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public DataKind DataKind { get; init; } = DataKind.Gauge;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool Ready { get; set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Load()
|
||||
{
|
||||
this.Ready = true;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Draw()
|
||||
{
|
||||
var clientState = Service<ClientState>.Get();
|
||||
var jobGauges = Service<JobGauges>.Get();
|
||||
|
||||
var player = clientState.LocalPlayer;
|
||||
if (player == null)
|
||||
{
|
||||
ImGui.Text("Player is not present");
|
||||
return;
|
||||
}
|
||||
|
||||
var jobID = player.ClassJob.Id;
|
||||
JobGaugeBase? gauge = jobID switch
|
||||
{
|
||||
19 => jobGauges.Get<PLDGauge>(),
|
||||
20 => jobGauges.Get<MNKGauge>(),
|
||||
21 => jobGauges.Get<WARGauge>(),
|
||||
22 => jobGauges.Get<DRGGauge>(),
|
||||
23 => jobGauges.Get<BRDGauge>(),
|
||||
24 => jobGauges.Get<WHMGauge>(),
|
||||
25 => jobGauges.Get<BLMGauge>(),
|
||||
27 => jobGauges.Get<SMNGauge>(),
|
||||
28 => jobGauges.Get<SCHGauge>(),
|
||||
30 => jobGauges.Get<NINGauge>(),
|
||||
31 => jobGauges.Get<MCHGauge>(),
|
||||
32 => jobGauges.Get<DRKGauge>(),
|
||||
33 => jobGauges.Get<ASTGauge>(),
|
||||
34 => jobGauges.Get<SAMGauge>(),
|
||||
35 => jobGauges.Get<RDMGauge>(),
|
||||
37 => jobGauges.Get<GNBGauge>(),
|
||||
38 => jobGauges.Get<DNCGauge>(),
|
||||
39 => jobGauges.Get<RPRGauge>(),
|
||||
40 => jobGauges.Get<SGEGauge>(),
|
||||
_ => null,
|
||||
};
|
||||
|
||||
if (gauge == null)
|
||||
{
|
||||
ImGui.Text("No supported gauge exists for this job.");
|
||||
return;
|
||||
}
|
||||
|
||||
Util.ShowObject(gauge);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,87 @@
|
|||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
using Dalamud.Hooking;
|
||||
using ImGuiNET;
|
||||
using PInvoke;
|
||||
using Serilog;
|
||||
|
||||
namespace Dalamud.Interface.Internal.Windows.Data;
|
||||
|
||||
/// <summary>
|
||||
/// Widget for displaying hook information.
|
||||
/// </summary>
|
||||
internal class HookWidget : IDataWindowWidget
|
||||
{
|
||||
private Hook<MessageBoxWDelegate>? messageBoxMinHook;
|
||||
private bool hookUseMinHook;
|
||||
|
||||
private delegate int MessageBoxWDelegate(
|
||||
IntPtr hWnd,
|
||||
[MarshalAs(UnmanagedType.LPWStr)] string text,
|
||||
[MarshalAs(UnmanagedType.LPWStr)] string caption,
|
||||
NativeFunctions.MessageBoxType type);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public DataKind DataKind { get; init; } = DataKind.Hook;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool Ready { get; set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Load()
|
||||
{
|
||||
this.Ready = true;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Draw()
|
||||
{
|
||||
try
|
||||
{
|
||||
ImGui.Checkbox("Use MinHook", ref this.hookUseMinHook);
|
||||
|
||||
if (ImGui.Button("Create"))
|
||||
this.messageBoxMinHook = Hook<MessageBoxWDelegate>.FromSymbol("User32", "MessageBoxW", this.MessageBoxWDetour, this.hookUseMinHook);
|
||||
|
||||
if (ImGui.Button("Enable"))
|
||||
this.messageBoxMinHook?.Enable();
|
||||
|
||||
if (ImGui.Button("Disable"))
|
||||
this.messageBoxMinHook?.Disable();
|
||||
|
||||
if (ImGui.Button("Call Original"))
|
||||
this.messageBoxMinHook?.Original(IntPtr.Zero, "Hello from .Original", "Hook Test", NativeFunctions.MessageBoxType.Ok);
|
||||
|
||||
if (ImGui.Button("Dispose"))
|
||||
{
|
||||
this.messageBoxMinHook?.Dispose();
|
||||
this.messageBoxMinHook = null;
|
||||
}
|
||||
|
||||
if (ImGui.Button("Test"))
|
||||
_ = NativeFunctions.MessageBoxW(IntPtr.Zero, "Hi", "Hello", NativeFunctions.MessageBoxType.Ok);
|
||||
|
||||
if (this.messageBoxMinHook != null)
|
||||
ImGui.Text("Enabled: " + this.messageBoxMinHook?.IsEnabled);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "MinHook error caught");
|
||||
}
|
||||
}
|
||||
|
||||
private int MessageBoxWDetour(IntPtr hwnd, string text, string caption, NativeFunctions.MessageBoxType type)
|
||||
{
|
||||
Log.Information("[DATAHOOK] {Hwnd} {Text} {Caption} {Type}", hwnd, text, caption, type);
|
||||
|
||||
var result = this.messageBoxMinHook!.Original(hwnd, "Cause Access Violation?", caption, NativeFunctions.MessageBoxType.YesNo);
|
||||
|
||||
if (result == (int)User32.MessageBoxResult.IDYES)
|
||||
{
|
||||
Marshal.ReadByte(IntPtr.Zero);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,74 @@
|
|||
using System;
|
||||
|
||||
using Dalamud.Interface.Internal.Notifications;
|
||||
using Dalamud.Interface.Windowing;
|
||||
using ImGuiNET;
|
||||
|
||||
namespace Dalamud.Interface.Internal.Windows.Data;
|
||||
|
||||
/// <summary>
|
||||
/// Widget for displaying ImGui test.
|
||||
/// </summary>
|
||||
internal class ImGuiWidget : IDataWindowWidget
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public DataKind DataKind { get; init; } = DataKind.ImGui;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool Ready { get; set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Load()
|
||||
{
|
||||
this.Ready = true;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Draw()
|
||||
{
|
||||
var interfaceManager = Service<InterfaceManager>.Get();
|
||||
var notifications = Service<NotificationManager>.Get();
|
||||
|
||||
ImGui.Text("Monitor count: " + ImGui.GetPlatformIO().Monitors.Size);
|
||||
ImGui.Text("OverrideGameCursor: " + interfaceManager.OverrideGameCursor);
|
||||
|
||||
ImGui.Button("THIS IS A BUTTON###hoverTestButton");
|
||||
interfaceManager.OverrideGameCursor = !ImGui.IsItemHovered();
|
||||
|
||||
ImGui.Separator();
|
||||
|
||||
ImGui.TextUnformatted($"WindowSystem.TimeSinceLastAnyFocus: {WindowSystem.TimeSinceLastAnyFocus.TotalMilliseconds:0}ms");
|
||||
|
||||
ImGui.Separator();
|
||||
|
||||
if (ImGui.Button("Add random notification"))
|
||||
{
|
||||
var rand = new Random();
|
||||
|
||||
var title = rand.Next(0, 5) switch
|
||||
{
|
||||
0 => "This is a toast",
|
||||
1 => "Truly, a toast",
|
||||
2 => "I am testing this toast",
|
||||
3 => "I hope this looks right",
|
||||
4 => "Good stuff",
|
||||
5 => "Nice",
|
||||
_ => null,
|
||||
};
|
||||
|
||||
var type = rand.Next(0, 4) switch
|
||||
{
|
||||
0 => NotificationType.Error,
|
||||
1 => NotificationType.Warning,
|
||||
2 => NotificationType.Info,
|
||||
3 => NotificationType.Success,
|
||||
4 => NotificationType.None,
|
||||
_ => NotificationType.None,
|
||||
};
|
||||
|
||||
const string text = "Bla bla bla bla bla bla bla bla bla bla bla.\nBla bla bla bla bla bla bla bla bla bla bla bla bla bla.";
|
||||
|
||||
notifications.AddNotification(text, title, type);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
using Dalamud.Game.ClientState.Keys;
|
||||
using Dalamud.Interface.Colors;
|
||||
using ImGuiNET;
|
||||
|
||||
namespace Dalamud.Interface.Internal.Windows.Data;
|
||||
|
||||
/// <summary>
|
||||
/// Widget for displaying keyboard state.
|
||||
/// </summary>
|
||||
internal class KeyStateWidget : IDataWindowWidget
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public DataKind DataKind { get; init; } = DataKind.KeyState;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool Ready { get; set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Load()
|
||||
{
|
||||
this.Ready = true;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Draw()
|
||||
{
|
||||
var keyState = Service<KeyState>.Get();
|
||||
|
||||
ImGui.Columns(4);
|
||||
|
||||
var i = 0;
|
||||
foreach (var vkCode in keyState.GetValidVirtualKeys())
|
||||
{
|
||||
var code = (int)vkCode;
|
||||
var value = keyState[code];
|
||||
|
||||
ImGui.PushStyleColor(ImGuiCol.Text, value ? ImGuiColors.HealerGreen : ImGuiColors.DPSRed);
|
||||
|
||||
ImGui.Text($"{vkCode} ({code})");
|
||||
|
||||
ImGui.PopStyleColor();
|
||||
|
||||
i++;
|
||||
if (i % 24 == 0)
|
||||
ImGui.NextColumn();
|
||||
}
|
||||
|
||||
ImGui.Columns(1);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,119 @@
|
|||
using System;
|
||||
using System.Numerics;
|
||||
|
||||
using Dalamud.Game.ClientState;
|
||||
using Dalamud.Game.ClientState.Objects;
|
||||
using Dalamud.Game.Gui;
|
||||
using Dalamud.Utility;
|
||||
using ImGuiNET;
|
||||
|
||||
namespace Dalamud.Interface.Internal.Windows.Data;
|
||||
|
||||
/// <summary>
|
||||
/// Widget to display the Object Table.
|
||||
/// </summary>
|
||||
internal class ObjectTableWidget : IDataWindowWidget
|
||||
{
|
||||
private bool resolveGameData;
|
||||
private bool drawCharacters;
|
||||
private float maxCharaDrawDistance = 20.0f;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public DataKind DataKind { get; init; } = DataKind.Object_Table;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool Ready { get; set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Load()
|
||||
{
|
||||
this.Ready = true;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Draw()
|
||||
{
|
||||
ImGui.Checkbox("Resolve GameData", ref this.resolveGameData);
|
||||
|
||||
var chatGui = Service<ChatGui>.Get();
|
||||
var clientState = Service<ClientState>.Get();
|
||||
var gameGui = Service<GameGui>.Get();
|
||||
var objectTable = Service<ObjectTable>.Get();
|
||||
|
||||
var stateString = string.Empty;
|
||||
|
||||
if (clientState.LocalPlayer == null)
|
||||
{
|
||||
ImGui.TextUnformatted("LocalPlayer null.");
|
||||
}
|
||||
else if (clientState.IsPvPExcludingDen)
|
||||
{
|
||||
ImGui.TextUnformatted("Cannot access object table while in PvP.");
|
||||
}
|
||||
else
|
||||
{
|
||||
stateString += $"ObjectTableLen: {objectTable.Length}\n";
|
||||
stateString += $"LocalPlayerName: {clientState.LocalPlayer.Name}\n";
|
||||
stateString += $"CurrentWorldName: {(this.resolveGameData ? clientState.LocalPlayer.CurrentWorld.GameData?.Name : clientState.LocalPlayer.CurrentWorld.Id.ToString())}\n";
|
||||
stateString += $"HomeWorldName: {(this.resolveGameData ? clientState.LocalPlayer.HomeWorld.GameData?.Name : clientState.LocalPlayer.HomeWorld.Id.ToString())}\n";
|
||||
stateString += $"LocalCID: {clientState.LocalContentId:X}\n";
|
||||
stateString += $"LastLinkedItem: {chatGui.LastLinkedItemId}\n";
|
||||
stateString += $"TerritoryType: {clientState.TerritoryType}\n\n";
|
||||
|
||||
ImGui.TextUnformatted(stateString);
|
||||
|
||||
ImGui.Checkbox("Draw characters on screen", ref this.drawCharacters);
|
||||
ImGui.SliderFloat("Draw Distance", ref this.maxCharaDrawDistance, 2f, 40f);
|
||||
|
||||
for (var i = 0; i < objectTable.Length; i++)
|
||||
{
|
||||
var obj = objectTable[i];
|
||||
|
||||
if (obj == null)
|
||||
continue;
|
||||
|
||||
Util.PrintGameObject(obj, i.ToString(), this.resolveGameData);
|
||||
|
||||
if (this.drawCharacters && gameGui.WorldToScreen(obj.Position, out var screenCoords))
|
||||
{
|
||||
// So, while WorldToScreen will return false if the point is off of game client screen, to
|
||||
// to avoid performance issues, we have to manually determine if creating a window would
|
||||
// produce a new viewport, and skip rendering it if so
|
||||
var objectText = $"{obj.Address.ToInt64():X}:{obj.ObjectId:X}[{i}] - {obj.ObjectKind} - {obj.Name}";
|
||||
|
||||
var screenPos = ImGui.GetMainViewport().Pos;
|
||||
var screenSize = ImGui.GetMainViewport().Size;
|
||||
|
||||
var windowSize = ImGui.CalcTextSize(objectText);
|
||||
|
||||
// Add some extra safety padding
|
||||
windowSize.X += ImGui.GetStyle().WindowPadding.X + 10;
|
||||
windowSize.Y += ImGui.GetStyle().WindowPadding.Y + 10;
|
||||
|
||||
if (screenCoords.X + windowSize.X > screenPos.X + screenSize.X ||
|
||||
screenCoords.Y + windowSize.Y > screenPos.Y + screenSize.Y)
|
||||
continue;
|
||||
|
||||
if (obj.YalmDistanceX > this.maxCharaDrawDistance)
|
||||
continue;
|
||||
|
||||
ImGui.SetNextWindowPos(new Vector2(screenCoords.X, screenCoords.Y));
|
||||
|
||||
ImGui.SetNextWindowBgAlpha(Math.Max(1f - (obj.YalmDistanceX / this.maxCharaDrawDistance), 0.2f));
|
||||
if (ImGui.Begin(
|
||||
$"Actor{i}##ActorWindow{i}",
|
||||
ImGuiWindowFlags.NoDecoration |
|
||||
ImGuiWindowFlags.AlwaysAutoResize |
|
||||
ImGuiWindowFlags.NoSavedSettings |
|
||||
ImGuiWindowFlags.NoMove |
|
||||
ImGuiWindowFlags.NoMouseInputs |
|
||||
ImGuiWindowFlags.NoDocking |
|
||||
ImGuiWindowFlags.NoFocusOnAppearing |
|
||||
ImGuiWindowFlags.NoNav))
|
||||
ImGui.Text(objectText);
|
||||
ImGui.End();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,63 @@
|
|||
using Dalamud.Game.ClientState.Party;
|
||||
using Dalamud.Utility;
|
||||
using ImGuiNET;
|
||||
|
||||
namespace Dalamud.Interface.Internal.Windows.Data;
|
||||
|
||||
/// <summary>
|
||||
/// Widget for displaying information about the current party.
|
||||
/// </summary>
|
||||
internal class PartyListWidget : IDataWindowWidget
|
||||
{
|
||||
private bool resolveGameData;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public DataKind DataKind { get; init; } = DataKind.Party_List;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool Ready { get; set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Load()
|
||||
{
|
||||
this.Ready = true;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Draw()
|
||||
{
|
||||
var partyList = Service<PartyList>.Get();
|
||||
|
||||
ImGui.Checkbox("Resolve GameData", ref this.resolveGameData);
|
||||
|
||||
ImGui.Text($"GroupManager: {partyList.GroupManagerAddress.ToInt64():X}");
|
||||
ImGui.Text($"GroupList: {partyList.GroupListAddress.ToInt64():X}");
|
||||
ImGui.Text($"AllianceList: {partyList.AllianceListAddress.ToInt64():X}");
|
||||
|
||||
ImGui.Text($"{partyList.Length} Members");
|
||||
|
||||
for (var i = 0; i < partyList.Length; i++)
|
||||
{
|
||||
var member = partyList[i];
|
||||
if (member == null)
|
||||
{
|
||||
ImGui.Text($"[{i}] was null");
|
||||
continue;
|
||||
}
|
||||
|
||||
ImGui.Text($"[{i}] {member.Address.ToInt64():X} - {member.Name} - {member.GameObject?.ObjectId}");
|
||||
if (this.resolveGameData)
|
||||
{
|
||||
var actor = member.GameObject;
|
||||
if (actor == null)
|
||||
{
|
||||
ImGui.Text("Actor was null");
|
||||
}
|
||||
else
|
||||
{
|
||||
Util.PrintGameObject(actor, "-", this.resolveGameData);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,84 @@
|
|||
using System;
|
||||
|
||||
using Dalamud.Plugin.Ipc;
|
||||
using Dalamud.Plugin.Ipc.Internal;
|
||||
using Dalamud.Utility;
|
||||
using ImGuiNET;
|
||||
using Serilog;
|
||||
|
||||
namespace Dalamud.Interface.Internal.Windows.Data;
|
||||
|
||||
/// <summary>
|
||||
/// Widget for testing plugin IPC systems.
|
||||
/// </summary>
|
||||
internal class PluginIpcWidget : IDataWindowWidget
|
||||
{
|
||||
// IPC
|
||||
private ICallGateProvider<string, string>? ipcPub;
|
||||
private ICallGateSubscriber<string, string>? ipcSub;
|
||||
private string callGateResponse = string.Empty;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public DataKind DataKind { get; init; } = DataKind.Plugin_IPC;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool Ready { get; set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Load()
|
||||
{
|
||||
this.Ready = true;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Draw()
|
||||
{
|
||||
if (this.ipcPub == null)
|
||||
{
|
||||
this.ipcPub = new CallGatePubSub<string, string>("dataDemo1");
|
||||
|
||||
this.ipcPub.RegisterAction(msg =>
|
||||
{
|
||||
Log.Information("Data action was called: {Msg}", msg);
|
||||
});
|
||||
|
||||
this.ipcPub.RegisterFunc(msg =>
|
||||
{
|
||||
Log.Information("Data func was called: {Msg}", msg);
|
||||
return Guid.NewGuid().ToString();
|
||||
});
|
||||
}
|
||||
|
||||
if (this.ipcSub == null)
|
||||
{
|
||||
this.ipcSub = new CallGatePubSub<string, string>("dataDemo1");
|
||||
this.ipcSub.Subscribe(_ =>
|
||||
{
|
||||
Log.Information("PONG1");
|
||||
});
|
||||
this.ipcSub.Subscribe(_ =>
|
||||
{
|
||||
Log.Information("PONG2");
|
||||
});
|
||||
this.ipcSub.Subscribe(_ => throw new Exception("PONG3"));
|
||||
}
|
||||
|
||||
if (ImGui.Button("PING"))
|
||||
{
|
||||
this.ipcPub.SendMessage("PING");
|
||||
}
|
||||
|
||||
if (ImGui.Button("Action"))
|
||||
{
|
||||
this.ipcSub.InvokeAction("button1");
|
||||
}
|
||||
|
||||
if (ImGui.Button("Func"))
|
||||
{
|
||||
this.callGateResponse = this.ipcSub.InvokeFunc("button2");
|
||||
}
|
||||
|
||||
if (!this.callGateResponse.IsNullOrEmpty())
|
||||
ImGui.Text($"Response: {this.callGateResponse}");
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
using Dalamud.Game.Text;
|
||||
using ImGuiNET;
|
||||
|
||||
namespace Dalamud.Interface.Internal.Windows.Data;
|
||||
|
||||
/// <summary>
|
||||
/// Widget for displaying test data for SE Font Symbols.
|
||||
/// </summary>
|
||||
internal class SeFontTestWidget : IDataWindowWidget
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public DataKind DataKind { get; init; } = DataKind.SE_Font_Test;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool Ready { get; set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Load()
|
||||
{
|
||||
this.Ready = true;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Draw()
|
||||
{
|
||||
var specialChars = string.Empty;
|
||||
|
||||
for (var i = 0xE020; i <= 0xE0DB; i++)
|
||||
specialChars += $"0x{i:X} - {(SeIconChar)i} - {(char)i}\n";
|
||||
|
||||
ImGui.TextUnformatted(specialChars);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
using Dalamud.Data;
|
||||
using ImGuiNET;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Dalamud.Interface.Internal.Windows.Data;
|
||||
|
||||
/// <summary>
|
||||
/// Widget to display the currently set server opcodes.
|
||||
/// </summary>
|
||||
internal class ServerOpcodeWidget : IDataWindowWidget
|
||||
{
|
||||
private string? serverOpString;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public DataKind DataKind { get; init; } = DataKind.Server_OpCode;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool Ready { get; set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Load()
|
||||
{
|
||||
var dataManager = Service<DataManager>.Get();
|
||||
|
||||
if (dataManager.IsDataReady)
|
||||
{
|
||||
this.serverOpString = JsonConvert.SerializeObject(dataManager.ServerOpCodes, Formatting.Indented);
|
||||
this.Ready = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Draw()
|
||||
{
|
||||
ImGui.TextUnformatted(this.serverOpString ?? "serverOpString not initialized");
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
using ImGuiNET;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Dalamud.Interface.Internal.Windows.Data;
|
||||
|
||||
/// <summary>
|
||||
/// Widget for displaying start info.
|
||||
/// </summary>
|
||||
internal class StartInfoWidget : IDataWindowWidget
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public DataKind DataKind { get; init; } = DataKind.StartInfo;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool Ready { get; set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Load()
|
||||
{
|
||||
this.Ready = true;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Draw()
|
||||
{
|
||||
var startInfo = Service<DalamudStartInfo>.Get();
|
||||
|
||||
ImGui.Text(JsonConvert.SerializeObject(startInfo, Formatting.Indented));
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,88 @@
|
|||
using Dalamud.Game.ClientState;
|
||||
using Dalamud.Game.ClientState.Objects;
|
||||
using Dalamud.Utility;
|
||||
using ImGuiNET;
|
||||
|
||||
namespace Dalamud.Interface.Internal.Windows.Data;
|
||||
|
||||
/// <summary>
|
||||
/// Widget for displaying target info.
|
||||
/// </summary>
|
||||
internal class TargetWidget : IDataWindowWidget
|
||||
{
|
||||
private bool resolveGameData;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public DataKind DataKind { get; init; } = DataKind.Target;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool Ready { get; set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Load()
|
||||
{
|
||||
this.Ready = true;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Draw()
|
||||
{
|
||||
ImGui.Checkbox("Resolve GameData", ref this.resolveGameData);
|
||||
|
||||
var clientState = Service<ClientState>.Get();
|
||||
var targetMgr = Service<TargetManager>.Get();
|
||||
|
||||
if (targetMgr.Target != null)
|
||||
{
|
||||
Util.PrintGameObject(targetMgr.Target, "CurrentTarget", this.resolveGameData);
|
||||
|
||||
ImGui.Text("Target");
|
||||
Util.ShowGameObjectStruct(targetMgr.Target);
|
||||
|
||||
var tot = targetMgr.Target.TargetObject;
|
||||
if (tot != null)
|
||||
{
|
||||
ImGuiHelpers.ScaledDummy(10);
|
||||
|
||||
ImGui.Separator();
|
||||
ImGui.Text("ToT");
|
||||
Util.ShowGameObjectStruct(tot);
|
||||
}
|
||||
|
||||
ImGuiHelpers.ScaledDummy(10);
|
||||
}
|
||||
|
||||
if (targetMgr.FocusTarget != null)
|
||||
Util.PrintGameObject(targetMgr.FocusTarget, "FocusTarget", this.resolveGameData);
|
||||
|
||||
if (targetMgr.MouseOverTarget != null)
|
||||
Util.PrintGameObject(targetMgr.MouseOverTarget, "MouseOverTarget", this.resolveGameData);
|
||||
|
||||
if (targetMgr.PreviousTarget != null)
|
||||
Util.PrintGameObject(targetMgr.PreviousTarget, "PreviousTarget", this.resolveGameData);
|
||||
|
||||
if (targetMgr.SoftTarget != null)
|
||||
Util.PrintGameObject(targetMgr.SoftTarget, "SoftTarget", this.resolveGameData);
|
||||
|
||||
if (ImGui.Button("Clear CT"))
|
||||
targetMgr.ClearTarget();
|
||||
|
||||
if (ImGui.Button("Clear FT"))
|
||||
targetMgr.ClearFocusTarget();
|
||||
|
||||
var localPlayer = clientState.LocalPlayer;
|
||||
|
||||
if (localPlayer != null)
|
||||
{
|
||||
if (ImGui.Button("Set CT"))
|
||||
targetMgr.SetTarget(localPlayer);
|
||||
|
||||
if (ImGui.Button("Set FT"))
|
||||
targetMgr.SetFocusTarget(localPlayer);
|
||||
}
|
||||
else
|
||||
{
|
||||
ImGui.Text("LocalPlayer is null.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,256 @@
|
|||
// ReSharper disable MethodSupportsCancellation // Using alternative method of cancelling tasks by throwing exceptions.
|
||||
using System;
|
||||
using System.Reflection;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Dalamud.Game;
|
||||
using Dalamud.Interface.Colors;
|
||||
using Dalamud.Logging.Internal;
|
||||
using ImGuiNET;
|
||||
using Serilog;
|
||||
|
||||
namespace Dalamud.Interface.Internal.Windows.Data;
|
||||
|
||||
/// <summary>
|
||||
/// Widget for displaying task scheduler test.
|
||||
/// </summary>
|
||||
internal class TaskSchedulerWidget : IDataWindowWidget
|
||||
{
|
||||
private CancellationTokenSource taskSchedulerCancelSource = new();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public DataKind DataKind { get; init; } = DataKind.TaskSched;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool Ready { get; set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Load()
|
||||
{
|
||||
this.Ready = true;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Draw()
|
||||
{
|
||||
if (ImGui.Button("Clear list"))
|
||||
{
|
||||
TaskTracker.Clear();
|
||||
}
|
||||
|
||||
ImGui.SameLine();
|
||||
ImGuiHelpers.ScaledDummy(10);
|
||||
ImGui.SameLine();
|
||||
|
||||
if (ImGui.Button("Cancel using CancellationTokenSource"))
|
||||
{
|
||||
this.taskSchedulerCancelSource.Cancel();
|
||||
this.taskSchedulerCancelSource = new();
|
||||
}
|
||||
|
||||
ImGui.Text("Run in any thread: ");
|
||||
ImGui.SameLine();
|
||||
|
||||
if (ImGui.Button("Short Task.Run"))
|
||||
{
|
||||
Task.Run(() => { Thread.Sleep(500); });
|
||||
}
|
||||
|
||||
ImGui.SameLine();
|
||||
|
||||
if (ImGui.Button("Task in task(Delay)"))
|
||||
{
|
||||
var token = this.taskSchedulerCancelSource.Token;
|
||||
Task.Run(async () => await this.TestTaskInTaskDelay(token), token);
|
||||
}
|
||||
|
||||
ImGui.SameLine();
|
||||
|
||||
if (ImGui.Button("Task in task(Sleep)"))
|
||||
{
|
||||
Task.Run(async () => await this.TestTaskInTaskSleep());
|
||||
}
|
||||
|
||||
ImGui.SameLine();
|
||||
|
||||
if (ImGui.Button("Faulting task"))
|
||||
{
|
||||
Task.Run(() =>
|
||||
{
|
||||
Thread.Sleep(200);
|
||||
|
||||
string a = null;
|
||||
a.Contains("dalamud"); // Intentional null exception.
|
||||
});
|
||||
}
|
||||
|
||||
ImGui.Text("Run in Framework.Update: ");
|
||||
ImGui.SameLine();
|
||||
|
||||
if (ImGui.Button("ASAP"))
|
||||
{
|
||||
Task.Run(async () => await Service<Framework>.Get().RunOnTick(() => { }, cancellationToken: this.taskSchedulerCancelSource.Token));
|
||||
}
|
||||
|
||||
ImGui.SameLine();
|
||||
|
||||
if (ImGui.Button("In 1s"))
|
||||
{
|
||||
Task.Run(async () => await Service<Framework>.Get().RunOnTick(() => { }, cancellationToken: this.taskSchedulerCancelSource.Token, delay: TimeSpan.FromSeconds(1)));
|
||||
}
|
||||
|
||||
ImGui.SameLine();
|
||||
|
||||
if (ImGui.Button("In 60f"))
|
||||
{
|
||||
Task.Run(async () => await Service<Framework>.Get().RunOnTick(() => { }, cancellationToken: this.taskSchedulerCancelSource.Token, delayTicks: 60));
|
||||
}
|
||||
|
||||
ImGui.SameLine();
|
||||
|
||||
if (ImGui.Button("Error in 1s"))
|
||||
{
|
||||
Task.Run(async () => await Service<Framework>.Get().RunOnTick(() => throw new Exception("Test Exception"), cancellationToken: this.taskSchedulerCancelSource.Token, delay: TimeSpan.FromSeconds(1)));
|
||||
}
|
||||
|
||||
ImGui.SameLine();
|
||||
|
||||
if (ImGui.Button("As long as it's in Framework Thread"))
|
||||
{
|
||||
Task.Run(async () => await Service<Framework>.Get().RunOnFrameworkThread(() => { Log.Information("Task dispatched from non-framework.update thread"); }));
|
||||
Service<Framework>.Get().RunOnFrameworkThread(() => { Log.Information("Task dispatched from framework.update thread"); }).Wait();
|
||||
}
|
||||
|
||||
if (ImGui.Button("Drown in tasks"))
|
||||
{
|
||||
var token = this.taskSchedulerCancelSource.Token;
|
||||
Task.Run(
|
||||
() =>
|
||||
{
|
||||
for (var i = 0; i < 100; i++)
|
||||
{
|
||||
token.ThrowIfCancellationRequested();
|
||||
Task.Run(
|
||||
() =>
|
||||
{
|
||||
for (var j = 0; j < 100; j++)
|
||||
{
|
||||
token.ThrowIfCancellationRequested();
|
||||
Task.Run(
|
||||
() =>
|
||||
{
|
||||
for (var k = 0; k < 100; k++)
|
||||
{
|
||||
token.ThrowIfCancellationRequested();
|
||||
Task.Run(
|
||||
() =>
|
||||
{
|
||||
for (var l = 0; l < 100; l++)
|
||||
{
|
||||
token.ThrowIfCancellationRequested();
|
||||
Task.Run(
|
||||
async () =>
|
||||
{
|
||||
for (var m = 0; m < 100; m++)
|
||||
{
|
||||
token.ThrowIfCancellationRequested();
|
||||
await Task.Delay(1, token);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
ImGui.SameLine();
|
||||
|
||||
ImGuiHelpers.ScaledDummy(20);
|
||||
|
||||
// Needed to init the task tracker, if we're not on a debug build
|
||||
Service<TaskTracker>.Get().Enable();
|
||||
|
||||
for (var i = 0; i < TaskTracker.Tasks.Count; i++)
|
||||
{
|
||||
var task = TaskTracker.Tasks[i];
|
||||
var subTime = DateTime.Now;
|
||||
if (task.Task == null)
|
||||
subTime = task.FinishTime;
|
||||
|
||||
switch (task.Status)
|
||||
{
|
||||
case TaskStatus.Created:
|
||||
case TaskStatus.WaitingForActivation:
|
||||
case TaskStatus.WaitingToRun:
|
||||
ImGui.PushStyleColor(ImGuiCol.Header, ImGuiColors.DalamudGrey);
|
||||
break;
|
||||
case TaskStatus.Running:
|
||||
case TaskStatus.WaitingForChildrenToComplete:
|
||||
ImGui.PushStyleColor(ImGuiCol.Header, ImGuiColors.ParsedBlue);
|
||||
break;
|
||||
case TaskStatus.RanToCompletion:
|
||||
ImGui.PushStyleColor(ImGuiCol.Header, ImGuiColors.ParsedGreen);
|
||||
break;
|
||||
case TaskStatus.Canceled:
|
||||
case TaskStatus.Faulted:
|
||||
ImGui.PushStyleColor(ImGuiCol.Header, ImGuiColors.DalamudRed);
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
|
||||
if (ImGui.CollapsingHeader($"#{task.Id} - {task.Status} {(subTime - task.StartTime).TotalMilliseconds}ms###task{i}"))
|
||||
{
|
||||
task.IsBeingViewed = true;
|
||||
|
||||
if (ImGui.Button("CANCEL (May not work)"))
|
||||
{
|
||||
try
|
||||
{
|
||||
var cancelFunc =
|
||||
typeof(Task).GetMethod("InternalCancel", BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
cancelFunc?.Invoke(task, null);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Could not cancel task");
|
||||
}
|
||||
}
|
||||
|
||||
ImGuiHelpers.ScaledDummy(10);
|
||||
|
||||
ImGui.TextUnformatted(task.StackTrace?.ToString());
|
||||
|
||||
if (task.Exception != null)
|
||||
{
|
||||
ImGuiHelpers.ScaledDummy(15);
|
||||
ImGui.TextColored(ImGuiColors.DalamudRed, "EXCEPTION:");
|
||||
ImGui.TextUnformatted(task.Exception.ToString());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
task.IsBeingViewed = false;
|
||||
}
|
||||
|
||||
ImGui.PopStyleColor(1);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task TestTaskInTaskDelay(CancellationToken token)
|
||||
{
|
||||
await Task.Delay(5000, token);
|
||||
}
|
||||
|
||||
#pragma warning disable 1998
|
||||
private async Task TestTaskInTaskSleep()
|
||||
#pragma warning restore 1998
|
||||
{
|
||||
Thread.Sleep(5000);
|
||||
}
|
||||
}
|
||||
69
Dalamud/Interface/Internal/Windows/Data/Widgets/TexWidget.cs
Normal file
69
Dalamud/Interface/Internal/Windows/Data/Widgets/TexWidget.cs
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
using System;
|
||||
using System.Numerics;
|
||||
|
||||
using Dalamud.Data;
|
||||
using Dalamud.Utility;
|
||||
using ImGuiNET;
|
||||
using ImGuiScene;
|
||||
using Serilog;
|
||||
|
||||
namespace Dalamud.Interface.Internal.Windows.Data;
|
||||
|
||||
/// <summary>
|
||||
/// Widget for displaying texture test.
|
||||
/// </summary>
|
||||
internal class TexWidget : IDataWindowWidget
|
||||
{
|
||||
private string inputTexPath = string.Empty;
|
||||
private TextureWrap? debugTex;
|
||||
private Vector2 inputTexUv0 = Vector2.Zero;
|
||||
private Vector2 inputTexUv1 = Vector2.One;
|
||||
private Vector4 inputTintCol = Vector4.One;
|
||||
private Vector2 inputTexScale = Vector2.Zero;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public DataKind DataKind { get; init; } = DataKind.Tex;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool Ready { get; set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Load()
|
||||
{
|
||||
this.Ready = true;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Draw()
|
||||
{
|
||||
var dataManager = Service<DataManager>.Get();
|
||||
|
||||
ImGui.InputText("Tex Path", ref this.inputTexPath, 255);
|
||||
ImGui.InputFloat2("UV0", ref this.inputTexUv0);
|
||||
ImGui.InputFloat2("UV1", ref this.inputTexUv1);
|
||||
ImGui.InputFloat4("Tint", ref this.inputTintCol);
|
||||
ImGui.InputFloat2("Scale", ref this.inputTexScale);
|
||||
|
||||
if (ImGui.Button("Load Tex"))
|
||||
{
|
||||
try
|
||||
{
|
||||
this.debugTex = dataManager.GetImGuiTexture(this.inputTexPath);
|
||||
this.inputTexScale = new Vector2(this.debugTex?.Width ?? 0, this.debugTex?.Height ?? 0);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Could not load tex");
|
||||
}
|
||||
}
|
||||
|
||||
ImGuiHelpers.ScaledDummy(10);
|
||||
|
||||
if (this.debugTex != null)
|
||||
{
|
||||
ImGui.Image(this.debugTex.ImGuiHandle, this.inputTexScale, this.inputTexUv0, this.inputTexUv1, this.inputTintCol);
|
||||
ImGuiHelpers.ScaledDummy(5);
|
||||
Util.ShowObject(this.debugTex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,74 @@
|
|||
using System.Numerics;
|
||||
|
||||
using Dalamud.Game.Gui.Toast;
|
||||
using ImGuiNET;
|
||||
|
||||
namespace Dalamud.Interface.Internal.Windows.Data;
|
||||
|
||||
/// <summary>
|
||||
/// Widget for displaying toast test.
|
||||
/// </summary>
|
||||
internal class ToastWidget : IDataWindowWidget
|
||||
{
|
||||
private string inputTextToast = string.Empty;
|
||||
private int toastPosition;
|
||||
private int toastSpeed;
|
||||
private int questToastPosition;
|
||||
private bool questToastSound;
|
||||
private int questToastIconId;
|
||||
private bool questToastCheckmark;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public DataKind DataKind { get; init; } = DataKind.Toast;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool Ready { get; set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Load()
|
||||
{
|
||||
this.Ready = true;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Draw()
|
||||
{
|
||||
var toastGui = Service<ToastGui>.Get();
|
||||
|
||||
ImGui.InputText("Toast text", ref this.inputTextToast, 200);
|
||||
|
||||
ImGui.Combo("Toast Position", ref this.toastPosition, new[] { "Bottom", "Top", }, 2);
|
||||
ImGui.Combo("Toast Speed", ref this.toastSpeed, new[] { "Slow", "Fast", }, 2);
|
||||
ImGui.Combo("Quest Toast Position", ref this.questToastPosition, new[] { "Centre", "Right", "Left" }, 3);
|
||||
ImGui.Checkbox("Quest Checkmark", ref this.questToastCheckmark);
|
||||
ImGui.Checkbox("Quest Play Sound", ref this.questToastSound);
|
||||
ImGui.InputInt("Quest Icon ID", ref this.questToastIconId);
|
||||
|
||||
ImGuiHelpers.ScaledDummy(new Vector2(10, 10));
|
||||
|
||||
if (ImGui.Button("Show toast"))
|
||||
{
|
||||
toastGui.ShowNormal(this.inputTextToast, new ToastOptions
|
||||
{
|
||||
Position = (ToastPosition)this.toastPosition,
|
||||
Speed = (ToastSpeed)this.toastSpeed,
|
||||
});
|
||||
}
|
||||
|
||||
if (ImGui.Button("Show Quest toast"))
|
||||
{
|
||||
toastGui.ShowQuest(this.inputTextToast, new QuestToastOptions
|
||||
{
|
||||
Position = (QuestToastPosition)this.questToastPosition,
|
||||
DisplayCheckmark = this.questToastCheckmark,
|
||||
IconId = (uint)this.questToastIconId,
|
||||
PlaySound = this.questToastSound,
|
||||
});
|
||||
}
|
||||
|
||||
if (ImGui.Button("Show Error toast"))
|
||||
{
|
||||
toastGui.ShowError(this.inputTextToast);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
using System.Numerics;
|
||||
|
||||
using Dalamud.Data;
|
||||
using ImGuiNET;
|
||||
using Lumina.Excel.GeneratedSheets;
|
||||
|
||||
namespace Dalamud.Interface.Internal.Windows.Data;
|
||||
|
||||
/// <summary>
|
||||
/// Widget for displaying all UI Colors from Lumina.
|
||||
/// </summary>
|
||||
internal class UIColorWidget : IDataWindowWidget
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public DataKind DataKind { get; init; } = DataKind.UIColor;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool Ready { get; set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Load()
|
||||
{
|
||||
this.Ready = true;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Draw()
|
||||
{
|
||||
var colorSheet = Service<DataManager>.Get().GetExcelSheet<UIColor>();
|
||||
if (colorSheet is null) return;
|
||||
|
||||
foreach (var color in colorSheet)
|
||||
{
|
||||
this.DrawUiColor(color);
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawUiColor(UIColor color)
|
||||
{
|
||||
ImGui.Text($"[{color.RowId:D3}] ");
|
||||
ImGui.SameLine();
|
||||
ImGui.TextColored(this.ConvertToVector4(color.Unknown2), $"Unknown2 ");
|
||||
ImGui.SameLine();
|
||||
ImGui.TextColored(this.ConvertToVector4(color.UIForeground), "UIForeground ");
|
||||
ImGui.SameLine();
|
||||
ImGui.TextColored(this.ConvertToVector4(color.Unknown3), "Unknown3 ");
|
||||
ImGui.SameLine();
|
||||
ImGui.TextColored(this.ConvertToVector4(color.UIGlow), "UIGlow");
|
||||
}
|
||||
|
||||
private Vector4 ConvertToVector4(uint color)
|
||||
{
|
||||
var r = (byte)(color >> 24);
|
||||
var g = (byte)(color >> 16);
|
||||
var b = (byte)(color >> 8);
|
||||
var a = (byte)color;
|
||||
|
||||
return new Vector4(r / 255.0f, g / 255.0f, b / 255.0f, a / 255.0f);
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -392,7 +392,8 @@ internal class PluginInstallerWindow : Window, IDisposable
|
|||
var windowSize = ImGui.GetWindowSize();
|
||||
var titleHeight = ImGui.GetFontSize() + (ImGui.GetStyle().FramePadding.Y * 2);
|
||||
|
||||
if (ImGui.BeginChild("###installerLoadingFrame", new Vector2(-1, -1), false))
|
||||
using var loadingChild = ImRaii.Child("###installerLoadingFrame", new Vector2(-1, -1), false);
|
||||
if (loadingChild)
|
||||
{
|
||||
ImGui.GetWindowDrawList().PushClipRectFullScreen();
|
||||
ImGui.GetWindowDrawList().AddRectFilled(
|
||||
|
|
@ -480,8 +481,6 @@ internal class PluginInstallerWindow : Window, IDisposable
|
|||
ImGuiHelpers.CenteredText("One of your plugins may be blocking the installer.");
|
||||
ImGui.PopStyleColor();
|
||||
}
|
||||
|
||||
ImGui.EndChild();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1110,30 +1109,41 @@ internal class PluginInstallerWindow : Window, IDisposable
|
|||
|
||||
var useContentWidth = ImGui.GetContentRegionAvail().X;
|
||||
|
||||
if (ImGui.BeginChild("InstallerCategories", new Vector2(useContentWidth, useContentHeight * ImGuiHelpers.GlobalScale)))
|
||||
using var installerMainChild = ImRaii.Child("InstallerCategories", new Vector2(useContentWidth, useContentHeight * ImGuiHelpers.GlobalScale));
|
||||
if (installerMainChild)
|
||||
{
|
||||
ImGui.PushStyleVar(ImGuiStyleVar.CellPadding, ImGuiHelpers.ScaledVector2(5, 0));
|
||||
if (ImGui.BeginTable("##InstallerCategoriesCont", 2, ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.Resizable | ImGuiTableFlags.BordersInnerV))
|
||||
using var style = ImRaii.PushStyle(ImGuiStyleVar.CellPadding, ImGuiHelpers.ScaledVector2(5, 0));
|
||||
|
||||
try
|
||||
{
|
||||
ImGui.TableSetupColumn("##InstallerCategoriesSelector", ImGuiTableColumnFlags.WidthFixed, useMenuWidth * ImGuiHelpers.GlobalScale);
|
||||
ImGui.TableSetupColumn("##InstallerCategoriesBody", ImGuiTableColumnFlags.WidthStretch);
|
||||
ImGui.TableNextRow();
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
this.DrawPluginCategorySelectors();
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
if (ImGui.BeginChild("ScrollingPlugins", new Vector2(-1, 0), false, ImGuiWindowFlags.NoBackground))
|
||||
using (var categoriesChild = ImRaii.Child("InstallerCategoriesSelector", new Vector2(useMenuWidth * ImGuiHelpers.GlobalScale, -1), false))
|
||||
{
|
||||
this.DrawPluginCategoryContent();
|
||||
if (categoriesChild)
|
||||
{
|
||||
this.DrawPluginCategorySelectors();
|
||||
}
|
||||
}
|
||||
|
||||
ImGui.EndChild();
|
||||
ImGui.EndTable();
|
||||
}
|
||||
ImGui.SameLine();
|
||||
|
||||
ImGui.PopStyleVar();
|
||||
ImGui.EndChild();
|
||||
using var scrollingChild =
|
||||
ImRaii.Child("ScrollingPlugins", new Vector2(-1, -1), false, ImGuiWindowFlags.NoBackground);
|
||||
if (scrollingChild)
|
||||
{
|
||||
try
|
||||
{
|
||||
this.DrawPluginCategoryContent();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Could not draw category content");
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Could not draw plugin categories");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -2174,7 +2184,10 @@ internal class PluginInstallerWindow : Window, IDisposable
|
|||
if (plugin.IsLoaded)
|
||||
{
|
||||
var commands = commandManager.Commands
|
||||
.Where(cInfo => cInfo.Value.ShowInHelp && cInfo.Value.LoaderAssemblyName == plugin.Manifest.InternalName)
|
||||
.Where(cInfo =>
|
||||
cInfo.Value != null &&
|
||||
cInfo.Value.ShowInHelp &&
|
||||
cInfo.Value.LoaderAssemblyName == plugin.Manifest.InternalName)
|
||||
.ToArray();
|
||||
|
||||
if (commands.Any())
|
||||
|
|
@ -2350,7 +2363,7 @@ internal class PluginInstallerWindow : Window, IDisposable
|
|||
var isLoadedAndUnloadable = plugin.State == PluginState.Loaded ||
|
||||
plugin.State == PluginState.DependencyResolutionFailed;
|
||||
|
||||
StyleModelV1.DalamudStandard.Push();
|
||||
//StyleModelV1.DalamudStandard.Push();
|
||||
|
||||
var profileChooserPopupName = $"###pluginProfileChooser{plugin.Manifest.InternalName}";
|
||||
if (ImGui.BeginPopup(profileChooserPopupName))
|
||||
|
|
@ -2364,12 +2377,12 @@ internal class PluginInstallerWindow : Window, IDisposable
|
|||
{
|
||||
if (inProfile)
|
||||
{
|
||||
Task.Run(() => profile.AddOrUpdate(plugin.Manifest.InternalName, true))
|
||||
Task.Run(() => profile.AddOrUpdateAsync(plugin.Manifest.InternalName, true))
|
||||
.ContinueWith(this.DisplayErrorContinuation, Locs.Profiles_CouldNotAdd);
|
||||
}
|
||||
else
|
||||
{
|
||||
Task.Run(() => profile.Remove(plugin.Manifest.InternalName))
|
||||
Task.Run(() => profile.RemoveAsync(plugin.Manifest.InternalName))
|
||||
.ContinueWith(this.DisplayErrorContinuation, Locs.Profiles_CouldNotRemove);
|
||||
}
|
||||
}
|
||||
|
|
@ -2388,14 +2401,17 @@ internal class PluginInstallerWindow : Window, IDisposable
|
|||
|
||||
if (ImGuiComponents.IconButton(FontAwesomeIcon.Times))
|
||||
{
|
||||
profileManager.DefaultProfile.AddOrUpdate(plugin.Manifest.InternalName, plugin.IsLoaded, false);
|
||||
// TODO: Work this out
|
||||
Task.Run(() => profileManager.DefaultProfile.AddOrUpdateAsync(plugin.Manifest.InternalName, plugin.IsLoaded, false))
|
||||
.GetAwaiter().GetResult();
|
||||
foreach (var profile in profileManager.Profiles.Where(x => !x.IsDefaultProfile && x.Plugins.Any(y => y.InternalName == plugin.Manifest.InternalName)))
|
||||
{
|
||||
profile.Remove(plugin.Manifest.InternalName, false);
|
||||
Task.Run(() => profile.RemoveAsync(plugin.Manifest.InternalName, false))
|
||||
.GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
// TODO error handling
|
||||
Task.Run(() => profileManager.ApplyAllWantStates());
|
||||
Task.Run(profileManager.ApplyAllWantStatesAsync)
|
||||
.ContinueWith(this.DisplayErrorContinuation, Locs.ErrorModal_ProfileApplyFail);
|
||||
}
|
||||
|
||||
ImGui.SameLine();
|
||||
|
|
@ -2445,7 +2461,9 @@ internal class PluginInstallerWindow : Window, IDisposable
|
|||
return;
|
||||
}
|
||||
|
||||
profileManager.DefaultProfile.AddOrUpdate(plugin.Manifest.InternalName, false, false);
|
||||
// TODO: Work this out
|
||||
Task.Run(() => profileManager.DefaultProfile.AddOrUpdateAsync(plugin.Manifest.InternalName, false, false))
|
||||
.GetAwaiter().GetResult();
|
||||
this.enableDisableStatus = OperationStatus.Complete;
|
||||
|
||||
notifications.AddNotification(Locs.Notifications_PluginDisabled(plugin.Manifest.Name), Locs.Notifications_PluginDisabledTitle, NotificationType.Success);
|
||||
|
|
@ -2463,7 +2481,9 @@ internal class PluginInstallerWindow : Window, IDisposable
|
|||
plugin.ReloadManifest();
|
||||
}
|
||||
|
||||
profileManager.DefaultProfile.AddOrUpdate(plugin.Manifest.InternalName, true, false);
|
||||
// TODO: Work this out
|
||||
Task.Run(() => profileManager.DefaultProfile.AddOrUpdateAsync(plugin.Manifest.InternalName, true, false))
|
||||
.GetAwaiter().GetResult();
|
||||
|
||||
var loadTask = Task.Run(() => plugin.LoadAsync(PluginLoadReason.Installer))
|
||||
.ContinueWith(
|
||||
|
|
@ -2506,7 +2526,7 @@ internal class PluginInstallerWindow : Window, IDisposable
|
|||
}
|
||||
}
|
||||
|
||||
StyleModelV1.DalamudStandard.Pop();
|
||||
//StyleModelV1.DalamudStandard.Pop();
|
||||
|
||||
ImGui.SameLine();
|
||||
ImGuiHelpers.ScaledDummy(15, 0);
|
||||
|
|
@ -2898,6 +2918,7 @@ internal class PluginInstallerWindow : Window, IDisposable
|
|||
private void OnInstalledPluginsChanged()
|
||||
{
|
||||
var pluginManager = Service<PluginManager>.Get();
|
||||
using var pmLock = pluginManager.GetSyncScope();
|
||||
|
||||
lock (this.listLock)
|
||||
{
|
||||
|
|
@ -3278,6 +3299,8 @@ internal class PluginInstallerWindow : Window, IDisposable
|
|||
|
||||
public static string ErrorModal_UpdaterFatal => Loc.Localize("InstallerUpdaterFatal", "Failed to update plugins.\nPlease restart your game and try again. If this error occurs again, please complain.");
|
||||
|
||||
public static string ErrorModal_ProfileApplyFail => Loc.Localize("InstallerProfileApplyFail", "Failed to process collections.\nPlease restart your game and try again. If this error occurs again, please complain.");
|
||||
|
||||
public static string ErrorModal_UpdaterFail(int failCount) => Loc.Localize("InstallerUpdaterFail", "Failed to update {0} plugins.\nPlease restart your game and try again. If this error occurs again, please complain.").Format(failCount);
|
||||
|
||||
public static string ErrorModal_UpdaterFailPartial(int successCount, int failCount) => Loc.Localize("InstallerUpdaterFailPartial", "Updated {0} plugins, failed to update {1}.\nPlease restart your game and try again. If this error occurs again, please complain.").Format(successCount, failCount);
|
||||
|
|
|
|||
|
|
@ -107,10 +107,12 @@ internal class ProfileManagerWidget
|
|||
|
||||
var windowSize = ImGui.GetWindowSize();
|
||||
|
||||
if (ImGui.BeginChild("###profileChooserScrolling"))
|
||||
using var profileChooserChild = ImRaii.Child("###profileChooserScrolling");
|
||||
if (profileChooserChild)
|
||||
{
|
||||
Guid? toCloneGuid = null;
|
||||
|
||||
using var syncScope = profman.GetSyncScope();
|
||||
foreach (var profile in profman.Profiles)
|
||||
{
|
||||
if (profile.IsDefaultProfile)
|
||||
|
|
@ -119,7 +121,7 @@ internal class ProfileManagerWidget
|
|||
var isEnabled = profile.IsEnabled;
|
||||
if (ImGuiComponents.ToggleButton($"###toggleButton{profile.Guid}", ref isEnabled))
|
||||
{
|
||||
Task.Run(() => profile.SetState(isEnabled))
|
||||
Task.Run(() => profile.SetStateAsync(isEnabled))
|
||||
.ContinueWith(this.installer.DisplayErrorContinuation, Locs.ErrorCouldNotChangeState);
|
||||
}
|
||||
|
||||
|
|
@ -179,8 +181,6 @@ internal class ProfileManagerWidget
|
|||
ImGuiHelpers.CenteredText(Locs.AddProfileHint);
|
||||
ImGui.PopStyleColor();
|
||||
}
|
||||
|
||||
ImGui.EndChild();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -206,6 +206,7 @@ internal class ProfileManagerWidget
|
|||
}
|
||||
|
||||
const string addPluginToProfilePopup = "###addPluginToProfile";
|
||||
var addPluginToProfilePopupId = ImGui.GetID(addPluginToProfilePopup);
|
||||
using (var popup = ImRaii.Popup(addPluginToProfilePopup))
|
||||
{
|
||||
if (popup.Success)
|
||||
|
|
@ -228,9 +229,7 @@ internal class ProfileManagerWidget
|
|||
|
||||
if (ImGui.Selectable($"{plugin.Manifest.Name}###selector{plugin.Manifest.InternalName}"))
|
||||
{
|
||||
// TODO this sucks
|
||||
profile.AddOrUpdate(plugin.Manifest.InternalName, true, false);
|
||||
Task.Run(() => profman.ApplyAllWantStates())
|
||||
Task.Run(() => profile.AddOrUpdateAsync(plugin.Manifest.InternalName, true, false))
|
||||
.ContinueWith(this.installer.DisplayErrorContinuation, Locs.ErrorCouldNotChangeState);
|
||||
}
|
||||
}
|
||||
|
|
@ -273,8 +272,12 @@ internal class ProfileManagerWidget
|
|||
this.Reset();
|
||||
|
||||
// DeleteProfile() is sync, it doesn't apply and we are modifying the plugins collection. Will throw below when iterating
|
||||
profman.DeleteProfile(profile);
|
||||
Task.Run(() => profman.ApplyAllWantStates())
|
||||
// TODO: DeleteProfileAsync should probably apply as well
|
||||
Task.Run(async () =>
|
||||
{
|
||||
await profman.DeleteProfileAsync(profile);
|
||||
await profman.ApplyAllWantStatesAsync();
|
||||
})
|
||||
.ContinueWith(t =>
|
||||
{
|
||||
this.installer.DisplayErrorContinuation(t, Locs.ErrorCouldNotChangeState);
|
||||
|
|
@ -300,7 +303,7 @@ internal class ProfileManagerWidget
|
|||
var isEnabled = profile.IsEnabled;
|
||||
if (ImGuiComponents.ToggleButton($"###toggleButton{profile.Guid}", ref isEnabled))
|
||||
{
|
||||
Task.Run(() => profile.SetState(isEnabled))
|
||||
Task.Run(() => profile.SetStateAsync(isEnabled))
|
||||
.ContinueWith(this.installer.DisplayErrorContinuation, Locs.ErrorCouldNotChangeState);
|
||||
}
|
||||
|
||||
|
|
@ -322,12 +325,14 @@ internal class ProfileManagerWidget
|
|||
ImGui.Separator();
|
||||
var wantPluginAddPopup = false;
|
||||
|
||||
if (ImGui.BeginChild("###profileEditorPluginList"))
|
||||
using var pluginListChild = ImRaii.Child("###profileEditorPluginList");
|
||||
if (pluginListChild)
|
||||
{
|
||||
var pluginLineHeight = 32 * ImGuiHelpers.GlobalScale;
|
||||
string? wantRemovePluginInternalName = null;
|
||||
|
||||
foreach (var plugin in profile.Plugins)
|
||||
using var syncScope = profile.GetSyncScope();
|
||||
foreach (var plugin in profile.Plugins.ToArray())
|
||||
{
|
||||
didAny = true;
|
||||
var pmPlugin = pm.InstalledPlugins.FirstOrDefault(x => x.Manifest.InternalName == plugin.InternalName);
|
||||
|
|
@ -391,7 +396,7 @@ internal class ProfileManagerWidget
|
|||
var enabled = plugin.IsEnabled;
|
||||
if (ImGui.Checkbox($"###{this.editingProfileGuid}-{plugin.InternalName}", ref enabled))
|
||||
{
|
||||
Task.Run(() => profile.AddOrUpdate(plugin.InternalName, enabled))
|
||||
Task.Run(() => profile.AddOrUpdateAsync(plugin.InternalName, enabled))
|
||||
.ContinueWith(this.installer.DisplayErrorContinuation, Locs.ErrorCouldNotChangeState);
|
||||
}
|
||||
|
||||
|
|
@ -411,9 +416,8 @@ internal class ProfileManagerWidget
|
|||
if (wantRemovePluginInternalName != null)
|
||||
{
|
||||
// TODO: handle error
|
||||
profile.Remove(wantRemovePluginInternalName, false);
|
||||
Task.Run(() => profman.ApplyAllWantStates())
|
||||
.ContinueWith(this.installer.DisplayErrorContinuation, Locs.ErrorCouldNotRemove);
|
||||
Task.Run(() => profile.RemoveAsync(wantRemovePluginInternalName, false))
|
||||
.ContinueWith(this.installer.DisplayErrorContinuation, Locs.ErrorCouldNotRemove);
|
||||
}
|
||||
|
||||
if (!didAny)
|
||||
|
|
@ -436,14 +440,12 @@ internal class ProfileManagerWidget
|
|||
ImGui.TextUnformatted(addPluginsText);
|
||||
|
||||
ImGuiHelpers.ScaledDummy(10);
|
||||
|
||||
ImGui.EndChild();
|
||||
}
|
||||
|
||||
if (wantPluginAddPopup)
|
||||
{
|
||||
this.pickerSearch = string.Empty;
|
||||
ImGui.OpenPopup(addPluginToProfilePopup);
|
||||
ImGui.OpenPopup(addPluginToProfilePopupId);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -226,7 +226,6 @@ internal class SettingsWindow : Window
|
|||
|
||||
configuration.QueueSave();
|
||||
|
||||
_ = Service<PluginManager>.Get().ReloadPluginMastersAsync();
|
||||
Service<InterfaceManager>.Get().RebuildFonts();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
15
Dalamud/Logging/Retention/DebugRetentionBehaviour.cs
Normal file
15
Dalamud/Logging/Retention/DebugRetentionBehaviour.cs
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
using System.IO;
|
||||
|
||||
namespace Dalamud.Logging.Retention;
|
||||
|
||||
/// <summary>
|
||||
/// Class implementing log retention behaviour for debug builds.
|
||||
/// </summary>
|
||||
internal class DebugRetentionBehaviour : RetentionBehaviour
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public override void Apply(FileInfo logFile, FileInfo rolloverFile)
|
||||
{
|
||||
CullLogFile(logFile, 1 * 1024 * 1024, rolloverFile, 10 * 1024 * 1024);
|
||||
}
|
||||
}
|
||||
15
Dalamud/Logging/Retention/ReleaseRetentionBehaviour.cs
Normal file
15
Dalamud/Logging/Retention/ReleaseRetentionBehaviour.cs
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
using System.IO;
|
||||
|
||||
namespace Dalamud.Logging.Retention;
|
||||
|
||||
/// <summary>
|
||||
/// Class implementing log retention behaviour for release builds.
|
||||
/// </summary>
|
||||
internal class ReleaseRetentionBehaviour : RetentionBehaviour
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public override void Apply(FileInfo logFile, FileInfo rolloverFile)
|
||||
{
|
||||
CullLogFile(logFile, 0, rolloverFile, 10 * 1024 * 1024);
|
||||
}
|
||||
}
|
||||
91
Dalamud/Logging/Retention/RetentionBehaviour.cs
Normal file
91
Dalamud/Logging/Retention/RetentionBehaviour.cs
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
|
||||
using Serilog;
|
||||
|
||||
namespace Dalamud.Logging.Retention;
|
||||
|
||||
/// <summary>
|
||||
/// Class implementing retention behaviour for log files.
|
||||
/// </summary>
|
||||
internal abstract class RetentionBehaviour
|
||||
{
|
||||
/// <summary>
|
||||
/// Apply the specified retention behaviour to log files.
|
||||
/// </summary>
|
||||
/// <param name="logFile">The regular log file path.</param>
|
||||
/// <param name="rolloverFile">The rollover "old" log file path.</param>
|
||||
public abstract void Apply(FileInfo logFile, FileInfo rolloverFile);
|
||||
|
||||
/// <summary>
|
||||
/// Trim existing log file to a specified length, and optionally move the excess data to another file.
|
||||
/// </summary>
|
||||
/// <param name="logFile">Target log file to trim.</param>
|
||||
/// <param name="logMaxSize">Maximum size of target log file.</param>
|
||||
/// <param name="oldFile">.old file to move excess data to.</param>
|
||||
/// <param name="oldMaxSize">Maximum size of .old file.</param>
|
||||
protected static void CullLogFile(FileInfo logFile, int logMaxSize, FileInfo oldFile, int oldMaxSize)
|
||||
{
|
||||
var targetFiles = new[]
|
||||
{
|
||||
(logFile, logMaxSize),
|
||||
(oldFile, oldMaxSize),
|
||||
};
|
||||
var buffer = new byte[4096];
|
||||
|
||||
try
|
||||
{
|
||||
if (!logFile.Exists)
|
||||
logFile.Create().Close();
|
||||
|
||||
// 1. Move excess data from logFile to oldFile
|
||||
if (logFile.Length > logMaxSize)
|
||||
{
|
||||
using var reader = logFile.Open(FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
|
||||
using var writer = oldFile.Open(FileMode.Append, FileAccess.Write, FileShare.ReadWrite);
|
||||
|
||||
var amountToMove = (int)Math.Min(logFile.Length - logMaxSize, oldMaxSize);
|
||||
reader.Seek(-(logMaxSize + amountToMove), SeekOrigin.End);
|
||||
|
||||
for (var i = 0; i < amountToMove; i += buffer.Length)
|
||||
writer.Write(buffer, 0, reader.Read(buffer, 0, Math.Min(buffer.Length, amountToMove - i)));
|
||||
}
|
||||
|
||||
// 2. Cull each of .log and .old files
|
||||
foreach (var (file, maxSize) in targetFiles)
|
||||
{
|
||||
if (!file.Exists || file.Length <= maxSize)
|
||||
continue;
|
||||
|
||||
using var reader = file.Open(FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
|
||||
using var writer = file.Open(FileMode.Open, FileAccess.Write, FileShare.ReadWrite);
|
||||
|
||||
reader.Seek(file.Length - maxSize, SeekOrigin.Begin);
|
||||
for (int read; (read = reader.Read(buffer, 0, buffer.Length)) > 0;)
|
||||
writer.Write(buffer, 0, read);
|
||||
|
||||
writer.SetLength(maxSize);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (ex is IOException)
|
||||
{
|
||||
foreach (var (file, _) in targetFiles)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (file.Exists)
|
||||
file.Delete();
|
||||
}
|
||||
catch (Exception ex2)
|
||||
{
|
||||
Log.Error(ex2, "Failed to delete {file}", file.FullName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Log.Error(ex, "Log cull failed");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +1,6 @@
|
|||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.IO.Compression;
|
||||
|
|
@ -69,6 +68,10 @@ internal partial class PluginManager : IDisposable, IServiceType
|
|||
private readonly object pluginListLock = new();
|
||||
private readonly DirectoryInfo pluginDirectory;
|
||||
private readonly BannedPlugin[]? bannedPlugins;
|
||||
|
||||
private readonly List<LocalPlugin> installedPluginsList = new();
|
||||
private readonly List<RemotePluginManifest> availablePluginsList = new();
|
||||
private readonly List<AvailablePluginUpdate> updatablePluginsList = new();
|
||||
|
||||
private readonly DalamudLinkPayload openInstallerWindowPluginChangelogsLink;
|
||||
|
||||
|
|
@ -146,19 +149,46 @@ internal partial class PluginManager : IDisposable, IServiceType
|
|||
public event Action? OnAvailablePluginsChanged;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a list of all loaded plugins.
|
||||
/// Gets a copy of the list of all loaded plugins.
|
||||
/// </summary>
|
||||
public ImmutableList<LocalPlugin> InstalledPlugins { get; private set; } = ImmutableList.Create<LocalPlugin>();
|
||||
public IEnumerable<LocalPlugin> InstalledPlugins
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (this.pluginListLock)
|
||||
{
|
||||
return this.installedPluginsList.ToList();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a list of all available plugins.
|
||||
/// Gets a copy of the list of all available plugins.
|
||||
/// </summary>
|
||||
public ImmutableList<RemotePluginManifest> AvailablePlugins { get; private set; } = ImmutableList.Create<RemotePluginManifest>();
|
||||
|
||||
public IEnumerable<RemotePluginManifest> AvailablePlugins
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (this.pluginListLock)
|
||||
{
|
||||
return this.availablePluginsList.ToList();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a list of all plugins with an available update.
|
||||
/// Gets a copy of the list of all plugins with an available update.
|
||||
/// </summary>
|
||||
public ImmutableList<AvailablePluginUpdate> UpdatablePlugins { get; private set; } = ImmutableList.Create<AvailablePluginUpdate>();
|
||||
public IEnumerable<AvailablePluginUpdate> UpdatablePlugins
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (this.pluginListLock)
|
||||
{
|
||||
return this.updatablePluginsList.ToList();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a list of all plugin repositories. The main repo should always be first.
|
||||
|
|
@ -173,7 +203,7 @@ internal partial class PluginManager : IDisposable, IServiceType
|
|||
/// <summary>
|
||||
/// Gets a value indicating whether all added repos are not in progress.
|
||||
/// </summary>
|
||||
public bool ReposReady => this.Repos.All(repo => repo.State != PluginRepositoryState.InProgress);
|
||||
public bool ReposReady { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the plugin manager started in safe mode.
|
||||
|
|
@ -231,6 +261,13 @@ internal partial class PluginManager : IDisposable, IServiceType
|
|||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get a disposable that will lock plugin lists while it is not disposed.
|
||||
/// You must NEVER use this in async code.
|
||||
/// </summary>
|
||||
/// <returns>The aforementioned disposable.</returns>
|
||||
public IDisposable GetSyncScope() => new ScopedSyncRoot(this.pluginListLock);
|
||||
|
||||
/// <summary>
|
||||
/// Print to chat any plugin updates and whether they were successful.
|
||||
/// </summary>
|
||||
|
|
@ -309,7 +346,7 @@ internal partial class PluginManager : IDisposable, IServiceType
|
|||
public void Dispose()
|
||||
{
|
||||
var disposablePlugins =
|
||||
this.InstalledPlugins.Where(plugin => plugin.State is PluginState.Loaded or PluginState.LoadError).ToArray();
|
||||
this.installedPluginsList.Where(plugin => plugin.State is PluginState.Loaded or PluginState.LoadError).ToArray();
|
||||
if (disposablePlugins.Any())
|
||||
{
|
||||
// Unload them first, just in case some of plugin codes are still running via callbacks initiated externally.
|
||||
|
|
@ -582,7 +619,7 @@ internal partial class PluginManager : IDisposable, IServiceType
|
|||
|
||||
var sigScanner = await Service<SigScanner>.GetAsync().ConfigureAwait(false);
|
||||
this.PluginsReady = true;
|
||||
this.NotifyInstalledPluginsChanged();
|
||||
this.NotifyinstalledPluginsListChanged();
|
||||
sigScanner.Save();
|
||||
},
|
||||
tokenSource.Token);
|
||||
|
|
@ -596,15 +633,27 @@ internal partial class PluginManager : IDisposable, IServiceType
|
|||
public async Task ReloadPluginMastersAsync(bool notify = true)
|
||||
{
|
||||
Log.Information("Now reloading all PluginMasters...");
|
||||
this.ReposReady = false;
|
||||
|
||||
Debug.Assert(!this.Repos.First().IsThirdParty, "First repository should be main repository");
|
||||
await this.Repos.First().ReloadPluginMasterAsync(); // Load official repo first
|
||||
try
|
||||
{
|
||||
Debug.Assert(!this.Repos.First().IsThirdParty, "First repository should be main repository");
|
||||
await this.Repos.First().ReloadPluginMasterAsync(); // Load official repo first
|
||||
|
||||
await Task.WhenAll(this.Repos.Skip(1).Select(repo => repo.ReloadPluginMasterAsync()));
|
||||
await Task.WhenAll(this.Repos.Skip(1).Select(repo => repo.ReloadPluginMasterAsync()));
|
||||
|
||||
Log.Information("PluginMasters reloaded, now refiltering...");
|
||||
Log.Information("PluginMasters reloaded, now refiltering...");
|
||||
|
||||
this.RefilterPluginMasters(notify);
|
||||
this.RefilterPluginMasters(notify);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Could not reload plugin repositories");
|
||||
}
|
||||
finally
|
||||
{
|
||||
this.ReposReady = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -613,15 +662,18 @@ internal partial class PluginManager : IDisposable, IServiceType
|
|||
/// <param name="notify">Whether to notify that available plugins have changed afterwards.</param>
|
||||
public void RefilterPluginMasters(bool notify = true)
|
||||
{
|
||||
this.AvailablePlugins = this.Repos
|
||||
.SelectMany(repo => repo.PluginMaster)
|
||||
.Where(this.IsManifestEligible)
|
||||
.Where(IsManifestVisible)
|
||||
.ToImmutableList();
|
||||
|
||||
if (notify)
|
||||
lock (this.pluginListLock)
|
||||
{
|
||||
this.NotifyAvailablePluginsChanged();
|
||||
this.availablePluginsList.Clear();
|
||||
this.availablePluginsList.AddRange(this.Repos
|
||||
.SelectMany(repo => repo.PluginMaster)
|
||||
.Where(this.IsManifestEligible)
|
||||
.Where(IsManifestVisible));
|
||||
|
||||
if (notify)
|
||||
{
|
||||
this.NotifyAvailablePluginsChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -639,6 +691,8 @@ internal partial class PluginManager : IDisposable, IServiceType
|
|||
{
|
||||
if (!setting.IsEnabled)
|
||||
continue;
|
||||
|
||||
Log.Verbose("Scanning dev plugins at {Path}", setting.Path);
|
||||
|
||||
if (Directory.Exists(setting.Path))
|
||||
{
|
||||
|
|
@ -657,7 +711,7 @@ internal partial class PluginManager : IDisposable, IServiceType
|
|||
// This file is already known to us
|
||||
lock (this.pluginListLock)
|
||||
{
|
||||
if (this.InstalledPlugins.Any(lp => lp.DllFile.FullName == dllFile.FullName))
|
||||
if (this.installedPluginsList.Any(lp => lp.DllFile.FullName == dllFile.FullName))
|
||||
continue;
|
||||
}
|
||||
|
||||
|
|
@ -683,7 +737,7 @@ internal partial class PluginManager : IDisposable, IServiceType
|
|||
}
|
||||
|
||||
if (listChanged)
|
||||
this.NotifyInstalledPluginsChanged();
|
||||
this.NotifyinstalledPluginsListChanged();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -801,7 +855,7 @@ internal partial class PluginManager : IDisposable, IServiceType
|
|||
|
||||
var plugin = await this.LoadPluginAsync(dllFile, manifest, reason);
|
||||
|
||||
this.NotifyInstalledPluginsChanged();
|
||||
this.NotifyinstalledPluginsListChanged();
|
||||
return plugin;
|
||||
}
|
||||
|
||||
|
|
@ -816,12 +870,12 @@ internal partial class PluginManager : IDisposable, IServiceType
|
|||
|
||||
lock (this.pluginListLock)
|
||||
{
|
||||
this.InstalledPlugins = this.InstalledPlugins.Remove(plugin);
|
||||
this.installedPluginsList.Remove(plugin);
|
||||
}
|
||||
|
||||
PluginLocations.Remove(plugin.AssemblyName?.FullName ?? string.Empty, out _);
|
||||
|
||||
this.NotifyInstalledPluginsChanged();
|
||||
this.NotifyinstalledPluginsListChanged();
|
||||
this.NotifyAvailablePluginsChanged();
|
||||
}
|
||||
|
||||
|
|
@ -921,31 +975,34 @@ internal partial class PluginManager : IDisposable, IServiceType
|
|||
/// <param name="dryRun">Perform a dry run, don't install anything.</param>
|
||||
/// <param name="autoUpdate">If this action was performed as part of an auto-update.</param>
|
||||
/// <returns>Success or failure and a list of updated plugin metadata.</returns>
|
||||
public async Task<List<PluginUpdateStatus>> UpdatePluginsAsync(bool ignoreDisabled, bool dryRun, bool autoUpdate = false)
|
||||
public async Task<IEnumerable<PluginUpdateStatus>> UpdatePluginsAsync(bool ignoreDisabled, bool dryRun, bool autoUpdate = false)
|
||||
{
|
||||
Log.Information("Starting plugin update");
|
||||
|
||||
var updatedList = new List<PluginUpdateStatus>();
|
||||
var updateTasks = new List<Task<PluginUpdateStatus>>();
|
||||
|
||||
// Prevent collection was modified errors
|
||||
foreach (var plugin in this.UpdatablePlugins)
|
||||
lock (this.pluginListLock)
|
||||
{
|
||||
// Can't update that!
|
||||
if (plugin.InstalledPlugin.IsDev)
|
||||
continue;
|
||||
foreach (var plugin in this.updatablePluginsList)
|
||||
{
|
||||
// Can't update that!
|
||||
if (plugin.InstalledPlugin.IsDev)
|
||||
continue;
|
||||
|
||||
if (!plugin.InstalledPlugin.IsWantedByAnyProfile && ignoreDisabled)
|
||||
continue;
|
||||
if (!plugin.InstalledPlugin.IsWantedByAnyProfile && ignoreDisabled)
|
||||
continue;
|
||||
|
||||
if (plugin.InstalledPlugin.Manifest.ScheduledForDeletion)
|
||||
continue;
|
||||
if (plugin.InstalledPlugin.Manifest.ScheduledForDeletion)
|
||||
continue;
|
||||
|
||||
var result = await this.UpdateSinglePluginAsync(plugin, false, dryRun);
|
||||
if (result != null)
|
||||
updatedList.Add(result);
|
||||
updateTasks.Add(this.UpdateSinglePluginAsync(plugin, false, dryRun));
|
||||
}
|
||||
}
|
||||
|
||||
this.NotifyInstalledPluginsChanged();
|
||||
var updatedList = await Task.WhenAll(updateTasks);
|
||||
|
||||
this.NotifyinstalledPluginsListChanged();
|
||||
this.NotifyPluginsForStateChange(
|
||||
autoUpdate ? PluginListInvalidationKind.AutoUpdate : PluginListInvalidationKind.Update,
|
||||
updatedList.Select(x => x.InternalName));
|
||||
|
|
@ -962,7 +1019,7 @@ internal partial class PluginManager : IDisposable, IServiceType
|
|||
/// <param name="notify">Whether to notify that installed plugins have changed afterwards.</param>
|
||||
/// <param name="dryRun">Whether or not to actually perform the update, or just indicate success.</param>
|
||||
/// <returns>The status of the update.</returns>
|
||||
public async Task<PluginUpdateStatus?> UpdateSinglePluginAsync(AvailablePluginUpdate metadata, bool notify, bool dryRun)
|
||||
public async Task<PluginUpdateStatus> UpdateSinglePluginAsync(AvailablePluginUpdate metadata, bool notify, bool dryRun)
|
||||
{
|
||||
var plugin = metadata.InstalledPlugin;
|
||||
|
||||
|
|
@ -1009,7 +1066,7 @@ internal partial class PluginManager : IDisposable, IServiceType
|
|||
|
||||
lock (this.pluginListLock)
|
||||
{
|
||||
this.InstalledPlugins = this.InstalledPlugins.Remove(plugin);
|
||||
this.installedPluginsList.Remove(plugin);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
|
@ -1036,7 +1093,7 @@ internal partial class PluginManager : IDisposable, IServiceType
|
|||
}
|
||||
|
||||
if (notify && updateStatus.WasUpdated)
|
||||
this.NotifyInstalledPluginsChanged();
|
||||
this.NotifyinstalledPluginsListChanged();
|
||||
|
||||
return updateStatus;
|
||||
}
|
||||
|
|
@ -1168,7 +1225,7 @@ internal partial class PluginManager : IDisposable, IServiceType
|
|||
|
||||
lock (this.pluginListLock)
|
||||
{
|
||||
foreach (var plugin in this.InstalledPlugins)
|
||||
foreach (var plugin in this.installedPluginsList)
|
||||
{
|
||||
if (plugin.AssemblyName != null &&
|
||||
plugin.AssemblyName.FullName == declaringType.Assembly.GetName().FullName)
|
||||
|
|
@ -1203,11 +1260,11 @@ internal partial class PluginManager : IDisposable, IServiceType
|
|||
var name = manifest?.Name ?? dllFile.Name;
|
||||
var loadPlugin = !doNotLoad;
|
||||
|
||||
LocalPlugin plugin;
|
||||
LocalPlugin? plugin;
|
||||
|
||||
if (manifest != null && manifest.InternalName == null)
|
||||
if (manifest != null && (manifest.InternalName == null || manifest.Name == null))
|
||||
{
|
||||
Log.Error("{FileName}: Your manifest has no internal name set! Can't load this.", dllFile.FullName);
|
||||
Log.Error("{FileName}: Your manifest has no internal name or name set! Can't load this.", dllFile.FullName);
|
||||
throw new Exception("No internal name");
|
||||
}
|
||||
|
||||
|
|
@ -1215,18 +1272,44 @@ internal partial class PluginManager : IDisposable, IServiceType
|
|||
{
|
||||
Log.Information($"Loading dev plugin {name}");
|
||||
var devPlugin = new LocalDevPlugin(dllFile, manifest);
|
||||
loadPlugin &= !isBoot || devPlugin.StartOnBoot;
|
||||
loadPlugin &= !isBoot;
|
||||
|
||||
var probablyInternalNameForThisPurpose = manifest?.InternalName ?? dllFile.Name;
|
||||
|
||||
var wantsInDefaultProfile =
|
||||
this.profileManager.DefaultProfile.WantsPlugin(probablyInternalNameForThisPurpose);
|
||||
if (wantsInDefaultProfile == false && devPlugin.StartOnBoot)
|
||||
if (wantsInDefaultProfile == null)
|
||||
{
|
||||
this.profileManager.DefaultProfile.AddOrUpdate(probablyInternalNameForThisPurpose, true, false);
|
||||
// We don't know about this plugin, so we don't want to do anything here.
|
||||
// The code below will take care of it and add it with the default value.
|
||||
}
|
||||
else if (wantsInDefaultProfile == false && devPlugin.StartOnBoot)
|
||||
{
|
||||
// We didn't want this plugin, and StartOnBoot is on. That means we don't want it and it should stay off until manually enabled.
|
||||
Log.Verbose("DevPlugin {Name} disabled and StartOnBoot => disable", probablyInternalNameForThisPurpose);
|
||||
await this.profileManager.DefaultProfile.AddOrUpdateAsync(probablyInternalNameForThisPurpose, false, false);
|
||||
loadPlugin = false;
|
||||
}
|
||||
else if (wantsInDefaultProfile == true && devPlugin.StartOnBoot)
|
||||
{
|
||||
// We wanted this plugin, and StartOnBoot is on. That means we actually do want it.
|
||||
Log.Verbose("DevPlugin {Name} enabled and StartOnBoot => enable", probablyInternalNameForThisPurpose);
|
||||
await this.profileManager.DefaultProfile.AddOrUpdateAsync(probablyInternalNameForThisPurpose, true, false);
|
||||
loadPlugin = !doNotLoad;
|
||||
}
|
||||
else if (wantsInDefaultProfile == true && !devPlugin.StartOnBoot)
|
||||
{
|
||||
this.profileManager.DefaultProfile.AddOrUpdate(probablyInternalNameForThisPurpose, false, false);
|
||||
// We wanted this plugin, but StartOnBoot is off. This means we don't want it anymore.
|
||||
Log.Verbose("DevPlugin {Name} enabled and !StartOnBoot => disable", probablyInternalNameForThisPurpose);
|
||||
await this.profileManager.DefaultProfile.AddOrUpdateAsync(probablyInternalNameForThisPurpose, false, false);
|
||||
loadPlugin = false;
|
||||
}
|
||||
else if (wantsInDefaultProfile == false && !devPlugin.StartOnBoot)
|
||||
{
|
||||
// We didn't want this plugin, and StartOnBoot is off. We don't want it.
|
||||
Log.Verbose("DevPlugin {Name} disabled and !StartOnBoot => disable", probablyInternalNameForThisPurpose);
|
||||
await this.profileManager.DefaultProfile.AddOrUpdateAsync(probablyInternalNameForThisPurpose, false, false);
|
||||
loadPlugin = false;
|
||||
}
|
||||
|
||||
plugin = devPlugin;
|
||||
|
|
@ -1242,7 +1325,7 @@ internal partial class PluginManager : IDisposable, IServiceType
|
|||
#pragma warning restore CS0618
|
||||
|
||||
// Need to do this here, so plugins that don't load are still added to the default profile
|
||||
var wantToLoad = this.profileManager.GetWantState(plugin.Manifest.InternalName, defaultState);
|
||||
var wantToLoad = await this.profileManager.GetWantStateAsync(plugin.Manifest.InternalName, defaultState);
|
||||
|
||||
if (loadPlugin)
|
||||
{
|
||||
|
|
@ -1311,9 +1394,12 @@ internal partial class PluginManager : IDisposable, IServiceType
|
|||
}
|
||||
}
|
||||
|
||||
if (plugin == null)
|
||||
throw new Exception("Plugin was null when adding to list");
|
||||
|
||||
lock (this.pluginListLock)
|
||||
{
|
||||
this.InstalledPlugins = this.InstalledPlugins.Add(plugin);
|
||||
this.installedPluginsList.Add(plugin);
|
||||
}
|
||||
|
||||
return plugin;
|
||||
|
|
@ -1321,39 +1407,40 @@ internal partial class PluginManager : IDisposable, IServiceType
|
|||
|
||||
private void DetectAvailablePluginUpdates()
|
||||
{
|
||||
var updatablePlugins = new List<AvailablePluginUpdate>();
|
||||
|
||||
foreach (var plugin in this.InstalledPlugins)
|
||||
lock (this.pluginListLock)
|
||||
{
|
||||
var installedVersion = plugin.IsTesting
|
||||
? plugin.Manifest.TestingAssemblyVersion
|
||||
: plugin.Manifest.AssemblyVersion;
|
||||
|
||||
var updates = this.AvailablePlugins
|
||||
.Where(remoteManifest => plugin.Manifest.InternalName == remoteManifest.InternalName)
|
||||
.Where(remoteManifest => plugin.Manifest.InstalledFromUrl == remoteManifest.SourceRepo.PluginMasterUrl || !remoteManifest.SourceRepo.IsThirdParty)
|
||||
.Where(remoteManifest => remoteManifest.DalamudApiLevel == DalamudApiLevel)
|
||||
.Select(remoteManifest =>
|
||||
{
|
||||
var useTesting = this.UseTesting(remoteManifest);
|
||||
var candidateVersion = useTesting
|
||||
? remoteManifest.TestingAssemblyVersion
|
||||
: remoteManifest.AssemblyVersion;
|
||||
var isUpdate = candidateVersion > installedVersion;
|
||||
|
||||
return (isUpdate, useTesting, candidateVersion, remoteManifest);
|
||||
})
|
||||
.Where(tpl => tpl.isUpdate)
|
||||
.ToList();
|
||||
|
||||
if (updates.Count > 0)
|
||||
this.updatablePluginsList.Clear();
|
||||
|
||||
foreach (var plugin in this.installedPluginsList)
|
||||
{
|
||||
var update = updates.Aggregate((t1, t2) => t1.candidateVersion > t2.candidateVersion ? t1 : t2);
|
||||
updatablePlugins.Add(new AvailablePluginUpdate(plugin, update.remoteManifest, update.useTesting));
|
||||
var installedVersion = plugin.IsTesting
|
||||
? plugin.Manifest.TestingAssemblyVersion
|
||||
: plugin.Manifest.AssemblyVersion;
|
||||
|
||||
var updates = this.AvailablePlugins
|
||||
.Where(remoteManifest => plugin.Manifest.InternalName == remoteManifest.InternalName)
|
||||
.Where(remoteManifest => plugin.Manifest.InstalledFromUrl == remoteManifest.SourceRepo.PluginMasterUrl || !remoteManifest.SourceRepo.IsThirdParty)
|
||||
.Where(remoteManifest => remoteManifest.DalamudApiLevel == DalamudApiLevel)
|
||||
.Select(remoteManifest =>
|
||||
{
|
||||
var useTesting = this.UseTesting(remoteManifest);
|
||||
var candidateVersion = useTesting
|
||||
? remoteManifest.TestingAssemblyVersion
|
||||
: remoteManifest.AssemblyVersion;
|
||||
var isUpdate = candidateVersion > installedVersion;
|
||||
|
||||
return (isUpdate, useTesting, candidateVersion, remoteManifest);
|
||||
})
|
||||
.Where(tpl => tpl.isUpdate)
|
||||
.ToList();
|
||||
|
||||
if (updates.Count > 0)
|
||||
{
|
||||
var update = updates.Aggregate((t1, t2) => t1.candidateVersion > t2.candidateVersion ? t1 : t2);
|
||||
this.updatablePluginsList.Add(new AvailablePluginUpdate(plugin, update.remoteManifest, update.useTesting));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.UpdatablePlugins = updatablePlugins.ToImmutableList();
|
||||
}
|
||||
|
||||
private void NotifyAvailablePluginsChanged()
|
||||
|
|
@ -1363,7 +1450,7 @@ internal partial class PluginManager : IDisposable, IServiceType
|
|||
this.OnAvailablePluginsChanged?.InvokeSafely();
|
||||
}
|
||||
|
||||
private void NotifyInstalledPluginsChanged()
|
||||
private void NotifyinstalledPluginsListChanged()
|
||||
{
|
||||
this.DetectAvailablePluginUpdates();
|
||||
|
||||
|
|
@ -1372,7 +1459,7 @@ internal partial class PluginManager : IDisposable, IServiceType
|
|||
|
||||
private void NotifyPluginsForStateChange(PluginListInvalidationKind kind, IEnumerable<string> affectedInternalNames)
|
||||
{
|
||||
foreach (var installedPlugin in this.InstalledPlugins)
|
||||
foreach (var installedPlugin in this.installedPluginsList)
|
||||
{
|
||||
if (!installedPlugin.IsLoaded || installedPlugin.DalamudInterface == null)
|
||||
continue;
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Dalamud.Configuration.Internal;
|
||||
using Dalamud.Logging.Internal;
|
||||
|
|
@ -108,6 +109,13 @@ internal class Profile
|
|||
/// </summary>
|
||||
public ProfileModel Model => this.modelV1;
|
||||
|
||||
/// <summary>
|
||||
/// Get a disposable that will lock the plugin list while it is not disposed.
|
||||
/// You must NEVER use this in async code.
|
||||
/// </summary>
|
||||
/// <returns>The aforementioned disposable.</returns>
|
||||
public IDisposable GetSyncScope() => new ScopedSyncRoot(this);
|
||||
|
||||
/// <summary>
|
||||
/// Set this profile's state. This cannot be called for the default profile.
|
||||
/// This will block until all states have been applied.
|
||||
|
|
@ -115,7 +123,8 @@ internal class Profile
|
|||
/// <param name="enabled">Whether or not the profile is enabled.</param>
|
||||
/// <param name="apply">Whether or not the current state should immediately be applied.</param>
|
||||
/// <exception cref="InvalidOperationException">Thrown when an untoggleable profile is toggled.</exception>
|
||||
public void SetState(bool enabled, bool apply = true)
|
||||
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
|
||||
public async Task SetStateAsync(bool enabled, bool apply = true)
|
||||
{
|
||||
if (this.IsDefaultProfile)
|
||||
throw new InvalidOperationException("Cannot set state of default profile");
|
||||
|
|
@ -127,7 +136,7 @@ internal class Profile
|
|||
Service<DalamudConfiguration>.Get().QueueSave();
|
||||
|
||||
if (apply)
|
||||
this.manager.ApplyAllWantStates();
|
||||
await this.manager.ApplyAllWantStatesAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -137,8 +146,11 @@ internal class Profile
|
|||
/// <returns>Null if this profile does not declare the plugin, true if the profile declares the plugin and wants it enabled, false if the profile declares the plugin and does not want it enabled.</returns>
|
||||
public bool? WantsPlugin(string internalName)
|
||||
{
|
||||
var entry = this.modelV1.Plugins.FirstOrDefault(x => x.InternalName == internalName);
|
||||
return entry?.IsEnabled;
|
||||
lock (this)
|
||||
{
|
||||
var entry = this.modelV1.Plugins.FirstOrDefault(x => x.InternalName == internalName);
|
||||
return entry?.IsEnabled;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -148,34 +160,38 @@ internal class Profile
|
|||
/// <param name="internalName">The internal name of the plugin.</param>
|
||||
/// <param name="state">Whether or not the plugin should be enabled.</param>
|
||||
/// <param name="apply">Whether or not the current state should immediately be applied.</param>
|
||||
public void AddOrUpdate(string internalName, bool state, bool apply = true)
|
||||
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
|
||||
public async Task AddOrUpdateAsync(string internalName, bool state, bool apply = true)
|
||||
{
|
||||
Debug.Assert(!internalName.IsNullOrEmpty(), "!internalName.IsNullOrEmpty()");
|
||||
|
||||
var existing = this.modelV1.Plugins.FirstOrDefault(x => x.InternalName == internalName);
|
||||
if (existing != null)
|
||||
lock (this)
|
||||
{
|
||||
existing.IsEnabled = state;
|
||||
}
|
||||
else
|
||||
{
|
||||
this.modelV1.Plugins.Add(new ProfileModelV1.ProfileModelV1Plugin
|
||||
var existing = this.modelV1.Plugins.FirstOrDefault(x => x.InternalName == internalName);
|
||||
if (existing != null)
|
||||
{
|
||||
InternalName = internalName,
|
||||
IsEnabled = state,
|
||||
});
|
||||
existing.IsEnabled = state;
|
||||
}
|
||||
else
|
||||
{
|
||||
this.modelV1.Plugins.Add(new ProfileModelV1.ProfileModelV1Plugin
|
||||
{
|
||||
InternalName = internalName,
|
||||
IsEnabled = state,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// We need to remove this plugin from the default profile, if it declares it.
|
||||
if (!this.IsDefaultProfile && this.manager.DefaultProfile.WantsPlugin(internalName) != null)
|
||||
{
|
||||
this.manager.DefaultProfile.Remove(internalName, false);
|
||||
await this.manager.DefaultProfile.RemoveAsync(internalName, false);
|
||||
}
|
||||
|
||||
Service<DalamudConfiguration>.Get().QueueSave();
|
||||
|
||||
if (apply)
|
||||
this.manager.ApplyAllWantStates();
|
||||
await this.manager.ApplyAllWantStatesAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -184,21 +200,26 @@ internal class Profile
|
|||
/// </summary>
|
||||
/// <param name="internalName">The internal name of the plugin.</param>
|
||||
/// <param name="apply">Whether or not the current state should immediately be applied.</param>
|
||||
public void Remove(string internalName, bool apply = true)
|
||||
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
|
||||
public async Task RemoveAsync(string internalName, bool apply = true)
|
||||
{
|
||||
var entry = this.modelV1.Plugins.FirstOrDefault(x => x.InternalName == internalName);
|
||||
if (entry == null)
|
||||
throw new ArgumentException($"No plugin \"{internalName}\" in profile \"{this.Guid}\"");
|
||||
ProfileModelV1.ProfileModelV1Plugin entry;
|
||||
lock (this)
|
||||
{
|
||||
entry = this.modelV1.Plugins.FirstOrDefault(x => x.InternalName == internalName);
|
||||
if (entry == null)
|
||||
throw new ArgumentException($"No plugin \"{internalName}\" in profile \"{this.Guid}\"");
|
||||
|
||||
if (!this.modelV1.Plugins.Remove(entry))
|
||||
throw new Exception("Couldn't remove plugin from model collection");
|
||||
if (!this.modelV1.Plugins.Remove(entry))
|
||||
throw new Exception("Couldn't remove plugin from model collection");
|
||||
}
|
||||
|
||||
// We need to add this plugin back to the default profile, if we were the last profile to have it.
|
||||
if (!this.manager.IsInAnyProfile(internalName))
|
||||
{
|
||||
if (!this.IsDefaultProfile)
|
||||
{
|
||||
this.manager.DefaultProfile.AddOrUpdate(internalName, entry.IsEnabled, false);
|
||||
await this.manager.DefaultProfile.AddOrUpdateAsync(internalName, entry.IsEnabled, false);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
@ -209,6 +230,6 @@ internal class Profile
|
|||
Service<DalamudConfiguration>.Get().QueueSave();
|
||||
|
||||
if (apply)
|
||||
this.manager.ApplyAllWantStates();
|
||||
await this.manager.ApplyAllWantStatesAsync();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -96,14 +96,14 @@ internal class ProfileCommandHandler : IServiceType, IDisposable
|
|||
{
|
||||
case ProfileOp.Enable:
|
||||
if (!profile.IsEnabled)
|
||||
profile.SetState(true, false);
|
||||
Task.Run(() => profile.SetStateAsync(true, false)).GetAwaiter().GetResult();
|
||||
break;
|
||||
case ProfileOp.Disable:
|
||||
if (profile.IsEnabled)
|
||||
profile.SetState(false, false);
|
||||
Task.Run(() => profile.SetStateAsync(false, false)).GetAwaiter().GetResult();
|
||||
break;
|
||||
case ProfileOp.Toggle:
|
||||
profile.SetState(!profile.IsEnabled, false);
|
||||
Task.Run(() => profile.SetStateAsync(!profile.IsEnabled, false)).GetAwaiter().GetResult();
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
|
|
@ -118,7 +118,7 @@ internal class ProfileCommandHandler : IServiceType, IDisposable
|
|||
this.chat.Print(Loc.Localize("ProfileCommandsDisabling", "Disabling collection \"{0}\"...").Format(profile.Name));
|
||||
}
|
||||
|
||||
Task.Run(() => this.profileManager.ApplyAllWantStates()).ContinueWith(t =>
|
||||
Task.Run(this.profileManager.ApplyAllWantStatesAsync).ContinueWith(t =>
|
||||
{
|
||||
if (!t.IsCompletedSuccessfully && t.Exception != null)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
|
|
@ -41,7 +40,14 @@ internal class ProfileManager : IServiceType
|
|||
/// <summary>
|
||||
/// Gets the default profile.
|
||||
/// </summary>
|
||||
public Profile DefaultProfile => this.profiles.First(x => x.IsDefaultProfile);
|
||||
public Profile DefaultProfile
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (this.profiles)
|
||||
return this.profiles.First(x => x.IsDefaultProfile);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets all profiles, including the default profile.
|
||||
|
|
@ -52,6 +58,13 @@ internal class ProfileManager : IServiceType
|
|||
/// Gets a value indicating whether or not the profile manager is busy enabling/disabling plugins.
|
||||
/// </summary>
|
||||
public bool IsBusy => this.isBusy;
|
||||
|
||||
/// <summary>
|
||||
/// Get a disposable that will lock the profile list while it is not disposed.
|
||||
/// You must NEVER use this in async code.
|
||||
/// </summary>
|
||||
/// <returns>The aforementioned disposable.</returns>
|
||||
public IDisposable GetSyncScope() => new ScopedSyncRoot(this.profiles);
|
||||
|
||||
/// <summary>
|
||||
/// Check if any enabled profile wants a specific plugin enabled.
|
||||
|
|
@ -60,25 +73,29 @@ internal class ProfileManager : IServiceType
|
|||
/// <param name="defaultState">The state the plugin shall be in, if it needs to be added.</param>
|
||||
/// <param name="addIfNotDeclared">Whether or not the plugin should be added to the default preset, if it's not present in any preset.</param>
|
||||
/// <returns>Whether or not the plugin shall be enabled.</returns>
|
||||
public bool GetWantState(string internalName, bool defaultState, bool addIfNotDeclared = true)
|
||||
public async Task<bool> GetWantStateAsync(string internalName, bool defaultState, bool addIfNotDeclared = true)
|
||||
{
|
||||
var want = false;
|
||||
var wasInAnyProfile = false;
|
||||
|
||||
foreach (var profile in this.profiles)
|
||||
|
||||
lock (this.profiles)
|
||||
{
|
||||
var state = profile.WantsPlugin(internalName);
|
||||
if (state.HasValue)
|
||||
foreach (var profile in this.profiles)
|
||||
{
|
||||
want = want || (profile.IsEnabled && state.Value);
|
||||
wasInAnyProfile = true;
|
||||
var state = profile.WantsPlugin(internalName);
|
||||
if (state.HasValue)
|
||||
{
|
||||
want = want || (profile.IsEnabled && state.Value);
|
||||
wasInAnyProfile = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!wasInAnyProfile && addIfNotDeclared)
|
||||
{
|
||||
Log.Warning("{Name} was not in any profile, adding to default with {Default}", internalName, defaultState);
|
||||
this.DefaultProfile.AddOrUpdate(internalName, defaultState, false);
|
||||
await this.DefaultProfile.AddOrUpdateAsync(internalName, defaultState, false);
|
||||
|
||||
return defaultState;
|
||||
}
|
||||
|
||||
|
|
@ -91,7 +108,10 @@ internal class ProfileManager : IServiceType
|
|||
/// <param name="internalName">The internal name of the plugin.</param>
|
||||
/// <returns>Whether or not the plugin is in any profile.</returns>
|
||||
public bool IsInAnyProfile(string internalName)
|
||||
=> this.profiles.Any(x => x.WantsPlugin(internalName) != null);
|
||||
{
|
||||
lock (this.profiles)
|
||||
return this.profiles.Any(x => x.WantsPlugin(internalName) != null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check whether a plugin is only in the default profile.
|
||||
|
|
@ -167,16 +187,24 @@ internal class ProfileManager : IServiceType
|
|||
/// Go through all profiles and plugins, and enable/disable plugins they want active.
|
||||
/// This will block until all plugins have been loaded/reloaded.
|
||||
/// </summary>
|
||||
public void ApplyAllWantStates()
|
||||
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
|
||||
public async Task ApplyAllWantStatesAsync()
|
||||
{
|
||||
if (this.isBusy)
|
||||
throw new Exception("Already busy, this must not run in parallel. Check before starting another apply!");
|
||||
|
||||
this.isBusy = true;
|
||||
Log.Information("Getting want states...");
|
||||
|
||||
var wantActive = this.profiles
|
||||
List<string> wantActive;
|
||||
lock (this.profiles)
|
||||
{
|
||||
wantActive = this.profiles
|
||||
.Where(x => x.IsEnabled)
|
||||
.SelectMany(profile => profile.Plugins.Where(plugin => plugin.IsEnabled)
|
||||
.Select(plugin => plugin.InternalName))
|
||||
.Distinct().ToList();
|
||||
}
|
||||
|
||||
foreach (var internalName in wantActive)
|
||||
{
|
||||
|
|
@ -185,9 +213,9 @@ internal class ProfileManager : IServiceType
|
|||
|
||||
Log.Information("Applying want states...");
|
||||
|
||||
var pm = Service<PluginManager>.Get();
|
||||
var tasks = new List<Task>();
|
||||
|
||||
var pm = Service<PluginManager>.Get();
|
||||
foreach (var installedPlugin in pm.InstalledPlugins)
|
||||
{
|
||||
var wantThis = wantActive.Contains(installedPlugin.Manifest.InternalName);
|
||||
|
|
@ -215,7 +243,7 @@ internal class ProfileManager : IServiceType
|
|||
// This is probably not ideal... Might need to rethink the error handling strategy for this.
|
||||
try
|
||||
{
|
||||
Task.WaitAll(tasks.ToArray());
|
||||
await Task.WhenAll(tasks.ToArray());
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
|
|
@ -233,12 +261,13 @@ internal class ProfileManager : IServiceType
|
|||
/// You should definitely apply states after this. It doesn't do it for you.
|
||||
/// </remarks>
|
||||
/// <param name="profile">The profile to delete.</param>
|
||||
public void DeleteProfile(Profile profile)
|
||||
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
|
||||
public async Task DeleteProfileAsync(Profile profile)
|
||||
{
|
||||
// We need to remove all plugins from the profile first, so that they are re-added to the default profile if needed
|
||||
foreach (var plugin in profile.Plugins.ToArray())
|
||||
{
|
||||
profile.Remove(plugin.InternalName, false);
|
||||
await profile.RemoveAsync(plugin.InternalName, false);
|
||||
}
|
||||
|
||||
if (!this.config.SavedProfiles!.Remove(profile.Model))
|
||||
|
|
|
|||
|
|
@ -213,7 +213,7 @@ internal class LocalPlugin : IDisposable
|
|||
/// INCLUDES the default profile.
|
||||
/// </summary>
|
||||
public bool IsWantedByAnyProfile =>
|
||||
Service<ProfileManager>.Get().GetWantState(this.Manifest.InternalName, false, false);
|
||||
Service<ProfileManager>.Get().GetWantStateAsync(this.Manifest.InternalName, false, false).GetAwaiter().GetResult();
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether this plugin's API level is out of date.
|
||||
|
|
@ -630,7 +630,7 @@ internal class LocalPlugin : IDisposable
|
|||
if (manifest.Exists)
|
||||
{
|
||||
// var isDisabled = this.IsDisabled; // saving the internal state because it could have been deleted
|
||||
this.Manifest = LocalPluginManifest.Load(manifest);
|
||||
this.Manifest = LocalPluginManifest.Load(manifest) ?? throw new Exception("Could not reload manifest.");
|
||||
// this.Manifest.Disabled = isDisabled;
|
||||
|
||||
this.SaveManifest();
|
||||
|
|
|
|||
|
|
@ -76,7 +76,7 @@ internal record LocalPluginManifest : PluginManifest
|
|||
/// </summary>
|
||||
/// <param name="manifestFile">Path to the manifest.</param>
|
||||
/// <returns>A <see cref="PluginManifest"/> object.</returns>
|
||||
public static LocalPluginManifest Load(FileInfo manifestFile) => JsonConvert.DeserializeObject<LocalPluginManifest>(File.ReadAllText(manifestFile.FullName))!;
|
||||
public static LocalPluginManifest? Load(FileInfo manifestFile) => JsonConvert.DeserializeObject<LocalPluginManifest>(File.ReadAllText(manifestFile.FullName));
|
||||
|
||||
/// <summary>
|
||||
/// A standardized way to get the plugin DLL name that should accompany a manifest file. May not exist.
|
||||
|
|
|
|||
29
Dalamud/Utility/ScopedSyncRoot.cs
Normal file
29
Dalamud/Utility/ScopedSyncRoot.cs
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
using System;
|
||||
using System.Threading;
|
||||
|
||||
namespace Dalamud.Utility;
|
||||
|
||||
/// <summary>
|
||||
/// Scope for plugin list locks.
|
||||
/// </summary>
|
||||
public class ScopedSyncRoot : IDisposable
|
||||
{
|
||||
private readonly object lockObj;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ScopedSyncRoot"/> class.
|
||||
/// </summary>
|
||||
/// <param name="lockObj">The object to lock.</param>
|
||||
public ScopedSyncRoot(object lockObj)
|
||||
{
|
||||
this.lockObj = lockObj;
|
||||
Monitor.Enter(lockObj);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Dispose()
|
||||
{
|
||||
GC.SuppressFinalize(this);
|
||||
Monitor.Exit(this.lockObj);
|
||||
}
|
||||
}
|
||||
|
|
@ -13,6 +13,7 @@ using System.Text;
|
|||
using Dalamud.Configuration.Internal;
|
||||
using Dalamud.Data;
|
||||
using Dalamud.Game;
|
||||
using Dalamud.Game.ClientState.Objects.SubKinds;
|
||||
using Dalamud.Game.ClientState.Objects.Types;
|
||||
using Dalamud.Interface;
|
||||
using Dalamud.Interface.Colors;
|
||||
|
|
@ -36,7 +37,7 @@ public static class Util
|
|||
|
||||
private static ulong moduleStartAddr;
|
||||
private static ulong moduleEndAddr;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Gets the assembly version of Dalamud.
|
||||
/// </summary>
|
||||
|
|
@ -650,6 +651,40 @@ public static class Util
|
|||
return names.ElementAt(rng.Next(0, names.Count() - 1)).Singular.RawString;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Print formatted GameObject Information to ImGui
|
||||
/// </summary>
|
||||
/// <param name="actor">Game Object to Display.</param>
|
||||
/// <param name="tag">Display Tag.</param>
|
||||
/// <param name="resolveGameData">If the GameObjects data should be resolved.</param>
|
||||
internal static void PrintGameObject(GameObject actor, string tag, bool resolveGameData)
|
||||
{
|
||||
var actorString =
|
||||
$"{actor.Address.ToInt64():X}:{actor.ObjectId:X}[{tag}] - {actor.ObjectKind} - {actor.Name} - X{actor.Position.X} Y{actor.Position.Y} Z{actor.Position.Z} D{actor.YalmDistanceX} R{actor.Rotation} - Target: {actor.TargetObjectId:X}\n";
|
||||
|
||||
if (actor is Npc npc)
|
||||
actorString += $" DataId: {npc.DataId} NameId:{npc.NameId}\n";
|
||||
|
||||
if (actor is Character chara)
|
||||
{
|
||||
actorString +=
|
||||
$" Level: {chara.Level} ClassJob: {(resolveGameData ? chara.ClassJob.GameData?.Name : chara.ClassJob.Id.ToString())} CHP: {chara.CurrentHp} MHP: {chara.MaxHp} CMP: {chara.CurrentMp} MMP: {chara.MaxMp}\n Customize: {BitConverter.ToString(chara.Customize).Replace("-", " ")} StatusFlags: {chara.StatusFlags}\n";
|
||||
}
|
||||
|
||||
if (actor is PlayerCharacter pc)
|
||||
{
|
||||
actorString +=
|
||||
$" HomeWorld: {(resolveGameData ? pc.HomeWorld.GameData?.Name : pc.HomeWorld.Id.ToString())} CurrentWorld: {(resolveGameData ? pc.CurrentWorld.GameData?.Name : pc.CurrentWorld.Id.ToString())} FC: {pc.CompanyTag}\n";
|
||||
}
|
||||
|
||||
ImGui.TextUnformatted(actorString);
|
||||
ImGui.SameLine();
|
||||
if (ImGui.Button($"C##{actor.Address.ToInt64()}"))
|
||||
{
|
||||
ImGui.SetClipboardText(actor.Address.ToInt64().ToString("X"));
|
||||
}
|
||||
}
|
||||
|
||||
private static unsafe void ShowValue(ulong addr, IEnumerable<string> path, Type type, object value)
|
||||
{
|
||||
if (type.IsPointer)
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
Subproject commit 130a4df5298e9bcdf343ce1d93ee0afdafac587f
|
||||
Subproject commit f2abb4a11319b26b77cd29b69a52b34e1d56069d
|
||||
Loading…
Add table
Add a link
Reference in a new issue