mirror of
https://github.com/goatcorp/Dalamud.git
synced 2025-12-12 18:27:23 +01:00
Merge branch 'net5'
This commit is contained in:
commit
6dcddb1f29
30 changed files with 2600 additions and 2349 deletions
|
|
@ -123,10 +123,12 @@ resharper_foreach_can_be_partly_converted_to_query_using_another_get_enumerator_
|
|||
resharper_invert_if_highlighting = none
|
||||
resharper_loop_can_be_converted_to_query_highlighting = none
|
||||
resharper_method_has_async_overload_highlighting = none
|
||||
resharper_private_field_can_be_converted_to_local_variable_highlighting = none
|
||||
resharper_redundant_base_qualifier_highlighting = none
|
||||
resharper_suggest_var_or_type_built_in_types_highlighting = hint
|
||||
resharper_suggest_var_or_type_elsewhere_highlighting = hint
|
||||
resharper_suggest_var_or_type_simple_types_highlighting = hint
|
||||
resharper_unused_auto_property_accessor_global_highlighting = none
|
||||
csharp_style_deconstructed_variable_declaration=true:silent
|
||||
|
||||
[*.{appxmanifest,asax,ascx,aspx,axaml,axml,build,c,c++,cc,cginc,compute,config,cp,cpp,cs,cshtml,csproj,css,cu,cuh,cxx,dbml,discomap,dtd,h,hh,hlsl,hlsli,hlslinc,hpp,htm,html,hxx,inc,inl,ino,ipp,js,json,jsproj,jsx,lsproj,master,mpp,mq4,mq5,mqh,njsproj,nuspec,paml,proj,props,proto,razor,resjson,resw,resx,skin,StyleCop,targets,tasks,tpp,ts,tsx,usf,ush,vb,vbproj,xaml,xamlx,xml,xoml,xsd}]
|
||||
|
|
|
|||
1
.github/workflows/tag-build.yml
vendored
1
.github/workflows/tag-build.yml
vendored
|
|
@ -1,6 +1,5 @@
|
|||
name: Tag Build
|
||||
on: [push]
|
||||
concurrency: build_dalamud
|
||||
|
||||
jobs:
|
||||
tag:
|
||||
|
|
|
|||
|
|
@ -37,9 +37,9 @@ namespace Dalamud.Injector
|
|||
/// <param name="argvPtr">byte** string arguments.</param>
|
||||
public static void Main(int argc, IntPtr argvPtr)
|
||||
{
|
||||
Init();
|
||||
|
||||
List<string> args = new(argc);
|
||||
Init(args);
|
||||
|
||||
unsafe
|
||||
{
|
||||
var argv = (IntPtr*)argvPtr;
|
||||
|
|
@ -59,7 +59,11 @@ namespace Dalamud.Injector
|
|||
// No command defaults to inject
|
||||
args.Add("inject");
|
||||
args.Add("--all");
|
||||
|
||||
#if !DEBUG
|
||||
args.Add("--warn");
|
||||
#endif
|
||||
|
||||
}
|
||||
else if (int.TryParse(args[1], out var _))
|
||||
{
|
||||
|
|
@ -92,15 +96,13 @@ namespace Dalamud.Injector
|
|||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine("Invalid command: {0}", mainCommand);
|
||||
ProcessHelpCommand(args);
|
||||
Environment.Exit(-1);
|
||||
throw new CommandLineException($"\"{mainCommand}\" is not a valid command.");
|
||||
}
|
||||
}
|
||||
|
||||
private static void Init()
|
||||
private static void Init(List<string> args)
|
||||
{
|
||||
InitUnhandledException();
|
||||
InitUnhandledException(args);
|
||||
InitLogging();
|
||||
|
||||
var cwd = new FileInfo(Assembly.GetExecutingAssembly().Location).Directory;
|
||||
|
|
@ -111,25 +113,31 @@ namespace Dalamud.Injector
|
|||
}
|
||||
}
|
||||
|
||||
private static void InitUnhandledException()
|
||||
private static void InitUnhandledException(List<string> args)
|
||||
{
|
||||
AppDomain.CurrentDomain.UnhandledException += (sender, eventArgs) =>
|
||||
{
|
||||
if (Log.Logger == null)
|
||||
var exObj = eventArgs.ExceptionObject;
|
||||
|
||||
if (exObj is CommandLineException clex)
|
||||
{
|
||||
Console.WriteLine();
|
||||
Console.WriteLine("Command line error: {0}", clex.Message);
|
||||
Console.WriteLine();
|
||||
ProcessHelpCommand(args);
|
||||
Environment.Exit(-1);
|
||||
}
|
||||
else if (Log.Logger == null)
|
||||
{
|
||||
Console.WriteLine($"A fatal error has occurred: {eventArgs.ExceptionObject}");
|
||||
}
|
||||
else if (exObj is Exception ex)
|
||||
{
|
||||
Log.Error(ex, "A fatal error has occurred.");
|
||||
}
|
||||
else
|
||||
{
|
||||
var exObj = eventArgs.ExceptionObject;
|
||||
if (exObj is Exception ex)
|
||||
{
|
||||
Log.Error(ex, "A fatal error has occurred.");
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.Error($"A fatal error has occurred: {eventArgs.ExceptionObject}");
|
||||
}
|
||||
Log.Error($"A fatal error has occurred: {eventArgs.ExceptionObject}");
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
|
|
@ -146,7 +154,7 @@ namespace Dalamud.Injector
|
|||
#endif
|
||||
_ = MessageBoxW(IntPtr.Zero, message, caption, MessageBoxType.IconError | MessageBoxType.Ok);
|
||||
|
||||
Environment.Exit(0);
|
||||
Environment.Exit(-1);
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -225,6 +233,9 @@ namespace Dalamud.Injector
|
|||
|
||||
private static DalamudStartInfo ExtractAndInitializeStartInfoFromArguments(DalamudStartInfo? startInfo, List<string> args)
|
||||
{
|
||||
int len;
|
||||
string key;
|
||||
|
||||
if (startInfo == null)
|
||||
startInfo = new();
|
||||
|
||||
|
|
@ -234,10 +245,10 @@ namespace Dalamud.Injector
|
|||
var defaultPluginDirectory = startInfo.DefaultPluginDirectory;
|
||||
var assetDirectory = startInfo.AssetDirectory;
|
||||
var delayInitializeMs = startInfo.DelayInitializeMs;
|
||||
var languageStr = startInfo.Language.ToString().ToLowerInvariant();
|
||||
|
||||
for (var i = 2; i < args.Count; i++)
|
||||
{
|
||||
string key;
|
||||
if (args[i].StartsWith(key = "--dalamud-working-directory="))
|
||||
workingDirectory = args[i][key.Length..];
|
||||
else if (args[i].StartsWith(key = "--dalamud-configuration-path="))
|
||||
|
|
@ -250,6 +261,8 @@ namespace Dalamud.Injector
|
|||
assetDirectory = args[i][key.Length..];
|
||||
else if (args[i].StartsWith(key = "--dalamud-delay-initialize="))
|
||||
delayInitializeMs = int.Parse(args[i][key.Length..]);
|
||||
else if (args[i].StartsWith(key = "--dalamud-client-language="))
|
||||
languageStr = args[i][key.Length..].ToLowerInvariant();
|
||||
else
|
||||
continue;
|
||||
|
||||
|
|
@ -266,6 +279,26 @@ namespace Dalamud.Injector
|
|||
defaultPluginDirectory ??= Path.Combine(xivlauncherDir, "devPlugins");
|
||||
assetDirectory ??= Path.Combine(xivlauncherDir, "dalamudAssets", "dev");
|
||||
|
||||
ClientLanguage clientLanguage;
|
||||
if (languageStr[0..(len = Math.Min(languageStr.Length, (key = "english").Length))] == key[0..len])
|
||||
clientLanguage = ClientLanguage.English;
|
||||
else if (languageStr[0..(len = Math.Min(languageStr.Length, (key = "japanese").Length))] == key[0..len])
|
||||
clientLanguage = ClientLanguage.Japanese;
|
||||
else if (languageStr[0..(len = Math.Min(languageStr.Length, (key = "日本語").Length))] == key[0..len])
|
||||
clientLanguage = ClientLanguage.Japanese;
|
||||
else if (languageStr[0..(len = Math.Min(languageStr.Length, (key = "german").Length))] == key[0..len])
|
||||
clientLanguage = ClientLanguage.German;
|
||||
else if (languageStr[0..(len = Math.Min(languageStr.Length, (key = "deutsche").Length))] == key[0..len])
|
||||
clientLanguage = ClientLanguage.German;
|
||||
else if (languageStr[0..(len = Math.Min(languageStr.Length, (key = "french").Length))] == key[0..len])
|
||||
clientLanguage = ClientLanguage.French;
|
||||
else if (languageStr[0..(len = Math.Min(languageStr.Length, (key = "français").Length))] == key[0..len])
|
||||
clientLanguage = ClientLanguage.French;
|
||||
else if (int.TryParse(languageStr, out var languageInt) && Enum.IsDefined((ClientLanguage)languageInt))
|
||||
clientLanguage = (ClientLanguage)languageInt;
|
||||
else
|
||||
throw new CommandLineException($"\"{languageStr}\" is not a valid supported language.");
|
||||
|
||||
return new()
|
||||
{
|
||||
WorkingDirectory = workingDirectory,
|
||||
|
|
@ -273,7 +306,7 @@ namespace Dalamud.Injector
|
|||
PluginDirectory = pluginDirectory,
|
||||
DefaultPluginDirectory = defaultPluginDirectory,
|
||||
AssetDirectory = assetDirectory,
|
||||
Language = ClientLanguage.English,
|
||||
Language = clientLanguage,
|
||||
GameVersion = null,
|
||||
DelayInitializeMs = delayInitializeMs,
|
||||
};
|
||||
|
|
@ -299,12 +332,14 @@ namespace Dalamud.Injector
|
|||
Console.WriteLine("{0} [-g path/to/ffxiv_dx11.exe] [--game=path/to/ffxiv_dx11.exe]", exeSpaces);
|
||||
Console.WriteLine("{0} [-m entrypoint|inject] [--mode=entrypoint|inject]", exeSpaces);
|
||||
Console.WriteLine("{0} [--handle-owner=inherited-handle-value]", exeSpaces);
|
||||
Console.WriteLine("{0} [--without-dalamud]", exeSpaces);
|
||||
Console.WriteLine("{0} [-- game_arg1=value1 game_arg2=value2 ...]", exeSpaces);
|
||||
}
|
||||
|
||||
Console.WriteLine("Specifying dalamud start info: [--dalamud-working-directory path] [--dalamud-configuration-path path]");
|
||||
Console.WriteLine(" [--dalamud-plugin-directory path] [--dalamud-dev-plugin-directory path]");
|
||||
Console.WriteLine(" [--dalamud-asset-directory path] [--dalamud-delay-initialize 0(ms)]");
|
||||
Console.WriteLine("Specifying dalamud start info: [--dalamud-working-directory=path] [--dalamud-configuration-path=path]");
|
||||
Console.WriteLine(" [--dalamud-plugin-directory=path] [--dalamud-dev-plugin-directory=path]");
|
||||
Console.WriteLine(" [--dalamud-asset-directory=path] [--dalamud-delay-initialize=0(ms)]");
|
||||
Console.WriteLine(" [--dalamud-client-language=0-3|j(apanese)|e(nglish)|d|g(erman)|f(rench)]");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -349,8 +384,7 @@ namespace Dalamud.Injector
|
|||
}
|
||||
else
|
||||
{
|
||||
Log.Error("\"{0}\" is not a valid argument.", args[i]);
|
||||
return -1;
|
||||
throw new CommandLineException($"\"{args[i]}\" is not a command line argument.");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -362,8 +396,7 @@ namespace Dalamud.Injector
|
|||
|
||||
if (!targetProcessSpecified)
|
||||
{
|
||||
Log.Error("No target process has been specified.");
|
||||
return -1;
|
||||
throw new CommandLineException("No target process has been specified. Use -a(--all) option to inject to all ffxiv_dx11.exe processes.");
|
||||
}
|
||||
else if (!processes.Any())
|
||||
{
|
||||
|
|
@ -397,6 +430,7 @@ namespace Dalamud.Injector
|
|||
var useFakeArguments = false;
|
||||
var showHelp = args.Count <= 2;
|
||||
var handleOwner = IntPtr.Zero;
|
||||
var withoutDalamud = false;
|
||||
|
||||
var parsingGameArgument = false;
|
||||
for (var i = 2; i < args.Count; i++)
|
||||
|
|
@ -408,42 +442,25 @@ namespace Dalamud.Injector
|
|||
}
|
||||
|
||||
if (args[i] == "-h" || args[i] == "--help")
|
||||
{
|
||||
showHelp = true;
|
||||
}
|
||||
else if (args[i] == "-f" || args[i] == "--fake-arguments")
|
||||
{
|
||||
useFakeArguments = true;
|
||||
}
|
||||
else if (args[i] == "--without-dalamud")
|
||||
withoutDalamud = true;
|
||||
else if (args[i] == "-g")
|
||||
{
|
||||
gamePath = args[++i];
|
||||
}
|
||||
else if (args[i].StartsWith("--game="))
|
||||
{
|
||||
gamePath = args[i].Split('=', 2)[1];
|
||||
}
|
||||
else if (args[i] == "-m")
|
||||
{
|
||||
mode = args[++i];
|
||||
}
|
||||
else if (args[i].StartsWith("--mode="))
|
||||
{
|
||||
gamePath = args[i].Split('=', 2)[1];
|
||||
}
|
||||
mode = args[i].Split('=', 2)[1];
|
||||
else if (args[i].StartsWith("--handle-owner="))
|
||||
{
|
||||
handleOwner = IntPtr.Parse(args[i].Split('=', 2)[1]);
|
||||
}
|
||||
else if (args[i] == "--")
|
||||
{
|
||||
parsingGameArgument = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.Error("No such command found: {0}", args[i]);
|
||||
return -1;
|
||||
}
|
||||
throw new CommandLineException($"\"{args[i]}\" is not a command line argument.");
|
||||
}
|
||||
|
||||
if (showHelp)
|
||||
|
|
@ -463,8 +480,7 @@ namespace Dalamud.Injector
|
|||
}
|
||||
else
|
||||
{
|
||||
Log.Error("Invalid mode: {0}", mode);
|
||||
return -1;
|
||||
throw new CommandLineException($"\"{mode}\" is not a valid Dalamud load mode.");
|
||||
}
|
||||
|
||||
if (gamePath == null)
|
||||
|
|
@ -522,7 +538,7 @@ namespace Dalamud.Injector
|
|||
"DEV.LobbyHost09=127.0.0.9",
|
||||
"DEV.LobbyPort09=54994",
|
||||
"SYS.Region=0",
|
||||
"language=1",
|
||||
$"language={(int)dalamudStartInfo.Language}",
|
||||
$"ver={gameVersion}",
|
||||
$"DEV.MaxEntitledExpansionID={maxEntitledExpansionId}",
|
||||
"DEV.GMServerHost=127.0.0.100",
|
||||
|
|
@ -533,7 +549,7 @@ namespace Dalamud.Injector
|
|||
var gameArgumentString = string.Join(" ", gameArguments.Select(x => EncodeParameterArgument(x)));
|
||||
var process = NativeAclFix.LaunchGame(Path.GetDirectoryName(gamePath), gamePath, gameArgumentString, (Process p) =>
|
||||
{
|
||||
if (mode == "entrypoint")
|
||||
if (!withoutDalamud && mode == "entrypoint")
|
||||
{
|
||||
var startInfo = AdjustStartInfo(dalamudStartInfo, gamePath);
|
||||
Log.Information("Using start info: {0}", JsonConvert.SerializeObject(startInfo));
|
||||
|
|
@ -545,7 +561,7 @@ namespace Dalamud.Injector
|
|||
}
|
||||
});
|
||||
|
||||
if (mode == "inject")
|
||||
if (!withoutDalamud && mode == "inject")
|
||||
{
|
||||
var startInfo = AdjustStartInfo(dalamudStartInfo, gamePath);
|
||||
Log.Information("Using start info: {0}", JsonConvert.SerializeObject(startInfo));
|
||||
|
|
@ -625,7 +641,7 @@ namespace Dalamud.Injector
|
|||
PluginDirectory = startInfo.PluginDirectory,
|
||||
DefaultPluginDirectory = startInfo.DefaultPluginDirectory,
|
||||
AssetDirectory = startInfo.AssetDirectory,
|
||||
Language = ClientLanguage.English,
|
||||
Language = startInfo.Language,
|
||||
GameVersion = gameVer,
|
||||
DelayInitializeMs = startInfo.DelayInitializeMs,
|
||||
};
|
||||
|
|
@ -734,5 +750,13 @@ namespace Dalamud.Injector
|
|||
|
||||
return quoted.ToString();
|
||||
}
|
||||
|
||||
private class CommandLineException : Exception
|
||||
{
|
||||
public CommandLineException(string cause)
|
||||
: base(cause)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -50,11 +50,15 @@
|
|||
<s:Boolean x:Key="/Default/UserDictionary/Words/=bannedplugin/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=clientopcode/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Dalamud/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=FFXIV/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Flytext/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Gpose/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=LOCALPLUGIN/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=lumina/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Materia/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=PLUGINM/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=pluginmaster/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=PLUGINR/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Refilter/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=serveropcode/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Universalis/@EntryIndexedValue">True</s:Boolean>
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@
|
|||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Label="Feature">
|
||||
<DalamudVersion>6.4.0.6</DalamudVersion>
|
||||
<DalamudVersion>6.4.0.9</DalamudVersion>
|
||||
<Description>XIV Launcher addon framework</Description>
|
||||
<AssemblyVersion>$(DalamudVersion)</AssemblyVersion>
|
||||
<Version>$(DalamudVersion)</Version>
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ using System.Diagnostics;
|
|||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Dalamud.Game.Gui;
|
||||
using Dalamud.Game.Gui.Toast;
|
||||
|
|
@ -26,7 +27,9 @@ namespace Dalamud.Game
|
|||
public sealed class Framework : IDisposable
|
||||
{
|
||||
private static Stopwatch statsStopwatch = new();
|
||||
private Stopwatch updateStopwatch = new();
|
||||
|
||||
private readonly List<RunOnNextTickTaskBase> runOnNextTickTaskList = new();
|
||||
private readonly Stopwatch updateStopwatch = new();
|
||||
|
||||
private bool tier2Initialized = false;
|
||||
private bool tier3Initialized = false;
|
||||
|
|
@ -36,6 +39,8 @@ namespace Dalamud.Game
|
|||
private Hook<OnDestroyDetour> destroyHook;
|
||||
private Hook<OnRealDestroyDelegate> realDestroyHook;
|
||||
|
||||
private Thread? frameworkUpdateThread;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Framework"/> class.
|
||||
/// </summary>
|
||||
|
|
@ -113,6 +118,11 @@ namespace Dalamud.Game
|
|||
/// </summary>
|
||||
public TimeSpan UpdateDelta { get; private set; } = TimeSpan.Zero;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether currently executing code is running in the game's framework update thread.
|
||||
/// </summary>
|
||||
public bool IsInFrameworkUpdateThread => Thread.CurrentThread == this.frameworkUpdateThread;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether to dispatch update events.
|
||||
/// </summary>
|
||||
|
|
@ -132,6 +142,84 @@ namespace Dalamud.Game
|
|||
this.realDestroyHook.Enable();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Run given function right away if this function has been called from game's Framework.Update thread, or otherwise run on next Framework.Update call.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Return type.</typeparam>
|
||||
/// <param name="func">Function to call.</param>
|
||||
/// <returns>Task representing the pending or already completed function.</returns>
|
||||
public Task<T> RunOnFrameworkThread<T>(Func<T> func) => this.IsInFrameworkUpdateThread ? Task.FromResult(func()) : this.RunOnTick(func);
|
||||
|
||||
/// <summary>
|
||||
/// Run given function right away if this function has been called from game's Framework.Update thread, or otherwise run on next Framework.Update call.
|
||||
/// </summary>
|
||||
/// <param name="action">Function to call.</param>
|
||||
/// <returns>Task representing the pending or already completed function.</returns>
|
||||
public Task RunOnFrameworkThread(Action action)
|
||||
{
|
||||
if (this.IsInFrameworkUpdateThread)
|
||||
{
|
||||
try
|
||||
{
|
||||
action();
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return Task.FromException(ex);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return this.RunOnTick(action);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Run given function in upcoming Framework.Tick call.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Return type.</typeparam>
|
||||
/// <param name="func">Function to call.</param>
|
||||
/// <param name="delay">Wait for given timespan before calling this function.</param>
|
||||
/// <param name="delayTicks">Count given number of Framework.Tick calls before calling this function. This takes precedence over delay parameter.</param>
|
||||
/// <param name="cancellationToken">Cancellation token which will prevent the execution of this function if wait conditions are not met.</param>
|
||||
/// <returns>Task representing the pending function.</returns>
|
||||
public Task<T> RunOnTick<T>(Func<T> func, TimeSpan delay = default, int delayTicks = default, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var tcs = new TaskCompletionSource<T>();
|
||||
this.runOnNextTickTaskList.Add(new RunOnNextTickTaskFunc<T>()
|
||||
{
|
||||
RemainingTicks = delayTicks,
|
||||
RunAfterTickCount = Environment.TickCount64 + (long)Math.Ceiling(delay.TotalMilliseconds),
|
||||
CancellationToken = cancellationToken,
|
||||
TaskCompletionSource = tcs,
|
||||
Func = func,
|
||||
});
|
||||
return tcs.Task;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Run given function in upcoming Framework.Tick call.
|
||||
/// </summary>
|
||||
/// <param name="action">Function to call.</param>
|
||||
/// <param name="delay">Wait for given timespan before calling this function.</param>
|
||||
/// <param name="delayTicks">Count given number of Framework.Tick calls before calling this function. This takes precedence over delay parameter.</param>
|
||||
/// <param name="cancellationToken">Cancellation token which will prevent the execution of this function if wait conditions are not met.</param>
|
||||
/// <returns>Task representing the pending function.</returns>
|
||||
public Task RunOnTick(Action action, TimeSpan delay = default, int delayTicks = default, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var tcs = new TaskCompletionSource();
|
||||
this.runOnNextTickTaskList.Add(new RunOnNextTickTaskAction()
|
||||
{
|
||||
RemainingTicks = delayTicks,
|
||||
RunAfterTickCount = Environment.TickCount64 + (long)Math.Ceiling(delay.TotalMilliseconds),
|
||||
CancellationToken = cancellationToken,
|
||||
TaskCompletionSource = tcs,
|
||||
Action = action,
|
||||
});
|
||||
return tcs.Task;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Dispose of managed and unmanaged resources.
|
||||
/// </summary>
|
||||
|
|
@ -179,6 +267,8 @@ namespace Dalamud.Game
|
|||
if (this.tierInitError)
|
||||
goto original;
|
||||
|
||||
this.frameworkUpdateThread ??= Thread.CurrentThread;
|
||||
|
||||
var dalamud = Service<Dalamud>.Get();
|
||||
|
||||
// If this is the first time we are running this loop, we need to init Dalamud subsystems synchronously
|
||||
|
|
@ -223,6 +313,8 @@ namespace Dalamud.Game
|
|||
|
||||
try
|
||||
{
|
||||
this.runOnNextTickTaskList.RemoveAll(x => x.Run());
|
||||
|
||||
if (StatsEnabled && this.Update != null)
|
||||
{
|
||||
// Stat Tracking for Framework Updates
|
||||
|
|
@ -312,5 +404,88 @@ namespace Dalamud.Game
|
|||
// Return the original trampoline location to cleanly exit
|
||||
return originalPtr;
|
||||
}
|
||||
|
||||
private abstract class RunOnNextTickTaskBase
|
||||
{
|
||||
internal int RemainingTicks { get; set; }
|
||||
|
||||
internal long RunAfterTickCount { get; init; }
|
||||
|
||||
internal CancellationToken CancellationToken { get; init; }
|
||||
|
||||
internal bool Run()
|
||||
{
|
||||
if (this.CancellationToken.IsCancellationRequested)
|
||||
{
|
||||
this.CancelImpl();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (this.RemainingTicks > 0)
|
||||
this.RemainingTicks -= 1;
|
||||
if (this.RemainingTicks > 0)
|
||||
return false;
|
||||
|
||||
if (this.RunAfterTickCount > Environment.TickCount64)
|
||||
return false;
|
||||
|
||||
this.RunImpl();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected abstract void RunImpl();
|
||||
|
||||
protected abstract void CancelImpl();
|
||||
}
|
||||
|
||||
private class RunOnNextTickTaskFunc<T> : RunOnNextTickTaskBase
|
||||
{
|
||||
internal TaskCompletionSource<T> TaskCompletionSource { get; init; }
|
||||
|
||||
internal Func<T> Func { get; init; }
|
||||
|
||||
protected override void RunImpl()
|
||||
{
|
||||
try
|
||||
{
|
||||
this.TaskCompletionSource.SetResult(this.Func());
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
this.TaskCompletionSource.SetException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void CancelImpl()
|
||||
{
|
||||
this.TaskCompletionSource.SetCanceled();
|
||||
}
|
||||
}
|
||||
|
||||
private class RunOnNextTickTaskAction : RunOnNextTickTaskBase
|
||||
{
|
||||
internal TaskCompletionSource TaskCompletionSource { get; init; }
|
||||
|
||||
internal Action Action { get; init; }
|
||||
|
||||
protected override void RunImpl()
|
||||
{
|
||||
try
|
||||
{
|
||||
this.Action();
|
||||
this.TaskCompletionSource.SetResult();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
this.TaskCompletionSource.SetException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void CancelImpl()
|
||||
{
|
||||
this.TaskCompletionSource.SetCanceled();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ using System.Text;
|
|||
|
||||
using Dalamud.Data;
|
||||
using Dalamud.Interface.Internal;
|
||||
using Dalamud.Utility;
|
||||
using ImGuiNET;
|
||||
using Lumina.Data.Files;
|
||||
using Serilog;
|
||||
|
|
@ -38,6 +39,9 @@ namespace Dalamud.Interface.GameFonts
|
|||
private readonly Dictionary<GameFontStyle, int> fontUseCounter = new();
|
||||
private readonly Dictionary<GameFontStyle, Dictionary<char, Tuple<int, FdtReader.FontTableEntry>>> glyphRectIds = new();
|
||||
|
||||
private bool isBetweenBuildFontsAndAfterBuildFonts = false;
|
||||
private bool isBuildingAsFallbackFontMode = false;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="GameFontManager"/> class.
|
||||
/// </summary>
|
||||
|
|
@ -110,65 +114,6 @@ namespace Dalamud.Interface.GameFonts
|
|||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fills missing glyphs in target font from source font, if both are not null.
|
||||
/// </summary>
|
||||
/// <param name="source">Source font.</param>
|
||||
/// <param name="target">Target font.</param>
|
||||
/// <param name="missingOnly">Whether to copy missing glyphs only.</param>
|
||||
/// <param name="rebuildLookupTable">Whether to call target.BuildLookupTable().</param>
|
||||
/// <param name="rangeLow">Low codepoint range to copy.</param>
|
||||
/// <param name="rangeHigh">High codepoing range to copy.</param>
|
||||
public static void CopyGlyphsAcrossFonts(ImFontPtr? source, ImFontPtr? target, bool missingOnly, bool rebuildLookupTable, int rangeLow = 32, int rangeHigh = 0xFFFE)
|
||||
{
|
||||
if (!source.HasValue || !target.HasValue)
|
||||
return;
|
||||
|
||||
var scale = target.Value!.FontSize / source.Value!.FontSize;
|
||||
unsafe
|
||||
{
|
||||
var glyphs = (ImFontGlyphReal*)source.Value!.Glyphs.Data;
|
||||
for (int j = 0, j_ = source.Value!.Glyphs.Size; j < j_; j++)
|
||||
{
|
||||
var glyph = &glyphs[j];
|
||||
if (glyph->Codepoint < rangeLow || glyph->Codepoint > rangeHigh)
|
||||
continue;
|
||||
|
||||
var prevGlyphPtr = (ImFontGlyphReal*)target.Value!.FindGlyphNoFallback((ushort)glyph->Codepoint).NativePtr;
|
||||
if ((IntPtr)prevGlyphPtr == IntPtr.Zero)
|
||||
{
|
||||
target.Value!.AddGlyph(
|
||||
target.Value!.ConfigData,
|
||||
(ushort)glyph->Codepoint,
|
||||
glyph->X0 * scale,
|
||||
((glyph->Y0 - source.Value!.Ascent) * scale) + target.Value!.Ascent,
|
||||
glyph->X1 * scale,
|
||||
((glyph->Y1 - source.Value!.Ascent) * scale) + target.Value!.Ascent,
|
||||
glyph->U0,
|
||||
glyph->V0,
|
||||
glyph->U1,
|
||||
glyph->V1,
|
||||
glyph->AdvanceX * scale);
|
||||
}
|
||||
else if (!missingOnly)
|
||||
{
|
||||
prevGlyphPtr->X0 = glyph->X0 * scale;
|
||||
prevGlyphPtr->Y0 = ((glyph->Y0 - source.Value!.Ascent) * scale) + target.Value!.Ascent;
|
||||
prevGlyphPtr->X1 = glyph->X1 * scale;
|
||||
prevGlyphPtr->Y1 = ((glyph->Y1 - source.Value!.Ascent) * scale) + target.Value!.Ascent;
|
||||
prevGlyphPtr->U0 = glyph->U0;
|
||||
prevGlyphPtr->V0 = glyph->V0;
|
||||
prevGlyphPtr->U1 = glyph->U1;
|
||||
prevGlyphPtr->V1 = glyph->V1;
|
||||
prevGlyphPtr->AdvanceX = glyph->AdvanceX * scale;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (rebuildLookupTable)
|
||||
target.Value!.BuildLookupTable();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unscales fonts after they have been rendered onto atlas.
|
||||
/// </summary>
|
||||
|
|
@ -191,7 +136,7 @@ namespace Dalamud.Interface.GameFonts
|
|||
font->Descent /= fontScale;
|
||||
if (font->ConfigData != null)
|
||||
font->ConfigData->SizePixels /= fontScale;
|
||||
var glyphs = (ImFontGlyphReal*)font->Glyphs.Data;
|
||||
var glyphs = (ImGuiHelpers.ImFontGlyphReal*)font->Glyphs.Data;
|
||||
for (int i = 0, i_ = font->Glyphs.Size; i < i_; i++)
|
||||
{
|
||||
var glyph = &glyphs[i];
|
||||
|
|
@ -223,15 +168,22 @@ namespace Dalamud.Interface.GameFonts
|
|||
|
||||
lock (this.syncRoot)
|
||||
{
|
||||
var prevValue = this.fontUseCounter.GetValueOrDefault(style, 0);
|
||||
var newValue = this.fontUseCounter[style] = prevValue + 1;
|
||||
needRebuild = (prevValue == 0) != (newValue == 0) && !this.fonts.ContainsKey(style);
|
||||
this.fontUseCounter[style] = this.fontUseCounter.GetValueOrDefault(style, 0) + 1;
|
||||
}
|
||||
|
||||
needRebuild = !this.fonts.ContainsKey(style);
|
||||
if (needRebuild)
|
||||
{
|
||||
Log.Information("[GameFontManager] Calling RebuildFonts because {0} has been requested.", style.ToString());
|
||||
this.interfaceManager.RebuildFonts();
|
||||
if (Service<InterfaceManager>.Get().IsBuildingFontsBeforeAtlasBuild && this.isBetweenBuildFontsAndAfterBuildFonts)
|
||||
{
|
||||
Log.Information("[GameFontManager] NewFontRef: Building {0} right now, as it is called while BuildFonts is already in progress yet atlas build has not been called yet.", style.ToString());
|
||||
this.EnsureFont(style);
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.Information("[GameFontManager] NewFontRef: Calling RebuildFonts because {0} has been requested.", style.ToString());
|
||||
this.interfaceManager.RebuildFonts();
|
||||
}
|
||||
}
|
||||
|
||||
return new(this, style);
|
||||
|
|
@ -260,7 +212,7 @@ namespace Dalamud.Interface.GameFonts
|
|||
/// <param name="rebuildLookupTable">Whether to call target.BuildLookupTable().</param>
|
||||
public void CopyGlyphsAcrossFonts(ImFontPtr? source, GameFontStyle target, bool missingOnly, bool rebuildLookupTable)
|
||||
{
|
||||
GameFontManager.CopyGlyphsAcrossFonts(source, this.fonts[target], missingOnly, rebuildLookupTable);
|
||||
ImGuiHelpers.CopyGlyphsAcrossFonts(source, this.fonts[target], missingOnly, rebuildLookupTable);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -272,7 +224,7 @@ namespace Dalamud.Interface.GameFonts
|
|||
/// <param name="rebuildLookupTable">Whether to call target.BuildLookupTable().</param>
|
||||
public void CopyGlyphsAcrossFonts(GameFontStyle source, ImFontPtr? target, bool missingOnly, bool rebuildLookupTable)
|
||||
{
|
||||
GameFontManager.CopyGlyphsAcrossFonts(this.fonts[source], target, missingOnly, rebuildLookupTable);
|
||||
ImGuiHelpers.CopyGlyphsAcrossFonts(this.fonts[source], target, missingOnly, rebuildLookupTable);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -284,7 +236,7 @@ namespace Dalamud.Interface.GameFonts
|
|||
/// <param name="rebuildLookupTable">Whether to call target.BuildLookupTable().</param>
|
||||
public void CopyGlyphsAcrossFonts(GameFontStyle source, GameFontStyle target, bool missingOnly, bool rebuildLookupTable)
|
||||
{
|
||||
GameFontManager.CopyGlyphsAcrossFonts(this.fonts[source], this.fonts[target], missingOnly, rebuildLookupTable);
|
||||
ImGuiHelpers.CopyGlyphsAcrossFonts(this.fonts[source], this.fonts[target], missingOnly, rebuildLookupTable);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -293,57 +245,20 @@ namespace Dalamud.Interface.GameFonts
|
|||
/// <param name="forceMinSize">Whether to load fonts in minimum sizes.</param>
|
||||
public void BuildFonts(bool forceMinSize)
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
ImFontConfigPtr fontConfig = ImGuiNative.ImFontConfig_ImFontConfig();
|
||||
fontConfig.OversampleH = 1;
|
||||
fontConfig.OversampleV = 1;
|
||||
fontConfig.PixelSnapH = false;
|
||||
this.isBuildingAsFallbackFontMode = forceMinSize;
|
||||
this.isBetweenBuildFontsAndAfterBuildFonts = true;
|
||||
|
||||
var io = ImGui.GetIO();
|
||||
this.glyphRectIds.Clear();
|
||||
this.fonts.Clear();
|
||||
|
||||
this.glyphRectIds.Clear();
|
||||
this.fonts.Clear();
|
||||
|
||||
foreach (var style in this.fontUseCounter.Keys)
|
||||
{
|
||||
var rectIds = this.glyphRectIds[style] = new();
|
||||
|
||||
var fdt = this.fdts[(int)(forceMinSize ? style.FamilyWithMinimumSize : style.FamilyAndSize)];
|
||||
if (fdt == null)
|
||||
continue;
|
||||
|
||||
var font = io.Fonts.AddFontDefault(fontConfig);
|
||||
|
||||
this.fonts[style] = font;
|
||||
foreach (var glyph in fdt.Glyphs)
|
||||
{
|
||||
var c = glyph.Char;
|
||||
if (c < 32 || c >= 0xFFFF)
|
||||
continue;
|
||||
|
||||
var widthAdjustment = style.CalculateBaseWidthAdjustment(fdt, glyph);
|
||||
rectIds[c] = Tuple.Create(
|
||||
io.Fonts.AddCustomRectFontGlyph(
|
||||
font,
|
||||
c,
|
||||
glyph.BoundingWidth + widthAdjustment + 1,
|
||||
glyph.BoundingHeight + 1,
|
||||
glyph.AdvanceWidth,
|
||||
new Vector2(0, glyph.CurrentOffsetY)),
|
||||
glyph);
|
||||
}
|
||||
}
|
||||
|
||||
fontConfig.Destroy();
|
||||
}
|
||||
foreach (var style in this.fontUseCounter.Keys)
|
||||
this.EnsureFont(style);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Post-build fonts before plugins do something more. To be called from InterfaceManager.
|
||||
/// </summary>
|
||||
/// <param name="forceMinSize">Whether to load fonts in minimum sizes.</param>
|
||||
public unsafe void AfterBuildFonts(bool forceMinSize)
|
||||
public unsafe void AfterBuildFonts()
|
||||
{
|
||||
var ioFonts = ImGui.GetIO().Fonts;
|
||||
ioFonts.GetTexDataAsRGBA32(out byte* pixels8, out var width, out var height);
|
||||
|
|
@ -352,7 +267,7 @@ namespace Dalamud.Interface.GameFonts
|
|||
|
||||
foreach (var (style, font) in this.fonts)
|
||||
{
|
||||
var fdt = this.fdts[(int)(forceMinSize ? style.FamilyWithMinimumSize : style.FamilyAndSize)];
|
||||
var fdt = this.fdts[(int)(this.isBuildingAsFallbackFontMode ? style.FamilyWithMinimumSize : style.FamilyAndSize)];
|
||||
var scale = style.SizePt / fdt.FontHeader.Size;
|
||||
var fontPtr = font.NativePtr;
|
||||
fontPtr->FontSize = fdt.FontHeader.Size * 4 / 3;
|
||||
|
|
@ -449,10 +364,12 @@ namespace Dalamud.Interface.GameFonts
|
|||
}
|
||||
}
|
||||
|
||||
CopyGlyphsAcrossFonts(InterfaceManager.DefaultFont, font, true, false);
|
||||
ImGuiHelpers.CopyGlyphsAcrossFonts(InterfaceManager.DefaultFont, font, true, false);
|
||||
UnscaleFont(font, 1 / scale, false);
|
||||
font.BuildLookupTable();
|
||||
}
|
||||
|
||||
this.isBetweenBuildFontsAndAfterBuildFonts = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -471,35 +388,41 @@ namespace Dalamud.Interface.GameFonts
|
|||
}
|
||||
}
|
||||
|
||||
private struct ImFontGlyphReal
|
||||
private unsafe void EnsureFont(GameFontStyle style)
|
||||
{
|
||||
public uint ColoredVisibleCodepoint;
|
||||
public float AdvanceX;
|
||||
public float X0;
|
||||
public float Y0;
|
||||
public float X1;
|
||||
public float Y1;
|
||||
public float U0;
|
||||
public float V0;
|
||||
public float U1;
|
||||
public float V1;
|
||||
var rectIds = this.glyphRectIds[style] = new();
|
||||
|
||||
public bool Colored
|
||||
{
|
||||
get => ((this.ColoredVisibleCodepoint >> 0) & 1) != 0;
|
||||
set => this.ColoredVisibleCodepoint = (this.ColoredVisibleCodepoint & 0xFFFFFFFEu) | (value ? 1u : 0u);
|
||||
}
|
||||
var fdt = this.fdts[(int)(this.isBuildingAsFallbackFontMode ? style.FamilyWithMinimumSize : style.FamilyAndSize)];
|
||||
if (fdt == null)
|
||||
return;
|
||||
|
||||
public bool Visible
|
||||
{
|
||||
get => ((this.ColoredVisibleCodepoint >> 1) & 1) != 0;
|
||||
set => this.ColoredVisibleCodepoint = (this.ColoredVisibleCodepoint & 0xFFFFFFFDu) | (value ? 2u : 0u);
|
||||
}
|
||||
ImFontConfigPtr fontConfig = ImGuiNative.ImFontConfig_ImFontConfig();
|
||||
fontConfig.OversampleH = 1;
|
||||
fontConfig.OversampleV = 1;
|
||||
fontConfig.PixelSnapH = false;
|
||||
|
||||
public int Codepoint
|
||||
var io = ImGui.GetIO();
|
||||
var font = io.Fonts.AddFontDefault(fontConfig);
|
||||
|
||||
fontConfig.Destroy();
|
||||
|
||||
this.fonts[style] = font;
|
||||
foreach (var glyph in fdt.Glyphs)
|
||||
{
|
||||
get => (int)(this.ColoredVisibleCodepoint >> 2);
|
||||
set => this.ColoredVisibleCodepoint = (this.ColoredVisibleCodepoint & 3u) | ((uint)this.Codepoint << 2);
|
||||
var c = glyph.Char;
|
||||
if (c < 32 || c >= 0xFFFF)
|
||||
continue;
|
||||
|
||||
var widthAdjustment = style.CalculateBaseWidthAdjustment(fdt, glyph);
|
||||
rectIds[c] = Tuple.Create(
|
||||
io.Fonts.AddCustomRectFontGlyph(
|
||||
font,
|
||||
c,
|
||||
glyph.BoundingWidth + widthAdjustment + 1,
|
||||
glyph.BoundingHeight + 1,
|
||||
glyph.AdvanceWidth,
|
||||
new Vector2(0, glyph.CurrentOffsetY)),
|
||||
glyph);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,8 +20,7 @@ namespace Dalamud.Interface.ImGuiFileDialog
|
|||
/// <param name="callback">The action to execute when the dialog is finished.</param>
|
||||
public void OpenFolderDialog(string title, Action<bool, string> callback)
|
||||
{
|
||||
this.SetCallback(callback);
|
||||
this.SetDialog("OpenFolderDialog", title, string.Empty, this.savedPath, ".", string.Empty, 1, false, ImGuiFileDialogFlags.SelectOnly);
|
||||
this.SetDialog("OpenFolderDialog", title, string.Empty, this.savedPath, ".", string.Empty, 1, false, ImGuiFileDialogFlags.SelectOnly, callback);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -33,8 +32,7 @@ namespace Dalamud.Interface.ImGuiFileDialog
|
|||
/// <param name="isModal">Whether the dialog should be a modal popup.</param>
|
||||
public void OpenFolderDialog(string title, Action<bool, string> callback, string? startPath, bool isModal = false)
|
||||
{
|
||||
this.SetCallback(callback);
|
||||
this.SetDialog("OpenFolderDialog", title, string.Empty, startPath ?? this.savedPath, ".", string.Empty, 1, isModal, ImGuiFileDialogFlags.SelectOnly);
|
||||
this.SetDialog("OpenFolderDialog", title, string.Empty, startPath ?? this.savedPath, ".", string.Empty, 1, isModal, ImGuiFileDialogFlags.SelectOnly, callback);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -45,8 +43,7 @@ namespace Dalamud.Interface.ImGuiFileDialog
|
|||
/// <param name="callback">The action to execute when the dialog is finished.</param>
|
||||
public void SaveFolderDialog(string title, string defaultFolderName, Action<bool, string> callback)
|
||||
{
|
||||
this.SetCallback(callback);
|
||||
this.SetDialog("SaveFolderDialog", title, string.Empty, this.savedPath, defaultFolderName, string.Empty, 1, false, ImGuiFileDialogFlags.None);
|
||||
this.SetDialog("SaveFolderDialog", title, string.Empty, this.savedPath, defaultFolderName, string.Empty, 1, false, ImGuiFileDialogFlags.None, callback);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -59,8 +56,7 @@ namespace Dalamud.Interface.ImGuiFileDialog
|
|||
/// <param name="isModal">Whether the dialog should be a modal popup.</param>
|
||||
public void SaveFolderDialog(string title, string defaultFolderName, Action<bool, string> callback, string? startPath, bool isModal = false)
|
||||
{
|
||||
this.SetCallback(callback);
|
||||
this.SetDialog("SaveFolderDialog", title, string.Empty, startPath ?? this.savedPath, defaultFolderName, string.Empty, 1, isModal, ImGuiFileDialogFlags.None);
|
||||
this.SetDialog("SaveFolderDialog", title, string.Empty, startPath ?? this.savedPath, defaultFolderName, string.Empty, 1, isModal, ImGuiFileDialogFlags.None, callback);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -71,8 +67,7 @@ namespace Dalamud.Interface.ImGuiFileDialog
|
|||
/// <param name="callback">The action to execute when the dialog is finished.</param>
|
||||
public void OpenFileDialog(string title, string filters, Action<bool, string> callback)
|
||||
{
|
||||
this.SetCallback(callback);
|
||||
this.SetDialog("OpenFileDialog", title, filters, this.savedPath, ".", string.Empty, 1, false, ImGuiFileDialogFlags.SelectOnly);
|
||||
this.SetDialog("OpenFileDialog", title, filters, this.savedPath, ".", string.Empty, 1, false, ImGuiFileDialogFlags.SelectOnly, callback);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -81,19 +76,18 @@ namespace Dalamud.Interface.ImGuiFileDialog
|
|||
/// <param name="title">The header title of the dialog.</param>
|
||||
/// <param name="filters">Which files to show in the dialog.</param>
|
||||
/// <param name="callback">The action to execute when the dialog is finished.</param>
|
||||
/// <param name="startPath">The directory which the dialog should start inside of. The last path this manager was in is used if this is null.</param>
|
||||
/// <param name="selectionCountMax">The maximum amount of files or directories which can be selected. Set to 0 for an infinite number.</param>
|
||||
/// <param name="startPath">The directory which the dialog should start inside of. The last path this manager was in is used if this is null.</param>
|
||||
/// <param name="isModal">Whether the dialog should be a modal popup.</param>
|
||||
public void OpenFileDialog(
|
||||
string title,
|
||||
string filters,
|
||||
Action<bool, List<string>> callback,
|
||||
int selectionCountMax,
|
||||
string? startPath = null,
|
||||
int selectionCountMax = 1,
|
||||
bool isModal = false)
|
||||
{
|
||||
this.SetCallback(callback);
|
||||
this.SetDialog("OpenFileDialog", title, filters, startPath ?? this.savedPath, ".", string.Empty, selectionCountMax, isModal, ImGuiFileDialogFlags.SelectOnly);
|
||||
this.SetDialog("OpenFileDialog", title, filters, startPath ?? this.savedPath, ".", string.Empty, selectionCountMax, isModal, ImGuiFileDialogFlags.SelectOnly, callback);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -111,8 +105,7 @@ namespace Dalamud.Interface.ImGuiFileDialog
|
|||
string defaultExtension,
|
||||
Action<bool, string> callback)
|
||||
{
|
||||
this.SetCallback(callback);
|
||||
this.SetDialog("SaveFileDialog", title, filters, this.savedPath, defaultFileName, defaultExtension, 1, false, ImGuiFileDialogFlags.None);
|
||||
this.SetDialog("SaveFileDialog", title, filters, this.savedPath, defaultFileName, defaultExtension, 1, false, ImGuiFileDialogFlags.None, callback);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -134,8 +127,7 @@ namespace Dalamud.Interface.ImGuiFileDialog
|
|||
string? startPath,
|
||||
bool isModal = false)
|
||||
{
|
||||
this.SetCallback(callback);
|
||||
this.SetDialog("SaveFileDialog", title, filters, startPath ?? this.savedPath, defaultFileName, defaultExtension, 1, isModal, ImGuiFileDialogFlags.None);
|
||||
this.SetDialog("SaveFileDialog", title, filters, startPath ?? this.savedPath, defaultFileName, defaultExtension, 1, isModal, ImGuiFileDialogFlags.None, callback);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -166,18 +158,6 @@ namespace Dalamud.Interface.ImGuiFileDialog
|
|||
this.multiCallback = null;
|
||||
}
|
||||
|
||||
private void SetCallback(Action<bool, string> action)
|
||||
{
|
||||
this.callback = action;
|
||||
this.multiCallback = null;
|
||||
}
|
||||
|
||||
private void SetCallback(Action<bool, List<string>> action)
|
||||
{
|
||||
this.multiCallback = action;
|
||||
this.callback = null;
|
||||
}
|
||||
|
||||
private void SetDialog(
|
||||
string id,
|
||||
string title,
|
||||
|
|
@ -187,9 +167,19 @@ namespace Dalamud.Interface.ImGuiFileDialog
|
|||
string defaultExtension,
|
||||
int selectionCountMax,
|
||||
bool isModal,
|
||||
ImGuiFileDialogFlags flags)
|
||||
ImGuiFileDialogFlags flags,
|
||||
Delegate callback)
|
||||
{
|
||||
this.Reset();
|
||||
if (callback is Action<bool, List<string>> multi)
|
||||
{
|
||||
this.multiCallback = multi;
|
||||
}
|
||||
else
|
||||
{
|
||||
this.callback = callback as Action<bool, string>;
|
||||
}
|
||||
|
||||
this.dialog = new FileDialog(id, title, filters, path, defaultFileName, defaultExtension, selectionCountMax, isModal, flags);
|
||||
this.dialog.Show();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,7 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Numerics;
|
||||
|
||||
using ImGuiNET;
|
||||
|
|
@ -136,6 +139,67 @@ namespace Dalamud.Interface
|
|||
/// <param name="text">The text to write.</param>
|
||||
public static void SafeTextWrapped(string text) => ImGui.TextWrapped(text.Replace("%", "%%"));
|
||||
|
||||
/// <summary>
|
||||
/// Fills missing glyphs in target font from source font, if both are not null.
|
||||
/// </summary>
|
||||
/// <param name="source">Source font.</param>
|
||||
/// <param name="target">Target font.</param>
|
||||
/// <param name="missingOnly">Whether to copy missing glyphs only.</param>
|
||||
/// <param name="rebuildLookupTable">Whether to call target.BuildLookupTable().</param>
|
||||
/// <param name="rangeLow">Low codepoint range to copy.</param>
|
||||
/// <param name="rangeHigh">High codepoing range to copy.</param>
|
||||
public static void CopyGlyphsAcrossFonts(ImFontPtr? source, ImFontPtr? target, bool missingOnly, bool rebuildLookupTable, int rangeLow = 32, int rangeHigh = 0xFFFE)
|
||||
{
|
||||
if (!source.HasValue || !target.HasValue)
|
||||
return;
|
||||
|
||||
var scale = target.Value!.FontSize / source.Value!.FontSize;
|
||||
unsafe
|
||||
{
|
||||
var glyphs = (ImFontGlyphReal*)source.Value!.Glyphs.Data;
|
||||
for (int j = 0, k = source.Value!.Glyphs.Size; j < k; j++)
|
||||
{
|
||||
Debug.Assert(glyphs != null, nameof(glyphs) + " != null");
|
||||
|
||||
var glyph = &glyphs[j];
|
||||
if (glyph->Codepoint < rangeLow || glyph->Codepoint > rangeHigh)
|
||||
continue;
|
||||
|
||||
var prevGlyphPtr = (ImFontGlyphReal*)target.Value!.FindGlyphNoFallback((ushort)glyph->Codepoint).NativePtr;
|
||||
if ((IntPtr)prevGlyphPtr == IntPtr.Zero)
|
||||
{
|
||||
target.Value!.AddGlyph(
|
||||
target.Value!.ConfigData,
|
||||
(ushort)glyph->Codepoint,
|
||||
glyph->X0 * scale,
|
||||
((glyph->Y0 - source.Value!.Ascent) * scale) + target.Value!.Ascent,
|
||||
glyph->X1 * scale,
|
||||
((glyph->Y1 - source.Value!.Ascent) * scale) + target.Value!.Ascent,
|
||||
glyph->U0,
|
||||
glyph->V0,
|
||||
glyph->U1,
|
||||
glyph->V1,
|
||||
glyph->AdvanceX * scale);
|
||||
}
|
||||
else if (!missingOnly)
|
||||
{
|
||||
prevGlyphPtr->X0 = glyph->X0 * scale;
|
||||
prevGlyphPtr->Y0 = ((glyph->Y0 - source.Value!.Ascent) * scale) + target.Value!.Ascent;
|
||||
prevGlyphPtr->X1 = glyph->X1 * scale;
|
||||
prevGlyphPtr->Y1 = ((glyph->Y1 - source.Value!.Ascent) * scale) + target.Value!.Ascent;
|
||||
prevGlyphPtr->U0 = glyph->U0;
|
||||
prevGlyphPtr->V0 = glyph->V0;
|
||||
prevGlyphPtr->U1 = glyph->U1;
|
||||
prevGlyphPtr->V1 = glyph->V1;
|
||||
prevGlyphPtr->AdvanceX = glyph->AdvanceX * scale;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (rebuildLookupTable)
|
||||
target.Value!.BuildLookupTable();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get data needed for each new frame.
|
||||
/// </summary>
|
||||
|
|
@ -143,5 +207,41 @@ namespace Dalamud.Interface
|
|||
{
|
||||
GlobalScale = ImGui.GetIO().FontGlobalScale;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// ImFontGlyph the correct version.
|
||||
/// </summary>
|
||||
[SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements should be documented", Justification = "ImGui internals")]
|
||||
public struct ImFontGlyphReal
|
||||
{
|
||||
public uint ColoredVisibleCodepoint;
|
||||
public float AdvanceX;
|
||||
public float X0;
|
||||
public float Y0;
|
||||
public float X1;
|
||||
public float Y1;
|
||||
public float U0;
|
||||
public float V0;
|
||||
public float U1;
|
||||
public float V1;
|
||||
|
||||
public bool Colored
|
||||
{
|
||||
get => ((this.ColoredVisibleCodepoint >> 0) & 1) != 0;
|
||||
set => this.ColoredVisibleCodepoint = (this.ColoredVisibleCodepoint & 0xFFFFFFFEu) | (value ? 1u : 0u);
|
||||
}
|
||||
|
||||
public bool Visible
|
||||
{
|
||||
get => ((this.ColoredVisibleCodepoint >> 1) & 1) != 0;
|
||||
set => this.ColoredVisibleCodepoint = (this.ColoredVisibleCodepoint & 0xFFFFFFFDu) | (value ? 2u : 0u);
|
||||
}
|
||||
|
||||
public int Codepoint
|
||||
{
|
||||
get => (int)(this.ColoredVisibleCodepoint >> 2);
|
||||
set => this.ColoredVisibleCodepoint = (this.ColoredVisibleCodepoint & 3u) | ((uint)value << 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -248,6 +248,11 @@ namespace Dalamud.Interface.Internal
|
|||
/// </summary>
|
||||
public int FontResolutionLevel => this.FontResolutionLevelOverride ?? Service<DalamudConfiguration>.Get().FontResolutionLevel;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether we're building fonts but haven't generated atlas yet.
|
||||
/// </summary>
|
||||
public bool IsBuildingFontsBeforeAtlasBuild => this.isRebuildingFonts && !this.fontBuildSignal.WaitOne(0);
|
||||
|
||||
/// <summary>
|
||||
/// Enable this module.
|
||||
/// </summary>
|
||||
|
|
@ -900,7 +905,7 @@ namespace Dalamud.Interface.Internal
|
|||
texPixels[i] = (byte)(Math.Pow(texPixels[i] / 255.0f, 1.0f / fontGamma) * 255.0f);
|
||||
}
|
||||
|
||||
gameFontManager.AfterBuildFonts(disableBigFonts);
|
||||
gameFontManager.AfterBuildFonts();
|
||||
|
||||
foreach (var (font, mod) in this.loadedFontInfo)
|
||||
{
|
||||
|
|
@ -929,14 +934,14 @@ namespace Dalamud.Interface.Internal
|
|||
font.Descent = mod.SourceAxis.ImFont.Descent;
|
||||
font.FallbackChar = mod.SourceAxis.ImFont.FallbackChar;
|
||||
font.EllipsisChar = mod.SourceAxis.ImFont.EllipsisChar;
|
||||
GameFontManager.CopyGlyphsAcrossFonts(mod.SourceAxis.ImFont, font, false, false);
|
||||
ImGuiHelpers.CopyGlyphsAcrossFonts(mod.SourceAxis.ImFont, font, false, false);
|
||||
}
|
||||
else if (mod.Axis == TargetFontModification.AxisMode.GameGlyphsOnly)
|
||||
{
|
||||
Log.Verbose("[FONT] {0}: Overwrite game specific glyphs from AXIS of size {1}px", mod.Name, mod.SourceAxis.ImFont.FontSize, font.FontSize);
|
||||
if (!this.UseAxis && font.NativePtr == DefaultFont.NativePtr)
|
||||
mod.SourceAxis.ImFont.FontSize -= 1;
|
||||
GameFontManager.CopyGlyphsAcrossFonts(mod.SourceAxis.ImFont, font, true, false, 0xE020, 0xE0DB);
|
||||
ImGuiHelpers.CopyGlyphsAcrossFonts(mod.SourceAxis.ImFont, font, true, false, 0xE020, 0xE0DB);
|
||||
if (!this.UseAxis && font.NativePtr == DefaultFont.NativePtr)
|
||||
mod.SourceAxis.ImFont.FontSize += 1;
|
||||
}
|
||||
|
|
@ -946,7 +951,7 @@ namespace Dalamud.Interface.Internal
|
|||
}
|
||||
|
||||
// Fill missing glyphs in MonoFont from DefaultFont
|
||||
GameFontManager.CopyGlyphsAcrossFonts(DefaultFont, MonoFont, true, false);
|
||||
ImGuiHelpers.CopyGlyphsAcrossFonts(DefaultFont, MonoFont, true, false);
|
||||
|
||||
for (int i = 0, i_ = ioFonts.Fonts.Size; i < i_; i++)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -114,6 +114,9 @@ namespace Dalamud.Interface.Internal.Windows
|
|||
private DtrBarEntry? dtrTest2;
|
||||
private DtrBarEntry? dtrTest3;
|
||||
|
||||
// Task Scheduler
|
||||
private CancellationTokenSource taskSchedCancelSource = new();
|
||||
|
||||
private uint copyButtonIndex = 0;
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -1359,6 +1362,15 @@ namespace Dalamud.Interface.Internal.Windows
|
|||
ImGuiHelpers.ScaledDummy(10);
|
||||
ImGui.SameLine();
|
||||
|
||||
if (ImGui.Button("Cancel using CancellationTokenSource"))
|
||||
{
|
||||
this.taskSchedCancelSource.Cancel();
|
||||
this.taskSchedCancelSource = new();
|
||||
}
|
||||
|
||||
ImGui.Text("Run in any thread: ");
|
||||
ImGui.SameLine();
|
||||
|
||||
if (ImGui.Button("Short Task.Run"))
|
||||
{
|
||||
Task.Run(() => { Thread.Sleep(500); });
|
||||
|
|
@ -1368,7 +1380,8 @@ namespace Dalamud.Interface.Internal.Windows
|
|||
|
||||
if (ImGui.Button("Task in task(Delay)"))
|
||||
{
|
||||
Task.Run(async () => await this.TestTaskInTaskDelay());
|
||||
var token = this.taskSchedCancelSource.Token;
|
||||
Task.Run(async () => await this.TestTaskInTaskDelay(token));
|
||||
}
|
||||
|
||||
ImGui.SameLine();
|
||||
|
|
@ -1391,29 +1404,72 @@ namespace Dalamud.Interface.Internal.Windows
|
|||
});
|
||||
}
|
||||
|
||||
ImGui.Text("Run in Framework.Update: ");
|
||||
ImGui.SameLine();
|
||||
|
||||
if (ImGui.Button("ASAP"))
|
||||
{
|
||||
Task.Run(async () => await Service<Framework>.Get().RunOnTick(() => { }, cancellationToken: this.taskSchedCancelSource.Token));
|
||||
}
|
||||
|
||||
ImGui.SameLine();
|
||||
|
||||
if (ImGui.Button("In 1s"))
|
||||
{
|
||||
Task.Run(async () => await Service<Framework>.Get().RunOnTick(() => { }, cancellationToken: this.taskSchedCancelSource.Token, delay: TimeSpan.FromSeconds(1)));
|
||||
}
|
||||
|
||||
ImGui.SameLine();
|
||||
|
||||
if (ImGui.Button("In 60f"))
|
||||
{
|
||||
Task.Run(async () => await Service<Framework>.Get().RunOnTick(() => { }, cancellationToken: this.taskSchedCancelSource.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.taskSchedCancelSource.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.taskSchedCancelSource.Token;
|
||||
Task.Run(() =>
|
||||
{
|
||||
for (var i = 0; i < 100; i++)
|
||||
{
|
||||
token.ThrowIfCancellationRequested();
|
||||
Task.Run(() =>
|
||||
{
|
||||
for (var i = 0; i < 100; i++)
|
||||
{
|
||||
token.ThrowIfCancellationRequested();
|
||||
Task.Run(() =>
|
||||
{
|
||||
for (var i = 0; i < 100; i++)
|
||||
{
|
||||
token.ThrowIfCancellationRequested();
|
||||
Task.Run(() =>
|
||||
{
|
||||
for (var i = 0; i < 100; i++)
|
||||
{
|
||||
Task.Run(() =>
|
||||
token.ThrowIfCancellationRequested();
|
||||
Task.Run(async () =>
|
||||
{
|
||||
for (var i = 0; i < 100; i++)
|
||||
{
|
||||
Thread.Sleep(1);
|
||||
token.ThrowIfCancellationRequested();
|
||||
await Task.Delay(1);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
@ -1652,9 +1708,9 @@ namespace Dalamud.Interface.Internal.Windows
|
|||
}
|
||||
}
|
||||
|
||||
private async Task TestTaskInTaskDelay()
|
||||
private async Task TestTaskInTaskDelay(CancellationToken token)
|
||||
{
|
||||
await Task.Delay(5000);
|
||||
await Task.Delay(5000, token);
|
||||
}
|
||||
|
||||
#pragma warning disable 1998
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
using System;
|
||||
|
||||
using Dalamud.Plugin.Internal;
|
||||
using Dalamud.Plugin.Internal.Types;
|
||||
using Dalamud.Utility;
|
||||
|
||||
namespace Dalamud.Interface.Internal.Windows.PluginInstaller
|
||||
|
|
|
|||
|
|
@ -2013,6 +2013,7 @@ namespace Dalamud.Interface.Internal.Windows.PluginInstaller
|
|||
return hasSearchString && !(
|
||||
manifest.Name.ToLowerInvariant().Contains(searchString) ||
|
||||
(!manifest.Author.IsNullOrEmpty() && manifest.Author.Equals(this.searchText, StringComparison.InvariantCultureIgnoreCase)) ||
|
||||
(!manifest.Punchline.IsNullOrEmpty() && manifest.Punchline.ToLowerInvariant().Contains(searchString)) ||
|
||||
(manifest.Tags != null && manifest.Tags.Contains(searchString, StringComparer.InvariantCultureIgnoreCase)));
|
||||
}
|
||||
|
||||
|
|
@ -2253,7 +2254,7 @@ namespace Dalamud.Interface.Internal.Windows.PluginInstaller
|
|||
|
||||
public static string PluginBody_AuthorWithoutDownloadCount(string author) => Loc.Localize("InstallerAuthorWithoutDownloadCount", " by {0}").Format(author);
|
||||
|
||||
public static string PluginBody_AuthorWithDownloadCount(string author, long count) => Loc.Localize("InstallerAuthorWithDownloadCount", " by {0}, {1} downloads").Format(author, count);
|
||||
public static string PluginBody_AuthorWithDownloadCount(string author, long count) => Loc.Localize("InstallerAuthorWithDownloadCount", " by {0} ({1} downloads)").Format(author, count.ToString("N0"));
|
||||
|
||||
public static string PluginBody_AuthorWithDownloadCountUnavailable(string author) => Loc.Localize("InstallerAuthorWithDownloadCountUnavailable", " by {0}, download count unavailable").Format(author);
|
||||
|
||||
|
|
|
|||
|
|
@ -24,9 +24,6 @@ namespace Dalamud.Interface.Internal.Windows
|
|||
/// </summary>
|
||||
internal class SettingsWindow : Window
|
||||
{
|
||||
private const float MinScale = 0.3f;
|
||||
private const float MaxScale = 3.0f;
|
||||
|
||||
private readonly string[] languages;
|
||||
private readonly string[] locLanguages;
|
||||
|
||||
|
|
@ -179,7 +176,8 @@ namespace Dalamud.Interface.Internal.Windows
|
|||
var configuration = Service<DalamudConfiguration>.Get();
|
||||
var interfaceManager = Service<InterfaceManager>.Get();
|
||||
|
||||
var rebuildFont = interfaceManager.FontGamma != configuration.FontGammaLevel
|
||||
var rebuildFont = ImGui.GetIO().FontGlobalScale != configuration.GlobalUiScale
|
||||
|| interfaceManager.FontGamma != configuration.FontGammaLevel
|
||||
|| interfaceManager.FontResolutionLevel != configuration.FontResolutionLevel
|
||||
|| interfaceManager.UseAxis != configuration.UseAxisFontsFromGame;
|
||||
|
||||
|
|
@ -298,7 +296,7 @@ namespace Dalamud.Interface.Internal.Windows
|
|||
ImGui.Text(Loc.Localize("DalamudSettingsGlobalUiScale", "Global Font Scale"));
|
||||
ImGui.SameLine();
|
||||
ImGui.SetCursorPosY(ImGui.GetCursorPosY() - 3);
|
||||
if (ImGui.Button(Loc.Localize("DalamudSettingsUiScalePreset6", "9.6pt") + "##DalamudSettingsGlobalUiScaleReset96"))
|
||||
if (ImGui.Button("9.6pt##DalamudSettingsGlobalUiScaleReset96"))
|
||||
{
|
||||
this.globalUiScale = 9.6f / 12.0f;
|
||||
ImGui.GetIO().FontGlobalScale = this.globalUiScale;
|
||||
|
|
@ -306,7 +304,7 @@ namespace Dalamud.Interface.Internal.Windows
|
|||
}
|
||||
|
||||
ImGui.SameLine();
|
||||
if (ImGui.Button(Loc.Localize("DalamudSettingsUiScalePreset12", "Reset (12pt)") + "##DalamudSettingsGlobalUiScaleReset12"))
|
||||
if (ImGui.Button("12pt##DalamudSettingsGlobalUiScaleReset12"))
|
||||
{
|
||||
this.globalUiScale = 1.0f;
|
||||
ImGui.GetIO().FontGlobalScale = this.globalUiScale;
|
||||
|
|
@ -314,7 +312,7 @@ namespace Dalamud.Interface.Internal.Windows
|
|||
}
|
||||
|
||||
ImGui.SameLine();
|
||||
if (ImGui.Button(Loc.Localize("DalamudSettingsUiScalePreset14", "14pt") + "##DalamudSettingsGlobalUiScaleReset14"))
|
||||
if (ImGui.Button("14pt##DalamudSettingsGlobalUiScaleReset14"))
|
||||
{
|
||||
this.globalUiScale = 14.0f / 12.0f;
|
||||
ImGui.GetIO().FontGlobalScale = this.globalUiScale;
|
||||
|
|
@ -322,7 +320,7 @@ namespace Dalamud.Interface.Internal.Windows
|
|||
}
|
||||
|
||||
ImGui.SameLine();
|
||||
if (ImGui.Button(Loc.Localize("DalamudSettingsUiScalePreset18", "18pt") + "##DalamudSettingsGlobalUiScaleReset18"))
|
||||
if (ImGui.Button("18pt##DalamudSettingsGlobalUiScaleReset18"))
|
||||
{
|
||||
this.globalUiScale = 18.0f / 12.0f;
|
||||
ImGui.GetIO().FontGlobalScale = this.globalUiScale;
|
||||
|
|
@ -330,15 +328,17 @@ namespace Dalamud.Interface.Internal.Windows
|
|||
}
|
||||
|
||||
ImGui.SameLine();
|
||||
if (ImGui.Button(Loc.Localize("DalamudSettingsUiScalePreset36", "36pt") + "##DalamudSettingsGlobalUiScaleReset36"))
|
||||
if (ImGui.Button("36pt##DalamudSettingsGlobalUiScaleReset36"))
|
||||
{
|
||||
this.globalUiScale = 36.0f / 12.0f;
|
||||
ImGui.GetIO().FontGlobalScale = this.globalUiScale;
|
||||
interfaceManager.RebuildFonts();
|
||||
}
|
||||
|
||||
if (ImGui.DragFloat("##DalamudSettingsGlobalUiScaleDrag", ref this.globalUiScale, 0.005f, MinScale, MaxScale, "%.2f", ImGuiSliderFlags.AlwaysClamp))
|
||||
var globalUiScaleInPt = 12f * this.globalUiScale;
|
||||
if (ImGui.DragFloat("##DalamudSettingsGlobalUiScaleDrag", ref globalUiScaleInPt, 0.1f, 9.6f, 36f, "%.1fpt", ImGuiSliderFlags.AlwaysClamp))
|
||||
{
|
||||
this.globalUiScale = globalUiScaleInPt / 12f;
|
||||
ImGui.GetIO().FontGlobalScale = this.globalUiScale;
|
||||
interfaceManager.RebuildFonts();
|
||||
}
|
||||
|
|
@ -436,7 +436,7 @@ namespace Dalamud.Interface.Internal.Windows
|
|||
interfaceManager.RebuildFonts();
|
||||
}
|
||||
|
||||
if (ImGui.DragFloat("##DalamudSettingsFontGammaDrag", ref this.fontGamma, 0.005f, MinScale, MaxScale, "%.2f", ImGuiSliderFlags.AlwaysClamp))
|
||||
if (ImGui.DragFloat("##DalamudSettingsFontGammaDrag", ref this.fontGamma, 0.005f, 0.3f, 3f, "%.2f", ImGuiSliderFlags.AlwaysClamp))
|
||||
{
|
||||
interfaceManager.FontGammaOverride = this.fontGamma;
|
||||
interfaceManager.RebuildFonts();
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
|
|
@ -138,11 +139,7 @@ namespace Dalamud.Plugin
|
|||
/// <summary>
|
||||
/// Gets a value indicating whether Dalamud is running in Debug mode or the /xldev menu is open. This can occur on release builds.
|
||||
/// </summary>
|
||||
#if DEBUG
|
||||
public bool IsDebugging => true;
|
||||
#else
|
||||
public bool IsDebugging => Service<DalamudInterface>.GetNullable() is {IsDevMenuOpen: true}; // Can be null during boot
|
||||
#endif
|
||||
public bool IsDebugging => Debugger.IsAttached;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current UI language in two-letter iso format.
|
||||
|
|
|
|||
|
|
@ -1,164 +0,0 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Dalamud.Configuration.Internal;
|
||||
using Dalamud.Interface.Internal.Notifications;
|
||||
using Dalamud.Logging.Internal;
|
||||
using Dalamud.Plugin.Internal.Types;
|
||||
|
||||
namespace Dalamud.Plugin.Internal
|
||||
{
|
||||
/// <summary>
|
||||
/// This class represents a dev plugin and all facets of its lifecycle.
|
||||
/// The DLL on disk, dependencies, loaded assembly, etc.
|
||||
/// </summary>
|
||||
internal class LocalDevPlugin : LocalPlugin, IDisposable
|
||||
{
|
||||
private static readonly ModuleLog Log = new("PLUGIN");
|
||||
|
||||
// Ref to Dalamud.Configuration.DevPluginSettings
|
||||
private readonly DevPluginSettings devSettings;
|
||||
|
||||
private FileSystemWatcher? fileWatcher;
|
||||
private CancellationTokenSource fileWatcherTokenSource = new();
|
||||
private int reloadCounter;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="LocalDevPlugin"/> class.
|
||||
/// </summary>
|
||||
/// <param name="dllFile">Path to the DLL file.</param>
|
||||
/// <param name="manifest">The plugin manifest.</param>
|
||||
public LocalDevPlugin(FileInfo dllFile, LocalPluginManifest? manifest)
|
||||
: base(dllFile, manifest)
|
||||
{
|
||||
var configuration = Service<DalamudConfiguration>.Get();
|
||||
|
||||
if (!configuration.DevPluginSettings.TryGetValue(dllFile.FullName, out this.devSettings))
|
||||
{
|
||||
configuration.DevPluginSettings[dllFile.FullName] = this.devSettings = new DevPluginSettings();
|
||||
configuration.Save();
|
||||
}
|
||||
|
||||
if (this.AutomaticReload)
|
||||
{
|
||||
this.EnableReloading();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether this dev plugin should start on boot.
|
||||
/// </summary>
|
||||
public bool StartOnBoot
|
||||
{
|
||||
get => this.devSettings.StartOnBoot;
|
||||
set => this.devSettings.StartOnBoot = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether this dev plugin should reload on change.
|
||||
/// </summary>
|
||||
public bool AutomaticReload
|
||||
{
|
||||
get => this.devSettings.AutomaticReloading;
|
||||
set
|
||||
{
|
||||
this.devSettings.AutomaticReloading = value;
|
||||
|
||||
if (this.devSettings.AutomaticReloading)
|
||||
{
|
||||
this.EnableReloading();
|
||||
}
|
||||
else
|
||||
{
|
||||
this.DisableReloading();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public new void Dispose()
|
||||
{
|
||||
if (this.fileWatcher != null)
|
||||
{
|
||||
this.fileWatcher.Changed -= this.OnFileChanged;
|
||||
this.fileWatcherTokenSource.Cancel();
|
||||
this.fileWatcher.Dispose();
|
||||
}
|
||||
|
||||
base.Dispose();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Configure this plugin for automatic reloading and enable it.
|
||||
/// </summary>
|
||||
public void EnableReloading()
|
||||
{
|
||||
if (this.fileWatcher == null)
|
||||
{
|
||||
this.fileWatcherTokenSource = new();
|
||||
this.fileWatcher = new FileSystemWatcher(this.DllFile.DirectoryName);
|
||||
this.fileWatcher.Changed += this.OnFileChanged;
|
||||
this.fileWatcher.Filter = this.DllFile.Name;
|
||||
this.fileWatcher.NotifyFilter = NotifyFilters.LastWrite;
|
||||
this.fileWatcher.EnableRaisingEvents = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disable automatic reloading for this plugin.
|
||||
/// </summary>
|
||||
public void DisableReloading()
|
||||
{
|
||||
if (this.fileWatcher != null)
|
||||
{
|
||||
this.fileWatcherTokenSource.Cancel();
|
||||
this.fileWatcher.Changed -= this.OnFileChanged;
|
||||
this.fileWatcher.Dispose();
|
||||
this.fileWatcher = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnFileChanged(object sender, FileSystemEventArgs args)
|
||||
{
|
||||
var current = Interlocked.Increment(ref this.reloadCounter);
|
||||
|
||||
Task.Delay(500).ContinueWith(
|
||||
_ =>
|
||||
{
|
||||
if (this.fileWatcherTokenSource.IsCancellationRequested)
|
||||
{
|
||||
Log.Debug($"Skipping reload of {this.Name}, file watcher was cancelled.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (current != this.reloadCounter)
|
||||
{
|
||||
Log.Debug($"Skipping reload of {this.Name}, file has changed again.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.State != PluginState.Loaded)
|
||||
{
|
||||
Log.Debug($"Skipping reload of {this.Name}, state ({this.State}) is not {PluginState.Loaded}.");
|
||||
return;
|
||||
}
|
||||
|
||||
var notificationManager = Service<NotificationManager>.Get();
|
||||
|
||||
try
|
||||
{
|
||||
this.Reload();
|
||||
notificationManager.AddNotification($"The DevPlugin '{this.Name} was reloaded successfully.", "Plugin reloaded!", NotificationType.Success);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "DevPlugin reload failed.");
|
||||
notificationManager.AddNotification($"The DevPlugin '{this.Name} could not be reloaded.", "Plugin reload failed!", NotificationType.Error);
|
||||
}
|
||||
},
|
||||
this.fileWatcherTokenSource.Token);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,481 +0,0 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
|
||||
using Dalamud.Configuration.Internal;
|
||||
using Dalamud.Game;
|
||||
using Dalamud.Game.Gui.Dtr;
|
||||
using Dalamud.IoC.Internal;
|
||||
using Dalamud.Logging.Internal;
|
||||
using Dalamud.Plugin.Internal.Exceptions;
|
||||
using Dalamud.Plugin.Internal.Loader;
|
||||
using Dalamud.Plugin.Internal.Types;
|
||||
using Dalamud.Utility;
|
||||
using Dalamud.Utility.Signatures;
|
||||
|
||||
namespace Dalamud.Plugin.Internal
|
||||
{
|
||||
/// <summary>
|
||||
/// This class represents a plugin and all facets of its lifecycle.
|
||||
/// The DLL on disk, dependencies, loaded assembly, etc.
|
||||
/// </summary>
|
||||
internal class LocalPlugin : IDisposable
|
||||
{
|
||||
private static readonly ModuleLog Log = new("LOCALPLUGIN");
|
||||
|
||||
private readonly FileInfo manifestFile;
|
||||
private readonly FileInfo disabledFile;
|
||||
private readonly FileInfo testingFile;
|
||||
|
||||
private PluginLoader? loader;
|
||||
private Assembly? pluginAssembly;
|
||||
private Type? pluginType;
|
||||
private IDalamudPlugin? instance;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="LocalPlugin"/> class.
|
||||
/// </summary>
|
||||
/// <param name="dllFile">Path to the DLL file.</param>
|
||||
/// <param name="manifest">The plugin manifest.</param>
|
||||
public LocalPlugin(FileInfo dllFile, LocalPluginManifest? manifest)
|
||||
{
|
||||
if (dllFile.Name == "FFXIVClientStructs.Generators.dll")
|
||||
{
|
||||
// Could this be done another way? Sure. It is an extremely common source
|
||||
// of errors in the log through, and should never be loaded as a plugin.
|
||||
Log.Error($"Not a plugin: {dllFile.FullName}");
|
||||
throw new InvalidPluginException(dllFile);
|
||||
}
|
||||
|
||||
this.DllFile = dllFile;
|
||||
this.State = PluginState.Unloaded;
|
||||
|
||||
this.loader = PluginLoader.CreateFromAssemblyFile(this.DllFile.FullName, this.SetupLoaderConfig);
|
||||
|
||||
try
|
||||
{
|
||||
this.pluginAssembly = this.loader.LoadDefaultAssembly();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
this.pluginAssembly = null;
|
||||
this.pluginType = null;
|
||||
this.loader.Dispose();
|
||||
|
||||
Log.Error(ex, $"Not a plugin: {this.DllFile.FullName}");
|
||||
throw new InvalidPluginException(this.DllFile);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
this.pluginType = this.pluginAssembly.GetTypes().FirstOrDefault(type => type.IsAssignableTo(typeof(IDalamudPlugin)));
|
||||
}
|
||||
catch (ReflectionTypeLoadException ex)
|
||||
{
|
||||
Log.Error(ex, $"Could not load one or more types when searching for IDalamudPlugin: {this.DllFile.FullName}");
|
||||
// Something blew up when parsing types, but we still want to look for IDalamudPlugin. Let Load() handle the error.
|
||||
this.pluginType = ex.Types.FirstOrDefault(type => type.IsAssignableTo(typeof(IDalamudPlugin)));
|
||||
}
|
||||
|
||||
if (this.pluginType == default)
|
||||
{
|
||||
this.pluginAssembly = null;
|
||||
this.pluginType = null;
|
||||
this.loader.Dispose();
|
||||
|
||||
Log.Error($"Nothing inherits from IDalamudPlugin: {this.DllFile.FullName}");
|
||||
throw new InvalidPluginException(this.DllFile);
|
||||
}
|
||||
|
||||
var assemblyVersion = this.pluginAssembly.GetName().Version;
|
||||
|
||||
// Although it is conditionally used here, we need to set the initial value regardless.
|
||||
this.manifestFile = LocalPluginManifest.GetManifestFile(this.DllFile);
|
||||
|
||||
// If the parameter manifest was null
|
||||
if (manifest == null)
|
||||
{
|
||||
this.Manifest = new LocalPluginManifest()
|
||||
{
|
||||
Author = "developer",
|
||||
Name = Path.GetFileNameWithoutExtension(this.DllFile.Name),
|
||||
InternalName = Path.GetFileNameWithoutExtension(this.DllFile.Name),
|
||||
AssemblyVersion = assemblyVersion,
|
||||
Description = string.Empty,
|
||||
ApplicableVersion = GameVersion.Any,
|
||||
DalamudApiLevel = PluginManager.DalamudApiLevel,
|
||||
IsHide = false,
|
||||
};
|
||||
|
||||
// Save the manifest to disk so there won't be any problems later.
|
||||
// We'll update the name property after it can be retrieved from the instance.
|
||||
this.Manifest.Save(this.manifestFile);
|
||||
}
|
||||
else
|
||||
{
|
||||
this.Manifest = manifest;
|
||||
}
|
||||
|
||||
// This converts from the ".disabled" file feature to the manifest instead.
|
||||
this.disabledFile = LocalPluginManifest.GetDisabledFile(this.DllFile);
|
||||
if (this.disabledFile.Exists)
|
||||
{
|
||||
this.Manifest.Disabled = true;
|
||||
this.disabledFile.Delete();
|
||||
}
|
||||
|
||||
// This converts from the ".testing" file feature to the manifest instead.
|
||||
this.testingFile = LocalPluginManifest.GetTestingFile(this.DllFile);
|
||||
if (this.testingFile.Exists)
|
||||
{
|
||||
this.Manifest.Testing = true;
|
||||
this.testingFile.Delete();
|
||||
}
|
||||
|
||||
var pluginManager = Service<PluginManager>.Get();
|
||||
this.IsBanned = pluginManager.IsManifestBanned(this.Manifest);
|
||||
this.BanReason = pluginManager.GetBanReason(this.Manifest);
|
||||
|
||||
this.SaveManifest();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the <see cref="DalamudPluginInterface"/> associated with this plugin.
|
||||
/// </summary>
|
||||
public DalamudPluginInterface? DalamudInterface { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the path to the plugin DLL.
|
||||
/// </summary>
|
||||
public FileInfo DllFile { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the plugin manifest, if one exists.
|
||||
/// </summary>
|
||||
public LocalPluginManifest Manifest { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the current state of the plugin.
|
||||
/// </summary>
|
||||
public PluginState State { get; protected set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the AssemblyName plugin, populated during <see cref="Load(PluginLoadReason, bool)"/>.
|
||||
/// </summary>
|
||||
/// <returns>Plugin type.</returns>
|
||||
public AssemblyName? AssemblyName { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the plugin name, directly from the plugin or if it is not loaded from the manifest.
|
||||
/// </summary>
|
||||
public string Name => this.instance?.Name ?? this.Manifest.Name;
|
||||
|
||||
/// <summary>
|
||||
/// Gets an optional reason, if the plugin is banned.
|
||||
/// </summary>
|
||||
public string BanReason { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the plugin is loaded and running.
|
||||
/// </summary>
|
||||
public bool IsLoaded => this.State == PluginState.Loaded;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the plugin is disabled.
|
||||
/// </summary>
|
||||
public bool IsDisabled => this.Manifest.Disabled;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether this plugin's API level is out of date.
|
||||
/// </summary>
|
||||
public bool IsOutdated => this.Manifest.DalamudApiLevel < PluginManager.DalamudApiLevel;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the plugin is for testing use only.
|
||||
/// </summary>
|
||||
public bool IsTesting => this.Manifest.IsTestingExclusive || this.Manifest.Testing;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether this plugin has been banned.
|
||||
/// </summary>
|
||||
public bool IsBanned { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether this plugin is dev plugin.
|
||||
/// </summary>
|
||||
public bool IsDev => this is LocalDevPlugin;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Dispose()
|
||||
{
|
||||
this.instance?.Dispose();
|
||||
this.instance = null;
|
||||
|
||||
this.DalamudInterface?.ExplicitDispose();
|
||||
this.DalamudInterface = null;
|
||||
|
||||
this.pluginType = null;
|
||||
this.pluginAssembly = null;
|
||||
|
||||
this.loader?.Dispose();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Load this plugin.
|
||||
/// </summary>
|
||||
/// <param name="reason">The reason why this plugin is being loaded.</param>
|
||||
/// <param name="reloading">Load while reloading.</param>
|
||||
public void Load(PluginLoadReason reason, bool reloading = false)
|
||||
{
|
||||
var startInfo = Service<DalamudStartInfo>.Get();
|
||||
var configuration = Service<DalamudConfiguration>.Get();
|
||||
var pluginManager = Service<PluginManager>.Get();
|
||||
|
||||
// Allowed: Unloaded
|
||||
switch (this.State)
|
||||
{
|
||||
case PluginState.InProgress:
|
||||
throw new InvalidPluginOperationException($"Unable to load {this.Name}, already working");
|
||||
case PluginState.Loaded:
|
||||
throw new InvalidPluginOperationException($"Unable to load {this.Name}, already loaded");
|
||||
case PluginState.LoadError:
|
||||
throw new InvalidPluginOperationException($"Unable to load {this.Name}, load previously faulted, unload first");
|
||||
case PluginState.UnloadError:
|
||||
throw new InvalidPluginOperationException($"Unable to load {this.Name}, unload previously faulted, restart Dalamud");
|
||||
}
|
||||
|
||||
if (pluginManager.IsManifestBanned(this.Manifest))
|
||||
throw new BannedPluginException($"Unable to load {this.Name}, banned");
|
||||
|
||||
if (this.Manifest.ApplicableVersion < startInfo.GameVersion)
|
||||
throw new InvalidPluginOperationException($"Unable to load {this.Name}, no applicable version");
|
||||
|
||||
if (this.Manifest.DalamudApiLevel < PluginManager.DalamudApiLevel && !configuration.LoadAllApiLevels)
|
||||
throw new InvalidPluginOperationException($"Unable to load {this.Name}, incompatible API level");
|
||||
|
||||
if (this.Manifest.Disabled)
|
||||
throw new InvalidPluginOperationException($"Unable to load {this.Name}, disabled");
|
||||
|
||||
this.State = PluginState.InProgress;
|
||||
Log.Information($"Loading {this.DllFile.Name}");
|
||||
|
||||
if (this.DllFile.DirectoryName != null && File.Exists(Path.Combine(this.DllFile.DirectoryName, "Dalamud.dll")))
|
||||
{
|
||||
Log.Error("==== IMPORTANT MESSAGE TO {0}, THE DEVELOPER OF {1} ====", this.Manifest.Author!, this.Manifest.InternalName);
|
||||
Log.Error("YOU ARE INCLUDING DALAMUD DEPENDENCIES IN YOUR BUILDS!!!");
|
||||
Log.Error("You may not be able to load your plugin. \"<Private>False</Private>\" needs to be set in your csproj.");
|
||||
Log.Error("If you are using ILMerge, do not merge anything other than your direct dependencies.");
|
||||
Log.Error("Do not merge FFXIVClientStructs.Generators.dll.");
|
||||
Log.Error("Please refer to https://github.com/goatcorp/Dalamud/discussions/603 for more information.");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
this.loader ??= PluginLoader.CreateFromAssemblyFile(this.DllFile.FullName, this.SetupLoaderConfig);
|
||||
|
||||
if (reloading || this.IsDev)
|
||||
{
|
||||
if (this.IsDev)
|
||||
{
|
||||
// If a dev plugin is set to not autoload on boot, but we want to reload it at the arbitrary load
|
||||
// time, we need to essentially "Unload" the plugin, but we can't call plugin.Unload because of the
|
||||
// load state checks. Null any references to the assembly and types, then proceed with regular reload
|
||||
// operations.
|
||||
this.pluginAssembly = null;
|
||||
this.pluginType = null;
|
||||
}
|
||||
|
||||
this.loader.Reload();
|
||||
|
||||
if (this.IsDev)
|
||||
{
|
||||
// Reload the manifest in-case there were changes here too.
|
||||
var manifestFile = LocalPluginManifest.GetManifestFile(this.DllFile);
|
||||
if (manifestFile.Exists)
|
||||
{
|
||||
this.Manifest = LocalPluginManifest.Load(manifestFile);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Load the assembly
|
||||
this.pluginAssembly ??= this.loader.LoadDefaultAssembly();
|
||||
|
||||
this.AssemblyName = this.pluginAssembly.GetName();
|
||||
|
||||
// Find the plugin interface implementation. It is guaranteed to exist after checking in the ctor.
|
||||
this.pluginType ??= this.pluginAssembly.GetTypes().First(type => type.IsAssignableTo(typeof(IDalamudPlugin)));
|
||||
|
||||
// Check for any loaded plugins with the same assembly name
|
||||
var assemblyName = this.pluginAssembly.GetName().Name;
|
||||
foreach (var otherPlugin in pluginManager.InstalledPlugins)
|
||||
{
|
||||
// During hot-reloading, this plugin will be in the plugin list, and the instance will have been disposed
|
||||
if (otherPlugin == this || otherPlugin.instance == null)
|
||||
continue;
|
||||
|
||||
var otherPluginAssemblyName = otherPlugin.instance.GetType().Assembly.GetName().Name;
|
||||
if (otherPluginAssemblyName == assemblyName)
|
||||
{
|
||||
this.State = PluginState.Unloaded;
|
||||
Log.Debug($"Duplicate assembly: {this.Name}");
|
||||
|
||||
throw new DuplicatePluginException(assemblyName);
|
||||
}
|
||||
}
|
||||
|
||||
// Update the location for the Location and CodeBase patches
|
||||
PluginManager.PluginLocations[this.pluginType.Assembly.FullName] = new(this.DllFile);
|
||||
|
||||
this.DalamudInterface = new DalamudPluginInterface(this.pluginAssembly.GetName().Name!, this.DllFile, reason, this.IsDev);
|
||||
|
||||
var ioc = Service<ServiceContainer>.Get();
|
||||
this.instance = ioc.Create(this.pluginType, this.DalamudInterface) as IDalamudPlugin;
|
||||
if (this.instance == null)
|
||||
{
|
||||
this.State = PluginState.LoadError;
|
||||
this.DalamudInterface.ExplicitDispose();
|
||||
Log.Error($"Error while loading {this.Name}, failed to bind and call the plugin constructor");
|
||||
return;
|
||||
}
|
||||
|
||||
SignatureHelper.Initialise(this.instance);
|
||||
|
||||
// In-case the manifest name was a placeholder. Can occur when no manifest was included.
|
||||
if (this.instance.Name != this.Manifest.Name)
|
||||
{
|
||||
this.Manifest.Name = this.instance.Name;
|
||||
this.Manifest.Save(this.manifestFile);
|
||||
}
|
||||
|
||||
this.State = PluginState.Loaded;
|
||||
Log.Information($"Finished loading {this.DllFile.Name}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
this.State = PluginState.LoadError;
|
||||
Log.Error(ex, $"Error while loading {this.Name}");
|
||||
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unload this plugin. This is the same as dispose, but without the "disposed" connotations. This object should stay
|
||||
/// in the plugin list until it has been actually disposed.
|
||||
/// </summary>
|
||||
/// <param name="reloading">Unload while reloading.</param>
|
||||
public void Unload(bool reloading = false)
|
||||
{
|
||||
// Allowed: Loaded, LoadError(we are cleaning this up while we're at it)
|
||||
switch (this.State)
|
||||
{
|
||||
case PluginState.InProgress:
|
||||
throw new InvalidPluginOperationException($"Unable to unload {this.Name}, already working");
|
||||
case PluginState.Unloaded:
|
||||
throw new InvalidPluginOperationException($"Unable to unload {this.Name}, already unloaded");
|
||||
case PluginState.UnloadError:
|
||||
throw new InvalidPluginOperationException($"Unable to unload {this.Name}, unload previously faulted, restart Dalamud");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
this.State = PluginState.InProgress;
|
||||
Log.Information($"Unloading {this.DllFile.Name}");
|
||||
|
||||
this.instance?.Dispose();
|
||||
this.instance = null;
|
||||
|
||||
this.DalamudInterface?.ExplicitDispose();
|
||||
this.DalamudInterface = null;
|
||||
|
||||
this.pluginType = null;
|
||||
this.pluginAssembly = null;
|
||||
|
||||
if (!reloading)
|
||||
{
|
||||
this.loader?.Dispose();
|
||||
this.loader = null;
|
||||
}
|
||||
|
||||
this.State = PluginState.Unloaded;
|
||||
Log.Information($"Finished unloading {this.DllFile.Name}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
this.State = PluginState.UnloadError;
|
||||
Log.Error(ex, $"Error while unloading {this.Name}");
|
||||
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reload this plugin.
|
||||
/// </summary>
|
||||
public void Reload()
|
||||
{
|
||||
this.Unload(true);
|
||||
|
||||
// We need to handle removed DTR nodes here, as otherwise, plugins will not be able to re-add their bar entries after updates.
|
||||
var dtr = Service<DtrBar>.Get();
|
||||
dtr.HandleRemovedNodes();
|
||||
|
||||
this.Load(PluginLoadReason.Reload, true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Revert a disable. Must be unloaded first, does not load.
|
||||
/// </summary>
|
||||
public void Enable()
|
||||
{
|
||||
// Allowed: Unloaded, UnloadError
|
||||
switch (this.State)
|
||||
{
|
||||
case PluginState.InProgress:
|
||||
case PluginState.Loaded:
|
||||
case PluginState.LoadError:
|
||||
throw new InvalidPluginOperationException($"Unable to enable {this.Name}, still loaded");
|
||||
}
|
||||
|
||||
if (!this.Manifest.Disabled)
|
||||
throw new InvalidPluginOperationException($"Unable to enable {this.Name}, not disabled");
|
||||
|
||||
this.Manifest.Disabled = false;
|
||||
this.SaveManifest();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disable this plugin, must be unloaded first.
|
||||
/// </summary>
|
||||
public void Disable()
|
||||
{
|
||||
// Allowed: Unloaded, UnloadError
|
||||
switch (this.State)
|
||||
{
|
||||
case PluginState.InProgress:
|
||||
case PluginState.Loaded:
|
||||
case PluginState.LoadError:
|
||||
throw new InvalidPluginOperationException($"Unable to disable {this.Name}, still loaded");
|
||||
}
|
||||
|
||||
if (this.Manifest.Disabled)
|
||||
throw new InvalidPluginOperationException($"Unable to disable {this.Name}, already disabled");
|
||||
|
||||
this.Manifest.Disabled = true;
|
||||
this.SaveManifest();
|
||||
}
|
||||
|
||||
private void SetupLoaderConfig(LoaderConfig config)
|
||||
{
|
||||
config.IsUnloadable = true;
|
||||
config.LoadInMemory = true;
|
||||
config.PreferSharedTypes = false;
|
||||
config.SharedAssemblies.Add(typeof(Lumina.GameData).Assembly.GetName());
|
||||
config.SharedAssemblies.Add(typeof(Lumina.Excel.ExcelSheetImpl).Assembly.GetName());
|
||||
}
|
||||
|
||||
private void SaveManifest() => this.Manifest.Save(this.manifestFile);
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -1,120 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Dalamud.Logging.Internal;
|
||||
using Dalamud.Plugin.Internal.Types;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Dalamud.Plugin.Internal
|
||||
{
|
||||
/// <summary>
|
||||
/// This class represents a single plugin repository.
|
||||
/// </summary>
|
||||
internal class PluginRepository
|
||||
{
|
||||
private const string DalamudPluginsMasterUrl = "https://kamori.goats.dev/Plugin/PluginMaster";
|
||||
|
||||
private static readonly ModuleLog Log = new("PLUGINR");
|
||||
|
||||
private static readonly HttpClient HttpClient = new()
|
||||
{
|
||||
DefaultRequestHeaders =
|
||||
{
|
||||
CacheControl = new CacheControlHeaderValue
|
||||
{
|
||||
NoCache = true,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="PluginRepository"/> class.
|
||||
/// </summary>
|
||||
/// <param name="pluginMasterUrl">The plugin master URL.</param>
|
||||
/// <param name="isEnabled">Whether the plugin repo is enabled.</param>
|
||||
public PluginRepository(string pluginMasterUrl, bool isEnabled)
|
||||
{
|
||||
this.PluginMasterUrl = pluginMasterUrl;
|
||||
this.IsThirdParty = pluginMasterUrl != DalamudPluginsMasterUrl;
|
||||
this.IsEnabled = isEnabled;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a new instance of the <see cref="PluginRepository"/> class for the main repo.
|
||||
/// </summary>
|
||||
public static PluginRepository MainRepo => new(DalamudPluginsMasterUrl, true);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the pluginmaster.json URL.
|
||||
/// </summary>
|
||||
public string PluginMasterUrl { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether this plugin repository is from a third party.
|
||||
/// </summary>
|
||||
public bool IsThirdParty { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether this repo is enabled.
|
||||
/// </summary>
|
||||
public bool IsEnabled { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the plugin master list of available plugins.
|
||||
/// </summary>
|
||||
public ReadOnlyCollection<RemotePluginManifest>? PluginMaster { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the initialization state of the plugin repository.
|
||||
/// </summary>
|
||||
public PluginRepositoryState State { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Reload the plugin master asynchronously in a task.
|
||||
/// </summary>
|
||||
/// <returns>The new state.</returns>
|
||||
public async Task ReloadPluginMasterAsync()
|
||||
{
|
||||
this.State = PluginRepositoryState.InProgress;
|
||||
this.PluginMaster = new List<RemotePluginManifest>().AsReadOnly();
|
||||
|
||||
try
|
||||
{
|
||||
Log.Information($"Fetching repo: {this.PluginMasterUrl}");
|
||||
|
||||
using var response = await HttpClient.GetAsync(this.PluginMasterUrl);
|
||||
response.EnsureSuccessStatusCode();
|
||||
|
||||
var data = await response.Content.ReadAsStringAsync();
|
||||
var pluginMaster = JsonConvert.DeserializeObject<List<RemotePluginManifest>>(data);
|
||||
|
||||
if (pluginMaster == null)
|
||||
{
|
||||
throw new Exception("Deserialized PluginMaster was null.");
|
||||
}
|
||||
|
||||
pluginMaster.Sort((pm1, pm2) => pm1.Name.CompareTo(pm2.Name));
|
||||
|
||||
// Set the source for each remote manifest. Allows for checking if is 3rd party.
|
||||
foreach (var manifest in pluginMaster)
|
||||
{
|
||||
manifest.SourceRepo = this;
|
||||
}
|
||||
|
||||
this.PluginMaster = pluginMaster.AsReadOnly();
|
||||
|
||||
Log.Debug($"Successfully fetched repo: {this.PluginMasterUrl}");
|
||||
this.State = PluginRepositoryState.Success;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, $"PluginMaster failed: {this.PluginMasterUrl}");
|
||||
this.State = PluginRepositoryState.Fail;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,36 +1,35 @@
|
|||
namespace Dalamud.Plugin.Internal.Types
|
||||
namespace Dalamud.Plugin.Internal.Types;
|
||||
|
||||
/// <summary>
|
||||
/// Information about an available plugin update.
|
||||
/// </summary>
|
||||
internal record AvailablePluginUpdate
|
||||
{
|
||||
/// <summary>
|
||||
/// Information about an available plugin update.
|
||||
/// Initializes a new instance of the <see cref="AvailablePluginUpdate"/> class.
|
||||
/// </summary>
|
||||
internal record AvailablePluginUpdate
|
||||
/// <param name="installedPlugin">The installed plugin to update.</param>
|
||||
/// <param name="updateManifest">The manifest to use for the update.</param>
|
||||
/// <param name="useTesting">If the testing version should be used for the update.</param>
|
||||
public AvailablePluginUpdate(LocalPlugin installedPlugin, RemotePluginManifest updateManifest, bool useTesting)
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AvailablePluginUpdate"/> class.
|
||||
/// </summary>
|
||||
/// <param name="installedPlugin">The installed plugin to update.</param>
|
||||
/// <param name="updateManifest">The manifest to use for the update.</param>
|
||||
/// <param name="useTesting">If the testing version should be used for the update.</param>
|
||||
public AvailablePluginUpdate(LocalPlugin installedPlugin, RemotePluginManifest updateManifest, bool useTesting)
|
||||
{
|
||||
this.InstalledPlugin = installedPlugin;
|
||||
this.UpdateManifest = updateManifest;
|
||||
this.UseTesting = useTesting;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the currently installed plugin.
|
||||
/// </summary>
|
||||
public LocalPlugin InstalledPlugin { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the available update manifest.
|
||||
/// </summary>
|
||||
public RemotePluginManifest UpdateManifest { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the update should use the testing URL.
|
||||
/// </summary>
|
||||
public bool UseTesting { get; init; }
|
||||
this.InstalledPlugin = installedPlugin;
|
||||
this.UpdateManifest = updateManifest;
|
||||
this.UseTesting = useTesting;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the currently installed plugin.
|
||||
/// </summary>
|
||||
public LocalPlugin InstalledPlugin { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the available update manifest.
|
||||
/// </summary>
|
||||
public RemotePluginManifest UpdateManifest { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the update should use the testing URL.
|
||||
/// </summary>
|
||||
public bool UseTesting { get; init; }
|
||||
}
|
||||
|
|
|
|||
162
Dalamud/Plugin/Internal/Types/LocalDevPlugin.cs
Normal file
162
Dalamud/Plugin/Internal/Types/LocalDevPlugin.cs
Normal file
|
|
@ -0,0 +1,162 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Dalamud.Configuration.Internal;
|
||||
using Dalamud.Interface.Internal.Notifications;
|
||||
using Dalamud.Logging.Internal;
|
||||
|
||||
namespace Dalamud.Plugin.Internal.Types;
|
||||
|
||||
/// <summary>
|
||||
/// This class represents a dev plugin and all facets of its lifecycle.
|
||||
/// The DLL on disk, dependencies, loaded assembly, etc.
|
||||
/// </summary>
|
||||
internal class LocalDevPlugin : LocalPlugin, IDisposable
|
||||
{
|
||||
private static readonly ModuleLog Log = new("PLUGIN");
|
||||
|
||||
// Ref to Dalamud.Configuration.DevPluginSettings
|
||||
private readonly DevPluginSettings devSettings;
|
||||
|
||||
private FileSystemWatcher? fileWatcher;
|
||||
private CancellationTokenSource fileWatcherTokenSource = new();
|
||||
private int reloadCounter;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="LocalDevPlugin"/> class.
|
||||
/// </summary>
|
||||
/// <param name="dllFile">Path to the DLL file.</param>
|
||||
/// <param name="manifest">The plugin manifest.</param>
|
||||
public LocalDevPlugin(FileInfo dllFile, LocalPluginManifest? manifest)
|
||||
: base(dllFile, manifest)
|
||||
{
|
||||
var configuration = Service<DalamudConfiguration>.Get();
|
||||
|
||||
if (!configuration.DevPluginSettings.TryGetValue(dllFile.FullName, out this.devSettings))
|
||||
{
|
||||
configuration.DevPluginSettings[dllFile.FullName] = this.devSettings = new DevPluginSettings();
|
||||
configuration.Save();
|
||||
}
|
||||
|
||||
if (this.AutomaticReload)
|
||||
{
|
||||
this.EnableReloading();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether this dev plugin should start on boot.
|
||||
/// </summary>
|
||||
public bool StartOnBoot
|
||||
{
|
||||
get => this.devSettings.StartOnBoot;
|
||||
set => this.devSettings.StartOnBoot = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether this dev plugin should reload on change.
|
||||
/// </summary>
|
||||
public bool AutomaticReload
|
||||
{
|
||||
get => this.devSettings.AutomaticReloading;
|
||||
set
|
||||
{
|
||||
this.devSettings.AutomaticReloading = value;
|
||||
|
||||
if (this.devSettings.AutomaticReloading)
|
||||
{
|
||||
this.EnableReloading();
|
||||
}
|
||||
else
|
||||
{
|
||||
this.DisableReloading();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public new void Dispose()
|
||||
{
|
||||
if (this.fileWatcher != null)
|
||||
{
|
||||
this.fileWatcher.Changed -= this.OnFileChanged;
|
||||
this.fileWatcherTokenSource.Cancel();
|
||||
this.fileWatcher.Dispose();
|
||||
}
|
||||
|
||||
base.Dispose();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Configure this plugin for automatic reloading and enable it.
|
||||
/// </summary>
|
||||
public void EnableReloading()
|
||||
{
|
||||
if (this.fileWatcher == null && this.DllFile.DirectoryName != null)
|
||||
{
|
||||
this.fileWatcherTokenSource = new CancellationTokenSource();
|
||||
this.fileWatcher = new FileSystemWatcher(this.DllFile.DirectoryName);
|
||||
this.fileWatcher.Changed += this.OnFileChanged;
|
||||
this.fileWatcher.Filter = this.DllFile.Name;
|
||||
this.fileWatcher.NotifyFilter = NotifyFilters.LastWrite;
|
||||
this.fileWatcher.EnableRaisingEvents = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disable automatic reloading for this plugin.
|
||||
/// </summary>
|
||||
public void DisableReloading()
|
||||
{
|
||||
if (this.fileWatcher != null)
|
||||
{
|
||||
this.fileWatcherTokenSource.Cancel();
|
||||
this.fileWatcher.Changed -= this.OnFileChanged;
|
||||
this.fileWatcher.Dispose();
|
||||
this.fileWatcher = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnFileChanged(object sender, FileSystemEventArgs args)
|
||||
{
|
||||
var current = Interlocked.Increment(ref this.reloadCounter);
|
||||
|
||||
Task.Delay(500).ContinueWith(
|
||||
_ =>
|
||||
{
|
||||
if (this.fileWatcherTokenSource.IsCancellationRequested)
|
||||
{
|
||||
Log.Debug($"Skipping reload of {this.Name}, file watcher was cancelled.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (current != this.reloadCounter)
|
||||
{
|
||||
Log.Debug($"Skipping reload of {this.Name}, file has changed again.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.State != PluginState.Loaded)
|
||||
{
|
||||
Log.Debug($"Skipping reload of {this.Name}, state ({this.State}) is not {PluginState.Loaded}.");
|
||||
return;
|
||||
}
|
||||
|
||||
var notificationManager = Service<NotificationManager>.Get();
|
||||
|
||||
try
|
||||
{
|
||||
this.Reload();
|
||||
notificationManager.AddNotification($"The DevPlugin '{this.Name} was reloaded successfully.", "Plugin reloaded!", NotificationType.Success);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "DevPlugin reload failed.");
|
||||
notificationManager.AddNotification($"The DevPlugin '{this.Name} could not be reloaded.", "Plugin reload failed!", NotificationType.Error);
|
||||
}
|
||||
},
|
||||
this.fileWatcherTokenSource.Token);
|
||||
}
|
||||
}
|
||||
501
Dalamud/Plugin/Internal/Types/LocalPlugin.cs
Normal file
501
Dalamud/Plugin/Internal/Types/LocalPlugin.cs
Normal file
|
|
@ -0,0 +1,501 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
|
||||
using Dalamud.Configuration.Internal;
|
||||
using Dalamud.Game;
|
||||
using Dalamud.Game.Gui.Dtr;
|
||||
using Dalamud.IoC.Internal;
|
||||
using Dalamud.Logging.Internal;
|
||||
using Dalamud.Plugin.Internal.Exceptions;
|
||||
using Dalamud.Plugin.Internal.Loader;
|
||||
using Dalamud.Utility;
|
||||
using Dalamud.Utility.Signatures;
|
||||
|
||||
namespace Dalamud.Plugin.Internal.Types;
|
||||
|
||||
/// <summary>
|
||||
/// This class represents a plugin and all facets of its lifecycle.
|
||||
/// The DLL on disk, dependencies, loaded assembly, etc.
|
||||
/// </summary>
|
||||
internal class LocalPlugin : IDisposable
|
||||
{
|
||||
private static readonly ModuleLog Log = new("LOCALPLUGIN");
|
||||
|
||||
private readonly FileInfo manifestFile;
|
||||
private readonly FileInfo disabledFile;
|
||||
private readonly FileInfo testingFile;
|
||||
|
||||
private PluginLoader? loader;
|
||||
private Assembly? pluginAssembly;
|
||||
private Type? pluginType;
|
||||
private IDalamudPlugin? instance;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="LocalPlugin"/> class.
|
||||
/// </summary>
|
||||
/// <param name="dllFile">Path to the DLL file.</param>
|
||||
/// <param name="manifest">The plugin manifest.</param>
|
||||
public LocalPlugin(FileInfo dllFile, LocalPluginManifest? manifest)
|
||||
{
|
||||
if (dllFile.Name == "FFXIVClientStructs.Generators.dll")
|
||||
{
|
||||
// Could this be done another way? Sure. It is an extremely common source
|
||||
// of errors in the log through, and should never be loaded as a plugin.
|
||||
Log.Error($"Not a plugin: {dllFile.FullName}");
|
||||
throw new InvalidPluginException(dllFile);
|
||||
}
|
||||
|
||||
this.DllFile = dllFile;
|
||||
this.State = PluginState.Unloaded;
|
||||
|
||||
this.loader = PluginLoader.CreateFromAssemblyFile(this.DllFile.FullName, SetupLoaderConfig);
|
||||
|
||||
try
|
||||
{
|
||||
this.pluginAssembly = this.loader.LoadDefaultAssembly();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
this.pluginAssembly = null;
|
||||
this.pluginType = null;
|
||||
this.loader.Dispose();
|
||||
|
||||
Log.Error(ex, $"Not a plugin: {this.DllFile.FullName}");
|
||||
throw new InvalidPluginException(this.DllFile);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
this.pluginType = this.pluginAssembly.GetTypes().FirstOrDefault(type => type.IsAssignableTo(typeof(IDalamudPlugin)));
|
||||
}
|
||||
catch (ReflectionTypeLoadException ex)
|
||||
{
|
||||
Log.Error(ex, $"Could not load one or more types when searching for IDalamudPlugin: {this.DllFile.FullName}");
|
||||
// Something blew up when parsing types, but we still want to look for IDalamudPlugin. Let Load() handle the error.
|
||||
this.pluginType = ex.Types.FirstOrDefault(type => type != null && type.IsAssignableTo(typeof(IDalamudPlugin)));
|
||||
}
|
||||
|
||||
if (this.pluginType == default)
|
||||
{
|
||||
this.pluginAssembly = null;
|
||||
this.pluginType = null;
|
||||
this.loader.Dispose();
|
||||
|
||||
Log.Error($"Nothing inherits from IDalamudPlugin: {this.DllFile.FullName}");
|
||||
throw new InvalidPluginException(this.DllFile);
|
||||
}
|
||||
|
||||
var assemblyVersion = this.pluginAssembly.GetName().Version;
|
||||
|
||||
// Although it is conditionally used here, we need to set the initial value regardless.
|
||||
this.manifestFile = LocalPluginManifest.GetManifestFile(this.DllFile);
|
||||
|
||||
// If the parameter manifest was null
|
||||
if (manifest == null)
|
||||
{
|
||||
this.Manifest = new LocalPluginManifest()
|
||||
{
|
||||
Author = "developer",
|
||||
Name = Path.GetFileNameWithoutExtension(this.DllFile.Name),
|
||||
InternalName = Path.GetFileNameWithoutExtension(this.DllFile.Name),
|
||||
AssemblyVersion = assemblyVersion ?? new Version("1.0.0.0"),
|
||||
Description = string.Empty,
|
||||
ApplicableVersion = GameVersion.Any,
|
||||
DalamudApiLevel = PluginManager.DalamudApiLevel,
|
||||
IsHide = false,
|
||||
};
|
||||
|
||||
// Save the manifest to disk so there won't be any problems later.
|
||||
// We'll update the name property after it can be retrieved from the instance.
|
||||
this.Manifest.Save(this.manifestFile);
|
||||
}
|
||||
else
|
||||
{
|
||||
this.Manifest = manifest;
|
||||
}
|
||||
|
||||
// This converts from the ".disabled" file feature to the manifest instead.
|
||||
this.disabledFile = LocalPluginManifest.GetDisabledFile(this.DllFile);
|
||||
if (this.disabledFile.Exists)
|
||||
{
|
||||
this.Manifest.Disabled = true;
|
||||
this.disabledFile.Delete();
|
||||
}
|
||||
|
||||
// This converts from the ".testing" file feature to the manifest instead.
|
||||
this.testingFile = LocalPluginManifest.GetTestingFile(this.DllFile);
|
||||
if (this.testingFile.Exists)
|
||||
{
|
||||
this.Manifest.Testing = true;
|
||||
this.testingFile.Delete();
|
||||
}
|
||||
|
||||
var pluginManager = Service<PluginManager>.Get();
|
||||
this.IsBanned = pluginManager.IsManifestBanned(this.Manifest);
|
||||
this.BanReason = pluginManager.GetBanReason(this.Manifest);
|
||||
|
||||
this.SaveManifest();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the <see cref="DalamudPluginInterface"/> associated with this plugin.
|
||||
/// </summary>
|
||||
public DalamudPluginInterface? DalamudInterface { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the path to the plugin DLL.
|
||||
/// </summary>
|
||||
public FileInfo DllFile { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the plugin manifest, if one exists.
|
||||
/// </summary>
|
||||
public LocalPluginManifest Manifest { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the current state of the plugin.
|
||||
/// </summary>
|
||||
public PluginState State { get; protected set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the AssemblyName plugin, populated during <see cref="Load(PluginLoadReason, bool)"/>.
|
||||
/// </summary>
|
||||
/// <returns>Plugin type.</returns>
|
||||
public AssemblyName? AssemblyName { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the plugin name, directly from the plugin or if it is not loaded from the manifest.
|
||||
/// </summary>
|
||||
public string Name => this.instance?.Name ?? this.Manifest.Name;
|
||||
|
||||
/// <summary>
|
||||
/// Gets an optional reason, if the plugin is banned.
|
||||
/// </summary>
|
||||
public string BanReason { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the plugin is loaded and running.
|
||||
/// </summary>
|
||||
public bool IsLoaded => this.State == PluginState.Loaded;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the plugin is disabled.
|
||||
/// </summary>
|
||||
public bool IsDisabled => this.Manifest.Disabled;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether this plugin's API level is out of date.
|
||||
/// </summary>
|
||||
public bool IsOutdated => this.Manifest.DalamudApiLevel < PluginManager.DalamudApiLevel;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the plugin is for testing use only.
|
||||
/// </summary>
|
||||
public bool IsTesting => this.Manifest.IsTestingExclusive || this.Manifest.Testing;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether this plugin has been banned.
|
||||
/// </summary>
|
||||
public bool IsBanned { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether this plugin is dev plugin.
|
||||
/// </summary>
|
||||
public bool IsDev => this is LocalDevPlugin;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Dispose()
|
||||
{
|
||||
this.instance?.Dispose();
|
||||
this.instance = null;
|
||||
|
||||
this.DalamudInterface?.ExplicitDispose();
|
||||
this.DalamudInterface = null;
|
||||
|
||||
this.pluginType = null;
|
||||
this.pluginAssembly = null;
|
||||
|
||||
this.loader?.Dispose();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Load this plugin.
|
||||
/// </summary>
|
||||
/// <param name="reason">The reason why this plugin is being loaded.</param>
|
||||
/// <param name="reloading">Load while reloading.</param>
|
||||
public void Load(PluginLoadReason reason, bool reloading = false)
|
||||
{
|
||||
var startInfo = Service<DalamudStartInfo>.Get();
|
||||
var configuration = Service<DalamudConfiguration>.Get();
|
||||
var pluginManager = Service<PluginManager>.Get();
|
||||
|
||||
// Allowed: Unloaded
|
||||
switch (this.State)
|
||||
{
|
||||
case PluginState.InProgress:
|
||||
throw new InvalidPluginOperationException($"Unable to load {this.Name}, already working");
|
||||
case PluginState.Loaded:
|
||||
throw new InvalidPluginOperationException($"Unable to load {this.Name}, already loaded");
|
||||
case PluginState.LoadError:
|
||||
throw new InvalidPluginOperationException($"Unable to load {this.Name}, load previously faulted, unload first");
|
||||
case PluginState.UnloadError:
|
||||
throw new InvalidPluginOperationException($"Unable to load {this.Name}, unload previously faulted, restart Dalamud");
|
||||
case PluginState.Unloaded:
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(this.State.ToString());
|
||||
}
|
||||
|
||||
if (pluginManager.IsManifestBanned(this.Manifest))
|
||||
throw new BannedPluginException($"Unable to load {this.Name}, banned");
|
||||
|
||||
if (this.Manifest.ApplicableVersion < startInfo.GameVersion)
|
||||
throw new InvalidPluginOperationException($"Unable to load {this.Name}, no applicable version");
|
||||
|
||||
if (this.Manifest.DalamudApiLevel < PluginManager.DalamudApiLevel && !configuration.LoadAllApiLevels)
|
||||
throw new InvalidPluginOperationException($"Unable to load {this.Name}, incompatible API level");
|
||||
|
||||
if (this.Manifest.Disabled)
|
||||
throw new InvalidPluginOperationException($"Unable to load {this.Name}, disabled");
|
||||
|
||||
this.State = PluginState.InProgress;
|
||||
Log.Information($"Loading {this.DllFile.Name}");
|
||||
|
||||
if (this.DllFile.DirectoryName != null && File.Exists(Path.Combine(this.DllFile.DirectoryName, "Dalamud.dll")))
|
||||
{
|
||||
Log.Error("==== IMPORTANT MESSAGE TO {0}, THE DEVELOPER OF {1} ====", this.Manifest.Author!, this.Manifest.InternalName);
|
||||
Log.Error("YOU ARE INCLUDING DALAMUD DEPENDENCIES IN YOUR BUILDS!!!");
|
||||
Log.Error("You may not be able to load your plugin. \"<Private>False</Private>\" needs to be set in your csproj.");
|
||||
Log.Error("If you are using ILMerge, do not merge anything other than your direct dependencies.");
|
||||
Log.Error("Do not merge FFXIVClientStructs.Generators.dll.");
|
||||
Log.Error("Please refer to https://github.com/goatcorp/Dalamud/discussions/603 for more information.");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
this.loader ??= PluginLoader.CreateFromAssemblyFile(this.DllFile.FullName, SetupLoaderConfig);
|
||||
|
||||
if (reloading || this.IsDev)
|
||||
{
|
||||
if (this.IsDev)
|
||||
{
|
||||
// If a dev plugin is set to not autoload on boot, but we want to reload it at the arbitrary load
|
||||
// time, we need to essentially "Unload" the plugin, but we can't call plugin.Unload because of the
|
||||
// load state checks. Null any references to the assembly and types, then proceed with regular reload
|
||||
// operations.
|
||||
this.pluginAssembly = null;
|
||||
this.pluginType = null;
|
||||
}
|
||||
|
||||
this.loader.Reload();
|
||||
|
||||
if (this.IsDev)
|
||||
{
|
||||
// Reload the manifest in-case there were changes here too.
|
||||
var manifestDevFile = LocalPluginManifest.GetManifestFile(this.DllFile);
|
||||
if (manifestDevFile.Exists)
|
||||
{
|
||||
this.Manifest = LocalPluginManifest.Load(manifestDevFile);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Load the assembly
|
||||
this.pluginAssembly ??= this.loader.LoadDefaultAssembly();
|
||||
|
||||
this.AssemblyName = this.pluginAssembly.GetName();
|
||||
|
||||
// Find the plugin interface implementation. It is guaranteed to exist after checking in the ctor.
|
||||
this.pluginType ??= this.pluginAssembly.GetTypes().First(type => type.IsAssignableTo(typeof(IDalamudPlugin)));
|
||||
|
||||
// Check for any loaded plugins with the same assembly name
|
||||
var assemblyName = this.pluginAssembly.GetName().Name;
|
||||
foreach (var otherPlugin in pluginManager.InstalledPlugins)
|
||||
{
|
||||
// During hot-reloading, this plugin will be in the plugin list, and the instance will have been disposed
|
||||
if (otherPlugin == this || otherPlugin.instance == null)
|
||||
continue;
|
||||
|
||||
var otherPluginAssemblyName = otherPlugin.instance.GetType().Assembly.GetName().Name;
|
||||
if (otherPluginAssemblyName == assemblyName && otherPluginAssemblyName != null)
|
||||
{
|
||||
this.State = PluginState.Unloaded;
|
||||
Log.Debug($"Duplicate assembly: {this.Name}");
|
||||
|
||||
throw new DuplicatePluginException(assemblyName);
|
||||
}
|
||||
}
|
||||
|
||||
// Update the location for the Location and CodeBase patches
|
||||
PluginManager.PluginLocations[this.pluginType.Assembly.FullName] = new PluginPatchData(this.DllFile);
|
||||
|
||||
this.DalamudInterface = new DalamudPluginInterface(this.pluginAssembly.GetName().Name!, this.DllFile, reason, this.IsDev);
|
||||
|
||||
var ioc = Service<ServiceContainer>.Get();
|
||||
this.instance = ioc.Create(this.pluginType, this.DalamudInterface) as IDalamudPlugin;
|
||||
if (this.instance == null)
|
||||
{
|
||||
this.State = PluginState.LoadError;
|
||||
this.DalamudInterface.ExplicitDispose();
|
||||
Log.Error($"Error while loading {this.Name}, failed to bind and call the plugin constructor");
|
||||
return;
|
||||
}
|
||||
|
||||
SignatureHelper.Initialise(this.instance);
|
||||
|
||||
// In-case the manifest name was a placeholder. Can occur when no manifest was included.
|
||||
if (this.instance.Name != this.Manifest.Name)
|
||||
{
|
||||
this.Manifest.Name = this.instance.Name;
|
||||
this.Manifest.Save(this.manifestFile);
|
||||
}
|
||||
|
||||
this.State = PluginState.Loaded;
|
||||
Log.Information($"Finished loading {this.DllFile.Name}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
this.State = PluginState.LoadError;
|
||||
Log.Error(ex, $"Error while loading {this.Name}");
|
||||
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unload this plugin. This is the same as dispose, but without the "disposed" connotations. This object should stay
|
||||
/// in the plugin list until it has been actually disposed.
|
||||
/// </summary>
|
||||
/// <param name="reloading">Unload while reloading.</param>
|
||||
public void Unload(bool reloading = false)
|
||||
{
|
||||
// Allowed: Loaded, LoadError(we are cleaning this up while we're at it)
|
||||
switch (this.State)
|
||||
{
|
||||
case PluginState.InProgress:
|
||||
throw new InvalidPluginOperationException($"Unable to unload {this.Name}, already working");
|
||||
case PluginState.Unloaded:
|
||||
throw new InvalidPluginOperationException($"Unable to unload {this.Name}, already unloaded");
|
||||
case PluginState.UnloadError:
|
||||
throw new InvalidPluginOperationException($"Unable to unload {this.Name}, unload previously faulted, restart Dalamud");
|
||||
case PluginState.Loaded:
|
||||
break;
|
||||
case PluginState.LoadError:
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(this.State.ToString());
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
this.State = PluginState.InProgress;
|
||||
Log.Information($"Unloading {this.DllFile.Name}");
|
||||
|
||||
this.instance?.Dispose();
|
||||
this.instance = null;
|
||||
|
||||
this.DalamudInterface?.ExplicitDispose();
|
||||
this.DalamudInterface = null;
|
||||
|
||||
this.pluginType = null;
|
||||
this.pluginAssembly = null;
|
||||
|
||||
if (!reloading)
|
||||
{
|
||||
this.loader?.Dispose();
|
||||
this.loader = null;
|
||||
}
|
||||
|
||||
this.State = PluginState.Unloaded;
|
||||
Log.Information($"Finished unloading {this.DllFile.Name}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
this.State = PluginState.UnloadError;
|
||||
Log.Error(ex, $"Error while unloading {this.Name}");
|
||||
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reload this plugin.
|
||||
/// </summary>
|
||||
public void Reload()
|
||||
{
|
||||
this.Unload(true);
|
||||
|
||||
// We need to handle removed DTR nodes here, as otherwise, plugins will not be able to re-add their bar entries after updates.
|
||||
var dtr = Service<DtrBar>.Get();
|
||||
dtr.HandleRemovedNodes();
|
||||
|
||||
this.Load(PluginLoadReason.Reload, true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Revert a disable. Must be unloaded first, does not load.
|
||||
/// </summary>
|
||||
public void Enable()
|
||||
{
|
||||
// Allowed: Unloaded, UnloadError
|
||||
switch (this.State)
|
||||
{
|
||||
case PluginState.InProgress:
|
||||
case PluginState.Loaded:
|
||||
case PluginState.LoadError:
|
||||
throw new InvalidPluginOperationException($"Unable to enable {this.Name}, still loaded");
|
||||
case PluginState.Unloaded:
|
||||
break;
|
||||
case PluginState.UnloadError:
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(this.State.ToString());
|
||||
}
|
||||
|
||||
if (!this.Manifest.Disabled)
|
||||
throw new InvalidPluginOperationException($"Unable to enable {this.Name}, not disabled");
|
||||
|
||||
this.Manifest.Disabled = false;
|
||||
this.SaveManifest();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disable this plugin, must be unloaded first.
|
||||
/// </summary>
|
||||
public void Disable()
|
||||
{
|
||||
// Allowed: Unloaded, UnloadError
|
||||
switch (this.State)
|
||||
{
|
||||
case PluginState.InProgress:
|
||||
case PluginState.Loaded:
|
||||
case PluginState.LoadError:
|
||||
throw new InvalidPluginOperationException($"Unable to disable {this.Name}, still loaded");
|
||||
case PluginState.Unloaded:
|
||||
break;
|
||||
case PluginState.UnloadError:
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(this.State.ToString());
|
||||
}
|
||||
|
||||
if (this.Manifest.Disabled)
|
||||
throw new InvalidPluginOperationException($"Unable to disable {this.Name}, already disabled");
|
||||
|
||||
this.Manifest.Disabled = true;
|
||||
this.SaveManifest();
|
||||
}
|
||||
|
||||
private static void SetupLoaderConfig(LoaderConfig config)
|
||||
{
|
||||
config.IsUnloadable = true;
|
||||
config.LoadInMemory = true;
|
||||
config.PreferSharedTypes = false;
|
||||
config.SharedAssemblies.Add(typeof(Lumina.GameData).Assembly.GetName());
|
||||
config.SharedAssemblies.Add(typeof(Lumina.Excel.ExcelSheetImpl).Assembly.GetName());
|
||||
}
|
||||
|
||||
private void SaveManifest() => this.Manifest.Save(this.manifestFile);
|
||||
}
|
||||
|
|
@ -1,99 +1,79 @@
|
|||
using System.IO;
|
||||
|
||||
using Dalamud.Utility;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Dalamud.Plugin.Internal.Types
|
||||
namespace Dalamud.Plugin.Internal.Types;
|
||||
|
||||
/// <summary>
|
||||
/// Information about a plugin, packaged in a json file with the DLL. This variant includes additional information such as
|
||||
/// if the plugin is disabled and if it was installed from a testing URL. This is designed for use with manifests on disk.
|
||||
/// </summary>
|
||||
internal record LocalPluginManifest : PluginManifest
|
||||
{
|
||||
/// <summary>
|
||||
/// Information about a plugin, packaged in a json file with the DLL. This variant includes additional information such as
|
||||
/// if the plugin is disabled and if it was installed from a testing URL. This is designed for use with manifests on disk.
|
||||
/// Gets or sets a value indicating whether the plugin is disabled and should not be loaded.
|
||||
/// This value supersedes the ".disabled" file functionality and should not be included in the plugin master.
|
||||
/// </summary>
|
||||
internal record LocalPluginManifest : PluginManifest
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the plugin is disabled and should not be loaded.
|
||||
/// This value supercedes the ".disabled" file functionality and should not be included in the plugin master.
|
||||
/// </summary>
|
||||
public bool Disabled { get; set; } = false;
|
||||
public bool Disabled { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the plugin should only be loaded when testing is enabled.
|
||||
/// This value supercedes the ".testing" file functionality and should not be included in the plugin master.
|
||||
/// </summary>
|
||||
public bool Testing { get; set; } = false;
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the plugin should only be loaded when testing is enabled.
|
||||
/// This value supersedes the ".testing" file functionality and should not be included in the plugin master.
|
||||
/// </summary>
|
||||
public bool Testing { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the 3rd party repo URL that this plugin was installed from. Used to display where the plugin was
|
||||
/// sourced from on the installed plugin view. This should not be included in the plugin master. This value is null
|
||||
/// when installed from the main repo.
|
||||
/// </summary>
|
||||
public string InstalledFromUrl { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets the 3rd party repo URL that this plugin was installed from. Used to display where the plugin was
|
||||
/// sourced from on the installed plugin view. This should not be included in the plugin master. This value is null
|
||||
/// when installed from the main repo.
|
||||
/// </summary>
|
||||
public string InstalledFromUrl { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether this manifest is associated with a plugin that was installed from a third party
|
||||
/// repo. Unless the manifest has been manually modified, this is determined by the InstalledFromUrl being null.
|
||||
/// </summary>
|
||||
public bool IsThirdParty => !string.IsNullOrEmpty(this.InstalledFromUrl);
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether this manifest is associated with a plugin that was installed from a third party
|
||||
/// repo. Unless the manifest has been manually modified, this is determined by the InstalledFromUrl being null.
|
||||
/// </summary>
|
||||
public bool IsThirdParty => !string.IsNullOrEmpty(this.InstalledFromUrl);
|
||||
|
||||
/// <summary>
|
||||
/// Save a plugin manifest to file.
|
||||
/// </summary>
|
||||
/// <param name="manifestFile">Path to save at.</param>
|
||||
public void Save(FileInfo manifestFile) => File.WriteAllText(manifestFile.FullName, JsonConvert.SerializeObject(this, Formatting.Indented));
|
||||
/// <summary>
|
||||
/// Save a plugin manifest to file.
|
||||
/// </summary>
|
||||
/// <param name="manifestFile">Path to save at.</param>
|
||||
public void Save(FileInfo manifestFile) => File.WriteAllText(manifestFile.FullName, JsonConvert.SerializeObject(this, Formatting.Indented));
|
||||
|
||||
/// <summary>
|
||||
/// Loads a plugin manifest from file.
|
||||
/// </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));
|
||||
/// <summary>
|
||||
/// Loads a plugin manifest from file.
|
||||
/// </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))!;
|
||||
|
||||
/// <summary>
|
||||
/// A standardized way to get the plugin DLL name that should accompany a manifest file. May not exist.
|
||||
/// </summary>
|
||||
/// <param name="dir">Manifest directory.</param>
|
||||
/// <param name="manifest">The manifest.</param>
|
||||
/// <returns>The <see cref="LocalPlugin"/> file.</returns>
|
||||
public static FileInfo GetPluginFile(DirectoryInfo dir, PluginManifest manifest) => new(Path.Combine(dir.FullName, $"{manifest.InternalName}.dll"));
|
||||
/// <summary>
|
||||
/// A standardized way to get the plugin DLL name that should accompany a manifest file. May not exist.
|
||||
/// </summary>
|
||||
/// <param name="dir">Manifest directory.</param>
|
||||
/// <param name="manifest">The manifest.</param>
|
||||
/// <returns>The <see cref="LocalPlugin"/> file.</returns>
|
||||
public static FileInfo GetPluginFile(DirectoryInfo dir, PluginManifest manifest) => new(Path.Combine(dir.FullName, $"{manifest.InternalName}.dll"));
|
||||
|
||||
/// <summary>
|
||||
/// A standardized way to get the manifest file that should accompany a plugin DLL. May not exist.
|
||||
/// </summary>
|
||||
/// <param name="dllFile">The plugin DLL.</param>
|
||||
/// <returns>The <see cref="PluginManifest"/> file.</returns>
|
||||
public static FileInfo GetManifestFile(FileInfo dllFile) => new(Path.Combine(dllFile.DirectoryName, Path.GetFileNameWithoutExtension(dllFile.Name) + ".json"));
|
||||
/// <summary>
|
||||
/// A standardized way to get the manifest file that should accompany a plugin DLL. May not exist.
|
||||
/// </summary>
|
||||
/// <param name="dllFile">The plugin DLL.</param>
|
||||
/// <returns>The <see cref="PluginManifest"/> file.</returns>
|
||||
public static FileInfo GetManifestFile(FileInfo dllFile) => new(Path.Combine(dllFile.DirectoryName!, Path.GetFileNameWithoutExtension(dllFile.Name) + ".json"));
|
||||
|
||||
/// <summary>
|
||||
/// A standardized way to get the obsolete .disabled file that should accompany a plugin DLL. May not exist.
|
||||
/// </summary>
|
||||
/// <param name="dllFile">The plugin DLL.</param>
|
||||
/// <returns>The <see cref="PluginManifest"/> .disabled file.</returns>
|
||||
public static FileInfo GetDisabledFile(FileInfo dllFile) => new(Path.Combine(dllFile.DirectoryName, ".disabled"));
|
||||
/// <summary>
|
||||
/// A standardized way to get the obsolete .disabled file that should accompany a plugin DLL. May not exist.
|
||||
/// </summary>
|
||||
/// <param name="dllFile">The plugin DLL.</param>
|
||||
/// <returns>The <see cref="PluginManifest"/> .disabled file.</returns>
|
||||
public static FileInfo GetDisabledFile(FileInfo dllFile) => new(Path.Combine(dllFile.DirectoryName!, ".disabled"));
|
||||
|
||||
/// <summary>
|
||||
/// A standardized way to get the obsolete .testing file that should accompany a plugin DLL. May not exist.
|
||||
/// </summary>
|
||||
/// <param name="dllFile">The plugin DLL.</param>
|
||||
/// <returns>The <see cref="PluginManifest"/> .testing file.</returns>
|
||||
public static FileInfo GetTestingFile(FileInfo dllFile) => new(Path.Combine(dllFile.DirectoryName, ".testing"));
|
||||
|
||||
/// <summary>
|
||||
/// Check if this manifest is valid.
|
||||
/// </summary>
|
||||
/// <returns>Whether or not this manifest is valid.</returns>
|
||||
public bool CheckSanity()
|
||||
{
|
||||
if (this.InternalName.IsNullOrEmpty())
|
||||
return false;
|
||||
|
||||
if (this.Name.IsNullOrEmpty())
|
||||
return false;
|
||||
|
||||
if (this.DalamudApiLevel == 0)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// A standardized way to get the obsolete .testing file that should accompany a plugin DLL. May not exist.
|
||||
/// </summary>
|
||||
/// <param name="dllFile">The plugin DLL.</param>
|
||||
/// <returns>The <see cref="PluginManifest"/> .testing file.</returns>
|
||||
public static FileInfo GetTestingFile(FileInfo dllFile) => new(Path.Combine(dllFile.DirectoryName!, ".testing"));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,177 +4,159 @@ using System.Collections.Generic;
|
|||
using Dalamud.Game;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Dalamud.Plugin.Internal.Types
|
||||
namespace Dalamud.Plugin.Internal.Types;
|
||||
|
||||
/// <summary>
|
||||
/// Information about a plugin, packaged in a json file with the DLL.
|
||||
/// </summary>
|
||||
internal record PluginManifest
|
||||
{
|
||||
/// <summary>
|
||||
/// Information about a plugin, packaged in a json file with the DLL.
|
||||
/// Gets the author/s of the plugin.
|
||||
/// </summary>
|
||||
internal record PluginManifest
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the author/s of the plugin.
|
||||
/// </summary>
|
||||
[JsonProperty]
|
||||
public string? Author { get; init; }
|
||||
[JsonProperty]
|
||||
public string? Author { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the public name of the plugin.
|
||||
/// </summary>
|
||||
[JsonProperty]
|
||||
public string Name { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets the public name of the plugin.
|
||||
/// </summary>
|
||||
[JsonProperty]
|
||||
public string Name { get; set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a punchline of the plugins functions.
|
||||
/// </summary>
|
||||
[JsonProperty]
|
||||
public string? Punchline { get; init; }
|
||||
/// <summary>
|
||||
/// Gets a punchline of the plugins functions.
|
||||
/// </summary>
|
||||
[JsonProperty]
|
||||
public string? Punchline { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a description of the plugins functions.
|
||||
/// </summary>
|
||||
[JsonProperty]
|
||||
public string? Description { get; init; }
|
||||
/// <summary>
|
||||
/// Gets a description of the plugins functions.
|
||||
/// </summary>
|
||||
[JsonProperty]
|
||||
public string? Description { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a changelog.
|
||||
/// </summary>
|
||||
[JsonProperty]
|
||||
public string? Changelog { get; init; }
|
||||
/// <summary>
|
||||
/// Gets a changelog.
|
||||
/// </summary>
|
||||
[JsonProperty]
|
||||
public string? Changelog { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a list of tags defined on the plugin.
|
||||
/// </summary>
|
||||
[JsonProperty]
|
||||
public List<string>? Tags { get; init; }
|
||||
/// <summary>
|
||||
/// Gets a list of tags defined on the plugin.
|
||||
/// </summary>
|
||||
[JsonProperty]
|
||||
public List<string>? Tags { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a list of category tags defined on the plugin.
|
||||
/// </summary>
|
||||
[JsonProperty]
|
||||
public List<string>? CategoryTags { get; init; }
|
||||
/// <summary>
|
||||
/// Gets a list of category tags defined on the plugin.
|
||||
/// </summary>
|
||||
[JsonProperty]
|
||||
public List<string>? CategoryTags { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether or not the plugin is hidden in the plugin installer.
|
||||
/// This value comes from the plugin master and is in addition to the list of hidden names kept by Dalamud.
|
||||
/// </summary>
|
||||
[JsonProperty]
|
||||
public bool IsHide { get; init; }
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether or not the plugin is hidden in the plugin installer.
|
||||
/// This value comes from the plugin master and is in addition to the list of hidden names kept by Dalamud.
|
||||
/// </summary>
|
||||
[JsonProperty]
|
||||
public bool IsHide { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the internal name of the plugin, which should match the assembly name of the plugin.
|
||||
/// </summary>
|
||||
[JsonProperty]
|
||||
public string InternalName { get; init; }
|
||||
/// <summary>
|
||||
/// Gets the internal name of the plugin, which should match the assembly name of the plugin.
|
||||
/// </summary>
|
||||
[JsonProperty]
|
||||
public string InternalName { get; init; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current assembly version of the plugin.
|
||||
/// </summary>
|
||||
[JsonProperty]
|
||||
public Version AssemblyVersion { get; init; }
|
||||
/// <summary>
|
||||
/// Gets the current assembly version of the plugin.
|
||||
/// </summary>
|
||||
[JsonProperty]
|
||||
public Version AssemblyVersion { get; init; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current testing assembly version of the plugin.
|
||||
/// </summary>
|
||||
[JsonProperty]
|
||||
public Version? TestingAssemblyVersion { get; init; }
|
||||
/// <summary>
|
||||
/// Gets the current testing assembly version of the plugin.
|
||||
/// </summary>
|
||||
[JsonProperty]
|
||||
public Version? TestingAssemblyVersion { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the <see cref="AssemblyVersion"/> is not null.
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public bool HasAssemblyVersion => this.AssemblyVersion != null;
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the plugin is only available for testing.
|
||||
/// </summary>
|
||||
[JsonProperty]
|
||||
public bool IsTestingExclusive { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the <see cref="TestingAssemblyVersion"/> is not null.
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public bool HasTestingAssemblyVersion => this.TestingAssemblyVersion != null;
|
||||
/// <summary>
|
||||
/// Gets an URL to the website or source code of the plugin.
|
||||
/// </summary>
|
||||
[JsonProperty]
|
||||
public string? RepoUrl { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the plugin is only available for testing.
|
||||
/// </summary>
|
||||
[JsonProperty]
|
||||
public bool IsTestingExclusive { get; init; }
|
||||
/// <summary>
|
||||
/// Gets the version of the game this plugin works with.
|
||||
/// </summary>
|
||||
[JsonProperty]
|
||||
[JsonConverter(typeof(GameVersionConverter))]
|
||||
public GameVersion? ApplicableVersion { get; init; } = GameVersion.Any;
|
||||
|
||||
/// <summary>
|
||||
/// Gets an URL to the website or source code of the plugin.
|
||||
/// </summary>
|
||||
[JsonProperty]
|
||||
public string? RepoUrl { get; init; }
|
||||
/// <summary>
|
||||
/// Gets the API level of this plugin. For the current API level, please see <see cref="PluginManager.DalamudApiLevel"/>
|
||||
/// for the currently used API level.
|
||||
/// </summary>
|
||||
[JsonProperty]
|
||||
public int DalamudApiLevel { get; init; } = PluginManager.DalamudApiLevel;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the version of the game this plugin works with.
|
||||
/// </summary>
|
||||
[JsonProperty]
|
||||
[JsonConverter(typeof(GameVersionConverter))]
|
||||
public GameVersion? ApplicableVersion { get; init; } = GameVersion.Any;
|
||||
/// <summary>
|
||||
/// Gets the number of downloads this plugin has.
|
||||
/// </summary>
|
||||
[JsonProperty]
|
||||
public long DownloadCount { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the API level of this plugin. For the current API level, please see <see cref="PluginManager.DalamudApiLevel"/>
|
||||
/// for the currently used API level.
|
||||
/// </summary>
|
||||
[JsonProperty]
|
||||
public int DalamudApiLevel { get; init; } = PluginManager.DalamudApiLevel;
|
||||
/// <summary>
|
||||
/// Gets the last time this plugin was updated.
|
||||
/// </summary>
|
||||
[JsonProperty]
|
||||
public long LastUpdate { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the number of downloads this plugin has.
|
||||
/// </summary>
|
||||
[JsonProperty]
|
||||
public long DownloadCount { get; init; }
|
||||
/// <summary>
|
||||
/// Gets the download link used to install the plugin.
|
||||
/// </summary>
|
||||
[JsonProperty]
|
||||
public string DownloadLinkInstall { get; init; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the last time this plugin was updated.
|
||||
/// </summary>
|
||||
[JsonProperty]
|
||||
public long LastUpdate { get; init; }
|
||||
/// <summary>
|
||||
/// Gets the download link used to update the plugin.
|
||||
/// </summary>
|
||||
[JsonProperty]
|
||||
public string DownloadLinkUpdate { get; init; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the download link used to install the plugin.
|
||||
/// </summary>
|
||||
[JsonProperty]
|
||||
public string DownloadLinkInstall { get; init; }
|
||||
/// <summary>
|
||||
/// Gets the download link used to get testing versions of the plugin.
|
||||
/// </summary>
|
||||
[JsonProperty]
|
||||
public string DownloadLinkTesting { get; init; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the download link used to update the plugin.
|
||||
/// </summary>
|
||||
[JsonProperty]
|
||||
public string DownloadLinkUpdate { get; init; }
|
||||
/// <summary>
|
||||
/// Gets the load priority for this plugin. Higher values means higher priority. 0 is default priority.
|
||||
/// </summary>
|
||||
[JsonProperty]
|
||||
public int LoadPriority { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the download link used to get testing versions of the plugin.
|
||||
/// </summary>
|
||||
[JsonProperty]
|
||||
public string DownloadLinkTesting { get; init; }
|
||||
/// <summary>
|
||||
/// Gets a list of screenshot image URLs to show in the plugin installer.
|
||||
/// </summary>
|
||||
public List<string>? ImageUrls { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the load priority for this plugin. Higher values means higher priority. 0 is default priority.
|
||||
/// </summary>
|
||||
[JsonProperty]
|
||||
public int LoadPriority { get; init; }
|
||||
/// <summary>
|
||||
/// Gets an URL for the plugin's icon.
|
||||
/// </summary>
|
||||
public string? IconUrl { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a list of screenshot image URLs to show in the plugin installer.
|
||||
/// </summary>
|
||||
public List<string>? ImageUrls { get; init; }
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether this plugin accepts feedback.
|
||||
/// </summary>
|
||||
public bool AcceptsFeedback { get; init; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Gets an URL for the plugin's icon.
|
||||
/// </summary>
|
||||
public string? IconUrl { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether this plugin accepts feedback.
|
||||
/// </summary>
|
||||
public bool AcceptsFeedback { get; init; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a message that is shown to users when sending feedback.
|
||||
/// </summary>
|
||||
public string? FeedbackMessage { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating the webhook URL feedback is sent to.
|
||||
/// </summary>
|
||||
public string? FeedbackWebhook { get; init; }
|
||||
}
|
||||
/// <summary>
|
||||
/// Gets a message that is shown to users when sending feedback.
|
||||
/// </summary>
|
||||
public string? FeedbackMessage { get; init; }
|
||||
}
|
||||
|
|
|
|||
118
Dalamud/Plugin/Internal/Types/PluginRepository.cs
Normal file
118
Dalamud/Plugin/Internal/Types/PluginRepository.cs
Normal file
|
|
@ -0,0 +1,118 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Dalamud.Logging.Internal;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Dalamud.Plugin.Internal.Types;
|
||||
|
||||
/// <summary>
|
||||
/// This class represents a single plugin repository.
|
||||
/// </summary>
|
||||
internal class PluginRepository
|
||||
{
|
||||
private const string DalamudPluginsMasterUrl = "https://kamori.goats.dev/Plugin/PluginMaster";
|
||||
|
||||
private static readonly ModuleLog Log = new("PLUGINR");
|
||||
|
||||
private static readonly HttpClient HttpClient = new()
|
||||
{
|
||||
DefaultRequestHeaders =
|
||||
{
|
||||
CacheControl = new CacheControlHeaderValue
|
||||
{
|
||||
NoCache = true,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="PluginRepository"/> class.
|
||||
/// </summary>
|
||||
/// <param name="pluginMasterUrl">The plugin master URL.</param>
|
||||
/// <param name="isEnabled">Whether the plugin repo is enabled.</param>
|
||||
public PluginRepository(string pluginMasterUrl, bool isEnabled)
|
||||
{
|
||||
this.PluginMasterUrl = pluginMasterUrl;
|
||||
this.IsThirdParty = pluginMasterUrl != DalamudPluginsMasterUrl;
|
||||
this.IsEnabled = isEnabled;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a new instance of the <see cref="PluginRepository"/> class for the main repo.
|
||||
/// </summary>
|
||||
public static PluginRepository MainRepo => new(DalamudPluginsMasterUrl, true);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the pluginmaster.json URL.
|
||||
/// </summary>
|
||||
public string PluginMasterUrl { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether this plugin repository is from a third party.
|
||||
/// </summary>
|
||||
public bool IsThirdParty { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether this repo is enabled.
|
||||
/// </summary>
|
||||
public bool IsEnabled { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the plugin master list of available plugins.
|
||||
/// </summary>
|
||||
public ReadOnlyCollection<RemotePluginManifest>? PluginMaster { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the initialization state of the plugin repository.
|
||||
/// </summary>
|
||||
public PluginRepositoryState State { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Reload the plugin master asynchronously in a task.
|
||||
/// </summary>
|
||||
/// <returns>The new state.</returns>
|
||||
public async Task ReloadPluginMasterAsync()
|
||||
{
|
||||
this.State = PluginRepositoryState.InProgress;
|
||||
this.PluginMaster = new List<RemotePluginManifest>().AsReadOnly();
|
||||
|
||||
try
|
||||
{
|
||||
Log.Information($"Fetching repo: {this.PluginMasterUrl}");
|
||||
|
||||
using var response = await HttpClient.GetAsync(this.PluginMasterUrl);
|
||||
response.EnsureSuccessStatusCode();
|
||||
|
||||
var data = await response.Content.ReadAsStringAsync();
|
||||
var pluginMaster = JsonConvert.DeserializeObject<List<RemotePluginManifest>>(data);
|
||||
|
||||
if (pluginMaster == null)
|
||||
{
|
||||
throw new Exception("Deserialized PluginMaster was null.");
|
||||
}
|
||||
|
||||
pluginMaster.Sort((pm1, pm2) => string.Compare(pm1.Name, pm2.Name, StringComparison.Ordinal));
|
||||
|
||||
// Set the source for each remote manifest. Allows for checking if is 3rd party.
|
||||
foreach (var manifest in pluginMaster)
|
||||
{
|
||||
manifest.SourceRepo = this;
|
||||
}
|
||||
|
||||
this.PluginMaster = pluginMaster.AsReadOnly();
|
||||
|
||||
Log.Debug($"Successfully fetched repo: {this.PluginMasterUrl}");
|
||||
this.State = PluginRepositoryState.Success;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, $"PluginMaster failed: {this.PluginMasterUrl}");
|
||||
this.State = PluginRepositoryState.Fail;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,28 +1,27 @@
|
|||
namespace Dalamud.Plugin.Internal.Types
|
||||
namespace Dalamud.Plugin.Internal.Types;
|
||||
|
||||
/// <summary>
|
||||
/// Values representing plugin repository state.
|
||||
/// </summary>
|
||||
internal enum PluginRepositoryState
|
||||
{
|
||||
/// <summary>
|
||||
/// Values representing plugin repository state.
|
||||
/// State is unknown.
|
||||
/// </summary>
|
||||
internal enum PluginRepositoryState
|
||||
{
|
||||
/// <summary>
|
||||
/// State is unknown.
|
||||
/// </summary>
|
||||
Unknown,
|
||||
Unknown,
|
||||
|
||||
/// <summary>
|
||||
/// Currently loading.
|
||||
/// </summary>
|
||||
InProgress,
|
||||
/// <summary>
|
||||
/// Currently loading.
|
||||
/// </summary>
|
||||
InProgress,
|
||||
|
||||
/// <summary>
|
||||
/// Load was successful.
|
||||
/// </summary>
|
||||
Success,
|
||||
/// <summary>
|
||||
/// Load was successful.
|
||||
/// </summary>
|
||||
Success,
|
||||
|
||||
/// <summary>
|
||||
/// Load failed.
|
||||
/// </summary>
|
||||
Fail,
|
||||
}
|
||||
/// <summary>
|
||||
/// Load failed.
|
||||
/// </summary>
|
||||
Fail,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,33 +1,32 @@
|
|||
namespace Dalamud.Plugin.Internal.Types
|
||||
namespace Dalamud.Plugin.Internal.Types;
|
||||
|
||||
/// <summary>
|
||||
/// Values representing plugin load state.
|
||||
/// </summary>
|
||||
internal enum PluginState
|
||||
{
|
||||
/// <summary>
|
||||
/// Values representing plugin load state.
|
||||
/// Plugin is defined, but unloaded.
|
||||
/// </summary>
|
||||
internal enum PluginState
|
||||
{
|
||||
/// <summary>
|
||||
/// Plugin is defined, but unloaded.
|
||||
/// </summary>
|
||||
Unloaded,
|
||||
Unloaded,
|
||||
|
||||
/// <summary>
|
||||
/// Plugin has thrown an error during unload.
|
||||
/// </summary>
|
||||
UnloadError,
|
||||
/// <summary>
|
||||
/// Plugin has thrown an error during unload.
|
||||
/// </summary>
|
||||
UnloadError,
|
||||
|
||||
/// <summary>
|
||||
/// Currently loading.
|
||||
/// </summary>
|
||||
InProgress,
|
||||
/// <summary>
|
||||
/// Currently loading.
|
||||
/// </summary>
|
||||
InProgress,
|
||||
|
||||
/// <summary>
|
||||
/// Load is successful.
|
||||
/// </summary>
|
||||
Loaded,
|
||||
/// <summary>
|
||||
/// Load is successful.
|
||||
/// </summary>
|
||||
Loaded,
|
||||
|
||||
/// <summary>
|
||||
/// Plugin has thrown an error during loading.
|
||||
/// </summary>
|
||||
LoadError,
|
||||
}
|
||||
/// <summary>
|
||||
/// Plugin has thrown an error during loading.
|
||||
/// </summary>
|
||||
LoadError,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,30 +1,29 @@
|
|||
using System;
|
||||
|
||||
namespace Dalamud.Plugin.Internal.Types
|
||||
namespace Dalamud.Plugin.Internal.Types;
|
||||
|
||||
/// <summary>
|
||||
/// Plugin update status.
|
||||
/// </summary>
|
||||
internal class PluginUpdateStatus
|
||||
{
|
||||
/// <summary>
|
||||
/// Plugin update status.
|
||||
/// Gets the plugin internal name.
|
||||
/// </summary>
|
||||
internal class PluginUpdateStatus
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the plugin internal name.
|
||||
/// </summary>
|
||||
public string InternalName { get; set; }
|
||||
public string InternalName { get; init; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the plugin name.
|
||||
/// </summary>
|
||||
public string Name { get; set; }
|
||||
/// <summary>
|
||||
/// Gets the plugin name.
|
||||
/// </summary>
|
||||
public string Name { get; init; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the plugin version.
|
||||
/// </summary>
|
||||
public Version Version { get; set; }
|
||||
/// <summary>
|
||||
/// Gets the plugin version.
|
||||
/// </summary>
|
||||
public Version Version { get; init; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the plugin was updated.
|
||||
/// </summary>
|
||||
public bool WasUpdated { get; set; }
|
||||
}
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the plugin was updated.
|
||||
/// </summary>
|
||||
public bool WasUpdated { get; set; }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,18 +1,19 @@
|
|||
using JetBrains.Annotations;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Dalamud.Plugin.Internal.Types
|
||||
namespace Dalamud.Plugin.Internal.Types;
|
||||
|
||||
/// <summary>
|
||||
/// Information about a plugin, packaged in a json file with the DLL. This variant includes additional information such as
|
||||
/// if the plugin is disabled and if it was installed from a testing URL. This is designed for use with manifests on disk.
|
||||
/// </summary>
|
||||
[UsedImplicitly]
|
||||
internal record RemotePluginManifest : PluginManifest
|
||||
{
|
||||
/// <summary>
|
||||
/// Information about a plugin, packaged in a json file with the DLL. This variant includes additional information such as
|
||||
/// if the plugin is disabled and if it was installed from a testing URL. This is designed for use with manifests on disk.
|
||||
/// Gets or sets the plugin repository this manifest came from. Used in reporting which third party repo a manifest
|
||||
/// may have come from in the plugins available view. This functionality should not be included in the plugin master.
|
||||
/// </summary>
|
||||
internal record RemotePluginManifest : PluginManifest
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the plugin repository this manifest came from. Used in reporting which third party repo a manifest
|
||||
/// may have come from in the plugins available view. This functionality should not be included in the plugin master.
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public PluginRepository SourceRepo { get; set; } = null;
|
||||
}
|
||||
[JsonIgnore]
|
||||
public PluginRepository SourceRepo { get; set; } = null!;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
Subproject commit 7d95dce097dce7aa6712e4ef499a9d4ad8fafed7
|
||||
Subproject commit a2972adbd333d0ad9c127fff1cfc288d1cecf6b4
|
||||
Loading…
Add table
Add a link
Reference in a new issue