From 50a6fde2632f732b2cf5356ab391c9841bda2c28 Mon Sep 17 00:00:00 2001 From: Kaz Wolfe Date: Sat, 23 Apr 2022 15:24:42 -0700 Subject: [PATCH] Add *experimental* support for Penumbra IPC - Allows loading in icons from Penumbra (for Material UI users) - Add experimental (default-off) checkbox for above feature. - Partial rewrite of IconManager to handle this new case, as well as clarity --- FFXIVPlugin/Base/PluginConfig.cs | 2 + FFXIVPlugin/Game/IconManager.cs | 60 +++++++++++++++++------- FFXIVPlugin/IPC/PenumbraIPC.cs | 27 +++++++++++ FFXIVPlugin/UI/Windows/SettingsWindow.cs | 11 ++++- FFXIVPlugin/XIVDeckPlugin.cs | 4 ++ XIVDeck.sln.DotSettings | 1 + 6 files changed, 87 insertions(+), 18 deletions(-) create mode 100644 FFXIVPlugin/IPC/PenumbraIPC.cs diff --git a/FFXIVPlugin/Base/PluginConfig.cs b/FFXIVPlugin/Base/PluginConfig.cs index 494176d..2a7bd42 100644 --- a/FFXIVPlugin/Base/PluginConfig.cs +++ b/FFXIVPlugin/Base/PluginConfig.cs @@ -19,6 +19,8 @@ public class PluginConfig : IPluginConfiguration { * If not, the plugin will display a nag message on startup to install and set up the associated plugin. */ public bool HasLinkedStreamDeckPlugin { get; set; } + + public bool UsePenumbraIPC { get; set; } = false; public int WebSocketPort { get; set; } = 37984; diff --git a/FFXIVPlugin/Game/IconManager.cs b/FFXIVPlugin/Game/IconManager.cs index 4de7f37..3f4973b 100644 --- a/FFXIVPlugin/Game/IconManager.cs +++ b/FFXIVPlugin/Game/IconManager.cs @@ -12,11 +12,14 @@ using SixLabors.ImageSharp; using SixLabors.ImageSharp.PixelFormats; using XIVDeck.FFXIVPlugin.Base; +using XIVDeck.FFXIVPlugin.IPC; namespace XIVDeck.FFXIVPlugin.Game; // borrowed from https://github.com/Caraxi/RemindMe/blob/master/IconManager.cs public class IconManager : IDisposable { + private const string IconFileFormat = "ui/icon/{0:D3}000/{1}{2:D6}{3}.tex"; + private readonly DalamudPluginInterface _pluginInterface; private bool _disposed; private readonly Dictionary<(int, bool), TextureWrap?> _iconTextures = new(); @@ -64,7 +67,7 @@ private void LoadIconTexture(int iconId, bool hq = false) { this.GetIcon(Injections.DataManager.Language, iconId, hq, highres); private TexFile? GetIcon(ClientLanguage iconLanguage, int iconId, bool hq = false, bool highres = false) { - string type = iconLanguage switch { + string language = iconLanguage switch { ClientLanguage.Japanese => "ja/", ClientLanguage.English => "en/", ClientLanguage.German => "de/", @@ -73,28 +76,51 @@ private void LoadIconTexture(int iconId, bool hq = false) { "Unknown Language: " + Injections.DataManager.Language) }; - return this.GetIcon(type, iconId, hq, highres); + return this.GetIcon(language, iconId, hq, highres); } - public TexFile? GetIcon(string type, int iconId, bool hq = false, bool highres = false) { - if (type.Length > 0 && !type.EndsWith("/")) - type += "/"; + public static string GetIconPath(string lang, int iconId, bool hq = false, bool highres = false, bool forceOriginal = false) { + var path = string.Format(IconFileFormat, + iconId / 1000, (hq ? "hq/" : "") + lang, iconId, highres ? "_hr1" : ""); - var formatStr = $"ui/icon/{{0:D3}}000/{(hq ? "hq/" : "")}{{1}}{{2:D6}}{(highres ? "_hr1" : "")}.tex"; - TexFile? file = - Injections.DataManager.GetFile(string.Format(formatStr, (iconId / 1000), type, iconId)); + if (PenumbraIPC.PenumbraEnabled && !forceOriginal && XIVDeckPlugin.Instance.Configuration.UsePenumbraIPC) + path = PenumbraIPC.ResolvePenumbraPath(path); - if (file == null && highres) { - // high-res probably doesn't exist, fallback and try low-res instead - PluginLog.Debug( - $"Couldn't get high res icon for {string.Format(formatStr, (iconId / 1000), type, iconId)}"); - return this.GetIcon(type, iconId, hq); + return path; + } + + public TexFile? GetIcon(string lang, int iconId, bool hq = false, bool highres = false) { + TexFile? texFile; + + if (lang.Length > 0 && !lang.EndsWith("/")) + lang += "/"; + + var texPath = GetIconPath(lang, iconId, hq, true); + + if (texPath.Substring(1, 2) == ":/") { + PluginLog.Debug("LOADING ASSET FROM DISK!!!"); + texFile = Injections.DataManager.GameData.GetFileFromDisk(texPath); + } else { + texFile = Injections.DataManager.GetFile(texPath); } - return file != null || type.Length <= 0 - ? file - : Injections.DataManager.GetFile(string.Format(formatStr, (iconId / 1000), string.Empty, - iconId)); + // recursion steps: + // - attempt to get the high-res file exactly as described. + // - attempt to get the high-res file without language + // - attempt to get the low-res file without language + // - attempt to get the low-res file exactly as described + // - attempt to get the low-res file without language + // - give up and return null + switch (texFile) { + case null when lang.Length > 0: + PluginLog.Debug($"Couldn't get lang-specific icon for {texPath}, falling back to no-lang"); + return this.GetIcon(string.Empty, iconId, hq, true); + case null when highres: + PluginLog.Debug($"Couldn't get highres icon for {texPath}, falling back to lowres"); + return this.GetIcon(lang, iconId, hq); + default: + return texFile; + } } public TextureWrap? GetIconTexture(int iconId, bool hq = false) { diff --git a/FFXIVPlugin/IPC/PenumbraIPC.cs b/FFXIVPlugin/IPC/PenumbraIPC.cs new file mode 100644 index 0000000..b3ee6e1 --- /dev/null +++ b/FFXIVPlugin/IPC/PenumbraIPC.cs @@ -0,0 +1,27 @@ +using Dalamud.Logging; +using Dalamud.Plugin.Ipc; +using XIVDeck.FFXIVPlugin.Base; + +namespace XIVDeck.FFXIVPlugin.IPC; + +public class PenumbraIPC { + public static bool PenumbraEnabled { get; private set; } = false; + private static ICallGateSubscriber? _penumbraApiVersionSubscriber; + private static ICallGateSubscriber? _penumbraResolveDefaultSubscriber; + + public static int PenumbraApiVersion => _penumbraApiVersionSubscriber?.InvokeFunc() ?? 0; + + public static void Initialize() { + _penumbraApiVersionSubscriber = Injections.PluginInterface.GetIpcSubscriber("Penumbra.ApiVersion"); + + if (PenumbraApiVersion == 3) { + _penumbraResolveDefaultSubscriber = Injections.PluginInterface.GetIpcSubscriber("Penumbra.ResolveDefaultPath"); + PenumbraEnabled = true; + PluginLog.Information("Enabled Penumbra IPC connection!"); + } + } + + public static string ResolvePenumbraPath(string path) { + return _penumbraResolveDefaultSubscriber?.InvokeFunc(path) ?? path; + } +} \ No newline at end of file diff --git a/FFXIVPlugin/UI/Windows/SettingsWindow.cs b/FFXIVPlugin/UI/Windows/SettingsWindow.cs index 4806b47..e8e786a 100644 --- a/FFXIVPlugin/UI/Windows/SettingsWindow.cs +++ b/FFXIVPlugin/UI/Windows/SettingsWindow.cs @@ -1,4 +1,5 @@ using System.Numerics; +using System.Threading; using Dalamud.Interface; using Dalamud.Interface.Colors; using Dalamud.Interface.Components; @@ -16,11 +17,12 @@ public class SettingsWindow : Window { // settings private int _websocketPort; private bool _safeMode = true; + private bool _usePenumbraIPC = false; public SettingsWindow(bool forceMainWindow = true) : base(WindowKey, ImGuiWindowFlags.NoScrollbar | ImGuiWindowFlags.NoScrollWithMouse, forceMainWindow) { - this.Size = new Vector2(300, 150); + this.Size = new Vector2(300, 250); this.SizeCondition = ImGuiCond.FirstUseEver; this.IsOpen = true; @@ -29,6 +31,7 @@ public SettingsWindow(bool forceMainWindow = true) : public override void OnOpen() { this._websocketPort = this._plugin.Configuration.WebSocketPort; this._safeMode = this._plugin.Configuration.SafeMode; + this._usePenumbraIPC = this._plugin.Configuration.UsePenumbraIPC; } public override void OnClose() { @@ -61,6 +64,10 @@ public override void Draw() { ImGuiComponents.HelpMarker("Default port: 37984\n\nRange: 1024-59999"); ImGui.TextWrapped($"Listen IP: 127.0.0.1"); + + ImGui.Dummy(new Vector2(0, 20)); + + ImGui.Checkbox("[EXPERIMENTAL] Use Penumbra Icons", ref this._usePenumbraIPC); /* FOOTER */ var placeholderButtonSize = ImGuiHelpers.GetButtonSize("placeholder"); @@ -89,6 +96,8 @@ private void SaveSettings() { SetupNag.Show(); } + this._plugin.Configuration.UsePenumbraIPC = this._usePenumbraIPC; + // initialize regardless of change(s) so that we can easily restart the server when necessary this._plugin.InitializeWebServer(); } diff --git a/FFXIVPlugin/XIVDeckPlugin.cs b/FFXIVPlugin/XIVDeckPlugin.cs index 82e437d..0ef9e38 100644 --- a/FFXIVPlugin/XIVDeckPlugin.cs +++ b/FFXIVPlugin/XIVDeckPlugin.cs @@ -6,6 +6,7 @@ using XIVDeck.FFXIVPlugin.ActionExecutor; using XIVDeck.FFXIVPlugin.Base; using XIVDeck.FFXIVPlugin.Game; +using XIVDeck.FFXIVPlugin.IPC; using XIVDeck.FFXIVPlugin.Server; using XIVDeck.FFXIVPlugin.Server.Types; using XIVDeck.FFXIVPlugin.UI; @@ -57,6 +58,9 @@ public XIVDeckPlugin(DalamudPluginInterface pluginInterface) { this._chatLinkWiring = new ChatLinkWiring(this.PluginInterface); this._hotbarWatcher = new HotbarWatcher(); this.WindowSystem = new WindowSystem(this.Name); + + // IPC registration + PenumbraIPC.Initialize(); // Start the websocket server itself. this.InitializeWebServer(); diff --git a/XIVDeck.sln.DotSettings b/XIVDeck.sln.DotSettings index dc9c4db..ca6d81c 100644 --- a/XIVDeck.sln.DotSettings +++ b/XIVDeck.sln.DotSettings @@ -1,5 +1,6 @@  HD + IPC JWT SD UI