Add GameEventManager, change cutscene character and subfile container resets.

This commit is contained in:
Ottermandias 2023-01-22 15:16:53 +01:00
parent 7ab1426a2c
commit 24fda725a2
7 changed files with 183 additions and 107 deletions

View file

@ -3,7 +3,6 @@ namespace Penumbra.GameData;
public static class Sigs
{
// ResourceLoader.Debug
public const string ResourceHandleDestructor = "48 89 5C 24 ?? 57 48 83 EC ?? 48 8D 05 ?? ?? ?? ?? 48 8B D9 48 89 01 B8";
public const string ResourceManager = "48 8B 05 ?? ?? ?? ?? 33 ED F0";
// ResourceLoader.Replacement
@ -19,10 +18,10 @@ public static class Sigs
public const string LoadTexFileExtern = "E8 ?? ?? ?? ?? 0F B6 E8 48 8B CB E8";
public const string LoadMdlFileExtern = "E8 ?? ?? ?? ?? EB 02 B0 F1";
// CutsceneCharacters
// GameEventManager
public const string ResourceHandleDestructor = "48 89 5C 24 ?? 57 48 83 EC ?? 48 8D 05 ?? ?? ?? ?? 48 8B D9 48 89 01 B8";
public const string CopyCharacter = "E8 ?? ?? ?? ?? 0F B6 9F ?? ?? ?? ?? 48 8D 8F";
// IdentifiedCollectionCache
public const string CharacterDestructor =
"48 89 5C 24 ?? 48 89 74 24 ?? 57 48 83 EC ?? 48 8D 05 ?? ?? ?? ?? 48 8B D9 48 89 01 48 8D 05 ?? ?? ?? ?? 48 89 81 ?? ?? ?? ?? 48 8D 05";

View file

@ -0,0 +1,126 @@
using Dalamud.Hooking;
using Dalamud.Utility.Signatures;
using Penumbra.GameData;
using System;
using FFXIVClientStructs.FFXIV.Client.Game.Character;
using FFXIVClientStructs.FFXIV.Client.Game.Object;
using Penumbra.Interop.Structs;
namespace Penumbra.Interop;
public unsafe class GameEventManager : IDisposable
{
public GameEventManager()
{
SignatureHelper.Initialise( this );
_characterDtorHook.Enable();
_copyCharacterHook.Enable();
_resourceHandleDestructorHook.Enable();
}
public void Dispose()
{
_characterDtorHook.Dispose();
_copyCharacterHook.Dispose();
_resourceHandleDestructorHook.Dispose();
}
#region Character Destructor
private delegate void CharacterDestructorDelegate( Character* character );
[Signature( Sigs.CharacterDestructor, DetourName = nameof( CharacterDestructorDetour ) )]
private readonly Hook< CharacterDestructorDelegate > _characterDtorHook = null!;
private void CharacterDestructorDetour( Character* character )
{
if( CharacterDestructor != null )
{
foreach( var subscriber in CharacterDestructor.GetInvocationList() )
{
try
{
( ( CharacterDestructorEvent )subscriber ).Invoke( character );
}
catch( Exception ex )
{
Penumbra.Log.Error( $"Error in {nameof( CharacterDestructor )} event when executing {subscriber.Method.Name}:\n{ex}" );
}
}
}
Penumbra.Log.Verbose( $"{nameof( CharacterDestructor )} triggered with 0x{( nint )character:X}." );
_characterDtorHook.Original( character );
}
public delegate void CharacterDestructorEvent( Character* character );
public event CharacterDestructorEvent? CharacterDestructor;
#endregion
#region Copy Character
private unsafe delegate ulong CopyCharacterDelegate( GameObject* target, GameObject* source, uint unk );
[Signature( Sigs.CopyCharacter, DetourName = nameof( CopyCharacterDetour ) )]
private readonly Hook< CopyCharacterDelegate > _copyCharacterHook = null!;
private ulong CopyCharacterDetour( GameObject* target, GameObject* source, uint unk )
{
if( CopyCharacter != null )
{
foreach( var subscriber in CopyCharacter.GetInvocationList() )
{
try
{
( ( CopyCharacterEvent )subscriber ).Invoke( ( Character* )target, ( Character* )source );
}
catch( Exception ex )
{
Penumbra.Log.Error( $"Error in {nameof( CopyCharacter )} event when executing {subscriber.Method.Name}:\n{ex}" );
}
}
}
Penumbra.Log.Verbose( $"{nameof( CopyCharacter )} triggered with target 0x{( nint )target:X} and source 0x{( nint )source:X}." );
return _copyCharacterHook.Original( target, source, unk );
}
public delegate void CopyCharacterEvent( Character* target, Character* source );
public event CopyCharacterEvent? CopyCharacter;
#endregion
#region ResourceHandle Destructor
private delegate IntPtr ResourceHandleDestructorDelegate( ResourceHandle* handle );
[Signature( Sigs.ResourceHandleDestructor, DetourName = nameof( ResourceHandleDestructorDetour ) )]
private readonly Hook< ResourceHandleDestructorDelegate > _resourceHandleDestructorHook = null!;
private IntPtr ResourceHandleDestructorDetour( ResourceHandle* handle )
{
if( ResourceHandleDestructor != null )
{
foreach( var subscriber in ResourceHandleDestructor.GetInvocationList() )
{
try
{
( ( ResourceHandleDestructorEvent )subscriber ).Invoke( handle );
}
catch( Exception ex )
{
Penumbra.Log.Error( $"Error in {nameof( ResourceHandleDestructor )} event when executing {subscriber.Method.Name}:\n{ex}" );
}
}
}
Penumbra.Log.Verbose( $"{nameof( ResourceHandleDestructor )} triggered with 0x{( nint )handle:X}." );
return _resourceHandleDestructorHook!.Original( handle );
}
public delegate void ResourceHandleDestructorEvent( ResourceHandle* handle );
public event ResourceHandleDestructorEvent? ResourceHandleDestructor;
#endregion
}

View file

@ -2,11 +2,7 @@ using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using Dalamud.Game.ClientState.Conditions;
using Dalamud.Hooking;
using Dalamud.Utility.Signatures;
using FFXIVClientStructs.FFXIV.Client.Game.Object;
using Penumbra.GameData;
using FFXIVClientStructs.FFXIV.Client.Game.Character;
namespace Penumbra.Interop.Resolver;
@ -16,6 +12,7 @@ public class CutsceneCharacters : IDisposable
public const int CutsceneSlots = 40;
public const int CutsceneEndIdx = CutsceneStartIdx + CutsceneSlots;
private readonly GameEventManager _events;
private readonly short[] _copiedCharacters = Enumerable.Repeat( ( short )-1, CutsceneSlots ).ToArray();
public IEnumerable< KeyValuePair< int, global::Dalamud.Game.ClientState.Objects.Types.GameObject > > Actors
@ -23,10 +20,10 @@ public class CutsceneCharacters : IDisposable
.Where( i => Dalamud.Objects[ i ] != null )
.Select( i => KeyValuePair.Create( i, this[ i ] ?? Dalamud.Objects[ i ]! ) );
public CutsceneCharacters()
public CutsceneCharacters(GameEventManager events)
{
SignatureHelper.Initialise( this );
Dalamud.Conditions.ConditionChange += Reset;
_events = events;
Enable();
}
// Get the related actor to a cutscene actor.
@ -53,71 +50,36 @@ public class CutsceneCharacters : IDisposable
return -1;
}
public void Reset( ConditionFlag flag, bool value )
public unsafe void Enable()
{
switch( flag )
{
case ConditionFlag.BetweenAreas:
case ConditionFlag.BetweenAreas51:
if( !value )
{
return;
_events.CopyCharacter += OnCharacterCopy;
_events.CharacterDestructor += OnCharacterDestructor;
}
break;
case ConditionFlag.OccupiedInCutSceneEvent:
case ConditionFlag.WatchingCutscene:
case ConditionFlag.WatchingCutscene78:
if( value )
public unsafe void Disable()
{
return;
_events.CopyCharacter -= OnCharacterCopy;
_events.CharacterDestructor -= OnCharacterDestructor;
}
break;
default: return;
}
for( var i = 0; i < _copiedCharacters.Length; ++i )
{
_copiedCharacters[ i ] = -1;
}
}
public void Enable()
=> _copyCharacterHook.Enable();
public void Disable()
=> _copyCharacterHook.Disable();
public void Dispose()
=> Disable();
private unsafe void OnCharacterDestructor( Character* character )
{
_copyCharacterHook.Dispose();
Dalamud.Conditions.ConditionChange -= Reset;
if( character->GameObject.ObjectIndex is >= CutsceneStartIdx and < CutsceneEndIdx )
{
var idx = character->GameObject.ObjectIndex - CutsceneStartIdx;
_copiedCharacters[ idx ] = -1;
}
}
private unsafe delegate ulong CopyCharacterDelegate( GameObject* target, GameObject* source, uint unk );
[Signature( Sigs.CopyCharacter, DetourName = nameof( CopyCharacterDetour ) )]
private readonly Hook< CopyCharacterDelegate > _copyCharacterHook = null!;
private unsafe ulong CopyCharacterDetour( GameObject* target, GameObject* source, uint unk )
private unsafe void OnCharacterCopy( Character* target, Character* source )
{
try
if( target != null && target->GameObject.ObjectIndex is >= CutsceneStartIdx and < CutsceneEndIdx )
{
if( target != null && target->ObjectIndex is >= CutsceneStartIdx and < CutsceneEndIdx )
{
var parent = source == null || source->ObjectIndex is < 0 or >= CutsceneStartIdx
? -1
: source->ObjectIndex;
_copiedCharacters[ target->ObjectIndex - CutsceneStartIdx ] = ( short )parent;
Penumbra.Log.Debug( $"Set cutscene character {target->ObjectIndex} to {parent}." );
var idx = target->GameObject.ObjectIndex - CutsceneStartIdx;
_copiedCharacters[idx] = (short) (source != null ? source->GameObject.ObjectIndex : -1);
}
}
catch
{
// ignored
}
return _copyCharacterHook.Original( target, source, unk );
}
}

View file

@ -1,25 +1,23 @@
using System;
using System.Collections;
using System.Collections.Generic;
using Dalamud.Hooking;
using Dalamud.Utility.Signatures;
using FFXIVClientStructs.FFXIV.Client.Game.Character;
using FFXIVClientStructs.FFXIV.Client.Game.Object;
using Penumbra.Collections;
using Penumbra.GameData;
using Penumbra.GameData.Actors;
namespace Penumbra.Interop.Resolver;
public unsafe class IdentifiedCollectionCache : IDisposable, IEnumerable< (IntPtr Address, ActorIdentifier Identifier, ModCollection Collection) >
{
private readonly GameEventManager _events;
private readonly Dictionary< IntPtr, (ActorIdentifier, ModCollection) > _cache = new(317);
private bool _dirty = false;
private bool _enabled = false;
public IdentifiedCollectionCache()
public IdentifiedCollectionCache(GameEventManager events)
{
SignatureHelper.Initialise( this );
_events = events;
}
public void Enable()
@ -32,7 +30,7 @@ public unsafe class IdentifiedCollectionCache : IDisposable, IEnumerable< (IntPt
Penumbra.CollectionManager.CollectionChanged += CollectionChangeClear;
Penumbra.TempMods.CollectionChanged += CollectionChangeClear;
Dalamud.ClientState.TerritoryChanged += TerritoryClear;
_characterDtorHook.Enable();
_events.CharacterDestructor += OnCharacterDestruct;
_enabled = true;
}
@ -46,7 +44,7 @@ public unsafe class IdentifiedCollectionCache : IDisposable, IEnumerable< (IntPt
Penumbra.CollectionManager.CollectionChanged -= CollectionChangeClear;
Penumbra.TempMods.CollectionChanged -= CollectionChangeClear;
Dalamud.ClientState.TerritoryChanged -= TerritoryClear;
_characterDtorHook.Disable();
_events.CharacterDestructor -= OnCharacterDestruct;
_enabled = false;
}
@ -82,7 +80,6 @@ public unsafe class IdentifiedCollectionCache : IDisposable, IEnumerable< (IntPt
public void Dispose()
{
Disable();
_characterDtorHook.Dispose();
GC.SuppressFinalize( this );
}
@ -116,14 +113,6 @@ public unsafe class IdentifiedCollectionCache : IDisposable, IEnumerable< (IntPt
private void TerritoryClear( object? _1, ushort _2 )
=> _dirty = _cache.Count > 0;
private delegate void CharacterDestructorDelegate( Character* character );
[Signature( Sigs.CharacterDestructor, DetourName = nameof( CharacterDestructorDetour ) )]
private Hook< CharacterDestructorDelegate > _characterDtorHook = null!;
private void CharacterDestructorDetour( Character* character )
{
_cache.Remove( ( IntPtr )character );
_characterDtorHook.Original( character );
}
private void OnCharacterDestruct( Character* character )
=> _cache.Remove( ( IntPtr )character );
}

View file

@ -25,17 +25,19 @@ public unsafe partial class PathResolver
public class SubfileHelper : IDisposable, IReadOnlyCollection< KeyValuePair< IntPtr, ResolveData > >
{
private readonly ResourceLoader _loader;
private readonly GameEventManager _events;
private readonly ThreadLocal< ResolveData > _mtrlData = new(() => ResolveData.Invalid);
private readonly ThreadLocal< ResolveData > _avfxData = new(() => ResolveData.Invalid);
private readonly ConcurrentDictionary< IntPtr, ResolveData > _subFileCollection = new();
public SubfileHelper( ResourceLoader loader )
public SubfileHelper( ResourceLoader loader, GameEventManager events )
{
SignatureHelper.Initialise( this );
_loader = loader;
_events = events;
}
// Check specifically for shpk and tex files whether we are currently in a material load.
@ -85,7 +87,7 @@ public unsafe partial class PathResolver
_apricotResourceLoadHook.Enable();
_loader.ResourceLoadCustomization += SubfileLoadHandler;
_loader.ResourceLoaded += SubfileContainerRequested;
_loader.FileLoaded += SubfileContainerLoaded;
_events.ResourceHandleDestructor += ResourceDestroyed;
}
public void Disable()
@ -95,7 +97,7 @@ public unsafe partial class PathResolver
_apricotResourceLoadHook.Disable();
_loader.ResourceLoadCustomization -= SubfileLoadHandler;
_loader.ResourceLoaded -= SubfileContainerRequested;
_loader.FileLoaded -= SubfileContainerLoaded;
_events.ResourceHandleDestructor -= ResourceDestroyed;
}
public void Dispose()
@ -121,16 +123,8 @@ public unsafe partial class PathResolver
}
}
private void SubfileContainerLoaded( ResourceHandle* handle, ByteString path, bool success, bool custom )
{
switch( handle->FileType )
{
case ResourceType.Mtrl:
case ResourceType.Avfx:
_subFileCollection.TryRemove( ( IntPtr )handle, out _ );
break;
}
}
private void ResourceDestroyed( ResourceHandle* handle )
=> _subFileCollection.TryRemove( ( IntPtr )handle, out _ );
// We need to set the correct collection for the actual material path that is loaded
// before actually loading the file.

View file

@ -24,10 +24,10 @@ public partial class PathResolver : IDisposable
public bool Enabled { get; private set; }
private readonly ResourceLoader _loader;
private static readonly CutsceneCharacters Cutscenes = new();
private static readonly CutsceneCharacters Cutscenes = new(Penumbra.GameEvents);
private static readonly DrawObjectState DrawObjects = new();
private static readonly BitArray ValidHumanModels;
internal static readonly IdentifiedCollectionCache IdentifiedCache = new();
internal static readonly IdentifiedCollectionCache IdentifiedCache = new(Penumbra.GameEvents);
private readonly AnimationState _animations;
private readonly PathState _paths;
private readonly MetaState _meta;
@ -43,7 +43,7 @@ public partial class PathResolver : IDisposable
_animations = new AnimationState( DrawObjects );
_paths = new PathState( this );
_meta = new MetaState( _paths.HumanVTable );
_subFiles = new SubfileHelper( _loader );
_subFiles = new SubfileHelper( _loader, Penumbra.GameEvents );
}
// The modified resolver that handles game path resolving.
@ -175,6 +175,9 @@ public partial class PathResolver : IDisposable
internal IEnumerable< KeyValuePair< IntPtr, ResolveData > > ResourceCollections
=> _subFiles;
internal int SubfileCount
=> _subFiles.Count;
internal ResolveData CurrentMtrlData
=> _subFiles.MtrlData;

