Rework some metastuff.

This commit is contained in:
Ottermandias 2022-08-27 00:47:03 +02:00
parent 53818f3556
commit f0b970c102
25 changed files with 312 additions and 221 deletions

View file

@ -2,8 +2,6 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel; using System.ComponentModel;
using System.Linq; using System.Linq;
using System.Reflection.Metadata.Ecma335;
using Newtonsoft.Json;
namespace Penumbra.GameData.Enums; namespace Penumbra.GameData.Enums;
@ -31,7 +29,9 @@ public enum EquipSlot : byte
FullBody = 19, FullBody = 19,
BodyHands = 20, BodyHands = 20,
BodyLegsFeet = 21, BodyLegsFeet = 21,
All = 22, // Not officially existing ChestHands = 22,
Nothing = 23,
All = 24, // Not officially existing
} }
public static class EquipSlotExtensions public static class EquipSlotExtensions
@ -111,7 +111,8 @@ public static class EquipSlotExtensions
EquipSlot.FullBody => EquipSlot.Body, EquipSlot.FullBody => EquipSlot.Body,
EquipSlot.BodyHands => EquipSlot.Body, EquipSlot.BodyHands => EquipSlot.Body,
EquipSlot.BodyLegsFeet => EquipSlot.Body, EquipSlot.BodyLegsFeet => EquipSlot.Body,
_ => throw new InvalidEnumArgumentException(), EquipSlot.ChestHands => EquipSlot.Body,
_ => throw new InvalidEnumArgumentException($"{value} ({(int) value}) is not valid."),
}; };
} }

View file

@ -1,14 +1,12 @@
using System; using System;
using System.IO; using System.IO;
using Lumina.Data.Files; using Lumina.Data.Files;
using OtterGui;
using SixLabors.ImageSharp; using SixLabors.ImageSharp;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
using Functions = Penumbra.GameData.Util.Functions;
namespace Penumbra.Import.Dds; namespace Penumbra.Import.Dds;
public class TextureImporter public static class TextureImporter
{ {
private static void WriteHeader( byte[] target, int width, int height ) private static void WriteHeader( byte[] target, int width, int height )
{ {
@ -91,7 +89,4 @@ public class TextureImporter
texData = buffer; texData = buffer;
return true; return true;
} }
public void Import( string inputFile )
{ }
} }

View file

