mirror of
https://github.com/xivdev/Penumbra.git
synced 2025-12-12 18:27:24 +01:00
Metamanipulations seemingly working.
This commit is contained in:
parent
707570615c
commit
6f527a1dbc
26 changed files with 1637 additions and 1237 deletions
|
|
@ -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 ) );
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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 )
|
||||
|
|
|
|||
|
|
@ -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 )
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
59
Penumbra/Meta/Manager/MetaManager.Cmp.cs
Normal file
59
Penumbra/Meta/Manager/MetaManager.Cmp.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
72
Penumbra/Meta/Manager/MetaManager.Eqdp.cs
Normal file
72
Penumbra/Meta/Manager/MetaManager.Eqdp.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
59
Penumbra/Meta/Manager/MetaManager.Eqp.cs
Normal file
59
Penumbra/Meta/Manager/MetaManager.Eqp.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
78
Penumbra/Meta/Manager/MetaManager.Est.cs
Normal file
78
Penumbra/Meta/Manager/MetaManager.Est.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
57
Penumbra/Meta/Manager/MetaManager.Gmp.cs
Normal file
57
Penumbra/Meta/Manager/MetaManager.Gmp.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
148
Penumbra/Meta/Manager/MetaManager.Imc.cs
Normal file
148
Penumbra/Meta/Manager/MetaManager.Imc.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
99
Penumbra/Meta/Manager/MetaManager.cs
Normal file
99
Penumbra/Meta/Manager/MetaManager.cs
Normal 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,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -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}"
|
||||
|
|
|
|||
|
|
@ -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 );
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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 )
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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 );
|
||||
}
|
||||
}
|
||||
|
|
@ -238,7 +238,7 @@ public class Penumbra : IDalamudPlugin
|
|||
//PathResolver.Dispose();
|
||||
ResourceLogger.Dispose();
|
||||
ResourceLoader.Dispose();
|
||||
|
||||
CharacterUtility.Dispose();
|
||||
|
||||
ShutdownWebServer();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -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 ) )
|
||||
{
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue