Add handling for the 1.0 Decal texture.

This commit is contained in:
Ottermandias 2022-09-19 18:52:49 +02:00
parent 57e66f9b66
commit ea023ebb5c
9 changed files with 147 additions and 17 deletions

View file

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

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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