Improve Path preprocessing.

This commit is contained in:
Ottermandias 2024-06-18 18:33:37 +02:00
parent f9c45a2f3f
commit cf1dcfcb7c
14 changed files with 209 additions and 246 deletions

View file

@ -2,6 +2,7 @@ using FFXIVClientStructs.FFXIV.Client.System.Resource;
using Penumbra.Api.Enums;
using Penumbra.Collections;
using Penumbra.Collections.Manager;
using Penumbra.Interop.Processing;
using Penumbra.Interop.ResourceLoading;
using Penumbra.String.Classes;
using Penumbra.Util;
@ -15,14 +16,16 @@ public class PathResolver : IDisposable
private readonly CollectionManager _collectionManager;
private readonly ResourceLoader _loader;
private readonly SubfileHelper _subfileHelper;
private readonly PathState _pathState;
private readonly MetaState _metaState;
private readonly GameState _gameState;
private readonly CollectionResolver _collectionResolver;
private readonly SubfileHelper _subfileHelper;
private readonly PathState _pathState;
private readonly MetaState _metaState;
private readonly GameState _gameState;
private readonly CollectionResolver _collectionResolver;
private readonly GamePathPreProcessService _preprocessor;
public unsafe PathResolver(PerformanceTracker performance, Configuration config, CollectionManager collectionManager, ResourceLoader loader,
SubfileHelper subfileHelper, PathState pathState, MetaState metaState, CollectionResolver collectionResolver, GameState gameState)
public PathResolver(PerformanceTracker performance, Configuration config, CollectionManager collectionManager, ResourceLoader loader,
SubfileHelper subfileHelper, PathState pathState, MetaState metaState, CollectionResolver collectionResolver, GameState gameState,
GamePathPreProcessService preprocessor)
{
_performance = performance;
_config = config;
@ -31,6 +34,7 @@ public class PathResolver : IDisposable
_pathState = pathState;
_metaState = metaState;
_gameState = gameState;
_preprocessor = preprocessor;
_collectionResolver = collectionResolver;
_loader = loader;
_loader.ResolvePath = ResolvePath;
@ -102,11 +106,10 @@ public class PathResolver : IDisposable
// so that the functions loading tex and shpk can find that path and use its collection.
// We also need to handle defaulted materials against a non-default collection.
var path = resolved == null ? gamePath.Path : resolved.Value.InternalName;
SubfileHelper.HandleCollection(resolveData, path, nonDefault, type, resolved, gamePath, out var pair);
return pair;
return _preprocessor.PreProcess(resolveData, path, nonDefault, type, resolved, gamePath);
}
public unsafe void Dispose()
public void Dispose()
{
_loader.ResetResolvePath();
}

View file

@ -66,22 +66,6 @@ public sealed unsafe class SubfileHelper : IDisposable, IReadOnlyCollection<KeyV
return false;
}
/// <summary> Materials, TMB, and AVFX need to be set per collection, so they can load their sub files independently of each other. </summary>
public static void HandleCollection(ResolveData resolveData, ByteString path, bool nonDefault, ResourceType type, FullPath? resolved,
Utf8GamePath originalPath, out (FullPath?, ResolveData) data)
{
resolved = type switch
{
ResourceType.Mtrl when nonDefault => PathDataHandler.CreateMtrl(path, resolveData.ModCollection, originalPath),
ResourceType.Avfx when nonDefault => PathDataHandler.CreateAvfx(path, resolveData.ModCollection),
ResourceType.Tmb when nonDefault => PathDataHandler.CreateTmb(path, resolveData.ModCollection),
ResourceType.Imc when resolveData.ModCollection.MetaCache?.Imc.HasFile(path) ?? false => PathDataHandler.CreateImc(path,
resolveData.ModCollection),
_ => resolved,
};
data = (resolved, resolveData);
}
public void Dispose()
{
_loader.ResourceLoaded -= SubfileContainerRequested;

View file

@ -0,0 +1,16 @@
using Penumbra.Api.Enums;
using Penumbra.Collections;
using Penumbra.Interop.PathResolving;
using Penumbra.String;
using Penumbra.String.Classes;
namespace Penumbra.Interop.Processing;
public sealed class AvfxPathPreProcessor : IPathPreProcessor
{
public ResourceType Type
=> ResourceType.Avfx;
public FullPath? PreProcess(ResolveData resolveData, ByteString path, Utf8GamePath _, bool nonDefault, FullPath? resolved)
=> nonDefault ? PathDataHandler.CreateAvfx(path, resolveData.ModCollection) : resolved;
}

View file

@ -0,0 +1,39 @@
using System.Collections.Frozen;
using OtterGui.Services;
using Penumbra.Api.Enums;
using Penumbra.Interop.ResourceLoading;
using Penumbra.Interop.Structs;
using Penumbra.String;
namespace Penumbra.Interop.Processing;
public interface IFilePostProcessor : IService
{
public ResourceType Type { get; }
public unsafe void PostProcess(ResourceHandle* resource, ByteString originalGamePath, ReadOnlySpan<byte> additionalData);
}
public unsafe class FilePostProcessService : IRequiredService, IDisposable
{
private readonly ResourceLoader _resourceLoader;
private readonly FrozenDictionary<ResourceType, IFilePostProcessor> _processors;
public FilePostProcessService(ResourceLoader resourceLoader, ServiceManager services)
{
_resourceLoader = resourceLoader;
_processors = services.GetServicesImplementing<IFilePostProcessor>().ToFrozenDictionary(s => s.Type, s => s);
_resourceLoader.FileLoaded += OnFileLoaded;
}
public void Dispose()
{
_resourceLoader.FileLoaded -= OnFileLoaded;
}
private void OnFileLoaded(ResourceHandle* resource, ByteString path, bool returnValue, bool custom,
ReadOnlySpan<byte> additionalData)
{
if (_processors.TryGetValue(resource->FileType, out var processor))
processor.PostProcess(resource, path, additionalData);
}
}

View file

@ -0,0 +1,37 @@
using System.Collections.Frozen;
using OtterGui.Services;
using Penumbra.Api.Enums;
using Penumbra.Collections;
using Penumbra.String;
using Penumbra.String.Classes;
namespace Penumbra.Interop.Processing;
public interface IPathPreProcessor : IService
{
public ResourceType Type { get; }
public FullPath? PreProcess(ResolveData resolveData, ByteString path, Utf8GamePath originalGamePath, bool nonDefault, FullPath? resolved);
}
public class GamePathPreProcessService : IService
{
private readonly FrozenDictionary<ResourceType, IPathPreProcessor> _processors;
public GamePathPreProcessService(ServiceManager services)
{
_processors = services.GetServicesImplementing<IPathPreProcessor>().ToFrozenDictionary(s => s.Type, s => s);
}
public (FullPath? Path, ResolveData Data) PreProcess(ResolveData resolveData, ByteString path, bool nonDefault, ResourceType type,
FullPath? resolved,
Utf8GamePath originalPath)
{
if (!_processors.TryGetValue(type, out var processor))
return (resolved, resolveData);
resolved = processor.PreProcess(resolveData, path, originalPath, nonDefault, resolved);
return (resolved, resolveData);
}
}

View file

@ -0,0 +1,30 @@
using Penumbra.Api.Enums;
using Penumbra.Collections.Manager;
using Penumbra.Interop.PathResolving;
using Penumbra.Interop.Structs;
using Penumbra.String;
namespace Penumbra.Interop.Processing;
public sealed class ImcFilePostProcessor(CollectionStorage collections) : IFilePostProcessor
{
public ResourceType Type
=> ResourceType.Imc;
public unsafe void PostProcess(ResourceHandle* resource, ByteString originalGamePath, ReadOnlySpan<byte> additionalData)
{
if (!PathDataHandler.Read(additionalData, out var data) || data.Discriminator != PathDataHandler.Discriminator)
return;
var collection = collections.ByLocalId(data.Collection);
if (collection.MetaCache is not { } cache)
return;
if (!cache.Imc.GetFile(originalGamePath, out var file))
return;
file.Replace(resource);
Penumbra.Log.Information(
$"[ResourceLoader] Loaded {originalGamePath} from file and replaced with IMC from collection {collection.AnonymizedName}.");
}
}

View file

@ -0,0 +1,18 @@
using Penumbra.Api.Enums;
using Penumbra.Collections;
using Penumbra.Interop.PathResolving;
using Penumbra.String;
using Penumbra.String.Classes;
namespace Penumbra.Interop.Processing;
public sealed class ImcPathPreProcessor : IPathPreProcessor
{
public ResourceType Type
=> ResourceType.Imc;
public FullPath? PreProcess(ResolveData resolveData, ByteString path, Utf8GamePath originalGamePath, bool _, FullPath? resolved)
=> resolveData.ModCollection.MetaCache?.Imc.HasFile(originalGamePath.Path) ?? false
? PathDataHandler.CreateImc(path, resolveData.ModCollection)
: resolved;
}

View file

@ -0,0 +1,18 @@
using Penumbra.Api.Enums;
using Penumbra.Interop.PathResolving;
using Penumbra.Interop.Structs;
using Penumbra.String;
namespace Penumbra.Interop.Processing;
public sealed class MaterialFilePostProcessor //: IFilePostProcessor
{
public ResourceType Type
=> ResourceType.Mtrl;
public unsafe void PostProcess(ResourceHandle* resource, ByteString originalGamePath, ReadOnlySpan<byte> additionalData)
{
if (!PathDataHandler.ReadMtrl(additionalData, out var data))
return;
}
}

View file

@ -0,0 +1,16 @@
using Penumbra.Api.Enums;
using Penumbra.Collections;
using Penumbra.Interop.PathResolving;
using Penumbra.String;
using Penumbra.String.Classes;
namespace Penumbra.Interop.Processing;
public sealed class MtrlPathPreProcessor : IPathPreProcessor
{
public ResourceType Type
=> ResourceType.Mtrl;
public FullPath? PreProcess(ResolveData resolveData, ByteString path, Utf8GamePath originalGamePath, bool nonDefault, FullPath? resolved)
=> nonDefault ? PathDataHandler.CreateMtrl(path, resolveData.ModCollection, originalGamePath) : resolved;
}

View file

@ -0,0 +1,16 @@
using Penumbra.Api.Enums;
using Penumbra.Collections;
using Penumbra.Interop.PathResolving;
using Penumbra.String;
using Penumbra.String.Classes;
namespace Penumbra.Interop.Processing;
public sealed class TmbPathPreProcessor : IPathPreProcessor
{
public ResourceType Type
=> ResourceType.Tmb;
public FullPath? PreProcess(ResolveData resolveData, ByteString path, Utf8GamePath _, bool nonDefault, FullPath? resolved)
=> nonDefault ? PathDataHandler.CreateTmb(path, resolveData.ModCollection) : resolved;
}

View file

@ -1,9 +1,6 @@
using System.Collections.Frozen;
using FFXIVClientStructs.FFXIV.Client.System.Resource;
using OtterGui.Services;
using Penumbra.Api.Enums;
using Penumbra.Collections;
using Penumbra.Collections.Manager;
using Penumbra.Interop.PathResolving;
using Penumbra.Interop.SafeHandles;
using Penumbra.Interop.Structs;
@ -13,72 +10,6 @@ using FileMode = Penumbra.Interop.Structs.FileMode;
namespace Penumbra.Interop.ResourceLoading;
public interface IFilePostProcessor : IService
{
public ResourceType Type { get; }
public unsafe void PostProcess(ResourceHandle* resource, ByteString originalGamePath, ReadOnlySpan<byte> additionalData);
}
public sealed class MaterialFilePostProcessor : IFilePostProcessor
{
public ResourceType Type
=> ResourceType.Mtrl;
public unsafe void PostProcess(ResourceHandle* resource, ByteString originalGamePath, ReadOnlySpan<byte> additionalData)
{
if (!PathDataHandler.ReadMtrl(additionalData, out var data))
return;
}
}
public sealed class ImcFilePostProcessor(CollectionStorage collections) : IFilePostProcessor
{
public ResourceType Type
=> ResourceType.Imc;
public unsafe void PostProcess(ResourceHandle* resource, ByteString originalGamePath, ReadOnlySpan<byte> additionalData)
{
if (!PathDataHandler.Read(additionalData, out var data) || data.Discriminator != PathDataHandler.Discriminator)
return;
var collection = collections.ByLocalId(data.Collection);
if (collection.MetaCache is not { } cache)
return;
if (!cache.Imc.GetFile(originalGamePath, out var file))
return;
file.Replace(resource);
Penumbra.Log.Information(
$"[ResourceLoader] Loaded {originalGamePath} from file and replaced with IMC from collection {collection.AnonymizedName}.");
}
}
public unsafe class FilePostProcessService : IRequiredService, IDisposable
{
private readonly ResourceLoader _resourceLoader;
private readonly FrozenDictionary<ResourceType, IFilePostProcessor> _processors;
public FilePostProcessService(ResourceLoader resourceLoader, ServiceManager services)
{
_resourceLoader = resourceLoader;
_processors = services.GetServicesImplementing<IFilePostProcessor>().ToFrozenDictionary(s => s.Type, s => s);
_resourceLoader.FileLoaded += OnFileLoaded;
}
public void Dispose()
{
_resourceLoader.FileLoaded -= OnFileLoaded;
}
private void OnFileLoaded(ResourceHandle* resource, ByteString path, bool returnValue, bool custom,
ReadOnlySpan<byte> additionalData)
{
if (_processors.TryGetValue(resource->FileType, out var processor))
processor.PostProcess(resource, path, additionalData);
}
}
public unsafe class ResourceLoader : IDisposable
{
private readonly ResourceService _resources;

View file

@ -46,9 +46,6 @@ public unsafe class CharacterUtility : IDisposable
private readonly MetaList[] _lists;
public IReadOnlyList<MetaList> Lists
=> _lists;
public (nint Address, int Size) DefaultResource(InternalIndex idx)
=> _lists[idx.Value].DefaultResource;
@ -58,7 +55,7 @@ public unsafe class CharacterUtility : IDisposable
{
interop.InitializeFromAttributes(this);
_lists = Enumerable.Range(0, RelevantIndices.Length)
.Select(idx => new MetaList(this, new InternalIndex(idx)))
.Select(idx => new MetaList(new InternalIndex(idx)))
.ToArray();
_framework = framework;
LoadingFinished += () => Penumbra.Log.Debug("Loading of CharacterUtility finished.");
@ -124,9 +121,6 @@ public unsafe class CharacterUtility : IDisposable
if (!Ready)
return;
foreach (var list in _lists)
list.Dispose();
Address->HumanPbdResource = (ResourceHandle*)DefaultHumanPbdResource;
Address->TransparentTexResource = (TextureResourceHandle*)DefaultTransparentResource;
Address->DecalTexResource = (TextureResourceHandle*)DefaultDecalResource;

View file

@ -2,26 +2,14 @@ using Penumbra.Interop.Structs;
namespace Penumbra.Interop.Services;
public unsafe class MetaList : IDisposable
public class MetaList(CharacterUtility.InternalIndex index)
{
private readonly CharacterUtility _utility;
private readonly LinkedList<MetaReverter> _entries = new();
public readonly CharacterUtility.InternalIndex Index;
public readonly MetaIndex GlobalMetaIndex;
public IReadOnlyCollection<MetaReverter> Entries
=> _entries;
public readonly CharacterUtility.InternalIndex Index = index;
public readonly MetaIndex GlobalMetaIndex = CharacterUtility.RelevantIndices[index.Value];
private nint _defaultResourceData = nint.Zero;
private int _defaultResourceSize = 0;
public bool Ready { get; private set; } = false;
public MetaList(CharacterUtility utility, CharacterUtility.InternalIndex index)
{
_utility = utility;
Index = index;
GlobalMetaIndex = CharacterUtility.RelevantIndices[index.Value];
}
private int _defaultResourceSize;
public bool Ready { get; private set; }
public void SetDefaultResource(nint data, int size)
{
@ -31,116 +19,8 @@ public unsafe class MetaList : IDisposable
_defaultResourceData = data;
_defaultResourceSize = size;
Ready = _defaultResourceData != nint.Zero && size != 0;
if (_entries.Count <= 0)
return;
var first = _entries.First!.Value;
SetResource(first.Data, first.Length);
}
public (nint Address, int Size) DefaultResource
=> (_defaultResourceData, _defaultResourceSize);
public MetaReverter TemporarilySetResource(nint data, int length)
{
Penumbra.Log.Excessive($"Temporarily set resource {GlobalMetaIndex} to 0x{(ulong)data:X} ({length} bytes).");
var reverter = new MetaReverter(this, data, length);
_entries.AddFirst(reverter);
SetResourceInternal(data, length);
return reverter;
}
public MetaReverter TemporarilyResetResource()
{
Penumbra.Log.Excessive(
$"Temporarily reset resource {GlobalMetaIndex} to default at 0x{_defaultResourceData:X} ({_defaultResourceSize} bytes).");
var reverter = new MetaReverter(this);
_entries.AddFirst(reverter);
ResetResourceInternal();
return reverter;
}
public void SetResource(nint data, int length)
{
Penumbra.Log.Excessive($"Set resource {GlobalMetaIndex} to 0x{(ulong)data:X} ({length} bytes).");
SetResourceInternal(data, length);
}
public void ResetResource()
{
Penumbra.Log.Excessive($"Reset resource {GlobalMetaIndex} to default at 0x{_defaultResourceData:X} ({_defaultResourceSize} bytes).");
ResetResourceInternal();
}
/// <summary> Set the currently stored data of this resource to new values. </summary>
private void SetResourceInternal(nint data, int length)
{
if (!Ready)
return;
var resource = _utility.Address->Resource(GlobalMetaIndex);
resource->SetData(data, length);
}
/// <summary> Reset the currently stored data of this resource to its default values. </summary>
private void ResetResourceInternal()
=> SetResourceInternal(_defaultResourceData, _defaultResourceSize);
private void SetResourceToDefaultCollection()
{}
public void Dispose()
{
if (_entries.Count > 0)
{
foreach (var entry in _entries)
entry.Disposed = true;
_entries.Clear();
}
ResetResourceInternal();
}
public sealed class MetaReverter(MetaList metaList, nint data, int length) : IDisposable
{
public static readonly MetaReverter Disabled = new(null!) { Disposed = true };
public readonly MetaList MetaList = metaList;
public readonly nint Data = data;
public readonly int Length = length;
public readonly bool Resetter;
public bool Disposed;
public MetaReverter(MetaList metaList)
: this(metaList, nint.Zero, 0)
=> Resetter = true;
public void Dispose()
{
if (Disposed)
return;
var list = MetaList._entries;
var wasCurrent = ReferenceEquals(this, list.First?.Value);
list.Remove(this);
if (!wasCurrent)
return;
if (list.Count == 0)
{
MetaList.SetResourceToDefaultCollection();
}
else
{
var next = list.First!.Value;
if (next.Resetter)
MetaList.ResetResourceInternal();
else
MetaList.SetResourceInternal(next.Data, next.Length);
}
Disposed = true;
}
}
}

View file

@ -179,8 +179,6 @@ public class DebugTab : Window, ITab
ImGui.NewLine();
DrawData();
ImGui.NewLine();
DrawDebugTabMetaLists();
ImGui.NewLine();
DrawResourceProblems();
ImGui.NewLine();
DrawPlayerModelInfo();
@ -788,23 +786,6 @@ public class DebugTab : Window, ITab
}
}
private void DrawDebugTabMetaLists()
{
if (!ImGui.CollapsingHeader("Metadata Changes"))
return;
using var table = Table("##DebugMetaTable", 3, ImGuiTableFlags.SizingFixedFit);
if (!table)
return;
foreach (var list in _characterUtility.Lists)
{
ImGuiUtil.DrawTableColumn(list.GlobalMetaIndex.ToString());
ImGuiUtil.DrawTableColumn(list.Entries.Count.ToString());
ImGuiUtil.DrawTableColumn(string.Join(", ", list.Entries.Select(e => $"0x{e.Data:X}")));
}
}
/// <summary> Draw information about the resident resource files. </summary>
private unsafe void DrawDebugResidentResources()
{