diff --git a/Penumbra/Interop/CharacterUtility.List.cs b/Penumbra/Interop/CharacterUtility.List.cs new file mode 100644 index 00000000..27146af6 --- /dev/null +++ b/Penumbra/Interop/CharacterUtility.List.cs @@ -0,0 +1,147 @@ +using System; +using System.Collections.Generic; + +namespace Penumbra.Interop; + +public unsafe partial class CharacterUtility +{ + public class List : IDisposable + { + private readonly LinkedList< MetaReverter > _entries = new(); + public readonly InternalIndex Index; + public readonly Structs.CharacterUtility.Index GlobalIndex; + + private IntPtr _defaultResourceData = IntPtr.Zero; + private int _defaultResourceSize = 0; + public bool Ready { get; private set; } = false; + + public List( InternalIndex index ) + { + Index = index; + GlobalIndex = RelevantIndices[ index.Value ]; + } + + public void SetDefaultResource( IntPtr data, int size ) + { + if( !Ready ) + { + _defaultResourceData = data; + _defaultResourceSize = size; + Ready = _defaultResourceData != IntPtr.Zero && size != 0; + if( _entries.Count > 0 ) + { + var first = _entries.First!.Value; + SetResource( first.Data, first.Length ); + } + } + } + + public (IntPtr Address, int Size) DefaultResource + => ( _defaultResourceData, _defaultResourceSize ); + + public MetaReverter TemporarilySetResource( IntPtr data, int length ) + { + Penumbra.Log.Verbose( $"Temporarily set resource {GlobalIndex} 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.Verbose( $"Temporarily reset resource {GlobalIndex} to default at 0x{_defaultResourceData:X} ({_defaultResourceSize} bytes)." ); + var reverter = new MetaReverter( this ); + _entries.AddFirst( reverter ); + ResetResourceInternal(); + return reverter; + } + + public void SetResource( IntPtr data, int length ) + { + Penumbra.Log.Verbose( $"Set resource {GlobalIndex} to 0x{( ulong )data:X} ({length} bytes)." ); + SetResourceInternal( data, length ); + } + + public void ResetResource() + { + Penumbra.Log.Verbose( $"Reset resource {GlobalIndex} to default at 0x{_defaultResourceData:X} ({_defaultResourceSize} bytes)." ); + ResetResourceInternal(); + } + + + // Set the currently stored data of this resource to new values. + private void SetResourceInternal( IntPtr data, int length ) + { + if( Ready ) + { + var resource = Penumbra.CharacterUtility.Address->Resource( GlobalIndex ); + resource->SetData( data, length ); + } + } + + // Reset the currently stored data of this resource to its default values. + private void ResetResourceInternal() + => SetResourceInternal( _defaultResourceData, _defaultResourceSize ); + + public void Dispose() + { + if( _entries.Count > 0 ) + { + _entries.Clear(); + ResetResourceInternal(); + } + } + + public sealed class MetaReverter : IDisposable + { + public readonly List List; + public readonly IntPtr Data; + public readonly int Length; + public readonly bool Resetter; + + public MetaReverter( List list, IntPtr data, int length ) + { + List = list; + Data = data; + Length = length; + } + + public MetaReverter( List list ) + { + List = list; + Data = IntPtr.Zero; + Length = 0; + Resetter = true; + } + + public void Dispose() + { + var list = List._entries; + var wasCurrent = ReferenceEquals( this, list.First?.Value ); + list.Remove( this ); + if( !wasCurrent ) + { + return; + } + + if( list.Count == 0 ) + { + List.ResetResourceInternal(); + } + else + { + var next = list.First!.Value; + if( next.Resetter ) + { + List.ResetResourceInternal(); + } + else + { + List.SetResourceInternal( next.Data, next.Length ); + } + } + } + } + } +} \ No newline at end of file diff --git a/Penumbra/Interop/CharacterUtility.cs b/Penumbra/Interop/CharacterUtility.cs index 84dc750c..c168f8c4 100644 --- a/Penumbra/Interop/CharacterUtility.cs +++ b/Penumbra/Interop/CharacterUtility.cs @@ -4,7 +4,7 @@ using Dalamud.Utility.Signatures; namespace Penumbra.Interop; -public unsafe class CharacterUtility : IDisposable +public unsafe partial class CharacterUtility : IDisposable { public record struct InternalIndex( int Value ); @@ -34,17 +34,15 @@ public unsafe class CharacterUtility : IDisposable public static readonly InternalIndex[] ReverseIndices = Enumerable.Range( 0, Structs.CharacterUtility.TotalNumResources ) - .Select( i => new InternalIndex( Array.IndexOf( RelevantIndices, (Structs.CharacterUtility.Index) i ) ) ) + .Select( i => new InternalIndex( Array.IndexOf( RelevantIndices, ( Structs.CharacterUtility.Index )i ) ) ) .ToArray(); - - private readonly (IntPtr Address, int Size)[] _defaultResources = new (IntPtr, int)[RelevantIndices.Length]; - - public (IntPtr Address, int Size) DefaultResource( Structs.CharacterUtility.Index idx ) - => _defaultResources[ ReverseIndices[ ( int )idx ].Value ]; + private readonly List[] _lists = Enumerable.Range( 0, RelevantIndices.Length ) + .Select( idx => new List( new InternalIndex( idx ) ) ) + .ToArray(); public (IntPtr Address, int Size) DefaultResource( InternalIndex idx ) - => _defaultResources[ idx.Value ]; + => _lists[ idx.Value ].DefaultResource; public CharacterUtility() { @@ -60,30 +58,25 @@ public unsafe class CharacterUtility : IDisposable // We store the default data of the resources so we can always restore them. private void LoadDefaultResources( object _ ) { - var missingCount = 0; if( Address == null ) { return; } + var anyMissing = false; for( var i = 0; i < RelevantIndices.Length; ++i ) { - if( _defaultResources[ i ].Size == 0 ) + var list = _lists[ i ]; + if( !list.Ready ) { - var resource = Address->Resource( RelevantIndices[i] ); - var data = resource->GetData(); - if( data.Data != IntPtr.Zero && data.Length != 0 ) - { - _defaultResources[ i ] = data; - } - else - { - ++missingCount; - } + var resource = Address->Resource( RelevantIndices[ i ] ); + var (data, length) = resource->GetData(); + list.SetDefaultResource( data, length ); + anyMissing |= !_lists[ i ].Ready; } } - if( missingCount == 0 ) + if( !anyMissing ) { Ready = true; LoadingFinished.Invoke(); @@ -91,51 +84,27 @@ public unsafe class CharacterUtility : IDisposable } } - // Set the data of one of the stored resources to a given pointer and length. - public bool SetResource( Structs.CharacterUtility.Index resourceIdx, IntPtr data, int length ) + public List.MetaReverter SetResource( Structs.CharacterUtility.Index resourceIdx, IntPtr data, int length ) { - if( !Ready ) - { - Penumbra.Log.Error( $"Can not set resource {resourceIdx}: CharacterUtility not ready yet." ); - return false; - } - - var resource = Address->Resource( resourceIdx ); - var ret = resource->SetData( data, length ); - Penumbra.Log.Verbose( $"Set resource {resourceIdx} to 0x{( ulong )data:X} ({length} bytes)."); - return ret; + var idx = ReverseIndices[ ( int )resourceIdx ]; + var list = _lists[ idx.Value ]; + return list.SetResource( data, length ); } - // Reset the data of one of the stored resources to its default values. - public void ResetResource( Structs.CharacterUtility.Index resourceIdx ) + public List.MetaReverter ResetResource( Structs.CharacterUtility.Index resourceIdx ) { - if( !Ready ) - { - Penumbra.Log.Error( $"Can not reset {resourceIdx}: CharacterUtility not ready yet." ); - return; - } - - var (data, length) = DefaultResource( resourceIdx); - var resource = Address->Resource( resourceIdx ); - Penumbra.Log.Verbose( $"Reset resource {resourceIdx} to default at 0x{(ulong)data:X} ({length} bytes)."); - resource->SetData( data, length ); + var idx = ReverseIndices[ ( int )resourceIdx ]; + var list = _lists[ idx.Value ]; + return list.ResetResource(); } // Return all relevant resources to the default resource. public void ResetAll() { - if( !Ready ) + foreach( var list in _lists ) { - Penumbra.Log.Error( "Can not reset all resources: CharacterUtility not ready yet." ); - return; + list.Dispose(); } - - foreach( var idx in RelevantIndices ) - { - ResetResource( idx ); - } - - Penumbra.Log.Debug( "Reset all CharacterUtility resources to default." ); } public void Dispose()