diff --git a/Penumbra/Interop/PathResolver.cs b/Penumbra/Interop/PathResolver.cs new file mode 100644 index 00000000..6c683863 --- /dev/null +++ b/Penumbra/Interop/PathResolver.cs @@ -0,0 +1,108 @@ +using System; +using System.Linq; +using System.Runtime.InteropServices; +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.Util; +using Penumbra.Mods; +using Penumbra.Util; + +namespace Penumbra.Interop; + +public unsafe class PathResolver : IDisposable +{ + public delegate IntPtr ResolveMdlPath( IntPtr drawObject, IntPtr path, IntPtr unk3, uint unk4 ); + public delegate IntPtr ResolveMtrlPath( IntPtr drawObject, IntPtr path, IntPtr unk3, uint unk4, IntPtr unk5 ); + + [Signature( "?? 89 ?? ?? ?? ?? 89 ?? ?? ?? ?? 89 ?? ?? ?? ?? 89 ?? ?? ?? 41 ?? 48 83 ?? ?? 45 8B ?? 49 8B ?? 48 8B ?? 48 8B ?? 41" )] + public Hook< ResolveMdlPath >? ResolveMdlPathHook; + + [Signature( "?? 89 ?? ?? ?? ?? 89 ?? ?? ?? ?? 89 ?? ?? ?? 57 48 83 ?? ?? 49 8B ?? 48 8B ?? 48 8B ?? 41 83 ?? ?? 0F" )] + public Hook? ResolveMtrlPathHook; + + private global::Dalamud.Game.ClientState.Objects.Types.GameObject? FindParent( IntPtr drawObject ) + => Dalamud.Objects.FirstOrDefault( a => ( ( GameObject* )a.Address )->DrawObject == ( DrawObject* )drawObject ); + + private readonly byte[] _data = new byte[512]; + + private unsafe IntPtr ResolveMdlPathDetour( IntPtr drawObject, IntPtr path, IntPtr unk3, uint unk4 ) + { + var ret = ResolveMdlPathHook!.Original( drawObject, path, unk3, unk4 ); + var n = Marshal.PtrToStringAnsi( ret )!; + var name = FindParent( drawObject )?.Name.ToString() ?? string.Empty; + PluginLog.Information( $"{drawObject:X} {path:X} {unk3:X} {unk4}\n{n}\n{name}" ); + if( Service< ModManager >.Get().Collections.CharacterCollection.TryGetValue( name, out var collection ) ) + { + var replacement = collection.ResolveSwappedOrReplacementPath( GamePath.GenerateUncheckedLower( n ) ); + if( replacement != null ) + { + for( var i = 0; i < replacement.Length; ++i ) + { + _data[ i ] = ( byte )replacement[ i ]; + } + + _data[ replacement.Length ] = 0; + fixed( byte* data = _data ) + { + return ( IntPtr )data; + } + } + } + + return ret; + } + + private unsafe IntPtr ResolveMtrlPathDetour( IntPtr drawObject, IntPtr path, IntPtr unk3, uint unk4, IntPtr unk5 ) + { + var ret = ResolveMtrlPathHook!.Original( drawObject, path, unk3, unk4, unk5 ); + var n = Marshal.PtrToStringAnsi( ret )!; + var name = FindParent( drawObject )?.Name.ToString() ?? string.Empty; + PluginLog.Information( $"{drawObject:X} {path:X} {unk3:X} {unk4} {unk5:X}\n{n}\n{name}" ); + if( Service.Get().Collections.CharacterCollection.TryGetValue( name, out var collection ) ) + { + var replacement = collection.ResolveSwappedOrReplacementPath( GamePath.GenerateUncheckedLower( n ) ); + if( replacement != null ) + { + for( var i = 0; i < replacement.Length; ++i ) + { + _data[i] = ( byte )replacement[i]; + } + + _data[replacement.Length] = 0; + fixed( byte* data = _data ) + { + return ( IntPtr )data; + } + } + } + + return ret; + } + + public PathResolver() + { + SignatureHelper.Initialise( this ); + Enable(); + } + + public void Enable() + { + ResolveMdlPathHook?.Enable(); + ResolveMtrlPathHook?.Enable(); + } + + public void Disable() + { + ResolveMdlPathHook?.Disable(); + ResolveMtrlPathHook?.Disable(); + } + + public void Dispose() + { + ResolveMdlPathHook?.Dispose(); + ResolveMtrlPathHook?.Dispose(); + } +} \ No newline at end of file diff --git a/Penumbra/Interop/ResourceLoader.cs b/Penumbra/Interop/ResourceLoader.cs index fbb05077..6f8fbb49 100644 --- a/Penumbra/Interop/ResourceLoader.cs +++ b/Penumbra/Interop/ResourceLoader.cs @@ -129,7 +129,7 @@ public class ResourceLoader : IDisposable private IntPtr CheckFileStateDetour( IntPtr ptr, ulong crc64 ) { var modManager = Service< ModManager >.Get(); - return modManager.CheckCrc64( crc64 ) ? CustomFileFlag : CheckFileStateHook!.Original( ptr, crc64 ); + return true || modManager.CheckCrc64( crc64 ) ? CustomFileFlag : CheckFileStateHook!.Original( ptr, crc64 ); } private byte LoadTexFileExternDetour( IntPtr resourceHandle, int unk1, IntPtr unk2, bool unk3, IntPtr ptr ) @@ -282,8 +282,8 @@ public class ResourceLoader : IDisposable Marshal.Copy( utfPath, 0, new IntPtr( fd + 0x21 ), utfPath.Length ); pFileDesc->FileDescriptor = fd; - - return ReadFile( pFileHandler, pFileDesc, priority, isSync ); + var ret = ReadFile( pFileHandler, pFileDesc, priority, isSync ); + return ret; } public void Enable() @@ -311,7 +311,7 @@ public class ResourceLoader : IDisposable LoadTexFileExternHook.Enable(); LoadMdlFileExternHook.Enable(); - IsEnabled = true; + IsEnabled = true; } public void Disable() @@ -327,7 +327,7 @@ public class ResourceLoader : IDisposable CheckFileStateHook?.Disable(); LoadTexFileExternHook?.Disable(); LoadMdlFileExternHook?.Disable(); - IsEnabled = false; + IsEnabled = false; } public void Dispose() diff --git a/Penumbra/Penumbra.cs b/Penumbra/Penumbra.cs index 683ad296..14329de8 100644 --- a/Penumbra/Penumbra.cs +++ b/Penumbra/Penumbra.cs @@ -29,6 +29,7 @@ public class Penumbra : IDalamudPlugin public static IPlayerWatcher PlayerWatcher { get; private set; } = null!; public ResourceLoader ResourceLoader { get; } + public PathResolver PathResolver { get; } public SettingsInterface SettingsInterface { get; } public MusicManager MusicManager { get; } public ObjectReloader ObjectReloader { get; } @@ -53,6 +54,7 @@ public class Penumbra : IDalamudPlugin } var gameUtils = Service< ResidentResources >.Set(); + PathResolver = new PathResolver(); PlayerWatcher = PlayerWatchFactory.Create( Dalamud.Framework, Dalamud.ClientState, Dalamud.Objects ); Service< MetaDefaults >.Set(); _modManager = Service< ModManager >.Set(); @@ -190,6 +192,7 @@ public class Penumbra : IDalamudPlugin Dalamud.Commands.RemoveHandler( CommandName ); + PathResolver.Dispose(); ResourceLoader.Dispose(); ShutdownWebServer();