mirror of
https://github.com/goatcorp/Dalamud.git
synced 2025-12-14 04:34:16 +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_invert_if_highlighting = none
|
||||||
resharper_loop_can_be_converted_to_query_highlighting = none
|
resharper_loop_can_be_converted_to_query_highlighting = none
|
||||||
resharper_method_has_async_overload_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_redundant_base_qualifier_highlighting = none
|
||||||
resharper_suggest_var_or_type_built_in_types_highlighting = hint
|
resharper_suggest_var_or_type_built_in_types_highlighting = hint
|
||||||
resharper_suggest_var_or_type_elsewhere_highlighting = hint
|
resharper_suggest_var_or_type_elsewhere_highlighting = hint
|
||||||
resharper_suggest_var_or_type_simple_types_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
|
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}]
|
[*.{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
|
name: Tag Build
|
||||||
on: [push]
|
on: [push]
|
||||||
concurrency: build_dalamud
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
tag:
|
tag:
|
||||||
|
|
|
||||||
|
|
@ -37,9 +37,9 @@ namespace Dalamud.Injector
|
||||||
/// <param name="argvPtr">byte** string arguments.</param>
|
/// <param name="argvPtr">byte** string arguments.</param>
|
||||||
public static void Main(int argc, IntPtr argvPtr)
|
public static void Main(int argc, IntPtr argvPtr)
|
||||||
{
|
{
|
||||||
Init();
|
|
||||||
|
|
||||||
List<string> args = new(argc);
|
List<string> args = new(argc);
|
||||||
|
Init(args);
|
||||||
|
|
||||||
unsafe
|
unsafe
|
||||||
{
|
{
|
||||||
var argv = (IntPtr*)argvPtr;
|
var argv = (IntPtr*)argvPtr;
|
||||||
|
|
@ -59,7 +59,11 @@ namespace Dalamud.Injector
|
||||||
// No command defaults to inject
|
// No command defaults to inject
|
||||||
args.Add("inject");
|
args.Add("inject");
|
||||||
args.Add("--all");
|
args.Add("--all");
|
||||||
|
|
||||||
|
#if !DEBUG
|
||||||
args.Add("--warn");
|
args.Add("--warn");
|
||||||
|
#endif
|
||||||
|
|
||||||
}
|
}
|
||||||
else if (int.TryParse(args[1], out var _))
|
else if (int.TryParse(args[1], out var _))
|
||||||
{
|
{
|
||||||
|
|
@ -92,15 +96,13 @@ namespace Dalamud.Injector
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Console.WriteLine("Invalid command: {0}", mainCommand);
|
throw new CommandLineException($"\"{mainCommand}\" is not a valid command.");
|
||||||
ProcessHelpCommand(args);
|
|
||||||
Environment.Exit(-1);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void Init()
|
private static void Init(List<string> args)
|
||||||
{
|
{
|
||||||
InitUnhandledException();
|
InitUnhandledException(args);
|
||||||
InitLogging();
|
InitLogging();
|
||||||
|
|
||||||
var cwd = new FileInfo(Assembly.GetExecutingAssembly().Location).Directory;
|
var cwd = new FileInfo(Assembly.GetExecutingAssembly().Location).Directory;
|
||||||
|
|
@ -111,18 +113,25 @@ namespace Dalamud.Injector
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void InitUnhandledException()
|
private static void InitUnhandledException(List<string> args)
|
||||||
{
|
{
|
||||||
AppDomain.CurrentDomain.UnhandledException += (sender, eventArgs) =>
|
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}");
|
Console.WriteLine($"A fatal error has occurred: {eventArgs.ExceptionObject}");
|
||||||
}
|
}
|
||||||
else
|
else if (exObj is Exception ex)
|
||||||
{
|
|
||||||
var exObj = eventArgs.ExceptionObject;
|
|
||||||
if (exObj is Exception ex)
|
|
||||||
{
|
{
|
||||||
Log.Error(ex, "A fatal error has occurred.");
|
Log.Error(ex, "A fatal error has occurred.");
|
||||||
}
|
}
|
||||||
|
|
@ -130,7 +139,6 @@ namespace Dalamud.Injector
|
||||||
{
|
{
|
||||||
Log.Error($"A fatal error has occurred: {eventArgs.ExceptionObject}");
|
Log.Error($"A fatal error has occurred: {eventArgs.ExceptionObject}");
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
var caption = "Debug Error";
|
var caption = "Debug Error";
|
||||||
|
|
@ -146,7 +154,7 @@ namespace Dalamud.Injector
|
||||||
#endif
|
#endif
|
||||||
_ = MessageBoxW(IntPtr.Zero, message, caption, MessageBoxType.IconError | MessageBoxType.Ok);
|
_ = 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)
|
private static DalamudStartInfo ExtractAndInitializeStartInfoFromArguments(DalamudStartInfo? startInfo, List<string> args)
|
||||||
{
|
{
|
||||||
|
int len;
|
||||||
|
string key;
|
||||||
|
|
||||||
if (startInfo == null)
|
if (startInfo == null)
|
||||||
startInfo = new();
|
startInfo = new();
|
||||||
|
|
||||||
|
|
@ -234,10 +245,10 @@ namespace Dalamud.Injector
|
||||||
var defaultPluginDirectory = startInfo.DefaultPluginDirectory;
|
var defaultPluginDirectory = startInfo.DefaultPluginDirectory;
|
||||||
var assetDirectory = startInfo.AssetDirectory;
|
var assetDirectory = startInfo.AssetDirectory;
|
||||||
var delayInitializeMs = startInfo.DelayInitializeMs;
|
var delayInitializeMs = startInfo.DelayInitializeMs;
|
||||||
|
var languageStr = startInfo.Language.ToString().ToLowerInvariant();
|
||||||
|
|
||||||
for (var i = 2; i < args.Count; i++)
|
for (var i = 2; i < args.Count; i++)
|
||||||
{
|
{
|
||||||
string key;
|
|
||||||
if (args[i].StartsWith(key = "--dalamud-working-directory="))
|
if (args[i].StartsWith(key = "--dalamud-working-directory="))
|
||||||
workingDirectory = args[i][key.Length..];
|
workingDirectory = args[i][key.Length..];
|
||||||
else if (args[i].StartsWith(key = "--dalamud-configuration-path="))
|
else if (args[i].StartsWith(key = "--dalamud-configuration-path="))
|
||||||
|
|
@ -250,6 +261,8 @@ namespace Dalamud.Injector
|
||||||
assetDirectory = args[i][key.Length..];
|
assetDirectory = args[i][key.Length..];
|
||||||
else if (args[i].StartsWith(key = "--dalamud-delay-initialize="))
|
else if (args[i].StartsWith(key = "--dalamud-delay-initialize="))
|
||||||
delayInitializeMs = int.Parse(args[i][key.Length..]);
|
delayInitializeMs = int.Parse(args[i][key.Length..]);
|
||||||
|
else if (args[i].StartsWith(key = "--dalamud-client-language="))
|
||||||
|
languageStr = args[i][key.Length..].ToLowerInvariant();
|
||||||
else
|
else
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
|
|
@ -266,6 +279,26 @@ namespace Dalamud.Injector
|
||||||
defaultPluginDirectory ??= Path.Combine(xivlauncherDir, "devPlugins");
|
defaultPluginDirectory ??= Path.Combine(xivlauncherDir, "devPlugins");
|
||||||
assetDirectory ??= Path.Combine(xivlauncherDir, "dalamudAssets", "dev");
|
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()
|
return new()
|
||||||
{
|
{
|
||||||
WorkingDirectory = workingDirectory,
|
WorkingDirectory = workingDirectory,
|
||||||
|
|
@ -273,7 +306,7 @@ namespace Dalamud.Injector
|
||||||
PluginDirectory = pluginDirectory,
|
PluginDirectory = pluginDirectory,
|
||||||
DefaultPluginDirectory = defaultPluginDirectory,
|
DefaultPluginDirectory = defaultPluginDirectory,
|
||||||
AssetDirectory = assetDirectory,
|
AssetDirectory = assetDirectory,
|
||||||
Language = ClientLanguage.English,
|
Language = clientLanguage,
|
||||||
GameVersion = null,
|
GameVersion = null,
|
||||||
DelayInitializeMs = delayInitializeMs,
|
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} [-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} [-m entrypoint|inject] [--mode=entrypoint|inject]", exeSpaces);
|
||||||
Console.WriteLine("{0} [--handle-owner=inherited-handle-value]", exeSpaces);
|
Console.WriteLine("{0} [--handle-owner=inherited-handle-value]", exeSpaces);
|
||||||
|
Console.WriteLine("{0} [--without-dalamud]", exeSpaces);
|
||||||
Console.WriteLine("{0} [-- game_arg1=value1 game_arg2=value2 ...]", 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("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-plugin-directory=path] [--dalamud-dev-plugin-directory=path]");
|
||||||
Console.WriteLine(" [--dalamud-asset-directory path] [--dalamud-delay-initialize 0(ms)]");
|
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;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
@ -349,8 +384,7 @@ namespace Dalamud.Injector
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Log.Error("\"{0}\" is not a valid argument.", args[i]);
|
throw new CommandLineException($"\"{args[i]}\" is not a command line argument.");
|
||||||
return -1;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -362,8 +396,7 @@ namespace Dalamud.Injector
|
||||||
|
|
||||||
if (!targetProcessSpecified)
|
if (!targetProcessSpecified)
|
||||||
{
|
{
|
||||||
Log.Error("No target process has been specified.");
|
throw new CommandLineException("No target process has been specified. Use -a(--all) option to inject to all ffxiv_dx11.exe processes.");
|
||||||
return -1;
|
|
||||||
}
|
}
|
||||||
else if (!processes.Any())
|
else if (!processes.Any())
|
||||||
{
|
{
|
||||||
|
|
@ -397,6 +430,7 @@ namespace Dalamud.Injector
|
||||||
var useFakeArguments = false;
|
var useFakeArguments = false;
|
||||||
var showHelp = args.Count <= 2;
|
var showHelp = args.Count <= 2;
|
||||||
var handleOwner = IntPtr.Zero;
|
var handleOwner = IntPtr.Zero;
|
||||||
|
var withoutDalamud = false;
|
||||||
|
|
||||||
var parsingGameArgument = false;
|
var parsingGameArgument = false;
|
||||||
for (var i = 2; i < args.Count; i++)
|
for (var i = 2; i < args.Count; i++)
|
||||||
|
|
@ -408,42 +442,25 @@ namespace Dalamud.Injector
|
||||||
}
|
}
|
||||||
|
|
||||||
if (args[i] == "-h" || args[i] == "--help")
|
if (args[i] == "-h" || args[i] == "--help")
|
||||||
{
|
|
||||||
showHelp = true;
|
showHelp = true;
|
||||||
}
|
|
||||||
else if (args[i] == "-f" || args[i] == "--fake-arguments")
|
else if (args[i] == "-f" || args[i] == "--fake-arguments")
|
||||||
{
|
|
||||||
useFakeArguments = true;
|
useFakeArguments = true;
|
||||||
}
|
else if (args[i] == "--without-dalamud")
|
||||||
|
withoutDalamud = true;
|
||||||
else if (args[i] == "-g")
|
else if (args[i] == "-g")
|
||||||
{
|
|
||||||
gamePath = args[++i];
|
gamePath = args[++i];
|
||||||
}
|
|
||||||
else if (args[i].StartsWith("--game="))
|
else if (args[i].StartsWith("--game="))
|
||||||
{
|
|
||||||
gamePath = args[i].Split('=', 2)[1];
|
gamePath = args[i].Split('=', 2)[1];
|
||||||
}
|
|
||||||
else if (args[i] == "-m")
|
else if (args[i] == "-m")
|
||||||
{
|
|
||||||
mode = args[++i];
|
mode = args[++i];
|
||||||
}
|
|
||||||
else if (args[i].StartsWith("--mode="))
|
else if (args[i].StartsWith("--mode="))
|
||||||
{
|
mode = args[i].Split('=', 2)[1];
|
||||||
gamePath = args[i].Split('=', 2)[1];
|
|
||||||
}
|
|
||||||
else if (args[i].StartsWith("--handle-owner="))
|
else if (args[i].StartsWith("--handle-owner="))
|
||||||
{
|
|
||||||
handleOwner = IntPtr.Parse(args[i].Split('=', 2)[1]);
|
handleOwner = IntPtr.Parse(args[i].Split('=', 2)[1]);
|
||||||
}
|
|
||||||
else if (args[i] == "--")
|
else if (args[i] == "--")
|
||||||
{
|
|
||||||
parsingGameArgument = true;
|
parsingGameArgument = true;
|
||||||
}
|
|
||||||
else
|
else
|
||||||
{
|
throw new CommandLineException($"\"{args[i]}\" is not a command line argument.");
|
||||||
Log.Error("No such command found: {0}", args[i]);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (showHelp)
|
if (showHelp)
|
||||||
|
|
@ -463,8 +480,7 @@ namespace Dalamud.Injector
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Log.Error("Invalid mode: {0}", mode);
|
throw new CommandLineException($"\"{mode}\" is not a valid Dalamud load mode.");
|
||||||
return -1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (gamePath == null)
|
if (gamePath == null)
|
||||||
|
|
@ -522,7 +538,7 @@ namespace Dalamud.Injector
|
||||||
"DEV.LobbyHost09=127.0.0.9",
|
"DEV.LobbyHost09=127.0.0.9",
|
||||||
"DEV.LobbyPort09=54994",
|
"DEV.LobbyPort09=54994",
|
||||||
"SYS.Region=0",
|
"SYS.Region=0",
|
||||||
"language=1",
|
$"language={(int)dalamudStartInfo.Language}",
|
||||||
$"ver={gameVersion}",
|
$"ver={gameVersion}",
|
||||||
$"DEV.MaxEntitledExpansionID={maxEntitledExpansionId}",
|
$"DEV.MaxEntitledExpansionID={maxEntitledExpansionId}",
|
||||||
"DEV.GMServerHost=127.0.0.100",
|
"DEV.GMServerHost=127.0.0.100",
|
||||||
|
|
@ -533,7 +549,7 @@ namespace Dalamud.Injector
|
||||||
var gameArgumentString = string.Join(" ", gameArguments.Select(x => EncodeParameterArgument(x)));
|
var gameArgumentString = string.Join(" ", gameArguments.Select(x => EncodeParameterArgument(x)));
|
||||||
var process = NativeAclFix.LaunchGame(Path.GetDirectoryName(gamePath), gamePath, gameArgumentString, (Process p) =>
|
var process = NativeAclFix.LaunchGame(Path.GetDirectoryName(gamePath), gamePath, gameArgumentString, (Process p) =>
|
||||||
{
|
{
|
||||||
if (mode == "entrypoint")
|
if (!withoutDalamud && mode == "entrypoint")
|
||||||
{
|
{
|
||||||
var startInfo = AdjustStartInfo(dalamudStartInfo, gamePath);
|
var startInfo = AdjustStartInfo(dalamudStartInfo, gamePath);
|
||||||
Log.Information("Using start info: {0}", JsonConvert.SerializeObject(startInfo));
|
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);
|
var startInfo = AdjustStartInfo(dalamudStartInfo, gamePath);
|
||||||
Log.Information("Using start info: {0}", JsonConvert.SerializeObject(startInfo));
|
Log.Information("Using start info: {0}", JsonConvert.SerializeObject(startInfo));
|
||||||
|
|
@ -625,7 +641,7 @@ namespace Dalamud.Injector
|
||||||
PluginDirectory = startInfo.PluginDirectory,
|
PluginDirectory = startInfo.PluginDirectory,
|
||||||
DefaultPluginDirectory = startInfo.DefaultPluginDirectory,
|
DefaultPluginDirectory = startInfo.DefaultPluginDirectory,
|
||||||
AssetDirectory = startInfo.AssetDirectory,
|
AssetDirectory = startInfo.AssetDirectory,
|
||||||
Language = ClientLanguage.English,
|
Language = startInfo.Language,
|
||||||
GameVersion = gameVer,
|
GameVersion = gameVer,
|
||||||
DelayInitializeMs = startInfo.DelayInitializeMs,
|
DelayInitializeMs = startInfo.DelayInitializeMs,
|
||||||
};
|
};
|
||||||
|
|
@ -734,5 +750,13 @@ namespace Dalamud.Injector
|
||||||
|
|
||||||
return quoted.ToString();
|
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/=bannedplugin/@EntryIndexedValue">True</s:Boolean>
|
||||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=clientopcode/@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/=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/=Flytext/@EntryIndexedValue">True</s:Boolean>
|
||||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Gpose/@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/=lumina/@EntryIndexedValue">True</s:Boolean>
|
||||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Materia/@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/=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/=Refilter/@EntryIndexedValue">True</s:Boolean>
|
||||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=serveropcode/@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>
|
<s:Boolean x:Key="/Default/UserDictionary/Words/=Universalis/@EntryIndexedValue">True</s:Boolean>
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup Label="Feature">
|
<PropertyGroup Label="Feature">
|
||||||
<DalamudVersion>6.4.0.6</DalamudVersion>
|
<DalamudVersion>6.4.0.9</DalamudVersion>
|
||||||
<Description>XIV Launcher addon framework</Description>
|
<Description>XIV Launcher addon framework</Description>
|
||||||
<AssemblyVersion>$(DalamudVersion)</AssemblyVersion>
|
<AssemblyVersion>$(DalamudVersion)</AssemblyVersion>
|
||||||
<Version>$(DalamudVersion)</Version>
|
<Version>$(DalamudVersion)</Version>
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ using System.Diagnostics;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
using Dalamud.Game.Gui;
|
using Dalamud.Game.Gui;
|
||||||
using Dalamud.Game.Gui.Toast;
|
using Dalamud.Game.Gui.Toast;
|
||||||
|
|
@ -26,7 +27,9 @@ namespace Dalamud.Game
|
||||||
public sealed class Framework : IDisposable
|
public sealed class Framework : IDisposable
|
||||||
{
|
{
|
||||||
private static Stopwatch statsStopwatch = new();
|
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 tier2Initialized = false;
|
||||||
private bool tier3Initialized = false;
|
private bool tier3Initialized = false;
|
||||||
|
|
@ -36,6 +39,8 @@ namespace Dalamud.Game
|
||||||
private Hook<OnDestroyDetour> destroyHook;
|
private Hook<OnDestroyDetour> destroyHook;
|
||||||
private Hook<OnRealDestroyDelegate> realDestroyHook;
|
private Hook<OnRealDestroyDelegate> realDestroyHook;
|
||||||
|
|
||||||
|
private Thread? frameworkUpdateThread;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="Framework"/> class.
|
/// Initializes a new instance of the <see cref="Framework"/> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -113,6 +118,11 @@ namespace Dalamud.Game
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public TimeSpan UpdateDelta { get; private set; } = TimeSpan.Zero;
|
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>
|
/// <summary>
|
||||||
/// Gets or sets a value indicating whether to dispatch update events.
|
/// Gets or sets a value indicating whether to dispatch update events.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -132,6 +142,84 @@ namespace Dalamud.Game
|
||||||
this.realDestroyHook.Enable();
|
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>
|
/// <summary>
|
||||||
/// Dispose of managed and unmanaged resources.
|
/// Dispose of managed and unmanaged resources.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -179,6 +267,8 @@ namespace Dalamud.Game
|
||||||
if (this.tierInitError)
|
if (this.tierInitError)
|
||||||
goto original;
|
goto original;
|
||||||
|
|
||||||
|
this.frameworkUpdateThread ??= Thread.CurrentThread;
|
||||||
|
|
||||||
var dalamud = Service<Dalamud>.Get();
|
var dalamud = Service<Dalamud>.Get();
|
||||||
|
|
||||||
// If this is the first time we are running this loop, we need to init Dalamud subsystems synchronously
|
// If this is the first time we are running this loop, we need to init Dalamud subsystems synchronously
|
||||||
|
|
@ -223,6 +313,8 @@ namespace Dalamud.Game
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
this.runOnNextTickTaskList.RemoveAll(x => x.Run());
|
||||||
|
|
||||||
if (StatsEnabled && this.Update != null)
|
if (StatsEnabled && this.Update != null)
|
||||||
{
|
{
|
||||||
// Stat Tracking for Framework Updates
|
// Stat Tracking for Framework Updates
|
||||||
|
|
@ -312,5 +404,88 @@ namespace Dalamud.Game
|
||||||
// Return the original trampoline location to cleanly exit
|
// Return the original trampoline location to cleanly exit
|
||||||
return originalPtr;
|
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.Data;
|
||||||
using Dalamud.Interface.Internal;
|
using Dalamud.Interface.Internal;
|
||||||
|
using Dalamud.Utility;
|
||||||
using ImGuiNET;
|
using ImGuiNET;
|
||||||
using Lumina.Data.Files;
|
using Lumina.Data.Files;
|
||||||
using Serilog;
|
using Serilog;
|
||||||
|
|
@ -38,6 +39,9 @@ namespace Dalamud.Interface.GameFonts
|
||||||
private readonly Dictionary<GameFontStyle, int> fontUseCounter = new();
|
private readonly Dictionary<GameFontStyle, int> fontUseCounter = new();
|
||||||
private readonly Dictionary<GameFontStyle, Dictionary<char, Tuple<int, FdtReader.FontTableEntry>>> glyphRectIds = new();
|
private readonly Dictionary<GameFontStyle, Dictionary<char, Tuple<int, FdtReader.FontTableEntry>>> glyphRectIds = new();
|
||||||
|
|
||||||
|
private bool isBetweenBuildFontsAndAfterBuildFonts = false;
|
||||||
|
private bool isBuildingAsFallbackFontMode = false;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="GameFontManager"/> class.
|
/// Initializes a new instance of the <see cref="GameFontManager"/> class.
|
||||||
/// </summary>
|
/// </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>
|
/// <summary>
|
||||||
/// Unscales fonts after they have been rendered onto atlas.
|
/// Unscales fonts after they have been rendered onto atlas.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -191,7 +136,7 @@ namespace Dalamud.Interface.GameFonts
|
||||||
font->Descent /= fontScale;
|
font->Descent /= fontScale;
|
||||||
if (font->ConfigData != null)
|
if (font->ConfigData != null)
|
||||||
font->ConfigData->SizePixels /= fontScale;
|
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++)
|
for (int i = 0, i_ = font->Glyphs.Size; i < i_; i++)
|
||||||
{
|
{
|
||||||
var glyph = &glyphs[i];
|
var glyph = &glyphs[i];
|
||||||
|
|
@ -223,16 +168,23 @@ namespace Dalamud.Interface.GameFonts
|
||||||
|
|
||||||
lock (this.syncRoot)
|
lock (this.syncRoot)
|
||||||
{
|
{
|
||||||
var prevValue = this.fontUseCounter.GetValueOrDefault(style, 0);
|
this.fontUseCounter[style] = this.fontUseCounter.GetValueOrDefault(style, 0) + 1;
|
||||||
var newValue = this.fontUseCounter[style] = prevValue + 1;
|
|
||||||
needRebuild = (prevValue == 0) != (newValue == 0) && !this.fonts.ContainsKey(style);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
needRebuild = !this.fonts.ContainsKey(style);
|
||||||
if (needRebuild)
|
if (needRebuild)
|
||||||
{
|
{
|
||||||
Log.Information("[GameFontManager] Calling RebuildFonts because {0} has been requested.", style.ToString());
|
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();
|
this.interfaceManager.RebuildFonts();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return new(this, style);
|
return new(this, style);
|
||||||
}
|
}
|
||||||
|
|
@ -260,7 +212,7 @@ namespace Dalamud.Interface.GameFonts
|
||||||
/// <param name="rebuildLookupTable">Whether to call target.BuildLookupTable().</param>
|
/// <param name="rebuildLookupTable">Whether to call target.BuildLookupTable().</param>
|
||||||
public void CopyGlyphsAcrossFonts(ImFontPtr? source, GameFontStyle target, bool missingOnly, bool rebuildLookupTable)
|
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>
|
/// <summary>
|
||||||
|
|
@ -272,7 +224,7 @@ namespace Dalamud.Interface.GameFonts
|
||||||
/// <param name="rebuildLookupTable">Whether to call target.BuildLookupTable().</param>
|
/// <param name="rebuildLookupTable">Whether to call target.BuildLookupTable().</param>
|
||||||
public void CopyGlyphsAcrossFonts(GameFontStyle source, ImFontPtr? target, bool missingOnly, bool rebuildLookupTable)
|
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>
|
/// <summary>
|
||||||
|
|
@ -284,7 +236,7 @@ namespace Dalamud.Interface.GameFonts
|
||||||
/// <param name="rebuildLookupTable">Whether to call target.BuildLookupTable().</param>
|
/// <param name="rebuildLookupTable">Whether to call target.BuildLookupTable().</param>
|
||||||
public void CopyGlyphsAcrossFonts(GameFontStyle source, GameFontStyle target, bool missingOnly, bool rebuildLookupTable)
|
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>
|
/// <summary>
|
||||||
|
|
@ -293,57 +245,20 @@ namespace Dalamud.Interface.GameFonts
|
||||||
/// <param name="forceMinSize">Whether to load fonts in minimum sizes.</param>
|
/// <param name="forceMinSize">Whether to load fonts in minimum sizes.</param>
|
||||||
public void BuildFonts(bool forceMinSize)
|
public void BuildFonts(bool forceMinSize)
|
||||||
{
|
{
|
||||||
unsafe
|
this.isBuildingAsFallbackFontMode = forceMinSize;
|
||||||
{
|
this.isBetweenBuildFontsAndAfterBuildFonts = true;
|
||||||
ImFontConfigPtr fontConfig = ImGuiNative.ImFontConfig_ImFontConfig();
|
|
||||||
fontConfig.OversampleH = 1;
|
|
||||||
fontConfig.OversampleV = 1;
|
|
||||||
fontConfig.PixelSnapH = false;
|
|
||||||
|
|
||||||
var io = ImGui.GetIO();
|
|
||||||
|
|
||||||
this.glyphRectIds.Clear();
|
this.glyphRectIds.Clear();
|
||||||
this.fonts.Clear();
|
this.fonts.Clear();
|
||||||
|
|
||||||
foreach (var style in this.fontUseCounter.Keys)
|
foreach (var style in this.fontUseCounter.Keys)
|
||||||
{
|
this.EnsureFont(style);
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Post-build fonts before plugins do something more. To be called from InterfaceManager.
|
/// Post-build fonts before plugins do something more. To be called from InterfaceManager.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="forceMinSize">Whether to load fonts in minimum sizes.</param>
|
public unsafe void AfterBuildFonts()
|
||||||
public unsafe void AfterBuildFonts(bool forceMinSize)
|
|
||||||
{
|
{
|
||||||
var ioFonts = ImGui.GetIO().Fonts;
|
var ioFonts = ImGui.GetIO().Fonts;
|
||||||
ioFonts.GetTexDataAsRGBA32(out byte* pixels8, out var width, out var height);
|
ioFonts.GetTexDataAsRGBA32(out byte* pixels8, out var width, out var height);
|
||||||
|
|
@ -352,7 +267,7 @@ namespace Dalamud.Interface.GameFonts
|
||||||
|
|
||||||
foreach (var (style, font) in this.fonts)
|
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 scale = style.SizePt / fdt.FontHeader.Size;
|
||||||
var fontPtr = font.NativePtr;
|
var fontPtr = font.NativePtr;
|
||||||
fontPtr->FontSize = fdt.FontHeader.Size * 4 / 3;
|
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);
|
UnscaleFont(font, 1 / scale, false);
|
||||||
font.BuildLookupTable();
|
font.BuildLookupTable();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.isBetweenBuildFontsAndAfterBuildFonts = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -471,35 +388,41 @@ namespace Dalamud.Interface.GameFonts
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private struct ImFontGlyphReal
|
private unsafe void EnsureFont(GameFontStyle style)
|
||||||
{
|
{
|
||||||
public uint ColoredVisibleCodepoint;
|
var rectIds = this.glyphRectIds[style] = new();
|
||||||
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
|
var fdt = this.fdts[(int)(this.isBuildingAsFallbackFontMode ? style.FamilyWithMinimumSize : style.FamilyAndSize)];
|
||||||
{
|
if (fdt == null)
|
||||||
get => ((this.ColoredVisibleCodepoint >> 0) & 1) != 0;
|
return;
|
||||||
set => this.ColoredVisibleCodepoint = (this.ColoredVisibleCodepoint & 0xFFFFFFFEu) | (value ? 1u : 0u);
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool Visible
|
ImFontConfigPtr fontConfig = ImGuiNative.ImFontConfig_ImFontConfig();
|
||||||
{
|
fontConfig.OversampleH = 1;
|
||||||
get => ((this.ColoredVisibleCodepoint >> 1) & 1) != 0;
|
fontConfig.OversampleV = 1;
|
||||||
set => this.ColoredVisibleCodepoint = (this.ColoredVisibleCodepoint & 0xFFFFFFFDu) | (value ? 2u : 0u);
|
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);
|
var c = glyph.Char;
|
||||||
set => this.ColoredVisibleCodepoint = (this.ColoredVisibleCodepoint & 3u) | ((uint)this.Codepoint << 2);
|
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>
|
/// <param name="callback">The action to execute when the dialog is finished.</param>
|
||||||
public void OpenFolderDialog(string title, Action<bool, string> callback)
|
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, callback);
|
||||||
this.SetDialog("OpenFolderDialog", title, string.Empty, this.savedPath, ".", string.Empty, 1, false, ImGuiFileDialogFlags.SelectOnly);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -33,8 +32,7 @@ namespace Dalamud.Interface.ImGuiFileDialog
|
||||||
/// <param name="isModal">Whether the dialog should be a modal popup.</param>
|
/// <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)
|
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, callback);
|
||||||
this.SetDialog("OpenFolderDialog", title, string.Empty, startPath ?? this.savedPath, ".", string.Empty, 1, isModal, ImGuiFileDialogFlags.SelectOnly);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -45,8 +43,7 @@ namespace Dalamud.Interface.ImGuiFileDialog
|
||||||
/// <param name="callback">The action to execute when the dialog is finished.</param>
|
/// <param name="callback">The action to execute when the dialog is finished.</param>
|
||||||
public void SaveFolderDialog(string title, string defaultFolderName, Action<bool, string> callback)
|
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, callback);
|
||||||
this.SetDialog("SaveFolderDialog", title, string.Empty, this.savedPath, defaultFolderName, string.Empty, 1, false, ImGuiFileDialogFlags.None);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -59,8 +56,7 @@ namespace Dalamud.Interface.ImGuiFileDialog
|
||||||
/// <param name="isModal">Whether the dialog should be a modal popup.</param>
|
/// <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)
|
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, callback);
|
||||||
this.SetDialog("SaveFolderDialog", title, string.Empty, startPath ?? this.savedPath, defaultFolderName, string.Empty, 1, isModal, ImGuiFileDialogFlags.None);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -71,8 +67,7 @@ namespace Dalamud.Interface.ImGuiFileDialog
|
||||||
/// <param name="callback">The action to execute when the dialog is finished.</param>
|
/// <param name="callback">The action to execute when the dialog is finished.</param>
|
||||||
public void OpenFileDialog(string title, string filters, Action<bool, string> callback)
|
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, callback);
|
||||||
this.SetDialog("OpenFileDialog", title, filters, this.savedPath, ".", string.Empty, 1, false, ImGuiFileDialogFlags.SelectOnly);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -81,19 +76,18 @@ namespace Dalamud.Interface.ImGuiFileDialog
|
||||||
/// <param name="title">The header title of the dialog.</param>
|
/// <param name="title">The header title of the dialog.</param>
|
||||||
/// <param name="filters">Which files to show in 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="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="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>
|
/// <param name="isModal">Whether the dialog should be a modal popup.</param>
|
||||||
public void OpenFileDialog(
|
public void OpenFileDialog(
|
||||||
string title,
|
string title,
|
||||||
string filters,
|
string filters,
|
||||||
Action<bool, List<string>> callback,
|
Action<bool, List<string>> callback,
|
||||||
|
int selectionCountMax,
|
||||||
string? startPath = null,
|
string? startPath = null,
|
||||||
int selectionCountMax = 1,
|
|
||||||
bool isModal = false)
|
bool isModal = false)
|
||||||
{
|
{
|
||||||
this.SetCallback(callback);
|
this.SetDialog("OpenFileDialog", title, filters, startPath ?? this.savedPath, ".", string.Empty, selectionCountMax, isModal, ImGuiFileDialogFlags.SelectOnly, callback);
|
||||||
this.SetDialog("OpenFileDialog", title, filters, startPath ?? this.savedPath, ".", string.Empty, selectionCountMax, isModal, ImGuiFileDialogFlags.SelectOnly);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -111,8 +105,7 @@ namespace Dalamud.Interface.ImGuiFileDialog
|
||||||
string defaultExtension,
|
string defaultExtension,
|
||||||
Action<bool, string> callback)
|
Action<bool, string> callback)
|
||||||
{
|
{
|
||||||
this.SetCallback(callback);
|
this.SetDialog("SaveFileDialog", title, filters, this.savedPath, defaultFileName, defaultExtension, 1, false, ImGuiFileDialogFlags.None, callback);
|
||||||
this.SetDialog("SaveFileDialog", title, filters, this.savedPath, defaultFileName, defaultExtension, 1, false, ImGuiFileDialogFlags.None);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -134,8 +127,7 @@ namespace Dalamud.Interface.ImGuiFileDialog
|
||||||
string? startPath,
|
string? startPath,
|
||||||
bool isModal = false)
|
bool isModal = false)
|
||||||
{
|
{
|
||||||
this.SetCallback(callback);
|
this.SetDialog("SaveFileDialog", title, filters, startPath ?? this.savedPath, defaultFileName, defaultExtension, 1, isModal, ImGuiFileDialogFlags.None, callback);
|
||||||
this.SetDialog("SaveFileDialog", title, filters, startPath ?? this.savedPath, defaultFileName, defaultExtension, 1, isModal, ImGuiFileDialogFlags.None);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -166,18 +158,6 @@ namespace Dalamud.Interface.ImGuiFileDialog
|
||||||
this.multiCallback = null;
|
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(
|
private void SetDialog(
|
||||||
string id,
|
string id,
|
||||||
string title,
|
string title,
|
||||||
|
|
@ -187,9 +167,19 @@ namespace Dalamud.Interface.ImGuiFileDialog
|
||||||
string defaultExtension,
|
string defaultExtension,
|
||||||
int selectionCountMax,
|
int selectionCountMax,
|
||||||
bool isModal,
|
bool isModal,
|
||||||
ImGuiFileDialogFlags flags)
|
ImGuiFileDialogFlags flags,
|
||||||
|
Delegate callback)
|
||||||
{
|
{
|
||||||
this.Reset();
|
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 = new FileDialog(id, title, filters, path, defaultFileName, defaultExtension, selectionCountMax, isModal, flags);
|
||||||
this.dialog.Show();
|
this.dialog.Show();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,7 @@
|
||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
|
|
||||||
using ImGuiNET;
|
using ImGuiNET;
|
||||||
|
|
@ -136,6 +139,67 @@ namespace Dalamud.Interface
|
||||||
/// <param name="text">The text to write.</param>
|
/// <param name="text">The text to write.</param>
|
||||||
public static void SafeTextWrapped(string text) => ImGui.TextWrapped(text.Replace("%", "%%"));
|
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>
|
/// <summary>
|
||||||
/// Get data needed for each new frame.
|
/// Get data needed for each new frame.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -143,5 +207,41 @@ namespace Dalamud.Interface
|
||||||
{
|
{
|
||||||
GlobalScale = ImGui.GetIO().FontGlobalScale;
|
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>
|
/// </summary>
|
||||||
public int FontResolutionLevel => this.FontResolutionLevelOverride ?? Service<DalamudConfiguration>.Get().FontResolutionLevel;
|
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>
|
/// <summary>
|
||||||
/// Enable this module.
|
/// Enable this module.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -900,7 +905,7 @@ namespace Dalamud.Interface.Internal
|
||||||
texPixels[i] = (byte)(Math.Pow(texPixels[i] / 255.0f, 1.0f / fontGamma) * 255.0f);
|
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)
|
foreach (var (font, mod) in this.loadedFontInfo)
|
||||||
{
|
{
|
||||||
|
|
@ -929,14 +934,14 @@ namespace Dalamud.Interface.Internal
|
||||||
font.Descent = mod.SourceAxis.ImFont.Descent;
|
font.Descent = mod.SourceAxis.ImFont.Descent;
|
||||||
font.FallbackChar = mod.SourceAxis.ImFont.FallbackChar;
|
font.FallbackChar = mod.SourceAxis.ImFont.FallbackChar;
|
||||||
font.EllipsisChar = mod.SourceAxis.ImFont.EllipsisChar;
|
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)
|
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);
|
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)
|
if (!this.UseAxis && font.NativePtr == DefaultFont.NativePtr)
|
||||||
mod.SourceAxis.ImFont.FontSize -= 1;
|
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)
|
if (!this.UseAxis && font.NativePtr == DefaultFont.NativePtr)
|
||||||
mod.SourceAxis.ImFont.FontSize += 1;
|
mod.SourceAxis.ImFont.FontSize += 1;
|
||||||
}
|
}
|
||||||
|
|
@ -946,7 +951,7 @@ namespace Dalamud.Interface.Internal
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fill missing glyphs in MonoFont from DefaultFont
|
// 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++)
|
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? dtrTest2;
|
||||||
private DtrBarEntry? dtrTest3;
|
private DtrBarEntry? dtrTest3;
|
||||||
|
|
||||||
|
// Task Scheduler
|
||||||
|
private CancellationTokenSource taskSchedCancelSource = new();
|
||||||
|
|
||||||
private uint copyButtonIndex = 0;
|
private uint copyButtonIndex = 0;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -1359,6 +1362,15 @@ namespace Dalamud.Interface.Internal.Windows
|
||||||
ImGuiHelpers.ScaledDummy(10);
|
ImGuiHelpers.ScaledDummy(10);
|
||||||
ImGui.SameLine();
|
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"))
|
if (ImGui.Button("Short Task.Run"))
|
||||||
{
|
{
|
||||||
Task.Run(() => { Thread.Sleep(500); });
|
Task.Run(() => { Thread.Sleep(500); });
|
||||||
|
|
@ -1368,7 +1380,8 @@ namespace Dalamud.Interface.Internal.Windows
|
||||||
|
|
||||||
if (ImGui.Button("Task in task(Delay)"))
|
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();
|
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"))
|
if (ImGui.Button("Drown in tasks"))
|
||||||
{
|
{
|
||||||
|
var token = this.taskSchedCancelSource.Token;
|
||||||
Task.Run(() =>
|
Task.Run(() =>
|
||||||
{
|
{
|
||||||
for (var i = 0; i < 100; i++)
|
for (var i = 0; i < 100; i++)
|
||||||
{
|
{
|
||||||
|
token.ThrowIfCancellationRequested();
|
||||||
Task.Run(() =>
|
Task.Run(() =>
|
||||||
{
|
{
|
||||||
for (var i = 0; i < 100; i++)
|
for (var i = 0; i < 100; i++)
|
||||||
{
|
{
|
||||||
|
token.ThrowIfCancellationRequested();
|
||||||
Task.Run(() =>
|
Task.Run(() =>
|
||||||
{
|
{
|
||||||
for (var i = 0; i < 100; i++)
|
for (var i = 0; i < 100; i++)
|
||||||
{
|
{
|
||||||
|
token.ThrowIfCancellationRequested();
|
||||||
Task.Run(() =>
|
Task.Run(() =>
|
||||||
{
|
{
|
||||||
for (var i = 0; i < 100; i++)
|
for (var i = 0; i < 100; i++)
|
||||||
{
|
{
|
||||||
Task.Run(() =>
|
token.ThrowIfCancellationRequested();
|
||||||
|
Task.Run(async () =>
|
||||||
{
|
{
|
||||||
for (var i = 0; i < 100; i++)
|
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
|
#pragma warning disable 1998
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
using System;
|
using System;
|
||||||
|
|
||||||
using Dalamud.Plugin.Internal;
|
using Dalamud.Plugin.Internal;
|
||||||
|
using Dalamud.Plugin.Internal.Types;
|
||||||
using Dalamud.Utility;
|
using Dalamud.Utility;
|
||||||
|
|
||||||
namespace Dalamud.Interface.Internal.Windows.PluginInstaller
|
namespace Dalamud.Interface.Internal.Windows.PluginInstaller
|
||||||
|
|
|
||||||
|
|
@ -2013,6 +2013,7 @@ namespace Dalamud.Interface.Internal.Windows.PluginInstaller
|
||||||
return hasSearchString && !(
|
return hasSearchString && !(
|
||||||
manifest.Name.ToLowerInvariant().Contains(searchString) ||
|
manifest.Name.ToLowerInvariant().Contains(searchString) ||
|
||||||
(!manifest.Author.IsNullOrEmpty() && manifest.Author.Equals(this.searchText, StringComparison.InvariantCultureIgnoreCase)) ||
|
(!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)));
|
(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_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);
|
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>
|
/// </summary>
|
||||||
internal class SettingsWindow : Window
|
internal class SettingsWindow : Window
|
||||||
{
|
{
|
||||||
private const float MinScale = 0.3f;
|
|
||||||
private const float MaxScale = 3.0f;
|
|
||||||
|
|
||||||
private readonly string[] languages;
|
private readonly string[] languages;
|
||||||
private readonly string[] locLanguages;
|
private readonly string[] locLanguages;
|
||||||
|
|
||||||
|
|
@ -179,7 +176,8 @@ namespace Dalamud.Interface.Internal.Windows
|
||||||
var configuration = Service<DalamudConfiguration>.Get();
|
var configuration = Service<DalamudConfiguration>.Get();
|
||||||
var interfaceManager = Service<InterfaceManager>.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.FontResolutionLevel != configuration.FontResolutionLevel
|
||||||
|| interfaceManager.UseAxis != configuration.UseAxisFontsFromGame;
|
|| interfaceManager.UseAxis != configuration.UseAxisFontsFromGame;
|
||||||
|
|
||||||
|
|
@ -298,7 +296,7 @@ namespace Dalamud.Interface.Internal.Windows
|
||||||
ImGui.Text(Loc.Localize("DalamudSettingsGlobalUiScale", "Global Font Scale"));
|
ImGui.Text(Loc.Localize("DalamudSettingsGlobalUiScale", "Global Font Scale"));
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
ImGui.SetCursorPosY(ImGui.GetCursorPosY() - 3);
|
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;
|
this.globalUiScale = 9.6f / 12.0f;
|
||||||
ImGui.GetIO().FontGlobalScale = this.globalUiScale;
|
ImGui.GetIO().FontGlobalScale = this.globalUiScale;
|
||||||
|
|
@ -306,7 +304,7 @@ namespace Dalamud.Interface.Internal.Windows
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
if (ImGui.Button(Loc.Localize("DalamudSettingsUiScalePreset12", "Reset (12pt)") + "##DalamudSettingsGlobalUiScaleReset12"))
|
if (ImGui.Button("12pt##DalamudSettingsGlobalUiScaleReset12"))
|
||||||
{
|
{
|
||||||
this.globalUiScale = 1.0f;
|
this.globalUiScale = 1.0f;
|
||||||
ImGui.GetIO().FontGlobalScale = this.globalUiScale;
|
ImGui.GetIO().FontGlobalScale = this.globalUiScale;
|
||||||
|
|
@ -314,7 +312,7 @@ namespace Dalamud.Interface.Internal.Windows
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
if (ImGui.Button(Loc.Localize("DalamudSettingsUiScalePreset14", "14pt") + "##DalamudSettingsGlobalUiScaleReset14"))
|
if (ImGui.Button("14pt##DalamudSettingsGlobalUiScaleReset14"))
|
||||||
{
|
{
|
||||||
this.globalUiScale = 14.0f / 12.0f;
|
this.globalUiScale = 14.0f / 12.0f;
|
||||||
ImGui.GetIO().FontGlobalScale = this.globalUiScale;
|
ImGui.GetIO().FontGlobalScale = this.globalUiScale;
|
||||||
|
|
@ -322,7 +320,7 @@ namespace Dalamud.Interface.Internal.Windows
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
if (ImGui.Button(Loc.Localize("DalamudSettingsUiScalePreset18", "18pt") + "##DalamudSettingsGlobalUiScaleReset18"))
|
if (ImGui.Button("18pt##DalamudSettingsGlobalUiScaleReset18"))
|
||||||
{
|
{
|
||||||
this.globalUiScale = 18.0f / 12.0f;
|
this.globalUiScale = 18.0f / 12.0f;
|
||||||
ImGui.GetIO().FontGlobalScale = this.globalUiScale;
|
ImGui.GetIO().FontGlobalScale = this.globalUiScale;
|
||||||
|
|
@ -330,15 +328,17 @@ namespace Dalamud.Interface.Internal.Windows
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
if (ImGui.Button(Loc.Localize("DalamudSettingsUiScalePreset36", "36pt") + "##DalamudSettingsGlobalUiScaleReset36"))
|
if (ImGui.Button("36pt##DalamudSettingsGlobalUiScaleReset36"))
|
||||||
{
|
{
|
||||||
this.globalUiScale = 36.0f / 12.0f;
|
this.globalUiScale = 36.0f / 12.0f;
|
||||||
ImGui.GetIO().FontGlobalScale = this.globalUiScale;
|
ImGui.GetIO().FontGlobalScale = this.globalUiScale;
|
||||||
interfaceManager.RebuildFonts();
|
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;
|
ImGui.GetIO().FontGlobalScale = this.globalUiScale;
|
||||||
interfaceManager.RebuildFonts();
|
interfaceManager.RebuildFonts();
|
||||||
}
|
}
|
||||||
|
|
@ -436,7 +436,7 @@ namespace Dalamud.Interface.Internal.Windows
|
||||||
interfaceManager.RebuildFonts();
|
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.FontGammaOverride = this.fontGamma;
|
||||||
interfaceManager.RebuildFonts();
|
interfaceManager.RebuildFonts();
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
|
@ -138,11 +139,7 @@ namespace Dalamud.Plugin
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets a value indicating whether Dalamud is running in Debug mode or the /xldev menu is open. This can occur on release builds.
|
/// Gets a value indicating whether Dalamud is running in Debug mode or the /xldev menu is open. This can occur on release builds.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
#if DEBUG
|
public bool IsDebugging => Debugger.IsAttached;
|
||||||
public bool IsDebugging => true;
|
|
||||||
#else
|
|
||||||
public bool IsDebugging => Service<DalamudInterface>.GetNullable() is {IsDevMenuOpen: true}; // Can be null during boot
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the current UI language in two-letter iso format.
|
/// 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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -21,8 +21,8 @@ using Dalamud.Plugin.Internal.Types;
|
||||||
using Dalamud.Utility;
|
using Dalamud.Utility;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
namespace Dalamud.Plugin.Internal
|
namespace Dalamud.Plugin.Internal;
|
||||||
{
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Class responsible for loading and unloading plugins.
|
/// Class responsible for loading and unloading plugins.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -1134,4 +1134,3 @@ namespace Dalamud.Plugin.Internal
|
||||||
this.assemblyCodeBaseMonoHook = new MonoMod.RuntimeDetour.Hook(codebaseTarget, codebasePatch);
|
this.assemblyCodeBaseMonoHook = new MonoMod.RuntimeDetour.Hook(codebaseTarget, codebasePatch);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -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,5 +1,5 @@
|
||||||
namespace Dalamud.Plugin.Internal.Types
|
namespace Dalamud.Plugin.Internal.Types;
|
||||||
{
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Information about an available plugin update.
|
/// Information about an available plugin update.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -33,4 +33,3 @@ namespace Dalamud.Plugin.Internal.Types
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool UseTesting { get; init; }
|
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,10 +1,9 @@
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
|
||||||
using Dalamud.Utility;
|
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
namespace Dalamud.Plugin.Internal.Types
|
namespace Dalamud.Plugin.Internal.Types;
|
||||||
{
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Information about a plugin, packaged in a json file with the DLL. This variant includes additional information such as
|
/// 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.
|
/// if the plugin is disabled and if it was installed from a testing URL. This is designed for use with manifests on disk.
|
||||||
|
|
@ -13,22 +12,22 @@ namespace Dalamud.Plugin.Internal.Types
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets a value indicating whether the plugin is disabled and should not be loaded.
|
/// 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.
|
/// This value supersedes the ".disabled" file functionality and should not be included in the plugin master.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool Disabled { get; set; } = false;
|
public bool Disabled { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets a value indicating whether the plugin should only be loaded when testing is enabled.
|
/// 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.
|
/// This value supersedes the ".testing" file functionality and should not be included in the plugin master.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool Testing { get; set; } = false;
|
public bool Testing { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the 3rd party repo URL that this plugin was installed from. Used to display where the plugin was
|
/// 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
|
/// 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.
|
/// when installed from the main repo.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string InstalledFromUrl { get; set; }
|
public string InstalledFromUrl { get; set; } = string.Empty;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets a value indicating whether this manifest is associated with a plugin that was installed from a third party
|
/// Gets a value indicating whether this manifest is associated with a plugin that was installed from a third party
|
||||||
|
|
@ -47,7 +46,7 @@ namespace Dalamud.Plugin.Internal.Types
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="manifestFile">Path to the manifest.</param>
|
/// <param name="manifestFile">Path to the manifest.</param>
|
||||||
/// <returns>A <see cref="PluginManifest"/> object.</returns>
|
/// <returns>A <see cref="PluginManifest"/> object.</returns>
|
||||||
public static LocalPluginManifest Load(FileInfo manifestFile) => JsonConvert.DeserializeObject<LocalPluginManifest>(File.ReadAllText(manifestFile.FullName));
|
public static LocalPluginManifest Load(FileInfo manifestFile) => JsonConvert.DeserializeObject<LocalPluginManifest>(File.ReadAllText(manifestFile.FullName))!;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A standardized way to get the plugin DLL name that should accompany a manifest file. May not exist.
|
/// A standardized way to get the plugin DLL name that should accompany a manifest file. May not exist.
|
||||||
|
|
@ -62,38 +61,19 @@ namespace Dalamud.Plugin.Internal.Types
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="dllFile">The plugin DLL.</param>
|
/// <param name="dllFile">The plugin DLL.</param>
|
||||||
/// <returns>The <see cref="PluginManifest"/> file.</returns>
|
/// <returns>The <see cref="PluginManifest"/> file.</returns>
|
||||||
public static FileInfo GetManifestFile(FileInfo dllFile) => new(Path.Combine(dllFile.DirectoryName, Path.GetFileNameWithoutExtension(dllFile.Name) + ".json"));
|
public static FileInfo GetManifestFile(FileInfo dllFile) => new(Path.Combine(dllFile.DirectoryName!, Path.GetFileNameWithoutExtension(dllFile.Name) + ".json"));
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A standardized way to get the obsolete .disabled file that should accompany a plugin DLL. May not exist.
|
/// A standardized way to get the obsolete .disabled file that should accompany a plugin DLL. May not exist.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="dllFile">The plugin DLL.</param>
|
/// <param name="dllFile">The plugin DLL.</param>
|
||||||
/// <returns>The <see cref="PluginManifest"/> .disabled file.</returns>
|
/// <returns>The <see cref="PluginManifest"/> .disabled file.</returns>
|
||||||
public static FileInfo GetDisabledFile(FileInfo dllFile) => new(Path.Combine(dllFile.DirectoryName, ".disabled"));
|
public static FileInfo GetDisabledFile(FileInfo dllFile) => new(Path.Combine(dllFile.DirectoryName!, ".disabled"));
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A standardized way to get the obsolete .testing file that should accompany a plugin DLL. May not exist.
|
/// A standardized way to get the obsolete .testing file that should accompany a plugin DLL. May not exist.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="dllFile">The plugin DLL.</param>
|
/// <param name="dllFile">The plugin DLL.</param>
|
||||||
/// <returns>The <see cref="PluginManifest"/> .testing file.</returns>
|
/// <returns>The <see cref="PluginManifest"/> .testing file.</returns>
|
||||||
public static FileInfo GetTestingFile(FileInfo dllFile) => new(Path.Combine(dllFile.DirectoryName, ".testing"));
|
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,8 +4,8 @@ using System.Collections.Generic;
|
||||||
using Dalamud.Game;
|
using Dalamud.Game;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
namespace Dalamud.Plugin.Internal.Types
|
namespace Dalamud.Plugin.Internal.Types;
|
||||||
{
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Information about a plugin, packaged in a json file with the DLL.
|
/// Information about a plugin, packaged in a json file with the DLL.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -21,7 +21,7 @@ namespace Dalamud.Plugin.Internal.Types
|
||||||
/// Gets or sets the public name of the plugin.
|
/// Gets or sets the public name of the plugin.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[JsonProperty]
|
[JsonProperty]
|
||||||
public string Name { get; set; }
|
public string Name { get; set; } = null!;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets a punchline of the plugins functions.
|
/// Gets a punchline of the plugins functions.
|
||||||
|
|
@ -64,13 +64,13 @@ namespace Dalamud.Plugin.Internal.Types
|
||||||
/// Gets the internal name of the plugin, which should match the assembly name of the plugin.
|
/// Gets the internal name of the plugin, which should match the assembly name of the plugin.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[JsonProperty]
|
[JsonProperty]
|
||||||
public string InternalName { get; init; }
|
public string InternalName { get; init; } = null!;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the current assembly version of the plugin.
|
/// Gets the current assembly version of the plugin.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[JsonProperty]
|
[JsonProperty]
|
||||||
public Version AssemblyVersion { get; init; }
|
public Version AssemblyVersion { get; init; } = null!;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the current testing assembly version of the plugin.
|
/// Gets the current testing assembly version of the plugin.
|
||||||
|
|
@ -78,18 +78,6 @@ namespace Dalamud.Plugin.Internal.Types
|
||||||
[JsonProperty]
|
[JsonProperty]
|
||||||
public Version? TestingAssemblyVersion { get; init; }
|
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 <see cref="TestingAssemblyVersion"/> is not null.
|
|
||||||
/// </summary>
|
|
||||||
[JsonIgnore]
|
|
||||||
public bool HasTestingAssemblyVersion => this.TestingAssemblyVersion != null;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets a value indicating whether the plugin is only available for testing.
|
/// Gets a value indicating whether the plugin is only available for testing.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -132,19 +120,19 @@ namespace Dalamud.Plugin.Internal.Types
|
||||||
/// Gets the download link used to install the plugin.
|
/// Gets the download link used to install the plugin.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[JsonProperty]
|
[JsonProperty]
|
||||||
public string DownloadLinkInstall { get; init; }
|
public string DownloadLinkInstall { get; init; } = null!;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the download link used to update the plugin.
|
/// Gets the download link used to update the plugin.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[JsonProperty]
|
[JsonProperty]
|
||||||
public string DownloadLinkUpdate { get; init; }
|
public string DownloadLinkUpdate { get; init; } = null!;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the download link used to get testing versions of the plugin.
|
/// Gets the download link used to get testing versions of the plugin.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[JsonProperty]
|
[JsonProperty]
|
||||||
public string DownloadLinkTesting { get; init; }
|
public string DownloadLinkTesting { get; init; } = null!;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the load priority for this plugin. Higher values means higher priority. 0 is default priority.
|
/// Gets the load priority for this plugin. Higher values means higher priority. 0 is default priority.
|
||||||
|
|
@ -171,10 +159,4 @@ namespace Dalamud.Plugin.Internal.Types
|
||||||
/// Gets a message that is shown to users when sending feedback.
|
/// Gets a message that is shown to users when sending feedback.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string? FeedbackMessage { get; init; }
|
public string? FeedbackMessage { get; init; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets a value indicating the webhook URL feedback is sent to.
|
|
||||||
/// </summary>
|
|
||||||
public string? FeedbackWebhook { 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,5 +1,5 @@
|
||||||
namespace Dalamud.Plugin.Internal.Types
|
namespace Dalamud.Plugin.Internal.Types;
|
||||||
{
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Values representing plugin repository state.
|
/// Values representing plugin repository state.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -25,4 +25,3 @@ namespace Dalamud.Plugin.Internal.Types
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Fail,
|
Fail,
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
namespace Dalamud.Plugin.Internal.Types
|
namespace Dalamud.Plugin.Internal.Types;
|
||||||
{
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Values representing plugin load state.
|
/// Values representing plugin load state.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -30,4 +30,3 @@ namespace Dalamud.Plugin.Internal.Types
|
||||||
/// </summary>
|
/// </summary>
|
||||||
LoadError,
|
LoadError,
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -1,30 +1,29 @@
|
||||||
using System;
|
using System;
|
||||||
|
|
||||||
namespace Dalamud.Plugin.Internal.Types
|
namespace Dalamud.Plugin.Internal.Types;
|
||||||
{
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Plugin update status.
|
/// Plugin update status.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal class PluginUpdateStatus
|
internal class PluginUpdateStatus
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the plugin internal name.
|
/// Gets the plugin internal name.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string InternalName { get; set; }
|
public string InternalName { get; init; } = null!;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the plugin name.
|
/// Gets the plugin name.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string Name { get; set; }
|
public string Name { get; init; } = null!;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the plugin version.
|
/// Gets the plugin version.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Version Version { get; set; }
|
public Version Version { get; init; } = null!;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets a value indicating whether the plugin was updated.
|
/// Gets or sets a value indicating whether the plugin was updated.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool WasUpdated { get; set; }
|
public bool WasUpdated { get; set; }
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,13 @@
|
||||||
|
using JetBrains.Annotations;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
namespace Dalamud.Plugin.Internal.Types
|
namespace Dalamud.Plugin.Internal.Types;
|
||||||
{
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Information about a plugin, packaged in a json file with the DLL. This variant includes additional information such as
|
/// 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.
|
/// if the plugin is disabled and if it was installed from a testing URL. This is designed for use with manifests on disk.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[UsedImplicitly]
|
||||||
internal record RemotePluginManifest : PluginManifest
|
internal record RemotePluginManifest : PluginManifest
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -13,6 +15,5 @@ namespace Dalamud.Plugin.Internal.Types
|
||||||
/// may have come from in the plugins available view. This functionality should not be included in the plugin master.
|
/// may have come from in the plugins available view. This functionality should not be included in the plugin master.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[JsonIgnore]
|
[JsonIgnore]
|
||||||
public PluginRepository SourceRepo { get; set; } = null;
|
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