View file

@ -54,6 +54,7 @@ public class Penumbra : IDalamudPlugin
public static ResidentResourceManager ResidentResources { get; private set; } = null!;
public static CharacterUtility CharacterUtility { get; private set; } = null!;
public static GameEventManager GameEvents { get; private set; } = null!;
public static MetaFileManager MetaFileManager { get; private set; } = null!;
public static Mod.Manager ModManager { get; private set; } = null!;
public static ModCollection.Manager CollectionManager { get; private set; } = null!;
@ -93,6 +94,7 @@ public class Penumbra : IDalamudPlugin
DevPenumbraExists = CheckDevPluginPenumbra();
IsNotInstalledPenumbra = CheckIsNotInstalled();
IsValidSourceRepo = CheckSourceRepo();
GameEvents = new GameEventManager();
Identifier = GameData.GameData.GetIdentifier( Dalamud.PluginInterface, Dalamud.GameData );
GamePathParser = GameData.GameData.GetGamePathParser();
StainManager = new StainManager( Dalamud.PluginInterface, Dalamud.GameData );
@ -305,6 +307,7 @@ public class Penumbra : IDalamudPlugin
PathResolver?.Dispose();
ResourceLogger?.Dispose();
ResourceLoader?.Dispose();
GameEvents?.Dispose();
CharacterUtility?.Dispose();
Performance?.Dispose();
}