@ -7,6 +7,8 @@ namespace Penumbra.Interop;
public unsafe class CharacterUtility : IDisposable public unsafe class CharacterUtility : IDisposable
{ {
public record struct InternalIndex( int Value );
// A static pointer to the CharacterUtility address. // A static pointer to the CharacterUtility address.
[Signature( "48 8B 05 ?? ?? ?? ?? 83 B9", ScanType = ScanType.StaticAddress )] [Signature( "48 8B 05 ?? ?? ?? ?? 83 B9", ScanType = ScanType.StaticAddress )]
private readonly Structs.CharacterUtility** _characterUtilityAddress = null; private readonly Structs.CharacterUtility** _characterUtilityAddress = null;
@ -28,25 +30,22 @@ public unsafe class CharacterUtility : IDisposable
// The relevant indices depend on which meta manipulations we allow for. // The relevant indices depend on which meta manipulations we allow for.
// The defines are set in the project configuration. // The defines are set in the project configuration.
public static readonly int[] RelevantIndices public static readonly Structs.CharacterUtility.Index[]
= Array.Empty< int >() RelevantIndices = Enum.GetValues< Structs.CharacterUtility.Index >();
.Append( Structs.CharacterUtility.EqpIdx )
.Append( Structs.CharacterUtility.GmpIdx ) public static readonly InternalIndex[] ReverseIndices
.Concat( Enumerable.Range( Structs.CharacterUtility.EqdpStartIdx, Structs.CharacterUtility.NumEqdpFiles ) = Enumerable.Range( 0, Structs.CharacterUtility.TotalNumResources )
.Where( i => i is not Structs.CharacterUtility.EqdpStartIdx + 15 or Structs.CharacterUtility.EqdpStartIdx + 15 + Structs.CharacterUtility.NumEqdpFiles / 2 ) ) // TODO: Female Hrothgar .Select( i => new InternalIndex( Array.IndexOf( RelevantIndices, (Structs.CharacterUtility.Index) i ) ) )
.Append( Structs.CharacterUtility.HumanCmpIdx )
.Concat( Enumerable.Range( Structs.CharacterUtility.FaceEstIdx, 4 ) )
.ToArray(); .ToArray();
public static readonly int[] ReverseIndices
= Enumerable.Range( 0, Structs.CharacterUtility.NumResources )
.Select( i => Array.IndexOf( RelevantIndices, i ) ).ToArray();
private readonly (IntPtr Address, int Size)[] _defaultResources = new (IntPtr, int)[RelevantIndices.Length];
public readonly (IntPtr Address, int Size)[] DefaultResources = new (IntPtr, int)[RelevantIndices.Length]; public (IntPtr Address, int Size) DefaultResource( Structs.CharacterUtility.Index idx )
=> _defaultResources[ ReverseIndices[ ( int )idx ].Value ];
public (IntPtr Address, int Size) DefaultResource( int fullIdx ) public (IntPtr Address, int Size) DefaultResource( InternalIndex idx )
=> DefaultResources[ ReverseIndices[ fullIdx ] ]; => _defaultResources[ idx.Value ];
public CharacterUtility() public CharacterUtility()
{ {
@ -70,13 +69,13 @@ public unsafe class CharacterUtility : IDisposable
for( var i = 0; i < RelevantIndices.Length; ++i ) for( var i = 0; i < RelevantIndices.Length; ++i )
{ {
if( DefaultResources[ i ].Size == 0 ) if( _defaultResources[ i ].Size == 0 )
{ {
var resource = ( Structs.ResourceHandle* )Address->Resources[ RelevantIndices[ i ] ]; var resource = Address->Resource( RelevantIndices[i] );
var data = resource->GetData(); var data = resource->GetData();
if( data.Data != IntPtr.Zero && data.Length != 0 ) if( data.Data != IntPtr.Zero && data.Length != 0 )
{ {
DefaultResources[ i ] = data; _defaultResources[ i ] = data;
} }
else else
{ {
@ -94,7 +93,7 @@ public unsafe class CharacterUtility : IDisposable
} }
// Set the data of one of the stored resources to a given pointer and length. // Set the data of one of the stored resources to a given pointer and length.
public bool SetResource( int resourceIdx, IntPtr data, int length ) public bool SetResource( Structs.CharacterUtility.Index resourceIdx, IntPtr data, int length )
{ {
if( !Ready ) if( !Ready )
{ {
@ -109,7 +108,7 @@ public unsafe class CharacterUtility : IDisposable
} }
// Reset the data of one of the stored resources to its default values. // Reset the data of one of the stored resources to its default values.
public void ResetResource( int resourceIdx ) public void ResetResource( Structs.CharacterUtility.Index resourceIdx )
{ {
if( !Ready ) if( !Ready )
{ {
@ -117,8 +116,7 @@ public unsafe class CharacterUtility : IDisposable
return; return;
} }
var relevantIdx = ReverseIndices[ resourceIdx ]; var (data, length) = DefaultResource( resourceIdx);
var (data, length) = DefaultResources[ relevantIdx ];
var resource = Address->Resource( resourceIdx ); var resource = Address->Resource( resourceIdx );
PluginLog.Verbose( "Reset resource {Idx} to default at 0x{DefaultData:X} ({NewLength} bytes).", resourceIdx, ( ulong )data, length ); PluginLog.Verbose( "Reset resource {Idx} to default at 0x{DefaultData:X} ({NewLength} bytes).", resourceIdx, ( ulong )data, length );
resource->SetData( data, length ); resource->SetData( data, length );

View file

@ -160,11 +160,11 @@ public unsafe partial class PathResolver
{ {
if( _inChangeCustomize ) if( _inChangeCustomize )
{ {
using var rsp = MetaChanger.ChangeCmp( _parent, drawObject );
_rspSetupCharacterHook.Original( drawObject, unk2, unk3, unk4, unk5 ); _rspSetupCharacterHook.Original( drawObject, unk2, unk3, unk4, unk5 );
} }
else else
{ {
using var rsp = MetaChanger.ChangeCmp( _parent, drawObject );
_rspSetupCharacterHook.Original( drawObject, unk2, unk3, unk4, unk5 ); _rspSetupCharacterHook.Original( drawObject, unk2, unk3, unk4, unk5 );
} }
} }

View file

@ -8,87 +8,153 @@ namespace Penumbra.Interop.Structs;
[StructLayout( LayoutKind.Explicit )] [StructLayout( LayoutKind.Explicit )]
public unsafe struct CharacterUtility public unsafe struct CharacterUtility
{ {
// TODO: female Hrothgar public enum Index : int
public static readonly int[] EqdpIndices {
= Enumerable.Range( EqdpStartIdx, NumEqdpFiles ).Where( i => i != EqdpStartIdx + 15 && i != EqdpStartIdx + 15 + NumEqdpFiles / 2 ).ToArray(); Eqp = 0,
Gmp = 2,
public const int NumResources = 86; Eqdp0101 = 3,
public const int EqpIdx = 0; Eqdp0201,
public const int GmpIdx = 2; Eqdp0301,
public const int HumanCmpIdx = 64; Eqdp0401,
public const int FaceEstIdx = 65; Eqdp0501,
public const int HairEstIdx = 66; Eqdp0601,
public const int HeadEstIdx = 67; Eqdp0701,
public const int BodyEstIdx = 68; Eqdp0801,
public const int EqdpStartIdx = 3; Eqdp0901,
public const int NumEqdpFiles = 2 * 28; Eqdp1001,
Eqdp1101,
Eqdp1201,
Eqdp1301,
Eqdp1401,
Eqdp1501,
public static int EqdpIdx( GenderRace raceCode, bool accessory ) //Eqdp1601, // TODO: female Hrothgar
=> ( accessory ? NumEqdpFiles / 2 : 0 ) Eqdp1701 = Eqdp1501 + 2,
+ ( int )raceCode switch Eqdp1801,
{ Eqdp0104,
0101 => EqdpStartIdx, Eqdp0204,
0201 => EqdpStartIdx + 1, Eqdp0504,
0301 => EqdpStartIdx + 2, Eqdp0604,
0401 => EqdpStartIdx + 3, Eqdp0704,
0501 => EqdpStartIdx + 4, Eqdp0804,
0601 => EqdpStartIdx + 5, Eqdp1304,
0701 => EqdpStartIdx + 6, Eqdp1404,
0801 => EqdpStartIdx + 7, Eqdp9104,
0901 => EqdpStartIdx + 8, Eqdp9204,
1001 => EqdpStartIdx + 9,
1101 => EqdpStartIdx + 10, Eqdp0101Acc,
1201 => EqdpStartIdx + 11, Eqdp0201Acc,
1301 => EqdpStartIdx + 12, Eqdp0301Acc,
1401 => EqdpStartIdx + 13, Eqdp0401Acc,
1501 => EqdpStartIdx + 14, Eqdp0501Acc,
1601 => EqdpStartIdx + 15, // TODO: female Hrothgar Eqdp0601Acc,
1701 => EqdpStartIdx + 16, Eqdp0701Acc,
1801 => EqdpStartIdx + 17, Eqdp0801Acc,
0104 => EqdpStartIdx + 18, Eqdp0901Acc,
0204 => EqdpStartIdx + 19, Eqdp1001Acc,
0504 => EqdpStartIdx + 20, Eqdp1101Acc,
0604 => EqdpStartIdx + 21, Eqdp1201Acc,
0704 => EqdpStartIdx + 22, Eqdp1301Acc,
0804 => EqdpStartIdx + 23, Eqdp1401Acc,
1304 => EqdpStartIdx + 24, Eqdp1501Acc,
1404 => EqdpStartIdx + 25,
9104 => EqdpStartIdx + 26, //Eqdp1601Acc, // TODO: female Hrothgar
9204 => EqdpStartIdx + 27, Eqdp1701Acc = Eqdp1501Acc + 2,
_ => -1, Eqdp1801Acc,
}; Eqdp0104Acc,
Eqdp0204Acc,
Eqdp0504Acc,
Eqdp0604Acc,
Eqdp0704Acc,
Eqdp0804Acc,
Eqdp1304Acc,
Eqdp1404Acc,
Eqdp9104Acc,
Eqdp9204Acc,
HumanCmp = 64,
FaceEst,
HairEst,
HeadEst,
BodyEst,
}
public static readonly Index[] EqdpIndices = Enum.GetNames< Index >()
.Zip( Enum.GetValues< Index >() )
.Where( n => n.First.StartsWith( "Eqdp" ) )
.Select( n => n.Second ).ToArray();
public const int TotalNumResources = 87;
public static Index EqdpIdx( GenderRace raceCode, bool accessory )
=> +( int )raceCode switch
{
0101 => accessory ? Index.Eqdp0101Acc : Index.Eqdp0101,
0201 => accessory ? Index.Eqdp0201Acc : Index.Eqdp0201,
0301 => accessory ? Index.Eqdp0301Acc : Index.Eqdp0301,
0401 => accessory ? Index.Eqdp0401Acc : Index.Eqdp0401,
0501 => accessory ? Index.Eqdp0501Acc : Index.Eqdp0501,
0601 => accessory ? Index.Eqdp0601Acc : Index.Eqdp0601,
0701 => accessory ? Index.Eqdp0701Acc : Index.Eqdp0701,
0801 => accessory ? Index.Eqdp0801Acc : Index.Eqdp0801,
0901 => accessory ? Index.Eqdp0901Acc : Index.Eqdp0901,
1001 => accessory ? Index.Eqdp1001Acc : Index.Eqdp1001,
1101 => accessory ? Index.Eqdp1101Acc : Index.Eqdp1101,
1201 => accessory ? Index.Eqdp1201Acc : Index.Eqdp1201,
1301 => accessory ? Index.Eqdp1301Acc : Index.Eqdp1301,
1401 => accessory ? Index.Eqdp1401Acc : Index.Eqdp1401,
1501 => accessory ? Index.Eqdp1501Acc : Index.Eqdp1501,
//1601 => accessory ? RelevantIndex.Eqdp1601Acc : RelevantIndex.Eqdp1601, Female Hrothgar
1701 => accessory ? Index.Eqdp1701Acc : Index.Eqdp1701,
1801 => accessory ? Index.Eqdp1801Acc : Index.Eqdp1801,
0104 => accessory ? Index.Eqdp0104Acc : Index.Eqdp0104,
0204 => accessory ? Index.Eqdp0204Acc : Index.Eqdp0204,
0504 => accessory ? Index.Eqdp0504Acc : Index.Eqdp0504,
0604 => accessory ? Index.Eqdp0604Acc : Index.Eqdp0604,
0704 => accessory ? Index.Eqdp0704Acc : Index.Eqdp0704,
0804 => accessory ? Index.Eqdp0804Acc : Index.Eqdp0804,
1304 => accessory ? Index.Eqdp1304Acc : Index.Eqdp1304,
1404 => accessory ? Index.Eqdp1404Acc : Index.Eqdp1404,
9104 => accessory ? Index.Eqdp9104Acc : Index.Eqdp9104,
9204 => accessory ? Index.Eqdp9204Acc : Index.Eqdp9204,
_ => ( Index )( -1 ),
};
[FieldOffset( 0 )] [FieldOffset( 0 )]
public void* VTable; public void* VTable;
[FieldOffset( 8 )] [FieldOffset( 8 )]
public fixed ulong Resources[NumResources]; public fixed ulong Resources[TotalNumResources];
[FieldOffset( 8 + EqpIdx * 8 )] [FieldOffset( 8 + ( int )Index.Eqp * 8 )]
public ResourceHandle* EqpResource; public ResourceHandle* EqpResource;
[FieldOffset( 8 + GmpIdx * 8 )] [FieldOffset( 8 + ( int )Index.Gmp * 8 )]
public ResourceHandle* GmpResource; public ResourceHandle* GmpResource;
public ResourceHandle* Resource( int idx ) public ResourceHandle* Resource( int idx )
=> ( ResourceHandle* )Resources[ idx ]; => ( ResourceHandle* )Resources[ idx ];
public ResourceHandle* EqdpResource( GenderRace raceCode, bool accessory ) public ResourceHandle* Resource( Index idx )
=> Resource( EqdpIdx( raceCode, accessory ) ); => Resource( ( int )idx );
[FieldOffset( 8 + HumanCmpIdx * 8 )] public ResourceHandle* EqdpResource( GenderRace raceCode, bool accessory )
=> Resource( ( int )EqdpIdx( raceCode, accessory ) );
[FieldOffset( 8 + ( int )Index.HumanCmp * 8 )]
public ResourceHandle* HumanCmpResource; public ResourceHandle* HumanCmpResource;
[FieldOffset( 8 + FaceEstIdx * 8 )] [FieldOffset( 8 + ( int )Index.FaceEst * 8 )]
public ResourceHandle* FaceEstResource; public ResourceHandle* FaceEstResource;
[FieldOffset( 8 + HairEstIdx * 8 )] [FieldOffset( 8 + ( int )Index.HairEst * 8 )]
public ResourceHandle* HairEstResource; public ResourceHandle* HairEstResource;
[FieldOffset( 8 + BodyEstIdx * 8 )] [FieldOffset( 8 + ( int )Index.BodyEst * 8 )]
public ResourceHandle* BodyEstResource; public ResourceHandle* BodyEstResource;
[FieldOffset( 8 + HeadEstIdx * 8 )] [FieldOffset( 8 + ( int )Index.HeadEst * 8 )]
public ResourceHandle* HeadEstResource; public ResourceHandle* HeadEstResource;
// not included resources have no known use case. // not included resources have no known use case.

View file

@ -11,6 +11,9 @@ namespace Penumbra.Meta.Files;
// We only support manipulating the racial scaling parameters at the moment. // We only support manipulating the racial scaling parameters at the moment.
public sealed unsafe class CmpFile : MetaBaseFile public sealed unsafe class CmpFile : MetaBaseFile
{ {
public static readonly Interop.CharacterUtility.InternalIndex InternalIndex =
Interop.CharacterUtility.ReverseIndices[ ( int )CharacterUtility.Index.HumanCmp ];
private const int RacialScalingStart = 0x2A800; private const int RacialScalingStart = 0x2A800;
public float this[ SubRace subRace, RspAttribute attribute ] public float this[ SubRace subRace, RspAttribute attribute ]
@ -31,7 +34,7 @@ public sealed unsafe class CmpFile : MetaBaseFile
} }
public CmpFile() public CmpFile()
: base( CharacterUtility.HumanCmpIdx ) : base( CharacterUtility.Index.HumanCmp )
{ {
AllocateData( DefaultData.Length ); AllocateData( DefaultData.Length );
Reset(); Reset();
@ -39,7 +42,7 @@ public sealed unsafe class CmpFile : MetaBaseFile
public static float GetDefault( SubRace subRace, RspAttribute attribute ) public static float GetDefault( SubRace subRace, RspAttribute attribute )
{ {
var data = ( byte* )Penumbra.CharacterUtility.DefaultResource( CharacterUtility.HumanCmpIdx ).Address; var data = ( byte* )Penumbra.CharacterUtility.DefaultResource( InternalIndex ).Address;
return *( float* )( data + RacialScalingStart + ToRspIndex( subRace ) * RspEntry.ByteSize + ( int )attribute * 4 ); return *( float* )( data + RacialScalingStart + ToRspIndex( subRace ) * RspEntry.ByteSize + ( int )attribute * 4 );
} }

View file

@ -114,8 +114,8 @@ public sealed unsafe class ExpandedEqdpFile : MetaBaseFile
public EqdpEntry GetDefault( int setIdx ) public EqdpEntry GetDefault( int setIdx )
=> GetDefault( Index, setIdx ); => GetDefault( Index, setIdx );
public static EqdpEntry GetDefault( int fileIdx, int setIdx ) public static EqdpEntry GetDefault( Interop.CharacterUtility.InternalIndex idx, int setIdx )
=> GetDefault( ( byte* )Penumbra.CharacterUtility.DefaultResource( fileIdx ).Address, setIdx ); => GetDefault( ( byte* )Penumbra.CharacterUtility.DefaultResource( idx ).Address, setIdx );
public static EqdpEntry GetDefault( byte* data, int setIdx ) public static EqdpEntry GetDefault( byte* data, int setIdx )
{ {
@ -139,5 +139,5 @@ public sealed unsafe class ExpandedEqdpFile : MetaBaseFile
} }
public static EqdpEntry GetDefault( GenderRace raceCode, bool accessory, int setIdx ) public static EqdpEntry GetDefault( GenderRace raceCode, bool accessory, int setIdx )
=> GetDefault( CharacterUtility.EqdpIdx( raceCode, accessory ), setIdx ); => GetDefault( Interop.CharacterUtility.ReverseIndices[ ( int )CharacterUtility.EqdpIdx( raceCode, accessory ) ], setIdx );
} }

View file

@ -76,15 +76,15 @@ public unsafe class ExpandedEqpGmpBase : MetaBaseFile
} }
public ExpandedEqpGmpBase( bool gmp ) public ExpandedEqpGmpBase( bool gmp )
: base( gmp ? CharacterUtility.GmpIdx : CharacterUtility.EqpIdx ) : base( gmp ? CharacterUtility.Index.Gmp : CharacterUtility.Index.Eqp )
{ {
AllocateData( MaxSize ); AllocateData( MaxSize );
Reset(); Reset();
} }
protected static ulong GetDefaultInternal( int fileIdx, int setIdx, ulong def ) protected static ulong GetDefaultInternal( Interop.CharacterUtility.InternalIndex fileIndex, int setIdx, ulong def )
{ {
var data = ( byte* )Penumbra.CharacterUtility.DefaultResources[ fileIdx ].Address; var data = ( byte* )Penumbra.CharacterUtility.DefaultResource(fileIndex).Address;
if( setIdx == 0 ) if( setIdx == 0 )
{ {
setIdx = 1; setIdx = 1;
@ -112,6 +112,9 @@ public unsafe class ExpandedEqpGmpBase : MetaBaseFile
public sealed class ExpandedEqpFile : ExpandedEqpGmpBase, IEnumerable<EqpEntry> public sealed class ExpandedEqpFile : ExpandedEqpGmpBase, IEnumerable<EqpEntry>
{ {
public static readonly Interop.CharacterUtility.InternalIndex InternalIndex =
Interop.CharacterUtility.ReverseIndices[ (int) CharacterUtility.Index.Eqp ];
public ExpandedEqpFile() public ExpandedEqpFile()
: base( false ) : base( false )
{ } { }
@ -124,7 +127,7 @@ public sealed class ExpandedEqpFile : ExpandedEqpGmpBase, IEnumerable<EqpEntry>
public static EqpEntry GetDefault( int setIdx ) public static EqpEntry GetDefault( int setIdx )
=> ( EqpEntry )GetDefaultInternal( CharacterUtility.EqpIdx, setIdx, ( ulong )Eqp.DefaultEntry ); => ( EqpEntry )GetDefaultInternal( InternalIndex, setIdx, ( ulong )Eqp.DefaultEntry );
protected override unsafe void SetEmptyBlock( int idx ) protected override unsafe void SetEmptyBlock( int idx )
{ {
@ -156,6 +159,9 @@ public sealed class ExpandedEqpFile : ExpandedEqpGmpBase, IEnumerable<EqpEntry>
public sealed class ExpandedGmpFile : ExpandedEqpGmpBase, IEnumerable<GmpEntry> public sealed class ExpandedGmpFile : ExpandedEqpGmpBase, IEnumerable<GmpEntry>
{ {
public static readonly Interop.CharacterUtility.InternalIndex InternalIndex =
Interop.CharacterUtility.ReverseIndices[( int )CharacterUtility.Index.Gmp];
public ExpandedGmpFile() public ExpandedGmpFile()
: base( true ) : base( true )
{ } { }
@ -167,7 +173,7 @@ public sealed class ExpandedGmpFile : ExpandedEqpGmpBase, IEnumerable<GmpEntry>
} }
public static GmpEntry GetDefault( int setIdx ) public static GmpEntry GetDefault( int setIdx )
=> ( GmpEntry )GetDefaultInternal( CharacterUtility.GmpIdx, setIdx, ( ulong )GmpEntry.Default ); => ( GmpEntry )GetDefaultInternal( InternalIndex, setIdx, ( ulong )GmpEntry.Default );
public void Reset( IEnumerable< int > entries ) public void Reset( IEnumerable< int > entries )
{ {

View file

@ -2,6 +2,7 @@ using System;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using Penumbra.GameData.Enums; using Penumbra.GameData.Enums;
using Penumbra.GameData.Util; using Penumbra.GameData.Util;
using Penumbra.Interop.Structs;
using Penumbra.Meta.Manipulations; using Penumbra.Meta.Manipulations;
namespace Penumbra.Meta.Files; namespace Penumbra.Meta.Files;
@ -53,7 +54,7 @@ public sealed unsafe class EstFile : MetaBaseFile
ResizeResources( Length + IncreaseSize ); ResizeResources( Length + IncreaseSize );
} }
var control = ( Info* )( Data + 4 ); var control = ( Info* )( Data + 4 );
var entries = ( ushort* )( control + Count ); var entries = ( ushort* )( control + Count );
for( var i = Count - 1; i >= idx; --i ) for( var i = Count - 1; i >= idx; --i )
@ -95,7 +96,7 @@ public sealed unsafe class EstFile : MetaBaseFile
for( var i = idx; i < Count - 1; ++i ) for( var i = idx; i < Count - 1; ++i )
{ {
entries[i - 2] = entries[i + 1]; entries[ i - 2 ] = entries[ i + 1 ];
} }
entries[ Count - 3 ] = 0; entries[ Count - 3 ] = 0;
@ -174,7 +175,7 @@ public sealed unsafe class EstFile : MetaBaseFile
} }
public EstFile( EstManipulation.EstType estType ) public EstFile( EstManipulation.EstType estType )
: base( ( int )estType ) : base( ( CharacterUtility.Index )estType )
{ {
var length = DefaultData.Length; var length = DefaultData.Length;
AllocateData( length + IncreaseSize ); AllocateData( length + IncreaseSize );
@ -182,11 +183,11 @@ public sealed unsafe class EstFile : MetaBaseFile
} }
public ushort GetDefault( GenderRace genderRace, ushort setId ) public ushort GetDefault( GenderRace genderRace, ushort setId )
=> GetDefault( ( EstManipulation.EstType )Index, genderRace, setId ); => GetDefault( Index, genderRace, setId );
public static ushort GetDefault( EstManipulation.EstType estType, GenderRace genderRace, ushort setId ) public static ushort GetDefault( Interop.CharacterUtility.InternalIndex index, GenderRace genderRace, ushort setId )
{ {
var data = ( byte* )Penumbra.CharacterUtility.DefaultResource( ( int )estType ).Address; var data = ( byte* )Penumbra.CharacterUtility.DefaultResource( index ).Address;
var count = *( int* )data; var count = *( int* )data;
var span = new ReadOnlySpan< Info >( data + 4, count ); var span = new ReadOnlySpan< Info >( data + 4, count );
var (idx, found) = FindEntry( span, genderRace, setId ); var (idx, found) = FindEntry( span, genderRace, setId );
@ -197,4 +198,10 @@ public sealed unsafe class EstFile : MetaBaseFile
return *( ushort* )( data + 4 + count * EntryDescSize + idx * EntrySize ); return *( ushort* )( data + 4 + count * EntryDescSize + idx * EntrySize );
} }
public static ushort GetDefault( CharacterUtility.Index index, GenderRace genderRace, ushort setId )
=> GetDefault( Interop.CharacterUtility.ReverseIndices[ ( int )index ], genderRace, setId );
public static ushort GetDefault( EstManipulation.EstType estType, GenderRace genderRace, ushort setId )
=> GetDefault( ( CharacterUtility.Index )estType, genderRace, setId );
} }

View file

@ -1,6 +1,7 @@
using System; using System;
using Dalamud.Memory; using Dalamud.Memory;
using Penumbra.GameData.Util; using Penumbra.GameData.Util;
using CharacterUtility = Penumbra.Interop.CharacterUtility;
namespace Penumbra.Meta.Files; namespace Penumbra.Meta.Files;
@ -8,10 +9,10 @@ public unsafe class MetaBaseFile : IDisposable
{ {
public byte* Data { get; private set; } public byte* Data { get; private set; }
public int Length { get; private set; } public int Length { get; private set; }
public int Index { get; } public CharacterUtility.InternalIndex Index { get; }
public MetaBaseFile( int idx ) public MetaBaseFile( Interop.Structs.CharacterUtility.Index idx )
=> Index = idx; => Index = CharacterUtility.ReverseIndices[(int) idx];
protected (IntPtr Data, int Length) DefaultData protected (IntPtr Data, int Length) DefaultData
=> Penumbra.CharacterUtility.DefaultResource( Index ); => Penumbra.CharacterUtility.DefaultResource( Index );

View file

@ -13,10 +13,10 @@ public partial class MetaManager
private readonly List< RspManipulation > _cmpManipulations = new(); private readonly List< RspManipulation > _cmpManipulations = new();
public void SetCmpFiles() public void SetCmpFiles()
=> SetFile( _cmpFile, CharacterUtility.HumanCmpIdx ); => SetFile( _cmpFile, CharacterUtility.Index.HumanCmp );
public static void ResetCmpFiles() public static void ResetCmpFiles()
=> SetFile( null, CharacterUtility.HumanCmpIdx ); => SetFile( null, CharacterUtility.Index.HumanCmp );
public void ResetCmp() public void ResetCmp()
{ {

View file

@ -11,7 +11,7 @@ namespace Penumbra.Meta.Manager;
public partial class MetaManager public partial class MetaManager
{ {
private readonly ExpandedEqdpFile?[] _eqdpFiles = new ExpandedEqdpFile?[CharacterUtility.NumEqdpFiles - 2]; // TODO: female Hrothgar private readonly ExpandedEqdpFile?[] _eqdpFiles = new ExpandedEqdpFile[CharacterUtility.EqdpIndices.Length]; // TODO: female Hrothgar
private readonly List< EqdpManipulation > _eqdpManipulations = new(); private readonly List< EqdpManipulation > _eqdpManipulations = new();
@ -33,9 +33,10 @@ public partial class MetaManager
public void ResetEqdp() public void ResetEqdp()
{ {
foreach( var file in _eqdpFiles ) foreach( var file in _eqdpFiles.OfType<ExpandedEqdpFile>() )
{ {
file?.Reset( _eqdpManipulations.Where( m => m.FileIndex() == file.Index ).Select( m => ( int )m.SetId ) ); var relevant = Interop.CharacterUtility.RelevantIndices[ file.Index.Value ];
file.Reset( _eqdpManipulations.Where( m => m.FileIndex() == relevant ).Select( m => ( int )m.SetId ) );
} }
_eqdpManipulations.Clear(); _eqdpManipulations.Clear();

View file

@ -13,10 +13,10 @@ public partial class MetaManager
private readonly List< EqpManipulation > _eqpManipulations = new(); private readonly List< EqpManipulation > _eqpManipulations = new();
public void SetEqpFiles() public void SetEqpFiles()
=> SetFile( _eqpFile, CharacterUtility.EqpIdx ); => SetFile( _eqpFile, CharacterUtility.Index.Eqp );
public static void ResetEqpFiles() public static void ResetEqpFiles()
=> SetFile( null, CharacterUtility.EqpIdx ); => SetFile( null, CharacterUtility.Index.Eqp );
public void ResetEqp() public void ResetEqp()
{ {

View file

@ -19,18 +19,18 @@ public partial class MetaManager
public void SetEstFiles() public void SetEstFiles()
{ {
SetFile( _estFaceFile, CharacterUtility.FaceEstIdx ); SetFile( _estFaceFile, CharacterUtility.Index.FaceEst );
SetFile( _estHairFile, CharacterUtility.HairEstIdx ); SetFile( _estHairFile, CharacterUtility.Index.HairEst );
SetFile( _estBodyFile, CharacterUtility.BodyEstIdx ); SetFile( _estBodyFile, CharacterUtility.Index.BodyEst );
SetFile( _estHeadFile, CharacterUtility.HeadEstIdx ); SetFile( _estHeadFile, CharacterUtility.Index.HeadEst );
} }
public static void ResetEstFiles() public static void ResetEstFiles()
{ {
SetFile( null, CharacterUtility.FaceEstIdx ); SetFile( null, CharacterUtility.Index.FaceEst );
SetFile( null, CharacterUtility.HairEstIdx ); SetFile( null, CharacterUtility.Index.HairEst );
SetFile( null, CharacterUtility.BodyEstIdx ); SetFile( null, CharacterUtility.Index.BodyEst );
SetFile( null, CharacterUtility.HeadEstIdx ); SetFile( null, CharacterUtility.Index.HeadEst );
} }
public void ResetEst() public void ResetEst()

View file

@ -16,10 +16,10 @@ public partial class MetaManager
private readonly List< GmpManipulation > _gmpManipulations = new(); private readonly List< GmpManipulation > _gmpManipulations = new();
public void SetGmpFiles() public void SetGmpFiles()
=> SetFile( _gmpFile, CharacterUtility.GmpIdx ); => SetFile( _gmpFile, CharacterUtility.Index.Gmp );
public static void ResetGmpFiles() public static void ResetGmpFiles()
=> SetFile( null, CharacterUtility.GmpIdx ); => SetFile( null, CharacterUtility.Index.Gmp );
public void ResetGmp() public void ResetGmp()
{ {

View file

@ -6,6 +6,7 @@ using System.Linq;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using Dalamud.Logging; using Dalamud.Logging;
using Penumbra.Collections; using Penumbra.Collections;
using Penumbra.Interop.Structs;
using Penumbra.Meta.Files; using Penumbra.Meta.Files;
using Penumbra.Meta.Manipulations; using Penumbra.Meta.Manipulations;
using Penumbra.Mods; using Penumbra.Mods;
@ -168,7 +169,7 @@ public partial class MetaManager : IDisposable, IEnumerable< KeyValuePair< MetaM
} }
[MethodImpl( MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization )] [MethodImpl( MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization )]
private static unsafe void SetFile( MetaBaseFile? file, int index ) private static unsafe void SetFile( MetaBaseFile? file, CharacterUtility.Index index )
{ {
if( file == null ) if( file == null )
{ {

View file

@ -67,7 +67,7 @@ public readonly struct EqdpManipulation : IMetaManipulation< EqdpManipulation >
return set != 0 ? set : Slot.CompareTo( other.Slot ); return set != 0 ? set : Slot.CompareTo( other.Slot );
} }
public int FileIndex() public CharacterUtility.Index FileIndex()
=> CharacterUtility.EqdpIdx( Names.CombinedRace( Gender, Race ), Slot.IsAccessory() ); => CharacterUtility.EqdpIdx( Names.CombinedRace( Gender, Race ), Slot.IsAccessory() );
public bool Apply( ExpandedEqdpFile file ) public bool Apply( ExpandedEqdpFile file )

View file

@ -47,8 +47,8 @@ public readonly struct EqpManipulation : IMetaManipulation< EqpManipulation >
return set != 0 ? set : Slot.CompareTo( other.Slot ); return set != 0 ? set : Slot.CompareTo( other.Slot );
} }
public int FileIndex() public CharacterUtility.Index FileIndex()
=> CharacterUtility.EqpIdx; => CharacterUtility.Index.Eqp;
public bool Apply( ExpandedEqpFile file ) public bool Apply( ExpandedEqpFile file )
{ {

View file

@ -13,10 +13,10 @@ public readonly struct EstManipulation : IMetaManipulation< EstManipulation >
{ {
public enum EstType : byte public enum EstType : byte
{ {
Hair = CharacterUtility.HairEstIdx, Hair = CharacterUtility.Index.HairEst,
Face = CharacterUtility.FaceEstIdx, Face = CharacterUtility.Index.FaceEst,
Body = CharacterUtility.BodyEstIdx, Body = CharacterUtility.Index.BodyEst,
Head = CharacterUtility.HeadEstIdx, Head = CharacterUtility.Index.HeadEst,
} }
public ushort Entry { get; init; } // SkeletonIdx. public ushort Entry { get; init; } // SkeletonIdx.
@ -76,8 +76,8 @@ public readonly struct EstManipulation : IMetaManipulation< EstManipulation >
return s != 0 ? s : SetId.CompareTo( other.SetId ); return s != 0 ? s : SetId.CompareTo( other.SetId );
} }
public int FileIndex() public CharacterUtility.Index FileIndex()
=> ( int )Slot; => ( CharacterUtility.Index )Slot;
public bool Apply( EstFile file ) public bool Apply( EstFile file )
{ {

View file

@ -33,8 +33,8 @@ public readonly struct GmpManipulation : IMetaManipulation< GmpManipulation >
public int CompareTo( GmpManipulation other ) public int CompareTo( GmpManipulation other )
=> SetId.CompareTo( other.SetId ); => SetId.CompareTo( other.SetId );
public int FileIndex() public CharacterUtility.Index FileIndex()
=> CharacterUtility.GmpIdx; => CharacterUtility.Index.Gmp;
public bool Apply( ExpandedGmpFile file ) public bool Apply( ExpandedGmpFile file )
{ {

View file

@ -4,6 +4,7 @@ using Newtonsoft.Json;
using Newtonsoft.Json.Converters; using Newtonsoft.Json.Converters;
using Penumbra.GameData.ByteString; using Penumbra.GameData.ByteString;
using Penumbra.GameData.Enums; using Penumbra.GameData.Enums;
using Penumbra.Interop.Structs;
using Penumbra.Meta.Files; using Penumbra.Meta.Files;
namespace Penumbra.Meta.Manipulations; namespace Penumbra.Meta.Manipulations;
@ -119,8 +120,8 @@ public readonly struct ImcManipulation : IMetaManipulation< ImcManipulation >
return b != 0 ? b : Variant.CompareTo( other.Variant ); return b != 0 ? b : Variant.CompareTo( other.Variant );
} }
public int FileIndex() public CharacterUtility.Index FileIndex()
=> -1; => ( CharacterUtility.Index )( -1 );
public Utf8GamePath GamePath() public Utf8GamePath GamePath()
{ {

View file

@ -3,12 +3,13 @@ using System.Runtime.InteropServices;
using Newtonsoft.Json; using Newtonsoft.Json;
using Newtonsoft.Json.Converters; using Newtonsoft.Json.Converters;
using Penumbra.GameData.Util; using Penumbra.GameData.Util;
using Penumbra.Interop.Structs;
namespace Penumbra.Meta.Manipulations; namespace Penumbra.Meta.Manipulations;
public interface IMetaManipulation public interface IMetaManipulation
{ {
public int FileIndex(); public CharacterUtility.Index FileIndex();
} }
public interface IMetaManipulation< T > public interface IMetaManipulation< T >

View file

@ -45,8 +45,8 @@ public readonly struct RspManipulation : IMetaManipulation< RspManipulation >
return s != 0 ? s : Attribute.CompareTo( other.Attribute ); return s != 0 ? s : Attribute.CompareTo( other.Attribute );
} }
public int FileIndex() public CharacterUtility.Index FileIndex()
=> CharacterUtility.HumanCmpIdx; => CharacterUtility.Index.HumanCmp;
public bool Apply( CmpFile file ) public bool Apply( CmpFile file )
{ {

View file

@ -69,75 +69,84 @@ public class Penumbra : IDalamudPlugin
public Penumbra( DalamudPluginInterface pluginInterface ) public Penumbra( DalamudPluginInterface pluginInterface )
{ {
Dalamud.Initialize( pluginInterface ); try
GameData.GameData.GetIdentifier( Dalamud.GameData );
DevPenumbraExists = CheckDevPluginPenumbra();
IsNotInstalledPenumbra = CheckIsNotInstalled();
Framework = new FrameworkManager();
CharacterUtility = new CharacterUtility();
Backup.CreateBackup( pluginInterface.ConfigDirectory, PenumbraBackupFiles() );
Config = Configuration.Load();
TempMods = new TempModManager();
MetaFileManager = new MetaFileManager();
ResourceLoader = new ResourceLoader( this );
ResourceLoader.EnableHooks();
ResourceLogger = new ResourceLogger( ResourceLoader );
ResidentResources = new ResidentResourceManager();
ModManager = new Mod.Manager( Config.ModDirectory );
ModManager.DiscoverMods();
CollectionManager = new ModCollection.Manager( ModManager );
ModFileSystem = ModFileSystem.Load();
ObjectReloader = new ObjectReloader();
PathResolver = new PathResolver( ResourceLoader );
Dalamud.Commands.AddHandler( CommandName, new CommandInfo( OnCommand )
{ {
HelpMessage = "/penumbra - toggle ui\n/penumbra reload - reload mod file lists & discover any new mods", Dalamud.Initialize( pluginInterface );
} ); GameData.GameData.GetIdentifier( Dalamud.GameData );
DevPenumbraExists = CheckDevPluginPenumbra();
IsNotInstalledPenumbra = CheckIsNotInstalled();
SetupInterface( out _configWindow, out _launchButton, out _windowSystem ); Framework = new FrameworkManager();
CharacterUtility = new CharacterUtility();
Backup.CreateBackup( pluginInterface.ConfigDirectory, PenumbraBackupFiles() );
Config = Configuration.Load();
if( Config.EnableMods ) TempMods = new TempModManager();
{ MetaFileManager = new MetaFileManager();
ResourceLoader.EnableReplacements(); ResourceLoader = new ResourceLoader( this );
PathResolver.Enable(); ResourceLoader.EnableHooks();
ResourceLogger = new ResourceLogger( ResourceLoader );
ResidentResources = new ResidentResourceManager();
ModManager = new Mod.Manager( Config.ModDirectory );
ModManager.DiscoverMods();
CollectionManager = new ModCollection.Manager( ModManager );
ModFileSystem = ModFileSystem.Load();
ObjectReloader = new ObjectReloader();
PathResolver = new PathResolver( ResourceLoader );
Dalamud.Commands.AddHandler( CommandName, new CommandInfo( OnCommand )
{
HelpMessage = "/penumbra - toggle ui\n/penumbra reload - reload mod file lists & discover any new mods",
} );
SetupInterface( out _configWindow, out _launchButton, out _windowSystem );
if( Config.EnableMods )
{
ResourceLoader.EnableReplacements();
PathResolver.Enable();
}
if( Config.EnableHttpApi )
{
CreateWebServer();
}
if( Config.DebugMode )
{
ResourceLoader.EnableDebug();
_configWindow.IsOpen = true;
}
if( Config.EnableFullResourceLogging )
{
ResourceLoader.EnableFullLogging();
}
if( CharacterUtility.Ready )
{
ResidentResources.Reload();
}
Api = new PenumbraApi( this );
Ipc = new PenumbraIpc( Dalamud.PluginInterface, Api );
SubscribeItemLinks();
if( ImcExceptions > 0 )
{
PluginLog.Error( $"{ImcExceptions} IMC Exceptions thrown. Please repair your game files." );
}
else
{
PluginLog.Information( $"Penumbra Version {Version}, Commit #{CommitHash} successfully Loaded." );
}
Dalamud.PluginInterface.UiBuilder.Draw += _windowSystem.Draw;
} }
catch
if( Config.EnableHttpApi )
{ {
CreateWebServer(); Dispose();
throw;
} }
if( Config.DebugMode )
{
ResourceLoader.EnableDebug();
_configWindow.IsOpen = true;
}
if( Config.EnableFullResourceLogging )
{
ResourceLoader.EnableFullLogging();
}
if( CharacterUtility.Ready )
{
ResidentResources.Reload();
}
Api = new PenumbraApi( this );
Ipc = new PenumbraIpc( Dalamud.PluginInterface, Api );
SubscribeItemLinks();
if( ImcExceptions > 0 )
{
PluginLog.Error( $"{ImcExceptions} IMC Exceptions thrown. Please repair your game files." );
}
else
{
PluginLog.Information( $"Penumbra Version {Version}, Commit #{CommitHash} successfully Loaded." );
}
Dalamud.PluginInterface.UiBuilder.Draw += _windowSystem.Draw;
} }
private void SetupInterface( out ConfigWindow cfg, out LaunchButton btn, out WindowSystem system ) private void SetupInterface( out ConfigWindow cfg, out LaunchButton btn, out WindowSystem system )
@ -154,8 +163,8 @@ public class Penumbra : IDalamudPlugin
{ {
Dalamud.PluginInterface.UiBuilder.Draw -= _windowSystem.Draw; Dalamud.PluginInterface.UiBuilder.Draw -= _windowSystem.Draw;
Dalamud.PluginInterface.UiBuilder.OpenConfigUi -= _configWindow.Toggle; Dalamud.PluginInterface.UiBuilder.OpenConfigUi -= _configWindow.Toggle;
_launchButton.Dispose(); _launchButton?.Dispose();
_configWindow.Dispose(); _configWindow?.Dispose();
} }
public bool Enable() public bool Enable()
@ -249,18 +258,18 @@ public class Penumbra : IDalamudPlugin
public void Dispose() public void Dispose()
{ {
DisposeInterface(); DisposeInterface();
Ipc.Dispose(); Ipc?.Dispose();
Api.Dispose(); Api?.Dispose();
ObjectReloader.Dispose(); ObjectReloader?.Dispose();
ModFileSystem.Dispose(); ModFileSystem?.Dispose();
CollectionManager.Dispose(); CollectionManager?.Dispose();
Dalamud.Commands.RemoveHandler( CommandName ); Dalamud.Commands.RemoveHandler( CommandName );
PathResolver.Dispose(); PathResolver?.Dispose();
ResourceLogger.Dispose(); ResourceLogger?.Dispose();
ResourceLoader.Dispose(); ResourceLoader?.Dispose();
CharacterUtility.Dispose(); CharacterUtility?.Dispose();
ShutdownWebServer(); ShutdownWebServer();
} }

View file

@ -237,7 +237,8 @@ public partial class ConfigWindow
for( var i = 0; i < CharacterUtility.RelevantIndices.Length; ++i ) for( var i = 0; i < CharacterUtility.RelevantIndices.Length; ++i )
{ {
var idx = CharacterUtility.RelevantIndices[ i ]; var idx = CharacterUtility.RelevantIndices[ i ];
var resource = ( ResourceHandle* )Penumbra.CharacterUtility.Address->Resources[ idx ]; var intern = new CharacterUtility.InternalIndex( i );
var resource = ( ResourceHandle* )Penumbra.CharacterUtility.Address->Resource(idx);
ImGui.TableNextColumn(); ImGui.TableNextColumn();
ImGui.TextUnformatted( $"0x{( ulong )resource:X}" ); ImGui.TextUnformatted( $"0x{( ulong )resource:X}" );
ImGui.TableNextColumn(); ImGui.TableNextColumn();
@ -259,18 +260,18 @@ public partial class ConfigWindow
ImGui.TableNextColumn(); ImGui.TableNextColumn();
ImGui.TextUnformatted( $"{resource->GetData().Length}" ); ImGui.TextUnformatted( $"{resource->GetData().Length}" );
ImGui.TableNextColumn(); ImGui.TableNextColumn();
ImGui.Selectable( $"0x{Penumbra.CharacterUtility.DefaultResources[ i ].Address:X}" ); ImGui.Selectable( $"0x{Penumbra.CharacterUtility.DefaultResource(intern).Address:X}" );
if( ImGui.IsItemClicked() ) if( ImGui.IsItemClicked() )
{ {
ImGui.SetClipboardText( string.Join( "\n", ImGui.SetClipboardText( string.Join( "\n",
new ReadOnlySpan< byte >( ( byte* )Penumbra.CharacterUtility.DefaultResources[ i ].Address, new ReadOnlySpan< byte >( ( byte* )Penumbra.CharacterUtility.DefaultResource(intern).Address,
Penumbra.CharacterUtility.DefaultResources[ i ].Size ).ToArray().Select( b => b.ToString( "X2" ) ) ) ); Penumbra.CharacterUtility.DefaultResource(intern).Size ).ToArray().Select( b => b.ToString( "X2" ) ) ) );
} }
ImGuiUtil.HoverTooltip( "Click to copy bytes to clipboard." ); ImGuiUtil.HoverTooltip( "Click to copy bytes to clipboard." );
ImGui.TableNextColumn(); ImGui.TableNextColumn();
ImGui.TextUnformatted( $"{Penumbra.CharacterUtility.DefaultResources[ i ].Size}" ); ImGui.TextUnformatted( $"{Penumbra.CharacterUtility.DefaultResource(intern).Size}" );
} }
} }