Metamanipulations seemingly working.

This commit is contained in:
Ottermandias 2022-03-14 15:23:00 +01:00
parent 707570615c
commit 6f527a1dbc
26 changed files with 1637 additions and 1237 deletions

View file

@ -108,7 +108,7 @@ public sealed unsafe partial class Utf8String
var start = 0;
for( var idx = IndexOf( b, start ); idx >= 0; idx = IndexOf( b, start ) )
{
if( start + 1 != idx || !removeEmpty )
if( start != idx || !removeEmpty )
{
ret.Add( Substring( start, idx - start ) );
}

View file

@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using Dalamud.Logging;
using Lumina.Data.Files;
@ -10,6 +11,7 @@ using Penumbra.GameData.Util;
using Penumbra.Meta.Files;
using Penumbra.Meta.Manipulations;
using Penumbra.Util;
using ImcFile = Penumbra.Meta.Files.ImcFile;
namespace Penumbra.Importer;
@ -254,22 +256,38 @@ public class TexToolsMeta
using var reader = new BinaryReader( new MemoryStream( data ) );
var values = reader.ReadStructures< ImcEntry >( num );
ushort i = 0;
if( info.PrimaryType is ObjectType.Equipment or ObjectType.Accessory )
try
{
// TODO check against default.
foreach( var value in values )
if( info.PrimaryType is ObjectType.Equipment or ObjectType.Accessory )
{
ImcManipulations.Add(new ImcManipulation(info.EquipSlot, i, info.PrimaryId, value) );
++i;
var def = new ImcFile( new ImcManipulation( info.EquipSlot, i, info.PrimaryId, new ImcEntry() ).GamePath() );
var partIdx = ImcFile.PartIndex( info.EquipSlot );
foreach( var value in values )
{
if( !value.Equals( def.GetEntry( partIdx, i ) ) )
{
ImcManipulations.Add( new ImcManipulation( info.EquipSlot, i, info.PrimaryId, value ) );
}
++i;
}
}
else
{
var def = new ImcFile( new ImcManipulation( info.PrimaryType, info.SecondaryType, info.PrimaryId, info.SecondaryId, i,
new ImcEntry() ).GamePath() );
foreach( var value in values.Where( v => true || !v.Equals( def.GetEntry( 0, i ) ) ) )
{
ImcManipulations.Add( new ImcManipulation( info.PrimaryType, info.SecondaryType, info.PrimaryId, info.SecondaryId, i,
value ) );
++i;
}
}
}
else
catch( Exception e )
{
foreach( var value in values )
{
ImcManipulations.Add( new ImcManipulation( info.PrimaryType, info.SecondaryType, info.PrimaryId, info.SecondaryId, i, value ) );
++i;
}
PluginLog.Error( "Could not compute IMC manipulation. This is in all likelihood due to TexTools corrupting your index files.\n"
+ $"If the following error looks like Lumina is having trouble to read an IMC file, please do a do-over in TexTools:\n{e}" );
}
}
@ -296,7 +314,7 @@ public class TexToolsMeta
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 )
{
@ -307,7 +325,7 @@ public class TexToolsMeta
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 );
@ -315,11 +333,11 @@ public class TexToolsMeta
{
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 ) );
@ -373,32 +391,34 @@ public class TexToolsMeta
void Add( RspAttribute attribute, float value )
{
var def = CmpFile.GetDefault( subRace, attribute );
if (value != def)
ret!.RspManipulations.Add(new RspManipulation(subRace, attribute, value));
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() );
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() );
Add( RspAttribute.MaleMinSize, br.ReadSingle() );
Add( RspAttribute.MaleMaxSize, br.ReadSingle() );
Add( RspAttribute.MaleMinTail, br.ReadSingle() );
Add( RspAttribute.MaleMaxTail, br.ReadSingle() );
}
return ret;
}
}

View file

@ -1,4 +1,5 @@
using System;
using System.Linq;
using Dalamud.Hooking;
using Dalamud.Logging;
using Dalamud.Utility.Signatures;
@ -20,7 +21,33 @@ public unsafe class CharacterUtility : IDisposable
public Structs.CharacterUtility* Address
=> *_characterUtilityAddress;
public (IntPtr Address, int Size)[] DefaultResources = new (IntPtr, int)[Structs.CharacterUtility.NumRelevantResources];
// The relevant indices depend on which meta manipulations we allow for.
// The defines are set in the project configuration.
public static readonly int[] RelevantIndices
= Array.Empty< int >()
#if USE_EQP
.Append( Structs.CharacterUtility.EqpIdx )
#endif
#if USE_GMP
.Append( Structs.CharacterUtility.GmpIdx )
#endif
#if USE_EQDP
.Concat( Enumerable.Range( Structs.CharacterUtility.EqdpStartIdx, Structs.CharacterUtility.NumEqdpFiles ) )
#endif
#if USE_CMP
.Append( Structs.CharacterUtility.HumanCmpIdx )
#endif
#if USE_EST
.Concat( Enumerable.Range( Structs.CharacterUtility.FaceEstIdx, 4 ) )
#endif
.ToArray();
private static readonly int[] ReverseIndices
= Enumerable.Range( 0, Structs.CharacterUtility.NumResources )
.Select( i => Array.IndexOf( RelevantIndices, i ) ).ToArray();
public (IntPtr Address, int Size)[] DefaultResources = new (IntPtr, int)[RelevantIndices.Length];
public CharacterUtility()
{
@ -48,9 +75,9 @@ 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.NumRelevantResources; ++i )
for( var i = 0; i < RelevantIndices.Length; ++i )
{
var resource = ( Structs.ResourceHandle* )Address->Resources[ i ];
var resource = ( Structs.ResourceHandle* )Address->Resources[ RelevantIndices[ i ] ];
DefaultResources[ i ] = resource->GetData();
}
}
@ -65,19 +92,25 @@ public unsafe class CharacterUtility : IDisposable
}
// Reset the data of one of the stored resources to its default values.
public void ResetResource( int idx )
public void ResetResource( int fileIdx )
{
var resource = ( Structs.ResourceHandle* )Address->Resources[ idx ];
resource->SetData( DefaultResources[ idx ].Address, DefaultResources[ idx ].Size );
var (data, size) = DefaultResources[ ReverseIndices[ fileIdx ] ];
var resource = ( Structs.ResourceHandle* )Address->Resources[ fileIdx ];
resource->SetData( data, size );
}
// Return all relevant resources to the default resource.
public void ResetAll()
{
foreach( var idx in RelevantIndices )
{
ResetResource( idx );
}
}
public void Dispose()
{
for( var i = 0; i < Structs.CharacterUtility.NumRelevantResources; ++i )
{
ResetResource( i );
}
ResetAll();
LoadDataFilesHook.Dispose();
}
}

View file

@ -117,12 +117,15 @@ public unsafe partial class ResourceLoader
// We use the IsRooted check to signify paths replaced by us pointing to the local filesystem instead of an SqPack.
if( !valid || !gamePath.IsRooted() )
{
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 );
if( valid && ResourceLoadCustomization != null && gamePath.Path[ 0 ] == ( byte )'|' )
{
ret = ResourceLoadCustomization.Invoke( gamePath, resourceManager, fileDescriptor, priority, isSync );
}
else
{
ret = ReadSqPackHook.Original( resourceManager, fileDescriptor, priority, isSync );
FileLoaded?.Invoke( gamePath.Path, ret != 0, false );
}
}
else
{

View file

@ -1,4 +1,5 @@
using System;
using System.Linq;
using System.Runtime.InteropServices;
using Penumbra.GameData.Enums;
@ -7,49 +8,52 @@ namespace Penumbra.Interop.Structs;
[StructLayout( LayoutKind.Explicit )]
public unsafe struct CharacterUtility
{
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 readonly int[] EqdpIndices
= Enumerable.Range( EqdpStartIdx, NumEqdpFiles ).ToArray();
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 EqdpStartIdx = 2;
public const int NumEqdpFiles = 2 * 28;
public static int EqdpIdx( GenderRace raceCode, bool accessory )
=> ( accessory ? 28 : 0 )
=> ( accessory ? NumEqdpFiles / 2 : 0 )
+ ( int )raceCode switch
{
0101 => 2,
0201 => 3,
0301 => 4,
0401 => 5,
0501 => 6,
0601 => 7,
0701 => 8,
0801 => 9,
0901 => 10,
1001 => 11,
1101 => 12,
1201 => 13,
1301 => 14,
1401 => 15,
1501 => 16,
1601 => 17, // Does not exist yet
1701 => 18,
1801 => 19,
0104 => 20,
0204 => 21,
0504 => 22,
0604 => 23,
0704 => 24,
0804 => 25,
1304 => 26,
1404 => 27,
9104 => 28,
9204 => 29,
0101 => EqdpStartIdx,
0201 => EqdpStartIdx + 1,
0301 => EqdpStartIdx + 2,
0401 => EqdpStartIdx + 3,
0501 => EqdpStartIdx + 4,
0601 => EqdpStartIdx + 5,
0701 => EqdpStartIdx + 6,
0801 => EqdpStartIdx + 7,
0901 => EqdpStartIdx + 8,
1001 => EqdpStartIdx + 9,
1101 => EqdpStartIdx + 10,
1201 => EqdpStartIdx + 11,
1301 => EqdpStartIdx + 12,
1401 => EqdpStartIdx + 13,
1501 => EqdpStartIdx + 14,
1601 => EqdpStartIdx + 15, // Does not exist yet
1701 => EqdpStartIdx + 16,
1801 => EqdpStartIdx + 17,
0104 => EqdpStartIdx + 18,
0204 => EqdpStartIdx + 19,
0504 => EqdpStartIdx + 20,
0604 => EqdpStartIdx + 21,
0704 => EqdpStartIdx + 22,
0804 => EqdpStartIdx + 23,
1304 => EqdpStartIdx + 24,
1404 => EqdpStartIdx + 25,
9104 => EqdpStartIdx + 26,
9204 => EqdpStartIdx + 27,
_ => throw new ArgumentException(),
};

View file

@ -47,7 +47,8 @@ public sealed unsafe class ExpandedEqdpFile : MetaBaseFile
throw new IndexOutOfRangeException();
}
return *( EqdpEntry* )( Data + DataOffset + EqdpEntrySize * idx );
var x = new ReadOnlySpan< ushort >( ( ushort* )Data, Length / 2 );
return ( EqdpEntry )( *( ushort* )( Data + DataOffset + EqdpEntrySize * idx ) );
}
set
{
@ -56,7 +57,7 @@ public sealed unsafe class ExpandedEqdpFile : MetaBaseFile
throw new IndexOutOfRangeException();
}
*( EqdpEntry* )( Data + DataOffset + EqdpEntrySize * idx ) = value;
*( ushort* )( Data + DataOffset + EqdpEntrySize * idx ) = ( ushort )value;
}
}
@ -65,9 +66,12 @@ public sealed unsafe class ExpandedEqdpFile : MetaBaseFile
var def = ( byte* )DefaultData.Data;
Functions.MemCpyUnchecked( Data, def, IdentifierSize + PreambleSize );
var controlPtr = ( ushort* )( def + IdentifierSize + PreambleSize );
var dataBasePtr = ( byte* )( controlPtr + BlockCount );
var myDataPtr = ( ushort* )( Data + IdentifierSize + PreambleSize + 2 * BlockCount );
var controlPtr = ( ushort* )( def + IdentifierSize + PreambleSize );
var dataBasePtr = controlPtr + BlockCount;
var myDataPtrStart = ( ushort* )( Data + IdentifierSize + PreambleSize + 2 * BlockCount );
var myDataPtr = myDataPtrStart;
var myControlPtr = ( ushort* )( Data + IdentifierSize + PreambleSize );
var x = new ReadOnlySpan< ushort >( ( ushort* )Data, Length / 2 );
for( var i = 0; i < BlockCount; ++i )
{
if( controlPtr[ i ] == CollapsedBlock )
@ -76,11 +80,16 @@ public sealed unsafe class ExpandedEqdpFile : MetaBaseFile
}
else
{
var y = new ReadOnlySpan< ushort >( dataBasePtr + controlPtr[ i ], BlockSize );
var z = new ReadOnlySpan< ushort >( myDataPtr, BlockSize );
Functions.MemCpyUnchecked( myDataPtr, dataBasePtr + controlPtr[ i ], BlockSize * EqdpEntrySize );
}
myDataPtr += BlockSize;
myControlPtr[ i ] = ( ushort )( i * BlockSize );
myDataPtr += BlockSize;
}
Functions.MemSet( myDataPtr, 0, Length - ( int )( ( byte* )myDataPtr - Data ) );
}
public void Reset( IEnumerable< int > entries )
@ -102,7 +111,7 @@ public sealed unsafe class ExpandedEqdpFile : MetaBaseFile
DataOffset = IdentifierSize + PreambleSize + totalBlockCount * BlockHeaderSize;
var fullLength = DataOffset + totalBlockCount * totalBlockSize;
fullLength += ( FileAlignment - ( Length & ( FileAlignment - 1 ) ) ) & ( FileAlignment - 1 );
fullLength += ( FileAlignment - ( fullLength & ( FileAlignment - 1 ) ) ) & ( FileAlignment - 1 );
AllocateData( fullLength );
Reset();
}
@ -128,8 +137,9 @@ public sealed unsafe class ExpandedEqdpFile : MetaBaseFile
return 0;
}
var blockData = ( EqdpEntry* )( data + IdentifierSize + PreambleSize + totalBlockCount * 2 + block );
return *( blockData + blockIdx % blockSize );
var blockData = ( ushort* )( data + IdentifierSize + PreambleSize + totalBlockCount * 2 + block * 2 );
var x = new ReadOnlySpan< ushort >( blockData, blockSize );
return (EqdpEntry) (*( blockData + setIdx % blockSize ));
}
public static EqdpEntry GetDefault( GenderRace raceCode, bool accessory, int setIdx )

View file

@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Numerics;
using System.Runtime.CompilerServices;
using Penumbra.GameData.Structs;
using Penumbra.GameData.Util;
using Penumbra.Interop.Structs;
@ -24,17 +25,17 @@ public unsafe class ExpandedEqpGmpBase : MetaBaseFile
public ulong ControlBlock
=> *( ulong* )Data;
protected T Get< T >( int idx ) where T : unmanaged
protected ulong GetInternal( int idx )
{
return idx switch
{
>= Count => throw new IndexOutOfRangeException(),
<= 1 => *( ( T* )Data + 1 ),
_ => *( ( T* )Data + idx ),
<= 1 => *( ( ulong* )Data + 1 ),
_ => *( ( ulong* )Data + idx ),
};
}
protected void Set< T >( int idx, T value ) where T : unmanaged
protected void SetInternal( int idx, ulong value )
{
idx = idx switch
{
@ -43,7 +44,7 @@ public unsafe class ExpandedEqpGmpBase : MetaBaseFile
_ => idx,
};
*( ( T* )Data + idx ) = value;
*( ( ulong* )Data + idx ) = value;
}
protected virtual void SetEmptyBlock( int idx )
@ -53,21 +54,24 @@ public unsafe class ExpandedEqpGmpBase : MetaBaseFile
public sealed override void Reset()
{
var ptr = ( byte* )DefaultData.Data;
var controlBlock = *( ulong* )ptr;
*( ulong* )Data = ulong.MaxValue;
for( var i = 0; i < 64; ++i )
var ptr = ( byte* )DefaultData.Data;
var controlBlock = *( ulong* )ptr;
var expandedBlocks = 0;
for( var i = 0; i < NumBlocks; ++i )
{
var collapsed = ( ( controlBlock >> i ) & 1 ) == 0;
if( !collapsed )
{
Functions.MemCpyUnchecked( Data + i * BlockSize * EntrySize, ptr + i * BlockSize * EntrySize, BlockSize * EntrySize );
Functions.MemCpyUnchecked( Data + i * BlockSize * EntrySize, ptr + expandedBlocks * BlockSize * EntrySize, BlockSize * EntrySize );
expandedBlocks++;
}
else
{
SetEmptyBlock( i );
}
}
*( ulong* )Data = ulong.MaxValue;
}
public ExpandedEqpGmpBase( bool gmp )
@ -77,7 +81,7 @@ public unsafe class ExpandedEqpGmpBase : MetaBaseFile
Reset();
}
protected static T GetDefault< T >( int fileIdx, int setIdx, T def ) where T : unmanaged
protected static ulong GetDefaultInternal( int fileIdx, int setIdx, ulong def )
{
var data = ( byte* )Penumbra.CharacterUtility.DefaultResources[ fileIdx ].Address;
if( setIdx == 0 )
@ -100,7 +104,7 @@ public unsafe class ExpandedEqpGmpBase : MetaBaseFile
var count = BitOperations.PopCount( control & ( blockBit - 1 ) );
var idx = setIdx % BlockSize;
var ptr = ( T* )data + BlockSize * count + idx;
var ptr = ( ulong* )data + BlockSize * count + idx;
return *ptr;
}
}
@ -113,12 +117,12 @@ public sealed class ExpandedEqpFile : ExpandedEqpGmpBase
public EqpEntry this[ int idx ]
{
get => Get< EqpEntry >( idx );
set => Set( idx, value );
get => ( EqpEntry )GetInternal( idx );
set => SetInternal( idx, ( ulong )value );
}
public static EqpEntry GetDefault( int setIdx )
=> GetDefault( CharacterUtility.EqpIdx, setIdx, Eqp.DefaultEntry );
=> ( EqpEntry )GetDefaultInternal( CharacterUtility.EqpIdx, setIdx, ( ulong )Eqp.DefaultEntry );
protected override unsafe void SetEmptyBlock( int idx )
{
@ -147,12 +151,12 @@ public sealed class ExpandedGmpFile : ExpandedEqpGmpBase
public GmpEntry this[ int idx ]
{
get => Get< GmpEntry >( idx );
set => Set( idx, value );
get => ( GmpEntry )GetInternal( idx );
set => SetInternal( idx, ( ulong )value );
}
public static GmpEntry GetDefault( int setIdx )
=> GetDefault( CharacterUtility.GmpIdx, setIdx, GmpEntry.Default );
=> ( GmpEntry )GetDefaultInternal( CharacterUtility.GmpIdx, setIdx, ( ulong )GmpEntry.Default );
public void Reset( IEnumerable< int > entries )
{

View file

@ -1,4 +1,5 @@
using System;
using System.Collections;
using System.Numerics;
using Dalamud.Logging;
using Dalamud.Memory;
@ -7,6 +8,7 @@ using Penumbra.GameData.ByteString;
using Penumbra.GameData.Enums;
using Penumbra.GameData.Util;
using Penumbra.Interop.Structs;
using Penumbra.Meta.Manipulations;
namespace Penumbra.Meta.Files;
@ -64,41 +66,57 @@ public unsafe class ImcFile : MetaBaseFile
=> NumParts * sizeof( ImcEntry ) * ( Count + 1 ) + PreambleSize;
public int Count
=> *( ushort* )Data;
public ushort PartMask
=> *( ushort* )( Data + 2 );
=> CountInternal( Data );
public readonly int NumParts;
public readonly Utf8GamePath Path;
public ImcEntry* DefaultPartPtr( int partIdx )
private static int CountInternal( byte* data )
=> *( ushort* )data;
private static ushort PartMask( byte* data )
=> *( ushort* )( data + 2 );
private static ImcEntry* DefaultPartPtr( byte* data, int partIdx )
{
var flag = 1 << partIdx;
if( ( PartMask & flag ) == 0 )
if( ( PartMask( data ) & flag ) == 0 )
{
return null;
}
return ( ImcEntry* )( Data + PreambleSize ) + partIdx;
return ( ImcEntry* )( data + PreambleSize ) + partIdx;
}
public ImcEntry* VariantPtr( int partIdx, int variantIdx )
private static ImcEntry* VariantPtr( byte* data, int partIdx, int variantIdx )
{
if( variantIdx == 0 )
{
return DefaultPartPtr( data, partIdx );
}
--variantIdx;
var flag = 1 << partIdx;
if( ( PartMask & flag ) == 0 || variantIdx >= Count )
if( ( PartMask( data ) & flag ) == 0 || variantIdx >= CountInternal( data ) )
{
return null;
}
var numParts = NumParts;
var ptr = ( ImcEntry* )( Data + PreambleSize );
var numParts = BitOperations.PopCount( PartMask( data ) );
var ptr = ( ImcEntry* )( data + PreambleSize );
ptr += numParts;
ptr += variantIdx * numParts;
ptr += partIdx;
return ptr;
}
public ImcEntry GetEntry( int partIdx, int variantIdx )
{
var ptr = VariantPtr( Data, partIdx, variantIdx );
return ptr == null ? new ImcEntry() : *ptr;
}
public static int PartIndex( EquipSlot slot )
=> slot switch
{
@ -122,7 +140,6 @@ public unsafe class ImcFile : MetaBaseFile
return true;
}
var numParts = NumParts;
if( ActualLength > Length )
{
PluginLog.Warning( "Adding too many variants to IMC, size exceeded." );
@ -130,10 +147,10 @@ public unsafe class ImcFile : MetaBaseFile
}
var defaultPtr = ( ImcEntry* )( Data + PreambleSize );
var endPtr = defaultPtr + ( numVariants + 1 ) * numParts;
for( var ptr = defaultPtr + numParts; ptr < endPtr; ptr += numParts )
var endPtr = defaultPtr + ( numVariants + 1 ) * NumParts;
for( var ptr = defaultPtr + NumParts; ptr < endPtr; ptr += NumParts )
{
Functions.MemCpyUnchecked( ptr, defaultPtr, numParts * sizeof( ImcEntry ) );
Functions.MemCpyUnchecked( ptr, defaultPtr, NumParts * sizeof( ImcEntry ) );
}
PluginLog.Verbose( "Expanded imc from {Count} to {NewCount} variants.", Count, numVariants );
@ -143,15 +160,14 @@ public unsafe class ImcFile : MetaBaseFile
public bool SetEntry( int partIdx, int variantIdx, ImcEntry entry )
{
var numParts = NumParts;
if( partIdx >= numParts )
if( partIdx >= NumParts )
{
return false;
}
EnsureVariantCount( variantIdx + 1 );
EnsureVariantCount( variantIdx );
var variantPtr = VariantPtr( partIdx, variantIdx );
var variantPtr = VariantPtr( Data, partIdx, variantIdx );
if( variantPtr == null )
{
PluginLog.Error( "Error during expansion of imc file." );
@ -168,9 +184,20 @@ public unsafe class ImcFile : MetaBaseFile
}
public override void Reset()
{
var file = Dalamud.GameData.GetFile( Path.ToString() );
fixed( byte* ptr = file!.Data )
{
Functions.MemCpyUnchecked( Data, ptr, file.Data.Length );
Functions.MemSet( Data + file.Data.Length, 0, Length - file.Data.Length );
}
}
public ImcFile( Utf8GamePath path )
: base( 0 )
{
Path = path;
var file = Dalamud.GameData.GetFile( path.ToString() );
if( file == null )
{
@ -182,6 +209,22 @@ public unsafe class ImcFile : MetaBaseFile
NumParts = BitOperations.PopCount( *( ushort* )( ptr + 2 ) );
AllocateData( file.Data.Length + sizeof( ImcEntry ) * 100 * NumParts );
Functions.MemCpyUnchecked( Data, ptr, file.Data.Length );
Functions.MemSet( Data + file.Data.Length, 0, sizeof( ImcEntry ) * 100 * NumParts );
}
}
public static ImcEntry GetDefault( Utf8GamePath path, EquipSlot slot, int variantIdx )
{
var file = Dalamud.GameData.GetFile( path.ToString() );
if( file == null )
{
throw new Exception();
}
fixed( byte* ptr = file.Data )
{
var entry = VariantPtr( ptr, PartIndex( slot ), variantIdx );
return entry == null ? new ImcEntry() : *entry;
}
}

View file

@ -0,0 +1,59 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using Penumbra.Interop.Structs;
using Penumbra.Meta.Files;
using Penumbra.Meta.Manipulations;
namespace Penumbra.Meta.Manager;
public partial class MetaManager
{
public struct MetaManagerCmp : IDisposable
{
public CmpFile? File = null;
public readonly Dictionary< RspManipulation, Mod.Mod > Manipulations = new();
public MetaManagerCmp()
{ }
[Conditional( "USE_CMP" )]
public void Reset()
{
if( File == null )
{
return;
}
File.Reset( Manipulations.Keys.Select( m => ( m.SubRace, m.Attribute ) ) );
Manipulations.Clear();
}
[Conditional( "USE_CMP" )]
public void SetFiles()
=> SetFile( File, CharacterUtility.HumanCmpIdx );
public bool ApplyMod( RspManipulation m, Mod.Mod mod )
{
#if USE_CMP
if( !Manipulations.TryAdd( m, mod ) )
{
return false;
}
File ??= new CmpFile();
return m.Apply( File );
#else
return false;
#endif
}
public void Dispose()
{
File?.Dispose();
File = null;
Manipulations.Clear();
}
}
}

View file

@ -0,0 +1,72 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using Penumbra.GameData.Enums;
using Penumbra.Interop.Structs;
using Penumbra.Meta.Files;
using Penumbra.Meta.Manipulations;
namespace Penumbra.Meta.Manager;
public partial class MetaManager
{
public struct MetaManagerEqdp : IDisposable
{
public ExpandedEqdpFile?[] Files = new ExpandedEqdpFile?[CharacterUtility.NumEqdpFiles];
public readonly Dictionary< EqdpManipulation, Mod.Mod > Manipulations = new();
public MetaManagerEqdp()
{ }
[Conditional( "USE_EQDP" )]
public void SetFiles()
{
foreach( var idx in CharacterUtility.EqdpIndices )
{
SetFile( Files[ idx - CharacterUtility.EqdpStartIdx ], idx );
}
}
[Conditional( "USE_EQDP" )]
public void Reset()
{
foreach( var file in Files )
{
file?.Reset( Manipulations.Keys.Where( m => m.FileIndex() == file.Index ).Select( m => ( int )m.SetId ) );
}
Manipulations.Clear();
}
public bool ApplyMod( EqdpManipulation m, Mod.Mod mod )
{
#if USE_EQDP
if( !Manipulations.TryAdd( m, mod ) )
{
return false;
}
var file = Files[ m.FileIndex() - 2 ] ??= new ExpandedEqdpFile( Names.CombinedRace( m.Gender, m.Race ), m.Slot.IsAccessory() );
return m.Apply( file );
#else
return false;
#endif
}
public ExpandedEqdpFile? File( GenderRace race, bool accessory )
=> Files[ CharacterUtility.EqdpIdx( race, accessory ) - CharacterUtility.EqdpStartIdx ];
public void Dispose()
{
for( var i = 0; i < Files.Length; ++i )
{
Files[ i ]?.Dispose();
Files[ i ] = null;
}
Manipulations.Clear();
}
}
}

View file

@ -0,0 +1,59 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using Penumbra.Interop.Structs;
using Penumbra.Meta.Files;
using Penumbra.Meta.Manipulations;
namespace Penumbra.Meta.Manager;
public partial class MetaManager
{
public struct MetaManagerEqp : IDisposable
{
public ExpandedEqpFile? File = null;
public readonly Dictionary< EqpManipulation, Mod.Mod > Manipulations = new();
public MetaManagerEqp()
{ }
[Conditional( "USE_EQP" )]
public void SetFiles()
=> SetFile( File, CharacterUtility.EqpIdx );
[Conditional( "USE_EQP" )]
public void Reset()
{
if( File == null )
{
return;
}
File.Reset( Manipulations.Keys.Select( m => ( int )m.SetId ) );
Manipulations.Clear();
}
public bool ApplyMod( EqpManipulation m, Mod.Mod mod )
{
#if USE_EQP
if( !Manipulations.TryAdd( m, mod ) )
{
return false;
}
File ??= new ExpandedEqpFile();
return m.Apply( File );
#else
return false;
#endif
}
public void Dispose()
{
File?.Dispose();
File = null;
Manipulations.Clear();
}
}
}

View file

@ -0,0 +1,78 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using Penumbra.Interop.Structs;
using Penumbra.Meta.Files;
using Penumbra.Meta.Manipulations;
namespace Penumbra.Meta.Manager;
public partial class MetaManager
{
public struct MetaManagerEst : IDisposable
{
public EstFile? FaceFile = null;
public EstFile? HairFile = null;
public EstFile? BodyFile = null;
public EstFile? HeadFile = null;
public readonly Dictionary< EstManipulation, Mod.Mod > Manipulations = new();
public MetaManagerEst()
{ }
[Conditional( "USE_EST" )]
public void SetFiles()
{
SetFile( FaceFile, CharacterUtility.FaceEstIdx );
SetFile( HairFile, CharacterUtility.HairEstIdx );
SetFile( BodyFile, CharacterUtility.BodyEstIdx );
SetFile( HeadFile, CharacterUtility.HeadEstIdx );
}
[Conditional( "USE_EST" )]
public void Reset()
{
FaceFile?.Reset();
HairFile?.Reset();
BodyFile?.Reset();
HeadFile?.Reset();
Manipulations.Clear();
}
public bool ApplyMod( EstManipulation m, Mod.Mod mod )
{
#if USE_EST
if( !Manipulations.TryAdd( m, mod ) )
{
return false;
}
var file = m.Slot switch
{
EstManipulation.EstType.Hair => HairFile ??= new EstFile( EstManipulation.EstType.Hair ),
EstManipulation.EstType.Face => FaceFile ??= new EstFile( EstManipulation.EstType.Face ),
EstManipulation.EstType.Body => BodyFile ??= new EstFile( EstManipulation.EstType.Body ),
EstManipulation.EstType.Head => HeadFile ??= new EstFile( EstManipulation.EstType.Head ),
_ => throw new ArgumentOutOfRangeException(),
};
return m.Apply( file );
#else
return false;
#endif
}
public void Dispose()
{
FaceFile?.Dispose();
HairFile?.Dispose();
BodyFile?.Dispose();
HeadFile?.Dispose();
FaceFile = null;
HairFile = null;
BodyFile = null;
HeadFile = null;
Manipulations.Clear();
}
}
}

View file

@ -0,0 +1,57 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using Penumbra.Interop.Structs;
using Penumbra.Meta.Files;
using Penumbra.Meta.Manipulations;
namespace Penumbra.Meta.Manager;
public partial class MetaManager
{
public struct MetaManagerGmp : IDisposable
{
public ExpandedGmpFile? File = null;
public readonly Dictionary< GmpManipulation, Mod.Mod > Manipulations = new();
public MetaManagerGmp()
{ }
[Conditional( "USE_GMP" )]
public void Reset()
{
if( File != null )
{
File.Reset( Manipulations.Keys.Select( m => ( int )m.SetId ) );
Manipulations.Clear();
}
}
[Conditional( "USE_GMP" )]
public void SetFiles()
=> SetFile( File, CharacterUtility.GmpIdx );
public bool ApplyMod( GmpManipulation m, Mod.Mod mod )
{
#if USE_GMP
if( Manipulations.TryAdd( m, mod ) )
{
return false;
}
File ??= new ExpandedGmpFile();
return m.Apply( File );
#else
return false;
#endif
}
public void Dispose()
{
File?.Dispose();
File = null;
Manipulations.Clear();
}
}
}

View file

@ -0,0 +1,148 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using FFXIVClientStructs.FFXIV.Client.System.Resource;
using Penumbra.GameData.ByteString;
using Penumbra.Interop;
using Penumbra.Interop.Structs;
using Penumbra.Meta.Files;
using Penumbra.Meta.Manipulations;
using Penumbra.Mods;
namespace Penumbra.Meta.Manager;
public partial class MetaManager
{
public readonly struct MetaManagerImc : IDisposable
{
public readonly Dictionary< Utf8GamePath, ImcFile > Files = new();
public readonly Dictionary< ImcManipulation, Mod.Mod > Manipulations = new();
private readonly ModCollection _collection;
private readonly ResourceLoader.ResourceLoadCustomizationDelegate? _previousDelegate;
public MetaManagerImc( ModCollection collection )
{
_collection = collection;
_previousDelegate = Penumbra.ResourceLoader.ResourceLoadCustomization;
}
[Conditional( "USE_IMC" )]
public void SetFiles()
{
if( _collection.Cache == null )
{
return;
}
foreach( var path in Files.Keys )
{
_collection.Cache.ResolvedFiles[ path ] = CreateImcPath( path );
}
}
[Conditional( "USE_IMC" )]
public void Reset()
{
foreach( var (path, file) in Files )
{
_collection.Cache?.ResolvedFiles.Remove( path );
file.Reset();
}
Manipulations.Clear();
}
public unsafe bool ApplyMod( ImcManipulation m, Mod.Mod mod )
{
const uint imcExt = 0x00696D63;
#if USE_IMC
if( !Manipulations.TryAdd( m, mod ) )
{
return false;
}
var path = m.GamePath();
if( !Files.TryGetValue( path, out var file ) )
{
file = new ImcFile( path );
}
if( !m.Apply( file ) )
{
return false;
}
Files[ 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;
#else
return false;
#endif
}
public void Dispose()
{
foreach( var file in Files.Values )
{
file.Dispose();
}
Files.Clear();
Manipulations.Clear();
RestoreDelegate();
}
[Conditional( "USE_IMC" )]
private unsafe void SetupDelegate()
{
Penumbra.ResourceLoader.ResourceLoadCustomization = ImcHandler;
}
[Conditional( "USE_IMC" )]
private unsafe void RestoreDelegate()
{
if( Penumbra.ResourceLoader.ResourceLoadCustomization == ImcHandler )
{
Penumbra.ResourceLoader.ResourceLoadCustomization = _previousDelegate;
}
}
private FullPath CreateImcPath( Utf8GamePath path )
=> new($"|{_collection.Name}|{path}");
private 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.Imc.Files.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;
}
}
}

View file

@ -0,0 +1,99 @@
using System;
using Penumbra.Meta.Files;
using Penumbra.Meta.Manipulations;
using Penumbra.Mods;
namespace Penumbra.Meta.Manager;
public partial class MetaManager : IDisposable
{
public MetaManagerEqp Eqp = new();
public MetaManagerEqdp Eqdp = new();
public MetaManagerGmp Gmp = new();
public MetaManagerEst Est = new();
public MetaManagerCmp Cmp = new();
public MetaManagerImc Imc;
private static unsafe void SetFile( MetaBaseFile? file, int index )
{
if( file == null )
{
Penumbra.CharacterUtility.ResetResource( index );
}
else
{
Penumbra.CharacterUtility.SetResource( index, ( IntPtr )file.Data, file.Length );
}
}
public bool TryGetValue( MetaManipulation manip, out Mod.Mod? mod )
{
mod = manip.ManipulationType switch
{
MetaManipulation.Type.Eqp => Eqp.Manipulations.TryGetValue( manip.Eqp, out var m ) ? m : null,
MetaManipulation.Type.Gmp => Gmp.Manipulations.TryGetValue( manip.Gmp, out var m ) ? m : null,
MetaManipulation.Type.Eqdp => Eqdp.Manipulations.TryGetValue( manip.Eqdp, out var m ) ? m : null,
MetaManipulation.Type.Est => Est.Manipulations.TryGetValue( manip.Est, out var m ) ? m : null,
MetaManipulation.Type.Rsp => Cmp.Manipulations.TryGetValue( manip.Rsp, out var m ) ? m : null,
MetaManipulation.Type.Imc => Imc.Manipulations.TryGetValue( manip.Imc, out var m ) ? m : null,
_ => throw new ArgumentOutOfRangeException(),
};
return mod != null;
}
public int Count
=> Imc.Manipulations.Count
+ Eqdp.Manipulations.Count
+ Cmp.Manipulations.Count
+ Gmp.Manipulations.Count
+ Est.Manipulations.Count
+ Eqp.Manipulations.Count;
public MetaManager( ModCollection collection )
=> Imc = new MetaManagerImc( collection );
public void SetFiles()
{
Eqp.SetFiles();
Eqdp.SetFiles();
Gmp.SetFiles();
Est.SetFiles();
Cmp.SetFiles();
Imc.SetFiles();
}
public void Reset()
{
Eqp.Reset();
Eqdp.Reset();
Gmp.Reset();
Est.Reset();
Cmp.Reset();
Imc.Reset();
}
public void Dispose()
{
Eqp.Dispose();
Eqdp.Dispose();
Gmp.Dispose();
Est.Dispose();
Cmp.Dispose();
Imc.Dispose();
}
public bool ApplyMod( MetaManipulation m, Mod.Mod mod )
{
return m.ManipulationType switch
{
MetaManipulation.Type.Eqp => Eqp.ApplyMod( m.Eqp, mod ),
MetaManipulation.Type.Gmp => Gmp.ApplyMod( m.Gmp, mod ),
MetaManipulation.Type.Eqdp => Eqdp.ApplyMod( m.Eqdp, mod ),
MetaManipulation.Type.Est => Est.ApplyMod( m.Est, mod ),
MetaManipulation.Type.Rsp => Cmp.ApplyMod( m.Rsp, mod ),
MetaManipulation.Type.Imc => Imc.ApplyMod( m.Imc, mod ),
MetaManipulation.Type.Unknown => false,
_ => false,
};
}
}

View file

@ -61,6 +61,10 @@ public readonly struct ImcManipulation : IMetaManipulation< ImcManipulation >
EquipSlot = equipSlot;
}
public ImcManipulation( ImcManipulation copy, ImcEntry entry )
: this( copy.ObjectType, copy.BodySlot, copy.PrimaryId, copy.SecondaryId, copy.Variant, copy.EquipSlot, entry )
{}
public override string ToString()
=> ObjectType is ObjectType.Equipment or ObjectType.Accessory
? $"Imc - {PrimaryId} - {EquipSlot} - {Variant}"

View file

@ -219,6 +219,12 @@ public class MetaCollection
if( collection != null )
{
if( collection.DefaultData.Concat( collection.GroupData.Values.SelectMany( kvp => kvp.Values.SelectMany( l => l ) ) )
.Any( m => m.ManipulationType == MetaManipulation.Type.Unknown || !Enum.IsDefined( m.ManipulationType ) ) )
{
throw new Exception( "Invalid collection" );
}
collection.Count = collection.DefaultData.Count
+ collection.GroupData.Values.SelectMany( kvp => kvp.Values ).Sum( l => l.Count );
}

View file

@ -1,367 +0,0 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
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.Mods;
using CharacterUtility = Penumbra.Interop.Structs.CharacterUtility;
using ImcFile = Penumbra.Meta.Files.ImcFile;
namespace Penumbra.Meta;
public class MetaManager2 : IDisposable
{
public readonly List< MetaBaseFile > ChangedData = new(7 + CharacterUtility.NumEqdpFiles);
public ExpandedEqpFile? EqpFile;
public ExpandedGmpFile? GmpFile;
public ExpandedEqdpFile?[] EqdpFile = new ExpandedEqdpFile?[CharacterUtility.NumEqdpFiles];
public EstFile? FaceEstFile;
public EstFile? HairEstFile;
public EstFile? BodyEstFile;
public EstFile? HeadEstFile;
public CmpFile? CmpFile;
public readonly Dictionary< EqpManipulation, Mod.Mod > EqpManipulations = new();
public readonly Dictionary< EstManipulation, Mod.Mod > EstManipulations = new();
public readonly Dictionary< GmpManipulation, Mod.Mod > GmpManipulations = new();
public readonly Dictionary< RspManipulation, Mod.Mod > RspManipulations = new();
public readonly Dictionary< EqdpManipulation, Mod.Mod > EqdpManipulations = new();
public readonly Dictionary< ImcManipulation, Mod.Mod > ImcManipulations = 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()
{
if( EqpFile != null )
{
EqpFile.Reset( EqpManipulations.Keys.Select( m => ( int )m.SetId ) );
EqpManipulations.Clear();
ChangedData.Remove( EqpFile );
}
}
public void ResetGmp()
{
if( GmpFile != null )
{
GmpFile.Reset( GmpManipulations.Keys.Select( m => ( int )m.SetId ) );
GmpManipulations.Clear();
ChangedData.Remove( GmpFile );
}
}
public void ResetCmp()
{
if( CmpFile != null )
{
CmpFile.Reset( RspManipulations.Keys.Select( m => ( m.SubRace, m.Attribute ) ) );
RspManipulations.Clear();
ChangedData.Remove( CmpFile );
}
}
public void ResetEst()
{
FaceEstFile?.Reset();
HairEstFile?.Reset();
BodyEstFile?.Reset();
HeadEstFile?.Reset();
RspManipulations.Clear();
ChangedData.RemoveAll( f => f is EstFile );
}
public void ResetEqdp()
{
foreach( var file in EqdpFile )
{
file?.Reset( EqdpManipulations.Keys.Where( m => m.FileIndex() == file.Index ).Select( m => ( int )m.SetId ) );
}
ChangedData.RemoveAll( f => f is ExpandedEqdpFile );
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 (path, file) in ImcFiles )
{
_collection.Cache?.ResolvedFiles.Remove( path );
path.Dispose();
file.Dispose();
}
ImcFiles.Clear();
ImcManipulations.Clear();
}
public void Reset()
{
ChangedData.Clear();
ResetEqp();
ResetGmp();
ResetCmp();
ResetEst();
ResetEqdp();
ResetImc();
}
private static void Dispose< T >( ref T? file ) where T : class, IDisposable
{
if( file != null )
{
file.Dispose();
file = null;
}
}
public void Dispose()
{
ChangedData.Clear();
EqpManipulations.Clear();
EstManipulations.Clear();
GmpManipulations.Clear();
RspManipulations.Clear();
EqdpManipulations.Clear();
Dispose( ref EqpFile );
Dispose( ref GmpFile );
Dispose( ref FaceEstFile );
Dispose( ref HairEstFile );
Dispose( ref BodyEstFile );
Dispose( ref HeadEstFile );
Dispose( ref CmpFile );
for( var i = 0; i < CharacterUtility.NumEqdpFiles; ++i )
{
Dispose( ref EqdpFile[ i ] );
}
ResetImc();
}
private void AddFile( MetaBaseFile file )
{
if( !ChangedData.Contains( file ) )
{
ChangedData.Add( file );
}
}
public bool ApplyMod( EqpManipulation m, Mod.Mod mod )
{
if( !EqpManipulations.TryAdd( m, mod ) )
{
return false;
}
EqpFile ??= new ExpandedEqpFile();
if( !m.Apply( EqpFile ) )
{
return false;
}
AddFile( EqpFile );
return true;
}
public bool ApplyMod( GmpManipulation m, Mod.Mod mod )
{
if( !GmpManipulations.TryAdd( m, mod ) )
{
return false;
}
GmpFile ??= new ExpandedGmpFile();
if( !m.Apply( GmpFile ) )
{
return false;
}
AddFile( GmpFile );
return true;
}
public bool ApplyMod( EstManipulation m, Mod.Mod mod )
{
if( !EstManipulations.TryAdd( m, mod ) )
{
return false;
}
var file = m.Slot switch
{
EstManipulation.EstType.Hair => HairEstFile ??= new EstFile( EstManipulation.EstType.Hair ),
EstManipulation.EstType.Face => FaceEstFile ??= new EstFile( EstManipulation.EstType.Face ),
EstManipulation.EstType.Body => BodyEstFile ??= new EstFile( EstManipulation.EstType.Body ),
EstManipulation.EstType.Head => HeadEstFile ??= new EstFile( EstManipulation.EstType.Head ),
_ => throw new ArgumentOutOfRangeException(),
};
if( !m.Apply( file ) )
{
return false;
}
AddFile( file );
return true;
}
public bool ApplyMod( RspManipulation m, Mod.Mod mod )
{
if( !RspManipulations.TryAdd( m, mod ) )
{
return false;
}
CmpFile ??= new CmpFile();
if( !m.Apply( CmpFile ) )
{
return false;
}
AddFile( CmpFile );
return true;
}
public bool ApplyMod( EqdpManipulation m, Mod.Mod mod )
{
if( !EqdpManipulations.TryAdd( m, mod ) )
{
return false;
}
var file = EqdpFile[ m.FileIndex() - 2 ] ??= new ExpandedEqdpFile( Names.CombinedRace( m.Gender, m.Race ), m.Slot.IsAccessory() );
if( !m.Apply( file ) )
{
return false;
}
AddFile( file );
return true;
}
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
{
MetaManipulation.Type.Eqp => ApplyMod( m.Eqp, mod ),
MetaManipulation.Type.Gmp => ApplyMod( m.Gmp, mod ),
MetaManipulation.Type.Eqdp => ApplyMod( m.Eqdp, mod ),
MetaManipulation.Type.Est => ApplyMod( m.Est, mod ),
MetaManipulation.Type.Rsp => ApplyMod( m.Rsp, mod ),
MetaManipulation.Type.Imc => ApplyMod( m.Imc, mod ),
_ => throw new ArgumentOutOfRangeException(),
};
}
}

