Small changes.

This commit is contained in:
Ottermandias 2024-03-28 17:37:17 +01:00
parent 3066bf84d5
commit 1ba5011bfa
9 changed files with 120 additions and 95 deletions

View file

@ -8,7 +8,6 @@ using Penumbra.Mods.Editor;
using Penumbra.String.Classes; using Penumbra.String.Classes;
using Penumbra.Mods.Manager; using Penumbra.Mods.Manager;
using Penumbra.Mods.Subclasses; using Penumbra.Mods.Subclasses;
using Penumbra.Interop.SafeHandles;
namespace Penumbra.Collections.Cache; namespace Penumbra.Collections.Cache;
@ -19,16 +18,16 @@ public record ModConflicts(IMod Mod2, List<object> Conflicts, bool HasPriority,
/// The Cache contains all required temporary data to use a collection. /// The Cache contains all required temporary data to use a collection.
/// It will only be setup if a collection gets activated in any way. /// It will only be setup if a collection gets activated in any way.
/// </summary> /// </summary>
public class CollectionCache : IDisposable public sealed class CollectionCache : IDisposable
{ {
private readonly CollectionCacheManager _manager; private readonly CollectionCacheManager _manager;
private readonly ModCollection _collection; private readonly ModCollection _collection;
public readonly CollectionModData ModData = new(); public readonly CollectionModData ModData = new();
private readonly SortedList<string, (SingleArray<IMod>, object?)> _changedItems = []; private readonly SortedList<string, (SingleArray<IMod>, object?)> _changedItems = [];
public readonly ConcurrentDictionary<Utf8GamePath, ModPath> ResolvedFiles = new(); public readonly ConcurrentDictionary<Utf8GamePath, ModPath> ResolvedFiles = new();
public readonly ConcurrentDictionary<Utf8GamePath, SafeResourceHandle> LoadedResources = new(); public readonly CustomResourceCache CustomResources;
public readonly MetaCache Meta; public readonly MetaCache Meta;
public readonly Dictionary<IMod, SingleArray<ModConflicts>> ConflictDict = []; public readonly Dictionary<IMod, SingleArray<ModConflicts>> ConflictDict = [];
public int Calculating = -1; public int Calculating = -1;
@ -39,7 +38,7 @@ public class CollectionCache : IDisposable
=> ConflictDict.Values; => ConflictDict.Values;
public SingleArray<ModConflicts> Conflicts(IMod mod) public SingleArray<ModConflicts> Conflicts(IMod mod)
=> ConflictDict.TryGetValue(mod, out SingleArray<ModConflicts> c) ? c : new SingleArray<ModConflicts>(); => ConflictDict.TryGetValue(mod, out var c) ? c : new SingleArray<ModConflicts>();
private int _changedItemsSaveCounter = -1; private int _changedItemsSaveCounter = -1;
@ -56,16 +55,21 @@ public class CollectionCache : IDisposable
// The cache reacts through events on its collection changing. // The cache reacts through events on its collection changing.
public CollectionCache(CollectionCacheManager manager, ModCollection collection) public CollectionCache(CollectionCacheManager manager, ModCollection collection)
{ {
_manager = manager; _manager = manager;
_collection = collection; _collection = collection;
Meta = new MetaCache(manager.MetaFileManager, _collection); Meta = new MetaCache(manager.MetaFileManager, _collection);
CustomResources = new CustomResourceCache(manager.ResourceLoader);
} }
public void Dispose() public void Dispose()
=> Meta.Dispose(); {
Meta.Dispose();
CustomResources.Dispose();
GC.SuppressFinalize(this);
}
~CollectionCache() ~CollectionCache()
=> Meta.Dispose(); => Dispose();
// Resolve a given game path according to this collection. // Resolve a given game path according to this collection.
public FullPath? ResolvePath(Utf8GamePath gameResourcePath) public FullPath? ResolvePath(Utf8GamePath gameResourcePath)
@ -74,7 +78,7 @@ public class CollectionCache : IDisposable
return null; return null;
if (candidate.Path.InternalName.Length > Utf8GamePath.MaxGamePathLength if (candidate.Path.InternalName.Length > Utf8GamePath.MaxGamePathLength
|| candidate.Path.IsRooted && !candidate.Path.Exists) || candidate.Path is { IsRooted: true, Exists: false })
return null; return null;
return candidate.Path; return candidate.Path;
@ -102,7 +106,7 @@ public class CollectionCache : IDisposable
public HashSet<Utf8GamePath>[] ReverseResolvePaths(IReadOnlyCollection<string> fullPaths) public HashSet<Utf8GamePath>[] ReverseResolvePaths(IReadOnlyCollection<string> fullPaths)
{ {
if (fullPaths.Count == 0) if (fullPaths.Count == 0)
return Array.Empty<HashSet<Utf8GamePath>>(); return [];
var ret = new HashSet<Utf8GamePath>[fullPaths.Count]; var ret = new HashSet<Utf8GamePath>[fullPaths.Count];
var dict = new Dictionary<FullPath, int>(fullPaths.Count); var dict = new Dictionary<FullPath, int>(fullPaths.Count);
@ -110,8 +114,8 @@ public class CollectionCache : IDisposable
{ {
dict[new FullPath(path)] = idx; dict[new FullPath(path)] = idx;
ret[idx] = !Path.IsPathRooted(path) && Utf8GamePath.FromString(path, out var utf8) ret[idx] = !Path.IsPathRooted(path) && Utf8GamePath.FromString(path, out var utf8)
? new HashSet<Utf8GamePath> { utf8 } ? [utf8]
: new HashSet<Utf8GamePath>(); : [];
} }
foreach (var (game, full) in ResolvedFiles) foreach (var (game, full) in ResolvedFiles)
@ -138,13 +142,6 @@ public class CollectionCache : IDisposable
public void RemoveMod(IMod mod, bool addMetaChanges) public void RemoveMod(IMod mod, bool addMetaChanges)
=> _manager.AddChange(ChangeData.ModRemoval(this, mod, addMetaChanges)); => _manager.AddChange(ChangeData.ModRemoval(this, mod, addMetaChanges));
/// <summary> Invalidates caches subsequently to a resolved file being modified. </summary>
private void InvalidateResolvedFile(Utf8GamePath path)
{
if (LoadedResources.Remove(path, out var handle))
handle.Dispose();
}
/// <summary> Force a file to be resolved to a specific path regardless of conflicts. </summary> /// <summary> Force a file to be resolved to a specific path regardless of conflicts. </summary>
internal void ForceFileSync(Utf8GamePath path, FullPath fullPath) internal void ForceFileSync(Utf8GamePath path, FullPath fullPath)
{ {
@ -157,20 +154,20 @@ public class CollectionCache : IDisposable
if (fullPath.FullName.Length > 0) if (fullPath.FullName.Length > 0)
{ {
ResolvedFiles.TryAdd(path, new ModPath(Mod.ForcedFiles, fullPath)); ResolvedFiles.TryAdd(path, new ModPath(Mod.ForcedFiles, fullPath));
InvalidateResolvedFile(path); CustomResources.Invalidate(path);
InvokeResolvedFileChange(_collection, ResolvedFileChanged.Type.Replaced, path, fullPath, modPath.Path, InvokeResolvedFileChange(_collection, ResolvedFileChanged.Type.Replaced, path, fullPath, modPath.Path,
Mod.ForcedFiles); Mod.ForcedFiles);
} }
else else
{ {
InvalidateResolvedFile(path); CustomResources.Invalidate(path);
InvokeResolvedFileChange(_collection, ResolvedFileChanged.Type.Removed, path, FullPath.Empty, modPath.Path, null); InvokeResolvedFileChange(_collection, ResolvedFileChanged.Type.Removed, path, FullPath.Empty, modPath.Path, null);
} }
} }
else if (fullPath.FullName.Length > 0) else if (fullPath.FullName.Length > 0)
{ {
ResolvedFiles.TryAdd(path, new ModPath(Mod.ForcedFiles, fullPath)); ResolvedFiles.TryAdd(path, new ModPath(Mod.ForcedFiles, fullPath));
InvalidateResolvedFile(path); CustomResources.Invalidate(path);
InvokeResolvedFileChange(_collection, ResolvedFileChanged.Type.Added, path, fullPath, FullPath.Empty, Mod.ForcedFiles); InvokeResolvedFileChange(_collection, ResolvedFileChanged.Type.Added, path, fullPath, FullPath.Empty, Mod.ForcedFiles);
} }
} }
@ -193,7 +190,7 @@ public class CollectionCache : IDisposable
{ {
if (ResolvedFiles.Remove(path, out var mp)) if (ResolvedFiles.Remove(path, out var mp))
{ {
InvalidateResolvedFile(path); CustomResources.Invalidate(path);
if (mp.Mod != mod) if (mp.Mod != mod)
Penumbra.Log.Warning( Penumbra.Log.Warning(
$"Invalid mod state, removing {mod.Name} and associated file {path} returned current mod {mp.Mod.Name}."); $"Invalid mod state, removing {mod.Name} and associated file {path} returned current mod {mp.Mod.Name}.");
@ -308,7 +305,7 @@ public class CollectionCache : IDisposable
if (ResolvedFiles.TryAdd(path, new ModPath(mod, file))) if (ResolvedFiles.TryAdd(path, new ModPath(mod, file)))
{ {
ModData.AddPath(mod, path); ModData.AddPath(mod, path);
InvalidateResolvedFile(path); CustomResources.Invalidate(path);
InvokeResolvedFileChange(_collection, ResolvedFileChanged.Type.Added, path, file, FullPath.Empty, mod); InvokeResolvedFileChange(_collection, ResolvedFileChanged.Type.Added, path, file, FullPath.Empty, mod);
return; return;
} }
@ -323,14 +320,14 @@ public class CollectionCache : IDisposable
ModData.RemovePath(modPath.Mod, path); ModData.RemovePath(modPath.Mod, path);
ResolvedFiles[path] = new ModPath(mod, file); ResolvedFiles[path] = new ModPath(mod, file);
ModData.AddPath(mod, path); ModData.AddPath(mod, path);
InvalidateResolvedFile(path); CustomResources.Invalidate(path);
InvokeResolvedFileChange(_collection, ResolvedFileChanged.Type.Replaced, path, file, modPath.Path, mod); InvokeResolvedFileChange(_collection, ResolvedFileChanged.Type.Replaced, path, file, modPath.Path, mod);
} }
} }
catch (Exception ex) catch (Exception ex)
{ {
Penumbra.Log.Error( Penumbra.Log.Error(
$"[{Thread.CurrentThread.ManagedThreadId}] Error adding redirection {file} -> {path} for mod {mod.Name} to collection cache {AnonymizedName}:\n{ex}"); $"[{Environment.CurrentManagedThreadId}] Error adding redirection {file} -> {path} for mod {mod.Name} to collection cache {AnonymizedName}:\n{ex}");
} }
} }

View file

@ -4,6 +4,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.Meta; using Penumbra.Meta;
using Penumbra.Mods; using Penumbra.Mods;
using Penumbra.Mods.Manager; using Penumbra.Mods.Manager;
@ -21,8 +22,8 @@ public class CollectionCacheManager : IDisposable
private readonly CollectionStorage _storage; private readonly CollectionStorage _storage;
private readonly ActiveCollections _active; private readonly ActiveCollections _active;
internal readonly ResolvedFileChanged ResolvedFileChanged; internal readonly ResolvedFileChanged ResolvedFileChanged;
internal readonly MetaFileManager MetaFileManager;
internal readonly MetaFileManager MetaFileManager; internal readonly ResourceLoader ResourceLoader;
private readonly ConcurrentQueue<CollectionCache.ChangeData> _changeQueue = new(); private readonly ConcurrentQueue<CollectionCache.ChangeData> _changeQueue = new();
@ -35,7 +36,7 @@ public class CollectionCacheManager : IDisposable
=> _storage.Where(c => c.HasCache); => _storage.Where(c => c.HasCache);
public CollectionCacheManager(FrameworkManager framework, CommunicatorService communicator, TempModManager tempMods, ModStorage modStorage, public CollectionCacheManager(FrameworkManager framework, CommunicatorService communicator, TempModManager tempMods, ModStorage modStorage,
MetaFileManager metaFileManager, ActiveCollections active, CollectionStorage storage) MetaFileManager metaFileManager, ActiveCollections active, CollectionStorage storage, ResourceLoader resourceLoader)
{ {
_framework = framework; _framework = framework;
_communicator = communicator; _communicator = communicator;
@ -44,6 +45,7 @@ public class CollectionCacheManager : IDisposable
MetaFileManager = metaFileManager; MetaFileManager = metaFileManager;
_active = active; _active = active;
_storage = storage; _storage = storage;
ResourceLoader = resourceLoader;
ResolvedFileChanged = _communicator.ResolvedFileChanged; ResolvedFileChanged = _communicator.ResolvedFileChanged;
if (!_active.Individuals.IsLoaded) if (!_active.Individuals.IsLoaded)

View file

@ -0,0 +1,49 @@
using FFXIVClientStructs.FFXIV.Client.System.Resource;
using Penumbra.Api.Enums;
using Penumbra.Interop.ResourceLoading;
using Penumbra.Interop.SafeHandles;
using Penumbra.String.Classes;
namespace Penumbra.Collections.Cache;
/// <summary> A cache for resources owned by a collection. </summary>
public sealed class CustomResourceCache(ResourceLoader loader)
: ConcurrentDictionary<Utf8GamePath, SafeResourceHandle>, IDisposable
{
/// <summary> Invalidate an existing resource by clearing it from the cache and disposing it. </summary>
public void Invalidate(Utf8GamePath path)
{
if (TryRemove(path, out var handle))
handle.Dispose();
}
public void Dispose()
{
foreach (var handle in Values)
handle.Dispose();
Clear();
}
/// <summary> Get the requested resource either from the cached resource, or load a new one if it does not exist. </summary>
public SafeResourceHandle Get(ResourceCategory category, ResourceType type, Utf8GamePath path, ResolveData data)
{
if (TryGetClonedValue(path, out var handle))
return handle;
handle = loader.LoadResolvedSafeResource(category, type, path.Path, data);
var clone = handle.Clone();
if (!TryAdd(path, clone))
clone.Dispose();
return handle;
}
/// <summary> Get a cloned cached resource if it exists. </summary>
private bool TryGetClonedValue(Utf8GamePath path, [NotNullWhen(true)] out SafeResourceHandle? handle)
{
if (!TryGetValue(path, out handle))
return false;
handle = handle.Clone();
return true;
}
}

View file

@ -49,24 +49,6 @@ public unsafe class ResourceLoader : IDisposable
return ret; return ret;
} }
public SafeResourceHandle LoadCacheableSafeResource(ResourceCategory category, ResourceType type, Utf8GamePath path, ResolveData resolveData)
{
var cache = resolveData.ModCollection._cache;
if (cache == null)
return LoadResolvedSafeResource(category, type, path.Path, resolveData);
if (cache.LoadedResources.TryGetValue(path, out var cached))
return cached.Clone();
var ret = LoadResolvedSafeResource(category, type, path.Path, resolveData);
cached = ret.Clone();
if (!cache.LoadedResources.TryAdd(path, cached))
cached.Dispose();
return ret;
}
/// <summary> The function to use to resolve a given path. </summary> /// <summary> The function to use to resolve a given path. </summary>
public Func<Utf8GamePath, ResourceCategory, ResourceType, (FullPath?, ResolveData)> ResolvePath = null!; public Func<Utf8GamePath, ResourceCategory, ResourceType, (FullPath?, ResolveData)> ResolvePath = null!;

View file

@ -156,7 +156,7 @@ public class ResourceTree
var genericContext = globalContext.CreateContext(&human->CharacterBase); var genericContext = globalContext.CreateContext(&human->CharacterBase);
var cache = globalContext.Collection._cache; var cache = globalContext.Collection._cache;
if (cache != null && cache.LoadedResources.TryGetValue(PreBoneDeformerReplacer.PreBoneDeformerPath, out var pbdHandle)) if (cache != null && cache.CustomResources.TryGetValue(PreBoneDeformerReplacer.PreBoneDeformerPath, out var pbdHandle))
{ {
var pbdNode = genericContext.CreateNodeFromPbd(pbdHandle.ResourceHandle); var pbdNode = genericContext.CreateNodeFromPbd(pbdHandle.ResourceHandle);
if (pbdNode != null) if (pbdNode != null)

View file

@ -1,10 +1,9 @@
using Dalamud.Hooking; using Dalamud.Hooking;
using Dalamud.Plugin.Services; using Dalamud.Plugin.Services;
using Dalamud.Utility.Signatures;
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
using FFXIVClientStructs.FFXIV.Client.System.Resource; using FFXIVClientStructs.FFXIV.Client.System.Resource;
using OtterGui.Services;
using Penumbra.Api.Enums; using Penumbra.Api.Enums;
using Penumbra.GameData;
using Penumbra.Interop.PathResolving; using Penumbra.Interop.PathResolving;
using Penumbra.Interop.ResourceLoading; using Penumbra.Interop.ResourceLoading;
using Penumbra.Interop.SafeHandles; using Penumbra.Interop.SafeHandles;
@ -12,14 +11,11 @@ using Penumbra.String.Classes;
namespace Penumbra.Interop.Services; namespace Penumbra.Interop.Services;
public sealed unsafe class PreBoneDeformerReplacer : IDisposable public sealed unsafe class PreBoneDeformerReplacer : IDisposable, IRequiredService
{ {
public static readonly Utf8GamePath PreBoneDeformerPath = public static readonly Utf8GamePath PreBoneDeformerPath =
Utf8GamePath.FromSpan("chara/xls/boneDeformer/human.pbd"u8, out var p) ? p : Utf8GamePath.Empty; Utf8GamePath.FromSpan("chara/xls/boneDeformer/human.pbd"u8, out var p) ? p : Utf8GamePath.Empty;
[Signature(Sigs.HumanVTable, ScanType = ScanType.StaticAddress)]
private readonly nint* _humanVTable = null!;
// Approximate name guesses. // Approximate name guesses.
private delegate void CharacterBaseSetupScalingDelegate(CharacterBase* drawObject, uint slotIndex); private delegate void CharacterBaseSetupScalingDelegate(CharacterBase* drawObject, uint slotIndex);
private delegate void* CharacterBaseCreateDeformerDelegate(CharacterBase* drawObject, uint slotIndex); private delegate void* CharacterBaseCreateDeformerDelegate(CharacterBase* drawObject, uint slotIndex);
@ -32,15 +28,16 @@ public sealed unsafe class PreBoneDeformerReplacer : IDisposable
private readonly ResourceLoader _resourceLoader; private readonly ResourceLoader _resourceLoader;
private readonly IFramework _framework; private readonly IFramework _framework;
public PreBoneDeformerReplacer(CharacterUtility utility, CollectionResolver collectionResolver, ResourceLoader resourceLoader, IGameInteropProvider interop, IFramework framework) public PreBoneDeformerReplacer(CharacterUtility utility, CollectionResolver collectionResolver, ResourceLoader resourceLoader,
IGameInteropProvider interop, IFramework framework, CharacterBaseVTables vTables)
{ {
interop.InitializeFromAttributes(this); interop.InitializeFromAttributes(this);
_utility = utility; _utility = utility;
_collectionResolver = collectionResolver; _collectionResolver = collectionResolver;
_resourceLoader = resourceLoader; _resourceLoader = resourceLoader;
_framework = framework; _framework = framework;
_humanSetupScalingHook = interop.HookFromAddress<CharacterBaseSetupScalingDelegate>(_humanVTable[57], SetupScaling); _humanSetupScalingHook = interop.HookFromAddress<CharacterBaseSetupScalingDelegate>(vTables.HumanVTable[57], SetupScaling);
_humanCreateDeformerHook = interop.HookFromAddress<CharacterBaseCreateDeformerDelegate>(_humanVTable[91], CreateDeformer); _humanCreateDeformerHook = interop.HookFromAddress<CharacterBaseCreateDeformerDelegate>(vTables.HumanVTable[91], CreateDeformer);
_humanSetupScalingHook.Enable(); _humanSetupScalingHook.Enable();
_humanCreateDeformerHook.Enable(); _humanCreateDeformerHook.Enable();
} }
@ -54,13 +51,17 @@ public sealed unsafe class PreBoneDeformerReplacer : IDisposable
private SafeResourceHandle GetPreBoneDeformerForCharacter(CharacterBase* drawObject) private SafeResourceHandle GetPreBoneDeformerForCharacter(CharacterBase* drawObject)
{ {
var resolveData = _collectionResolver.IdentifyCollection(&drawObject->DrawObject, true); var resolveData = _collectionResolver.IdentifyCollection(&drawObject->DrawObject, true);
return _resourceLoader.LoadCacheableSafeResource(ResourceCategory.Chara, ResourceType.Pbd, PreBoneDeformerPath, resolveData); if (resolveData.ModCollection._cache is not { } cache)
return _resourceLoader.LoadResolvedSafeResource(ResourceCategory.Chara, ResourceType.Pbd, PreBoneDeformerPath.Path, resolveData);
return cache.CustomResources.Get(ResourceCategory.Chara, ResourceType.Pbd, PreBoneDeformerPath, resolveData);
} }
private void SetupScaling(CharacterBase* drawObject, uint slotIndex) private void SetupScaling(CharacterBase* drawObject, uint slotIndex)
{ {
if (!_framework.IsInFrameworkUpdateThread) if (!_framework.IsInFrameworkUpdateThread)
Penumbra.Log.Warning($"{nameof(PreBoneDeformerReplacer)}.{nameof(SetupScaling)}(0x{(nint)drawObject:X}, {slotIndex}) called out of framework thread"); Penumbra.Log.Warning(
$"{nameof(PreBoneDeformerReplacer)}.{nameof(SetupScaling)}(0x{(nint)drawObject:X}, {slotIndex}) called out of framework thread");
using var preBoneDeformer = GetPreBoneDeformerForCharacter(drawObject); using var preBoneDeformer = GetPreBoneDeformerForCharacter(drawObject);
try try
@ -78,7 +79,8 @@ public sealed unsafe class PreBoneDeformerReplacer : IDisposable
private void* CreateDeformer(CharacterBase* drawObject, uint slotIndex) private void* CreateDeformer(CharacterBase* drawObject, uint slotIndex)
{ {
if (!_framework.IsInFrameworkUpdateThread) if (!_framework.IsInFrameworkUpdateThread)
Penumbra.Log.Warning($"{nameof(PreBoneDeformerReplacer)}.{nameof(CreateDeformer)}(0x{(nint)drawObject:X}, {slotIndex}) called out of framework thread"); Penumbra.Log.Warning(
$"{nameof(PreBoneDeformerReplacer)}.{nameof(CreateDeformer)}(0x{(nint)drawObject:X}, {slotIndex}) called out of framework thread");
using var preBoneDeformer = GetPreBoneDeformerForCharacter(drawObject); using var preBoneDeformer = GetPreBoneDeformerForCharacter(drawObject);
try try

View file

@ -5,6 +5,7 @@ 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;
using OtterGui.Classes; using OtterGui.Classes;
using OtterGui.Services;
using Penumbra.Communication; using Penumbra.Communication;
using Penumbra.GameData; using Penumbra.GameData;
using Penumbra.Interop.Hooks.Resources; using Penumbra.Interop.Hooks.Resources;
@ -13,7 +14,7 @@ using CSModelRenderer = FFXIVClientStructs.FFXIV.Client.Graphics.Render.ModelRen
namespace Penumbra.Interop.Services; namespace Penumbra.Interop.Services;
public sealed unsafe class ShaderReplacementFixer : IDisposable public sealed unsafe class ShaderReplacementFixer : IDisposable, IRequiredService
{ {
public static ReadOnlySpan<byte> SkinShpkName public static ReadOnlySpan<byte> SkinShpkName
=> "skin.shpk"u8; => "skin.shpk"u8;
@ -21,11 +22,10 @@ public sealed unsafe class ShaderReplacementFixer : IDisposable
public static ReadOnlySpan<byte> CharacterGlassShpkName public static ReadOnlySpan<byte> CharacterGlassShpkName
=> "characterglass.shpk"u8; => "characterglass.shpk"u8;
[Signature(Sigs.HumanVTable, ScanType = ScanType.StaticAddress)]
private readonly nint* _humanVTable = null!;
private delegate nint CharacterBaseOnRenderMaterialDelegate(CharacterBase* drawObject, CSModelRenderer.OnRenderMaterialParams* param); private delegate nint CharacterBaseOnRenderMaterialDelegate(CharacterBase* drawObject, CSModelRenderer.OnRenderMaterialParams* param);
private delegate nint ModelRendererOnRenderMaterialDelegate(CSModelRenderer* modelRenderer, ushort* outFlags, CSModelRenderer.OnRenderModelParams* param, Material* material, uint materialIndex);
private delegate nint ModelRendererOnRenderMaterialDelegate(CSModelRenderer* modelRenderer, ushort* outFlags,
CSModelRenderer.OnRenderModelParams* param, Material* material, uint materialIndex);
private readonly Hook<CharacterBaseOnRenderMaterialDelegate> _humanOnRenderMaterialHook; private readonly Hook<CharacterBaseOnRenderMaterialDelegate> _humanOnRenderMaterialHook;
@ -59,14 +59,15 @@ public sealed unsafe class ShaderReplacementFixer : IDisposable
=> _moddedCharacterGlassShpkCount; => _moddedCharacterGlassShpkCount;
public ShaderReplacementFixer(ResourceHandleDestructor resourceHandleDestructor, CharacterUtility utility, ModelRenderer modelRenderer, public ShaderReplacementFixer(ResourceHandleDestructor resourceHandleDestructor, CharacterUtility utility, ModelRenderer modelRenderer,
CommunicatorService communicator, IGameInteropProvider interop) CommunicatorService communicator, IGameInteropProvider interop, CharacterBaseVTables vTables)
{ {
interop.InitializeFromAttributes(this); interop.InitializeFromAttributes(this);
_resourceHandleDestructor = resourceHandleDestructor; _resourceHandleDestructor = resourceHandleDestructor;
_utility = utility; _utility = utility;
_modelRenderer = modelRenderer; _modelRenderer = modelRenderer;
_communicator = communicator; _communicator = communicator;
_humanOnRenderMaterialHook = interop.HookFromAddress<CharacterBaseOnRenderMaterialDelegate>(_humanVTable[62], OnRenderHumanMaterial); _humanOnRenderMaterialHook =
interop.HookFromAddress<CharacterBaseOnRenderMaterialDelegate>(vTables.HumanVTable[62], OnRenderHumanMaterial);
_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(); _humanOnRenderMaterialHook.Enable();
@ -82,7 +83,7 @@ public sealed unsafe class ShaderReplacementFixer : IDisposable
_moddedCharacterGlassShpkMaterials.Clear(); _moddedCharacterGlassShpkMaterials.Clear();
_moddedSkinShpkMaterials.Clear(); _moddedSkinShpkMaterials.Clear();
_moddedCharacterGlassShpkCount = 0; _moddedCharacterGlassShpkCount = 0;
_moddedSkinShpkCount = 0; _moddedSkinShpkCount = 0;
} }
public (ulong Skin, ulong CharacterGlass) GetAndResetSlowPathCallDeltas() public (ulong Skin, ulong CharacterGlass) GetAndResetSlowPathCallDeltas()
@ -106,16 +107,12 @@ public sealed unsafe class ShaderReplacementFixer : IDisposable
var shpkName = mtrl->ShpkNameSpan; var shpkName = mtrl->ShpkNameSpan;
if (SkinShpkName.SequenceEqual(shpkName) && (nint)shpk != _utility.DefaultSkinShpkResource) if (SkinShpkName.SequenceEqual(shpkName) && (nint)shpk != _utility.DefaultSkinShpkResource)
{
if (_moddedSkinShpkMaterials.TryAdd(mtrlResourceHandle)) if (_moddedSkinShpkMaterials.TryAdd(mtrlResourceHandle))
Interlocked.Increment(ref _moddedSkinShpkCount); Interlocked.Increment(ref _moddedSkinShpkCount);
}
if (CharacterGlassShpkName.SequenceEqual(shpkName) && shpk != _modelRenderer.DefaultCharacterGlassShaderPackage) if (CharacterGlassShpkName.SequenceEqual(shpkName) && shpk != _modelRenderer.DefaultCharacterGlassShaderPackage)
{
if (_moddedCharacterGlassShpkMaterials.TryAdd(mtrlResourceHandle)) if (_moddedCharacterGlassShpkMaterials.TryAdd(mtrlResourceHandle))
Interlocked.Increment(ref _moddedCharacterGlassShpkCount); Interlocked.Increment(ref _moddedCharacterGlassShpkCount);
}
} }
private void OnResourceHandleDestructor(Structs.ResourceHandle* handle) private void OnResourceHandleDestructor(Structs.ResourceHandle* handle)
@ -159,9 +156,9 @@ public sealed unsafe class ShaderReplacementFixer : IDisposable
} }
} }
private nint ModelRendererOnRenderMaterialDetour(CSModelRenderer* modelRenderer, ushort* outFlags, CSModelRenderer.OnRenderModelParams* param, Material* material, uint materialIndex) private nint ModelRendererOnRenderMaterialDetour(CSModelRenderer* modelRenderer, ushort* outFlags,
CSModelRenderer.OnRenderModelParams* param, Material* material, uint materialIndex)
{ {
// If we don't have any on-screen instances of modded characterglass.shpk, we don't need the slow path at all. // If we don't have any on-screen instances of modded characterglass.shpk, we don't need the slow path at all.
if (!Enabled || _moddedCharacterGlassShpkCount == 0) if (!Enabled || _moddedCharacterGlassShpkCount == 0)
return _modelRendererOnRenderMaterialHook.Original(modelRenderer, outFlags, param, material, materialIndex); return _modelRendererOnRenderMaterialHook.Original(modelRenderer, outFlags, param, material, materialIndex);

View file

@ -77,8 +77,6 @@ public class Penumbra : IDalamudPlugin
_services.GetService<ModCacheManager>(); // Initialize because not required anywhere else. _services.GetService<ModCacheManager>(); // Initialize because not required anywhere else.
_collectionManager.Caches.CreateNecessaryCaches(); _collectionManager.Caches.CreateNecessaryCaches();
_services.GetService<PathResolver>(); _services.GetService<PathResolver>();
_services.GetService<PreBoneDeformerReplacer>();
_services.GetService<ShaderReplacementFixer>();
_services.GetService<DalamudSubstitutionProvider>(); // Initialize before Interface. _services.GetService<DalamudSubstitutionProvider>(); // Initialize before Interface.

View file

@ -131,9 +131,7 @@ public static class StaticServiceManager
=> services.AddSingleton<ResourceLoader>() => services.AddSingleton<ResourceLoader>()
.AddSingleton<ResourceWatcher>() .AddSingleton<ResourceWatcher>()
.AddSingleton<ResourceTreeFactory>() .AddSingleton<ResourceTreeFactory>()
.AddSingleton<MetaFileManager>() .AddSingleton<MetaFileManager>();
.AddSingleton<PreBoneDeformerReplacer>()
.AddSingleton<ShaderReplacementFixer>();
private static ServiceManager AddResolvers(this ServiceManager services) private static ServiceManager AddResolvers(this ServiceManager services)
=> services.AddSingleton<CollectionResolver>() => services.AddSingleton<CollectionResolver>()