From 7ab5299f7ae77c93e5f318529d1071ec3dfd4931 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Mon, 18 Nov 2024 00:28:27 +0100 Subject: [PATCH] Add SCD handling and crc cache visualization. --- Penumbra.sln | 3 + .../Hooks/ResourceLoading/ResourceLoader.cs | 8 +-- .../Hooks/ResourceLoading/TexMdlService.cs | 62 ++++++++++++++----- Penumbra/UI/Tabs/Debug/DebugTab.cs | 29 ++++++++- 4 files changed, 80 insertions(+), 22 deletions(-) diff --git a/Penumbra.sln b/Penumbra.sln index 46609f85..94a04ef3 100644 --- a/Penumbra.sln +++ b/Penumbra.sln @@ -8,7 +8,10 @@ EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{F89C9EAE-25C8-43BE-8108-5921E5A93502}" ProjectSection(SolutionItems) = preProject .editorconfig = .editorconfig + .github\workflows\build.yml = .github\workflows\build.yml + .github\workflows\release.yml = .github\workflows\release.yml repo.json = repo.json + .github\workflows\test_release.yml = .github\workflows\test_release.yml EndProjectSection EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Penumbra.GameData", "Penumbra.GameData\Penumbra.GameData.csproj", "{EE551E87-FDB3-4612-B500-DC870C07C605}" diff --git a/Penumbra/Interop/Hooks/ResourceLoading/ResourceLoader.cs b/Penumbra/Interop/Hooks/ResourceLoading/ResourceLoader.cs index bcd09b37..442bac15 100644 --- a/Penumbra/Interop/Hooks/ResourceLoading/ResourceLoader.cs +++ b/Penumbra/Interop/Hooks/ResourceLoading/ResourceLoader.cs @@ -15,18 +15,18 @@ public unsafe class ResourceLoader : IDisposable, IService { private readonly ResourceService _resources; private readonly FileReadService _fileReadService; - private readonly TexMdlService _texMdlService; + private readonly TexMdlScdService _texMdlScdService; private readonly PapHandler _papHandler; private readonly Configuration _config; private ResolveData _resolvedData = ResolveData.Invalid; public event Action? PapRequested; - public ResourceLoader(ResourceService resources, FileReadService fileReadService, TexMdlService texMdlService, Configuration config) + public ResourceLoader(ResourceService resources, FileReadService fileReadService, TexMdlScdService texMdlScdService, Configuration config) { _resources = resources; _fileReadService = fileReadService; - _texMdlService = texMdlService; + _texMdlScdService = texMdlScdService; _config = config; ResetResolvePath(); @@ -140,7 +140,7 @@ public unsafe class ResourceLoader : IDisposable, IService return; } - _texMdlService.AddCrc(type, resolvedPath); + _texMdlScdService.AddCrc(type, resolvedPath); // Replace the hash and path with the correct one for the replacement. hash = ComputeHash(resolvedPath.Value.InternalName, parameters); var oldPath = path; diff --git a/Penumbra/Interop/Hooks/ResourceLoading/TexMdlService.cs b/Penumbra/Interop/Hooks/ResourceLoading/TexMdlService.cs index d4a2dfba..9c17e0cf 100644 --- a/Penumbra/Interop/Hooks/ResourceLoading/TexMdlService.cs +++ b/Penumbra/Interop/Hooks/ResourceLoading/TexMdlService.cs @@ -10,7 +10,7 @@ using ResourceHandle = FFXIVClientStructs.FFXIV.Client.System.Resource.Handle.Re namespace Penumbra.Interop.Hooks.ResourceLoading; -public unsafe class TexMdlService : IDisposable, IRequiredService +public unsafe class TexMdlScdService : IDisposable, IRequiredService { /// /// We need to be able to obtain the requested LoD level. @@ -42,7 +42,7 @@ public unsafe class TexMdlService : IDisposable, IRequiredService private readonly LodService _lodService; - public TexMdlService(IGameInteropProvider interop) + public TexMdlScdService(IGameInteropProvider interop) { interop.InitializeFromAttributes(this); _lodService = new LodService(interop); @@ -52,6 +52,7 @@ public unsafe class TexMdlService : IDisposable, IRequiredService _loadMdlFileExternHook.Enable(); if (!HookOverrides.Instance.ResourceLoading.TexResourceHandleOnLoad) _textureOnLoadHook.Enable(); + _soundOnLoadHook.Enable(); } /// Add CRC64 if the given file is a model or texture file and has an associated path. @@ -59,8 +60,9 @@ public unsafe class TexMdlService : IDisposable, IRequiredService { _ = type switch { - ResourceType.Mdl when path.HasValue => _customMdlCrc.Add(path.Value.Crc64), - ResourceType.Tex when path.HasValue => _customTexCrc.Add(path.Value.Crc64), + ResourceType.Mdl when path.HasValue => _customFileCrc.TryAdd(path.Value.Crc64, ResourceType.Mdl), + ResourceType.Tex when path.HasValue => _customFileCrc.TryAdd(path.Value.Crc64, ResourceType.Tex), + ResourceType.Scd when path.HasValue => _customFileCrc.TryAdd(path.Value.Crc64, ResourceType.Scd), _ => false, }; } @@ -70,15 +72,16 @@ public unsafe class TexMdlService : IDisposable, IRequiredService _checkFileStateHook.Dispose(); _loadMdlFileExternHook.Dispose(); _textureOnLoadHook.Dispose(); + _soundOnLoadHook.Dispose(); } /// /// 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. /// - private readonly HashSet _customMdlCrc = []; - - private readonly HashSet _customTexCrc = []; + private readonly Dictionary _customFileCrc = []; + public IReadOnlyDictionary CustomCache + => _customFileCrc; private delegate nint CheckFileStatePrototype(nint unk1, ulong crc64); @@ -86,12 +89,34 @@ public unsafe class TexMdlService : IDisposable, IRequiredService private readonly Hook _checkFileStateHook = null!; private readonly ThreadLocal _texReturnData = new(() => default); + private readonly ThreadLocal _scdReturnData = new(() => default); private delegate void UpdateCategoryDelegate(TextureResourceHandle* resourceHandle); [Signature(Sigs.TexHandleUpdateCategory)] private readonly UpdateCategoryDelegate _updateCategory = null!; + private delegate byte SoundOnLoadDelegate(ResourceHandle* handle, SeFileDescriptor* descriptor, byte unk); + + [Signature("48 89 5C 24 ?? 48 89 74 24 ?? 57 48 83 EC 30 8B 79 ?? 48 8B DA 8B D7")] + private readonly delegate* unmanaged _loadScdFileLocal = null!; + + [Signature("40 56 57 41 54 48 81 EC 90 00 00 00 80 3A 0B 45 0F B6 E0 48 8B F2", DetourName = nameof(OnScdLoadDetour))] + private readonly Hook _soundOnLoadHook = null!; + + private byte OnScdLoadDetour(ResourceHandle* handle, SeFileDescriptor* descriptor, byte unk) + { + var ret = _soundOnLoadHook.Original(handle, descriptor, unk); + if (!_scdReturnData.Value) + return ret; + + // Function failed on a replaced scd, call local. + _scdReturnData.Value = false; + ret = _loadScdFileLocal(handle, descriptor, unk); + _updateCategory((TextureResourceHandle*)handle); + return ret; + } + /// /// 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 for models. @@ -100,14 +125,17 @@ public unsafe class TexMdlService : IDisposable, IRequiredService /// private nint CheckFileStateDetour(nint ptr, ulong crc64) { - if (_customMdlCrc.Contains(crc64)) - return CustomFileFlag; - - if (_customTexCrc.Contains(crc64)) - { - _texReturnData.Value = true; - return nint.Zero; - } + if (_customFileCrc.TryGetValue(crc64, out var type)) + switch (type) + { + case ResourceType.Mdl: return CustomFileFlag; + case ResourceType.Tex: + _texReturnData.Value = true; + return nint.Zero; + case ResourceType.Scd: + _scdReturnData.Value = true; + return nint.Zero; + } var ret = _checkFileStateHook.Original(ptr, crc64); Penumbra.Log.Excessive($"[CheckFileState] Called on 0x{ptr:X} with CRC {crc64:X16}, returned 0x{ret:X}."); @@ -128,10 +156,10 @@ public unsafe class TexMdlService : IDisposable, IRequiredService private delegate byte TexResourceHandleOnLoadPrototype(TextureResourceHandle* handle, SeFileDescriptor* descriptor, byte unk2); - [Signature(Sigs.TexHandleOnLoad, DetourName = nameof(OnLoadDetour))] + [Signature(Sigs.TexHandleOnLoad, DetourName = nameof(OnTexLoadDetour))] private readonly Hook _textureOnLoadHook = null!; - private byte OnLoadDetour(TextureResourceHandle* handle, SeFileDescriptor* descriptor, byte unk2) + private byte OnTexLoadDetour(TextureResourceHandle* handle, SeFileDescriptor* descriptor, byte unk2) { var ret = _textureOnLoadHook.Original(handle, descriptor, unk2); if (!_texReturnData.Value) diff --git a/Penumbra/UI/Tabs/Debug/DebugTab.cs b/Penumbra/UI/Tabs/Debug/DebugTab.cs index 47c2c16c..9184ffe8 100644 --- a/Penumbra/UI/Tabs/Debug/DebugTab.cs +++ b/Penumbra/UI/Tabs/Debug/DebugTab.cs @@ -102,6 +102,7 @@ public class DebugTab : Window, ITab, IUiService private readonly CrashHandlerPanel _crashHandlerPanel; private readonly TexHeaderDrawer _texHeaderDrawer; private readonly HookOverrideDrawer _hookOverrides; + private readonly TexMdlScdService _texMdlScdService; public DebugTab(PerformanceTracker performance, Configuration config, CollectionManager collectionManager, ObjectManager objects, IClientState clientState, @@ -112,7 +113,7 @@ public class DebugTab : Window, ITab, IUiService CutsceneService cutsceneService, ModImportManager modImporter, ImportPopup importPopup, FrameworkManager framework, TextureManager textureManager, ShaderReplacementFixer shaderReplacementFixer, RedrawService redraws, DictEmote emotes, Diagnostics diagnostics, IpcTester ipcTester, CrashHandlerPanel crashHandlerPanel, TexHeaderDrawer texHeaderDrawer, - HookOverrideDrawer hookOverrides) + HookOverrideDrawer hookOverrides, TexMdlScdService texMdlScdService) : base("Penumbra Debug Window", ImGuiWindowFlags.NoCollapse) { IsOpen = true; @@ -150,6 +151,7 @@ public class DebugTab : Window, ITab, IUiService _crashHandlerPanel = crashHandlerPanel; _texHeaderDrawer = texHeaderDrawer; _hookOverrides = hookOverrides; + _texMdlScdService = texMdlScdService; _objects = objects; _clientState = clientState; } @@ -183,6 +185,7 @@ public class DebugTab : Window, ITab, IUiService DrawDebugCharacterUtility(); DrawShaderReplacementFixer(); DrawData(); + DrawCrcCache(); DrawResourceProblems(); _hookOverrides.Draw(); DrawPlayerModelInfo(); @@ -1021,6 +1024,30 @@ public class DebugTab : Window, ITab, IUiService DrawDebugResidentResources(); } + private unsafe void DrawCrcCache() + { + var header = ImUtf8.CollapsingHeader("CRC Cache"u8); + if (!header) + return; + + using var table = ImUtf8.Table("table"u8, 2); + if (!table) + return; + + using var font = ImRaii.PushFont(UiBuilder.MonoFont); + ImUtf8.TableSetupColumn("Hash"u8, ImGuiTableColumnFlags.WidthFixed, 18 * UiBuilder.MonoFont.GetCharAdvance('0')); + ImUtf8.TableSetupColumn("Type"u8, ImGuiTableColumnFlags.WidthFixed, 5 * UiBuilder.MonoFont.GetCharAdvance('0')); + ImGui.TableHeadersRow(); + + foreach (var (hash, type) in _texMdlScdService.CustomCache) + { + ImGui.TableNextColumn(); + ImUtf8.Text($"{hash:X16}"); + ImGui.TableNextColumn(); + ImUtf8.Text($"{type}"); + } + } + /// Draw resources with unusual reference count. private unsafe void DrawResourceProblems() {