This commit is contained in:
Ottermandias 2022-03-16 17:12:31 +01:00
parent 8d2e84eecf
commit 581b91b337
13 changed files with 123 additions and 52 deletions

View file

@ -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 )
{

View file

@ -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.

View file

@ -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;

View file

@ -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 );

View file

@ -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 )

View file

@ -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;

View file

@ -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 );
}

View file

@ -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 );

View file

@ -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;

View file

@ -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;
}

View file

@ -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;
}

View file

@ -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 );
}

View file

@ -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 ) )
{