mirror of
https://github.com/goatcorp/Dalamud.git
synced 2025-12-13 20:24:16 +01:00
feat: add support to load textures from files
This commit is contained in:
parent
8df9821f0e
commit
22a6261c98
4 changed files with 116 additions and 33 deletions
|
|
@ -1,8 +1,8 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Reflection;
|
|
||||||
|
|
||||||
using Dalamud.Data;
|
using Dalamud.Data;
|
||||||
using Dalamud.Game;
|
using Dalamud.Game;
|
||||||
|
|
@ -11,7 +11,6 @@ using Dalamud.IoC.Internal;
|
||||||
using Dalamud.Logging.Internal;
|
using Dalamud.Logging.Internal;
|
||||||
using Dalamud.Plugin.Services;
|
using Dalamud.Plugin.Services;
|
||||||
using ImGuiScene;
|
using ImGuiScene;
|
||||||
using Lumina.Data;
|
|
||||||
using Lumina.Data.Files;
|
using Lumina.Data.Files;
|
||||||
|
|
||||||
namespace Dalamud.Interface.Internal;
|
namespace Dalamud.Interface.Internal;
|
||||||
|
|
@ -36,6 +35,7 @@ internal class TextureManager : IDisposable, IServiceType, ITextureSubstitutionP
|
||||||
|
|
||||||
private readonly Framework framework;
|
private readonly Framework framework;
|
||||||
private readonly DataManager dataManager;
|
private readonly DataManager dataManager;
|
||||||
|
private readonly InterfaceManager im;
|
||||||
private readonly DalamudStartInfo startInfo;
|
private readonly DalamudStartInfo startInfo;
|
||||||
|
|
||||||
private readonly Dictionary<string, TextureInfo> activeTextures = new();
|
private readonly Dictionary<string, TextureInfo> activeTextures = new();
|
||||||
|
|
@ -45,12 +45,14 @@ internal class TextureManager : IDisposable, IServiceType, ITextureSubstitutionP
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="framework">Framework instance.</param>
|
/// <param name="framework">Framework instance.</param>
|
||||||
/// <param name="dataManager">DataManager instance.</param>
|
/// <param name="dataManager">DataManager instance.</param>
|
||||||
|
/// <param name="im">InterfaceManager instance.</param>
|
||||||
/// <param name="startInfo">DalamudStartInfo instance.</param>
|
/// <param name="startInfo">DalamudStartInfo instance.</param>
|
||||||
[ServiceManager.ServiceConstructor]
|
[ServiceManager.ServiceConstructor]
|
||||||
public TextureManager(Framework framework, DataManager dataManager, DalamudStartInfo startInfo)
|
public TextureManager(Framework framework, DataManager dataManager, InterfaceManager im, DalamudStartInfo startInfo)
|
||||||
{
|
{
|
||||||
this.framework = framework;
|
this.framework = framework;
|
||||||
this.dataManager = dataManager;
|
this.dataManager = dataManager;
|
||||||
|
this.im = im;
|
||||||
this.startInfo = startInfo;
|
this.startInfo = startInfo;
|
||||||
|
|
||||||
this.framework.Update += this.FrameworkOnUpdate;
|
this.framework.Update += this.FrameworkOnUpdate;
|
||||||
|
|
@ -145,10 +147,30 @@ internal class TextureManager : IDisposable, IServiceType, ITextureSubstitutionP
|
||||||
/// <param name="path">The path to the texture in the game's VFS.</param>
|
/// <param name="path">The path to the texture in the game's VFS.</param>
|
||||||
/// <param name="keepAlive">Prevent Dalamud from automatically unloading this texture to save memory. Usually does not need to be set.</param>
|
/// <param name="keepAlive">Prevent Dalamud from automatically unloading this texture to save memory. Usually does not need to be set.</param>
|
||||||
/// <returns>Null, if the icon does not exist, or a texture wrap that can be used to render the texture.</returns>
|
/// <returns>Null, if the icon does not exist, or a texture wrap that can be used to render the texture.</returns>
|
||||||
public TextureManagerTextureWrap? GetTextureFromGamePath(string path, bool keepAlive)
|
public TextureManagerTextureWrap? GetTextureFromGame(string path, bool keepAlive)
|
||||||
{
|
{
|
||||||
|
ArgumentException.ThrowIfNullOrEmpty(path);
|
||||||
|
|
||||||
|
if (Path.IsPathRooted(path))
|
||||||
|
throw new ArgumentException("Use GetTextureFromFile() to load textures directly from a file.", nameof(path));
|
||||||
|
|
||||||
return !this.dataManager.FileExists(path) ? null : this.CreateWrap(path, keepAlive);
|
return !this.dataManager.FileExists(path) ? null : this.CreateWrap(path, keepAlive);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get a texture handle for the image or texture, specified by the passed FileInfo.
|
||||||
|
/// You may only specify paths on the native file system.
|
||||||
|
///
|
||||||
|
/// This API can load .png and .tex files.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="file">The FileInfo describing the image or texture file.</param>
|
||||||
|
/// <param name="keepAlive">Prevent Dalamud from automatically unloading this texture to save memory. Usually does not need to be set.</param>
|
||||||
|
/// <returns>Null, if the file does not exist, or a texture wrap that can be used to render the texture.</returns>
|
||||||
|
public TextureManagerTextureWrap? GetTextureFromFile(FileInfo file, bool keepAlive)
|
||||||
|
{
|
||||||
|
ArgumentNullException.ThrowIfNull(file);
|
||||||
|
return !file.Exists ? null : this.CreateWrap(file.FullName, keepAlive);
|
||||||
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
|
|
@ -185,7 +207,7 @@ internal class TextureManager : IDisposable, IServiceType, ITextureSubstitutionP
|
||||||
lock (this.activeTextures)
|
lock (this.activeTextures)
|
||||||
{
|
{
|
||||||
if (!this.activeTextures.TryAdd(path, info))
|
if (!this.activeTextures.TryAdd(path, info))
|
||||||
Log.Warning("Texture {Path} tracked twice, this might not be an issue", path);
|
Log.Warning("Texture {Path} tracked twice", path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -197,29 +219,53 @@ internal class TextureManager : IDisposable, IServiceType, ITextureSubstitutionP
|
||||||
|
|
||||||
if (refresh)
|
if (refresh)
|
||||||
{
|
{
|
||||||
byte[]? interceptData = null;
|
if (!this.im.IsReady)
|
||||||
this.InterceptTexDataLoad?.Invoke(path, ref interceptData);
|
throw new InvalidOperationException("Cannot create textures before scene is ready");
|
||||||
|
|
||||||
// TODO: Do we also want to support loading from actual fs here? Doesn't seem to be a big deal, collect interest
|
|
||||||
|
|
||||||
TexFile? file;
|
string? interceptPath = null;
|
||||||
if (interceptData != null)
|
this.InterceptTexDataLoad?.Invoke(path, ref interceptPath);
|
||||||
|
|
||||||
|
if (interceptPath != null)
|
||||||
{
|
{
|
||||||
// TODO: upstream to lumina
|
Log.Verbose("Intercept: {OriginalPath} => {ReplacePath}", path, interceptPath);
|
||||||
file = Activator.CreateInstance<TexFile>();
|
path = interceptPath;
|
||||||
var type = typeof(TexFile);
|
}
|
||||||
type.GetProperty("Data", BindingFlags.NonPublic | BindingFlags.Instance)!.GetSetMethod()!
|
|
||||||
.Invoke(file, new object[] { interceptData });
|
TextureWrap? wrap;
|
||||||
type.GetProperty("Reader", BindingFlags.NonPublic | BindingFlags.Instance)!.GetSetMethod()!
|
|
||||||
.Invoke(file, new object[] { new LuminaBinaryReader(file.Data) });
|
// TODO: The actual loading here may fail due to circumstances outside of our control.
|
||||||
file.LoadFile();
|
// We should create a fallback texture and return it instead, so that plugins don't crash.
|
||||||
|
|
||||||
|
// We want to load this from the disk, probably, if the path has a root
|
||||||
|
// Not sure if this can cause issues with e.g. network drives, might have to rethink
|
||||||
|
// and add a flag instead if it does.
|
||||||
|
if (Path.IsPathRooted(path))
|
||||||
|
{
|
||||||
|
if (Path.GetExtension(path) == ".tex")
|
||||||
|
{
|
||||||
|
// Attempt to load via Lumina
|
||||||
|
var file = this.dataManager.GameData.GetFileFromDisk<TexFile>(path);
|
||||||
|
wrap = this.dataManager.GetImGuiTexture(file);
|
||||||
|
Log.Verbose("Texture {Path} loaded FS via Lumina", path);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Attempt to load image
|
||||||
|
wrap = this.im.LoadImage(path);
|
||||||
|
Log.Verbose("Texture {Path} loaded FS via LoadImage", path);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
file = this.dataManager.GetFile<TexFile>(path);
|
// Load regularly from dats
|
||||||
|
var file = this.dataManager.GetFile<TexFile>(path);
|
||||||
|
wrap = this.dataManager.GetImGuiTexture(file);
|
||||||
|
Log.Verbose("Texture {Path} loaded from SqPack", path);
|
||||||
}
|
}
|
||||||
|
|
||||||
var wrap = this.dataManager.GetImGuiTexture(file);
|
if (wrap == null)
|
||||||
|
throw new Exception("Could not create texture");
|
||||||
|
|
||||||
info.Wrap = wrap;
|
info.Wrap = wrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -377,9 +423,24 @@ internal class TextureManagerPluginScoped : ITextureProvider, IServiceType, IDis
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public IDalamudTextureWrap? GetTextureFromGamePath(string path, bool keepAlive = false)
|
public IDalamudTextureWrap? GetTextureFromGame(string path, bool keepAlive = false)
|
||||||
{
|
{
|
||||||
var wrap = this.textureManager.GetTextureFromGamePath(path, keepAlive);
|
ArgumentException.ThrowIfNullOrEmpty(path);
|
||||||
|
|
||||||
|
var wrap = this.textureManager.GetTextureFromGame(path, keepAlive);
|
||||||
|
if (wrap == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
this.trackedTextures.Add(wrap);
|
||||||
|
return wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public IDalamudTextureWrap? GetTextureFromFile(FileInfo file, bool keepAlive)
|
||||||
|
{
|
||||||
|
ArgumentNullException.ThrowIfNull(file);
|
||||||
|
|
||||||
|
var wrap = this.textureManager.GetTextureFromFile(file, keepAlive);
|
||||||
if (wrap == null)
|
if (wrap == null)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,9 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
|
|
||||||
using Dalamud.Data;
|
|
||||||
using Dalamud.Plugin.Services;
|
using Dalamud.Plugin.Services;
|
||||||
using Dalamud.Utility;
|
|
||||||
using ImGuiNET;
|
using ImGuiNET;
|
||||||
using ImGuiScene;
|
using ImGuiScene;
|
||||||
using Serilog;
|
using Serilog;
|
||||||
|
|
@ -74,7 +73,19 @@ internal class TexWidget : IDataWindowWidget
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
this.addedTextures.Add(texManager.GetTextureFromGamePath(this.inputTexPath, this.keepAlive));
|
this.addedTextures.Add(texManager.GetTextureFromGame(this.inputTexPath, this.keepAlive));
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Log.Error(ex, "Could not load tex");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ImGui.Button("Load File"))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
this.addedTextures.Add(texManager.GetTextureFromFile(new FileInfo(this.inputTexPath), this.keepAlive));
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
using System;
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
using Dalamud.Interface.Internal;
|
using Dalamud.Interface.Internal;
|
||||||
using Lumina.Data.Files;
|
using Lumina.Data.Files;
|
||||||
|
|
@ -58,7 +59,18 @@ public interface ITextureProvider
|
||||||
/// <param name="path">The path to the texture in the game's VFS.</param>
|
/// <param name="path">The path to the texture in the game's VFS.</param>
|
||||||
/// <param name="keepAlive">Prevent Dalamud from automatically unloading this texture to save memory. Usually does not need to be set.</param>
|
/// <param name="keepAlive">Prevent Dalamud from automatically unloading this texture to save memory. Usually does not need to be set.</param>
|
||||||
/// <returns>Null, if the icon does not exist, or a texture wrap that can be used to render the texture.</returns>
|
/// <returns>Null, if the icon does not exist, or a texture wrap that can be used to render the texture.</returns>
|
||||||
public IDalamudTextureWrap? GetTextureFromGamePath(string path, bool keepAlive = false);
|
public IDalamudTextureWrap? GetTextureFromGame(string path, bool keepAlive = false);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get a texture handle for the image or texture, specified by the passed FileInfo.
|
||||||
|
/// You may only specify paths on the native file system.
|
||||||
|
///
|
||||||
|
/// This API can load .png and .tex files.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="file">The FileInfo describing the image or texture file.</param>
|
||||||
|
/// <param name="keepAlive">Prevent Dalamud from automatically unloading this texture to save memory. Usually does not need to be set.</param>
|
||||||
|
/// <returns>Null, if the file does not exist, or a texture wrap that can be used to render the texture.</returns>
|
||||||
|
public IDalamudTextureWrap? GetTextureFromFile(FileInfo file, bool keepAlive = false);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Get a texture handle for the specified Lumina TexFile.
|
/// Get a texture handle for the specified Lumina TexFile.
|
||||||
|
|
|
||||||
|
|
@ -7,15 +7,14 @@ public interface ITextureSubstitutionProvider
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Delegate describing a function that may be used to intercept and replace texture data.
|
/// Delegate describing a function that may be used to intercept and replace texture data.
|
||||||
|
/// The path assigned may point to another texture inside the game's dats, or a .tex file or image on the disk.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="path">The path to the texture that is to be loaded.</param>
|
/// <param name="path">The path to the texture that is to be loaded.</param>
|
||||||
/// <param name="data">The texture data. Null by default, assign something if you wish to replace the data from the game dats.</param>
|
/// <param name="replacementPath">The path that should be loaded instead.</param>
|
||||||
public delegate void TextureDataInterceptorDelegate(string path, ref byte[]? data);
|
public delegate void TextureDataInterceptorDelegate(string path, ref string? replacementPath);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Event that will be called once Dalamud wants to load texture data.
|
/// Event that will be called once Dalamud wants to load texture data.
|
||||||
/// If you have data that should replace the data from the game dats, assign it to the
|
|
||||||
/// data argument.
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public event TextureDataInterceptorDelegate? InterceptTexDataLoad;
|
public event TextureDataInterceptorDelegate? InterceptTexDataLoad;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue