Skip to content

Commit

Permalink
Add *experimental* support for Penumbra IPC
Browse files Browse the repository at this point in the history
- 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
  • Loading branch information
KazWolfe committed Apr 23, 2022
1 parent b829170 commit 50a6fde
Show file tree
Hide file tree
Showing 6 changed files with 87 additions and 18 deletions.
2 changes: 2 additions & 0 deletions FFXIVPlugin/Base/PluginConfig.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
60 changes: 43 additions & 17 deletions FFXIVPlugin/Game/IconManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -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/",
Expand All @@ -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<TexFile>(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<TexFile>(texPath);
} else {
texFile = Injections.DataManager.GetFile<TexFile>(texPath);
}

return file != null || type.Length <= 0
? file
: Injections.DataManager.GetFile<TexFile>(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) {
Expand Down
27 changes: 27 additions & 0 deletions FFXIVPlugin/IPC/PenumbraIPC.cs
Original file line number Diff line number Diff line change
@@ -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<int>? _penumbraApiVersionSubscriber;
private static ICallGateSubscriber<string, string>? _penumbraResolveDefaultSubscriber;

public static int PenumbraApiVersion => _penumbraApiVersionSubscriber?.InvokeFunc() ?? 0;

public static void Initialize() {
_penumbraApiVersionSubscriber = Injections.PluginInterface.GetIpcSubscriber<int>("Penumbra.ApiVersion");

if (PenumbraApiVersion == 3) {
_penumbraResolveDefaultSubscriber = Injections.PluginInterface.GetIpcSubscriber<string, string>("Penumbra.ResolveDefaultPath");
PenumbraEnabled = true;
PluginLog.Information("Enabled Penumbra IPC connection!");
}
}

public static string ResolvePenumbraPath(string path) {
return _penumbraResolveDefaultSubscriber?.InvokeFunc(path) ?? path;
}
}
11 changes: 10 additions & 1 deletion FFXIVPlugin/UI/Windows/SettingsWindow.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Numerics;
using System.Threading;
using Dalamud.Interface;
using Dalamud.Interface.Colors;
using Dalamud.Interface.Components;
Expand All @@ -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;
Expand All @@ -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() {
Expand Down Expand Up @@ -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");
Expand Down Expand Up @@ -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();
}
Expand Down
4 changes: 4 additions & 0 deletions FFXIVPlugin/XIVDeckPlugin.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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();
Expand Down
1 change: 1 addition & 0 deletions XIVDeck.sln.DotSettings
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=HD/@EntryIndexedValue">HD</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=IPC/@EntryIndexedValue">IPC</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=JWT/@EntryIndexedValue">JWT</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=SD/@EntryIndexedValue">SD</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=UI/@EntryIndexedValue">UI</s:String>
Expand Down

0 comments on commit 50a6fde

Please sign in to comment.