View file

@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.IO;
using System.Linq;
using Dalamud.Logging;
using Penumbra.Interop.Structs;
using Penumbra.Mod;
using Penumbra.Util;
@ -43,6 +44,14 @@ public class CollectionManager
{
ActiveCollection = newActive;
Penumbra.ResidentResources.Reload();
if( ActiveCollection.Cache == null )
{
Penumbra.CharacterUtility.ResetAll();
}
else
{
ActiveCollection.Cache.MetaManipulations.SetFiles();
}
}
else
{
@ -206,10 +215,9 @@ public class CollectionManager
public void SetDefaultCollection( ModCollection newCollection )
=> SetCollection( newCollection, DefaultCollection, c =>
{
if( !CollectionChangedTo.Any() )
if( CollectionChangedTo.Length == 0 )
{
ActiveCollection = c;
Penumbra.ResidentResources.Reload();
SetActiveCollection( c, string.Empty );
}
DefaultCollection = c;
@ -228,8 +236,7 @@ public class CollectionManager
{
if( CollectionChangedTo == characterName && CharacterCollection.TryGetValue( characterName, out var collection ) )
{
ActiveCollection = c;
Penumbra.ResidentResources.Reload();
SetActiveCollection( c, CollectionChangedTo );
}
CharacterCollection[ characterName ] = c;

View file

@ -7,8 +7,7 @@ using System.IO;
using System.Linq;
using Dalamud.Logging;
using Penumbra.GameData.ByteString;
using Penumbra.GameData.Util;
using Penumbra.Meta;
using Penumbra.Meta.Manager;
using Penumbra.Mod;
using Penumbra.Util;
@ -27,7 +26,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 MetaManager2 MetaManipulations;
public readonly MetaManager MetaManipulations;
public IReadOnlyDictionary< string, object? > ChangedItems
{
@ -39,7 +38,7 @@ public class ModCollectionCache
}
public ModCollectionCache( ModCollection collection )
=> MetaManipulations = new MetaManager2( collection );
=> MetaManipulations = new MetaManager( collection );
private static void ResetFileSeen( int size )
{
@ -258,7 +257,7 @@ public class ModCollectionCache
}
private void AddMetaFiles()
=> MetaManipulations.ApplyImcFiles( ResolvedFiles );
=> MetaManipulations.Imc.SetFiles();
private void AddSwaps( Mod.Mod mod )
{

View file

@ -2,14 +2,16 @@ using System;
using System.Collections.Generic;
using System.IO;
using Dalamud.Logging;
using Penumbra.Meta.Manipulations;
using Penumbra.Mod;
namespace Penumbra.Mods;
public partial class ModManagerNew
{
private readonly List<ModData> _mods = new();
public IReadOnlyList<ModData> Mods
private readonly List< ModData > _mods = new();
public IReadOnlyList< ModData > Mods
=> _mods;
public void DiscoverMods()
@ -35,7 +37,6 @@ public partial class ModManagerNew
//Collections.RecreateCaches();
}
}
public partial class ModManagerNew
{
public DirectoryInfo BasePath { get; private set; } = null!;
@ -52,7 +53,7 @@ public partial class ModManagerNew
{
if( Valid )
{
Valid = Directory.Exists(BasePath.FullName);
Valid = Directory.Exists( BasePath.FullName );
}
return Valid;
@ -87,7 +88,6 @@ public partial class ModManagerNew
return;
}
( BasePath, Valid ) = CreateDirectory( path );
if( Penumbra.Config.ModDirectory != BasePath.FullName )
@ -105,6 +105,6 @@ public partial class ModManagerNew
}
InitBaseDirectory( path );
BasePathChanged?.Invoke( BasePath );
BasePathChanged?.Invoke( BasePath );
}
}

View file

@ -238,7 +238,7 @@ public class Penumbra : IDalamudPlugin
//PathResolver.Dispose();
ResourceLogger.Dispose();
ResourceLoader.Dispose();
CharacterUtility.Dispose();
ShutdownWebServer();
}

View file

@ -18,11 +18,12 @@
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugType>full</DebugType>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<DefineConstants>DEBUG;TRACE;USE_EQP;USE_EQDP;USE_GMP;USE_EST;USE_CMP;USE_IMC</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<DefineConstants>$(DefineConstants)TRACE;USE_EQP;USE_EQDP;USE_GMP;USE_EST;USE_CMP;USE_IMC</DefineConstants>
</PropertyGroup>
<PropertyGroup>

View file

@ -1,28 +1,19 @@
using System;
using System.Collections.Generic;
using System.Drawing.Text;
using System.IO;
using System.Linq;
using System.Numerics;
using System.Reflection;
using System.Runtime.InteropServices;
using Dalamud.Game.ClientState.Objects.Types;
using FFXIVClientStructs.FFXIV.Client.System.Resource.Handle;
using FFXIVClientStructs.FFXIV.Client.System.String;
using ImGuiNET;
using Penumbra.Api;
using Penumbra.GameData.ByteString;
using Penumbra.GameData.Enums;
using Penumbra.GameData.Structs;
using Penumbra.GameData.Util;
using Penumbra.Interop;
using Penumbra.Meta;
using Penumbra.Mods;
using Penumbra.Meta.Files;
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;
namespace Penumbra.UI;
@ -391,6 +382,31 @@ public partial class SettingsInterface
return;
}
var eqp = 0;
ImGui.InputInt( "##EqpInput", ref eqp );
try
{
var def = ExpandedEqpFile.GetDefault( eqp );
var val = Penumbra.ModManager.Collections.ActiveCollection.Cache?.MetaManipulations.Eqp.File?[ eqp ] ?? def;
ImGui.Text( Convert.ToString( ( long )def, 2 ).PadLeft( 64, '0' ) );
ImGui.Text( Convert.ToString( ( long )val, 2 ).PadLeft( 64, '0' ) );
}
catch
{ }
var eqdp = 0;
ImGui.InputInt( "##EqdpInput", ref eqdp );
try
{
var def = ExpandedEqdpFile.GetDefault(GenderRace.MidlanderMale, false, eqdp );
var val = Penumbra.ModManager.Collections.ActiveCollection.Cache?.MetaManipulations.Eqdp.File(GenderRace.MidlanderMale, false)?[eqdp] ?? def;
ImGui.Text( Convert.ToString( ( ushort )def, 2 ).PadLeft( 16, '0' ) );
ImGui.Text( Convert.ToString( ( ushort )val, 2 ).PadLeft( 16, '0' ) );
}
catch
{ }
if( !ImGui.BeginTable( "##CharacterUtilityDebugList", 6, ImGuiTableFlags.RowBg | ImGuiTableFlags.SizingFixedFit, -Vector2.UnitX ) )
{
return;
@ -398,9 +414,10 @@ public partial class SettingsInterface
using var end = ImGuiRaii.DeferredEnd( ImGui.EndTable );
for( var i = 0; i < CharacterUtility.NumRelevantResources; ++i )
for( var i = 0; i < CharacterUtility.RelevantIndices.Length; ++i )
{
var resource = ( ResourceHandle* )Penumbra.CharacterUtility.Address->Resources[ i ];
var idx = CharacterUtility.RelevantIndices[ i ];
var resource = ( ResourceHandle* )Penumbra.CharacterUtility.Address->Resources[ idx ];
ImGui.TableNextColumn();
ImGui.Text( $"0x{( ulong )resource:X}" );
ImGui.TableNextColumn();

View file

@ -1,11 +1,13 @@
using System;
using System.Linq;
using System.Numerics;
using System.Reflection.Metadata.Ecma335;
using Dalamud.Interface;
using FFXIVClientStructs.FFXIV.Client.System.Resource;
using FFXIVClientStructs.FFXIV.Client.System.Resource.Handle;
using FFXIVClientStructs.STD;
using ImGuiNET;
using Penumbra.GameData.ByteString;
using Penumbra.Interop;
using Penumbra.UI.Custom;
@ -55,6 +57,11 @@ public partial class SettingsInterface
ResourceLoader.IterateResourceMap( map, ( hash, r ) =>
{
if( _filter.Length != 0 && !r->FileName.ToString().Contains( _filter, StringComparison.InvariantCultureIgnoreCase ) )
{
return;
}
ImGui.TableNextColumn();
ImGui.Text( $"0x{hash:X8}" );
ImGui.TableNextColumn();
@ -148,6 +155,8 @@ public partial class SettingsInterface
} );
}
private string _filter = string.Empty;
private unsafe void DrawResourceManagerTab()
{
if( !ImGui.BeginTabItem( "Resource Manager Tab" ) )
@ -164,6 +173,8 @@ public partial class SettingsInterface
return;
}
ImGui.InputTextWithHint( "##resourceFilter", "Filter...", ref _filter, Utf8GamePath.MaxGamePathLength );
raii.Push( ImGui.EndChild );
if( !ImGui.BeginChild( "##ResourceManagerChild", -Vector2.One, true ) )
{