diff --git a/Penumbra/Collections/Cache/CollectionCache.cs b/Penumbra/Collections/Cache/CollectionCache.cs index fd801d3b..4755840e 100644 --- a/Penumbra/Collections/Cache/CollectionCache.cs +++ b/Penumbra/Collections/Cache/CollectionCache.cs @@ -36,7 +36,7 @@ public sealed class CollectionCache : IDisposable => ConflictDict.Values; public SingleArray Conflicts(IMod mod) - => ConflictDict.TryGetValue(mod, out SingleArray c) ? c : new SingleArray(); + => ConflictDict.TryGetValue(mod, out var c) ? c : new SingleArray(); private int _changedItemsSaveCounter = -1; @@ -125,12 +125,6 @@ public sealed class CollectionCache : IDisposable 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) => _manager.AddChange(ChangeData.ModReload(this, mod, addMetaChanges)); @@ -251,9 +245,6 @@ public sealed class CollectionCache : IDisposable if (addMetaChanges) { _collection.IncrementCounter(); - if (mod.TotalManipulations > 0) - AddMetaFiles(false); - _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. private void SetChangedItems() { diff --git a/Penumbra/Collections/Cache/CollectionCacheManager.cs b/Penumbra/Collections/Cache/CollectionCacheManager.cs index ae424b94..02c9c8a9 100644 --- a/Penumbra/Collections/Cache/CollectionCacheManager.cs +++ b/Penumbra/Collections/Cache/CollectionCacheManager.cs @@ -180,8 +180,6 @@ public class CollectionCacheManager : IDisposable foreach (var mod in _modStorage) cache.AddModSync(mod, false); - cache.AddMetaFiles(true); - collection.IncrementCounter(); MetaFileManager.ApplyDefaultFiles(collection); diff --git a/Penumbra/Collections/Cache/ImcCache.cs b/Penumbra/Collections/Cache/ImcCache.cs index 786463bc..e7eedc04 100644 --- a/Penumbra/Collections/Cache/ImcCache.cs +++ b/Penumbra/Collections/Cache/ImcCache.cs @@ -1,20 +1,22 @@ using Penumbra.GameData.Structs; -using Penumbra.Interop.PathResolving; using Penumbra.Meta; using Penumbra.Meta.Files; using Penumbra.Meta.Manipulations; -using Penumbra.String.Classes; +using Penumbra.String; namespace Penumbra.Collections.Cache; public sealed class ImcCache(MetaFileManager manager, ModCollection collection) : MetaCacheBase(manager, collection) { - private readonly Dictionary)> _imcFiles = []; + private readonly Dictionary)> _imcFiles = []; 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)) { @@ -26,56 +28,31 @@ public sealed class ImcCache(MetaFileManager manager, ModCollection collection) 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() - { - 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() { - foreach (var (path, (file, set)) in _imcFiles) + foreach (var (_, (file, set)) in _imcFiles) { - Collection._cache!.RemovePath(path); file.Reset(); set.Clear(); } + _imcFiles.Clear(); Clear(); } protected override void ApplyModInternal(ImcIdentifier identifier, ImcEntry entry) { ++Collection.ImcChangeCounter; - if (Manager.CharacterUtility.Ready) - ApplyFile(identifier, entry); + ApplyFile(identifier, entry); } private void ApplyFile(ImcIdentifier identifier, ImcEntry entry) { - var path = identifier.GamePath(); + var path = identifier.GamePath().Path; try { if (!_imcFiles.TryGetValue(path, out var pair)) @@ -87,8 +64,6 @@ public sealed class ImcCache(MetaFileManager manager, ModCollection collection) pair.Item2.Add(identifier); _imcFiles[path] = pair; - var fullPath = PathDataHandler.CreateImc(pair.Item1.Path.Path, Collection); - Collection._cache!.ForceFile(path, fullPath); } catch (ImcException e) { @@ -104,7 +79,7 @@ public sealed class ImcCache(MetaFileManager manager, ModCollection collection) protected override void RevertModInternal(ImcIdentifier identifier) { ++Collection.ImcChangeCounter; - var path = identifier.GamePath(); + var path = identifier.GamePath().Path; if (!_imcFiles.TryGetValue(path, out var pair)) return; @@ -114,17 +89,12 @@ public sealed class ImcCache(MetaFileManager manager, ModCollection collection) if (pair.Item2.Count == 0) { _imcFiles.Remove(path); - Collection._cache!.ForceFile(pair.Item1.Path, FullPath.Empty); pair.Item1.Dispose(); return; } var def = ImcFile.GetDefault(Manager, pair.Item1.Path, identifier.EquipSlot, identifier.Variant, out _); - if (!Apply(pair.Item1, identifier, def)) - return; - - var fullPath = PathDataHandler.CreateImc(pair.Item1.Path.Path, Collection); - Collection._cache!.ForceFile(pair.Item1.Path, fullPath); + Apply(pair.Item1, identifier, def); } public static bool Apply(ImcFile file, ImcIdentifier identifier, ImcEntry entry) diff --git a/Penumbra/Collections/Cache/MetaCache.cs b/Penumbra/Collections/Cache/MetaCache.cs index 92a445dd..253f3c7f 100644 --- a/Penumbra/Collections/Cache/MetaCache.cs +++ b/Penumbra/Collections/Cache/MetaCache.cs @@ -36,7 +36,7 @@ public class MetaCache(MetaFileManager manager, ModCollection collection) Est.SetFiles(); Gmp.SetFiles(); Rsp.SetFiles(); - Imc.SetFiles(false); + Imc.SetFiles(); } public void Reset() @@ -113,13 +113,9 @@ public class MetaCache(MetaFileManager manager, ModCollection collection) ~MetaCache() => Dispose(); - /// Set the currently relevant IMC files for the collection cache. - public void SetImcFiles(bool fromFullCompute) - => Imc.SetFiles(fromFullCompute); - /// Try to obtain a manipulated IMC 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) => Eqdp.ApplyFullEntry(primaryId, race, accessory, Meta.Files.ExpandedEqdpFile.GetDefault(manager, race, accessory, primaryId)); diff --git a/Penumbra/Interop/PathResolving/PathResolver.cs b/Penumbra/Interop/PathResolving/PathResolver.cs index e5c75327..e069e3ea 100644 --- a/Penumbra/Interop/PathResolving/PathResolver.cs +++ b/Penumbra/Interop/PathResolving/PathResolver.cs @@ -1,11 +1,8 @@ -using System.Runtime; using FFXIVClientStructs.FFXIV.Client.System.Resource; using Penumbra.Api.Enums; using Penumbra.Collections; using Penumbra.Collections.Manager; using Penumbra.Interop.ResourceLoading; -using Penumbra.Interop.Structs; -using Penumbra.String; using Penumbra.String.Classes; using Penumbra.Util; @@ -27,24 +24,16 @@ public class PathResolver : IDisposable public unsafe PathResolver(PerformanceTracker performance, Configuration config, CollectionManager collectionManager, ResourceLoader loader, SubfileHelper subfileHelper, PathState pathState, MetaState metaState, CollectionResolver collectionResolver, GameState gameState) { - _performance = performance; - _config = config; - _collectionManager = collectionManager; - _subfileHelper = subfileHelper; - _pathState = pathState; - _metaState = metaState; - _gameState = gameState; - _collectionResolver = collectionResolver; - _loader = loader; - _loader.ResolvePath = ResolvePath; - _loader.FileLoaded += ImcLoadResource; - } - - /// Obtain a temporary or permanent collection by local ID. - public bool CollectionByLocalId(LocalCollectionId id, out ModCollection collection) - { - collection = _collectionManager.Storage.ByLocalId(id); - return collection != ModCollection.Empty; + _performance = performance; + _config = config; + _collectionManager = collectionManager; + _subfileHelper = subfileHelper; + _pathState = pathState; + _metaState = metaState; + _gameState = gameState; + _collectionResolver = collectionResolver; + _loader = loader; + _loader.ResolvePath = ResolvePath; } /// Try to resolve the given game path to the replaced path. @@ -120,7 +109,6 @@ public class PathResolver : IDisposable public unsafe void Dispose() { _loader.ResetResolvePath(); - _loader.FileLoaded -= ImcLoadResource; } /// Use the default method of path replacement. @@ -130,24 +118,6 @@ public class PathResolver : IDisposable return (resolved, _collectionManager.Active.Default.ToResolveData()); } - /// After loading an IMC file, replace its contents with the modded IMC file. - private unsafe void ImcLoadResource(ResourceHandle* resource, ByteString path, bool returnValue, bool custom, - ReadOnlySpan 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}."); - } - /// Resolve a path from the interface collection. private (FullPath?, ResolveData) ResolveUi(Utf8GamePath path) => (_collectionManager.Active.Interface.ResolvePath(path), diff --git a/Penumbra/Interop/PathResolving/SubfileHelper.cs b/Penumbra/Interop/PathResolving/SubfileHelper.cs index 793ea20b..b9631cf2 100644 --- a/Penumbra/Interop/PathResolving/SubfileHelper.cs +++ b/Penumbra/Interop/PathResolving/SubfileHelper.cs @@ -70,14 +70,15 @@ public sealed unsafe class SubfileHelper : IDisposable, IReadOnlyCollection PathDataHandler.CreateMtrl(path, resolveData.ModCollection, originalPath), - ResourceType.Avfx => PathDataHandler.CreateAvfx(path, resolveData.ModCollection), - ResourceType.Tmb => PathDataHandler.CreateTmb(path, resolveData.ModCollection), - _ => resolved, - }; + 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); } diff --git a/Penumbra/Interop/ResourceLoading/ResourceLoader.cs b/Penumbra/Interop/ResourceLoading/ResourceLoader.cs index 7b49beab..fae38907 100644 --- a/Penumbra/Interop/ResourceLoading/ResourceLoader.cs +++ b/Penumbra/Interop/ResourceLoading/ResourceLoader.cs @@ -1,6 +1,9 @@ +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; @@ -10,6 +13,72 @@ 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 additionalData); +} + +public sealed class MaterialFilePostProcessor : IFilePostProcessor +{ + public ResourceType Type + => ResourceType.Mtrl; + + public unsafe void PostProcess(ResourceHandle* resource, ByteString originalGamePath, ReadOnlySpan 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 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 _processors; + + public FilePostProcessService(ResourceLoader resourceLoader, ServiceManager services) + { + _resourceLoader = resourceLoader; + _processors = services.GetServicesImplementing().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 additionalData) + { + if (_processors.TryGetValue(resource->FileType, out var processor)) + processor.PostProcess(resource, path, additionalData); + } +} + public unsafe class ResourceLoader : IDisposable { private readonly ResourceService _resources; diff --git a/Penumbra/Interop/Services/CharacterUtility.cs b/Penumbra/Interop/Services/CharacterUtility.cs index da04bf90..9459df06 100644 --- a/Penumbra/Interop/Services/CharacterUtility.cs +++ b/Penumbra/Interop/Services/CharacterUtility.cs @@ -1,6 +1,5 @@ using Dalamud.Plugin.Services; using Dalamud.Utility.Signatures; -using Penumbra.Collections.Manager; using Penumbra.GameData; using Penumbra.Interop.Structs; @@ -53,17 +52,15 @@ public unsafe class CharacterUtility : IDisposable public (nint Address, int Size) DefaultResource(InternalIndex idx) => _lists[idx.Value].DefaultResource; - private readonly IFramework _framework; - public readonly ActiveCollectionData Active; + private readonly IFramework _framework; - public CharacterUtility(IFramework framework, IGameInteropProvider interop, ActiveCollectionData active) + public CharacterUtility(IFramework framework, IGameInteropProvider interop) { interop.InitializeFromAttributes(this); _lists = Enumerable.Range(0, RelevantIndices.Length) .Select(idx => new MetaList(this, new InternalIndex(idx))) .ToArray(); _framework = framework; - Active = active; LoadingFinished += () => Penumbra.Log.Debug("Loading of CharacterUtility finished."); LoadDefaultResources(null!); if (!Ready) @@ -121,34 +118,6 @@ public unsafe class CharacterUtility : IDisposable 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(); - } - /// Return all relevant resources to the default resource. public void ResetAll() { diff --git a/Penumbra/Meta/Files/CmpFile.cs b/Penumbra/Meta/Files/CmpFile.cs index 8ca7cb80..5028a3de 100644 --- a/Penumbra/Meta/Files/CmpFile.cs +++ b/Penumbra/Meta/Files/CmpFile.cs @@ -34,7 +34,7 @@ public sealed unsafe class CmpFile : MetaBaseFile } public CmpFile(MetaFileManager manager) - : base(manager, MetaIndex.HumanCmp) + : base(manager, manager.MarshalAllocator, MetaIndex.HumanCmp) { AllocateData(DefaultData.Length); Reset(); diff --git a/Penumbra/Meta/Files/EqdpFile.cs b/Penumbra/Meta/Files/EqdpFile.cs index e46e82e9..34b4f25b 100644 --- a/Penumbra/Meta/Files/EqdpFile.cs +++ b/Penumbra/Meta/Files/EqdpFile.cs @@ -87,7 +87,7 @@ public sealed unsafe class ExpandedEqdpFile : MetaBaseFile } 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 blockSize = *(ushort*)(def + IdentifierSize); diff --git a/Penumbra/Meta/Files/EqpGmpFile.cs b/Penumbra/Meta/Files/EqpGmpFile.cs index c47c84ef..a7540f4b 100644 --- a/Penumbra/Meta/Files/EqpGmpFile.cs +++ b/Penumbra/Meta/Files/EqpGmpFile.cs @@ -76,7 +76,7 @@ public unsafe class ExpandedEqpGmpBase : MetaBaseFile } public ExpandedEqpGmpBase(MetaFileManager manager, bool gmp) - : base(manager, gmp ? MetaIndex.Gmp : MetaIndex.Eqp) + : base(manager, manager.MarshalAllocator, gmp ? MetaIndex.Gmp : MetaIndex.Eqp) { AllocateData(MaxSize); Reset(); diff --git a/Penumbra/Meta/Files/EstFile.cs b/Penumbra/Meta/Files/EstFile.cs index f3860416..ba38d6d9 100644 --- a/Penumbra/Meta/Files/EstFile.cs +++ b/Penumbra/Meta/Files/EstFile.cs @@ -157,7 +157,7 @@ public sealed unsafe class EstFile : MetaBaseFile } public EstFile(MetaFileManager manager, EstType estType) - : base(manager, (MetaIndex)estType) + : base(manager, manager.MarshalAllocator, (MetaIndex)estType) { var length = DefaultData.Length; AllocateData(length + IncreaseSize); diff --git a/Penumbra/Meta/Files/EvpFile.cs b/Penumbra/Meta/Files/EvpFile.cs index 3d0b4dbe..6ab1591c 100644 --- a/Penumbra/Meta/Files/EvpFile.cs +++ b/Penumbra/Meta/Files/EvpFile.cs @@ -12,7 +12,7 @@ namespace Penumbra.Meta.Files; /// 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. /// -public unsafe class EvpFile : MetaBaseFile +public unsafe class EvpFile(MetaFileManager manager) : MetaBaseFile(manager, manager.MarshalAllocator, (MetaIndex)1) { public const int FlagArraySize = 512; @@ -57,8 +57,4 @@ public unsafe class EvpFile : MetaBaseFile return EvpFlag.None; } - - public EvpFile(MetaFileManager manager) - : base(manager, (MetaIndex)1) // TODO: Name - { } } diff --git a/Penumbra/Meta/Files/ImcFile.cs b/Penumbra/Meta/Files/ImcFile.cs index 892f5b44..01ef3f16 100644 --- a/Penumbra/Meta/Files/ImcFile.cs +++ b/Penumbra/Meta/Files/ImcFile.cs @@ -7,16 +7,10 @@ using Penumbra.String.Functions; namespace Penumbra.Meta.Files; -public class ImcException : Exception +public class ImcException(ImcIdentifier identifier, Utf8GamePath path) : Exception { - public readonly ImcIdentifier Identifier; - public readonly string GamePath; - - public ImcException(ImcIdentifier identifier, Utf8GamePath path) - { - Identifier = identifier; - GamePath = path.ToString(); - } + public readonly ImcIdentifier Identifier = identifier; + public readonly string GamePath = path.ToString(); public override string Message => "Could not obtain default Imc File.\n" @@ -146,7 +140,11 @@ public unsafe class ImcFile : MetaBaseFile } 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(); Path = Utf8GamePath.FromString(path, out var p) ? p : Utf8GamePath.Empty; @@ -194,7 +192,13 @@ public unsafe class ImcFile : MetaBaseFile public void Replace(ResourceHandle* resource) { 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) { 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); - Manager.Free(data, length); - resource->SetData((IntPtr)newData, ActualLength); + Manager.XivAllocator.Release((void*)data, length); + resource->SetData((nint)newData, ActualLength); } } diff --git a/Penumbra/Meta/Files/MetaBaseFile.cs b/Penumbra/Meta/Files/MetaBaseFile.cs index ab08efc2..86a55101 100644 --- a/Penumbra/Meta/Files/MetaBaseFile.cs +++ b/Penumbra/Meta/Files/MetaBaseFile.cs @@ -1,23 +1,75 @@ 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.String.Functions; using CharacterUtility = Penumbra.Interop.Services.CharacterUtility; namespace Penumbra.Meta.Files; -public unsafe class MetaBaseFile : IDisposable +public unsafe interface IFileAllocator { - protected readonly MetaFileManager Manager; + public T* Allocate(int length, int alignment = 1) where T : unmanaged; + public void Release(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(length, alignment); +} + +public sealed class MarshalAllocator : IFileAllocator +{ + public unsafe T* Allocate(int length, int alignment = 1) where T : unmanaged + => (T*)Marshal.AllocHGlobal(length * sizeof(T)); + + public unsafe void Release(ref T* pointer, int length) where T : unmanaged + { + Marshal.FreeHGlobal((nint)pointer); + pointer = null; + } +} + +public sealed unsafe class XivFileAllocator : IFileAllocator, IService +{ + /// + /// Allocate in the games space for file storage. + /// We only need this if using any meta file. + /// + [Signature(Sigs.GetFileSpace)] + private readonly nint _getFileSpaceAddress = nint.Zero; + + public XivFileAllocator(IGameInteropProvider provider) + => provider.InitializeFromAttributes(this); + + public IMemorySpace* GetFileSpace() + => ((delegate* unmanaged)_getFileSpaceAddress)(); + + public T* Allocate(int length, int alignment = 1) where T : unmanaged + => (T*)GetFileSpace()->Malloc((ulong)(length * sizeof(T)), (ulong)alignment); + + public void Release(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 int Length { get; private set; } - public CharacterUtility.InternalIndex Index { get; } - - public MetaBaseFile(MetaFileManager manager, MetaIndex idx) - { - Manager = manager; - Index = CharacterUtility.ReverseIndices[(int)idx]; - } + public CharacterUtility.InternalIndex Index { get; } = CharacterUtility.ReverseIndices[(int)idx]; protected (IntPtr Data, int Length) DefaultData => Manager.CharacterUtility.DefaultResource(Index); @@ -30,7 +82,7 @@ public unsafe class MetaBaseFile : IDisposable protected void AllocateData(int length) { Length = length; - Data = (byte*)Manager.AllocateFileMemory(length); + Data = Allocator.Allocate(length); if (length > 0) GC.AddMemoryPressure(length); } @@ -38,8 +90,7 @@ public unsafe class MetaBaseFile : IDisposable /// Free memory. protected void ReleaseUnmanagedResources() { - var ptr = (IntPtr)Data; - MemoryHelper.GameFree(ref ptr, (ulong)Length); + Allocator.Release(Data, Length); if (Length > 0) GC.RemoveMemoryPressure(Length); @@ -53,7 +104,7 @@ public unsafe class MetaBaseFile : IDisposable if (newLength == Length) return; - var data = (byte*)Manager.AllocateFileMemory((ulong)newLength); + var data = Allocator.Allocate(newLength); if (newLength > Length) { MemoryUtility.MemCpyUnchecked(data, Data, Length); diff --git a/Penumbra/Meta/MetaFileManager.cs b/Penumbra/Meta/MetaFileManager.cs index 40fceb07..81c0fa3e 100644 --- a/Penumbra/Meta/MetaFileManager.cs +++ b/Penumbra/Meta/MetaFileManager.cs @@ -1,14 +1,10 @@ using Dalamud.Plugin.Services; -using Dalamud.Utility.Signatures; -using FFXIVClientStructs.FFXIV.Client.System.Memory; using OtterGui.Compression; using Penumbra.Collections; using Penumbra.Collections.Manager; -using Penumbra.GameData; using Penumbra.GameData.Data; using Penumbra.Import; using Penumbra.Interop.Services; -using Penumbra.Interop.Structs; using Penumbra.Meta.Files; using Penumbra.Mods; using Penumbra.Mods.Groups; @@ -28,6 +24,9 @@ public unsafe class MetaFileManager internal readonly ObjectIdentification Identifier; internal readonly FileCompactor Compactor; internal readonly ImcChecker ImcChecker; + internal readonly IFileAllocator MarshalAllocator = new MarshalAllocator(); + internal readonly IFileAllocator XivAllocator; + public MetaFileManager(CharacterUtility characterUtility, ResidentResourceManager residentResources, IDataManager gameData, ActiveCollectionData activeCollections, Configuration config, ValidityChecker validityChecker, ObjectIdentification identifier, @@ -42,6 +41,7 @@ public unsafe class MetaFileManager Identifier = identifier; Compactor = compactor; ImcChecker = new ImcChecker(this); + XivAllocator = new XivFileAllocator(interop); 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) { if (ActiveCollections.Default != collection || !CharacterUtility.Ready || !Config.EnableMods) return; ResidentResources.Reload(); - if (collection._cache == null) - CharacterUtility.ResetAll(); - else - collection._cache.Meta.SetFiles(); } - - /// - /// Allocate in the games space for file storage. - /// We only need this if using any meta file. - /// - [Signature(Sigs.GetFileSpace)] - private readonly nint _getFileSpaceAddress = nint.Zero; - - public IMemorySpace* GetFileSpace() - => ((delegate* unmanaged)_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); }