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 public static class Sigs
{ {
// ResourceLoader.Debug // 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"; public const string ResourceManager = "48 8B 05 ?? ?? ?? ?? 33 ED F0";
// ResourceLoader.Replacement // 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 LoadTexFileExtern = "E8 ?? ?? ?? ?? 0F B6 E8 48 8B CB E8";
public const string LoadMdlFileExtern = "E8 ?? ?? ?? ?? EB 02 B0 F1"; 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"; public const string CopyCharacter = "E8 ?? ?? ?? ?? 0F B6 9F ?? ?? ?? ?? 48 8D 8F";
// IdentifiedCollectionCache
public const string CharacterDestructor = 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"; "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.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.Linq; using System.Linq;
using Dalamud.Game.ClientState.Conditions; using FFXIVClientStructs.FFXIV.Client.Game.Character;
using Dalamud.Hooking;
using Dalamud.Utility.Signatures;
using FFXIVClientStructs.FFXIV.Client.Game.Object;
using Penumbra.GameData;
namespace Penumbra.Interop.Resolver; namespace Penumbra.Interop.Resolver;
@ -16,6 +12,7 @@ public class CutsceneCharacters : IDisposable
public const int CutsceneSlots = 40; public const int CutsceneSlots = 40;
public const int CutsceneEndIdx = CutsceneStartIdx + CutsceneSlots; public const int CutsceneEndIdx = CutsceneStartIdx + CutsceneSlots;
private readonly GameEventManager _events;
private readonly short[] _copiedCharacters = Enumerable.Repeat( ( short )-1, CutsceneSlots ).ToArray(); private readonly short[] _copiedCharacters = Enumerable.Repeat( ( short )-1, CutsceneSlots ).ToArray();
public IEnumerable< KeyValuePair< int, global::Dalamud.Game.ClientState.Objects.Types.GameObject > > Actors 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 ) .Where( i => Dalamud.Objects[ i ] != null )
.Select( i => KeyValuePair.Create( i, this[ i ] ?? Dalamud.Objects[ i ]! ) ); .Select( i => KeyValuePair.Create( i, this[ i ] ?? Dalamud.Objects[ i ]! ) );
public CutsceneCharacters() public CutsceneCharacters(GameEventManager events)
{ {
SignatureHelper.Initialise( this ); _events = events;
Dalamud.Conditions.ConditionChange += Reset; Enable();
} }
// Get the related actor to a cutscene actor. // Get the related actor to a cutscene actor.
@ -53,71 +50,36 @@ public class CutsceneCharacters : IDisposable
return -1; return -1;
} }
public void Reset( ConditionFlag flag, bool value ) public unsafe void Enable()
{ {
switch( flag ) _events.CopyCharacter += OnCharacterCopy;
{ _events.CharacterDestructor += OnCharacterDestructor;
case ConditionFlag.BetweenAreas:
case ConditionFlag.BetweenAreas51:
if( !value )
{
return;
} }
break; public unsafe void Disable()
case ConditionFlag.OccupiedInCutSceneEvent:
case ConditionFlag.WatchingCutscene:
case ConditionFlag.WatchingCutscene78:
if( value )
{ {
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() public void Dispose()
=> Disable();
private unsafe void OnCharacterDestructor( Character* character )
{ {
_copyCharacterHook.Dispose(); if( character->GameObject.ObjectIndex is >= CutsceneStartIdx and < CutsceneEndIdx )
Dalamud.Conditions.ConditionChange -= Reset; {
var idx = character->GameObject.ObjectIndex - CutsceneStartIdx;
_copiedCharacters[ idx ] = -1;
}
} }
private unsafe delegate ulong CopyCharacterDelegate( GameObject* target, GameObject* source, uint unk ); private unsafe void OnCharacterCopy( Character* target, Character* source )
[Signature( Sigs.CopyCharacter, DetourName = nameof( CopyCharacterDetour ) )]
private readonly Hook< CopyCharacterDelegate > _copyCharacterHook = null!;
private unsafe ulong CopyCharacterDetour( GameObject* target, GameObject* source, uint unk )
{ {
try if( target != null && target->GameObject.ObjectIndex is >= CutsceneStartIdx and < CutsceneEndIdx )
{ {
if( target != null && target->ObjectIndex is >= CutsceneStartIdx and < CutsceneEndIdx ) var idx = target->GameObject.ObjectIndex - CutsceneStartIdx;
{ _copiedCharacters[idx] = (short) (source != null ? source->GameObject.ObjectIndex : -1);
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}." );
} }
} }
catch
{
// ignored
}
return _copyCharacterHook.Original( target, source, unk );
}
} }

View file

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

View file

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

View file

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

View file

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