diff --git a/Penumbra.GameData/ByteString/FullPath.cs b/Penumbra.GameData/ByteString/FullPath.cs index 2b3ffe23..5ccf71c2 100644 --- a/Penumbra.GameData/ByteString/FullPath.cs +++ b/Penumbra.GameData/ByteString/FullPath.cs @@ -27,7 +27,7 @@ public readonly struct FullPath : IComparable, IEquatable< FullPath > public FullPath( string s ) { FullName = s; - InternalName = Utf8String.FromString( FullName, out var name, true ) ? name.Replace( ( byte )'\\', ( byte )'/' ) : Utf8String.Empty; + InternalName = Utf8String.FromString( FullName.Replace( '\\', '/' ), out var name, true ) ? name : Utf8String.Empty; Crc64 = Functions.ComputeCrc64( InternalName.Span ); } diff --git a/Penumbra.GameData/Enums/Race.cs b/Penumbra.GameData/Enums/Race.cs index a83990ee..47575a31 100644 --- a/Penumbra.GameData/Enums/Race.cs +++ b/Penumbra.GameData/Enums/Race.cs @@ -107,30 +107,6 @@ public enum GenderRace : ushort public static class RaceEnumExtensions { - public static int ToRspIndex( this SubRace subRace ) - { - return subRace switch - { - SubRace.Midlander => 0, - SubRace.Highlander => 1, - SubRace.Wildwood => 10, - SubRace.Duskwight => 11, - SubRace.Plainsfolk => 20, - SubRace.Dunesfolk => 21, - SubRace.SeekerOfTheSun => 30, - SubRace.KeeperOfTheMoon => 31, - SubRace.Seawolf => 40, - SubRace.Hellsguard => 41, - SubRace.Raen => 50, - SubRace.Xaela => 51, - SubRace.Helion => 60, - SubRace.Lost => 61, - SubRace.Rava => 70, - SubRace.Veena => 71, - _ => throw new ArgumentOutOfRangeException( nameof( subRace ), subRace, null ), - }; - } - public static Race ToRace( this ModelRace race ) { return race switch diff --git a/Penumbra/Dalamud.cs b/Penumbra/Dalamud.cs index aee12c85..b8f0c425 100644 --- a/Penumbra/Dalamud.cs +++ b/Penumbra/Dalamud.cs @@ -30,5 +30,6 @@ public class Dalamud [PluginService][RequiredVersion("1.0")] public static TargetManager Targets { get; private set; } = null!; [PluginService][RequiredVersion("1.0")] public static ObjectTable Objects { get; private set; } = null!; [PluginService][RequiredVersion("1.0")] public static TitleScreenMenu TitleScreenMenu { get; private set; } = null!; + [PluginService][RequiredVersion("1.0")] public static GameGui GameGui { get; private set; } = null!; // @formatter:on } \ No newline at end of file diff --git a/Penumbra/Interop/ResourceLoader.Debug.cs b/Penumbra/Interop/Loader/ResourceLoader.Debug.cs similarity index 99% rename from Penumbra/Interop/ResourceLoader.Debug.cs rename to Penumbra/Interop/Loader/ResourceLoader.Debug.cs index 06f819f6..07b225c1 100644 --- a/Penumbra/Interop/ResourceLoader.Debug.cs +++ b/Penumbra/Interop/Loader/ResourceLoader.Debug.cs @@ -8,7 +8,7 @@ using FFXIVClientStructs.FFXIV.Client.System.Resource.Handle; using FFXIVClientStructs.STD; using Penumbra.GameData.ByteString; -namespace Penumbra.Interop; +namespace Penumbra.Interop.Loader; public unsafe partial class ResourceLoader { diff --git a/Penumbra/Interop/ResourceLoader.Replacement.cs b/Penumbra/Interop/Loader/ResourceLoader.Replacement.cs similarity index 99% rename from Penumbra/Interop/ResourceLoader.Replacement.cs rename to Penumbra/Interop/Loader/ResourceLoader.Replacement.cs index 837d2c19..b9473345 100644 --- a/Penumbra/Interop/ResourceLoader.Replacement.cs +++ b/Penumbra/Interop/Loader/ResourceLoader.Replacement.cs @@ -9,7 +9,7 @@ using Penumbra.Interop.Structs; using FileMode = Penumbra.Interop.Structs.FileMode; using ResourceHandle = FFXIVClientStructs.FFXIV.Client.System.Resource.Handle.ResourceHandle; -namespace Penumbra.Interop; +namespace Penumbra.Interop.Loader; public unsafe partial class ResourceLoader { diff --git a/Penumbra/Interop/ResourceLoader.TexMdl.cs b/Penumbra/Interop/Loader/ResourceLoader.TexMdl.cs similarity index 99% rename from Penumbra/Interop/ResourceLoader.TexMdl.cs rename to Penumbra/Interop/Loader/ResourceLoader.TexMdl.cs index 544e6ba4..42332f16 100644 --- a/Penumbra/Interop/ResourceLoader.TexMdl.cs +++ b/Penumbra/Interop/Loader/ResourceLoader.TexMdl.cs @@ -5,7 +5,7 @@ using Dalamud.Utility.Signatures; using FFXIVClientStructs.FFXIV.Client.System.Resource.Handle; using Penumbra.GameData.ByteString; -namespace Penumbra.Interop; +namespace Penumbra.Interop.Loader; // Since 6.0, Mdl and Tex Files require special treatment, probably due to datamining protection. public unsafe partial class ResourceLoader diff --git a/Penumbra/Interop/ResourceLoader.cs b/Penumbra/Interop/Loader/ResourceLoader.cs similarity index 99% rename from Penumbra/Interop/ResourceLoader.cs rename to Penumbra/Interop/Loader/ResourceLoader.cs index 8e86c215..161bbd07 100644 --- a/Penumbra/Interop/ResourceLoader.cs +++ b/Penumbra/Interop/Loader/ResourceLoader.cs @@ -3,7 +3,7 @@ using Dalamud.Utility.Signatures; using Penumbra.GameData.ByteString; using ResourceHandle = FFXIVClientStructs.FFXIV.Client.System.Resource.Handle.ResourceHandle; -namespace Penumbra.Interop; +namespace Penumbra.Interop.Loader; public unsafe partial class ResourceLoader : IDisposable { diff --git a/Penumbra/Interop/ResourceLogger.cs b/Penumbra/Interop/Loader/ResourceLogger.cs similarity index 98% rename from Penumbra/Interop/ResourceLogger.cs rename to Penumbra/Interop/Loader/ResourceLogger.cs index 025b7409..47be8a68 100644 --- a/Penumbra/Interop/ResourceLogger.cs +++ b/Penumbra/Interop/Loader/ResourceLogger.cs @@ -3,7 +3,7 @@ using System.Text.RegularExpressions; using Dalamud.Logging; using Penumbra.GameData.ByteString; -namespace Penumbra.Interop; +namespace Penumbra.Interop.Loader; // A logger class that contains the relevant data to log requested files via regex. // Filters are case-insensitive. diff --git a/Penumbra/Interop/PathResolver.cs b/Penumbra/Interop/PathResolver.cs deleted file mode 100644 index b94c2e86..00000000 --- a/Penumbra/Interop/PathResolver.cs +++ /dev/null @@ -1,440 +0,0 @@ -using System; -using System.Collections.Generic; -using Dalamud.Hooking; -using Dalamud.Logging; -using Dalamud.Utility.Signatures; -using FFXIVClientStructs.FFXIV.Client.Game.Object; -using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; -using Penumbra.GameData.ByteString; -using Penumbra.GameData.Util; -using Penumbra.Mods; -using Penumbra.Util; - -namespace Penumbra.Interop; - -public unsafe class PathResolver : IDisposable -{ - //public delegate IntPtr ResolveMdlImcPathDelegate( IntPtr drawObject, IntPtr path, IntPtr unk3, uint unk4 ); - //public delegate IntPtr ResolveMtrlPathDelegate( IntPtr drawObject, IntPtr path, IntPtr unk3, uint unk4, IntPtr unk5 ); - //public delegate byte LoadMtrlFilesDelegate( IntPtr mtrlResourceHandle ); - //public delegate IntPtr CharacterBaseCreateDelegate( uint a, IntPtr b, IntPtr c, byte d ); - //public delegate void EnableDrawDelegate( IntPtr gameObject, IntPtr b, IntPtr c, IntPtr d ); - //public delegate void CharacterBaseDestructorDelegate( IntPtr drawBase ); - // - //[Signature( "?? 89 ?? ?? ?? ?? 89 ?? ?? ?? ?? 89 ?? ?? ?? ?? 89 ?? ?? ?? 41 ?? 48 83 ?? ?? 45 8B ?? 49 8B ?? 48 8B ?? 48 8B ?? 41", - // DetourName = "ResolveMdlPathDetour" )] - //public Hook< ResolveMdlImcPathDelegate >? ResolveMdlPathHook; - // - //[Signature( "?? 89 ?? ?? ?? ?? 89 ?? ?? ?? ?? 89 ?? ?? ?? 57 48 83 ?? ?? 49 8B ?? 48 8B ?? 48 8B ?? 41 83 ?? ?? 0F", - // DetourName = "ResolveMtrlPathDetour" )] - //public Hook< ResolveMtrlPathDelegate >? ResolveMtrlPathHook; - // - //[Signature( "40 ?? 48 83 ?? ?? 4D 8B ?? 48 8B ?? 41", DetourName = "ResolveImcPathDetour" )] - //public Hook< ResolveMdlImcPathDelegate >? ResolveImcPathHook; - // - //[Signature( "4C 8B ?? ?? 89 ?? ?? ?? 89 ?? ?? 55 57 41 ?? 41", DetourName = "LoadMtrlTexDetour" )] - //public Hook< LoadMtrlFilesDelegate >? LoadMtrlTexHook; - // - //[Signature( "?? 89 ?? ?? ?? 57 48 81 ?? ?? ?? ?? ?? 48 8B ?? ?? ?? ?? ?? 48 33 ?? ?? 89 ?? ?? ?? ?? ?? ?? 44 ?? ?? ?? ?? ?? ?? ?? 4C", - // DetourName = "LoadMtrlShpkDetour" )] - //public Hook< LoadMtrlFilesDelegate >? LoadMtrlShpkHook; - // - //[Signature( "E8 ?? ?? ?? ?? 48 85 C0 74 21 C7 40" )] - //public Hook< CharacterBaseCreateDelegate >? CharacterBaseCreateHook; - // - //[Signature( - // "40 ?? 48 81 ?? ?? ?? ?? ?? 48 8B ?? ?? ?? ?? ?? 48 33 ?? ?? 89 ?? ?? ?? ?? ?? ?? 48 8B ?? 48 8B ?? ?? ?? ?? ?? E8 ?? ?? ?? ?? ?? BB" )] - //public Hook< EnableDrawDelegate >? EnableDrawHook; - // - //[Signature( "E8 ?? ?? ?? ?? 40 F6 C7 01 74 3A 40 F6 C7 04 75 27 48 85 DB 74 2F 48 8B 05 ?? ?? ?? ?? 48 8B D3 48 8B 48 30", - // DetourName = "CharacterBaseDestructorDetour" )] - //public Hook< CharacterBaseDestructorDelegate >? CharacterBaseDestructorHook; - // - //public delegate void UpdateModelDelegate( IntPtr drawObject ); - // - //[Signature( "48 8B ?? 56 48 83 ?? ?? ?? B9", DetourName = "UpdateModelsDetour" )] - //public Hook< UpdateModelDelegate >? UpdateModelsHook; - // - //public delegate void SetupConnectorModelAttributesDelegate( IntPtr drawObject, IntPtr unk ); - // - //[Signature( "?? 89 ?? ?? ?? ?? 89 ?? ?? ?? ?? 89 ?? ?? ?? 57 41 ?? 41 ?? 41 ?? 41 ?? 48 83 ?? ?? 8B ?? ?? ?? 4C", - // DetourName = "SetupConnectorModelAttributesDetour" )] - //public Hook< SetupConnectorModelAttributesDelegate >? SetupConnectorModelAttributesHook; - // - //public delegate void SetupModelAttributesDelegate( IntPtr drawObject ); - // - //[Signature( "48 89 6C 24 ?? 56 57 41 54 41 55 41 56 48 83 EC 20", DetourName = "SetupModelAttributesDetour" )] - //public Hook< SetupModelAttributesDelegate >? SetupModelAttributesHook; - // - //[Signature( "40 ?? 48 83 ?? ?? ?? 81 ?? ?? ?? ?? ?? 48 8B ?? 74 ?? ?? 83 ?? ?? ?? ?? ?? ?? 74 ?? 4C", - // DetourName = "GetSlotEqpFlagIndirectDetour" )] - //public Hook< SetupModelAttributesDelegate >? GetSlotEqpFlagIndirectHook; - // - //public delegate void ApplyVisorStuffDelegate( IntPtr drawObject, IntPtr unk1, float unk2, IntPtr unk3, ushort unk4, char unk5 ); - // - //[Signature( "48 8B ?? 53 55 57 48 83 ?? ?? 48 8B", DetourName = "ApplyVisorStuffDetour" )] - //public Hook< ApplyVisorStuffDelegate >? ApplyVisorStuffHook; - // - //private readonly ResourceLoader _loader; - //private readonly ResidentResourceManager _resident; - //internal readonly Dictionary< IntPtr, int > _drawObjectToObject = new(); - //internal readonly Dictionary< Utf8String, ModCollection > _pathCollections = new(); - // - //internal GameObject* _lastGameObject = null; - //internal DrawObject* _lastDrawObject = null; - // - //private bool EqpDataChanged = false; - //private IntPtr DefaultEqpData; - //private int DefaultEqpLength; - // - //private void ApplyVisorStuffDetour( IntPtr drawObject, IntPtr unk1, float unk2, IntPtr unk3, ushort unk4, char unk5 ) - //{ - // PluginLog.Information( $"{drawObject:X} {unk1:X} {unk2} {unk3:X} {unk4} {unk5} {( ulong )FindParent( drawObject ):X}" ); - // ApplyVisorStuffHook!.Original( drawObject, unk1, unk2, unk3, unk4, unk5 ); - //} - // - //private void GetSlotEqpFlagIndirectDetour( IntPtr drawObject ) - //{ - // if( ( *( byte* )( drawObject + 0xa30 ) & 1 ) == 0 || *( ulong* )( drawObject + 0xa28 ) == 0 ) - // { - // return; - // } - // - // ChangeEqp( drawObject ); - // GetSlotEqpFlagIndirectHook!.Original( drawObject ); - // RestoreEqp(); - //} - // - //private void ChangeEqp( IntPtr drawObject ) - //{ - // var parent = FindParent( drawObject ); - // if( parent == null ) - // { - // return; - // } - // - // var name = new Utf8String( parent->Name ); - // if( name.Length == 0 ) - // { - // return; - // } - // - // var charName = name.ToString(); - // if( !Service< ModManager >.Get().Collections.CharacterCollection.TryGetValue( charName, out var collection ) ) - // { - // collection = Service< ModManager >.Get().Collections.DefaultCollection; - // } - // - // if( collection.Cache == null ) - // { - // collection = Service< ModManager >.Get().Collections.ForcedCollection; - // } - // - // var data = collection.Cache?.MetaManipulations.EqpData; - // if( data == null || data.Length == 0 ) - // { - // return; - // } - // - // _resident.CharacterUtility->EqpResource->SetData( data ); - // PluginLog.Information( $"Changed eqp data to {collection.Name}." ); - // EqpDataChanged = true; - //} - // - //private void RestoreEqp() - //{ - // if( !EqpDataChanged ) - // { - // return; - // } - // - // _resident.CharacterUtility->EqpResource->SetData( new ReadOnlySpan< byte >( ( void* )DefaultEqpData, DefaultEqpLength ) ); - // PluginLog.Information( $"Changed eqp data back." ); - // EqpDataChanged = false; - //} - // - //private void SetupModelAttributesDetour( IntPtr drawObject ) - //{ - // ChangeEqp( drawObject ); - // SetupModelAttributesHook!.Original( drawObject ); - // RestoreEqp(); - //} - // - //private void UpdateModelsDetour( IntPtr drawObject ) - //{ - // if( *( int* )( drawObject + 0x90c ) == 0 ) - // { - // return; - // } - // - // ChangeEqp( drawObject ); - // UpdateModelsHook!.Original.Invoke( drawObject ); - // RestoreEqp(); - //} - // - //private void SetupConnectorModelAttributesDetour( IntPtr drawObject, IntPtr unk ) - //{ - // ChangeEqp( drawObject ); - // SetupConnectorModelAttributesHook!.Original.Invoke( drawObject, unk ); - // RestoreEqp(); - //} - // - //private void EnableDrawDetour( IntPtr gameObject, IntPtr b, IntPtr c, IntPtr d ) - //{ - // _lastGameObject = ( GameObject* )gameObject; - // EnableDrawHook!.Original.Invoke( gameObject, b, c, d ); - // _lastGameObject = null; - //} - // - //private IntPtr CharacterBaseCreateDetour( uint a, IntPtr b, IntPtr c, byte d ) - //{ - // var ret = CharacterBaseCreateHook!.Original( a, b, c, d ); - // if( _lastGameObject != null ) - // { - // _drawObjectToObject[ ret ] = _lastGameObject->ObjectIndex; - // } - // - // return ret; - //} - // - //private void CharacterBaseDestructorDetour( IntPtr drawBase ) - //{ - // _drawObjectToObject.Remove( drawBase ); - // CharacterBaseDestructorHook!.Original.Invoke( drawBase ); - //} - // - //private bool VerifyEntry( IntPtr drawObject, int gameObjectIdx, out GameObject* gameObject ) - //{ - // gameObject = ( GameObject* )( Dalamud.Objects[ gameObjectIdx ]?.Address ?? IntPtr.Zero ); - // if( gameObject != null && gameObject->DrawObject == ( DrawObject* )drawObject ) - // { - // return true; - // } - // - // _drawObjectToObject.Remove( drawObject ); - // return false; - //} - // - //private GameObject* FindParent( IntPtr drawObject ) - //{ - // if( _drawObjectToObject.TryGetValue( drawObject, out var gameObjectIdx ) ) - // { - // if( VerifyEntry( drawObject, gameObjectIdx, out var gameObject ) ) - // { - // return gameObject; - // } - // - // _drawObjectToObject.Remove( drawObject ); - // } - // - // if( _lastGameObject != null && ( _lastGameObject->DrawObject == null || _lastGameObject->DrawObject == ( DrawObject* )drawObject ) ) - // { - // return _lastGameObject; - // } - // - // return null; - //} - // - //private void SetCollection( Utf8String path, ModCollection? collection ) - //{ - // if( collection == null ) - // { - // _pathCollections.Remove( path ); - // } - // else if( _pathCollections.ContainsKey( path ) ) - // { - // _pathCollections[ path ] = collection; - // } - // else - // { - // _pathCollections[ path.Clone() ] = collection; - // } - //} - // - //private void LoadMtrlTexHelper( IntPtr mtrlResourceHandle ) - //{ - // if( mtrlResourceHandle == IntPtr.Zero ) - // { - // return; - // } - // - // var numTex = *( byte* )( mtrlResourceHandle + 0xFA ); - // if( numTex == 0 ) - // { - // return; - // } - // - // var handle = ( Structs.ResourceHandle* )mtrlResourceHandle; - // var mtrlName = Utf8String.FromSpanUnsafe( handle->FileNameSpan(), true, null, true ); - // var collection = _pathCollections.TryGetValue( mtrlName, out var c ) ? c : null; - // var texSpace = *( byte** )( mtrlResourceHandle + 0xD0 ); - // for( var i = 0; i < numTex; ++i ) - // { - // var texStringPtr = ( byte* )( *( ulong* )( mtrlResourceHandle + 0xE0 ) + *( ushort* )( texSpace + 8 + i * 16 ) ); - // var texString = new Utf8String( texStringPtr ); - // SetCollection( texString, collection ); - // } - //} - // - //private byte LoadMtrlTexDetour( IntPtr mtrlResourceHandle ) - //{ - // LoadMtrlTexHelper( mtrlResourceHandle ); - // return LoadMtrlTexHook!.Original( mtrlResourceHandle ); - //} - // - //private byte LoadMtrlShpkDetour( IntPtr mtrlResourceHandle ) - // => LoadMtrlShpkHook!.Original( mtrlResourceHandle ); - // - //private IntPtr ResolvePathDetour( IntPtr drawObject, IntPtr path ) - //{ - // if( path == IntPtr.Zero ) - // { - // return path; - // } - // - // var p = new Utf8String( ( byte* )path ); - // - // var parent = FindParent( drawObject ); - // if( parent == null ) - // { - // return path; - // } - // - // var name = new Utf8String( parent->Name ); - // if( name.Length == 0 ) - // { - // return path; - // } - // - // var charName = name.ToString(); - // var gamePath = new Utf8String( ( byte* )path ); - // if( !Service< ModManager >.Get().Collections.CharacterCollection.TryGetValue( charName, out var collection ) ) - // { - // SetCollection( gamePath, null ); - // return path; - // } - // - // SetCollection( gamePath, collection ); - // return path; - //} - // - //private IntPtr ResolveMdlPathDetour( IntPtr drawObject, IntPtr path, IntPtr unk3, uint unk4 ) - //{ - // ChangeEqp( drawObject ); - // var ret = ResolvePathDetour( drawObject, ResolveMdlPathHook!.Original( drawObject, path, unk3, unk4 ) ); - // RestoreEqp(); - // return ret; - //} - // - //private IntPtr ResolveImcPathDetour( IntPtr drawObject, IntPtr path, IntPtr unk3, uint unk4 ) - // => ResolvePathDetour( drawObject, ResolveImcPathHook!.Original( drawObject, path, unk3, unk4 ) ); - // - //private IntPtr ResolveMtrlPathDetour( IntPtr drawObject, IntPtr path, IntPtr unk3, uint unk4, IntPtr unk5 ) - // => ResolvePathDetour( drawObject, ResolveMtrlPathHook!.Original( drawObject, path, unk3, unk4, unk5 ) ); - // - //public PathResolver( ResourceLoader loader, ResidentResourceManager resident ) - //{ - // _loader = loader; - // _resident = resident; - // SignatureHelper.Initialise( this ); - // var data = _resident.CharacterUtility->EqpResource->GetData(); - // fixed( byte* ptr = data ) - // { - // DefaultEqpData = ( IntPtr )ptr; - // } - // - // DefaultEqpLength = data.Length; - // Enable(); - // foreach( var gameObject in Dalamud.Objects ) - // { - // var ptr = ( GameObject* )gameObject.Address; - // if( ptr->IsCharacter() && ptr->DrawObject != null ) - // { - // _drawObjectToObject[ ( IntPtr )ptr->DrawObject ] = ptr->ObjectIndex; - // } - // } - //} - // - // - //private (FullPath?, object?) CharacterReplacer( NewGamePath path ) - //{ - // var modManager = Service< ModManager >.Get(); - // var gamePath = new GamePath( path.ToString() ); - // var nonDefault = _pathCollections.TryGetValue( path.Path, out var collection ); - // if( !nonDefault ) - // { - // collection = modManager.Collections.DefaultCollection; - // } - // - // var resolved = collection!.ResolveSwappedOrReplacementPath( gamePath ); - // if( resolved == null ) - // { - // resolved = modManager.Collections.ForcedCollection.ResolveSwappedOrReplacementPath( gamePath ); - // if( resolved == null ) - // { - // return ( null, collection ); - // } - // - // collection = modManager.Collections.ForcedCollection; - // } - // - // var fullPath = new FullPath( resolved ); - // if( nonDefault ) - // { - // SetCollection( fullPath.InternalName, nonDefault ? collection : null ); - // } - // - // return ( fullPath, collection ); - //} - // - //public void Enable() - //{ - // ResolveMdlPathHook?.Enable(); - // ResolveMtrlPathHook?.Enable(); - // ResolveImcPathHook?.Enable(); - // LoadMtrlTexHook?.Enable(); - // LoadMtrlShpkHook?.Enable(); - // EnableDrawHook?.Enable(); - // CharacterBaseCreateHook?.Enable(); - // _loader.ResolvePath = CharacterReplacer; - // CharacterBaseDestructorHook?.Enable(); - // SetupConnectorModelAttributesHook?.Enable(); - // UpdateModelsHook?.Enable(); - // SetupModelAttributesHook?.Enable(); - // ApplyVisorStuffHook?.Enable(); - //} - // - //public void Disable() - //{ - // _loader.ResolvePath = ResourceLoader.DefaultReplacer; - // ResolveMdlPathHook?.Disable(); - // ResolveMtrlPathHook?.Disable(); - // ResolveImcPathHook?.Disable(); - // LoadMtrlTexHook?.Disable(); - // LoadMtrlShpkHook?.Disable(); - // EnableDrawHook?.Disable(); - // CharacterBaseCreateHook?.Disable(); - // CharacterBaseDestructorHook?.Disable(); - // SetupConnectorModelAttributesHook?.Disable(); - // UpdateModelsHook?.Disable(); - // SetupModelAttributesHook?.Disable(); - // ApplyVisorStuffHook?.Disable(); - //} - // - public void Dispose() - { - // Disable(); - // ResolveMdlPathHook?.Dispose(); - // ResolveMtrlPathHook?.Dispose(); - // ResolveImcPathHook?.Dispose(); - // LoadMtrlTexHook?.Dispose(); - // LoadMtrlShpkHook?.Dispose(); - // EnableDrawHook?.Dispose(); - // CharacterBaseCreateHook?.Dispose(); - // CharacterBaseDestructorHook?.Dispose(); - // SetupConnectorModelAttributesHook?.Dispose(); - // UpdateModelsHook?.Dispose(); - // SetupModelAttributesHook?.Dispose(); - // ApplyVisorStuffHook?.Dispose(); - } -} \ No newline at end of file diff --git a/Penumbra/Interop/Resolver/PathResolver.Data.cs b/Penumbra/Interop/Resolver/PathResolver.Data.cs new file mode 100644 index 00000000..3df5bd1c --- /dev/null +++ b/Penumbra/Interop/Resolver/PathResolver.Data.cs @@ -0,0 +1,249 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Dalamud.Hooking; +using Dalamud.Utility.Signatures; +using FFXIVClientStructs.FFXIV.Client.Game.Character; +using FFXIVClientStructs.FFXIV.Client.Game.Object; +using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; +using FFXIVClientStructs.FFXIV.Component.GUI; +using Penumbra.GameData.ByteString; +using Penumbra.Mods; +using ObjectKind = Dalamud.Game.ClientState.Objects.Enums.ObjectKind; + +namespace Penumbra.Interop.Resolver; + +public unsafe partial class PathResolver +{ + // Keep track of created DrawObjects that are CharacterBase, + // and use the last game object that called EnableDraw to link them. + public delegate IntPtr CharacterBaseCreateDelegate( uint a, IntPtr b, IntPtr c, byte d ); + + [Signature( "E8 ?? ?? ?? ?? 48 85 C0 74 21 C7 40" )] + public Hook< CharacterBaseCreateDelegate >? CharacterBaseCreateHook; + + private IntPtr CharacterBaseCreateDetour( uint a, IntPtr b, IntPtr c, byte d ) + { + using var cmp = MetaChanger.ChangeCmp( this, out var collection ); + var ret = CharacterBaseCreateHook!.Original( a, b, c, d ); + if( LastGameObject != null ) + { + DrawObjectToObject[ ret ] = ( collection!, LastGameObject->ObjectIndex ); + } + + return ret; + } + + + // Remove DrawObjects from the list when they are destroyed. + public delegate void CharacterBaseDestructorDelegate( IntPtr drawBase ); + + [Signature( "E8 ?? ?? ?? ?? 40 F6 C7 01 74 3A 40 F6 C7 04 75 27 48 85 DB 74 2F 48 8B 05 ?? ?? ?? ?? 48 8B D3 48 8B 48 30", + DetourName = "CharacterBaseDestructorDetour" )] + public Hook< CharacterBaseDestructorDelegate >? CharacterBaseDestructorHook; + + private void CharacterBaseDestructorDetour( IntPtr drawBase ) + { + DrawObjectToObject.Remove( drawBase ); + CharacterBaseDestructorHook!.Original.Invoke( drawBase ); + } + + + // EnableDraw is what creates DrawObjects for gameObjects, + // so we always keep track of the current GameObject to be able to link it to the DrawObject. + public delegate void EnableDrawDelegate( IntPtr gameObject, IntPtr b, IntPtr c, IntPtr d ); + + [Signature( "E8 ?? ?? ?? ?? 48 8B 8B ?? ?? ?? ?? 48 85 C9 74 ?? 33 D2 E8 ?? ?? ?? ?? 84 C0" )] + public Hook< EnableDrawDelegate >? EnableDrawHook; + + private void EnableDrawDetour( IntPtr gameObject, IntPtr b, IntPtr c, IntPtr d ) + { + LastGameObject = ( GameObject* )gameObject; + EnableDrawHook!.Original.Invoke( gameObject, b, c, d ); + LastGameObject = null; + } + + private void EnableDataHooks() + { + CharacterBaseCreateHook?.Enable(); + EnableDrawHook?.Enable(); + CharacterBaseDestructorHook?.Enable(); + } + + private void DisableDataHooks() + { + CharacterBaseCreateHook?.Disable(); + EnableDrawHook?.Disable(); + CharacterBaseDestructorHook?.Disable(); + } + + private void DisposeDataHooks() + { + CharacterBaseCreateHook?.Dispose(); + EnableDrawHook?.Dispose(); + CharacterBaseDestructorHook?.Dispose(); + } + + + // This map links DrawObjects directly to Actors (by ObjectTable index) and their collections. + // It contains any DrawObjects that correspond to a human actor, even those without specific collections. + internal readonly Dictionary< IntPtr, (ModCollection, int) > DrawObjectToObject = new(); + + // This map links files to their corresponding collection, if it is non-default. + internal readonly Dictionary< Utf8String, ModCollection > PathCollections = new(); + + internal GameObject* LastGameObject = null; + + // Check that a linked DrawObject still corresponds to the correct actor and that it still exists, otherwise remove it. + private bool VerifyEntry( IntPtr drawObject, int gameObjectIdx, out GameObject* gameObject ) + { + var tmp = Dalamud.Objects[ gameObjectIdx ]; + if( tmp != null ) + { + gameObject = ( GameObject* )tmp.Address; + if( gameObject->DrawObject == ( DrawObject* )drawObject ) + { + return true; + } + } + + gameObject = null; + DrawObjectToObject.Remove( drawObject ); + return false; + } + + // Obtain the name of the current player, if one exists. + private static string? GetPlayerName() + => Dalamud.Objects[ 0 ]?.Name.ToString(); + + // Obtain the name of the inspect target from its window, if it exists. + private static string? GetInspectName() + { + var addon = Dalamud.GameGui.GetAddonByName( "CharacterInspect", 1 ); + if( addon == IntPtr.Zero ) + { + return null; + } + + var ui = ( AtkUnitBase* )addon; + if( ui->UldManager.NodeListCount < 60 ) + { + return null; + } + + var text = ( AtkTextNode* )ui->UldManager.NodeList[ 60 ]; + return text != null ? text->NodeText.ToString() : null; + } + + // Guesstimate whether an unnamed cutscene actor corresponds to the player or not, + // and if so, return the player name. + private static string? GetCutsceneName( GameObject* gameObject ) + { + if( gameObject->Name[ 0 ] != 0 || gameObject->ObjectKind != ( byte )ObjectKind.Player ) + { + return null; + } + + var player = Dalamud.Objects[ 0 ]; + if( player == null ) + { + return null; + } + + var pc = ( Character* )player.Address; + return pc->ClassJob == ( ( Character* )gameObject )->ClassJob ? player.Name.ToString() : null; + } + + // Identify the correct collection for a GameObject by index and name. + private static ModCollection IdentifyCollection( GameObject* gameObject ) + { + if( gameObject == null ) + { + return Penumbra.ModManager.Collections.DefaultCollection; + } + + var name = gameObject->ObjectIndex switch + { + 240 => GetPlayerName(), // character window + 241 => GetInspectName(), // inspect + 242 => GetPlayerName(), // try-on + >= 200 => GetCutsceneName( gameObject ), + _ => null, + } + ?? new Utf8String( gameObject->Name ).ToString(); + + return Penumbra.ModManager.Collections.CharacterCollection.TryGetValue( name, out var col ) + ? col + : Penumbra.ModManager.Collections.DefaultCollection; + } + + // Update collections linked to Game/DrawObjects due to a change in collection configuration. + private void CheckCollections() + { + foreach( var (key, (_, idx)) in DrawObjectToObject.ToArray() ) + { + if( !VerifyEntry( key, idx, out var obj ) ) + { + DrawObjectToObject.Remove( key ); + } + + var collection = IdentifyCollection( obj ); + DrawObjectToObject[ key ] = ( collection, idx ); + } + } + + // Use the stored information to find the GameObject and Collection linked to a DrawObject. + private GameObject* FindParent( IntPtr drawObject, out ModCollection collection ) + { + if( DrawObjectToObject.TryGetValue( drawObject, out var data ) ) + { + var gameObjectIdx = data.Item2; + if( VerifyEntry( drawObject, gameObjectIdx, out var gameObject ) ) + { + collection = data.Item1; + return gameObject; + } + } + + if( LastGameObject != null && ( LastGameObject->DrawObject == null || LastGameObject->DrawObject == ( DrawObject* )drawObject ) ) + { + collection = IdentifyCollection( LastGameObject ); + return LastGameObject; + } + + + collection = IdentifyCollection( null ); + return null; + } + + + // Special handling for paths so that we do not store non-owned temporary strings in the dictionary. + private void SetCollection( Utf8String path, ModCollection? collection ) + { + if( collection == null ) + { + PathCollections.Remove( path ); + } + else if( PathCollections.ContainsKey( path ) || path.IsOwned ) + { + PathCollections[ path ] = collection; + } + else + { + PathCollections[ path.Clone() ] = collection; + } + } + + // Find all current DrawObjects used in the GameObject table. + private void InitializeDrawObjects() + { + foreach( var gameObject in Dalamud.Objects ) + { + var ptr = ( GameObject* )gameObject.Address; + if( ptr->IsCharacter() && ptr->DrawObject != null ) + { + DrawObjectToObject[ ( IntPtr )ptr->DrawObject ] = ( IdentifyCollection( ptr ), ptr->ObjectIndex ); + } + } + } +} \ No newline at end of file diff --git a/Penumbra/Interop/Resolver/PathResolver.Human.cs b/Penumbra/Interop/Resolver/PathResolver.Human.cs new file mode 100644 index 00000000..2acba0f6 --- /dev/null +++ b/Penumbra/Interop/Resolver/PathResolver.Human.cs @@ -0,0 +1,128 @@ +using System; +using Dalamud.Hooking; +using Dalamud.Utility.Signatures; + +namespace Penumbra.Interop.Resolver; + +// We can hook the different Resolve-Functions using just the VTable of Human. +// The other DrawObject VTables and the ResolveRoot function are currently unused. +public unsafe partial class PathResolver +{ + [Signature( "48 8D 05 ?? ?? ?? ?? 48 89 03 48 8D 8B ?? ?? ?? ?? 44 89 83 ?? ?? ?? ?? 48 8B C1", ScanType = ScanType.StaticAddress )] + public IntPtr* DrawObjectHumanVTable; + + // [Signature( "48 8D 1D ?? ?? ?? ?? 48 C7 41", ScanType = ScanType.StaticAddress )] + // public IntPtr* DrawObjectVTable; + // + // [Signature( "48 8D 05 ?? ?? ?? ?? 45 33 C0 48 89 03 BA", ScanType = ScanType.StaticAddress )] + // public IntPtr* DrawObjectDemihumanVTable; + // + // [Signature( "48 8D 05 ?? ?? ?? ?? 48 89 03 33 C0 48 89 83 ?? ?? ?? ?? 48 89 83 ?? ?? ?? ?? C7 83", ScanType = ScanType.StaticAddress )] + // public IntPtr* DrawObjectMonsterVTable; + // + // [Signature( "48 8D 05 ?? ?? ?? ?? 48 89 03 B8 ?? ?? ?? ?? 66 89 83 ?? ?? ?? ?? 48 8B C3 48 89 8B ?? ?? ?? ?? 48 89 8B", + // ScanType = ScanType.StaticAddress )] + // public IntPtr* DrawObjectWeaponVTable; + // + // public const int ResolveRootIdx = 71; + + public const int ResolveSklbIdx = 72; + public const int ResolveMdlIdx = 73; + public const int ResolveSkpIdx = 74; + public const int ResolvePhybIdx = 75; + public const int ResolvePapIdx = 76; + public const int ResolveTmbIdx = 77; + public const int ResolveMPapIdx = 79; + public const int ResolveImcIdx = 81; + public const int ResolveMtrlIdx = 82; + public const int ResolveDecalIdx = 83; + public const int ResolveVfxIdx = 84; + public const int ResolveEidIdx = 85; + + public const int OnModelLoadCompleteIdx = 58; + + public delegate IntPtr GeneralResolveDelegate( IntPtr drawObject, IntPtr path, IntPtr unk3, uint unk4 ); + public delegate IntPtr MPapResolveDelegate( IntPtr drawObject, IntPtr path, IntPtr unk3, uint unk4, uint unk5 ); + public delegate IntPtr MaterialResolveDetour( IntPtr drawObject, IntPtr path, IntPtr unk3, uint unk4, ulong unk5 ); + public delegate IntPtr EidResolveDelegate( IntPtr drawObject, IntPtr path, IntPtr unk3 ); + + public delegate void OnModelLoadCompleteDelegate( IntPtr drawObject ); + + public Hook< GeneralResolveDelegate >? ResolveDecalPathHook; + public Hook< EidResolveDelegate >? ResolveEidPathHook; + public Hook< GeneralResolveDelegate >? ResolveImcPathHook; + public Hook< MPapResolveDelegate >? ResolveMPapPathHook; + public Hook< GeneralResolveDelegate >? ResolveMdlPathHook; + public Hook< MaterialResolveDetour >? ResolveMtrlPathHook; + public Hook< MaterialResolveDetour >? ResolvePapPathHook; + public Hook< GeneralResolveDelegate >? ResolvePhybPathHook; + public Hook< GeneralResolveDelegate >? ResolveSklbPathHook; + public Hook< GeneralResolveDelegate >? ResolveSkpPathHook; + public Hook< EidResolveDelegate >? ResolveTmbPathHook; + public Hook< MaterialResolveDetour >? ResolveVfxPathHook; + + + private void SetupHumanHooks() + { + ResolveDecalPathHook = new Hook< GeneralResolveDelegate >( DrawObjectHumanVTable[ ResolveDecalIdx ], ResolveDecalDetour ); + ResolveEidPathHook = new Hook< EidResolveDelegate >( DrawObjectHumanVTable[ ResolveEidIdx ], ResolveEidDetour ); + ResolveImcPathHook = new Hook< GeneralResolveDelegate >( DrawObjectHumanVTable[ ResolveImcIdx ], ResolveImcDetour ); + ResolveMPapPathHook = new Hook< MPapResolveDelegate >( DrawObjectHumanVTable[ ResolveMPapIdx ], ResolveMPapDetour ); + ResolveMdlPathHook = new Hook< GeneralResolveDelegate >( DrawObjectHumanVTable[ ResolveMdlIdx ], ResolveMdlDetour ); + ResolveMtrlPathHook = new Hook< MaterialResolveDetour >( DrawObjectHumanVTable[ ResolveMtrlIdx ], ResolveMtrlDetour ); + ResolvePapPathHook = new Hook< MaterialResolveDetour >( DrawObjectHumanVTable[ ResolvePapIdx ], ResolvePapDetour ); + ResolvePhybPathHook = new Hook< GeneralResolveDelegate >( DrawObjectHumanVTable[ ResolvePhybIdx ], ResolvePhybDetour ); + ResolveSklbPathHook = new Hook< GeneralResolveDelegate >( DrawObjectHumanVTable[ ResolveSklbIdx ], ResolveSklbDetour ); + ResolveSkpPathHook = new Hook< GeneralResolveDelegate >( DrawObjectHumanVTable[ ResolveSkpIdx ], ResolveSkpDetour ); + ResolveTmbPathHook = new Hook< EidResolveDelegate >( DrawObjectHumanVTable[ ResolveTmbIdx ], ResolveTmbDetour ); + ResolveVfxPathHook = new Hook< MaterialResolveDetour >( DrawObjectHumanVTable[ ResolveVfxIdx ], ResolveVfxDetour ); + } + + private void EnableHumanHooks() + { + ResolveDecalPathHook?.Enable(); + ResolveEidPathHook?.Enable(); + ResolveImcPathHook?.Enable(); + ResolveMPapPathHook?.Enable(); + ResolveMdlPathHook?.Enable(); + ResolveMtrlPathHook?.Enable(); + ResolvePapPathHook?.Enable(); + ResolvePhybPathHook?.Enable(); + ResolveSklbPathHook?.Enable(); + ResolveSkpPathHook?.Enable(); + ResolveTmbPathHook?.Enable(); + ResolveVfxPathHook?.Enable(); + } + + private void DisableHumanHooks() + { + ResolveDecalPathHook?.Disable(); + ResolveEidPathHook?.Disable(); + ResolveImcPathHook?.Disable(); + ResolveMPapPathHook?.Disable(); + ResolveMdlPathHook?.Disable(); + ResolveMtrlPathHook?.Disable(); + ResolvePapPathHook?.Disable(); + ResolvePhybPathHook?.Disable(); + ResolveSklbPathHook?.Disable(); + ResolveSkpPathHook?.Disable(); + ResolveTmbPathHook?.Disable(); + ResolveVfxPathHook?.Disable(); + } + + private void DisposeHumanHooks() + { + ResolveDecalPathHook?.Dispose(); + ResolveEidPathHook?.Dispose(); + ResolveImcPathHook?.Dispose(); + ResolveMPapPathHook?.Dispose(); + ResolveMdlPathHook?.Dispose(); + ResolveMtrlPathHook?.Dispose(); + ResolvePapPathHook?.Dispose(); + ResolvePhybPathHook?.Dispose(); + ResolveSklbPathHook?.Dispose(); + ResolveSkpPathHook?.Dispose(); + ResolveTmbPathHook?.Dispose(); + ResolveVfxPathHook?.Dispose(); + } +} \ No newline at end of file diff --git a/Penumbra/Interop/Resolver/PathResolver.Material.cs b/Penumbra/Interop/Resolver/PathResolver.Material.cs new file mode 100644 index 00000000..8afffdba --- /dev/null +++ b/Penumbra/Interop/Resolver/PathResolver.Material.cs @@ -0,0 +1,91 @@ +using System; +using Dalamud.Hooking; +using Dalamud.Utility.Signatures; +using Penumbra.GameData.ByteString; +using Penumbra.Interop.Structs; + +namespace Penumbra.Interop.Resolver; + +// Materials do contain their own paths to textures and shader packages. +// Those are loaded synchronously. +// Thus, we need to ensure the correct files are loaded when a material is loaded. +public unsafe partial class PathResolver +{ + public delegate byte LoadMtrlFilesDelegate( IntPtr mtrlResourceHandle ); + + [Signature( "4C 8B DC 49 89 5B ?? 49 89 73 ?? 55 57 41 55", DetourName = "LoadMtrlTexDetour" )] + public Hook< LoadMtrlFilesDelegate >? LoadMtrlTexHook; + + private byte LoadMtrlTexDetour( IntPtr mtrlResourceHandle ) + { + LoadMtrlTexHelper( mtrlResourceHandle ); + return LoadMtrlTexHook!.Original( mtrlResourceHandle ); + } + + [Signature( "48 89 5C 24 ?? 57 48 81 EC ?? ?? ?? ?? 48 8B 05 ?? ?? ?? ?? 48 33 C4 48 89 84 24 ?? ?? ?? ?? 44 0F B7 89", + DetourName = "LoadMtrlShpkDetour" )] + public Hook< LoadMtrlFilesDelegate >? LoadMtrlShpkHook; + + private byte LoadMtrlShpkDetour( IntPtr mtrlResourceHandle ) + { + LoadMtrlShpkHelper( mtrlResourceHandle ); + return LoadMtrlShpkHook!.Original( mtrlResourceHandle ); + } + + // Taken from the actual hooked function. The Shader string is just concatenated to this base directory. + private static readonly Utf8String ShaderBase = Utf8String.FromStringUnsafe( "shader/sm5/shpk", true ); + + private void LoadMtrlShpkHelper( IntPtr mtrlResourceHandle ) + { + if( mtrlResourceHandle == IntPtr.Zero ) + { + return; + } + + var mtrl = ( MtrlResource* )mtrlResourceHandle; + var shpkPath = Utf8String.Join( ( byte )'/', ShaderBase, new Utf8String( mtrl->ShpkString ).AsciiToLower() ); + var mtrlPath = Utf8String.FromSpanUnsafe( mtrl->Handle.FileNameSpan(), true, null, true ); + var collection = PathCollections.TryGetValue( mtrlPath, out var c ) ? c : null; + SetCollection( shpkPath, collection ); + } + + private void LoadMtrlTexHelper( IntPtr mtrlResourceHandle ) + { + if( mtrlResourceHandle == IntPtr.Zero ) + { + return; + } + + var mtrl = ( MtrlResource* )mtrlResourceHandle; + if( mtrl->NumTex == 0 ) + { + return; + } + + var mtrlPath = Utf8String.FromSpanUnsafe( mtrl->Handle.FileNameSpan(), true, null, true ); + var collection = PathCollections.TryGetValue( mtrlPath, out var c ) ? c : null; + for( var i = 0; i < mtrl->NumTex; ++i ) + { + var texString = new Utf8String( mtrl->TexString( i ) ); + SetCollection( texString, collection ); + } + } + + private void EnableMtrlHooks() + { + LoadMtrlShpkHook?.Enable(); + LoadMtrlTexHook?.Enable(); + } + + private void DisableMtrlHooks() + { + LoadMtrlShpkHook?.Disable(); + LoadMtrlTexHook?.Disable(); + } + + private void DisposeMtrlHooks() + { + LoadMtrlShpkHook?.Dispose(); + LoadMtrlTexHook?.Dispose(); + } +} \ No newline at end of file diff --git a/Penumbra/Interop/Resolver/PathResolver.Meta.cs b/Penumbra/Interop/Resolver/PathResolver.Meta.cs new file mode 100644 index 00000000..2ae74559 --- /dev/null +++ b/Penumbra/Interop/Resolver/PathResolver.Meta.cs @@ -0,0 +1,274 @@ +using System; +using Dalamud.Hooking; +using Dalamud.Utility.Signatures; +using Penumbra.Meta.Manipulations; +using Penumbra.Mods; + +namespace Penumbra.Interop.Resolver; + +// State: 6.08 Hotfix. +// GetSlotEqpData seems to be the only function using the EQP table. +// It is only called by CheckSlotsForUnload (called by UpdateModels), +// SetupModelAttributes (called by UpdateModels and OnModelLoadComplete) +// and a unnamed function called by UpdateRender. +// It seems to be enough to change the EQP entries for UpdateModels. + +// GetEqdpDataFor[Adults|Children|Other] seem to be the only functions using the EQDP tables. +// They are called by ResolveMdlPath, UpdateModels and SetupConnectorModelAttributes, +// which is called by SetupModelAttributes, which is called by OnModelLoadComplete and UpdateModels. +// It seems to be enough to change EQDP on UpdateModels and ResolveMDLPath. + +// 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. + +// 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. + +public unsafe partial class PathResolver +{ + public delegate void UpdateModelDelegate( IntPtr drawObject ); + + [Signature( "48 8B ?? 56 48 83 ?? ?? ?? B9", DetourName = "UpdateModelsDetour" )] + public Hook< UpdateModelDelegate >? UpdateModelsHook; + + private void UpdateModelsDetour( IntPtr drawObject ) + { + // Shortcut because this is called all the time. + // Same thing is checked at the beginning of the original function. + if( *( int* )( drawObject + 0x90c ) == 0 ) + { + return; + } + + var collection = GetCollection( drawObject ); + if( collection != null ) + { + using var eqp = MetaChanger.ChangeEqp( collection ); + using var eqdp = MetaChanger.ChangeEqdp( collection ); + UpdateModelsHook!.Original.Invoke( drawObject ); + } + else + { + UpdateModelsHook!.Original.Invoke( drawObject ); + } + } + + [Signature( "40 ?? 48 83 ?? ?? ?? 81 ?? ?? ?? ?? ?? 48 8B ?? 74 ?? ?? 83 ?? ?? ?? ?? ?? ?? 74 ?? 4C", + DetourName = "GetEqpIndirectDetour" )] + public Hook< OnModelLoadCompleteDelegate >? GetEqpIndirectHook; + + private void GetEqpIndirectDetour( IntPtr drawObject ) + { + // Shortcut because this is also called all the time. + // Same thing is checked at the beginning of the original function. + if( ( *( byte* )( drawObject + 0xa30 ) & 1 ) == 0 || *( ulong* )( drawObject + 0xa28 ) == 0 ) + { + return; + } + + using var eqp = MetaChanger.ChangeEqp( this, drawObject ); + GetEqpIndirectHook!.Original( drawObject ); + } + + public Hook< OnModelLoadCompleteDelegate >? OnModelLoadCompleteHook; + + private void OnModelLoadCompleteDetour( IntPtr drawObject ) + { + var collection = GetCollection( drawObject ); + if( collection != null ) + { + using var eqp = MetaChanger.ChangeEqp( collection ); + using var eqdp = MetaChanger.ChangeEqdp( collection ); + OnModelLoadCompleteHook!.Original.Invoke( drawObject ); + } + else + { + OnModelLoadCompleteHook!.Original.Invoke( drawObject ); + } + } + + // GMP + public delegate void ApplyVisorDelegate( IntPtr drawObject, IntPtr unk1, float unk2, IntPtr unk3, ushort unk4, char unk5 ); + + [Signature( "48 8B ?? 53 55 57 48 83 ?? ?? 48 8B", DetourName = "ApplyVisorDetour" )] + public Hook< ApplyVisorDelegate >? ApplyVisorHook; + + private void ApplyVisorDetour( IntPtr drawObject, IntPtr unk1, float unk2, IntPtr unk3, ushort unk4, char unk5 ) + { + using var gmp = MetaChanger.ChangeGmp( this, drawObject ); + ApplyVisorHook!.Original( drawObject, unk1, unk2, unk3, unk4, unk5 ); + } + + private void SetupMetaHooks() + { + OnModelLoadCompleteHook = + new Hook< OnModelLoadCompleteDelegate >( DrawObjectHumanVTable[ OnModelLoadCompleteIdx ], OnModelLoadCompleteDetour ); + } + + private void EnableMetaHooks() + { +#if USE_EQP + GetEqpIndirectHook?.Enable(); +#endif +#if USE_EQP || USE_EQDP + UpdateModelsHook?.Enable(); + OnModelLoadCompleteHook?.Enable(); +#endif +#if USE_GMP + ApplyVisorHook?.Enable(); +#endif + } + + private void DisableMetaHooks() + { + GetEqpIndirectHook?.Disable(); + UpdateModelsHook?.Disable(); + OnModelLoadCompleteHook?.Disable(); + ApplyVisorHook?.Disable(); + } + + private void DisposeMetaHooks() + { + GetEqpIndirectHook?.Dispose(); + UpdateModelsHook?.Dispose(); + OnModelLoadCompleteHook?.Dispose(); + ApplyVisorHook?.Dispose(); + } + + private ModCollection? GetCollection( IntPtr drawObject ) + { + var parent = FindParent( drawObject, out var collection ); + if( parent == null || collection == Penumbra.ModManager.Collections.DefaultCollection ) + { + return null; + } + + return collection.Cache == null ? Penumbra.ModManager.Collections.ForcedCollection : collection; + } + + + // Small helper to handle setting metadata and reverting it at the end of the function. + private readonly struct MetaChanger : IDisposable + { + private readonly MetaManipulation.Type _type; + + private MetaChanger( MetaManipulation.Type type ) + => _type = type; + + public static MetaChanger ChangeEqp( ModCollection collection ) + { +#if USE_EQP + collection.SetEqpFiles(); + return new MetaChanger( MetaManipulation.Type.Eqp ); +#else + return new MetaChanger( MetaManipulation.Type.Unknown ); +#endif + } + + public static MetaChanger ChangeEqp( PathResolver resolver, IntPtr drawObject ) + { +#if USE_EQP + var collection = resolver.GetCollection( drawObject ); + if( collection != null ) + { + return ChangeEqp( collection ); + } +#endif + return new MetaChanger( MetaManipulation.Type.Unknown ); + } + + public static MetaChanger ChangeEqdp( PathResolver resolver, IntPtr drawObject ) + { +#if USE_EQDP + var collection = resolver.GetCollection( drawObject ); + if( collection != null ) + { + return ChangeEqdp( collection ); + } +#endif + return new MetaChanger( MetaManipulation.Type.Unknown ); + } + + public static MetaChanger ChangeEqdp( ModCollection collection ) + { +#if USE_EQDP + collection.SetEqdpFiles(); + return new MetaChanger( MetaManipulation.Type.Eqdp ); +#else + return new MetaChanger( MetaManipulation.Type.Unknown ); +#endif + } + + public static MetaChanger ChangeGmp( PathResolver resolver, IntPtr drawObject ) + { +#if USE_GMP + var collection = resolver.GetCollection( drawObject ); + if( collection != null ) + { + collection.SetGmpFiles(); + return new MetaChanger( MetaManipulation.Type.Gmp ); + } +#endif + return new MetaChanger( MetaManipulation.Type.Unknown ); + } + + public static MetaChanger ChangeEst( PathResolver resolver, IntPtr drawObject ) + { +#if USE_EST + var collection = resolver.GetCollection( drawObject ); + if( collection != null ) + { + collection.SetEstFiles(); + return new MetaChanger( MetaManipulation.Type.Est ); + } +#endif + return new MetaChanger( MetaManipulation.Type.Unknown ); + } + + public static MetaChanger ChangeCmp( PathResolver resolver, out ModCollection? collection ) + { + if( resolver.LastGameObject != null ) + { + collection = IdentifyCollection( resolver.LastGameObject ); +#if USE_CMP + if( collection != Penumbra.ModManager.Collections.DefaultCollection && collection.Cache != null ) + { + collection.SetCmpFiles(); + return new MetaChanger( MetaManipulation.Type.Rsp ); + } +#endif + } + else + { + collection = null; + } + + return new MetaChanger( MetaManipulation.Type.Unknown ); + } + + public void Dispose() + { + switch( _type ) + { + case MetaManipulation.Type.Eqdp: + Penumbra.ModManager.Collections.DefaultCollection.SetEqdpFiles(); + break; + case MetaManipulation.Type.Eqp: + Penumbra.ModManager.Collections.DefaultCollection.SetEqpFiles(); + break; + case MetaManipulation.Type.Est: + Penumbra.ModManager.Collections.DefaultCollection.SetEstFiles(); + break; + case MetaManipulation.Type.Gmp: + Penumbra.ModManager.Collections.DefaultCollection.SetGmpFiles(); + break; + case MetaManipulation.Type.Rsp: + Penumbra.ModManager.Collections.DefaultCollection.SetCmpFiles(); + break; + } + } + } +} \ No newline at end of file diff --git a/Penumbra/Interop/Resolver/PathResolver.Resolve.cs b/Penumbra/Interop/Resolver/PathResolver.Resolve.cs new file mode 100644 index 00000000..afc6b2c0 --- /dev/null +++ b/Penumbra/Interop/Resolver/PathResolver.Resolve.cs @@ -0,0 +1,89 @@ +using System; +using System.Runtime.CompilerServices; +using Penumbra.GameData.ByteString; +using Penumbra.Mods; + +namespace Penumbra.Interop.Resolver; + +// The actual resolve detours are basically all the same. +public unsafe partial class PathResolver +{ + private IntPtr ResolveDecalDetour( IntPtr drawObject, IntPtr path, IntPtr unk3, uint unk4 ) + => ResolvePathDetour( drawObject, ResolveDecalPathHook!.Original( drawObject, path, unk3, unk4 ) ); + + private IntPtr ResolveEidDetour( IntPtr drawObject, IntPtr path, IntPtr unk3 ) + => ResolvePathDetour( drawObject, ResolveEidPathHook!.Original( drawObject, path, unk3 ) ); + + private IntPtr ResolveImcDetour( IntPtr drawObject, IntPtr path, IntPtr unk3, uint unk4 ) + => ResolvePathDetour( drawObject, ResolveImcPathHook!.Original( drawObject, path, unk3, unk4 ) ); + + private IntPtr ResolveMPapDetour( IntPtr drawObject, IntPtr path, IntPtr unk3, uint unk4, uint unk5 ) + => ResolvePathDetour( drawObject, ResolveMPapPathHook!.Original( drawObject, path, unk3, unk4, unk5 ) ); + + private IntPtr ResolveMdlDetour( IntPtr drawObject, IntPtr path, IntPtr unk3, uint unk4 ) + { + using var eqdp = MetaChanger.ChangeEqdp( this, drawObject ); + return ResolvePathDetour( drawObject, ResolveMdlPathHook!.Original( drawObject, path, unk3, unk4 ) ); + } + + private IntPtr ResolveMtrlDetour( IntPtr drawObject, IntPtr path, IntPtr unk3, uint unk4, ulong unk5 ) + => ResolvePathDetour( drawObject, ResolveMtrlPathHook!.Original( drawObject, path, unk3, unk4, unk5 ) ); + + private IntPtr ResolvePapDetour( IntPtr drawObject, IntPtr path, IntPtr unk3, uint unk4, ulong unk5 ) + { + using var est = MetaChanger.ChangeEst( this, drawObject ); + return ResolvePathDetour( drawObject, ResolvePapPathHook!.Original( drawObject, path, unk3, unk4, unk5 ) ); + } + + private IntPtr ResolvePhybDetour( IntPtr drawObject, IntPtr path, IntPtr unk3, uint unk4 ) + { + using var est = MetaChanger.ChangeEst( this, drawObject ); + return ResolvePathDetour( drawObject, ResolvePhybPathHook!.Original( drawObject, path, unk3, unk4 ) ); + } + + private IntPtr ResolveSklbDetour( IntPtr drawObject, IntPtr path, IntPtr unk3, uint unk4 ) + { + using var est = MetaChanger.ChangeEst( this, drawObject ); + return ResolvePathDetour( drawObject, ResolveSklbPathHook!.Original( drawObject, path, unk3, unk4 ) ); + } + + private IntPtr ResolveSkpDetour( IntPtr drawObject, IntPtr path, IntPtr unk3, uint unk4 ) + { + using var est = MetaChanger.ChangeEst( this, drawObject ); + return ResolvePathDetour( drawObject, ResolveSkpPathHook!.Original( drawObject, path, unk3, unk4 ) ); + } + + private IntPtr ResolveTmbDetour( IntPtr drawObject, IntPtr path, IntPtr unk3 ) + => ResolvePathDetour( drawObject, ResolveTmbPathHook!.Original( drawObject, path, unk3 ) ); + + private IntPtr ResolveVfxDetour( IntPtr drawObject, IntPtr path, IntPtr unk3, uint unk4, ulong unk5 ) + => ResolvePathDetour( drawObject, ResolveVfxPathHook!.Original( drawObject, path, unk3, unk4, unk5 ) ); + + + [MethodImpl( MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization )] + private IntPtr ResolvePathDetour( IntPtr drawObject, IntPtr path ) + => ResolvePathDetour( FindParent( drawObject, out var collection ) == null + ? Penumbra.ModManager.Collections.DefaultCollection + : collection, path ); + + + // Just add or remove the resolved path. + [MethodImpl( MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization )] + private IntPtr ResolvePathDetour( ModCollection collection, IntPtr path ) + { + if( path == IntPtr.Zero ) + { + return path; + } + + var gamePath = new Utf8String( ( byte* )path ); + if( collection == Penumbra.ModManager.Collections.DefaultCollection ) + { + SetCollection( gamePath, null ); + return path; + } + + SetCollection( gamePath, collection ); + return path; + } +} \ No newline at end of file diff --git a/Penumbra/Interop/Resolver/PathResolver.cs b/Penumbra/Interop/Resolver/PathResolver.cs new file mode 100644 index 00000000..da0ae9b1 --- /dev/null +++ b/Penumbra/Interop/Resolver/PathResolver.cs @@ -0,0 +1,99 @@ +using System; +using Dalamud.Utility.Signatures; +using Penumbra.GameData.ByteString; +using Penumbra.Interop.Loader; + +namespace Penumbra.Interop.Resolver; + +// The Path Resolver handles character collections. +// It will hook any path resolving functions for humans, +// as well as DrawObject creation. +// It links draw objects to actors, and actors to character collections, +// to resolve paths for character collections. +public partial class PathResolver : IDisposable +{ + private readonly ResourceLoader _loader; + + // Keep track of the last path resolver to be able to restore it. + private Func< Utf8GamePath, (FullPath?, object?) > _oldResolver = null!; + + public PathResolver( ResourceLoader loader ) + { + _loader = loader; + SignatureHelper.Initialise( this ); + SetupHumanHooks(); + SetupMetaHooks(); + Enable(); + } + + // The modified resolver that handles game path resolving. + private (FullPath?, object?) CharacterResolver( Utf8GamePath gamePath ) + { + // Check if the path was marked for a specific collection, if not use the default collection. + var nonDefault = PathCollections.TryGetValue( gamePath.Path, out var collection ); + if( !nonDefault ) + { + collection = Penumbra.ModManager.Collections.DefaultCollection; + } + else + { + // We can remove paths after they have actually been loaded. + // A potential next request will add the path anew. + PathCollections.Remove( gamePath.Path ); + } + + // Resolve using character/default collection first, otherwise forced, as usual. + var resolved = collection!.ResolveSwappedOrReplacementPath( gamePath ); + if( resolved == null ) + { + resolved = Penumbra.ModManager.Collections.ForcedCollection.ResolveSwappedOrReplacementPath( gamePath ); + if( resolved == null ) + { + return ( null, collection ); + } + + collection = Penumbra.ModManager.Collections.ForcedCollection; + } + + // Since mtrl files load their files separately, we need to add the new, resolved path + // so that the functions loading tex and shpk can find that path and use its collection. + if( nonDefault && resolved.Value.Extension == ".mtrl" ) + { + SetCollection( resolved.Value.InternalName, nonDefault ? collection : null ); + } + + return ( resolved, collection ); + } + + public void Enable() + { + InitializeDrawObjects(); + + EnableHumanHooks(); + EnableMtrlHooks(); + EnableDataHooks(); + EnableMetaHooks(); + + _oldResolver = _loader.ResolvePath; + _loader.ResolvePath = CharacterResolver; + } + + public void Disable() + { + DisableHumanHooks(); + DisableMtrlHooks(); + DisableDataHooks(); + DisableMetaHooks(); + + _loader.ResolvePath = _oldResolver; + } + + public void Dispose() + { + Disable(); + DisposeHumanHooks(); + DisposeMtrlHooks(); + DisposeDataHooks(); + DisposeMetaHooks(); + } +} \ No newline at end of file diff --git a/Penumbra/Interop/Structs/MtrlResource.cs b/Penumbra/Interop/Structs/MtrlResource.cs new file mode 100644 index 00000000..ff5b6abf --- /dev/null +++ b/Penumbra/Interop/Structs/MtrlResource.cs @@ -0,0 +1,28 @@ +using System.Runtime.InteropServices; + +namespace Penumbra.Interop.Structs; + +[StructLayout( LayoutKind.Explicit )] +public unsafe struct MtrlResource +{ + [FieldOffset( 0x00 )] + public ResourceHandle Handle; + + [FieldOffset( 0xD0 )] + public ushort* TexSpace; // Contains the offsets for the tex files inside the string list. + + [FieldOffset( 0xE0 )] + public byte* StringList; + + [FieldOffset( 0xF8 )] + public ushort ShpkOffset; + + [FieldOffset( 0xFA )] + public byte NumTex; + + public byte* ShpkString + => StringList + ShpkOffset; + + public byte* TexString( int idx ) + => StringList + *( TexSpace + 4 + idx * 8 ); +} \ No newline at end of file diff --git a/Penumbra/Meta/Files/CmpFile.cs b/Penumbra/Meta/Files/CmpFile.cs index e54cc3e4..bdb5c10b 100644 --- a/Penumbra/Meta/Files/CmpFile.cs +++ b/Penumbra/Meta/Files/CmpFile.cs @@ -1,3 +1,4 @@ +using System; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; using Penumbra.GameData.Util; @@ -6,14 +7,16 @@ using System.Collections.Generic; namespace Penumbra.Meta.Files; +// The human.cmp file contains many character-relevant parameters like color sets. +// We only support manipulating the racial scaling parameters at the moment. public sealed unsafe class CmpFile : MetaBaseFile { private const int RacialScalingStart = 0x2A800; public float this[ SubRace subRace, RspAttribute attribute ] { - get => *( float* )( Data + RacialScalingStart + subRace.ToRspIndex() * RspEntry.ByteSize + ( int )attribute * 4 ); - set => *( float* )( Data + RacialScalingStart + subRace.ToRspIndex() * RspEntry.ByteSize + ( int )attribute * 4 ) = value; + get => *( float* )( Data + RacialScalingStart + ToRspIndex( subRace ) * RspEntry.ByteSize + ( int )attribute * 4 ); + set => *( float* )( Data + RacialScalingStart + ToRspIndex( subRace ) * RspEntry.ByteSize + ( int )attribute * 4 ) = value; } public override void Reset() @@ -37,6 +40,29 @@ public sealed unsafe class CmpFile : MetaBaseFile public static float GetDefault( SubRace subRace, RspAttribute attribute ) { var data = ( byte* )Penumbra.CharacterUtility.DefaultResources[ CharacterUtility.HumanCmpIdx ].Address; - return *( float* )( data + RacialScalingStart + subRace.ToRspIndex() * RspEntry.ByteSize + ( int )attribute * 4 ); + return *( float* )( data + RacialScalingStart + ToRspIndex( subRace ) * RspEntry.ByteSize + ( int )attribute * 4 ); } + + private static int ToRspIndex( SubRace subRace ) + => subRace switch + { + SubRace.Midlander => 0, + SubRace.Highlander => 1, + SubRace.Wildwood => 10, + SubRace.Duskwight => 11, + SubRace.Plainsfolk => 20, + SubRace.Dunesfolk => 21, + SubRace.SeekerOfTheSun => 30, + SubRace.KeeperOfTheMoon => 31, + SubRace.Seawolf => 40, + SubRace.Hellsguard => 41, + SubRace.Raen => 50, + SubRace.Xaela => 51, + SubRace.Helion => 60, + SubRace.Lost => 61, + SubRace.Rava => 70, + SubRace.Veena => 71, + SubRace.Unknown => 0, + _ => throw new ArgumentOutOfRangeException( nameof( subRace ), subRace, null ), + }; } \ No newline at end of file diff --git a/Penumbra/Meta/Manager/MetaManager.Cmp.cs b/Penumbra/Meta/Manager/MetaManager.Cmp.cs index d073631a..6cfa47b8 100644 --- a/Penumbra/Meta/Manager/MetaManager.Cmp.cs +++ b/Penumbra/Meta/Manager/MetaManager.Cmp.cs @@ -18,6 +18,14 @@ public partial class MetaManager public MetaManagerCmp() { } + [Conditional( "USE_CMP" )] + public void SetFiles() + => SetFile( File, CharacterUtility.HumanCmpIdx ); + + [Conditional( "USE_CMP" )] + public static void ResetFiles() + => SetFile( null, CharacterUtility.HumanCmpIdx ); + [Conditional( "USE_CMP" )] public void Reset() { @@ -30,10 +38,6 @@ public partial class MetaManager Manipulations.Clear(); } - [Conditional( "USE_CMP" )] - public void SetFiles() - => SetFile( File, CharacterUtility.HumanCmpIdx ); - public bool ApplyMod( RspManipulation m, Mod.Mod mod ) { #if USE_CMP diff --git a/Penumbra/Meta/Manager/MetaManager.Eqdp.cs b/Penumbra/Meta/Manager/MetaManager.Eqdp.cs index 608c77d4..8edf1150 100644 --- a/Penumbra/Meta/Manager/MetaManager.Eqdp.cs +++ b/Penumbra/Meta/Manager/MetaManager.Eqdp.cs @@ -29,6 +29,15 @@ public partial class MetaManager } } + [Conditional( "USE_EQDP" )] + public static void ResetFiles() + { + foreach( var idx in CharacterUtility.EqdpIndices ) + { + SetFile( null, idx ); + } + } + [Conditional( "USE_EQDP" )] public void Reset() { diff --git a/Penumbra/Meta/Manager/MetaManager.Eqp.cs b/Penumbra/Meta/Manager/MetaManager.Eqp.cs index 65c38ef5..dd7c63ff 100644 --- a/Penumbra/Meta/Manager/MetaManager.Eqp.cs +++ b/Penumbra/Meta/Manager/MetaManager.Eqp.cs @@ -22,6 +22,10 @@ public partial class MetaManager public void SetFiles() => SetFile( File, CharacterUtility.EqpIdx ); + [Conditional( "USE_EQP" )] + public static void ResetFiles() + => SetFile( null, CharacterUtility.EqpIdx ); + [Conditional( "USE_EQP" )] public void Reset() { diff --git a/Penumbra/Meta/Manager/MetaManager.Est.cs b/Penumbra/Meta/Manager/MetaManager.Est.cs index 558873cc..f53d145d 100644 --- a/Penumbra/Meta/Manager/MetaManager.Est.cs +++ b/Penumbra/Meta/Manager/MetaManager.Est.cs @@ -30,6 +30,15 @@ public partial class MetaManager SetFile( HeadFile, CharacterUtility.HeadEstIdx ); } + [Conditional( "USE_EST" )] + public static void ResetFiles() + { + SetFile( null, CharacterUtility.FaceEstIdx ); + SetFile( null, CharacterUtility.HairEstIdx ); + SetFile( null, CharacterUtility.BodyEstIdx ); + SetFile( null, CharacterUtility.HeadEstIdx ); + } + [Conditional( "USE_EST" )] public void Reset() { diff --git a/Penumbra/Meta/Manager/MetaManager.Gmp.cs b/Penumbra/Meta/Manager/MetaManager.Gmp.cs index 8daf8dd8..d281bb37 100644 --- a/Penumbra/Meta/Manager/MetaManager.Gmp.cs +++ b/Penumbra/Meta/Manager/MetaManager.Gmp.cs @@ -18,6 +18,15 @@ public partial class MetaManager public MetaManagerGmp() { } + + [Conditional( "USE_GMP" )] + public void SetFiles() + => SetFile( File, CharacterUtility.GmpIdx ); + + [Conditional( "USE_GMP" )] + public static void ResetFiles() + => SetFile( null, CharacterUtility.GmpIdx ); + [Conditional( "USE_GMP" )] public void Reset() { @@ -28,10 +37,6 @@ public partial class MetaManager } } - [Conditional( "USE_GMP" )] - public void SetFiles() - => SetFile( File, CharacterUtility.GmpIdx ); - public bool ApplyMod( GmpManipulation m, Mod.Mod mod ) { #if USE_GMP diff --git a/Penumbra/Meta/Manager/MetaManager.Imc.cs b/Penumbra/Meta/Manager/MetaManager.Imc.cs index e2457668..8c4fb7c2 100644 --- a/Penumbra/Meta/Manager/MetaManager.Imc.cs +++ b/Penumbra/Meta/Manager/MetaManager.Imc.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; using System.Diagnostics; using FFXIVClientStructs.FFXIV.Client.System.Resource; using Penumbra.GameData.ByteString; -using Penumbra.Interop; +using Penumbra.Interop.Loader; using Penumbra.Interop.Structs; using Penumbra.Meta.Files; using Penumbra.Meta.Manipulations; @@ -26,6 +26,7 @@ public partial class MetaManager { _collection = collection; _previousDelegate = Penumbra.ResourceLoader.ResourceLoadCustomization; + SetupDelegate(); } [Conditional( "USE_IMC" )] @@ -93,7 +94,6 @@ public partial class MetaManager #endif } - public void Dispose() { foreach( var file in Files.Values ) diff --git a/Penumbra/Meta/Manager/MetaManager.cs b/Penumbra/Meta/Manager/MetaManager.cs index 3eef46fd..d0932ae4 100644 --- a/Penumbra/Meta/Manager/MetaManager.cs +++ b/Penumbra/Meta/Manager/MetaManager.cs @@ -1,4 +1,5 @@ using System; +using System.Runtime.CompilerServices; using Penumbra.Meta.Files; using Penumbra.Meta.Manipulations; using Penumbra.Mods; @@ -14,7 +15,8 @@ public partial class MetaManager : IDisposable public MetaManagerCmp Cmp = new(); public MetaManagerImc Imc; - private static unsafe void SetFile( MetaBaseFile? file, int index ) + [MethodImpl( MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization )] + public static unsafe void SetFile( MetaBaseFile? file, int index ) { if( file == null ) { diff --git a/Penumbra/Mods/ModCollection.cs b/Penumbra/Mods/ModCollection.cs index 90a96dd1..0408367a 100644 --- a/Penumbra/Mods/ModCollection.cs +++ b/Penumbra/Mods/ModCollection.cs @@ -1,11 +1,14 @@ using Newtonsoft.Json; using System; using System.Collections.Generic; +using System.Diagnostics; using System.IO; using System.Linq; using Dalamud.Logging; using Penumbra.GameData.ByteString; using Penumbra.GameData.Util; +using Penumbra.Interop.Structs; +using Penumbra.Meta.Manager; using Penumbra.Mod; using Penumbra.Util; @@ -252,5 +255,83 @@ public class ModCollection public FullPath? ResolveSwappedOrReplacementPath( Utf8GamePath gameResourcePath ) => Cache?.ResolveSwappedOrReplacementPath( gameResourcePath ); + + [Conditional( "USE_EQP" )] + public void SetEqpFiles() + { + if( Cache == null ) + { + MetaManager.MetaManagerEqp.ResetFiles(); + } + else + { + Cache.MetaManipulations.Eqp.SetFiles(); + } + } + + [Conditional( "USE_EQDP" )] + public void SetEqdpFiles() + { + if( Cache == null ) + { + MetaManager.MetaManagerEqdp.ResetFiles(); + } + else + { + Cache.MetaManipulations.Eqdp.SetFiles(); + } + } + + [Conditional( "USE_GMP" )] + public void SetGmpFiles() + { + if( Cache == null ) + { + MetaManager.MetaManagerGmp.ResetFiles(); + } + else + { + Cache.MetaManipulations.Gmp.SetFiles(); + } + } + + [Conditional( "USE_EST" )] + public void SetEstFiles() + { + if( Cache == null ) + { + MetaManager.MetaManagerEst.ResetFiles(); + } + else + { + Cache.MetaManipulations.Est.SetFiles(); + } + } + + [Conditional( "USE_CMP" )] + public void SetCmpFiles() + { + if( Cache == null ) + { + MetaManager.MetaManagerCmp.ResetFiles(); + } + else + { + Cache.MetaManipulations.Cmp.SetFiles(); + } + } + + public void SetFiles() + { + if( Cache == null ) + { + Penumbra.CharacterUtility.ResetAll(); + } + else + { + Cache.MetaManipulations.SetFiles(); + } + } + public static readonly ModCollection Empty = new() { Name = "" }; } \ No newline at end of file diff --git a/Penumbra/Penumbra.cs b/Penumbra/Penumbra.cs index 930c72c7..cc9cc5e2 100644 --- a/Penumbra/Penumbra.cs +++ b/Penumbra/Penumbra.cs @@ -9,12 +9,13 @@ using Lumina.Excel.GeneratedSheets; using Penumbra.Api; using Penumbra.GameData.Enums; using Penumbra.Interop; -using Penumbra.Meta.Files; using Penumbra.Mods; using Penumbra.PlayerWatch; using Penumbra.UI; using Penumbra.Util; using System.Linq; +using Penumbra.Interop.Loader; +using Penumbra.Interop.Resolver; using Penumbra.Meta.Manipulations; namespace Penumbra; @@ -44,7 +45,7 @@ public class Penumbra : IDalamudPlugin public static ResourceLoader ResourceLoader { get; set; } = null!; public ResourceLogger ResourceLogger { get; } - //public PathResolver PathResolver { get; } + public PathResolver PathResolver { get; } public SettingsInterface SettingsInterface { get; } public MusicManager MusicManager { get; } public ObjectReloader ObjectReloader { get; } @@ -75,7 +76,7 @@ public class Penumbra : IDalamudPlugin ModManager = new ModManager(); ModManager.DiscoverMods(); ObjectReloader = new ObjectReloader( ModManager, Config.WaitFrames ); - //PathResolver = new PathResolver( ResourceLoader, gameUtils ); + PathResolver = new PathResolver( ResourceLoader ); Dalamud.Commands.AddHandler( CommandName, new CommandInfo( OnCommand ) { @@ -235,7 +236,7 @@ public class Penumbra : IDalamudPlugin Dalamud.Commands.RemoveHandler( CommandName ); - //PathResolver.Dispose(); + PathResolver.Dispose(); ResourceLogger.Dispose(); ResourceLoader.Dispose(); CharacterUtility.Dispose(); diff --git a/Penumbra/Penumbra.csproj b/Penumbra/Penumbra.csproj index 13a25cc0..84c1b98d 100644 --- a/Penumbra/Penumbra.csproj +++ b/Penumbra/Penumbra.csproj @@ -79,4 +79,8 @@ Always + + + + \ No newline at end of file diff --git a/Penumbra/UI/MenuTabs/TabDebug.cs b/Penumbra/UI/MenuTabs/TabDebug.cs index b353192f..8f6fd22a 100644 --- a/Penumbra/UI/MenuTabs/TabDebug.cs +++ b/Penumbra/UI/MenuTabs/TabDebug.cs @@ -398,8 +398,10 @@ public partial class SettingsInterface 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; + 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' ) ); } @@ -454,41 +456,43 @@ public partial class SettingsInterface return; } - //if( ImGui.TreeNodeEx( "Draw Object to Object" ) ) - //{ - // using var end = ImGuiRaii.DeferredEnd( ImGui.TreePop ); - // if( ImGui.BeginTable( "###DrawObjectResolverTable", 4, ImGuiTableFlags.SizingFixedFit ) ) - // { - // end.Push( ImGui.EndTable ); - // foreach( var (ptr, idx) in _penumbra.PathResolver._drawObjectToObject ) - // { - // ImGui.TableNextColumn(); - // ImGui.Text( ptr.ToString( "X" ) ); - // ImGui.TableNextColumn(); - // ImGui.Text( idx.ToString() ); - // ImGui.TableNextColumn(); - // ImGui.Text( Dalamud.Objects[ idx ]?.Address.ToString() ?? "NULL" ); - // ImGui.TableNextColumn(); - // ImGui.Text( Dalamud.Objects[ idx ]?.Name.ToString() ?? "NULL" ); - // } - // } - //} - // - //if( ImGui.TreeNodeEx( "Path Collections" ) ) - //{ - // using var end = ImGuiRaii.DeferredEnd( ImGui.TreePop ); - // if( ImGui.BeginTable( "###PathCollectionResolverTable", 2, ImGuiTableFlags.SizingFixedFit ) ) - // { - // end.Push( ImGui.EndTable ); - // foreach( var (path, collection) in _penumbra.PathResolver._pathCollections ) - // { - // ImGui.TableNextColumn(); - // ImGuiNative.igTextUnformatted( path.Path, path.Path + path.Length ); - // ImGui.TableNextColumn(); - // ImGui.Text( collection.Name ); - // } - // } - //} + if( ImGui.TreeNodeEx( "Draw Object to Object" ) ) + { + using var end = ImGuiRaii.DeferredEnd( ImGui.TreePop ); + if( ImGui.BeginTable( "###DrawObjectResolverTable", 5, ImGuiTableFlags.SizingFixedFit ) ) + { + end.Push( ImGui.EndTable ); + foreach( var (ptr, (c, idx)) in _penumbra.PathResolver.DrawObjectToObject ) + { + ImGui.TableNextColumn(); + ImGui.Text( ptr.ToString( "X" ) ); + ImGui.TableNextColumn(); + ImGui.Text( idx.ToString() ); + ImGui.TableNextColumn(); + ImGui.Text( Dalamud.Objects[ idx ]?.Address.ToString() ?? "NULL" ); + ImGui.TableNextColumn(); + ImGui.Text( Dalamud.Objects[ idx ]?.Name.ToString() ?? "NULL" ); + ImGui.TableNextColumn(); + ImGui.Text( c.Name ); + } + } + } + + if( ImGui.TreeNodeEx( "Path Collections" ) ) + { + using var end = ImGuiRaii.DeferredEnd( ImGui.TreePop ); + if( ImGui.BeginTable( "###PathCollectionResolverTable", 2, ImGuiTableFlags.SizingFixedFit ) ) + { + end.Push( ImGui.EndTable ); + foreach( var (path, collection) in _penumbra.PathResolver.PathCollections ) + { + ImGui.TableNextColumn(); + ImGuiNative.igTextUnformatted( path.Path, path.Path + path.Length ); + ImGui.TableNextColumn(); + ImGui.Text( collection.Name ); + } + } + } } private void DrawDebugTab() diff --git a/Penumbra/UI/MenuTabs/TabResourceManager.cs b/Penumbra/UI/MenuTabs/TabResourceManager.cs index fa187a94..507d3fe7 100644 --- a/Penumbra/UI/MenuTabs/TabResourceManager.cs +++ b/Penumbra/UI/MenuTabs/TabResourceManager.cs @@ -1,14 +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.Interop.Loader; using Penumbra.UI.Custom; namespace Penumbra.UI;