mirror of
https://github.com/xivdev/Penumbra.git
synced 2025-12-12 10:17:22 +01:00
Add handling for the 1.0 Decal texture.
This commit is contained in:
parent
57e66f9b66
commit
ea023ebb5c
9 changed files with 147 additions and 17 deletions
|
|
@ -31,6 +31,13 @@ public readonly struct FullPath : IComparable, IEquatable< FullPath >
|
|||
Crc64 = Functions.ComputeCrc64( InternalName.Span );
|
||||
}
|
||||
|
||||
public FullPath( Utf8GamePath path )
|
||||
{
|
||||
FullName = path.ToString().Replace( '/', '\\' );
|
||||
InternalName = path.Path;
|
||||
Crc64 = Functions.ComputeCrc64( InternalName.Span );
|
||||
}
|
||||
|
||||
public bool Exists
|
||||
=> File.Exists( FullName );
|
||||
|
||||
|
|
|
|||
65
Penumbra/Interop/CharacterUtility.DecalReverter.cs
Normal file
65
Penumbra/Interop/CharacterUtility.DecalReverter.cs
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
using System;
|
||||
using FFXIVClientStructs.FFXIV.Client.System.Resource;
|
||||
using Penumbra.Collections;
|
||||
using Penumbra.GameData.ByteString;
|
||||
using Penumbra.GameData.Enums;
|
||||
|
||||
namespace Penumbra.Interop;
|
||||
|
||||
public unsafe partial class CharacterUtility
|
||||
{
|
||||
public sealed class DecalReverter : IDisposable
|
||||
{
|
||||
public static readonly Utf8GamePath DecalPath =
|
||||
Utf8GamePath.FromString( "chara/common/texture/decal_equip/_stigma.tex", out var p ) ? p : Utf8GamePath.Empty;
|
||||
|
||||
public static readonly Utf8GamePath TransparentPath =
|
||||
Utf8GamePath.FromString( "chara/common/texture/transparent.tex", out var p ) ? p : Utf8GamePath.Empty;
|
||||
|
||||
private readonly Structs.TextureResourceHandle* _decal;
|
||||
private readonly Structs.TextureResourceHandle* _transparent;
|
||||
|
||||
public DecalReverter( ModCollection? collection, bool doDecal )
|
||||
{
|
||||
var ptr = Penumbra.CharacterUtility.Address;
|
||||
_decal = null;
|
||||
_transparent = null;
|
||||
if( doDecal )
|
||||
{
|
||||
var decalPath = collection?.ResolvePath( DecalPath )?.InternalName ?? DecalPath.Path;
|
||||
var decalHandle = Penumbra.ResourceLoader.ResolvePathSync( ResourceCategory.Chara, ResourceType.Tex, decalPath );
|
||||
_decal = ( Structs.TextureResourceHandle* )decalHandle;
|
||||
if( _decal != null )
|
||||
{
|
||||
ptr->DecalTexResource = _decal;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var transparentPath = collection?.ResolvePath( TransparentPath )?.InternalName ?? TransparentPath.Path;
|
||||
var transparentHandle = Penumbra.ResourceLoader.ResolvePathSync( ResourceCategory.Chara, ResourceType.Tex, transparentPath );
|
||||
_transparent = ( Structs.TextureResourceHandle* )transparentHandle;
|
||||
if( _transparent != null )
|
||||
{
|
||||
ptr->TransparentTexResource = _transparent;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
var ptr = Penumbra.CharacterUtility.Address;
|
||||
if( _decal != null )
|
||||
{
|
||||
ptr->DecalTexResource = ( Structs.TextureResourceHandle* )Penumbra.CharacterUtility._defaultDecalResource;
|
||||
--_decal->Handle.RefCount;
|
||||
}
|
||||
|
||||
if( _transparent != null )
|
||||
{
|
||||
ptr->TransparentTexResource = ( Structs.TextureResourceHandle* )Penumbra.CharacterUtility._defaultTransparentResource;
|
||||
--_transparent->Handle.RefCount;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -26,6 +26,8 @@ public unsafe partial class CharacterUtility : IDisposable
|
|||
|
||||
public bool Ready { get; private set; }
|
||||
public event Action LoadingFinished;
|
||||
private IntPtr _defaultTransparentResource;
|
||||
private IntPtr _defaultDecalResource;
|
||||
|
||||
// The relevant indices depend on which meta manipulations we allow for.
|
||||
// The defines are set in the project configuration.
|
||||
|
|
@ -76,6 +78,18 @@ public unsafe partial class CharacterUtility : IDisposable
|
|||
}
|
||||
}
|
||||
|
||||
if( _defaultTransparentResource == IntPtr.Zero )
|
||||
{
|
||||
_defaultTransparentResource = ( IntPtr )Address->TransparentTexResource;
|
||||
anyMissing |= _defaultTransparentResource == IntPtr.Zero;
|
||||
}
|
||||
|
||||
if( _defaultDecalResource == IntPtr.Zero )
|
||||
{
|
||||
_defaultDecalResource = ( IntPtr )Address->DecalTexResource;
|
||||
anyMissing |= _defaultDecalResource == IntPtr.Zero;
|
||||
}
|
||||
|
||||
if( !anyMissing )
|
||||
{
|
||||
Ready = true;
|
||||
|
|
@ -86,15 +100,15 @@ public unsafe partial class CharacterUtility : IDisposable
|
|||
|
||||
public void SetResource( Structs.CharacterUtility.Index resourceIdx, IntPtr data, int length )
|
||||
{
|
||||
var idx = ReverseIndices[( int )resourceIdx];
|
||||
var list = _lists[idx.Value];
|
||||
var idx = ReverseIndices[ ( int )resourceIdx ];
|
||||
var list = _lists[ idx.Value ];
|
||||
list.SetResource( data, length );
|
||||
}
|
||||
|
||||
public void ResetResource( Structs.CharacterUtility.Index resourceIdx )
|
||||
{
|
||||
var idx = ReverseIndices[( int )resourceIdx];
|
||||
var list = _lists[idx.Value];
|
||||
var idx = ReverseIndices[ ( int )resourceIdx ];
|
||||
var list = _lists[ idx.Value ];
|
||||
list.ResetResource();
|
||||
}
|
||||
|
||||
|
|
@ -119,6 +133,9 @@ public unsafe partial class CharacterUtility : IDisposable
|
|||
{
|
||||
list.Dispose();
|
||||
}
|
||||
|
||||
Address->TransparentTexResource = ( Structs.TextureResourceHandle* )_defaultTransparentResource;
|
||||
Address->DecalTexResource = ( Structs.TextureResourceHandle* )_defaultDecalResource;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
|
|
|
|||
|
|
@ -71,7 +71,13 @@ public unsafe partial class ResourceLoader
|
|||
|
||||
private event Action< Utf8GamePath, ResourceType, FullPath?, object? >? PathResolved;
|
||||
|
||||
private ResourceHandle* GetResourceHandler( bool isSync, ResourceManager* resourceManager, ResourceCategory* categoryId,
|
||||
public ResourceHandle* ResolvePathSync( ResourceCategory category, ResourceType type, Utf8String path )
|
||||
{
|
||||
var hash = path.Crc32;
|
||||
return GetResourceHandler( true, *ResourceManager, &category, &type, &hash, path.Path, null, false );
|
||||
}
|
||||
|
||||
internal ResourceHandle* GetResourceHandler( bool isSync, ResourceManager* resourceManager, ResourceCategory* categoryId,
|
||||
ResourceType* resourceType, int* resourceHash, byte* path, GetResourceParameters* pGetResParams, bool isUnk )
|
||||
{
|
||||
if( !Utf8GamePath.FromPointer( path, out var gamePath ) )
|
||||
|
|
@ -86,7 +92,7 @@ public unsafe partial class ResourceLoader
|
|||
|
||||
// If no replacements are being made, we still want to be able to trigger the event.
|
||||
var (resolvedPath, data) = ResolvePath( gamePath, *categoryId, *resourceType, *resourceHash );
|
||||
PathResolved?.Invoke( gamePath, *resourceType, resolvedPath, data );
|
||||
PathResolved?.Invoke( gamePath, *resourceType, resolvedPath ?? ( gamePath.IsRooted() ? new FullPath( gamePath ) : null ), data );
|
||||
if( resolvedPath == null )
|
||||
{
|
||||
var retUnmodified =
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ public unsafe partial class ResourceLoader
|
|||
// We use it to check against our stored CRC64s and if it corresponds, we return the custom flag.
|
||||
public delegate IntPtr CheckFileStatePrototype( IntPtr unk1, ulong crc64 );
|
||||
|
||||
[Signature( "E8 ?? ?? ?? ?? 48 85 c0 74 ?? 45 0f b6 ce 48 89 44 24", DetourName = "CheckFileStateDetour" )]
|
||||
[Signature( "E8 ?? ?? ?? ?? 48 85 c0 74 ?? 45 0f b6 ce 48 89 44 24", DetourName = nameof(CheckFileStateDetour) )]
|
||||
public Hook< CheckFileStatePrototype > CheckFileStateHook = null!;
|
||||
|
||||
private IntPtr CheckFileStateDetour( IntPtr ptr, ulong crc64 )
|
||||
|
|
@ -48,7 +48,7 @@ public unsafe partial class ResourceLoader
|
|||
// We hook the extern functions to just return the local one if given the custom flag as last argument.
|
||||
public delegate byte LoadTexFileExternPrototype( ResourceHandle* handle, int unk1, IntPtr unk2, bool unk3, IntPtr unk4 );
|
||||
|
||||
[Signature( "E8 ?? ?? ?? ?? 0F B6 E8 48 8B CB E8", DetourName = "LoadTexFileExternDetour" )]
|
||||
[Signature( "E8 ?? ?? ?? ?? 0F B6 E8 48 8B CB E8", DetourName = nameof(LoadTexFileExternDetour) )]
|
||||
public Hook< LoadTexFileExternPrototype > LoadTexFileExternHook = null!;
|
||||
|
||||
private byte LoadTexFileExternDetour( ResourceHandle* resourceHandle, int unk1, IntPtr unk2, bool unk3, IntPtr ptr )
|
||||
|
|
@ -59,7 +59,7 @@ public unsafe partial class ResourceLoader
|
|||
public delegate byte LoadMdlFileExternPrototype( ResourceHandle* handle, IntPtr unk1, bool unk2, IntPtr unk3 );
|
||||
|
||||
|
||||
[Signature( "E8 ?? ?? ?? ?? EB 02 B0 F1", DetourName = "LoadMdlFileExternDetour" )]
|
||||
[Signature( "E8 ?? ?? ?? ?? EB 02 B0 F1", DetourName = nameof(LoadMdlFileExternDetour) )]
|
||||
public Hook< LoadMdlFileExternPrototype > LoadMdlFileExternHook = null!;
|
||||
|
||||
private byte LoadMdlFileExternDetour( ResourceHandle* resourceHandle, IntPtr unk1, bool unk2, IntPtr ptr )
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ using System.Linq;
|
|||
using FFXIVClientStructs.FFXIV.Client.Game.Object;
|
||||
using Penumbra.Api;
|
||||
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
|
||||
using OtterGui.Classes;
|
||||
using Penumbra.GameData.ByteString;
|
||||
using Penumbra.GameData.Enums;
|
||||
|
||||
|
|
@ -135,18 +136,19 @@ public unsafe partial class PathResolver
|
|||
|
||||
private IntPtr CharacterBaseCreateDetour( uint a, IntPtr b, IntPtr c, byte d )
|
||||
{
|
||||
CharacterUtility.List.MetaReverter? cmp = null;
|
||||
var meta = DisposableContainer.Empty;
|
||||
if( LastGameObject != null )
|
||||
{
|
||||
_lastCreatedCollection = IdentifyCollection( LastGameObject );
|
||||
var modelPtr = &a;
|
||||
if( _lastCreatedCollection.ModCollection != Penumbra.CollectionManager.Default )
|
||||
{
|
||||
cmp = _lastCreatedCollection.ModCollection.TemporarilySetCmpFile();
|
||||
}
|
||||
|
||||
// Change the transparent or 1.0 Decal if necessary.
|
||||
var decal = new CharacterUtility.DecalReverter( _lastCreatedCollection.ModCollection, UsesDecal( a, c ) );
|
||||
// Change the rsp parameters if necessary.
|
||||
meta = new DisposableContainer( _lastCreatedCollection.ModCollection != Penumbra.CollectionManager.Default
|
||||
? _lastCreatedCollection.ModCollection.TemporarilySetCmpFile()
|
||||
: null, decal );
|
||||
try
|
||||
{
|
||||
var modelPtr = &a;
|
||||
CreatingCharacterBase?.Invoke( ( IntPtr )LastGameObject, _lastCreatedCollection!.ModCollection, ( IntPtr )modelPtr, b, c );
|
||||
}
|
||||
catch( Exception e )
|
||||
|
|
@ -156,7 +158,7 @@ public unsafe partial class PathResolver
|
|||
}
|
||||
|
||||
var ret = _characterBaseCreateHook.Original( a, b, c, d );
|
||||
using( cmp )
|
||||
using( meta )
|
||||
{
|
||||
if( LastGameObject != null )
|
||||
{
|
||||
|
|
@ -168,6 +170,11 @@ public unsafe partial class PathResolver
|
|||
}
|
||||
}
|
||||
|
||||
// Check the customize array for the FaceCustomization byte and the last bit of that.
|
||||
// Also check for humans.
|
||||
public static bool UsesDecal( uint modelId, IntPtr customizeData )
|
||||
=> modelId == 0 && ( ( byte* )customizeData )[ 12 ] > 0x7F;
|
||||
|
||||
|
||||
// Remove DrawObjects from the list when they are destroyed.
|
||||
private delegate void CharacterBaseDestructorDelegate( IntPtr drawBase );
|
||||
|
|
|
|||
|
|
@ -201,6 +201,9 @@ public unsafe partial class PathResolver
|
|||
_inChangeCustomize = true;
|
||||
var resolveData = GetResolveData( human );
|
||||
using var eqp = resolveData.Valid ? resolveData.ModCollection.TemporarilySetEqpFile() : null;
|
||||
using var decals = resolveData.Valid
|
||||
? new CharacterUtility.DecalReverter( resolveData.ModCollection, DrawObjectState.UsesDecal(0, data) )
|
||||
: null;
|
||||
return _changeCustomize.Original( human, data, skipEquipment );
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -80,6 +80,9 @@ public unsafe struct CharacterUtility
|
|||
BodyEst,
|
||||
}
|
||||
|
||||
public const int IndexTransparentTex = 72;
|
||||
public const int IndexDecalTex = 73;
|
||||
|
||||
public static readonly Index[] EqdpIndices = Enum.GetNames< Index >()
|
||||
.Zip( Enum.GetValues< Index >() )
|
||||
.Where( n => n.First.StartsWith( "Eqdp" ) )
|
||||
|
|
@ -157,5 +160,11 @@ public unsafe struct CharacterUtility
|
|||
[FieldOffset( 8 + ( int )Index.HeadEst * 8 )]
|
||||
public ResourceHandle* HeadEstResource;
|
||||
|
||||
[FieldOffset( 8 + IndexTransparentTex * 8 )]
|
||||
public TextureResourceHandle* TransparentTexResource;
|
||||
|
||||
[FieldOffset( 8 + IndexDecalTex * 8 )]
|
||||
public TextureResourceHandle* DecalTexResource;
|
||||
|
||||
// not included resources have no known use case.
|
||||
}
|
||||
|
|
@ -5,6 +5,22 @@ using Penumbra.GameData.Enums;
|
|||
|
||||
namespace Penumbra.Interop.Structs;
|
||||
|
||||
[StructLayout( LayoutKind.Explicit )]
|
||||
public unsafe struct TextureResourceHandle
|
||||
{
|
||||
[FieldOffset( 0x0 )]
|
||||
public ResourceHandle Handle;
|
||||
|
||||
[FieldOffset( 0x38 )]
|
||||
public IntPtr Unk;
|
||||
|
||||
[FieldOffset( 0x118 )]
|
||||
public IntPtr KernelTexture;
|
||||
|
||||
[FieldOffset( 0x20 )]
|
||||
public IntPtr NewKernelTexture;
|
||||
}
|
||||
|
||||
[StructLayout( LayoutKind.Explicit )]
|
||||
public unsafe struct ResourceHandle
|
||||
{
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue