diff --git a/Penumbra.GameData/ByteString/Utf8RelPath.cs b/Penumbra.GameData/ByteString/Utf8RelPath.cs index 5fd79ef7..d03ac745 100644 --- a/Penumbra.GameData/ByteString/Utf8RelPath.cs +++ b/Penumbra.GameData/ByteString/Utf8RelPath.cs @@ -20,7 +20,14 @@ public readonly struct Utf8RelPath : IEquatable< Utf8RelPath >, IComparable< Utf public static explicit operator Utf8RelPath( string s ) - => FromString( s, out var p ) ? p : Empty; + { + if( !FromString( s, out var p ) ) + { + return Empty; + } + + return new Utf8RelPath( p.Path.AsciiToLower() ); + } public static bool FromString( string? s, out Utf8RelPath path ) { diff --git a/Penumbra.GameData/ByteString/Utf8String.Manipulation.cs b/Penumbra.GameData/ByteString/Utf8String.Manipulation.cs index 0d895fa6..2a941020 100644 --- a/Penumbra.GameData/ByteString/Utf8String.Manipulation.cs +++ b/Penumbra.GameData/ByteString/Utf8String.Manipulation.cs @@ -71,7 +71,7 @@ public sealed unsafe partial class Utf8String var length = Length; var newPtr = ByteStringFunctions.CopyString( _path, length ); var numReplaced = ByteStringFunctions.Replace( newPtr, length, from, to ); - return new Utf8String().Setup( newPtr, length, numReplaced > 0 ? _crc32 : null, true, true, IsAsciiLowerInternal, IsAsciiInternal ); + return new Utf8String().Setup( newPtr, length, numReplaced == 0 ? _crc32 : null, true, true, IsAsciiLowerInternal, IsAsciiInternal ); } // Join a number of strings with a given byte between them. diff --git a/Penumbra/Importer/TexToolsMeta.cs b/Penumbra/Importer/TexToolsMeta.cs index 4b18fd07..e39b06ac 100644 --- a/Penumbra/Importer/TexToolsMeta.cs +++ b/Penumbra/Importer/TexToolsMeta.cs @@ -4,7 +4,6 @@ using System.IO; using System.Linq; using System.Text.RegularExpressions; using Dalamud.Logging; -using Lumina.Data.Files; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; using Penumbra.GameData.Util; diff --git a/Penumbra/Interop/CharacterUtility.cs b/Penumbra/Interop/CharacterUtility.cs index 03f4bba0..11d54eb4 100644 --- a/Penumbra/Interop/CharacterUtility.cs +++ b/Penumbra/Interop/CharacterUtility.cs @@ -49,6 +49,9 @@ public unsafe class CharacterUtility : IDisposable public (IntPtr Address, int Size)[] DefaultResources = new (IntPtr, int)[RelevantIndices.Length]; + public (IntPtr Address, int Size) DefaultResource( int fullIdx ) + => DefaultResources[ ReverseIndices[ fullIdx ] ]; + public CharacterUtility() { SignatureHelper.Initialise( this ); diff --git a/Penumbra/Interop/Resolver/PathResolver.Meta.cs b/Penumbra/Interop/Resolver/PathResolver.Meta.cs index 2ae74559..bd843982 100644 --- a/Penumbra/Interop/Resolver/PathResolver.Meta.cs +++ b/Penumbra/Interop/Resolver/PathResolver.Meta.cs @@ -21,9 +21,11 @@ namespace Penumbra.Interop.Resolver; // EST entries seem to be obtained by "44 8B C9 83 EA ?? 74", which is only called by // ResolveSKLBPath, ResolveSKPPath, ResolvePHYBPath and indirectly by ResolvePAPPath. -// RSP entries seem to be obtained by "E8 ?? ?? ?? ?? 48 8B 8E ?? ?? ?? ?? 44 8B CF", or maybe "E8 ?? ?? ?? ?? 0F 28 F0 48 8B 05", -// possibly also "E8 ?? ?? ?? ?? F2 0F 10 44 24 ?? 8B 44 24 ?? F2 0F 11 45 ?? 89 45 ?? 83 FF" -// which is called by a lot of functions, but the mostly relevant is probably Human.SetupFromCharacterData, which is only called by CharacterBase.Create. +// RSP height entries seem to be obtained by "E8 ?? ?? ?? ?? 48 8B 8E ?? ?? ?? ?? 44 8B CF" +// RSP tail entries seem to be obtained by "E8 ?? ?? ?? ?? 0F 28 F0 48 8B 05" +// RSP bust size entries seem to be obtained by "E8 ?? ?? ?? ?? F2 0F 10 44 24 ?? 8B 44 24 ?? F2 0F 11 45 ?? 89 45 ?? 83 FF" +// they all are called by many functions, but the most relevant seem to be Human.SetupFromCharacterData, which is only called by CharacterBase.Create, +// and RspSetupCharacter, which is hooked here. // GMP Entries seem to be only used by "48 8B ?? 53 55 57 48 83 ?? ?? 48 8B", which has a DrawObject as its first parameter. @@ -102,6 +104,18 @@ public unsafe partial class PathResolver ApplyVisorHook!.Original( drawObject, unk1, unk2, unk3, unk4, unk5 ); } + // RSP + public delegate void RspSetupCharacterDelegate( IntPtr drawObject, IntPtr unk2, float unk3, IntPtr unk4, byte unk5 ); + + [Signature( "48 89 5C 24 ?? 48 89 6C 24 ?? 48 89 74 24 ?? 88 54 24 ?? 57 41 56 ", DetourName = "RspSetupCharacterDetour" )] + public Hook< RspSetupCharacterDelegate >? RspSetupCharacterHook; + + private void RspSetupCharacterDetour( IntPtr drawObject, IntPtr unk2, float unk3, IntPtr unk4, byte unk5 ) + { + using var rsp = MetaChanger.ChangeCmp( this, drawObject ); + RspSetupCharacterHook!.Original( drawObject, unk2, unk3, unk4, unk5 ); + } + private void SetupMetaHooks() { OnModelLoadCompleteHook = @@ -119,6 +133,9 @@ public unsafe partial class PathResolver #endif #if USE_GMP ApplyVisorHook?.Enable(); +#endif +#if USE_CMP + RspSetupCharacterHook?.Enable(); #endif } @@ -128,6 +145,7 @@ public unsafe partial class PathResolver UpdateModelsHook?.Disable(); OnModelLoadCompleteHook?.Disable(); ApplyVisorHook?.Disable(); + RspSetupCharacterHook?.Disable(); } private void DisposeMetaHooks() @@ -136,6 +154,7 @@ public unsafe partial class PathResolver UpdateModelsHook?.Dispose(); OnModelLoadCompleteHook?.Dispose(); ApplyVisorHook?.Dispose(); + RspSetupCharacterHook?.Dispose(); } private ModCollection? GetCollection( IntPtr drawObject ) @@ -249,6 +268,19 @@ public unsafe partial class PathResolver return new MetaChanger( MetaManipulation.Type.Unknown ); } + public static MetaChanger ChangeCmp( PathResolver resolver, IntPtr drawObject ) + { +#if USE_CMP + var collection = resolver.GetCollection( drawObject ); + if( collection != null ) + { + collection.SetCmpFiles(); + return new MetaChanger( MetaManipulation.Type.Rsp ); + } +#endif + return new MetaChanger( MetaManipulation.Type.Unknown ); + } + public void Dispose() { switch( _type ) diff --git a/Penumbra/Interop/Structs/CharacterUtility.cs b/Penumbra/Interop/Structs/CharacterUtility.cs index cdd0c6cb..aaceee6b 100644 --- a/Penumbra/Interop/Structs/CharacterUtility.cs +++ b/Penumbra/Interop/Structs/CharacterUtility.cs @@ -17,8 +17,8 @@ public unsafe struct CharacterUtility 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 HeadEstIdx = 66; + public const int BodyEstIdx = 67; public const int EqdpStartIdx = 2; public const int NumEqdpFiles = 2 * 28; diff --git a/Penumbra/Meta/Files/CmpFile.cs b/Penumbra/Meta/Files/CmpFile.cs index bdb5c10b..356d826d 100644 --- a/Penumbra/Meta/Files/CmpFile.cs +++ b/Penumbra/Meta/Files/CmpFile.cs @@ -39,7 +39,7 @@ public sealed unsafe class CmpFile : MetaBaseFile public static float GetDefault( SubRace subRace, RspAttribute attribute ) { - var data = ( byte* )Penumbra.CharacterUtility.DefaultResources[ CharacterUtility.HumanCmpIdx ].Address; + var data = ( byte* )Penumbra.CharacterUtility.DefaultResource( CharacterUtility.HumanCmpIdx ).Address; return *( float* )( data + RacialScalingStart + ToRspIndex( subRace ) * RspEntry.ByteSize + ( int )attribute * 4 ); } diff --git a/Penumbra/Meta/Files/EstFile.cs b/Penumbra/Meta/Files/EstFile.cs index 4eb53be3..8880b9e0 100644 --- a/Penumbra/Meta/Files/EstFile.cs +++ b/Penumbra/Meta/Files/EstFile.cs @@ -1,5 +1,6 @@ using System; using System.Runtime.InteropServices; +using System.Windows.Forms; using Penumbra.GameData.Enums; using Penumbra.GameData.Util; using Penumbra.Meta.Manipulations; @@ -15,7 +16,7 @@ public sealed unsafe class EstFile : MetaBaseFile { private const ushort EntryDescSize = 4; private const ushort EntrySize = 2; - private const int IncreaseSize = 100; + private const int IncreaseSize = 512; public int Count => *( int* )Data; @@ -52,58 +53,57 @@ public sealed unsafe class EstFile : MetaBaseFile { var data = Data; var length = Length; - AllocateData( length + IncreaseSize * ( EntryDescSize + EntrySize ) ); + AllocateData( length + IncreaseSize ); Functions.MemCpyUnchecked( Data, data, length ); - Functions.MemSet( Data + length, 0, IncreaseSize * ( EntryDescSize + EntrySize ) ); + Functions.MemSet( Data + length, 0, IncreaseSize ); GC.RemoveMemoryPressure( length ); Marshal.FreeHGlobal( ( IntPtr )data ); } - var control = ( uint* )( Data + 4 ); - var entries = ( ushort* )( Data + 4 * ( Count + 1 ) ); + var control = ( Info* )( Data + 4 ); + var entries = ( ushort* )( control + Count ); - for( var i = Count; i > idx; --i ) + for( var i = Count - 1; i >= idx; --i ) { - *( entries + i + 2 ) = entries[ i - 1 ]; + entries[ i + 3 ] = entries[ i ]; } + entries[ idx + 2 ] = skeletonId; + for( var i = idx - 1; i >= 0; --i ) { - *( entries + i + 2 ) = entries[ i ]; + entries[ i + 2 ] = entries[ i ]; } - for( var i = Count; i > idx; --i ) + for( var i = Count - 1; i >= idx; --i ) { - *( control + i ) = control[ i - 1 ]; + control[ i + 1 ] = control[ i ]; } + control[ idx ] = new Info( genderRace, setId ); + *( int* )Data = Count + 1; - - *( ushort* )control = setId; - *( ( ushort* )control + 1 ) = ( ushort )genderRace; - control[ idx ] = skeletonId; } private void RemoveEntry( int idx ) { - var entries = ( ushort* )( Data + 4 * Count ); - var control = ( uint* )( Data + 4 ); - *( int* )Data = Count - 1; - var count = Count; + var control = ( Info* )( Data + 4 ); + var entries = ( ushort* )( control + Count ); - for( var i = idx; i < count; ++i ) + for( var i = idx; i < Count; ++i ) { control[ i ] = control[ i + 1 ]; } - for( var i = 0; i < count; ++i ) + for( var i = 0; i < Count; ++i ) { - entries[ i ] = entries[ i + 1 ]; + entries[ i - 2 ] = entries[ i + 1 ]; } - entries[ count ] = 0; - entries[ count + 1 ] = 0; - entries[ count + 2 ] = 0; + entries[ Count - 3 ] = 0; + entries[ Count - 2 ] = 0; + entries[ Count - 1 ] = 0; + *( int* )Data = Count - 1; } [StructLayout( LayoutKind.Sequential, Size = 4 )] @@ -179,7 +179,7 @@ public sealed unsafe class EstFile : MetaBaseFile : base( ( int )estType ) { var length = DefaultData.Length; - AllocateData( length + IncreaseSize * ( EntryDescSize + EntrySize ) ); + AllocateData( length + IncreaseSize ); Reset(); } @@ -188,7 +188,7 @@ public sealed unsafe class EstFile : MetaBaseFile public static ushort GetDefault( EstManipulation.EstType estType, GenderRace genderRace, ushort setId ) { - var data = ( byte* )Penumbra.CharacterUtility.DefaultResources[ ( int )estType ].Address; + var data = ( byte* )Penumbra.CharacterUtility.DefaultResource( ( int )estType ).Address; var count = *( int* )data; var span = new ReadOnlySpan< Info >( data + 4, count ); var (idx, found) = FindEntry( span, genderRace, setId ); diff --git a/Penumbra/Meta/Files/MetaBaseFile.cs b/Penumbra/Meta/Files/MetaBaseFile.cs index 304f0f09..c566cd16 100644 --- a/Penumbra/Meta/Files/MetaBaseFile.cs +++ b/Penumbra/Meta/Files/MetaBaseFile.cs @@ -1,5 +1,4 @@ using System; -using System.Runtime.InteropServices; using Dalamud.Memory; namespace Penumbra.Meta.Files; @@ -14,17 +13,18 @@ public unsafe class MetaBaseFile : IDisposable => Index = idx; protected (IntPtr Data, int Length) DefaultData - => Penumbra.CharacterUtility.DefaultResources[ Index ]; + => Penumbra.CharacterUtility.DefaultResource( Index ); // Reset to default values. public virtual void Reset() - {} + { } // Obtain memory. protected void AllocateData( int length ) { Length = length; - Data = ( byte* )MemoryHelper.GameAllocateDefault( ( ulong )length ); ; + Data = ( byte* )MemoryHelper.GameAllocateDefault( ( ulong )length ); + ; GC.AddMemoryPressure( length ); } @@ -32,7 +32,7 @@ public unsafe class MetaBaseFile : IDisposable protected void ReleaseUnmanagedResources() { var ptr = ( IntPtr )Data; - MemoryHelper.GameFree( ref ptr, (ulong) Length ); + MemoryHelper.GameFree( ref ptr, ( ulong )Length ); GC.RemoveMemoryPressure( Length ); Length = 0; Data = null; diff --git a/Penumbra/Meta/Manager/MetaManager.Gmp.cs b/Penumbra/Meta/Manager/MetaManager.Gmp.cs index d281bb37..8f43ac00 100644 --- a/Penumbra/Meta/Manager/MetaManager.Gmp.cs +++ b/Penumbra/Meta/Manager/MetaManager.Gmp.cs @@ -40,7 +40,7 @@ public partial class MetaManager public bool ApplyMod( GmpManipulation m, Mod.Mod mod ) { #if USE_GMP - if( Manipulations.TryAdd( m, mod ) ) + if( !Manipulations.TryAdd( m, mod ) ) { return false; } diff --git a/Penumbra/Meta/Manipulations/EstManipulation.cs b/Penumbra/Meta/Manipulations/EstManipulation.cs index 613ef965..d25893f1 100644 --- a/Penumbra/Meta/Manipulations/EstManipulation.cs +++ b/Penumbra/Meta/Manipulations/EstManipulation.cs @@ -19,22 +19,27 @@ public readonly struct EstManipulation : IMetaManipulation< EstManipulation > Head = CharacterUtility.HeadEstIdx, } - public readonly ushort SkeletonIdx; + public readonly ushort SkeletonIdx; + [JsonConverter( typeof( StringEnumConverter ) )] - public readonly Gender Gender; + public readonly Gender Gender; + [JsonConverter( typeof( StringEnumConverter ) )] public readonly ModelRace Race; - public readonly ushort SetId; - [JsonConverter( typeof( StringEnumConverter ) )] - public readonly EstType Slot; - public EstManipulation( Gender gender, ModelRace race, EstType estType, ushort setId, ushort skeletonIdx ) + public readonly ushort SetId; + + [JsonConverter( typeof( StringEnumConverter ) )] + public readonly EstType Slot; + + [JsonConstructor] + public EstManipulation( Gender gender, ModelRace race, EstType slot, ushort setId, ushort skeletonIdx ) { SkeletonIdx = skeletonIdx; Gender = gender; Race = race; SetId = setId; - Slot = estType; + Slot = slot; } diff --git a/Penumbra/UI/MenuTabs/TabCollections.cs b/Penumbra/UI/MenuTabs/TabCollections.cs index e2e6bdac..c5ce81a3 100644 --- a/Penumbra/UI/MenuTabs/TabCollections.cs +++ b/Penumbra/UI/MenuTabs/TabCollections.cs @@ -360,17 +360,15 @@ public partial class SettingsInterface using var font = ImGuiRaii.PushFont( UiBuilder.IconFont ); - using var style = ImGuiRaii.PushStyle( ImGuiStyleVar.FramePadding, Vector2.One * ImGuiHelpers.GlobalScale * 1.5f ); - if( ImGui.Button( $"{FontAwesomeIcon.Trash.ToIconString()}##{name}" ) ) + if( ImGui.Button( $"{FontAwesomeIcon.Trash.ToIconString()}##{name}", Vector2.One * ImGui.GetFrameHeight() ) ) { manager.Collections.RemoveCharacterCollection( name ); } - style.Pop(); - font.Pop(); ImGui.SameLine(); + ImGui.AlignTextToFramePadding(); ImGui.Text( name ); } diff --git a/Penumbra/UI/MenuTabs/TabDebug.cs b/Penumbra/UI/MenuTabs/TabDebug.cs index 81b86c32..72c3e86e 100644 --- a/Penumbra/UI/MenuTabs/TabDebug.cs +++ b/Penumbra/UI/MenuTabs/TabDebug.cs @@ -13,6 +13,7 @@ using Penumbra.GameData.Structs; using Penumbra.Interop; using Penumbra.Interop.Structs; using Penumbra.Meta.Files; +using Penumbra.Meta.Manipulations; using Penumbra.UI.Custom; using CharacterUtility = Penumbra.Interop.CharacterUtility; using ResourceHandle = Penumbra.Interop.Structs.ResourceHandle; @@ -410,6 +411,32 @@ public partial class SettingsInterface catch { } + var est = 0; + ImGui.InputInt( "##EstInput", ref est ); + try + { + var def = EstFile.GetDefault( EstManipulation.EstType.Body, GenderRace.MidlanderFemale, ( ushort )est ); + var val = Penumbra.ModManager.Collections.ActiveCollection.Cache?.MetaManipulations.Est.BodyFile?[ GenderRace.MidlanderFemale, + ( ushort )est ] + ?? def; + ImGui.Text( def.ToString() ); + ImGui.Text( val.ToString() ); + } + catch + { } + + var gmp = 0; + ImGui.InputInt( "##GmpInput", ref gmp ); + try + { + var def = ExpandedGmpFile.GetDefault( gmp ); + var val = Penumbra.ModManager.Collections.ActiveCollection.Cache?.MetaManipulations.Gmp.File?[ gmp ] ?? def; + ImGui.Text( def.Value.ToString("X") ); + ImGui.Text( val.Value.ToString("X") ); + } + catch + { } + if( !ImGui.BeginTable( "##CharacterUtilityDebugList", 6, ImGuiTableFlags.RowBg | ImGuiTableFlags.SizingFixedFit, -Vector2.UnitX ) ) {