Improve Imc Handling.

This commit is contained in:
Ottermandias 2024-06-18 17:52:34 +02:00
parent d7a8c9415b
commit 03d3c38ad5
16 changed files with 197 additions and 233 deletions

View file

@ -36,7 +36,7 @@ public sealed class CollectionCache : IDisposable
=> ConflictDict.Values; => ConflictDict.Values;
public SingleArray<ModConflicts> Conflicts(IMod mod) public SingleArray<ModConflicts> Conflicts(IMod mod)
=> ConflictDict.TryGetValue(mod, out SingleArray<ModConflicts> c) ? c : new SingleArray<ModConflicts>(); => ConflictDict.TryGetValue(mod, out var c) ? c : new SingleArray<ModConflicts>();
private int _changedItemsSaveCounter = -1; private int _changedItemsSaveCounter = -1;
@ -125,12 +125,6 @@ public sealed class CollectionCache : IDisposable
return ret; return ret;
} }
public void ForceFile(Utf8GamePath path, FullPath fullPath)
=> _manager.AddChange(ChangeData.ForcedFile(this, path, fullPath));
public void RemovePath(Utf8GamePath path)
=> _manager.AddChange(ChangeData.ForcedFile(this, path, FullPath.Empty));
public void ReloadMod(IMod mod, bool addMetaChanges) public void ReloadMod(IMod mod, bool addMetaChanges)
=> _manager.AddChange(ChangeData.ModReload(this, mod, addMetaChanges)); => _manager.AddChange(ChangeData.ModReload(this, mod, addMetaChanges));
@ -251,9 +245,6 @@ public sealed class CollectionCache : IDisposable
if (addMetaChanges) if (addMetaChanges)
{ {
_collection.IncrementCounter(); _collection.IncrementCounter();
if (mod.TotalManipulations > 0)
AddMetaFiles(false);
_manager.MetaFileManager.ApplyDefaultFiles(_collection); _manager.MetaFileManager.ApplyDefaultFiles(_collection);
} }
} }
@ -408,11 +399,6 @@ public sealed class CollectionCache : IDisposable
} }
// Add all necessary meta file redirects.
public void AddMetaFiles(bool fromFullCompute)
=> Meta.SetImcFiles(fromFullCompute);
// Identify and record all manipulated objects for this entire collection. // Identify and record all manipulated objects for this entire collection.
private void SetChangedItems() private void SetChangedItems()
{ {

View file

@ -180,8 +180,6 @@ public class CollectionCacheManager : IDisposable
foreach (var mod in _modStorage) foreach (var mod in _modStorage)
cache.AddModSync(mod, false); cache.AddModSync(mod, false);
cache.AddMetaFiles(true);
collection.IncrementCounter(); collection.IncrementCounter();
MetaFileManager.ApplyDefaultFiles(collection); MetaFileManager.ApplyDefaultFiles(collection);

View file

@ -1,20 +1,22 @@
using Penumbra.GameData.Structs; using Penumbra.GameData.Structs;
using Penumbra.Interop.PathResolving;
using Penumbra.Meta; using Penumbra.Meta;
using Penumbra.Meta.Files; using Penumbra.Meta.Files;
using Penumbra.Meta.Manipulations; using Penumbra.Meta.Manipulations;
using Penumbra.String.Classes; using Penumbra.String;
namespace Penumbra.Collections.Cache; namespace Penumbra.Collections.Cache;
public sealed class ImcCache(MetaFileManager manager, ModCollection collection) : MetaCacheBase<ImcIdentifier, ImcEntry>(manager, collection) public sealed class ImcCache(MetaFileManager manager, ModCollection collection) : MetaCacheBase<ImcIdentifier, ImcEntry>(manager, collection)
{ {
private readonly Dictionary<Utf8GamePath, (ImcFile, HashSet<ImcIdentifier>)> _imcFiles = []; private readonly Dictionary<ByteString, (ImcFile, HashSet<ImcIdentifier>)> _imcFiles = [];
public override void SetFiles() public override void SetFiles()
=> SetFiles(false); { }
public bool GetFile(Utf8GamePath path, [NotNullWhen(true)] out ImcFile? file) public bool HasFile(ByteString path)
=> _imcFiles.ContainsKey(path);
public bool GetFile(ByteString path, [NotNullWhen(true)] out ImcFile? file)
{ {
if (!_imcFiles.TryGetValue(path, out var p)) if (!_imcFiles.TryGetValue(path, out var p))
{ {
@ -26,56 +28,31 @@ public sealed class ImcCache(MetaFileManager manager, ModCollection collection)
return true; return true;
} }
public void SetFiles(bool fromFullCompute)
{
if (fromFullCompute)
foreach (var (path, _) in _imcFiles)
Collection._cache!.ForceFileSync(path, PathDataHandler.CreateImc(path.Path, Collection));
else
foreach (var (path, _) in _imcFiles)
Collection._cache!.ForceFile(path, PathDataHandler.CreateImc(path.Path, Collection));
}
public void ResetFiles()
{
foreach (var (path, _) in _imcFiles)
Collection._cache!.ForceFile(path, FullPath.Empty);
}
protected override void IncorporateChangesInternal() protected override void IncorporateChangesInternal()
{ { }
if (!Manager.CharacterUtility.Ready)
return;
foreach (var (identifier, (_, entry)) in this)
ApplyFile(identifier, entry);
Penumbra.Log.Verbose($"{Collection.AnonymizedName}: Loaded {Count} delayed IMC manipulations.");
}
public void Reset() public void Reset()
{ {
foreach (var (path, (file, set)) in _imcFiles) foreach (var (_, (file, set)) in _imcFiles)
{ {
Collection._cache!.RemovePath(path);
file.Reset(); file.Reset();
set.Clear(); set.Clear();
} }
_imcFiles.Clear();
Clear(); Clear();
} }
protected override void ApplyModInternal(ImcIdentifier identifier, ImcEntry entry) protected override void ApplyModInternal(ImcIdentifier identifier, ImcEntry entry)
{ {
++Collection.ImcChangeCounter; ++Collection.ImcChangeCounter;
if (Manager.CharacterUtility.Ready)
ApplyFile(identifier, entry); ApplyFile(identifier, entry);
} }
private void ApplyFile(ImcIdentifier identifier, ImcEntry entry) private void ApplyFile(ImcIdentifier identifier, ImcEntry entry)
{ {
var path = identifier.GamePath(); var path = identifier.GamePath().Path;
try try
{ {
if (!_imcFiles.TryGetValue(path, out var pair)) if (!_imcFiles.TryGetValue(path, out var pair))
@ -87,8 +64,6 @@ public sealed class ImcCache(MetaFileManager manager, ModCollection collection)
pair.Item2.Add(identifier); pair.Item2.Add(identifier);
_imcFiles[path] = pair; _imcFiles[path] = pair;
var fullPath = PathDataHandler.CreateImc(pair.Item1.Path.Path, Collection);
Collection._cache!.ForceFile(path, fullPath);
} }
catch (ImcException e) catch (ImcException e)
{ {
@ -104,7 +79,7 @@ public sealed class ImcCache(MetaFileManager manager, ModCollection collection)
protected override void RevertModInternal(ImcIdentifier identifier) protected override void RevertModInternal(ImcIdentifier identifier)
{ {
++Collection.ImcChangeCounter; ++Collection.ImcChangeCounter;
var path = identifier.GamePath(); var path = identifier.GamePath().Path;
if (!_imcFiles.TryGetValue(path, out var pair)) if (!_imcFiles.TryGetValue(path, out var pair))
return; return;
@ -114,17 +89,12 @@ public sealed class ImcCache(MetaFileManager manager, ModCollection collection)
if (pair.Item2.Count == 0) if (pair.Item2.Count == 0)
{ {
_imcFiles.Remove(path); _imcFiles.Remove(path);
Collection._cache!.ForceFile(pair.Item1.Path, FullPath.Empty);
pair.Item1.Dispose(); pair.Item1.Dispose();
return; return;
} }
var def = ImcFile.GetDefault(Manager, pair.Item1.Path, identifier.EquipSlot, identifier.Variant, out _); var def = ImcFile.GetDefault(Manager, pair.Item1.Path, identifier.EquipSlot, identifier.Variant, out _);
if (!Apply(pair.Item1, identifier, def)) Apply(pair.Item1, identifier, def);
return;
var fullPath = PathDataHandler.CreateImc(pair.Item1.Path.Path, Collection);
Collection._cache!.ForceFile(pair.Item1.Path, fullPath);
} }
public static bool Apply(ImcFile file, ImcIdentifier identifier, ImcEntry entry) public static bool Apply(ImcFile file, ImcIdentifier identifier, ImcEntry entry)

View file

@ -36,7 +36,7 @@ public class MetaCache(MetaFileManager manager, ModCollection collection)
Est.SetFiles(); Est.SetFiles();
Gmp.SetFiles(); Gmp.SetFiles();
Rsp.SetFiles(); Rsp.SetFiles();
Imc.SetFiles(false); Imc.SetFiles();
} }
public void Reset() public void Reset()
@ -113,13 +113,9 @@ public class MetaCache(MetaFileManager manager, ModCollection collection)
~MetaCache() ~MetaCache()
=> Dispose(); => Dispose();
/// <summary> Set the currently relevant IMC files for the collection cache. </summary>
public void SetImcFiles(bool fromFullCompute)
=> Imc.SetFiles(fromFullCompute);
/// <summary> Try to obtain a manipulated IMC file. </summary> /// <summary> Try to obtain a manipulated IMC file. </summary>
public bool GetImcFile(Utf8GamePath path, [NotNullWhen(true)] out Meta.Files.ImcFile? file) public bool GetImcFile(Utf8GamePath path, [NotNullWhen(true)] out Meta.Files.ImcFile? file)
=> Imc.GetFile(path, out file); => Imc.GetFile(path.Path, out file);
internal EqdpEntry GetEqdpEntry(GenderRace race, bool accessory, PrimaryId primaryId) internal EqdpEntry GetEqdpEntry(GenderRace race, bool accessory, PrimaryId primaryId)
=> Eqdp.ApplyFullEntry(primaryId, race, accessory, Meta.Files.ExpandedEqdpFile.GetDefault(manager, race, accessory, primaryId)); => Eqdp.ApplyFullEntry(primaryId, race, accessory, Meta.Files.ExpandedEqdpFile.GetDefault(manager, race, accessory, primaryId));

View file

@ -1,11 +1,8 @@
using System.Runtime;
using FFXIVClientStructs.FFXIV.Client.System.Resource; using FFXIVClientStructs.FFXIV.Client.System.Resource;
using Penumbra.Api.Enums; using Penumbra.Api.Enums;
using Penumbra.Collections; using Penumbra.Collections;
using Penumbra.Collections.Manager; using Penumbra.Collections.Manager;
using Penumbra.Interop.ResourceLoading; using Penumbra.Interop.ResourceLoading;
using Penumbra.Interop.Structs;
using Penumbra.String;
using Penumbra.String.Classes; using Penumbra.String.Classes;
using Penumbra.Util; using Penumbra.Util;
@ -37,14 +34,6 @@ public class PathResolver : IDisposable
_collectionResolver = collectionResolver; _collectionResolver = collectionResolver;
_loader = loader; _loader = loader;
_loader.ResolvePath = ResolvePath; _loader.ResolvePath = ResolvePath;
_loader.FileLoaded += ImcLoadResource;
}
/// <summary> Obtain a temporary or permanent collection by local ID. </summary>
public bool CollectionByLocalId(LocalCollectionId id, out ModCollection collection)
{
collection = _collectionManager.Storage.ByLocalId(id);
return collection != ModCollection.Empty;
} }
/// <summary> Try to resolve the given game path to the replaced path. </summary> /// <summary> Try to resolve the given game path to the replaced path. </summary>
@ -120,7 +109,6 @@ public class PathResolver : IDisposable
public unsafe void Dispose() public unsafe void Dispose()
{ {
_loader.ResetResolvePath(); _loader.ResetResolvePath();
_loader.FileLoaded -= ImcLoadResource;
} }
/// <summary> Use the default method of path replacement. </summary> /// <summary> Use the default method of path replacement. </summary>
@ -130,24 +118,6 @@ public class PathResolver : IDisposable
return (resolved, _collectionManager.Active.Default.ToResolveData()); return (resolved, _collectionManager.Active.Default.ToResolveData());
} }
/// <summary> After loading an IMC file, replace its contents with the modded IMC file. </summary>
private unsafe void ImcLoadResource(ResourceHandle* resource, ByteString path, bool returnValue, bool custom,
ReadOnlySpan<byte> additionalData)
{
if (resource->FileType != ResourceType.Imc
|| !PathDataHandler.Read(additionalData, out var data)
|| data.Discriminator != PathDataHandler.Discriminator
|| !Utf8GamePath.FromByteString(path, out var gamePath)
|| !CollectionByLocalId(data.Collection, out var collection)
|| !collection.HasCache
|| !collection.GetImcFile(gamePath, out var file))
return;
file.Replace(resource);
Penumbra.Log.Verbose(
$"[ResourceLoader] Loaded {gamePath} from file and replaced with IMC from collection {collection.AnonymizedName}.");
}
/// <summary> Resolve a path from the interface collection. </summary> /// <summary> Resolve a path from the interface collection. </summary>
private (FullPath?, ResolveData) ResolveUi(Utf8GamePath path) private (FullPath?, ResolveData) ResolveUi(Utf8GamePath path)
=> (_collectionManager.Active.Interface.ResolvePath(path), => (_collectionManager.Active.Interface.ResolvePath(path),

View file

@ -70,12 +70,13 @@ public sealed unsafe class SubfileHelper : IDisposable, IReadOnlyCollection<KeyV
public static void HandleCollection(ResolveData resolveData, ByteString path, bool nonDefault, ResourceType type, FullPath? resolved, public static void HandleCollection(ResolveData resolveData, ByteString path, bool nonDefault, ResourceType type, FullPath? resolved,
Utf8GamePath originalPath, out (FullPath?, ResolveData) data) Utf8GamePath originalPath, out (FullPath?, ResolveData) data)
{ {
if (nonDefault)
resolved = type switch resolved = type switch
{ {
ResourceType.Mtrl => PathDataHandler.CreateMtrl(path, resolveData.ModCollection, originalPath), ResourceType.Mtrl when nonDefault => PathDataHandler.CreateMtrl(path, resolveData.ModCollection, originalPath),
ResourceType.Avfx => PathDataHandler.CreateAvfx(path, resolveData.ModCollection), ResourceType.Avfx when nonDefault => PathDataHandler.CreateAvfx(path, resolveData.ModCollection),
ResourceType.Tmb => PathDataHandler.CreateTmb(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, _ => resolved,
}; };
data = (resolved, resolveData); data = (resolved, resolveData);

View file

@ -1,6 +1,9 @@
using System.Collections.Frozen;
using FFXIVClientStructs.FFXIV.Client.System.Resource; using FFXIVClientStructs.FFXIV.Client.System.Resource;
using OtterGui.Services;
using Penumbra.Api.Enums; using Penumbra.Api.Enums;
using Penumbra.Collections; using Penumbra.Collections;
using Penumbra.Collections.Manager;
using Penumbra.Interop.PathResolving; using Penumbra.Interop.PathResolving;
using Penumbra.Interop.SafeHandles; using Penumbra.Interop.SafeHandles;
using Penumbra.Interop.Structs; using Penumbra.Interop.Structs;
@ -10,6 +13,72 @@ using FileMode = Penumbra.Interop.Structs.FileMode;
namespace Penumbra.Interop.ResourceLoading; 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 public unsafe class ResourceLoader : IDisposable
{ {
private readonly ResourceService _resources; private readonly ResourceService _resources;

View file

@ -1,6 +1,5 @@
using Dalamud.Plugin.Services; using Dalamud.Plugin.Services;
using Dalamud.Utility.Signatures; using Dalamud.Utility.Signatures;
using Penumbra.Collections.Manager;
using Penumbra.GameData; using Penumbra.GameData;
using Penumbra.Interop.Structs; using Penumbra.Interop.Structs;
@ -54,16 +53,14 @@ public unsafe class CharacterUtility : IDisposable
=> _lists[idx.Value].DefaultResource; => _lists[idx.Value].DefaultResource;
private readonly IFramework _framework; private readonly IFramework _framework;
public readonly ActiveCollectionData Active;
public CharacterUtility(IFramework framework, IGameInteropProvider interop, ActiveCollectionData active) public CharacterUtility(IFramework framework, IGameInteropProvider interop)
{ {
interop.InitializeFromAttributes(this); interop.InitializeFromAttributes(this);
_lists = Enumerable.Range(0, RelevantIndices.Length) _lists = Enumerable.Range(0, RelevantIndices.Length)
.Select(idx => new MetaList(this, new InternalIndex(idx))) .Select(idx => new MetaList(this, new InternalIndex(idx)))
.ToArray(); .ToArray();
_framework = framework; _framework = framework;
Active = active;
LoadingFinished += () => Penumbra.Log.Debug("Loading of CharacterUtility finished."); LoadingFinished += () => Penumbra.Log.Debug("Loading of CharacterUtility finished.");
LoadDefaultResources(null!); LoadDefaultResources(null!);
if (!Ready) if (!Ready)
@ -121,34 +118,6 @@ public unsafe class CharacterUtility : IDisposable
LoadingFinished.Invoke(); LoadingFinished.Invoke();
} }
public void SetResource(MetaIndex resourceIdx, nint data, int length)
{
var idx = ReverseIndices[(int)resourceIdx];
var list = _lists[idx.Value];
list.SetResource(data, length);
}
public void ResetResource(MetaIndex resourceIdx)
{
var idx = ReverseIndices[(int)resourceIdx];
var list = _lists[idx.Value];
list.ResetResource();
}
public MetaList.MetaReverter TemporarilySetResource(MetaIndex resourceIdx, nint data, int length)
{
var idx = ReverseIndices[(int)resourceIdx];
var list = _lists[idx.Value];
return list.TemporarilySetResource(data, length);
}
public MetaList.MetaReverter TemporarilyResetResource(MetaIndex resourceIdx)
{
var idx = ReverseIndices[(int)resourceIdx];
var list = _lists[idx.Value];
return list.TemporarilyResetResource();
}
/// <summary> Return all relevant resources to the default resource. </summary> /// <summary> Return all relevant resources to the default resource. </summary>
public void ResetAll() public void ResetAll()
{ {

View file

@ -34,7 +34,7 @@ public sealed unsafe class CmpFile : MetaBaseFile
} }
public CmpFile(MetaFileManager manager) public CmpFile(MetaFileManager manager)
: base(manager, MetaIndex.HumanCmp) : base(manager, manager.MarshalAllocator, MetaIndex.HumanCmp)
{ {
AllocateData(DefaultData.Length); AllocateData(DefaultData.Length);
Reset(); Reset();

View file

@ -87,7 +87,7 @@ public sealed unsafe class ExpandedEqdpFile : MetaBaseFile
} }
public ExpandedEqdpFile(MetaFileManager manager, GenderRace raceCode, bool accessory) public ExpandedEqdpFile(MetaFileManager manager, GenderRace raceCode, bool accessory)
: base(manager, CharacterUtilityData.EqdpIdx(raceCode, accessory)) : base(manager, manager.MarshalAllocator, CharacterUtilityData.EqdpIdx(raceCode, accessory))
{ {
var def = (byte*)DefaultData.Data; var def = (byte*)DefaultData.Data;
var blockSize = *(ushort*)(def + IdentifierSize); var blockSize = *(ushort*)(def + IdentifierSize);

View file

@ -76,7 +76,7 @@ public unsafe class ExpandedEqpGmpBase : MetaBaseFile
} }
public ExpandedEqpGmpBase(MetaFileManager manager, bool gmp) public ExpandedEqpGmpBase(MetaFileManager manager, bool gmp)
: base(manager, gmp ? MetaIndex.Gmp : MetaIndex.Eqp) : base(manager, manager.MarshalAllocator, gmp ? MetaIndex.Gmp : MetaIndex.Eqp)
{ {
AllocateData(MaxSize); AllocateData(MaxSize);
Reset(); Reset();

View file

@ -157,7 +157,7 @@ public sealed unsafe class EstFile : MetaBaseFile
} }
public EstFile(MetaFileManager manager, EstType estType) public EstFile(MetaFileManager manager, EstType estType)
: base(manager, (MetaIndex)estType) : base(manager, manager.MarshalAllocator, (MetaIndex)estType)
{ {
var length = DefaultData.Length; var length = DefaultData.Length;
AllocateData(length + IncreaseSize); AllocateData(length + IncreaseSize);

View file

@ -12,7 +12,7 @@ namespace Penumbra.Meta.Files;
/// Containing Flags in each byte, 0x01 set for Body, 0x02 set for Helmet. /// Containing Flags in each byte, 0x01 set for Body, 0x02 set for Helmet.
/// Each flag corresponds to a mount row from the Mounts table and determines whether the mount disables the effect. /// Each flag corresponds to a mount row from the Mounts table and determines whether the mount disables the effect.
/// </summary> /// </summary>
public unsafe class EvpFile : MetaBaseFile public unsafe class EvpFile(MetaFileManager manager) : MetaBaseFile(manager, manager.MarshalAllocator, (MetaIndex)1)
{ {
public const int FlagArraySize = 512; public const int FlagArraySize = 512;
@ -57,8 +57,4 @@ public unsafe class EvpFile : MetaBaseFile
return EvpFlag.None; return EvpFlag.None;
} }
public EvpFile(MetaFileManager manager)
: base(manager, (MetaIndex)1) // TODO: Name
{ }
} }

View file

@ -7,16 +7,10 @@ using Penumbra.String.Functions;
namespace Penumbra.Meta.Files; namespace Penumbra.Meta.Files;
public class ImcException : Exception public class ImcException(ImcIdentifier identifier, Utf8GamePath path) : Exception
{ {
public readonly ImcIdentifier Identifier; public readonly ImcIdentifier Identifier = identifier;
public readonly string GamePath; public readonly string GamePath = path.ToString();
public ImcException(ImcIdentifier identifier, Utf8GamePath path)
{
Identifier = identifier;
GamePath = path.ToString();
}
public override string Message public override string Message
=> "Could not obtain default Imc File.\n" => "Could not obtain default Imc File.\n"
@ -146,7 +140,11 @@ public unsafe class ImcFile : MetaBaseFile
} }
public ImcFile(MetaFileManager manager, ImcIdentifier identifier) public ImcFile(MetaFileManager manager, ImcIdentifier identifier)
: base(manager, 0) : this(manager, manager.MarshalAllocator, identifier)
{ }
public ImcFile(MetaFileManager manager, IFileAllocator alloc, ImcIdentifier identifier)
: base(manager, alloc, 0)
{ {
var path = identifier.GamePathString(); var path = identifier.GamePathString();
Path = Utf8GamePath.FromString(path, out var p) ? p : Utf8GamePath.Empty; Path = Utf8GamePath.FromString(path, out var p) ? p : Utf8GamePath.Empty;
@ -194,7 +192,13 @@ public unsafe class ImcFile : MetaBaseFile
public void Replace(ResourceHandle* resource) public void Replace(ResourceHandle* resource)
{ {
var (data, length) = resource->GetData(); var (data, length) = resource->GetData();
var newData = Manager.AllocateDefaultMemory(ActualLength, 8); if (length == ActualLength)
{
MemoryUtility.MemCpyUnchecked((byte*)data, Data, ActualLength);
return;
}
var newData = Manager.XivAllocator.Allocate(ActualLength, 8);
if (newData == null) if (newData == null)
{ {
Penumbra.Log.Error($"Could not replace loaded IMC data at 0x{(ulong)resource:X}, allocation failed."); Penumbra.Log.Error($"Could not replace loaded IMC data at 0x{(ulong)resource:X}, allocation failed.");
@ -203,7 +207,7 @@ public unsafe class ImcFile : MetaBaseFile
MemoryUtility.MemCpyUnchecked(newData, Data, ActualLength); MemoryUtility.MemCpyUnchecked(newData, Data, ActualLength);
Manager.Free(data, length); Manager.XivAllocator.Release((void*)data, length);
resource->SetData((IntPtr)newData, ActualLength); resource->SetData((nint)newData, ActualLength);
} }
} }

View file

@ -1,23 +1,75 @@
using Dalamud.Memory; using Dalamud.Memory;
using Dalamud.Plugin.Services;
using Dalamud.Utility.Signatures;
using FFXIVClientStructs.FFXIV.Client.System.Memory;
using OtterGui.Services;
using Penumbra.GameData;
using Penumbra.Interop.Structs; using Penumbra.Interop.Structs;
using Penumbra.String.Functions; using Penumbra.String.Functions;
using CharacterUtility = Penumbra.Interop.Services.CharacterUtility; using CharacterUtility = Penumbra.Interop.Services.CharacterUtility;
namespace Penumbra.Meta.Files; namespace Penumbra.Meta.Files;
public unsafe class MetaBaseFile : IDisposable public unsafe interface IFileAllocator
{ {
protected readonly MetaFileManager Manager; public T* Allocate<T>(int length, int alignment = 1) where T : unmanaged;
public void Release<T>(ref T* pointer, int length) where T : unmanaged;
public void Release(void* pointer, int length)
{
var tmp = (byte*)pointer;
Release(ref tmp, length);
}
public byte* Allocate(int length, int alignment = 1)
=> Allocate<byte>(length, alignment);
}
public sealed class MarshalAllocator : IFileAllocator
{
public unsafe T* Allocate<T>(int length, int alignment = 1) where T : unmanaged
=> (T*)Marshal.AllocHGlobal(length * sizeof(T));
public unsafe void Release<T>(ref T* pointer, int length) where T : unmanaged
{
Marshal.FreeHGlobal((nint)pointer);
pointer = null;
}
}
public sealed unsafe class XivFileAllocator : IFileAllocator, IService
{
/// <summary>
/// Allocate in the games space for file storage.
/// We only need this if using any meta file.
/// </summary>
[Signature(Sigs.GetFileSpace)]
private readonly nint _getFileSpaceAddress = nint.Zero;
public XivFileAllocator(IGameInteropProvider provider)
=> provider.InitializeFromAttributes(this);
public IMemorySpace* GetFileSpace()
=> ((delegate* unmanaged<IMemorySpace*>)_getFileSpaceAddress)();
public T* Allocate<T>(int length, int alignment = 1) where T : unmanaged
=> (T*)GetFileSpace()->Malloc((ulong)(length * sizeof(T)), (ulong)alignment);
public void Release<T>(ref T* pointer, int length) where T : unmanaged
{
IMemorySpace.Free(pointer, (ulong)(length * sizeof(T)));
pointer = null;
}
}
public unsafe class MetaBaseFile(MetaFileManager manager, IFileAllocator alloc, MetaIndex idx) : IDisposable
{
protected readonly MetaFileManager Manager = manager;
protected readonly IFileAllocator Allocator = alloc;
public byte* Data { get; private set; } public byte* Data { get; private set; }
public int Length { get; private set; } public int Length { get; private set; }
public CharacterUtility.InternalIndex Index { get; } public CharacterUtility.InternalIndex Index { get; } = CharacterUtility.ReverseIndices[(int)idx];
public MetaBaseFile(MetaFileManager manager, MetaIndex idx)
{
Manager = manager;
Index = CharacterUtility.ReverseIndices[(int)idx];
}
protected (IntPtr Data, int Length) DefaultData protected (IntPtr Data, int Length) DefaultData
=> Manager.CharacterUtility.DefaultResource(Index); => Manager.CharacterUtility.DefaultResource(Index);
@ -30,7 +82,7 @@ public unsafe class MetaBaseFile : IDisposable
protected void AllocateData(int length) protected void AllocateData(int length)
{ {
Length = length; Length = length;
Data = (byte*)Manager.AllocateFileMemory(length); Data = Allocator.Allocate(length);
if (length > 0) if (length > 0)
GC.AddMemoryPressure(length); GC.AddMemoryPressure(length);
} }
@ -38,8 +90,7 @@ public unsafe class MetaBaseFile : IDisposable
/// <summary> Free memory. </summary> /// <summary> Free memory. </summary>
protected void ReleaseUnmanagedResources() protected void ReleaseUnmanagedResources()
{ {
var ptr = (IntPtr)Data; Allocator.Release(Data, Length);
MemoryHelper.GameFree(ref ptr, (ulong)Length);
if (Length > 0) if (Length > 0)
GC.RemoveMemoryPressure(Length); GC.RemoveMemoryPressure(Length);
@ -53,7 +104,7 @@ public unsafe class MetaBaseFile : IDisposable
if (newLength == Length) if (newLength == Length)
return; return;
var data = (byte*)Manager.AllocateFileMemory((ulong)newLength); var data = Allocator.Allocate(newLength);
if (newLength > Length) if (newLength > Length)
{ {
MemoryUtility.MemCpyUnchecked(data, Data, Length); MemoryUtility.MemCpyUnchecked(data, Data, Length);

View file

@ -1,14 +1,10 @@
using Dalamud.Plugin.Services; using Dalamud.Plugin.Services;
using Dalamud.Utility.Signatures;
using FFXIVClientStructs.FFXIV.Client.System.Memory;
using OtterGui.Compression; using OtterGui.Compression;
using Penumbra.Collections; using Penumbra.Collections;
using Penumbra.Collections.Manager; using Penumbra.Collections.Manager;
using Penumbra.GameData;
using Penumbra.GameData.Data; using Penumbra.GameData.Data;
using Penumbra.Import; using Penumbra.Import;
using Penumbra.Interop.Services; using Penumbra.Interop.Services;
using Penumbra.Interop.Structs;
using Penumbra.Meta.Files; using Penumbra.Meta.Files;
using Penumbra.Mods; using Penumbra.Mods;
using Penumbra.Mods.Groups; using Penumbra.Mods.Groups;
@ -28,6 +24,9 @@ public unsafe class MetaFileManager
internal readonly ObjectIdentification Identifier; internal readonly ObjectIdentification Identifier;
internal readonly FileCompactor Compactor; internal readonly FileCompactor Compactor;
internal readonly ImcChecker ImcChecker; internal readonly ImcChecker ImcChecker;
internal readonly IFileAllocator MarshalAllocator = new MarshalAllocator();
internal readonly IFileAllocator XivAllocator;
public MetaFileManager(CharacterUtility characterUtility, ResidentResourceManager residentResources, IDataManager gameData, public MetaFileManager(CharacterUtility characterUtility, ResidentResourceManager residentResources, IDataManager gameData,
ActiveCollectionData activeCollections, Configuration config, ValidityChecker validityChecker, ObjectIdentification identifier, ActiveCollectionData activeCollections, Configuration config, ValidityChecker validityChecker, ObjectIdentification identifier,
@ -42,6 +41,7 @@ public unsafe class MetaFileManager
Identifier = identifier; Identifier = identifier;
Compactor = compactor; Compactor = compactor;
ImcChecker = new ImcChecker(this); ImcChecker = new ImcChecker(this);
XivAllocator = new XivFileAllocator(interop);
interop.InitializeFromAttributes(this); interop.InitializeFromAttributes(this);
} }
@ -76,57 +76,11 @@ public unsafe class MetaFileManager
} }
} }
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
public void SetFile(MetaBaseFile? file, MetaIndex metaIndex)
{
if (file == null || !Config.EnableMods)
CharacterUtility.ResetResource(metaIndex);
else
CharacterUtility.SetResource(metaIndex, (nint)file.Data, file.Length);
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
public MetaList.MetaReverter TemporarilySetFile(MetaBaseFile? file, MetaIndex metaIndex)
=> Config.EnableMods
? file == null
? CharacterUtility.TemporarilyResetResource(metaIndex)
: CharacterUtility.TemporarilySetResource(metaIndex, (nint)file.Data, file.Length)
: MetaList.MetaReverter.Disabled;
public void ApplyDefaultFiles(ModCollection? collection) public void ApplyDefaultFiles(ModCollection? collection)
{ {
if (ActiveCollections.Default != collection || !CharacterUtility.Ready || !Config.EnableMods) if (ActiveCollections.Default != collection || !CharacterUtility.Ready || !Config.EnableMods)
return; return;
ResidentResources.Reload(); ResidentResources.Reload();
if (collection._cache == null)
CharacterUtility.ResetAll();
else
collection._cache.Meta.SetFiles();
} }
/// <summary>
/// Allocate in the games space for file storage.
/// We only need this if using any meta file.
/// </summary>
[Signature(Sigs.GetFileSpace)]
private readonly nint _getFileSpaceAddress = nint.Zero;
public IMemorySpace* GetFileSpace()
=> ((delegate* unmanaged<IMemorySpace*>)_getFileSpaceAddress)();
public void* AllocateFileMemory(ulong length, ulong alignment = 0)
=> GetFileSpace()->Malloc(length, alignment);
public void* AllocateFileMemory(int length, int alignment = 0)
=> AllocateFileMemory((ulong)length, (ulong)alignment);
public void* AllocateDefaultMemory(ulong length, ulong alignment = 0)
=> GetFileSpace()->Malloc(length, alignment);
public void* AllocateDefaultMemory(int length, int alignment = 0)
=> IMemorySpace.GetDefaultSpace()->Malloc((ulong)length, (ulong)alignment);
public void Free(nint ptr, int length)
=> IMemorySpace.Free((void*)ptr, (ulong)length);
} }