mirror of
https://github.com/xivdev/Penumbra.git
synced 2025-12-12 18:27:24 +01:00
Current state.
This commit is contained in:
parent
431933e9c1
commit
9fb8090781
39 changed files with 186 additions and 139 deletions
2
OtterGui
2
OtterGui
|
|
@ -1 +1 @@
|
||||||
Subproject commit 437ef65c6464c54c8f40196dd2428da901d73aab
|
Subproject commit c2738e1d42974cddbe5a31238c6ed236a831d17d
|
||||||
|
|
@ -1 +1 @@
|
||||||
Subproject commit 3a97e5aeee3b7375b333c1add5305d0ce80cbf83
|
Subproject commit 066637abe05c659b79d84f52e6db33487498f433
|
||||||
|
|
@ -2,8 +2,8 @@ using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
|
||||||
using OtterGui.Services;
|
using OtterGui.Services;
|
||||||
using Penumbra.Api.Enums;
|
using Penumbra.Api.Enums;
|
||||||
using Penumbra.Collections;
|
using Penumbra.Collections;
|
||||||
|
using Penumbra.Interop.Hooks.ResourceLoading;
|
||||||
using Penumbra.Interop.PathResolving;
|
using Penumbra.Interop.PathResolving;
|
||||||
using Penumbra.Interop.ResourceLoading;
|
|
||||||
using Penumbra.Interop.Structs;
|
using Penumbra.Interop.Structs;
|
||||||
using Penumbra.Services;
|
using Penumbra.Services;
|
||||||
using Penumbra.String.Classes;
|
using Penumbra.String.Classes;
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ using Penumbra.Api;
|
||||||
using Penumbra.Api.Enums;
|
using Penumbra.Api.Enums;
|
||||||
using Penumbra.Collections.Manager;
|
using Penumbra.Collections.Manager;
|
||||||
using Penumbra.Communication;
|
using Penumbra.Communication;
|
||||||
using Penumbra.Interop.ResourceLoading;
|
using Penumbra.Interop.Hooks.ResourceLoading;
|
||||||
using Penumbra.Meta;
|
using Penumbra.Meta;
|
||||||
using Penumbra.Mods;
|
using Penumbra.Mods;
|
||||||
using Penumbra.Mods.Groups;
|
using Penumbra.Mods.Groups;
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
using FFXIVClientStructs.FFXIV.Client.System.Resource;
|
using FFXIVClientStructs.FFXIV.Client.System.Resource;
|
||||||
using Penumbra.Api.Enums;
|
using Penumbra.Api.Enums;
|
||||||
using Penumbra.Interop.ResourceLoading;
|
using Penumbra.Interop.Hooks.ResourceLoading;
|
||||||
using Penumbra.Interop.SafeHandles;
|
using Penumbra.Interop.SafeHandles;
|
||||||
using Penumbra.String.Classes;
|
using Penumbra.String.Classes;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ public sealed class MtrlShpkLoaded() : EventWrapper<nint, nint, MtrlShpkLoaded.P
|
||||||
{
|
{
|
||||||
public enum Priority
|
public enum Priority
|
||||||
{
|
{
|
||||||
/// <seealso cref="Interop.Services.ShaderReplacementFixer.OnMtrlShpkLoaded"/>
|
/// <seealso cref="Penumbra.Interop.Hooks.PostProcessing.ShaderReplacementFixer.OnMtrlShpkLoaded"/>
|
||||||
ShaderReplacementFixer = 0,
|
ShaderReplacementFixer = 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,14 @@
|
||||||
namespace Penumbra.Interop.Hooks;
|
namespace Penumbra.Interop.Hooks;
|
||||||
|
|
||||||
public static class HookSettings
|
public static class HookSettings
|
||||||
{
|
{
|
||||||
public const bool MetaEntryHooks = false;
|
public const bool AllHooks = true;
|
||||||
public const bool MetaParentHooks = false;
|
|
||||||
public const bool VfxIdentificationHooks = false;
|
public const bool ObjectHooks = false && AllHooks;
|
||||||
}
|
public const bool ReplacementHooks = true && AllHooks;
|
||||||
|
public const bool ResourceHooks = false && AllHooks;
|
||||||
|
public const bool MetaEntryHooks = false && AllHooks;
|
||||||
|
public const bool MetaParentHooks = false && AllHooks;
|
||||||
|
public const bool VfxIdentificationHooks = false && AllHooks;
|
||||||
|
public const bool PostProcessingHooks = false && AllHooks;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ public sealed unsafe class CharacterBaseDestructor : EventWrapperPtr<CharacterBa
|
||||||
|
|
||||||
public CharacterBaseDestructor(HookManager hooks)
|
public CharacterBaseDestructor(HookManager hooks)
|
||||||
: base("Destroy CharacterBase")
|
: base("Destroy CharacterBase")
|
||||||
=> _task = hooks.CreateHook<Delegate>(Name, Address, Detour, true);
|
=> _task = hooks.CreateHook<Delegate>(Name, Address, Detour, HookSettings.ObjectHooks);
|
||||||
|
|
||||||
private readonly Task<Hook<Delegate>> _task;
|
private readonly Task<Hook<Delegate>> _task;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ public sealed unsafe class CharacterDestructor : EventWrapperPtr<Character, Char
|
||||||
|
|
||||||
public CharacterDestructor(HookManager hooks)
|
public CharacterDestructor(HookManager hooks)
|
||||||
: base("Character Destructor")
|
: base("Character Destructor")
|
||||||
=> _task = hooks.CreateHook<Delegate>(Name, Sigs.CharacterDestructor, Detour, true);
|
=> _task = hooks.CreateHook<Delegate>(Name, Sigs.CharacterDestructor, Detour, HookSettings.ObjectHooks);
|
||||||
|
|
||||||
private readonly Task<Hook<Delegate>> _task;
|
private readonly Task<Hook<Delegate>> _task;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ public sealed unsafe class CopyCharacter : EventWrapperPtr<Character, Character,
|
||||||
|
|
||||||
public CopyCharacter(HookManager hooks)
|
public CopyCharacter(HookManager hooks)
|
||||||
: base("Copy Character")
|
: base("Copy Character")
|
||||||
=> _task = hooks.CreateHook<Delegate>(Name, Address, Detour, true);
|
=> _task = hooks.CreateHook<Delegate>(Name, Address, Detour, HookSettings.ObjectHooks);
|
||||||
|
|
||||||
private readonly Task<Hook<Delegate>> _task;
|
private readonly Task<Hook<Delegate>> _task;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ public sealed unsafe class CreateCharacterBase : EventWrapperPtr<ModelCharaId, C
|
||||||
|
|
||||||
public CreateCharacterBase(HookManager hooks)
|
public CreateCharacterBase(HookManager hooks)
|
||||||
: base("Create CharacterBase")
|
: base("Create CharacterBase")
|
||||||
=> _task = hooks.CreateHook<Delegate>(Name, Address, Detour, true);
|
=> _task = hooks.CreateHook<Delegate>(Name, Address, Detour, HookSettings.ObjectHooks);
|
||||||
|
|
||||||
private readonly Task<Hook<Delegate>> _task;
|
private readonly Task<Hook<Delegate>> _task;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ public sealed unsafe class EnableDraw : IHookService
|
||||||
public EnableDraw(HookManager hooks, GameState state)
|
public EnableDraw(HookManager hooks, GameState state)
|
||||||
{
|
{
|
||||||
_state = state;
|
_state = state;
|
||||||
_task = hooks.CreateHook<Delegate>("Enable Draw", Sigs.EnableDraw, Detour, true);
|
_task = hooks.CreateHook<Delegate>("Enable Draw", Sigs.EnableDraw, Detour, HookSettings.ObjectHooks);
|
||||||
}
|
}
|
||||||
|
|
||||||
private delegate void Delegate(GameObject* gameObject);
|
private delegate void Delegate(GameObject* gameObject);
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ public sealed unsafe class WeaponReload : EventWrapperPtr<DrawDataContainer, Cha
|
||||||
|
|
||||||
public WeaponReload(HookManager hooks)
|
public WeaponReload(HookManager hooks)
|
||||||
: base("Reload Weapon")
|
: base("Reload Weapon")
|
||||||
=> _task = hooks.CreateHook<Delegate>(Name, Address, Detour, true);
|
=> _task = hooks.CreateHook<Delegate>(Name, Address, Detour, HookSettings.ObjectHooks);
|
||||||
|
|
||||||
private readonly Task<Hook<Delegate>> _task;
|
private readonly Task<Hook<Delegate>> _task;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,12 +4,13 @@ using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
|
||||||
using FFXIVClientStructs.FFXIV.Client.System.Resource;
|
using FFXIVClientStructs.FFXIV.Client.System.Resource;
|
||||||
using OtterGui.Services;
|
using OtterGui.Services;
|
||||||
using Penumbra.Api.Enums;
|
using Penumbra.Api.Enums;
|
||||||
|
using Penumbra.Interop.Hooks.ResourceLoading;
|
||||||
using Penumbra.Interop.PathResolving;
|
using Penumbra.Interop.PathResolving;
|
||||||
using Penumbra.Interop.ResourceLoading;
|
|
||||||
using Penumbra.Interop.SafeHandles;
|
using Penumbra.Interop.SafeHandles;
|
||||||
using Penumbra.String.Classes;
|
using Penumbra.String.Classes;
|
||||||
|
using CharacterUtility = Penumbra.Interop.Services.CharacterUtility;
|
||||||
|
|
||||||
namespace Penumbra.Interop.Services;
|
namespace Penumbra.Interop.Hooks.PostProcessing;
|
||||||
|
|
||||||
public sealed unsafe class PreBoneDeformerReplacer : IDisposable, IRequiredService
|
public sealed unsafe class PreBoneDeformerReplacer : IDisposable, IRequiredService
|
||||||
{
|
{
|
||||||
|
|
@ -29,17 +30,16 @@ public sealed unsafe class PreBoneDeformerReplacer : IDisposable, IRequiredServi
|
||||||
private readonly IFramework _framework;
|
private readonly IFramework _framework;
|
||||||
|
|
||||||
public PreBoneDeformerReplacer(CharacterUtility utility, CollectionResolver collectionResolver, ResourceLoader resourceLoader,
|
public PreBoneDeformerReplacer(CharacterUtility utility, CollectionResolver collectionResolver, ResourceLoader resourceLoader,
|
||||||
IGameInteropProvider interop, IFramework framework, CharacterBaseVTables vTables)
|
HookManager hooks, IFramework framework, CharacterBaseVTables vTables)
|
||||||
{
|
{
|
||||||
interop.InitializeFromAttributes(this);
|
_utility = utility;
|
||||||
_utility = utility;
|
_collectionResolver = collectionResolver;
|
||||||
_collectionResolver = collectionResolver;
|
_resourceLoader = resourceLoader;
|
||||||
_resourceLoader = resourceLoader;
|
_framework = framework;
|
||||||
_framework = framework;
|
_humanSetupScalingHook = hooks.CreateHook<CharacterBaseSetupScalingDelegate>("HumanSetupScaling", vTables.HumanVTable[58], SetupScaling,
|
||||||
_humanSetupScalingHook = interop.HookFromAddress<CharacterBaseSetupScalingDelegate>(vTables.HumanVTable[57], SetupScaling);
|
HookSettings.PostProcessingHooks).Result;
|
||||||
_humanCreateDeformerHook = interop.HookFromAddress<CharacterBaseCreateDeformerDelegate>(vTables.HumanVTable[91], CreateDeformer);
|
_humanCreateDeformerHook = hooks.CreateHook<CharacterBaseCreateDeformerDelegate>("HumanCreateDeformer", vTables.HumanVTable[101],
|
||||||
_humanSetupScalingHook.Enable();
|
CreateDeformer, HookSettings.PostProcessingHooks).Result;
|
||||||
_humanCreateDeformerHook.Enable();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
|
|
@ -1,6 +1,4 @@
|
||||||
using Dalamud.Hooking;
|
using Dalamud.Hooking;
|
||||||
using Dalamud.Plugin.Services;
|
|
||||||
using Dalamud.Utility.Signatures;
|
|
||||||
using FFXIVClientStructs.FFXIV.Client.Graphics.Render;
|
using FFXIVClientStructs.FFXIV.Client.Graphics.Render;
|
||||||
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
|
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
|
||||||
using FFXIVClientStructs.FFXIV.Client.System.Resource.Handle;
|
using FFXIVClientStructs.FFXIV.Client.System.Resource.Handle;
|
||||||
|
|
@ -10,9 +8,11 @@ using Penumbra.Communication;
|
||||||
using Penumbra.GameData;
|
using Penumbra.GameData;
|
||||||
using Penumbra.Interop.Hooks.Resources;
|
using Penumbra.Interop.Hooks.Resources;
|
||||||
using Penumbra.Services;
|
using Penumbra.Services;
|
||||||
|
using CharacterUtility = Penumbra.Interop.Services.CharacterUtility;
|
||||||
using CSModelRenderer = FFXIVClientStructs.FFXIV.Client.Graphics.Render.ModelRenderer;
|
using CSModelRenderer = FFXIVClientStructs.FFXIV.Client.Graphics.Render.ModelRenderer;
|
||||||
|
using ModelRenderer = Penumbra.Interop.Services.ModelRenderer;
|
||||||
|
|
||||||
namespace Penumbra.Interop.Services;
|
namespace Penumbra.Interop.Hooks.PostProcessing;
|
||||||
|
|
||||||
public sealed unsafe class ShaderReplacementFixer : IDisposable, IRequiredService
|
public sealed unsafe class ShaderReplacementFixer : IDisposable, IRequiredService
|
||||||
{
|
{
|
||||||
|
|
@ -29,8 +29,7 @@ public sealed unsafe class ShaderReplacementFixer : IDisposable, IRequiredServic
|
||||||
|
|
||||||
private readonly Hook<CharacterBaseOnRenderMaterialDelegate> _humanOnRenderMaterialHook;
|
private readonly Hook<CharacterBaseOnRenderMaterialDelegate> _humanOnRenderMaterialHook;
|
||||||
|
|
||||||
[Signature(Sigs.ModelRendererOnRenderMaterial, DetourName = nameof(ModelRendererOnRenderMaterialDetour))]
|
private readonly Hook<ModelRendererOnRenderMaterialDelegate> _modelRendererOnRenderMaterialHook;
|
||||||
private readonly Hook<ModelRendererOnRenderMaterialDelegate> _modelRendererOnRenderMaterialHook = null!;
|
|
||||||
|
|
||||||
private readonly ResourceHandleDestructor _resourceHandleDestructor;
|
private readonly ResourceHandleDestructor _resourceHandleDestructor;
|
||||||
private readonly CommunicatorService _communicator;
|
private readonly CommunicatorService _communicator;
|
||||||
|
|
@ -59,19 +58,18 @@ public sealed unsafe class ShaderReplacementFixer : IDisposable, IRequiredServic
|
||||||
=> _moddedCharacterGlassShpkCount;
|
=> _moddedCharacterGlassShpkCount;
|
||||||
|
|
||||||
public ShaderReplacementFixer(ResourceHandleDestructor resourceHandleDestructor, CharacterUtility utility, ModelRenderer modelRenderer,
|
public ShaderReplacementFixer(ResourceHandleDestructor resourceHandleDestructor, CharacterUtility utility, ModelRenderer modelRenderer,
|
||||||
CommunicatorService communicator, IGameInteropProvider interop, CharacterBaseVTables vTables)
|
CommunicatorService communicator, HookManager hooks, CharacterBaseVTables vTables)
|
||||||
{
|
{
|
||||||
interop.InitializeFromAttributes(this);
|
|
||||||
_resourceHandleDestructor = resourceHandleDestructor;
|
_resourceHandleDestructor = resourceHandleDestructor;
|
||||||
_utility = utility;
|
_utility = utility;
|
||||||
_modelRenderer = modelRenderer;
|
_modelRenderer = modelRenderer;
|
||||||
_communicator = communicator;
|
_communicator = communicator;
|
||||||
_humanOnRenderMaterialHook =
|
_humanOnRenderMaterialHook = hooks.CreateHook<CharacterBaseOnRenderMaterialDelegate>("Human.OnRenderMaterial", vTables.HumanVTable[62],
|
||||||
interop.HookFromAddress<CharacterBaseOnRenderMaterialDelegate>(vTables.HumanVTable[62], OnRenderHumanMaterial);
|
OnRenderHumanMaterial, HookSettings.PostProcessingHooks).Result;
|
||||||
|
_modelRendererOnRenderMaterialHook = hooks.CreateHook<ModelRendererOnRenderMaterialDelegate>("ModelRenderer.OnRenderMaterial",
|
||||||
|
Sigs.ModelRendererOnRenderMaterial, ModelRendererOnRenderMaterialDetour, HookSettings.PostProcessingHooks).Result;
|
||||||
_communicator.MtrlShpkLoaded.Subscribe(OnMtrlShpkLoaded, MtrlShpkLoaded.Priority.ShaderReplacementFixer);
|
_communicator.MtrlShpkLoaded.Subscribe(OnMtrlShpkLoaded, MtrlShpkLoaded.Priority.ShaderReplacementFixer);
|
||||||
_resourceHandleDestructor.Subscribe(OnResourceHandleDestructor, ResourceHandleDestructor.Priority.ShaderReplacementFixer);
|
_resourceHandleDestructor.Subscribe(OnResourceHandleDestructor, ResourceHandleDestructor.Priority.ShaderReplacementFixer);
|
||||||
_humanOnRenderMaterialHook.Enable();
|
|
||||||
_modelRendererOnRenderMaterialHook.Enable();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
|
|
@ -5,7 +5,7 @@ using Penumbra.String;
|
||||||
using Penumbra.String.Classes;
|
using Penumbra.String.Classes;
|
||||||
using Penumbra.String.Functions;
|
using Penumbra.String.Functions;
|
||||||
|
|
||||||
namespace Penumbra.Interop.ResourceLoading;
|
namespace Penumbra.Interop.Hooks.ResourceLoading;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// To allow XIV to load files of arbitrary path length,
|
/// To allow XIV to load files of arbitrary path length,
|
||||||
|
|
@ -19,7 +19,8 @@ public unsafe class CreateFileWHook : IDisposable, IRequiredService
|
||||||
public CreateFileWHook(IGameInteropProvider interop)
|
public CreateFileWHook(IGameInteropProvider interop)
|
||||||
{
|
{
|
||||||
_createFileWHook = interop.HookFromImport<CreateFileWDelegate>(null, "KERNEL32.dll", "CreateFileW", 0, CreateFileWDetour);
|
_createFileWHook = interop.HookFromImport<CreateFileWDelegate>(null, "KERNEL32.dll", "CreateFileW", 0, CreateFileWDetour);
|
||||||
_createFileWHook.Enable();
|
if (HookSettings.ReplacementHooks)
|
||||||
|
_createFileWHook.Enable();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -6,16 +6,17 @@ using Penumbra.GameData;
|
||||||
using Penumbra.Interop.Structs;
|
using Penumbra.Interop.Structs;
|
||||||
using Penumbra.Util;
|
using Penumbra.Util;
|
||||||
|
|
||||||
namespace Penumbra.Interop.ResourceLoading;
|
namespace Penumbra.Interop.Hooks.ResourceLoading;
|
||||||
|
|
||||||
public unsafe class FileReadService : IDisposable, IRequiredService
|
public unsafe class FileReadService : IDisposable, IRequiredService
|
||||||
{
|
{
|
||||||
public FileReadService(PerformanceTracker performance, ResourceManagerService resourceManager, IGameInteropProvider interop)
|
public FileReadService(PerformanceTracker performance, ResourceManagerService resourceManager, IGameInteropProvider interop)
|
||||||
{
|
{
|
||||||
_resourceManager = resourceManager;
|
_resourceManager = resourceManager;
|
||||||
_performance = performance;
|
_performance = performance;
|
||||||
interop.InitializeFromAttributes(this);
|
interop.InitializeFromAttributes(this);
|
||||||
_readSqPackHook.Enable();
|
if (HookSettings.ReplacementHooks)
|
||||||
|
_readSqPackHook.Enable();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary> Invoked when a file is supposed to be read from SqPack. </summary>
|
/// <summary> Invoked when a file is supposed to be read from SqPack. </summary>
|
||||||
|
|
@ -49,7 +50,7 @@ public unsafe class FileReadService : IDisposable, IRequiredService
|
||||||
_readSqPackHook.Dispose();
|
_readSqPackHook.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly PerformanceTracker _performance;
|
private readonly PerformanceTracker _performance;
|
||||||
private readonly ResourceManagerService _resourceManager;
|
private readonly ResourceManagerService _resourceManager;
|
||||||
|
|
||||||
private delegate byte ReadSqPackPrototype(nint resourceManager, SeFileDescriptor* pFileDesc, int priority, bool isSync);
|
private delegate byte ReadSqPackPrototype(nint resourceManager, SeFileDescriptor* pFileDesc, int priority, bool isSync);
|
||||||
|
|
@ -60,7 +61,7 @@ public unsafe class FileReadService : IDisposable, IRequiredService
|
||||||
private byte ReadSqPackDetour(nint resourceManager, SeFileDescriptor* fileDescriptor, int priority, bool isSync)
|
private byte ReadSqPackDetour(nint resourceManager, SeFileDescriptor* fileDescriptor, int priority, bool isSync)
|
||||||
{
|
{
|
||||||
using var performance = _performance.Measure(PerformanceType.ReadSqPack);
|
using var performance = _performance.Measure(PerformanceType.ReadSqPack);
|
||||||
byte? ret = null;
|
byte? ret = null;
|
||||||
_lastFileThreadResourceManager.Value = resourceManager;
|
_lastFileThreadResourceManager.Value = resourceManager;
|
||||||
ReadSqPack?.Invoke(fileDescriptor, ref priority, ref isSync, ref ret);
|
ReadSqPack?.Invoke(fileDescriptor, ref priority, ref isSync, ref ret);
|
||||||
_lastFileThreadResourceManager.Value = nint.Zero;
|
_lastFileThreadResourceManager.Value = nint.Zero;
|
||||||
|
|
@ -9,27 +9,27 @@ using Penumbra.String;
|
||||||
using Penumbra.String.Classes;
|
using Penumbra.String.Classes;
|
||||||
using FileMode = Penumbra.Interop.Structs.FileMode;
|
using FileMode = Penumbra.Interop.Structs.FileMode;
|
||||||
|
|
||||||
namespace Penumbra.Interop.ResourceLoading;
|
namespace Penumbra.Interop.Hooks.ResourceLoading;
|
||||||
|
|
||||||
public unsafe class ResourceLoader : IDisposable, IService
|
public unsafe class ResourceLoader : IDisposable, IService
|
||||||
{
|
{
|
||||||
private readonly ResourceService _resources;
|
private readonly ResourceService _resources;
|
||||||
private readonly FileReadService _fileReadService;
|
private readonly FileReadService _fileReadService;
|
||||||
private readonly TexMdlService _texMdlService;
|
private readonly TexMdlService _texMdlService;
|
||||||
|
|
||||||
private ResolveData _resolvedData = ResolveData.Invalid;
|
private ResolveData _resolvedData = ResolveData.Invalid;
|
||||||
|
|
||||||
public ResourceLoader(ResourceService resources, FileReadService fileReadService, TexMdlService texMdlService)
|
public ResourceLoader(ResourceService resources, FileReadService fileReadService, TexMdlService texMdlService)
|
||||||
{
|
{
|
||||||
_resources = resources;
|
_resources = resources;
|
||||||
_fileReadService = fileReadService;
|
_fileReadService = fileReadService;
|
||||||
_texMdlService = texMdlService;
|
_texMdlService = texMdlService;
|
||||||
ResetResolvePath();
|
ResetResolvePath();
|
||||||
|
|
||||||
_resources.ResourceRequested += ResourceHandler;
|
_resources.ResourceRequested += ResourceHandler;
|
||||||
_resources.ResourceHandleIncRef += IncRefProtection;
|
_resources.ResourceHandleIncRef += IncRefProtection;
|
||||||
_resources.ResourceHandleDecRef += DecRefProtection;
|
_resources.ResourceHandleDecRef += DecRefProtection;
|
||||||
_fileReadService.ReadSqPack += ReadSqPackDetour;
|
_fileReadService.ReadSqPack += ReadSqPackDetour;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary> Load a resource for a given path and a specific collection. </summary>
|
/// <summary> Load a resource for a given path and a specific collection. </summary>
|
||||||
|
|
@ -80,10 +80,10 @@ public unsafe class ResourceLoader : IDisposable, IService
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
_resources.ResourceRequested -= ResourceHandler;
|
_resources.ResourceRequested -= ResourceHandler;
|
||||||
_resources.ResourceHandleIncRef -= IncRefProtection;
|
_resources.ResourceHandleIncRef -= IncRefProtection;
|
||||||
_resources.ResourceHandleDecRef -= DecRefProtection;
|
_resources.ResourceHandleDecRef -= DecRefProtection;
|
||||||
_fileReadService.ReadSqPack -= ReadSqPackDetour;
|
_fileReadService.ReadSqPack -= ReadSqPackDetour;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ResourceHandler(ref ResourceCategory category, ref ResourceType type, ref int hash, ref Utf8GamePath path,
|
private void ResourceHandler(ref ResourceCategory category, ref ResourceType type, ref int hash, ref Utf8GamePath path,
|
||||||
|
|
@ -112,7 +112,7 @@ public unsafe class ResourceLoader : IDisposable, IService
|
||||||
// Replace the hash and path with the correct one for the replacement.
|
// Replace the hash and path with the correct one for the replacement.
|
||||||
hash = ComputeHash(resolvedPath.Value.InternalName, parameters);
|
hash = ComputeHash(resolvedPath.Value.InternalName, parameters);
|
||||||
var oldPath = path;
|
var oldPath = path;
|
||||||
path = p;
|
path = p;
|
||||||
returnValue = _resources.GetOriginalResource(sync, category, type, hash, path.Path, parameters);
|
returnValue = _resources.GetOriginalResource(sync, category, type, hash, path.Path, parameters);
|
||||||
ResourceLoaded?.Invoke(returnValue, oldPath, resolvedPath.Value, data);
|
ResourceLoaded?.Invoke(returnValue, oldPath, resolvedPath.Value, data);
|
||||||
}
|
}
|
||||||
|
|
@ -140,12 +140,12 @@ public unsafe class ResourceLoader : IDisposable, IService
|
||||||
}
|
}
|
||||||
|
|
||||||
var path = ByteString.FromSpanUnsafe(actualPath, gamePath.Path.IsNullTerminated, gamePath.Path.IsAsciiLowerCase, gamePath.Path.IsAscii);
|
var path = ByteString.FromSpanUnsafe(actualPath, gamePath.Path.IsNullTerminated, gamePath.Path.IsAsciiLowerCase, gamePath.Path.IsAscii);
|
||||||
fileDescriptor->ResourceHandle->FileNameData = path.Path;
|
fileDescriptor->ResourceHandle->FileNameData = path.Path;
|
||||||
fileDescriptor->ResourceHandle->FileNameLength = path.Length;
|
fileDescriptor->ResourceHandle->FileNameLength = path.Length;
|
||||||
MtrlForceSync(fileDescriptor, ref isSync);
|
MtrlForceSync(fileDescriptor, ref isSync);
|
||||||
returnValue = DefaultLoadResource(path, fileDescriptor, priority, isSync, data);
|
returnValue = DefaultLoadResource(path, fileDescriptor, priority, isSync, data);
|
||||||
// Return original resource handle path so that they can be loaded separately.
|
// Return original resource handle path so that they can be loaded separately.
|
||||||
fileDescriptor->ResourceHandle->FileNameData = gamePath.Path.Path;
|
fileDescriptor->ResourceHandle->FileNameData = gamePath.Path.Path;
|
||||||
fileDescriptor->ResourceHandle->FileNameLength = gamePath.Path.Length;
|
fileDescriptor->ResourceHandle->FileNameLength = gamePath.Path.Length;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -165,7 +165,7 @@ public unsafe class ResourceLoader : IDisposable, IService
|
||||||
// Ensure that the file descriptor has its wchar_t array on aligned boundary even if it has to be odd.
|
// Ensure that the file descriptor has its wchar_t array on aligned boundary even if it has to be odd.
|
||||||
var fd = stackalloc char[0x11 + 0x0B + 14];
|
var fd = stackalloc char[0x11 + 0x0B + 14];
|
||||||
fileDescriptor->FileDescriptor = (byte*)fd + 1;
|
fileDescriptor->FileDescriptor = (byte*)fd + 1;
|
||||||
CreateFileWHook.WritePtr(fd + 0x11, gamePath.Path, gamePath.Length);
|
CreateFileWHook.WritePtr(fd + 0x11, gamePath.Path, gamePath.Length);
|
||||||
CreateFileWHook.WritePtr(&fileDescriptor->Utf16FileName, gamePath.Path, gamePath.Length);
|
CreateFileWHook.WritePtr(&fileDescriptor->Utf16FileName, gamePath.Path, gamePath.Length);
|
||||||
|
|
||||||
// Use the SE ReadFile function.
|
// Use the SE ReadFile function.
|
||||||
|
|
@ -206,7 +206,7 @@ public unsafe class ResourceLoader : IDisposable, IService
|
||||||
return;
|
return;
|
||||||
|
|
||||||
_incMode.Value = true;
|
_incMode.Value = true;
|
||||||
returnValue = _resources.IncRef(handle);
|
returnValue = _resources.IncRef(handle);
|
||||||
_incMode.Value = false;
|
_incMode.Value = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -8,7 +8,7 @@ using OtterGui.Services;
|
||||||
using Penumbra.Api.Enums;
|
using Penumbra.Api.Enums;
|
||||||
using Penumbra.GameData;
|
using Penumbra.GameData;
|
||||||
|
|
||||||
namespace Penumbra.Interop.ResourceLoading;
|
namespace Penumbra.Interop.Hooks.ResourceLoading;
|
||||||
|
|
||||||
public unsafe class ResourceManagerService : IRequiredService
|
public unsafe class ResourceManagerService : IRequiredService
|
||||||
{
|
{
|
||||||
|
|
@ -23,10 +23,10 @@ public unsafe class ResourceManagerService : IRequiredService
|
||||||
public ResourceHandle* FindResource(ResourceCategory cat, ResourceType ext, uint crc32)
|
public ResourceHandle* FindResource(ResourceCategory cat, ResourceType ext, uint crc32)
|
||||||
{
|
{
|
||||||
ref var manager = ref *ResourceManager;
|
ref var manager = ref *ResourceManager;
|
||||||
var catIdx = (uint)cat >> 0x18;
|
var catIdx = (uint)cat >> 0x18;
|
||||||
cat = (ResourceCategory)(ushort)cat;
|
cat = (ResourceCategory)(ushort)cat;
|
||||||
ref var category = ref manager.ResourceGraph->Containers[(int)cat];
|
ref var category = ref manager.ResourceGraph->Containers[(int)cat];
|
||||||
var extMap = FindInMap(category.CategoryMaps[(int)catIdx].Value, (uint)ext);
|
var extMap = FindInMap(category.CategoryMaps[(int)catIdx].Value, (uint)ext);
|
||||||
if (extMap == null)
|
if (extMap == null)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
|
|
@ -12,7 +12,7 @@ using Penumbra.String.Classes;
|
||||||
using Penumbra.Util;
|
using Penumbra.Util;
|
||||||
using CSResourceHandle = FFXIVClientStructs.FFXIV.Client.System.Resource.Handle.ResourceHandle;
|
using CSResourceHandle = FFXIVClientStructs.FFXIV.Client.System.Resource.Handle.ResourceHandle;
|
||||||
|
|
||||||
namespace Penumbra.Interop.ResourceLoading;
|
namespace Penumbra.Interop.Hooks.ResourceLoading;
|
||||||
|
|
||||||
public unsafe class ResourceService : IDisposable, IRequiredService
|
public unsafe class ResourceService : IDisposable, IRequiredService
|
||||||
{
|
{
|
||||||
|
|
@ -24,16 +24,19 @@ public unsafe class ResourceService : IDisposable, IRequiredService
|
||||||
_performance = performance;
|
_performance = performance;
|
||||||
_resourceManager = resourceManager;
|
_resourceManager = resourceManager;
|
||||||
interop.InitializeFromAttributes(this);
|
interop.InitializeFromAttributes(this);
|
||||||
_getResourceSyncHook.Enable();
|
|
||||||
_getResourceAsyncHook.Enable();
|
|
||||||
_incRefHook = interop.HookFromAddress<ResourceHandlePrototype>(
|
_incRefHook = interop.HookFromAddress<ResourceHandlePrototype>(
|
||||||
(nint)CSResourceHandle.MemberFunctionPointers.IncRef,
|
(nint)CSResourceHandle.MemberFunctionPointers.IncRef,
|
||||||
ResourceHandleIncRefDetour);
|
ResourceHandleIncRefDetour);
|
||||||
_incRefHook.Enable();
|
|
||||||
_decRefHook = interop.HookFromAddress<ResourceHandleDecRefPrototype>(
|
_decRefHook = interop.HookFromAddress<ResourceHandleDecRefPrototype>(
|
||||||
(nint)CSResourceHandle.MemberFunctionPointers.DecRef,
|
(nint)CSResourceHandle.MemberFunctionPointers.DecRef,
|
||||||
ResourceHandleDecRefDetour);
|
ResourceHandleDecRefDetour);
|
||||||
_decRefHook.Enable();
|
if (HookSettings.ReplacementHooks)
|
||||||
|
{
|
||||||
|
_getResourceSyncHook.Enable();
|
||||||
|
_getResourceAsyncHook.Enable();
|
||||||
|
_incRefHook.Enable();
|
||||||
|
_decRefHook.Enable();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public ResourceHandle* GetResource(ResourceCategory category, ResourceType type, ByteString path)
|
public ResourceHandle* GetResource(ResourceCategory category, ResourceType type, ByteString path)
|
||||||
|
|
@ -1,109 +1,138 @@
|
||||||
using Dalamud.Hooking;
|
using Dalamud.Hooking;
|
||||||
using Dalamud.Plugin.Services;
|
using Dalamud.Plugin.Services;
|
||||||
using Dalamud.Utility.Signatures;
|
using Dalamud.Utility.Signatures;
|
||||||
using FFXIVClientStructs.FFXIV.Client.LayoutEngine;
|
using Lumina.Excel.GeneratedSheets2;
|
||||||
using FFXIVClientStructs.FFXIV.Client.System.Resource.Handle;
|
|
||||||
using OtterGui.Services;
|
using OtterGui.Services;
|
||||||
using Penumbra.Api.Enums;
|
using Penumbra.Api.Enums;
|
||||||
using Penumbra.GameData;
|
using Penumbra.GameData;
|
||||||
|
using Penumbra.Interop.Structs;
|
||||||
using Penumbra.String.Classes;
|
using Penumbra.String.Classes;
|
||||||
|
using FileMode = Penumbra.Interop.Structs.FileMode;
|
||||||
|
using ResourceHandle = FFXIVClientStructs.FFXIV.Client.System.Resource.Handle.ResourceHandle;
|
||||||
|
|
||||||
namespace Penumbra.Interop.ResourceLoading;
|
namespace Penumbra.Interop.Hooks.ResourceLoading;
|
||||||
|
|
||||||
public unsafe class TexMdlService : IDisposable, IRequiredService
|
public unsafe class TexMdlService : IDisposable, IRequiredService
|
||||||
{
|
{
|
||||||
/// <summary> Custom ulong flag to signal our files as opposed to SE files. </summary>
|
/// <summary> Custom ulong flag to signal our files as opposed to SE files. </summary>
|
||||||
public static readonly nint CustomFileFlag = new(0xDEADBEEF);
|
public static readonly nint CustomFileFlag = new(0xDEADBEEF);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// We need to keep a list of all CRC64 hash values of our replaced Mdl and Tex files,
|
/// We need to keep a list of all CRC64 hash values of our replaced Mdl and Tex files,
|
||||||
/// i.e. CRC32 of filename in the lower bytes, CRC32 of parent path in the upper bytes.
|
/// i.e. CRC32 of filename in the lower bytes, CRC32 of parent path in the upper bytes.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public IReadOnlySet<ulong> CustomFileCrc
|
public IReadOnlySet<ulong> CustomFileCrc
|
||||||
=> _customFileCrc;
|
=> _customFileCrc;
|
||||||
|
|
||||||
public TexMdlService(IGameInteropProvider interop)
|
public TexMdlService(IGameInteropProvider interop)
|
||||||
{
|
{
|
||||||
interop.InitializeFromAttributes(this);
|
interop.InitializeFromAttributes(this);
|
||||||
//_checkFileStateHook.Enable();
|
if (HookSettings.ReplacementHooks)
|
||||||
//_loadTexFileExternHook.Enable();
|
{
|
||||||
//_loadMdlFileExternHook.Enable();
|
_checkFileStateHook.Enable();
|
||||||
|
_loadMdlFileExternHook.Enable();
|
||||||
|
_textureSomethingHook.Enable();
|
||||||
|
_vf32Hook.Enable();
|
||||||
|
//_loadTexFileExternHook.Enable();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary> Add CRC64 if the given file is a model or texture file and has an associated path. </summary>
|
/// <summary> Add CRC64 if the given file is a model or texture file and has an associated path. </summary>
|
||||||
public void AddCrc(ResourceType type, FullPath? path)
|
public void AddCrc(ResourceType type, FullPath? path)
|
||||||
{
|
{
|
||||||
if (path.HasValue && type is ResourceType.Mdl or ResourceType.Tex)
|
if (path.HasValue && type is ResourceType.Mdl)
|
||||||
_customFileCrc.Add(path.Value.Crc64);
|
_customFileCrc.Add(path.Value.Crc64);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary> Add a fixed CRC64 value. </summary>
|
/// <summary> Add a fixed CRC64 value. </summary>
|
||||||
public void AddCrc(ulong crc64)
|
public void AddCrc(ulong crc64)
|
||||||
=> _customFileCrc.Add(crc64);
|
=> _customFileCrc.Add(crc64);
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
//_checkFileStateHook.Dispose();
|
_checkFileStateHook.Dispose();
|
||||||
//_loadTexFileExternHook.Dispose();
|
//_loadTexFileExternHook.Dispose();
|
||||||
//_loadMdlFileExternHook.Dispose();
|
_textureSomethingHook.Dispose();
|
||||||
|
_loadMdlFileExternHook.Dispose();
|
||||||
|
_vf32Hook.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly HashSet<ulong> _customFileCrc = new();
|
private readonly HashSet<ulong> _customFileCrc = [];
|
||||||
|
|
||||||
private delegate nint CheckFileStatePrototype(nint unk1, ulong crc64);
|
private delegate nint CheckFileStatePrototype(nint unk1, ulong crc64);
|
||||||
|
|
||||||
|
private delegate nint TextureSomethingDelegate(TextureResourceHandle* handle, int lod, SeFileDescriptor* descriptor);
|
||||||
|
|
||||||
[Signature(Sigs.CheckFileState, DetourName = nameof(CheckFileStateDetour))]
|
[Signature(Sigs.CheckFileState, DetourName = nameof(CheckFileStateDetour))]
|
||||||
private readonly Hook<CheckFileStatePrototype> _checkFileStateHook = null!;
|
private readonly Hook<CheckFileStatePrototype> _checkFileStateHook = null!;
|
||||||
|
|
||||||
|
[Signature("E8 ?? ?? ?? ?? 0F B6 C8 EB ?? 4C 8B 83", DetourName = nameof(TextureSomethingDetour))]
|
||||||
|
private readonly Hook<TextureSomethingDelegate> _textureSomethingHook = null!;
|
||||||
|
|
||||||
|
private nint TextureSomethingDetour(TextureResourceHandle* handle, int lod, SeFileDescriptor* descriptor)
|
||||||
|
{
|
||||||
|
//Penumbra.Log.Information($"SomethingDetour {handle->Handle.FileName()}");
|
||||||
|
//if (!handle->Handle.GamePath(out var path) || !path.IsRooted())
|
||||||
|
return _textureSomethingHook.Original(handle, lod, descriptor);
|
||||||
|
|
||||||
|
descriptor->FileMode = FileMode.LoadUnpackedResource;
|
||||||
|
return _loadTexFileLocal.Invoke((ResourceHandle*)handle, lod, (nint)descriptor, true);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The function that checks a files CRC64 to determine whether it is 'protected'.
|
/// The function that checks a files CRC64 to determine whether it is 'protected'.
|
||||||
/// We use it to check against our stored CRC64s and if it corresponds, we return the custom flag.
|
/// We use it to check against our stored CRC64s and if it corresponds, we return the custom flag.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private nint CheckFileStateDetour(nint ptr, ulong crc64)
|
private nint CheckFileStateDetour(nint ptr, ulong crc64)
|
||||||
=> _customFileCrc.Contains(crc64) ? CustomFileFlag : _checkFileStateHook.Original(ptr, crc64);
|
=> _customFileCrc.Contains(crc64) ? CustomFileFlag : _checkFileStateHook.Original(ptr, crc64);
|
||||||
|
|
||||||
|
|
||||||
private delegate byte LoadTexFileLocalDelegate(ResourceHandle* handle, int unk1, nint unk2, bool unk3);
|
private delegate byte LoadTexFileLocalDelegate(ResourceHandle* handle, int unk1, nint unk2, bool unk3);
|
||||||
|
|
||||||
/// <summary> We use the local functions for our own files in the extern hook. </summary>
|
/// <summary> We use the local functions for our own files in the extern hook. </summary>
|
||||||
[Signature(Sigs.LoadTexFileLocal)]
|
[Signature(Sigs.LoadTexFileLocal)]
|
||||||
private readonly LoadTexFileLocalDelegate _loadTexFileLocal = null!;
|
private readonly LoadTexFileLocalDelegate _loadTexFileLocal = null!;
|
||||||
|
|
||||||
private delegate byte LoadMdlFileLocalPrototype(ResourceHandle* handle, nint unk1, bool unk2);
|
private delegate byte LoadMdlFileLocalPrototype(ResourceHandle* handle, nint unk1, bool unk2);
|
||||||
|
|
||||||
/// <summary> We use the local functions for our own files in the extern hook. </summary>
|
/// <summary> We use the local functions for our own files in the extern hook. </summary>
|
||||||
[Signature(Sigs.LoadMdlFileLocal)]
|
[Signature(Sigs.LoadMdlFileLocal)]
|
||||||
private readonly LoadMdlFileLocalPrototype _loadMdlFileLocal = null!;
|
private readonly LoadMdlFileLocalPrototype _loadMdlFileLocal = null!;
|
||||||
|
|
||||||
|
|
||||||
private delegate byte LoadTexFileExternPrototype(ResourceHandle* handle, int unk1, nint unk2, bool unk3, nint unk4);
|
private delegate byte LoadTexFileExternPrototype(ResourceHandle* handle, int unk1, nint unk2, bool unk3, nint unk4);
|
||||||
|
|
||||||
|
private delegate byte TexResourceHandleVf32Prototype(TextureResourceHandle* handle, SeFileDescriptor* descriptor, byte unk2);
|
||||||
|
|
||||||
|
[Signature("40 53 55 41 54 41 55 41 56 41 57 48 81 EC ?? ?? ?? ?? 48 8B D9", DetourName = nameof(Vf32Detour))]
|
||||||
|
private readonly Hook<TexResourceHandleVf32Prototype> _vf32Hook = null!;
|
||||||
|
|
||||||
private delegate byte TexResourceHandleVf32Prototype(ResourceHandle* handle, nint unk1, byte unk2);
|
private byte Vf32Detour(TextureResourceHandle* handle, SeFileDescriptor* descriptor, byte unk2)
|
||||||
|
{
|
||||||
//[Signature("40 53 55 41 54 41 55 41 56 41 57 48 81 EC ?? ?? ?? ?? 48 8B D9", DetourName = nameof(Vf32Detour))]
|
//if (handle->Handle.GamePath(out var path) && path.IsRooted())
|
||||||
//private readonly Hook<TexResourceHandleVf32Prototype> _vf32Hook = null!;
|
//{
|
||||||
//
|
// Penumbra.Log.Information($"Replacing {descriptor->FileMode} with {FileMode.LoadSqPackResource} in VF32 for {path}.");
|
||||||
//private byte Vf32Detour(ResourceHandle* handle, nint unk1, byte unk2)
|
// descriptor->FileMode = FileMode.LoadSqPackResource;
|
||||||
//{
|
//}
|
||||||
// var ret = _vf32Hook.Original(handle, unk1, unk2);
|
|
||||||
// return _loadTexFileLocal()
|
var ret = _vf32Hook.Original(handle, descriptor, unk2);
|
||||||
//}
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
//[Signature(Sigs.LoadTexFileExtern, DetourName = nameof(LoadTexFileExternDetour))]
|
//[Signature(Sigs.LoadTexFileExtern, DetourName = nameof(LoadTexFileExternDetour))]
|
||||||
//private readonly Hook<LoadTexFileExternPrototype> _loadTexFileExternHook = null!;
|
//private readonly Hook<LoadTexFileExternPrototype> _loadTexFileExternHook = null!;
|
||||||
|
|
||||||
/// <summary> We hook the extern functions to just return the local one if given the custom flag as last argument. </summary>
|
/// <summary> We hook the extern functions to just return the local one if given the custom flag as last argument. </summary>
|
||||||
//private byte LoadTexFileExternDetour(ResourceHandle* resourceHandle, int unk1, nint unk2, bool unk3, nint ptr)
|
//private byte LoadTexFileExternDetour(ResourceHandle* resourceHandle, int unk1, nint unk2, bool unk3, nint ptr)
|
||||||
// => ptr.Equals(CustomFileFlag)
|
// => ptr.Equals(CustomFileFlag)
|
||||||
// ? _loadTexFileLocal.Invoke(resourceHandle, unk1, unk2, unk3)
|
// ? _loadTexFileLocal.Invoke(resourceHandle, unk1, unk2, unk3)
|
||||||
// : _loadTexFileExternHook.Original(resourceHandle, unk1, unk2, unk3, ptr);
|
// : _loadTexFileExternHook.Original(resourceHandle, unk1, unk2, unk3, ptr);
|
||||||
|
|
||||||
public delegate byte LoadMdlFileExternPrototype(ResourceHandle* handle, nint unk1, bool unk2, nint unk3);
|
public delegate byte LoadMdlFileExternPrototype(ResourceHandle* handle, nint unk1, bool unk2, nint unk3);
|
||||||
|
|
||||||
|
|
||||||
[Signature(Sigs.LoadMdlFileExtern, DetourName = nameof(LoadMdlFileExternDetour))]
|
[Signature(Sigs.LoadMdlFileExtern, DetourName = nameof(LoadMdlFileExternDetour))]
|
||||||
private readonly Hook<LoadMdlFileExternPrototype> _loadMdlFileExternHook = null!;
|
private readonly Hook<LoadMdlFileExternPrototype> _loadMdlFileExternHook = null!;
|
||||||
|
|
||||||
/// <summary> We hook the extern functions to just return the local one if given the custom flag as last argument. </summary>
|
/// <summary> We hook the extern functions to just return the local one if given the custom flag as last argument. </summary>
|
||||||
private byte LoadMdlFileExternDetour(ResourceHandle* resourceHandle, nint unk1, bool unk2, nint ptr)
|
private byte LoadMdlFileExternDetour(ResourceHandle* resourceHandle, nint unk1, bool unk2, nint ptr)
|
||||||
=> ptr.Equals(CustomFileFlag)
|
=> ptr.Equals(CustomFileFlag)
|
||||||
|
|
@ -11,7 +11,7 @@ public sealed unsafe class ApricotResourceLoad : FastHook<ApricotResourceLoad.De
|
||||||
public ApricotResourceLoad(HookManager hooks, GameState gameState)
|
public ApricotResourceLoad(HookManager hooks, GameState gameState)
|
||||||
{
|
{
|
||||||
_gameState = gameState;
|
_gameState = gameState;
|
||||||
Task = hooks.CreateHook<Delegate>("Load Apricot Resource", Sigs.ApricotResourceLoad, Detour, true);
|
Task = hooks.CreateHook<Delegate>("Load Apricot Resource", Sigs.ApricotResourceLoad, Detour, HookSettings.ResourceHooks);
|
||||||
}
|
}
|
||||||
|
|
||||||
public delegate byte Delegate(ResourceHandle* handle, nint unk1, byte unk2);
|
public delegate byte Delegate(ResourceHandle* handle, nint unk1, byte unk2);
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ public sealed unsafe class LoadMtrlShpk : FastHook<LoadMtrlShpk.Delegate>
|
||||||
{
|
{
|
||||||
_gameState = gameState;
|
_gameState = gameState;
|
||||||
_communicator = communicator;
|
_communicator = communicator;
|
||||||
Task = hooks.CreateHook<Delegate>("Load Material Shaders", Sigs.LoadMtrlShpk, Detour, true);
|
Task = hooks.CreateHook<Delegate>("Load Material Shaders", Sigs.LoadMtrlShpk, Detour, HookSettings.ResourceHooks);
|
||||||
}
|
}
|
||||||
|
|
||||||
public delegate byte Delegate(MaterialResourceHandle* mtrlResourceHandle);
|
public delegate byte Delegate(MaterialResourceHandle* mtrlResourceHandle);
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ public sealed unsafe class LoadMtrlTex : FastHook<LoadMtrlTex.Delegate>
|
||||||
public LoadMtrlTex(HookManager hooks, GameState gameState)
|
public LoadMtrlTex(HookManager hooks, GameState gameState)
|
||||||
{
|
{
|
||||||
_gameState = gameState;
|
_gameState = gameState;
|
||||||
Task = hooks.CreateHook<Delegate>("Load Material Textures", Sigs.LoadMtrlTex, Detour, true);
|
Task = hooks.CreateHook<Delegate>("Load Material Textures", Sigs.LoadMtrlTex, Detour, HookSettings.ResourceHooks);
|
||||||
}
|
}
|
||||||
|
|
||||||
public delegate byte Delegate(MaterialResourceHandle* mtrlResourceHandle);
|
public delegate byte Delegate(MaterialResourceHandle* mtrlResourceHandle);
|
||||||
|
|
|
||||||
|
|
@ -59,7 +59,8 @@ public sealed unsafe class ResolvePathHooksBase : IDisposable
|
||||||
_resolveVfxPathHook = Create<VfxResolveDelegate>( $"{name}.{nameof(ResolveVfx)}", hooks, vTable[93], type, ResolveVfx, ResolveVfxHuman);
|
_resolveVfxPathHook = Create<VfxResolveDelegate>( $"{name}.{nameof(ResolveVfx)}", hooks, vTable[93], type, ResolveVfx, ResolveVfxHuman);
|
||||||
_resolveEidPathHook = Create<SingleResolveDelegate>( $"{name}.{nameof(ResolveEid)}", hooks, vTable[94], ResolveEid);
|
_resolveEidPathHook = Create<SingleResolveDelegate>( $"{name}.{nameof(ResolveEid)}", hooks, vTable[94], ResolveEid);
|
||||||
// @formatter:on
|
// @formatter:on
|
||||||
Enable();
|
if (HookSettings.ResourceHooks)
|
||||||
|
Enable();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Enable()
|
public void Enable()
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ public sealed unsafe class ResourceHandleDestructor : EventWrapperPtr<ResourceHa
|
||||||
|
|
||||||
public ResourceHandleDestructor(HookManager hooks)
|
public ResourceHandleDestructor(HookManager hooks)
|
||||||
: base("Destroy ResourceHandle")
|
: base("Destroy ResourceHandle")
|
||||||
=> _task = hooks.CreateHook<Delegate>(Name, Sigs.ResourceHandleDestructor, Detour, true);
|
=> _task = hooks.CreateHook<Delegate>(Name, Sigs.ResourceHandleDestructor, Detour, HookSettings.ResourceHooks);
|
||||||
|
|
||||||
private readonly Task<Hook<Delegate>> _task;
|
private readonly Task<Hook<Delegate>> _task;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,12 +4,12 @@ using OtterGui.Services;
|
||||||
using Penumbra.Collections;
|
using Penumbra.Collections;
|
||||||
using Penumbra.Api.Enums;
|
using Penumbra.Api.Enums;
|
||||||
using Penumbra.GameData.Structs;
|
using Penumbra.GameData.Structs;
|
||||||
using Penumbra.Interop.ResourceLoading;
|
|
||||||
using Penumbra.Interop.Services;
|
using Penumbra.Interop.Services;
|
||||||
using Penumbra.Services;
|
using Penumbra.Services;
|
||||||
using Penumbra.String.Classes;
|
using Penumbra.String.Classes;
|
||||||
using CharacterUtility = Penumbra.Interop.Services.CharacterUtility;
|
using CharacterUtility = Penumbra.Interop.Services.CharacterUtility;
|
||||||
using Penumbra.Interop.Hooks.Objects;
|
using Penumbra.Interop.Hooks.Objects;
|
||||||
|
using Penumbra.Interop.Hooks.ResourceLoading;
|
||||||
|
|
||||||
namespace Penumbra.Interop.PathResolving;
|
namespace Penumbra.Interop.PathResolving;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,8 @@ using OtterGui.Services;
|
||||||
using Penumbra.Api.Enums;
|
using Penumbra.Api.Enums;
|
||||||
using Penumbra.Collections;
|
using Penumbra.Collections;
|
||||||
using Penumbra.Collections.Manager;
|
using Penumbra.Collections.Manager;
|
||||||
|
using Penumbra.Interop.Hooks.ResourceLoading;
|
||||||
using Penumbra.Interop.Processing;
|
using Penumbra.Interop.Processing;
|
||||||
using Penumbra.Interop.ResourceLoading;
|
|
||||||
using Penumbra.String.Classes;
|
using Penumbra.String.Classes;
|
||||||
using Penumbra.Util;
|
using Penumbra.Util;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
using OtterGui.Services;
|
using OtterGui.Services;
|
||||||
using Penumbra.Api.Enums;
|
using Penumbra.Api.Enums;
|
||||||
using Penumbra.Collections;
|
using Penumbra.Collections;
|
||||||
|
using Penumbra.Interop.Hooks.ResourceLoading;
|
||||||
using Penumbra.Interop.Hooks.Resources;
|
using Penumbra.Interop.Hooks.Resources;
|
||||||
using Penumbra.Interop.ResourceLoading;
|
|
||||||
using Penumbra.Interop.Structs;
|
using Penumbra.Interop.Structs;
|
||||||
using Penumbra.String.Classes;
|
using Penumbra.String.Classes;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
using System.Collections.Frozen;
|
using System.Collections.Frozen;
|
||||||
using OtterGui.Services;
|
using OtterGui.Services;
|
||||||
using Penumbra.Api.Enums;
|
using Penumbra.Api.Enums;
|
||||||
using Penumbra.Interop.ResourceLoading;
|
using Penumbra.Interop.Hooks.ResourceLoading;
|
||||||
using Penumbra.Interop.Structs;
|
using Penumbra.Interop.Structs;
|
||||||
using Penumbra.String;
|
using Penumbra.String;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,8 +9,8 @@ using Penumbra.Collections;
|
||||||
using Penumbra.GameData.Data;
|
using Penumbra.GameData.Data;
|
||||||
using Penumbra.GameData.Enums;
|
using Penumbra.GameData.Enums;
|
||||||
using Penumbra.GameData.Structs;
|
using Penumbra.GameData.Structs;
|
||||||
|
using Penumbra.Interop.Hooks.PostProcessing;
|
||||||
using Penumbra.Interop.PathResolving;
|
using Penumbra.Interop.PathResolving;
|
||||||
using Penumbra.Interop.Services;
|
|
||||||
using Penumbra.String;
|
using Penumbra.String;
|
||||||
using Penumbra.String.Classes;
|
using Penumbra.String.Classes;
|
||||||
using Penumbra.UI;
|
using Penumbra.UI;
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ using FFXIVClientStructs.FFXIV.Client.System.Resource.Handle;
|
||||||
using Penumbra.GameData.Data;
|
using Penumbra.GameData.Data;
|
||||||
using Penumbra.GameData.Enums;
|
using Penumbra.GameData.Enums;
|
||||||
using Penumbra.GameData.Structs;
|
using Penumbra.GameData.Structs;
|
||||||
using Penumbra.Interop.Services;
|
using Penumbra.Interop.Hooks.PostProcessing;
|
||||||
using Penumbra.UI;
|
using Penumbra.UI;
|
||||||
using CustomizeData = FFXIVClientStructs.FFXIV.Client.Game.Character.CustomizeData;
|
using CustomizeData = FFXIVClientStructs.FFXIV.Client.Game.Character.CustomizeData;
|
||||||
using CustomizeIndex = Dalamud.Game.ClientState.Objects.Enums.CustomizeIndex;
|
using CustomizeIndex = Dalamud.Game.ClientState.Objects.Enums.CustomizeIndex;
|
||||||
|
|
@ -31,7 +31,8 @@ public class ResourceTree
|
||||||
public CustomizeData CustomizeData;
|
public CustomizeData CustomizeData;
|
||||||
public GenderRace RaceCode;
|
public GenderRace RaceCode;
|
||||||
|
|
||||||
public ResourceTree(string name, string anonymizedName, int gameObjectIndex, nint gameObjectAddress, nint drawObjectAddress, bool localPlayerRelated, bool playerRelated, bool networked, string collectionName, string anonymizedCollectionName)
|
public ResourceTree(string name, string anonymizedName, int gameObjectIndex, nint gameObjectAddress, nint drawObjectAddress,
|
||||||
|
bool localPlayerRelated, bool playerRelated, bool networked, string collectionName, string anonymizedCollectionName)
|
||||||
{
|
{
|
||||||
Name = name;
|
Name = name;
|
||||||
AnonymizedName = anonymizedName;
|
AnonymizedName = anonymizedName;
|
||||||
|
|
@ -61,9 +62,10 @@ public class ResourceTree
|
||||||
var human = modelType == CharacterBase.ModelType.Human ? (Human*)model : null;
|
var human = modelType == CharacterBase.ModelType.Human ? (Human*)model : null;
|
||||||
var equipment = modelType switch
|
var equipment = modelType switch
|
||||||
{
|
{
|
||||||
CharacterBase.ModelType.Human => new ReadOnlySpan<CharacterArmor>(&human->Head, 10),
|
CharacterBase.ModelType.Human => new ReadOnlySpan<CharacterArmor>(&human->Head, 10),
|
||||||
CharacterBase.ModelType.DemiHuman => new ReadOnlySpan<CharacterArmor>(Unsafe.AsPointer(ref character->DrawData.EquipmentModelIds[0]), 10),
|
CharacterBase.ModelType.DemiHuman => new ReadOnlySpan<CharacterArmor>(
|
||||||
_ => ReadOnlySpan<CharacterArmor>.Empty,
|
Unsafe.AsPointer(ref character->DrawData.EquipmentModelIds[0]), 10),
|
||||||
|
_ => ReadOnlySpan<CharacterArmor>.Empty,
|
||||||
};
|
};
|
||||||
ModelId = character->CharacterData.ModelCharaId;
|
ModelId = character->CharacterData.ModelCharaId;
|
||||||
CustomizeData = character->DrawData.CustomizeData;
|
CustomizeData = character->DrawData.CustomizeData;
|
||||||
|
|
@ -112,15 +114,17 @@ public class ResourceTree
|
||||||
{
|
{
|
||||||
if (baseSubObject->GetObjectType() != FFXIVClientStructs.FFXIV.Client.Graphics.Scene.ObjectType.CharacterBase)
|
if (baseSubObject->GetObjectType() != FFXIVClientStructs.FFXIV.Client.Graphics.Scene.ObjectType.CharacterBase)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
var subObject = (CharacterBase*)baseSubObject;
|
var subObject = (CharacterBase*)baseSubObject;
|
||||||
|
|
||||||
if (subObject->GetModelType() != CharacterBase.ModelType.Weapon)
|
if (subObject->GetModelType() != CharacterBase.ModelType.Weapon)
|
||||||
continue;
|
continue;
|
||||||
var weapon = (Weapon*)subObject;
|
|
||||||
|
var weapon = (Weapon*)subObject;
|
||||||
|
|
||||||
// This way to tell apart MainHand and OffHand is not always accurate, but seems good enough for what we're doing with it.
|
// This way to tell apart MainHand and OffHand is not always accurate, but seems good enough for what we're doing with it.
|
||||||
var slot = weaponIndex > 0 ? EquipSlot.OffHand : EquipSlot.MainHand;
|
var slot = weaponIndex > 0 ? EquipSlot.OffHand : EquipSlot.MainHand;
|
||||||
var equipment = new CharacterArmor(weapon->ModelSetId, (byte)weapon->Variant, (byte)weapon->ModelUnknown);
|
var equipment = new CharacterArmor(weapon->ModelSetId, (byte)weapon->Variant, new StainIds(weapon->Stain1, weapon->Stain2));
|
||||||
var weaponType = weapon->SecondaryId;
|
var weaponType = weapon->SecondaryId;
|
||||||
|
|
||||||
var genericContext = globalContext.CreateContext(subObject, 0xFFFFFFFFu, slot, equipment, weaponType);
|
var genericContext = globalContext.CreateContext(subObject, 0xFFFFFFFFu, slot, equipment, weaponType);
|
||||||
|
|
@ -152,6 +156,7 @@ public class ResourceTree
|
||||||
|
|
||||||
++weaponIndex;
|
++weaponIndex;
|
||||||
}
|
}
|
||||||
|
|
||||||
Nodes.InsertRange(0, weaponNodes);
|
Nodes.InsertRange(0, weaponNodes);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -167,10 +172,11 @@ public class ResourceTree
|
||||||
{
|
{
|
||||||
if (globalContext.WithUiData)
|
if (globalContext.WithUiData)
|
||||||
{
|
{
|
||||||
pbdNode = pbdNode.Clone();
|
pbdNode = pbdNode.Clone();
|
||||||
pbdNode.FallbackName = "Racial Deformer";
|
pbdNode.FallbackName = "Racial Deformer";
|
||||||
pbdNode.Icon = ChangedItemDrawer.ChangedItemIcon.Customization;
|
pbdNode.Icon = ChangedItemDrawer.ChangedItemIcon.Customization;
|
||||||
}
|
}
|
||||||
|
|
||||||
Nodes.Add(pbdNode);
|
Nodes.Add(pbdNode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -184,10 +190,11 @@ public class ResourceTree
|
||||||
{
|
{
|
||||||
if (globalContext.WithUiData)
|
if (globalContext.WithUiData)
|
||||||
{
|
{
|
||||||
decalNode = decalNode.Clone();
|
decalNode = decalNode.Clone();
|
||||||
decalNode.FallbackName = "Face Decal";
|
decalNode.FallbackName = "Face Decal";
|
||||||
decalNode.Icon = ChangedItemDrawer.ChangedItemIcon.Customization;
|
decalNode.Icon = ChangedItemDrawer.ChangedItemIcon.Customization;
|
||||||
}
|
}
|
||||||
|
|
||||||
Nodes.Add(decalNode);
|
Nodes.Add(decalNode);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -200,10 +207,11 @@ public class ResourceTree
|
||||||
{
|
{
|
||||||
if (globalContext.WithUiData)
|
if (globalContext.WithUiData)
|
||||||
{
|
{
|
||||||
legacyDecalNode = legacyDecalNode.Clone();
|
legacyDecalNode = legacyDecalNode.Clone();
|
||||||
legacyDecalNode.FallbackName = "Legacy Body Decal";
|
legacyDecalNode.FallbackName = "Legacy Body Decal";
|
||||||
legacyDecalNode.Icon = ChangedItemDrawer.ChangedItemIcon.Customization;
|
legacyDecalNode.Icon = ChangedItemDrawer.ChangedItemIcon.Customization;
|
||||||
}
|
}
|
||||||
|
|
||||||
Nodes.Add(legacyDecalNode);
|
Nodes.Add(legacyDecalNode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
using FFXIVClientStructs.FFXIV.Client.System.Resource;
|
using FFXIVClientStructs.FFXIV.Client.System.Resource;
|
||||||
using Penumbra.Api.Enums;
|
using Penumbra.Api.Enums;
|
||||||
using Penumbra.Collections;
|
using Penumbra.Collections;
|
||||||
using Penumbra.Interop.ResourceLoading;
|
using Penumbra.Interop.Hooks.ResourceLoading;
|
||||||
using Penumbra.String.Classes;
|
using Penumbra.String.Classes;
|
||||||
|
|
||||||
namespace Penumbra.Interop.Services;
|
namespace Penumbra.Interop.Services;
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,6 @@ using Penumbra.Api;
|
||||||
using Penumbra.Api.Enums;
|
using Penumbra.Api.Enums;
|
||||||
using Penumbra.Collections;
|
using Penumbra.Collections;
|
||||||
using Penumbra.Collections.Cache;
|
using Penumbra.Collections.Cache;
|
||||||
using Penumbra.Interop.ResourceLoading;
|
|
||||||
using Penumbra.Interop.PathResolving;
|
using Penumbra.Interop.PathResolving;
|
||||||
using Penumbra.Services;
|
using Penumbra.Services;
|
||||||
using Penumbra.Interop.Services;
|
using Penumbra.Interop.Services;
|
||||||
|
|
@ -22,6 +21,7 @@ using Penumbra.GameData.Enums;
|
||||||
using Penumbra.UI;
|
using Penumbra.UI;
|
||||||
using ResidentResourceManager = Penumbra.Interop.Services.ResidentResourceManager;
|
using ResidentResourceManager = Penumbra.Interop.Services.ResidentResourceManager;
|
||||||
using System.Xml.Linq;
|
using System.Xml.Linq;
|
||||||
|
using Penumbra.Interop.Hooks.ResourceLoading;
|
||||||
|
|
||||||
namespace Penumbra;
|
namespace Penumbra;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -37,7 +37,6 @@
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<DalamudLibPath>$(AppData)\XIVLauncher\addon\Hooks\dev\</DalamudLibPath>
|
<DalamudLibPath>$(AppData)\XIVLauncher\addon\Hooks\dev\</DalamudLibPath>
|
||||||
<DalamudLibPath>H:\Projects\FFPlugins\Dalamud\bin\Release\</DalamudLibPath>
|
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|
|
||||||
|
|
@ -7,8 +7,8 @@ using Penumbra.Communication;
|
||||||
using Penumbra.CrashHandler;
|
using Penumbra.CrashHandler;
|
||||||
using Penumbra.CrashHandler.Buffers;
|
using Penumbra.CrashHandler.Buffers;
|
||||||
using Penumbra.GameData.Actors;
|
using Penumbra.GameData.Actors;
|
||||||
|
using Penumbra.Interop.Hooks.ResourceLoading;
|
||||||
using Penumbra.Interop.PathResolving;
|
using Penumbra.Interop.PathResolving;
|
||||||
using Penumbra.Interop.ResourceLoading;
|
|
||||||
using Penumbra.Interop.Structs;
|
using Penumbra.Interop.Structs;
|
||||||
using Penumbra.String;
|
using Penumbra.String;
|
||||||
using Penumbra.String.Classes;
|
using Penumbra.String.Classes;
|
||||||
|
|
|
||||||
|
|
@ -8,8 +8,8 @@ using Penumbra.Api.Enums;
|
||||||
using Penumbra.Collections;
|
using Penumbra.Collections;
|
||||||
using Penumbra.GameData.Actors;
|
using Penumbra.GameData.Actors;
|
||||||
using Penumbra.GameData.Enums;
|
using Penumbra.GameData.Enums;
|
||||||
|
using Penumbra.Interop.Hooks.ResourceLoading;
|
||||||
using Penumbra.Interop.Hooks.Resources;
|
using Penumbra.Interop.Hooks.Resources;
|
||||||
using Penumbra.Interop.ResourceLoading;
|
|
||||||
using Penumbra.Interop.Structs;
|
using Penumbra.Interop.Structs;
|
||||||
using Penumbra.Services;
|
using Penumbra.Services;
|
||||||
using Penumbra.String;
|
using Penumbra.String;
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,6 @@ using Penumbra.GameData.Interop;
|
||||||
using Penumbra.Import.Structs;
|
using Penumbra.Import.Structs;
|
||||||
using Penumbra.Import.Textures;
|
using Penumbra.Import.Textures;
|
||||||
using Penumbra.Interop.PathResolving;
|
using Penumbra.Interop.PathResolving;
|
||||||
using Penumbra.Interop.ResourceLoading;
|
|
||||||
using Penumbra.Interop.Services;
|
using Penumbra.Interop.Services;
|
||||||
using Penumbra.Interop.Structs;
|
using Penumbra.Interop.Structs;
|
||||||
using Penumbra.Mods;
|
using Penumbra.Mods;
|
||||||
|
|
@ -40,6 +39,8 @@ using CharacterUtility = Penumbra.Interop.Services.CharacterUtility;
|
||||||
using ResidentResourceManager = Penumbra.Interop.Services.ResidentResourceManager;
|
using ResidentResourceManager = Penumbra.Interop.Services.ResidentResourceManager;
|
||||||
using ImGuiClip = OtterGui.ImGuiClip;
|
using ImGuiClip = OtterGui.ImGuiClip;
|
||||||
using Penumbra.Api.IpcTester;
|
using Penumbra.Api.IpcTester;
|
||||||
|
using Penumbra.Interop.Hooks.PostProcessing;
|
||||||
|
using Penumbra.Interop.Hooks.ResourceLoading;
|
||||||
|
|
||||||
namespace Penumbra.UI.Tabs.Debug;
|
namespace Penumbra.UI.Tabs.Debug;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ using OtterGui;
|
||||||
using OtterGui.Raii;
|
using OtterGui.Raii;
|
||||||
using OtterGui.Services;
|
using OtterGui.Services;
|
||||||
using OtterGui.Widgets;
|
using OtterGui.Widgets;
|
||||||
using Penumbra.Interop.ResourceLoading;
|
using Penumbra.Interop.Hooks.ResourceLoading;
|
||||||
using Penumbra.String.Classes;
|
using Penumbra.String.Classes;
|
||||||
|
|
||||||
namespace Penumbra.UI.Tabs;
|
namespace Penumbra.UI.Tabs;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue