diff --git a/Penumbra/Configuration.cs b/Penumbra/Configuration.cs index a59ab245..fbfd7ddc 100644 --- a/Penumbra/Configuration.cs +++ b/Penumbra/Configuration.cs @@ -35,7 +35,6 @@ public class Configuration : IPluginConfiguration public int WaitFrames { get; set; } = 30; public string ModDirectory { get; set; } = string.Empty; - public string TempDirectory { get; set; } = string.Empty; public string CurrentCollection { get; set; } = "Default"; public string DefaultCollection { get; set; } = "Default"; diff --git a/Penumbra/Importer/TexToolsMeta.cs b/Penumbra/Importer/TexToolsMeta.cs index 4dc10b26..8c9e418f 100644 --- a/Penumbra/Importer/TexToolsMeta.cs +++ b/Penumbra/Importer/TexToolsMeta.cs @@ -256,11 +256,10 @@ public class TexToolsMeta ushort i = 0; if( info.PrimaryType is ObjectType.Equipment or ObjectType.Accessory ) { + // TODO check against default. foreach( var value in values ) { - ImcEntry def; - if( !value.Equals( def ) ) - ImcManipulations.Add(new ImcManipulation(info.EquipSlot, i, info.PrimaryId, value) ); + ImcManipulations.Add(new ImcManipulation(info.EquipSlot, i, info.PrimaryId, value) ); ++i; } } @@ -268,55 +267,64 @@ public class TexToolsMeta { foreach( var value in values ) { - ImcEntry def; - if( !value.Equals( def ) ) - ImcManipulations.Add( new ImcManipulation( info.PrimaryType, info.SecondaryType, info.PrimaryId, info.SecondaryId, i, value ) ); + ImcManipulations.Add( new ImcManipulation( info.PrimaryType, info.SecondaryType, info.PrimaryId, info.SecondaryId, i, value ) ); ++i; } } } + private static string ReadNullTerminated( BinaryReader reader ) + { + var builder = new System.Text.StringBuilder(); + for( var c = reader.ReadChar(); c != 0; c = reader.ReadChar() ) + { + builder.Append( c ); + } + + return builder.ToString(); + } + public TexToolsMeta( byte[] data ) { try { - //using var reader = new BinaryReader( new MemoryStream( data ) ); - //Version = reader.ReadUInt32(); - //FilePath = ReadNullTerminated( reader ); - //var metaInfo = new Info( FilePath ); - //var numHeaders = reader.ReadUInt32(); - //var headerSize = reader.ReadUInt32(); - //var headerStart = reader.ReadUInt32(); - //reader.BaseStream.Seek( headerStart, SeekOrigin.Begin ); - // - //List< (MetaType type, uint offset, int size) > entries = new(); - //for( var i = 0; i < numHeaders; ++i ) - //{ - // var currentOffset = reader.BaseStream.Position; - // var type = ( MetaType )reader.ReadUInt32(); - // var offset = reader.ReadUInt32(); - // var size = reader.ReadInt32(); - // entries.Add( ( type, offset, size ) ); - // reader.BaseStream.Seek( currentOffset + headerSize, SeekOrigin.Begin ); - //} - // - //byte[]? ReadEntry( MetaType type ) - //{ - // var idx = entries.FindIndex( t => t.type == type ); - // if( idx < 0 ) - // { - // return null; - // } - // - // reader.BaseStream.Seek( entries[ idx ].offset, SeekOrigin.Begin ); - // return reader.ReadBytes( entries[ idx ].size ); - //} - // - //DeserializeEqpEntry( metaInfo, ReadEntry( MetaManipulation.Type.Eqp ) ); - //DeserializeGmpEntry( metaInfo, ReadEntry( MetaManipulation.Type.Gmp ) ); - //DeserializeEqdpEntries( metaInfo, ReadEntry( MetaManipulation.Type.Eqdp ) ); - //DeserializeEstEntries( metaInfo, ReadEntry( MetaManipulation.Type.Est ) ); - //DeserializeImcEntries( metaInfo, ReadEntry( MetaManipulation.Type.Imc ) ); + using var reader = new BinaryReader( new MemoryStream( data ) ); + Version = reader.ReadUInt32(); + FilePath = ReadNullTerminated( reader ); + var metaInfo = new Info( FilePath ); + var numHeaders = reader.ReadUInt32(); + var headerSize = reader.ReadUInt32(); + var headerStart = reader.ReadUInt32(); + reader.BaseStream.Seek( headerStart, SeekOrigin.Begin ); + + List< (MetaManipulation.Type type, uint offset, int size) > entries = new(); + for( var i = 0; i < numHeaders; ++i ) + { + var currentOffset = reader.BaseStream.Position; + var type = ( MetaManipulation.Type )reader.ReadUInt32(); + var offset = reader.ReadUInt32(); + var size = reader.ReadInt32(); + entries.Add( ( type, offset, size ) ); + reader.BaseStream.Seek( currentOffset + headerSize, SeekOrigin.Begin ); + } + + byte[]? ReadEntry( MetaManipulation.Type type ) + { + var idx = entries.FindIndex( t => t.type == type ); + if( idx < 0 ) + { + return null; + } + + reader.BaseStream.Seek( entries[ idx ].offset, SeekOrigin.Begin ); + return reader.ReadBytes( entries[ idx ].size ); + } + + DeserializeEqpEntry( metaInfo, ReadEntry( MetaManipulation.Type.Eqp ) ); + DeserializeGmpEntry( metaInfo, ReadEntry( MetaManipulation.Type.Gmp ) ); + DeserializeEqdpEntries( metaInfo, ReadEntry( MetaManipulation.Type.Eqdp ) ); + DeserializeEstEntries( metaInfo, ReadEntry( MetaManipulation.Type.Est ) ); + DeserializeImcEntries( metaInfo, ReadEntry( MetaManipulation.Type.Imc ) ); } catch( Exception e ) { @@ -362,28 +370,35 @@ public class TexToolsMeta return Invalid; } - //if( gender == 1 ) - //{ - // ret.AddIfNotDefault( MetaManipulation.Rsp( subRace, RspAttribute.FemaleMinSize, br.ReadSingle() ) ); - // ret.AddIfNotDefault( MetaManipulation.Rsp( subRace, RspAttribute.FemaleMaxSize, br.ReadSingle() ) ); - // ret.AddIfNotDefault( MetaManipulation.Rsp( subRace, RspAttribute.FemaleMinTail, br.ReadSingle() ) ); - // ret.AddIfNotDefault( MetaManipulation.Rsp( subRace, RspAttribute.FemaleMaxTail, br.ReadSingle() ) ); - // - // ret.AddIfNotDefault( MetaManipulation.Rsp( subRace, RspAttribute.BustMinX, br.ReadSingle() ) ); - // ret.AddIfNotDefault( MetaManipulation.Rsp( subRace, RspAttribute.BustMinY, br.ReadSingle() ) ); - // ret.AddIfNotDefault( MetaManipulation.Rsp( subRace, RspAttribute.BustMinZ, br.ReadSingle() ) ); - // ret.AddIfNotDefault( MetaManipulation.Rsp( subRace, RspAttribute.BustMaxX, br.ReadSingle() ) ); - // ret.AddIfNotDefault( MetaManipulation.Rsp( subRace, RspAttribute.BustMaxY, br.ReadSingle() ) ); - // ret.AddIfNotDefault( MetaManipulation.Rsp( subRace, RspAttribute.BustMaxZ, br.ReadSingle() ) ); - //} - //else - //{ - // ret.AddIfNotDefault( MetaManipulation.Rsp( subRace, RspAttribute.MaleMinSize, br.ReadSingle() ) ); - // ret.AddIfNotDefault( MetaManipulation.Rsp( subRace, RspAttribute.MaleMaxSize, br.ReadSingle() ) ); - // ret.AddIfNotDefault( MetaManipulation.Rsp( subRace, RspAttribute.MaleMinTail, br.ReadSingle() ) ); - // ret.AddIfNotDefault( MetaManipulation.Rsp( subRace, RspAttribute.MaleMaxTail, br.ReadSingle() ) ); - //} - // + void Add( RspAttribute attribute, float value ) + { + var def = CmpFile.GetDefault( subRace, attribute ); + if (value != def) + ret!.RspManipulations.Add(new RspManipulation(subRace, attribute, value)); + } + + if( gender == 1 ) + { + Add(RspAttribute.FemaleMinSize, br.ReadSingle() ); + Add(RspAttribute.FemaleMaxSize, br.ReadSingle() ); + Add(RspAttribute.FemaleMinTail, br.ReadSingle() ); + Add(RspAttribute.FemaleMaxTail, br.ReadSingle() ); + + Add(RspAttribute.BustMinX, br.ReadSingle() ); + Add(RspAttribute.BustMinY, br.ReadSingle() ); + Add(RspAttribute.BustMinZ, br.ReadSingle() ); + Add(RspAttribute.BustMaxX, br.ReadSingle() ); + Add(RspAttribute.BustMaxY, br.ReadSingle() ); + Add(RspAttribute.BustMaxZ, br.ReadSingle() ); + } + else + { + Add(RspAttribute.MaleMinSize, br.ReadSingle() ); + Add(RspAttribute.MaleMaxSize, br.ReadSingle() ); + Add(RspAttribute.MaleMinTail, br.ReadSingle() ); + Add(RspAttribute.MaleMaxTail, br.ReadSingle() ); + } + return ret; } } \ No newline at end of file diff --git a/Penumbra/Interop/CharacterUtility.cs b/Penumbra/Interop/CharacterUtility.cs index f73c654f..9d938f34 100644 --- a/Penumbra/Interop/CharacterUtility.cs +++ b/Penumbra/Interop/CharacterUtility.cs @@ -20,12 +20,20 @@ public unsafe class CharacterUtility : IDisposable public Structs.CharacterUtility* Address => *_characterUtilityAddress; - public (IntPtr Address, int Size)[] DefaultResources = new (IntPtr, int)[Structs.CharacterUtility.NumResources]; + public (IntPtr Address, int Size)[] DefaultResources = new (IntPtr, int)[Structs.CharacterUtility.NumRelevantResources]; public CharacterUtility() { SignatureHelper.Initialise( this ); - LoadDataFilesHook.Enable(); + + if( Address->EqpResource != null ) + { + LoadDefaultResources(); + } + else + { + LoadDataFilesHook.Enable(); + } } // Self-disabling hook to set default resources after loading them. @@ -40,7 +48,7 @@ public unsafe class CharacterUtility : IDisposable // We store the default data of the resources so we can always restore them. private void LoadDefaultResources() { - for( var i = 0; i < Structs.CharacterUtility.NumResources; ++i ) + for( var i = 0; i < Structs.CharacterUtility.NumRelevantResources; ++i ) { var resource = ( Structs.ResourceHandle* )Address->Resources[ i ]; DefaultResources[ i ] = resource->GetData(); @@ -65,7 +73,7 @@ public unsafe class CharacterUtility : IDisposable public void Dispose() { - for( var i = 0; i < Structs.CharacterUtility.NumResources; ++i ) + for( var i = 0; i < Structs.CharacterUtility.NumRelevantResources; ++i ) { ResetResource( i ); } diff --git a/Penumbra/Interop/ResourceLoader.Replacement.cs b/Penumbra/Interop/ResourceLoader.Replacement.cs index 07113232..9ecb85f7 100644 --- a/Penumbra/Interop/ResourceLoader.Replacement.cs +++ b/Penumbra/Interop/ResourceLoader.Replacement.cs @@ -5,10 +5,7 @@ using Dalamud.Logging; using Dalamud.Utility.Signatures; using FFXIVClientStructs.FFXIV.Client.System.Resource; using Penumbra.GameData.ByteString; -using Penumbra.GameData.Util; using Penumbra.Interop.Structs; -using Penumbra.Mods; -using Penumbra.Util; using FileMode = Penumbra.Interop.Structs.FileMode; using ResourceHandle = FFXIVClientStructs.FFXIV.Client.System.Resource.Handle.ResourceHandle; @@ -123,6 +120,10 @@ public unsafe partial class ResourceLoader ret = ReadSqPackHook.Original( resourceManager, fileDescriptor, priority, isSync ); FileLoaded?.Invoke( gamePath.Path, ret != 0, false ); } + else if( ResourceLoadCustomization != null && gamePath.Path[0] == (byte) '|' ) + { + ret = ResourceLoadCustomization.Invoke( gamePath, resourceManager, fileDescriptor, priority, isSync ); + } else { // Specify that we are loading unpacked files from the drive. @@ -150,11 +151,18 @@ public unsafe partial class ResourceLoader return ret; } + // Customize file loading for any GamePaths that start with "|". + public delegate byte ResourceLoadCustomizationDelegate( Utf8GamePath gamePath, ResourceManager* resourceManager, + SeFileDescriptor* fileDescriptor, int priority, bool isSync ); + + public ResourceLoadCustomizationDelegate? ResourceLoadCustomization; + + // Use the default method of path replacement. public static (FullPath?, object?) DefaultReplacer( Utf8GamePath path ) { var resolved = Penumbra.ModManager.ResolveSwappedOrReplacementPath( path ); - return( resolved, null ); + return ( resolved, null ); } private void DisposeHooks() diff --git a/Penumbra/Interop/Structs/CharacterUtility.cs b/Penumbra/Interop/Structs/CharacterUtility.cs index 9164f6e8..62762243 100644 --- a/Penumbra/Interop/Structs/CharacterUtility.cs +++ b/Penumbra/Interop/Structs/CharacterUtility.cs @@ -7,15 +7,16 @@ namespace Penumbra.Interop.Structs; [StructLayout( LayoutKind.Explicit )] public unsafe struct CharacterUtility { - public const int NumResources = 85; - public const int EqpIdx = 0; - public const int GmpIdx = 1; - public const int HumanCmpIdx = 63; - public const int FaceEstIdx = 64; - public const int HairEstIdx = 65; - public const int BodyEstIdx = 66; - public const int HeadEstIdx = 67; - public const int NumEqdpFiles = 2 * 28; + public const int NumResources = 85; + public const int NumRelevantResources = 68; + public const int EqpIdx = 0; + public const int GmpIdx = 1; + public const int HumanCmpIdx = 63; + public const int FaceEstIdx = 64; + public const int HairEstIdx = 65; + public const int BodyEstIdx = 66; + public const int HeadEstIdx = 67; + public const int NumEqdpFiles = 2 * 28; public static int EqdpIdx( GenderRace raceCode, bool accessory ) => ( accessory ? 28 : 0 ) diff --git a/Penumbra/Meta/Files/EqpGmpFile.cs b/Penumbra/Meta/Files/EqpGmpFile.cs index 62aff162..5b6d6479 100644 --- a/Penumbra/Meta/Files/EqpGmpFile.cs +++ b/Penumbra/Meta/Files/EqpGmpFile.cs @@ -55,7 +55,7 @@ public unsafe class ExpandedEqpGmpBase : MetaBaseFile { var ptr = ( byte* )DefaultData.Data; var controlBlock = *( ulong* )ptr; - *( ulong* )ptr = ulong.MaxValue; + *( ulong* )Data = ulong.MaxValue; for( var i = 0; i < 64; ++i ) { var collapsed = ( ( controlBlock >> i ) & 1 ) == 0; diff --git a/Penumbra/Meta/Files/ImcFile.cs b/Penumbra/Meta/Files/ImcFile.cs index 36576d00..1c23dd32 100644 --- a/Penumbra/Meta/Files/ImcFile.cs +++ b/Penumbra/Meta/Files/ImcFile.cs @@ -1,19 +1,21 @@ using System; using System.Numerics; using Dalamud.Logging; +using Dalamud.Memory; using Penumbra.GameData.ByteString; using Penumbra.GameData.Enums; using Penumbra.GameData.Util; +using Penumbra.Interop.Structs; namespace Penumbra.Meta.Files; -public struct ImcEntry : IEquatable< ImcEntry > +public readonly struct ImcEntry : IEquatable< ImcEntry > { - public byte MaterialId; - public byte DecalId; - private ushort _attributeAndSound; - public byte VfxId; - public byte MaterialAnimationId; + public readonly byte MaterialId; + public readonly byte DecalId; + private readonly ushort _attributeAndSound; + public readonly byte VfxId; + public readonly byte MaterialAnimationId; public ushort AttributeMask => ( ushort )( _attributeAndSound & 0x3FF ); @@ -163,4 +165,26 @@ public unsafe class ImcFile : MetaBaseFile Functions.MemCpyUnchecked( Data, ptr, file.Data.Length ); } } + + public void Replace( ResourceHandle* resource ) + { + var (data, length) = resource->GetData(); + if( data == IntPtr.Zero ) + { + return; + } + + var requiredLength = ActualLength; + if( length >= requiredLength ) + { + Functions.MemCpyUnchecked( ( void* )data, Data, requiredLength ); + Functions.MemSet( ( byte* )data + requiredLength, 0, length - requiredLength ); + return; + } + + MemoryHelper.GameFree( ref data, ( ulong )length ); + var file = ( byte* )MemoryHelper.GameAllocateDefault( ( ulong )requiredLength ); + Functions.MemCpyUnchecked( file, Data, requiredLength ); + resource->SetData( ( IntPtr )file, requiredLength ); + } } \ No newline at end of file diff --git a/Penumbra/Meta/Files/MetaBaseFile.cs b/Penumbra/Meta/Files/MetaBaseFile.cs index 1d82e224..304f0f09 100644 --- a/Penumbra/Meta/Files/MetaBaseFile.cs +++ b/Penumbra/Meta/Files/MetaBaseFile.cs @@ -1,5 +1,6 @@ using System; using System.Runtime.InteropServices; +using Dalamud.Memory; namespace Penumbra.Meta.Files; @@ -23,14 +24,15 @@ public unsafe class MetaBaseFile : IDisposable protected void AllocateData( int length ) { Length = length; - Data = ( byte* )Marshal.AllocHGlobal( length ); + Data = ( byte* )MemoryHelper.GameAllocateDefault( ( ulong )length ); ; GC.AddMemoryPressure( length ); } // Free memory. protected void ReleaseUnmanagedResources() { - Marshal.FreeHGlobal( ( IntPtr )Data ); + var ptr = ( IntPtr )Data; + MemoryHelper.GameFree( ref ptr, (ulong) Length ); GC.RemoveMemoryPressure( Length ); Length = 0; Data = null; diff --git a/Penumbra/Meta/Files/MetaFilenames.cs b/Penumbra/Meta/Files/MetaFilenames.cs deleted file mode 100644 index 06360afe..00000000 --- a/Penumbra/Meta/Files/MetaFilenames.cs +++ /dev/null @@ -1,83 +0,0 @@ -using System; -using Penumbra.GameData.Enums; -using Penumbra.GameData.Util; - -namespace Penumbra.Meta.Files -{ - // Contains all filenames for meta changes depending on their parameters. - public static class MetaFileNames - { - public static GamePath Eqp() - => GamePath.GenerateUnchecked( "chara/xls/equipmentparameter/equipmentparameter.eqp" ); - - public static GamePath Gmp() - => GamePath.GenerateUnchecked( "chara/xls/equipmentparameter/gimmickparameter.gmp" ); - - public static GamePath Est( ObjectType type, EquipSlot equip, BodySlot slot ) - { - return type switch - { - ObjectType.Equipment => equip switch - { - EquipSlot.Body => GamePath.GenerateUnchecked( "chara/xls/charadb/extra_top.est" ), - EquipSlot.Head => GamePath.GenerateUnchecked( "chara/xls/charadb/extra_met.est" ), - _ => throw new NotImplementedException(), - }, - ObjectType.Character => slot switch - { - BodySlot.Hair => GamePath.GenerateUnchecked( "chara/xls/charadb/hairskeletontemplate.est" ), - BodySlot.Face => GamePath.GenerateUnchecked( "chara/xls/charadb/faceskeletontemplate.est" ), - _ => throw new NotImplementedException(), - }, - _ => throw new NotImplementedException(), - }; - } - - public static GamePath Imc( ObjectType type, ushort primaryId, ushort secondaryId ) - { - return type switch - { - ObjectType.Accessory => GamePath.GenerateUnchecked( $"chara/accessory/a{primaryId:D4}/a{primaryId:D4}.imc" ), - ObjectType.Equipment => GamePath.GenerateUnchecked( $"chara/equipment/e{primaryId:D4}/e{primaryId:D4}.imc" ), - ObjectType.DemiHuman => GamePath.GenerateUnchecked( - $"chara/demihuman/d{primaryId:D4}/obj/equipment/e{secondaryId:D4}/e{secondaryId:D4}.imc" ), - ObjectType.Monster => GamePath.GenerateUnchecked( - $"chara/monster/m{primaryId:D4}/obj/body/b{secondaryId:D4}/b{secondaryId:D4}.imc" ), - ObjectType.Weapon => GamePath.GenerateUnchecked( - $"chara/weapon/w{primaryId:D4}/obj/body/b{secondaryId:D4}/b{secondaryId:D4}.imc" ), - _ => throw new NotImplementedException(), - }; - } - - public static GamePath Eqdp( ObjectType type, GenderRace gr ) - { - return type switch - { - ObjectType.Accessory => GamePath.GenerateUnchecked( $"chara/xls/charadb/accessorydeformerparameter/c{gr.ToRaceCode()}.eqdp" ), - ObjectType.Equipment => GamePath.GenerateUnchecked( $"chara/xls/charadb/equipmentdeformerparameter/c{gr.ToRaceCode()}.eqdp" ), - _ => throw new NotImplementedException(), - }; - } - - public static GamePath Eqdp( EquipSlot slot, GenderRace gr ) - { - return slot switch - { - EquipSlot.Head => Eqdp( ObjectType.Equipment, gr ), - EquipSlot.Body => Eqdp( ObjectType.Equipment, gr ), - EquipSlot.Feet => Eqdp( ObjectType.Equipment, gr ), - EquipSlot.Hands => Eqdp( ObjectType.Equipment, gr ), - EquipSlot.Legs => Eqdp( ObjectType.Equipment, gr ), - EquipSlot.Neck => Eqdp( ObjectType.Accessory, gr ), - EquipSlot.Ears => Eqdp( ObjectType.Accessory, gr ), - EquipSlot.Wrists => Eqdp( ObjectType.Accessory, gr ), - EquipSlot.LFinger => Eqdp( ObjectType.Accessory, gr ), - EquipSlot.RFinger => Eqdp( ObjectType.Accessory, gr ), - _ => throw new NotImplementedException(), - }; - } - - public static GamePath Cmp() - => GamePath.GenerateUnchecked( "chara/xls/charamake/human.cmp" ); - } -} \ No newline at end of file diff --git a/Penumbra/Meta/Manipulations/ImcManipulation.cs b/Penumbra/Meta/Manipulations/ImcManipulation.cs index 287708a0..42a19a22 100644 --- a/Penumbra/Meta/Manipulations/ImcManipulation.cs +++ b/Penumbra/Meta/Manipulations/ImcManipulation.cs @@ -1,8 +1,8 @@ using System; using System.Runtime.InteropServices; +using Penumbra.GameData.ByteString; using Penumbra.GameData.Enums; using Penumbra.Meta.Files; -using ImcFile = Lumina.Data.Files.ImcFile; namespace Penumbra.Meta.Manipulations; @@ -58,4 +58,33 @@ public readonly struct ImcManipulation : IEquatable< ImcManipulation > public override int GetHashCode() => HashCode.Combine( PrimaryId, Variant, SecondaryId, ( int )ObjectType, ( int )EquipSlot, ( int )BodySlot ); + + public Utf8GamePath GamePath() + { + return ObjectType switch + { + ObjectType.Accessory => Utf8GamePath.FromString( $"chara/accessory/a{PrimaryId:D4}/a{PrimaryId:D4}.imc", out var p ) + ? p + : Utf8GamePath.Empty, + ObjectType.Equipment => Utf8GamePath.FromString( $"chara/equipment/e{PrimaryId:D4}/e{PrimaryId:D4}.imc", out var p ) + ? p + : Utf8GamePath.Empty, + ObjectType.DemiHuman => Utf8GamePath.FromString( + $"chara/demihuman/d{PrimaryId:D4}/obj/equipment/e{SecondaryId:D4}/e{SecondaryId:D4}.imc", out var p ) + ? p + : Utf8GamePath.Empty, + ObjectType.Monster => Utf8GamePath.FromString( $"chara/monster/m{PrimaryId:D4}/obj/body/b{SecondaryId:D4}/b{SecondaryId:D4}.imc", + out var p ) + ? p + : Utf8GamePath.Empty, + ObjectType.Weapon => Utf8GamePath.FromString( $"chara/weapon/w{PrimaryId:D4}/obj/body/b{SecondaryId:D4}/b{SecondaryId:D4}.imc", + out var p ) + ? p + : Utf8GamePath.Empty, + _ => throw new NotImplementedException(), + }; + } + + public bool Apply( ImcFile file ) + => file.SetEntry( ImcFile.PartIndex( EquipSlot ), Variant, Entry ); } \ No newline at end of file diff --git a/Penumbra/Meta/Manipulations/MetaManipulation.cs b/Penumbra/Meta/Manipulations/MetaManipulation.cs index f33e7a97..af07d17b 100644 --- a/Penumbra/Meta/Manipulations/MetaManipulation.cs +++ b/Penumbra/Meta/Manipulations/MetaManipulation.cs @@ -1,10 +1,11 @@ using System; using System.Runtime.InteropServices; +using Penumbra.GameData.Util; namespace Penumbra.Meta.Manipulations; [StructLayout( LayoutKind.Explicit, Pack = 1, Size = 16 )] -public readonly struct MetaManipulation : IEquatable< MetaManipulation > +public readonly struct MetaManipulation : IEquatable< MetaManipulation >, IComparable { public enum Type : byte { @@ -106,4 +107,10 @@ public readonly struct MetaManipulation : IEquatable< MetaManipulation > Type.Imc => Imc.GetHashCode(), _ => throw new ArgumentOutOfRangeException(), }; + + public unsafe int CompareTo( MetaManipulation other ) + { + fixed(MetaManipulation* lhs = &this) + return Functions.MemCmpUnchecked(lhs, &other, sizeof(MetaManipulation)); + } } \ No newline at end of file diff --git a/Penumbra/Meta/MetaCollection.cs b/Penumbra/Meta/MetaCollection.cs index a0ec0027..2c09aba3 100644 --- a/Penumbra/Meta/MetaCollection.cs +++ b/Penumbra/Meta/MetaCollection.cs @@ -93,14 +93,14 @@ public class MetaCollection return false; } - if( option.Value.Any( manip => defaultFiles.CheckAgainstDefault( manip ) ) ) - { - return false; - } + //if( option.Value.Any( manip => defaultFiles.CheckAgainstDefault( manip ) ) ) + //{ + // return false; + //} } - } + } // TODO - return DefaultData.All( manip => !defaultFiles.CheckAgainstDefault( manip ) ); + return true; //DefaultData.All( manip => !defaultFiles.CheckAgainstDefault( manip ) ); } // Re-sort all manipulations. @@ -117,31 +117,33 @@ public class MetaCollection // Creates the option group and the option if necessary. private void AddMeta( string group, string option, TexToolsMeta meta ) { - if( meta.Manipulations.Count == 0 ) - { - return; - } + var manipulations = meta.EqpManipulations.Select( m => new MetaManipulation( m ) ) + .Concat( meta.EqdpManipulations.Select( m => new MetaManipulation( m ) ) ) + .Concat( meta.EstManipulations.Select( m => new MetaManipulation( m ) ) ) + .Concat( meta.GmpManipulations.Select( m => new MetaManipulation( m ) ) ) + .Concat( meta.RspManipulations.Select( m => new MetaManipulation( m ) ) ) + .Concat( meta.ImcManipulations.Select( m => new MetaManipulation( m ) ) ).ToList(); if( group.Length == 0 ) { - DefaultData.AddRange( meta.Manipulations ); + DefaultData.AddRange( manipulations ); } else if( option.Length == 0 ) { } else if( !GroupData.TryGetValue( group, out var options ) ) { - GroupData.Add( group, new Dictionary< string, List< MetaManipulation > >() { { option, meta.Manipulations.ToList() } } ); + GroupData.Add( group, new Dictionary< string, List< MetaManipulation > >() { { option, manipulations } } ); } else if( !options.TryGetValue( option, out var list ) ) { - options.Add( option, meta.Manipulations.ToList() ); + options.Add( option, manipulations ); } else { - list.AddRange( meta.Manipulations ); + list.AddRange( manipulations ); } - Count += meta.Manipulations.Count; + Count += manipulations.Count; } // Update the whole meta collection by reading all TexTools .meta files in a mod directory anew, @@ -160,7 +162,7 @@ public class MetaCollection _ => TexToolsMeta.Invalid, }; - if( metaData.FilePath == string.Empty || metaData.Manipulations.Count == 0 ) + if( metaData.FilePath == string.Empty ) { continue; } diff --git a/Penumbra/Meta/MetaManager.cs b/Penumbra/Meta/MetaManager.cs index 1c56b35b..d9519863 100644 --- a/Penumbra/Meta/MetaManager.cs +++ b/Penumbra/Meta/MetaManager.cs @@ -2,26 +2,19 @@ using System; using System.Collections.Generic; using System.IO; using System.Linq; -using Dalamud.Logging; -using Lumina.Data.Files; +using FFXIVClientStructs.FFXIV.Client.System.Resource; using Penumbra.GameData.ByteString; using Penumbra.GameData.Enums; +using Penumbra.Interop; using Penumbra.Interop.Structs; using Penumbra.Meta.Files; using Penumbra.Meta.Manipulations; -using Penumbra.Util; +using Penumbra.Mods; +using CharacterUtility = Penumbra.Interop.Structs.CharacterUtility; +using ImcFile = Penumbra.Meta.Files.ImcFile; namespace Penumbra.Meta; -public struct TemporaryImcFile : IDisposable -{ - - public void Dispose() - { - - } -} - public class MetaManager2 : IDisposable { public readonly List< MetaBaseFile > ChangedData = new(7 + CharacterUtility.NumEqdpFiles); @@ -42,7 +35,51 @@ public class MetaManager2 : IDisposable public readonly Dictionary< EqdpManipulation, Mod.Mod > EqdpManipulations = new(); public readonly Dictionary< ImcManipulation, Mod.Mod > ImcManipulations = new(); - public readonly List< TemporaryImcFile > ImcFiles = new(); + public readonly Dictionary< Utf8GamePath, ImcFile > ImcFiles = new(); + + private readonly ModCollection _collection; + + public unsafe void SetFiles() + { + foreach( var file in ChangedData ) + { + Penumbra.CharacterUtility.SetResource( file.Index, ( IntPtr )file.Data, file.Length ); + } + } + + public bool TryGetValue( MetaManipulation manip, out Mod.Mod? mod ) + { + mod = manip.ManipulationType switch + { + MetaManipulation.Type.Eqp => EqpManipulations.TryGetValue( manip.Eqp, out var m ) ? m : null, + MetaManipulation.Type.Gmp => GmpManipulations.TryGetValue( manip.Gmp, out var m ) ? m : null, + MetaManipulation.Type.Eqdp => EqdpManipulations.TryGetValue( manip.Eqdp, out var m ) ? m : null, + MetaManipulation.Type.Est => EstManipulations.TryGetValue( manip.Est, out var m ) ? m : null, + MetaManipulation.Type.Rsp => RspManipulations.TryGetValue( manip.Rsp, out var m ) ? m : null, + MetaManipulation.Type.Imc => ImcManipulations.TryGetValue( manip.Imc, out var m ) ? m : null, + _ => throw new ArgumentOutOfRangeException(), + }; + return mod != null; + } + + public int Count + => ImcManipulations.Count + + EqdpManipulations.Count + + RspManipulations.Count + + GmpManipulations.Count + + EstManipulations.Count + + EqpManipulations.Count; + + public MetaManager2( ModCollection collection ) + => _collection = collection; + + public void ApplyImcFiles( Dictionary< Utf8GamePath, FullPath > resolvedFiles ) + { + foreach( var path in ImcFiles.Keys ) + { + resolvedFiles[ path ] = CreateImcPath( path ); + } + } public void ResetEqp() { @@ -95,10 +132,21 @@ public class MetaManager2 : IDisposable EqdpManipulations.Clear(); } + private FullPath CreateImcPath( Utf8GamePath path ) + { + var d = new DirectoryInfo( $":{_collection.Name}/" ); + return new FullPath( d, new Utf8RelPath( path ) ); + } + public void ResetImc() { - foreach( var file in ImcFiles ) + foreach( var (path, file) in ImcFiles ) + { + _collection.Cache?.ResolvedFiles.Remove( path ); + path.Dispose(); file.Dispose(); + } + ImcFiles.Clear(); ImcManipulations.Clear(); } @@ -154,7 +202,6 @@ public class MetaManager2 : IDisposable } } - public bool ApplyMod( EqpManipulation m, Mod.Mod mod ) { if( !EqpManipulations.TryAdd( m, mod ) ) @@ -247,16 +294,63 @@ public class MetaManager2 : IDisposable return true; } - public bool ApplyMod( ImcManipulation m, Mod.Mod mod ) + public unsafe bool ApplyMod( ImcManipulation m, Mod.Mod mod ) { + const uint imcExt = 0x00696D63; + if( !ImcManipulations.TryAdd( m, mod ) ) { return false; } + var path = m.GamePath(); + if( !ImcFiles.TryGetValue( path, out var file ) ) + { + file = new ImcFile( path ); + } + + if( !m.Apply( file ) ) + { + return false; + } + + ImcFiles[ path ] = file; + var fullPath = CreateImcPath( path ); + if( _collection.Cache != null ) + { + _collection.Cache.ResolvedFiles[ path ] = fullPath; + } + + var resource = ResourceLoader.FindResource( ResourceCategory.Chara, imcExt, ( uint )path.Path.Crc32 ); + if( resource != null ) + { + file.Replace( ( ResourceHandle* )resource ); + } + return true; } + public static unsafe byte ImcHandler( Utf8GamePath gamePath, ResourceManager* resourceManager, + SeFileDescriptor* fileDescriptor, int priority, bool isSync ) + { + var split = gamePath.Path.Split( ( byte )'|', 2, true ); + fileDescriptor->ResourceHandle->FileNameData = split[ 1 ].Path; + fileDescriptor->ResourceHandle->FileNameLength = split[ 1 ].Length; + + var ret = Penumbra.ResourceLoader.ReadSqPackHook.Original( resourceManager, fileDescriptor, priority, isSync ); + if( Penumbra.ModManager.Collections.Collections.TryGetValue( split[ 0 ].ToString(), out var collection ) + && collection.Cache != null + && collection.Cache.MetaManipulations.ImcFiles.TryGetValue( + Utf8GamePath.FromSpan( split[ 1 ].Span, out var p, false ) ? p : Utf8GamePath.Empty, out var file ) ) + { + file.Replace( fileDescriptor->ResourceHandle ); + } + + fileDescriptor->ResourceHandle->FileNameData = gamePath.Path.Path; + fileDescriptor->ResourceHandle->FileNameLength = gamePath.Path.Length; + return ret; + } + public bool ApplyMod( MetaManipulation m, Mod.Mod mod ) { return m.ManipulationType switch @@ -267,133 +361,7 @@ public class MetaManager2 : IDisposable MetaManipulation.Type.Est => ApplyMod( m.Est, mod ), MetaManipulation.Type.Rsp => ApplyMod( m.Rsp, mod ), MetaManipulation.Type.Imc => ApplyMod( m.Imc, mod ), - _ => throw new ArgumentOutOfRangeException() + _ => throw new ArgumentOutOfRangeException(), }; } -} - -public class MetaManager : IDisposable -{ - internal class FileInformation - { - public readonly object Data; - public bool Changed; - public FullPath? CurrentFile; - public byte[] ByteData = Array.Empty< byte >(); - - public FileInformation( object data ) - => Data = data; - - public void Write( DirectoryInfo dir, Utf8GamePath originalPath ) - { - ByteData = Data switch - { - ImcFile imc => imc.WriteBytes(), - _ => throw new NotImplementedException(), - }; - DisposeFile( CurrentFile ); - CurrentFile = new FullPath( TempFile.WriteNew( dir, ByteData, $"_{originalPath.Filename()}" ) ); - Changed = false; - } - } - - public const string TmpDirectory = "penumbrametatmp"; - - private readonly DirectoryInfo _dir; - private readonly Dictionary< Utf8GamePath, FullPath > _resolvedFiles; - - private readonly Dictionary< MetaManipulation, Mod.Mod > _currentManipulations = new(); - private readonly Dictionary< Utf8GamePath, FileInformation > _currentFiles = new(); - - public IEnumerable< (MetaManipulation, Mod.Mod) > Manipulations - => _currentManipulations.Select( kvp => ( kvp.Key, kvp.Value ) ); - - public IEnumerable< (Utf8GamePath, FullPath) > Files - => _currentFiles.Where( kvp => kvp.Value.CurrentFile != null ) - .Select( kvp => ( kvp.Key, kvp.Value.CurrentFile!.Value ) ); - - public int Count - => _currentManipulations.Count; - - public bool TryGetValue( MetaManipulation manip, out Mod.Mod mod ) - => _currentManipulations.TryGetValue( manip, out mod! ); - - public byte[] EqpData = Array.Empty< byte >(); - - private static void DisposeFile( FullPath? file ) - { - if( !( file?.Exists ?? false ) ) - { - return; - } - - try - { - File.Delete( file.Value.FullName ); - } - catch( Exception e ) - { - PluginLog.Error( $"Could not delete temporary file \"{file.Value.FullName}\":\n{e}" ); - } - } - - public void Reset( bool reload = true ) - { - foreach( var file in _currentFiles ) - { - _resolvedFiles.Remove( file.Key ); - DisposeFile( file.Value.CurrentFile ); - } - - _currentManipulations.Clear(); - _currentFiles.Clear(); - ClearDirectory(); - if( reload ) - { - Penumbra.ResidentResources.Reload(); - } - } - - public void Dispose() - => Reset(); - - private static void ClearDirectory( DirectoryInfo modDir ) - { - modDir.Refresh(); - if( modDir.Exists ) - { - try - { - Directory.Delete( modDir.FullName, true ); - } - catch( Exception e ) - { - PluginLog.Error( $"Could not clear temporary metafile directory \"{modDir.FullName}\":\n{e}" ); - } - } - } - - private void ClearDirectory() - => ClearDirectory( _dir ); - - public MetaManager( string name, Dictionary< Utf8GamePath, FullPath > resolvedFiles, DirectoryInfo tempDir ) - { - _resolvedFiles = resolvedFiles; - _dir = new DirectoryInfo( Path.Combine( tempDir.FullName, name.ReplaceBadXivSymbols() ) ); - ClearDirectory(); - } - - public void WriteNewFiles() - { - if( _currentFiles.Any() ) - { - Directory.CreateDirectory( _dir.FullName ); - } - - foreach( var (key, value) in _currentFiles.Where( kvp => kvp.Value.Changed ) ) - { - value.Write( _dir, key ); - _resolvedFiles[ key ] = value.CurrentFile!.Value; - } - } } \ No newline at end of file diff --git a/Penumbra/Mods/CollectionManager.cs b/Penumbra/Mods/CollectionManager.cs index f614fef3..82874d87 100644 --- a/Penumbra/Mods/CollectionManager.cs +++ b/Penumbra/Mods/CollectionManager.cs @@ -67,15 +67,9 @@ public class CollectionManager public void RecreateCaches() { - if( !_manager.TempWritable ) - { - PluginLog.Error( "No temporary directory available." ); - return; - } - foreach( var collection in Collections.Values.Where( c => c.Cache != null ) ) { - collection.CreateCache( _manager.TempPath, _manager.StructuredMods.AllMods( _manager.Config.SortFoldersFirst ) ); + collection.CreateCache( _manager.StructuredMods.AllMods( _manager.Config.SortFoldersFirst ) ); } CreateNecessaryCaches(); @@ -176,15 +170,9 @@ public class CollectionManager private void AddCache( ModCollection collection ) { - if( !_manager.TempWritable ) - { - PluginLog.Error( "No tmp directory available." ); - return; - } - if( collection.Cache == null && collection.Name != string.Empty ) { - collection.CreateCache( _manager.TempPath, _manager.StructuredMods.AllMods( _manager.Config.SortFoldersFirst ) ); + collection.CreateCache( _manager.StructuredMods.AllMods( _manager.Config.SortFoldersFirst ) ); } } @@ -289,7 +277,7 @@ public class CollectionManager CurrentCollection = Collections[ ModCollection.DefaultCollection ]; if( CurrentCollection.Cache == null ) { - CurrentCollection.CreateCache( _manager.TempPath, _manager.StructuredMods.AllMods( _manager.Config.SortFoldersFirst ) ); + CurrentCollection.CreateCache( _manager.StructuredMods.AllMods( _manager.Config.SortFoldersFirst ) ); } config.CurrentCollection = ModCollection.DefaultCollection; diff --git a/Penumbra/Mods/ModCollection.cs b/Penumbra/Mods/ModCollection.cs index cfaba390..90a96dd1 100644 --- a/Penumbra/Mods/ModCollection.cs +++ b/Penumbra/Mods/ModCollection.cs @@ -65,9 +65,9 @@ public class ModCollection return removeList.Length > 0; } - public void CreateCache( DirectoryInfo modDirectory, IEnumerable< ModData > data ) + public void CreateCache( IEnumerable< ModData > data ) { - Cache = new ModCollectionCache( Name, modDirectory ); + Cache = new ModCollectionCache( this ); var changedSettings = false; foreach( var mod in data ) { @@ -89,7 +89,7 @@ public class ModCollection Save(); } - CalculateEffectiveFileList( modDirectory, true, false ); + CalculateEffectiveFileList( true, false ); } public void ClearCache() @@ -135,11 +135,11 @@ public class ModCollection } } - public void CalculateEffectiveFileList( DirectoryInfo modDir, bool withMetaManipulations, bool activeCollection ) + public void CalculateEffectiveFileList( bool withMetaManipulations, bool activeCollection ) { PluginLog.Debug( "Recalculating effective file list for {CollectionName} [{WithMetaManipulations}] [{IsActiveCollection}]", Name, withMetaManipulations, activeCollection ); - Cache ??= new ModCollectionCache( Name, modDir ); + Cache ??= new ModCollectionCache( this ); UpdateSettings( false ); Cache.CalculateEffectiveFileList(); if( withMetaManipulations ) diff --git a/Penumbra/Mods/ModCollectionCache.cs b/Penumbra/Mods/ModCollectionCache.cs index d9f4b144..20a7b583 100644 --- a/Penumbra/Mods/ModCollectionCache.cs +++ b/Penumbra/Mods/ModCollectionCache.cs @@ -27,8 +27,7 @@ public class ModCollectionCache private readonly SortedList< string, object? > _changedItems = new(); public readonly Dictionary< Utf8GamePath, FullPath > ResolvedFiles = new(); public readonly HashSet< FullPath > MissingFiles = new(); - public readonly HashSet< ulong > Checksums = new(); - public readonly MetaManager MetaManipulations; + public readonly MetaManager2 MetaManipulations; public IReadOnlyDictionary< string, object? > ChangedItems { @@ -39,8 +38,8 @@ public class ModCollectionCache } } - public ModCollectionCache( string collectionName, DirectoryInfo tempDir ) - => MetaManipulations = new MetaManager( collectionName, ResolvedFiles, tempDir ); + public ModCollectionCache( ModCollection collection ) + => MetaManipulations = new MetaManager2( collection ); private static void ResetFileSeen( int size ) { @@ -73,11 +72,6 @@ public class ModCollectionCache } AddMetaFiles(); - Checksums.Clear(); - foreach( var file in ResolvedFiles ) - { - Checksums.Add( file.Value.Crc64 ); - } } private void SetChangedItems() @@ -89,11 +83,10 @@ public class ModCollectionCache try { - // Skip meta files because IMCs would result in far too many false-positive items, + // Skip IMCs because they would result in far too many false-positive items, // since they are per set instead of per item-slot/item/variant. - var metaFiles = MetaManipulations.Files.Select( p => p.Item1 ).ToHashSet(); var identifier = GameData.GameData.GetIdentifier(); - foreach( var resolved in ResolvedFiles.Keys.Where( file => !metaFiles.Contains( file ) ) ) + foreach( var resolved in ResolvedFiles.Keys.Where( file => !file.Path.EndsWith( 'i', 'm', 'c' ) ) ) { identifier.Identify( _changedItems, resolved.ToGamePath() ); } @@ -265,18 +258,7 @@ public class ModCollectionCache } private void AddMetaFiles() - { - foreach( var (gamePath, file) in MetaManipulations.Files ) - { - if( RegisteredFiles.TryGetValue( gamePath, out var mod ) ) - { - PluginLog.Warning( - $"The meta manipulation file {gamePath} was already completely replaced by {mod.Data.Meta.Name}. This is probably a mistake. Using the custom file {file.FullName}." ); - } - - ResolvedFiles[ gamePath ] = file; - } - } + => MetaManipulations.ApplyImcFiles( ResolvedFiles ); private void AddSwaps( Mod.Mod mod ) { @@ -304,12 +286,12 @@ public class ModCollectionCache { if( !MetaManipulations.TryGetValue( manip, out var oldMod ) ) { - //MetaManipulations.ApplyMod( manip, mod ); + MetaManipulations.ApplyMod( manip, mod ); } else { - mod.Cache.AddConflict( oldMod, manip ); - if( !ReferenceEquals( mod, oldMod ) && mod.Settings.Priority == oldMod.Settings.Priority ) + mod.Cache.AddConflict( oldMod!, manip ); + if( !ReferenceEquals( mod, oldMod ) && mod.Settings.Priority == oldMod!.Settings.Priority ) { oldMod.Cache.AddConflict( mod, manip ); } @@ -319,15 +301,13 @@ public class ModCollectionCache public void UpdateMetaManipulations() { - MetaManipulations.Reset( false ); + MetaManipulations.Reset(); foreach( var mod in AvailableMods.Values.Where( m => m.Settings.Enabled && m.Data.Resources.MetaManipulations.Count > 0 ) ) { mod.Cache.ClearMetaConflicts(); AddManipulations( mod ); } - - MetaManipulations.WriteNewFiles(); } public void RemoveMod( DirectoryInfo basePath ) diff --git a/Penumbra/Mods/ModManager.Directory.cs b/Penumbra/Mods/ModManager.Directory.cs new file mode 100644 index 00000000..b14f4929 --- /dev/null +++ b/Penumbra/Mods/ModManager.Directory.cs @@ -0,0 +1,110 @@ +using System; +using System.Collections.Generic; +using System.IO; +using Dalamud.Logging; +using Penumbra.Mod; + +namespace Penumbra.Mods; + +public partial class ModManagerNew +{ + private readonly List _mods = new(); + public IReadOnlyList Mods + => _mods; + + public void DiscoverMods() + { + //_mods.Clear(); + // + //if( CheckValidity() ) + //{ + // foreach( var modFolder in BasePath.EnumerateDirectories() ) + // { + // var mod = ModData.LoadMod( StructuredMods, modFolder ); + // if( mod == null ) + // { + // continue; + // } + // + // Mods.Add( modFolder.Name, mod ); + // } + // + // SetModStructure(); + //} + // + //Collections.RecreateCaches(); + } +} + +public partial class ModManagerNew +{ + public DirectoryInfo BasePath { get; private set; } = null!; + public bool Valid { get; private set; } + + public event Action< DirectoryInfo >? BasePathChanged; + + public ModManagerNew() + { + InitBaseDirectory( Penumbra.Config.ModDirectory ); + } + + public bool CheckValidity() + { + if( Valid ) + { + Valid = Directory.Exists(BasePath.FullName); + } + + return Valid; + } + + private static (DirectoryInfo, bool) CreateDirectory( string path ) + { + var newDir = new DirectoryInfo( path ); + if( !newDir.Exists ) + { + try + { + Directory.CreateDirectory( newDir.FullName ); + newDir.Refresh(); + } + catch( Exception e ) + { + PluginLog.Error( $"Could not create specified mod directory {newDir.FullName}:\n{e}" ); + return ( newDir, false ); + } + } + + return ( newDir, true ); + } + + private void InitBaseDirectory( string path ) + { + if( path.Length == 0 ) + { + Valid = false; + BasePath = new DirectoryInfo( "." ); + return; + } + + + ( BasePath, Valid ) = CreateDirectory( path ); + + if( Penumbra.Config.ModDirectory != BasePath.FullName ) + { + Penumbra.Config.ModDirectory = BasePath.FullName; + Penumbra.Config.Save(); + } + } + + private void ChangeBaseDirectory( string path ) + { + if( string.Equals( path, Penumbra.Config.ModDirectory, StringComparison.InvariantCultureIgnoreCase ) ) + { + return; + } + + InitBaseDirectory( path ); + BasePathChanged?.Invoke( BasePath ); + } +} \ No newline at end of file diff --git a/Penumbra/Mods/ModManager.cs b/Penumbra/Mods/ModManager.cs index d0b7bf2d..4538fa85 100644 --- a/Penumbra/Mods/ModManager.cs +++ b/Penumbra/Mods/ModManager.cs @@ -4,401 +4,297 @@ using System.IO; using System.Linq; using Dalamud.Logging; using Penumbra.GameData.ByteString; -using Penumbra.GameData.Util; using Penumbra.Meta; using Penumbra.Mod; -namespace Penumbra.Mods +namespace Penumbra.Mods; + +// The ModManager handles the basic mods installed to the mod directory. +// It also contains the CollectionManager that handles all collections. +public class ModManager { - // The ModManager handles the basic mods installed to the mod directory. - // It also contains the CollectionManager that handles all collections. - public class ModManager + public DirectoryInfo BasePath { get; private set; } = null!; + + public Dictionary< string, ModData > Mods { get; } = new(); + public ModFolder StructuredMods { get; } = ModFileSystem.Root; + + public CollectionManager Collections { get; } + + public bool Valid { get; private set; } + + public Configuration Config + => Penumbra.Config; + + public void DiscoverMods( string newDir ) { - public DirectoryInfo BasePath { get; private set; } = null!; - public DirectoryInfo TempPath { get; private set; } = null!; + SetBaseDirectory( newDir, false ); + DiscoverMods(); + } - public Dictionary< string, ModData > Mods { get; } = new(); - public ModFolder StructuredMods { get; } = ModFileSystem.Root; - - public CollectionManager Collections { get; } - - public bool Valid { get; private set; } - public bool TempWritable { get; private set; } - - public Configuration Config - => Penumbra.Config; - - public void DiscoverMods( string newDir ) + private void SetBaseDirectory( string newPath, bool firstTime ) + { + if( !firstTime && string.Equals( newPath, Config.ModDirectory, StringComparison.InvariantCultureIgnoreCase ) ) { - SetBaseDirectory( newDir, false ); - DiscoverMods(); + return; } - private void ClearOldTmpDir() + if( !newPath.Any() ) { - if( !TempWritable ) - { - return; - } - - TempPath.Refresh(); - if( TempPath.Exists ) + Valid = false; + BasePath = new DirectoryInfo( "." ); + } + else + { + var newDir = new DirectoryInfo( newPath ); + if( !newDir.Exists ) { try { - TempPath.Delete( true ); + Directory.CreateDirectory( newDir.FullName ); + newDir.Refresh(); } catch( Exception e ) { - PluginLog.Error( $"Could not delete temporary directory {TempPath.FullName}:\n{e}" ); + PluginLog.Error( $"Could not create specified mod directory {newDir.FullName}:\n{e}" ); } } + + BasePath = newDir; + Valid = true; + if( Config.ModDirectory != BasePath.FullName ) + { + Config.ModDirectory = BasePath.FullName; + Config.Save(); + } + + if( !firstTime ) + { + Collections.RecreateCaches(); + } + } + } + + public ModManager() + { + SetBaseDirectory( Config.ModDirectory, true ); + Collections = new CollectionManager( this ); + } + + private bool SetSortOrderPath( ModData mod, string path ) + { + mod.Move( path ); + var fixedPath = mod.SortOrder.FullPath; + if( !fixedPath.Any() || string.Equals( fixedPath, mod.Meta.Name, StringComparison.InvariantCultureIgnoreCase ) ) + { + Config.ModSortOrder.Remove( mod.BasePath.Name ); + return true; + } + + if( path != fixedPath ) + { + Config.ModSortOrder[ mod.BasePath.Name ] = fixedPath; + return true; + } + + return false; + } + + private void SetModStructure( bool removeOldPaths = false ) + { + var changes = false; + + foreach( var kvp in Config.ModSortOrder.ToArray() ) + { + if( kvp.Value.Any() && Mods.TryGetValue( kvp.Key, out var mod ) ) + { + changes |= SetSortOrderPath( mod, kvp.Value ); + } + else if( removeOldPaths ) + { + changes = true; + Config.ModSortOrder.Remove( kvp.Key ); + } } - private static bool CheckTmpDir( string newPath, out DirectoryInfo tmpDir ) + if( changes ) { - tmpDir = new DirectoryInfo( Path.Combine( newPath, MetaManager.TmpDirectory ) ); - try + Config.Save(); + } + } + + public void DiscoverMods() + { + Mods.Clear(); + BasePath.Refresh(); + + StructuredMods.SubFolders.Clear(); + StructuredMods.Mods.Clear(); + if( Valid && BasePath.Exists ) + { + foreach( var modFolder in BasePath.EnumerateDirectories() ) { - if( tmpDir.Exists ) + var mod = ModData.LoadMod( StructuredMods, modFolder ); + if( mod == null ) { - tmpDir.Delete( true ); - tmpDir.Refresh(); + continue; } - Directory.CreateDirectory( tmpDir.FullName ); - tmpDir.Refresh(); - return true; + Mods.Add( modFolder.Name, mod ); + } + + SetModStructure(); + } + + Collections.RecreateCaches(); + } + + public void DeleteMod( DirectoryInfo modFolder ) + { + modFolder.Refresh(); + if( modFolder.Exists ) + { + try + { + Directory.Delete( modFolder.FullName, true ); } catch( Exception e ) { - PluginLog.Error( $"Could not create temporary directory {tmpDir.FullName}:\n{e}" ); - return false; + PluginLog.Error( $"Could not delete the mod {modFolder.Name}:\n{e}" ); + } + + if( Mods.TryGetValue( modFolder.Name, out var mod ) ) + { + mod.SortOrder.ParentFolder.RemoveMod( mod ); + Mods.Remove( modFolder.Name ); + Collections.RemoveModFromCaches( modFolder ); } } + } - private void SetBaseDirectory( string newPath, bool firstTime ) + public bool AddMod( DirectoryInfo modFolder ) + { + var mod = ModData.LoadMod( StructuredMods, modFolder ); + if( mod == null ) { - if( !firstTime && string.Equals( newPath, Config.ModDirectory, StringComparison.InvariantCultureIgnoreCase ) ) - { - return; - } - - if( !newPath.Any() ) - { - Valid = false; - BasePath = new DirectoryInfo( "." ); - } - else - { - var newDir = new DirectoryInfo( newPath ); - if( !newDir.Exists ) - { - try - { - Directory.CreateDirectory( newDir.FullName ); - newDir.Refresh(); - } - catch( Exception e ) - { - PluginLog.Error( $"Could not create specified mod directory {newDir.FullName}:\n{e}" ); - } - } - - BasePath = newDir; - Valid = true; - if( Config.ModDirectory != BasePath.FullName ) - { - Config.ModDirectory = BasePath.FullName; - Config.Save(); - } - - if( !Config.TempDirectory.Any() ) - { - if( CheckTmpDir( BasePath.FullName, out var newTmpDir ) ) - { - if( !firstTime ) - { - ClearOldTmpDir(); - } - - TempPath = newTmpDir; - TempWritable = true; - } - else - { - TempWritable = false; - } - } - - if( !firstTime ) - { - Collections.RecreateCaches(); - } - } - } - - private void SetTempDirectory( string newPath, bool firstTime ) - { - if( !Valid || !firstTime && string.Equals( newPath, Config.TempDirectory, StringComparison.InvariantCultureIgnoreCase ) ) - { - return; - } - - if( !newPath.Any() && CheckTmpDir( BasePath.FullName, out var newTmpDir ) - || newPath.Any() && CheckTmpDir( newPath, out newTmpDir ) ) - { - if( !firstTime ) - { - ClearOldTmpDir(); - } - - TempPath = newTmpDir; - TempWritable = true; - var newName = newPath.Any() ? TempPath.Parent!.FullName : string.Empty; - if( Config.TempDirectory != newName ) - { - Config.TempDirectory = newName; - Config.Save(); - } - - if( !firstTime ) - { - Collections.RecreateCaches(); - } - } - else - { - TempWritable = false; - } - } - - public void SetTempDirectory( string newPath ) - => SetTempDirectory( newPath, false ); - - public ModManager() - { - SetBaseDirectory( Config.ModDirectory, true ); - SetTempDirectory( Config.TempDirectory, true ); - Collections = new CollectionManager( this ); - } - - private bool SetSortOrderPath( ModData mod, string path ) - { - mod.Move( path ); - var fixedPath = mod.SortOrder.FullPath; - if( !fixedPath.Any() || string.Equals( fixedPath, mod.Meta.Name, StringComparison.InvariantCultureIgnoreCase ) ) - { - Config.ModSortOrder.Remove( mod.BasePath.Name ); - return true; - } - - if( path != fixedPath ) - { - Config.ModSortOrder[ mod.BasePath.Name ] = fixedPath; - return true; - } - return false; } - private void SetModStructure( bool removeOldPaths = false ) + if( Config.ModSortOrder.TryGetValue( mod.BasePath.Name, out var sortOrder ) ) { - var changes = false; - - foreach( var kvp in Config.ModSortOrder.ToArray() ) - { - if( kvp.Value.Any() && Mods.TryGetValue( kvp.Key, out var mod ) ) - { - changes |= SetSortOrderPath( mod, kvp.Value ); - } - else if( removeOldPaths ) - { - changes = true; - Config.ModSortOrder.Remove( kvp.Key ); - } - } - - if( changes ) + if( SetSortOrderPath( mod, sortOrder ) ) { Config.Save(); } } - public void DiscoverMods() + if( Mods.ContainsKey( modFolder.Name ) ) { - Mods.Clear(); - BasePath.Refresh(); - - StructuredMods.SubFolders.Clear(); - StructuredMods.Mods.Clear(); - if( Valid && BasePath.Exists ) - { - foreach( var modFolder in BasePath.EnumerateDirectories() ) - { - var mod = ModData.LoadMod( StructuredMods, modFolder ); - if( mod == null ) - { - continue; - } - - Mods.Add( modFolder.Name, mod ); - } - - SetModStructure(); - } - - Collections.RecreateCaches(); + return false; } - public void DeleteMod( DirectoryInfo modFolder ) + Mods.Add( modFolder.Name, mod ); + foreach( var collection in Collections.Collections.Values ) { - modFolder.Refresh(); - if( modFolder.Exists ) - { - try - { - Directory.Delete( modFolder.FullName, true ); - } - catch( Exception e ) - { - PluginLog.Error( $"Could not delete the mod {modFolder.Name}:\n{e}" ); - } - - if( Mods.TryGetValue( modFolder.Name, out var mod ) ) - { - mod.SortOrder.ParentFolder.RemoveMod( mod ); - Mods.Remove( modFolder.Name ); - Collections.RemoveModFromCaches( modFolder ); - } - } + collection.AddMod( mod ); } - public bool AddMod( DirectoryInfo modFolder ) - { - var mod = ModData.LoadMod( StructuredMods, modFolder ); - if( mod == null ) - { - return false; - } + return true; + } + public bool UpdateMod( ModData mod, bool reloadMeta = false, bool recomputeMeta = false, bool force = false ) + { + var oldName = mod.Meta.Name; + var metaChanges = mod.Meta.RefreshFromFile( mod.MetaFile ) || force; + var fileChanges = mod.Resources.RefreshModFiles( mod.BasePath ); + + if( !recomputeMeta && !reloadMeta && !metaChanges && fileChanges == 0 ) + { + return false; + } + + if( metaChanges || fileChanges.HasFlag( ResourceChange.Files ) ) + { + mod.ComputeChangedItems(); if( Config.ModSortOrder.TryGetValue( mod.BasePath.Name, out var sortOrder ) ) { - if( SetSortOrderPath( mod, sortOrder ) ) + mod.Move( sortOrder ); + var path = mod.SortOrder.FullPath; + if( path != sortOrder ) { + Config.ModSortOrder[ mod.BasePath.Name ] = path; Config.Save(); } } - - if( Mods.ContainsKey( modFolder.Name ) ) + else { - return false; + mod.SortOrder = new SortOrder( StructuredMods, mod.Meta.Name ); } - - Mods.Add( modFolder.Name, mod ); - foreach( var collection in Collections.Collections.Values ) - { - collection.AddMod( mod ); - } - - return true; } - public bool UpdateMod( ModData mod, bool reloadMeta = false, bool recomputeMeta = false, bool force = false ) + var nameChange = !string.Equals( oldName, mod.Meta.Name, StringComparison.InvariantCulture ); + + recomputeMeta |= fileChanges.HasFlag( ResourceChange.Meta ); + if( recomputeMeta ) { - var oldName = mod.Meta.Name; - var metaChanges = mod.Meta.RefreshFromFile( mod.MetaFile ) || force; - var fileChanges = mod.Resources.RefreshModFiles( mod.BasePath ); - - if( !recomputeMeta && !reloadMeta && !metaChanges && fileChanges == 0 ) - { - return false; - } - - if( metaChanges || fileChanges.HasFlag( ResourceChange.Files ) ) - { - mod.ComputeChangedItems(); - if( Config.ModSortOrder.TryGetValue( mod.BasePath.Name, out var sortOrder ) ) - { - mod.Move( sortOrder ); - var path = mod.SortOrder.FullPath; - if( path != sortOrder ) - { - Config.ModSortOrder[ mod.BasePath.Name ] = path; - Config.Save(); - } - } - else - { - mod.SortOrder = new SortOrder( StructuredMods, mod.Meta.Name ); - } - } - - var nameChange = !string.Equals( oldName, mod.Meta.Name, StringComparison.InvariantCulture ); - - recomputeMeta |= fileChanges.HasFlag( ResourceChange.Meta ); - if( recomputeMeta ) - { - mod.Resources.MetaManipulations.Update( mod.Resources.MetaFiles, mod.BasePath, mod.Meta ); - mod.Resources.MetaManipulations.SaveToFile( MetaCollection.FileName( mod.BasePath ) ); - } - - Collections.UpdateCollections( mod, metaChanges, fileChanges, nameChange, reloadMeta ); - - return true; + mod.Resources.MetaManipulations.Update( mod.Resources.MetaFiles, mod.BasePath, mod.Meta ); + mod.Resources.MetaManipulations.SaveToFile( MetaCollection.FileName( mod.BasePath ) ); } - public FullPath? ResolveSwappedOrReplacementPath( Utf8GamePath gameResourcePath ) - { - var ret = Collections.ActiveCollection.ResolveSwappedOrReplacementPath( gameResourcePath ); - ret ??= Collections.ForcedCollection.ResolveSwappedOrReplacementPath( gameResourcePath ); - return ret; - } + Collections.UpdateCollections( mod, metaChanges, fileChanges, nameChange, reloadMeta ); - // private void FileSystemWatcherOnChanged( object sender, FileSystemEventArgs e ) - // { - // #if DEBUG - // PluginLog.Verbose( "file changed: {FullPath}", e.FullPath ); - // #endif - // - // if( _plugin.ImportInProgress ) - // { - // return; - // } - // - // if( _plugin.Configuration.DisableFileSystemNotifications ) - // { - // return; - // } - // - // var file = e.FullPath; - // - // if( !ResolvedFiles.Any( x => x.Value.FullName == file ) ) - // { - // return; - // } - // - // PluginLog.Log( "a loaded file has been modified - file: {FullPath}", file ); - // _plugin.GameUtils.ReloadPlayerResources(); - // } - // - // private void FileSystemPasta() - // { - // haha spaghet - // _fileSystemWatcher?.Dispose(); - // _fileSystemWatcher = new FileSystemWatcher( _basePath.FullName ) - // { - // NotifyFilter = NotifyFilters.LastWrite | - // NotifyFilters.FileName | - // NotifyFilters.DirectoryName, - // IncludeSubdirectories = true, - // EnableRaisingEvents = true - // }; - // - // _fileSystemWatcher.Changed += FileSystemWatcherOnChanged; - // _fileSystemWatcher.Created += FileSystemWatcherOnChanged; - // _fileSystemWatcher.Deleted += FileSystemWatcherOnChanged; - // _fileSystemWatcher.Renamed += FileSystemWatcherOnChanged; - // } + return true; } + + public FullPath? ResolveSwappedOrReplacementPath( Utf8GamePath gameResourcePath ) + { + var ret = Collections.ActiveCollection.ResolveSwappedOrReplacementPath( gameResourcePath ); + ret ??= Collections.ForcedCollection.ResolveSwappedOrReplacementPath( gameResourcePath ); + return ret; + } + + // private void FileSystemWatcherOnChanged( object sender, FileSystemEventArgs e ) + // { + // #if DEBUG + // PluginLog.Verbose( "file changed: {FullPath}", e.FullPath ); + // #endif + // + // if( _plugin.ImportInProgress ) + // { + // return; + // } + // + // if( _plugin.Configuration.DisableFileSystemNotifications ) + // { + // return; + // } + // + // var file = e.FullPath; + // + // if( !ResolvedFiles.Any( x => x.Value.FullName == file ) ) + // { + // return; + // } + // + // PluginLog.Log( "a loaded file has been modified - file: {FullPath}", file ); + // _plugin.GameUtils.ReloadPlayerResources(); + // } + // + // private void FileSystemPasta() + // { + // haha spaghet + // _fileSystemWatcher?.Dispose(); + // _fileSystemWatcher = new FileSystemWatcher( _basePath.FullName ) + // { + // NotifyFilter = NotifyFilters.LastWrite | + // NotifyFilters.FileName | + // NotifyFilters.DirectoryName, + // IncludeSubdirectories = true, + // EnableRaisingEvents = true + // }; + // + // _fileSystemWatcher.Changed += FileSystemWatcherOnChanged; + // _fileSystemWatcher.Created += FileSystemWatcherOnChanged; + // _fileSystemWatcher.Deleted += FileSystemWatcherOnChanged; + // _fileSystemWatcher.Renamed += FileSystemWatcherOnChanged; + // } } \ No newline at end of file diff --git a/Penumbra/Mods/ModManagerEditExtensions.cs b/Penumbra/Mods/ModManagerEditExtensions.cs index f6804d4f..6b8dde28 100644 --- a/Penumbra/Mods/ModManagerEditExtensions.cs +++ b/Penumbra/Mods/ModManagerEditExtensions.cs @@ -204,7 +204,7 @@ public static class ModManagerEditExtensions collection.Save(); if( collection.Cache != null && settings.Enabled ) { - collection.CalculateEffectiveFileList( manager.TempPath, mod.Resources.MetaManipulations.Count > 0, + collection.CalculateEffectiveFileList( mod.Resources.MetaManipulations.Count > 0, collection == manager.Collections.ActiveCollection ); } } diff --git a/Penumbra/Penumbra.cs b/Penumbra/Penumbra.cs index d37a1531..666a349f 100644 --- a/Penumbra/Penumbra.cs +++ b/Penumbra/Penumbra.cs @@ -18,10 +18,10 @@ using System.Linq; using Penumbra.Meta.Manipulations; namespace Penumbra; -public class MetaDefaults -{ -} +public class MetaDefaults +{ } + public class Penumbra : IDalamudPlugin { public string Name @@ -41,8 +41,7 @@ public class Penumbra : IDalamudPlugin public static MetaDefaults MetaDefaults { get; private set; } = null!; public static ModManager ModManager { get; private set; } = null!; - - public ResourceLoader ResourceLoader { get; } + public static ResourceLoader ResourceLoader { get; set; } = null!; public ResourceLogger ResourceLogger { get; } //public PathResolver PathResolver { get; } @@ -113,12 +112,20 @@ public class Penumbra : IDalamudPlugin }; ResourceLoader.EnableHooks(); - if (Config.EnableMods) + if( Config.EnableMods ) + { ResourceLoader.EnableReplacements(); - if (Config.DebugMode) + } + + if( Config.DebugMode ) + { ResourceLoader.EnableDebug(); - if (Config.EnableFullResourceLogging) + } + + if( Config.EnableFullResourceLogging ) + { ResourceLoader.EnableFullLogging(); + } unsafe { @@ -231,7 +238,7 @@ public class Penumbra : IDalamudPlugin //PathResolver.Dispose(); ResourceLogger.Dispose(); ResourceLoader.Dispose(); - + ShutdownWebServer(); } diff --git a/Penumbra/UI/MenuTabs/TabDebug.cs b/Penumbra/UI/MenuTabs/TabDebug.cs index 758a46ea..7d2e8ca9 100644 --- a/Penumbra/UI/MenuTabs/TabDebug.cs +++ b/Penumbra/UI/MenuTabs/TabDebug.cs @@ -20,6 +20,7 @@ using Penumbra.Meta; using Penumbra.Mods; using Penumbra.UI.Custom; using Penumbra.Util; +using CharacterUtility = Penumbra.Interop.Structs.CharacterUtility; using ResourceHandle = Penumbra.Interop.Structs.ResourceHandle; using Utf8String = Penumbra.GameData.ByteString.Utf8String; @@ -164,12 +165,6 @@ public partial class SettingsInterface PrintValue( "Mod Manager BasePath Exists", manager.BasePath != null ? Directory.Exists( manager.BasePath.FullName ).ToString() : false.ToString() ); PrintValue( "Mod Manager Valid", manager.Valid.ToString() ); - PrintValue( "Mod Manager Temp Path", manager.TempPath?.FullName ?? "NULL" ); - PrintValue( "Mod Manager Temp Path IsRooted", - ( !Penumbra.Config.TempDirectory.Any() || Path.IsPathRooted( Penumbra.Config.TempDirectory ) ).ToString() ); - PrintValue( "Mod Manager Temp Path Exists", - manager.TempPath != null ? Directory.Exists( manager.TempPath.FullName ).ToString() : false.ToString() ); - PrintValue( "Mod Manager Temp Path IsWritable", manager.TempWritable.ToString() ); //PrintValue( "Resource Loader Enabled", _penumbra.ResourceLoader.IsEnabled.ToString() ); } @@ -273,43 +268,6 @@ public partial class SettingsInterface } } - private static void DrawDebugTabTempFiles() - { - if( !ImGui.CollapsingHeader( "Temporary Files##Debug" ) ) - { - return; - } - - if( !ImGui.BeginTable( "##tempFileTable", 4, ImGuiTableFlags.SizingFixedFit ) ) - { - return; - } - - using var raii = ImGuiRaii.DeferredEnd( ImGui.EndTable ); - - foreach( var collection in Penumbra.ModManager.Collections.Collections.Values.Where( c => c.Cache != null ) ) - { - var manip = collection.Cache!.MetaManipulations; - var files = ( Dictionary< GamePath, MetaManager.FileInformation >? )manip.GetType() - .GetField( "_currentFiles", BindingFlags.NonPublic | BindingFlags.Instance )?.GetValue( manip ) - ?? new Dictionary< GamePath, MetaManager.FileInformation >(); - - - foreach( var (file, info) in files ) - { - ImGui.TableNextRow(); - ImGui.TableNextColumn(); - ImGui.Text( info.CurrentFile?.FullName ?? "None" ); - ImGui.TableNextColumn(); - ImGui.Text( file ); - ImGui.TableNextColumn(); - ImGui.Text( info.CurrentFile?.Exists ?? false ? "Exists" : "Missing" ); - ImGui.TableNextColumn(); - ImGui.Text( info.Changed ? "Data Changed" : "Unchanged" ); - } - } - } - private void DrawDebugTabIpc() { if( !ImGui.CollapsingHeader( "IPC##Debug" ) ) @@ -369,7 +327,7 @@ public partial class SettingsInterface return; } - var cache = Penumbra.ModManager.Collections.CurrentCollection.Cache; + var cache = Penumbra.ModManager.Collections.CurrentCollection.Cache; if( cache == null || !ImGui.BeginTable( "##MissingFilesDebugList", 1, ImGuiTableFlags.RowBg, -Vector2.UnitX ) ) { return; @@ -397,9 +355,9 @@ public partial class SettingsInterface return; } - _penumbra.ResourceLoader.UpdateDebugInfo(); + Penumbra.ResourceLoader.UpdateDebugInfo(); - if( _penumbra.ResourceLoader.DebugList.Count == 0 + if( Penumbra.ResourceLoader.DebugList.Count == 0 || !ImGui.BeginTable( "##ReplacedResourcesDebugList", 6, ImGuiTableFlags.RowBg | ImGuiTableFlags.SizingFixedFit, -Vector2.UnitX ) ) { return; @@ -407,7 +365,7 @@ public partial class SettingsInterface using var end = ImGuiRaii.DeferredEnd( ImGui.EndTable ); - foreach( var data in _penumbra.ResourceLoader.DebugList.Values.ToArray() ) + foreach( var data in Penumbra.ResourceLoader.DebugList.Values.ToArray() ) { var refCountManip = data.ManipulatedResource == null ? 0 : data.ManipulatedResource->RefCount; var refCountOrig = data.OriginalResource == null ? 0 : data.OriginalResource->RefCount; @@ -426,6 +384,52 @@ public partial class SettingsInterface } } + public unsafe void DrawDebugCharacterUtility() + { + if( !ImGui.CollapsingHeader( "Character Utility##Debug" ) ) + { + return; + } + + if( !ImGui.BeginTable( "##CharacterUtilityDebugList", 6, ImGuiTableFlags.RowBg | ImGuiTableFlags.SizingFixedFit, -Vector2.UnitX ) ) + { + return; + } + + using var end = ImGuiRaii.DeferredEnd( ImGui.EndTable ); + + for( var i = 0; i < CharacterUtility.NumRelevantResources; ++i ) + { + var resource = ( ResourceHandle* )Penumbra.CharacterUtility.Address->Resources[ i ]; + ImGui.TableNextColumn(); + ImGui.Text( $"0x{( ulong )resource:X}" ); + ImGui.TableNextColumn(); + ImGuiNative.igTextUnformatted( resource->FileName(), resource->FileName() + resource->FileNameLength ); + ImGui.TableNextColumn(); + ImGui.Text( $"0x{resource->GetData().Data:X}" ); + if( ImGui.IsItemClicked() ) + { + var (data, length) = resource->GetData(); + ImGui.SetClipboardText( string.Join( " ", + new ReadOnlySpan< byte >( ( byte* )data, length ).ToArray().Select( b => b.ToString( "X2" ) ) ) ); + } + + ImGui.TableNextColumn(); + ImGui.Text( $"{resource->GetData().Length}" ); + ImGui.TableNextColumn(); + ImGui.Text( $"0x{Penumbra.CharacterUtility.DefaultResources[ i ].Address:X}" ); + if( ImGui.IsItemClicked() ) + { + ImGui.SetClipboardText( string.Join( " ", + new ReadOnlySpan< byte >( ( byte* )Penumbra.CharacterUtility.DefaultResources[ i ].Address, + Penumbra.CharacterUtility.DefaultResources[ i ].Size ).ToArray().Select( b => b.ToString( "X2" ) ) ) ); + } + + ImGui.TableNextColumn(); + ImGui.Text( $"{Penumbra.CharacterUtility.DefaultResources[ i ].Size}" ); + } + } + private unsafe void DrawPathResolverDebug() { if( !ImGui.CollapsingHeader( "Path Resolver##Debug" ) ) @@ -479,6 +483,14 @@ public partial class SettingsInterface using var raii = ImGuiRaii.DeferredEnd( ImGui.EndTabItem ); + if( !ImGui.BeginChild( "##DebugChild", -Vector2.One ) ) + { + ImGui.EndChild(); + return; + } + + raii.Push( ImGui.EndChild ); + DrawDebugTabGeneral(); ImGui.NewLine(); DrawDebugTabReplacedResources(); @@ -491,12 +503,12 @@ public partial class SettingsInterface ImGui.NewLine(); DrawPathResolverDebug(); ImGui.NewLine(); + DrawDebugCharacterUtility(); + ImGui.NewLine(); DrawDebugTabRedraw(); ImGui.NewLine(); DrawDebugTabPlayers(); ImGui.NewLine(); - DrawDebugTabTempFiles(); - ImGui.NewLine(); DrawDebugTabIpc(); ImGui.NewLine(); } diff --git a/Penumbra/UI/MenuTabs/TabEffective.cs b/Penumbra/UI/MenuTabs/TabEffective.cs index 0ff8a78c..ae9f35b9 100644 --- a/Penumbra/UI/MenuTabs/TabEffective.cs +++ b/Penumbra/UI/MenuTabs/TabEffective.cs @@ -192,8 +192,9 @@ public partial class SettingsInterface } else if( ( row -= activeResolved ) < activeMeta ) { - var (manip, mod) = activeCollection!.MetaManipulations.Manipulations.ElementAt( row ); - DrawLine( manip.ToString(), mod.Data.Meta.Name ); + // TODO + //var (manip, mod) = activeCollection!.MetaManipulations.Manipulations.ElementAt( row ); + DrawLine( 0.ToString(), 0.ToString() ); } else if( ( row -= activeMeta ) < forcedResolved ) { @@ -202,9 +203,10 @@ public partial class SettingsInterface } else { + // TODO row -= forcedResolved; - var (manip, mod) = forcedCollection!.MetaManipulations.Manipulations.ElementAt( row ); - DrawLine( manip.ToString(), mod.Data.Meta.Name ); + //var (manip, mod) = forcedCollection!.MetaManipulations.Manipulations.ElementAt( row ); + DrawLine( 0.ToString(), 0.ToString() ); } } } diff --git a/Penumbra/UI/MenuTabs/TabInstalled/TabInstalledDetails.cs b/Penumbra/UI/MenuTabs/TabInstalled/TabInstalledDetails.cs index 7da647b1..d2000ba6 100644 --- a/Penumbra/UI/MenuTabs/TabInstalled/TabInstalledDetails.cs +++ b/Penumbra/UI/MenuTabs/TabInstalled/TabInstalledDetails.cs @@ -426,8 +426,7 @@ public partial class SettingsInterface foreach( var collection in modManager.Collections.Collections.Values .Where( c => c.Cache != null && c.Settings[ Mod!.Data.BasePath.Name ].Enabled ) ) { - collection.CalculateEffectiveFileList( modManager.TempPath, false, - collection == modManager.Collections.ActiveCollection ); + collection.CalculateEffectiveFileList( false, collection == modManager.Collections.ActiveCollection ); } // If the mod is enabled in the current collection, its conflicts may have changed. diff --git a/Penumbra/UI/MenuTabs/TabInstalled/TabInstalledDetailsManipulations.cs b/Penumbra/UI/MenuTabs/TabInstalled/TabInstalledDetailsManipulations.cs index a48a1732..ffe45792 100644 --- a/Penumbra/UI/MenuTabs/TabInstalled/TabInstalledDetailsManipulations.cs +++ b/Penumbra/UI/MenuTabs/TabInstalled/TabInstalledDetailsManipulations.cs @@ -1,18 +1,12 @@ using System; using System.Collections.Generic; -using System.ComponentModel; -using System.Linq; using System.Numerics; using Dalamud.Interface; using ImGuiNET; -using Lumina.Data.Files; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; -using Penumbra.Meta; -using Penumbra.Meta.Files; using Penumbra.Meta.Manipulations; using Penumbra.UI.Custom; -using Penumbra.Util; using ObjectType = Penumbra.GameData.Enums.ObjectType; namespace Penumbra.UI @@ -255,7 +249,6 @@ namespace Penumbra.UI private bool DrawGmpRow( int manipIdx, IList< MetaManipulation > list ) { - var defaults = ( GmpEntry )Penumbra.MetaDefaults.GetDefaultValue( list[ manipIdx ] )!; var ret = false; //var id = list[ manipIdx ].GmpIdentifier; //var val = list[ manipIdx ].GmpValue; @@ -365,7 +358,6 @@ namespace Penumbra.UI private bool DrawEqdpRow( int manipIdx, IList< MetaManipulation > list ) { - var defaults = ( EqdpEntry )Penumbra.MetaDefaults.GetDefaultValue( list[ manipIdx ] )!; var ret = false; //var id = list[ manipIdx ].EqdpIdentifier; //var val = list[ manipIdx ].EqdpValue; @@ -402,7 +394,6 @@ namespace Penumbra.UI private bool DrawEstRow( int manipIdx, IList< MetaManipulation > list ) { - var defaults = ( ushort )Penumbra.MetaDefaults.GetDefaultValue( list[ manipIdx ] )!; var ret = false; //var id = list[ manipIdx ].EstIdentifier; //var val = list[ manipIdx ].EstValue; @@ -434,7 +425,6 @@ namespace Penumbra.UI private bool DrawImcRow( int manipIdx, IList< MetaManipulation > list ) { - var defaults = ( ImcFile.ImageChangeData )Penumbra.MetaDefaults.GetDefaultValue( list[ manipIdx ] )!; var ret = false; //var id = list[ manipIdx ].ImcIdentifier; //var val = list[ manipIdx ].ImcValue; @@ -493,7 +483,6 @@ namespace Penumbra.UI private bool DrawRspRow( int manipIdx, IList< MetaManipulation > list ) { - var defaults = ( float )Penumbra.MetaDefaults.GetDefaultValue( list[ manipIdx ] )!; var ret = false; //var id = list[ manipIdx ].RspIdentifier; //var val = list[ manipIdx ].RspValue; diff --git a/Penumbra/UI/MenuTabs/TabInstalled/TabInstalledSelector.cs b/Penumbra/UI/MenuTabs/TabInstalled/TabInstalledSelector.cs index e6bfdea5..d805e41b 100644 --- a/Penumbra/UI/MenuTabs/TabInstalled/TabInstalledSelector.cs +++ b/Penumbra/UI/MenuTabs/TabInstalled/TabInstalledSelector.cs @@ -529,8 +529,7 @@ public partial class SettingsInterface var collection = Penumbra.ModManager.Collections.CurrentCollection; if( collection.Cache != null ) { - collection.CalculateEffectiveFileList( Penumbra.ModManager.TempPath, metaManips, - collection == Penumbra.ModManager.Collections.ActiveCollection ); + collection.CalculateEffectiveFileList( metaManips, collection == Penumbra.ModManager.Collections.ActiveCollection ); } collection.Save(); diff --git a/Penumbra/UI/MenuTabs/TabResourceManager.cs b/Penumbra/UI/MenuTabs/TabResourceManager.cs index 501653d4..f46ad018 100644 --- a/Penumbra/UI/MenuTabs/TabResourceManager.cs +++ b/Penumbra/UI/MenuTabs/TabResourceManager.cs @@ -82,7 +82,7 @@ public partial class SettingsInterface if( ImGui.IsItemClicked() ) { var data = ( ( Interop.Structs.ResourceHandle* )r )->GetData(); - ImGui.SetClipboardText( ((IntPtr)( ( Interop.Structs.ResourceHandle* )r )->Data->VTable).ToString("X") + string.Join( " ", + ImGui.SetClipboardText( string.Join( " ", new ReadOnlySpan< byte >( ( byte* )data.Data, data.Length ).ToArray().Select( b => b.ToString( "X2" ) ) ) ); //ImGuiNative.igSetClipboardText( ( byte* )Structs.ResourceHandle.GetData( ( IntPtr )r ) ); } diff --git a/Penumbra/UI/MenuTabs/TabSettings.cs b/Penumbra/UI/MenuTabs/TabSettings.cs index 8e1ba624..9d099eb0 100644 --- a/Penumbra/UI/MenuTabs/TabSettings.cs +++ b/Penumbra/UI/MenuTabs/TabSettings.cs @@ -24,8 +24,6 @@ public partial class SettingsInterface private readonly Configuration _config; private bool _configChanged; private string _newModDirectory; - private string _newTempDirectory; - public TabSettings( SettingsInterface ui ) { @@ -33,7 +31,6 @@ public partial class SettingsInterface _config = Penumbra.Config; _configChanged = false; _newModDirectory = _config.ModDirectory; - _newTempDirectory = _config.TempDirectory; } private static bool DrawPressEnterWarning( string old ) @@ -91,33 +88,6 @@ public partial class SettingsInterface } } - private void DrawTempFolder() - { - ImGui.BeginGroup(); - ImGui.SetNextItemWidth( SettingsMenu.InputTextWidth ); - var save = ImGui.InputText( "Temp Directory", ref _newTempDirectory, 255, ImGuiInputTextFlags.EnterReturnsTrue ); - ImGui.SameLine(); - ImGuiComponents.HelpMarker( "This is where Penumbra will store temporary meta manipulation files.\n" - + "Leave this blank if you have no reason not to.\n" - + "A directory 'penumbrametatmp' will be created as a sub-directory to the specified directory.\n" - + "If none is specified (i.e. this is blank) this directory will be created in the root directory instead.\n" ); - ImGui.SameLine(); - var modManager = Penumbra.ModManager; - DrawOpenDirectoryButton( 1, modManager.TempPath, modManager.TempWritable ); - ImGui.EndGroup(); - - if( _newTempDirectory == _config.TempDirectory ) - { - return; - } - - if( save || DrawPressEnterWarning( _config.TempDirectory ) ) - { - modManager.SetTempDirectory( _newTempDirectory ); - _newTempDirectory = _config.TempDirectory; - } - } - private void DrawRediscoverButton() { if( ImGui.Button( "Rediscover Mods" ) ) @@ -326,11 +296,11 @@ public partial class SettingsInterface { if( tmp ) { - _base._penumbra.ResourceLoader.EnableFullLogging(); + Penumbra.ResourceLoader.EnableFullLogging(); } else { - _base._penumbra.ResourceLoader.DisableFullLogging(); + Penumbra.ResourceLoader.DisableFullLogging(); } _config.EnableFullResourceLogging = tmp; @@ -348,11 +318,11 @@ public partial class SettingsInterface { if( tmp ) { - _base._penumbra.ResourceLoader.EnableDebug(); + Penumbra.ResourceLoader.EnableDebug(); } else { - _base._penumbra.ResourceLoader.DisableDebug(); + Penumbra.ResourceLoader.DisableDebug(); } _config.DebugMode = tmp; @@ -388,7 +358,6 @@ public partial class SettingsInterface private void DrawAdvancedSettings() { - DrawTempFolder(); DrawRequestedResourceLogging(); DrawDisableSoundStreamingBox(); DrawLogLoadedFilesBox(); diff --git a/Penumbra/UI/SettingsInterface.cs b/Penumbra/UI/SettingsInterface.cs index 7f0f74d6..897419f3 100644 --- a/Penumbra/UI/SettingsInterface.cs +++ b/Penumbra/UI/SettingsInterface.cs @@ -68,7 +68,7 @@ public partial class SettingsInterface : IDisposable var current = modManager.Collections.CurrentCollection; if( current.Cache != null ) { - current.CalculateEffectiveFileList( modManager.TempPath, recalculateMeta, + current.CalculateEffectiveFileList( recalculateMeta, current == modManager.Collections.ActiveCollection ); _menu.InstalledTab.Selector.Cache.TriggerFilterReset(); }