From 48ab98bee69117f5c74c425ab19d9c65fd545041 Mon Sep 17 00:00:00 2001 From: Actions User Date: Sat, 20 Jul 2024 22:53:37 +0000 Subject: [PATCH 1/6] [CI] Updating repo.json for testing_1.2.0.12 --- repo.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/repo.json b/repo.json index d715bca2..ca13bb5b 100644 --- a/repo.json +++ b/repo.json @@ -6,7 +6,7 @@ "Description": "Runtime mod loader and manager.", "InternalName": "Penumbra", "AssemblyVersion": "1.1.1.2", - "TestingAssemblyVersion": "1.2.0.11", + "TestingAssemblyVersion": "1.2.0.12", "RepoUrl": "https://github.com/xivdev/Penumbra", "ApplicableVersion": "any", "DalamudApiLevel": 9, @@ -19,7 +19,7 @@ "LoadRequiredState": 2, "LoadSync": true, "DownloadLinkInstall": "https://github.com/xivdev/Penumbra/releases/download/1.1.1.2/Penumbra.zip", - "DownloadLinkTesting": "https://github.com/xivdev/Penumbra/releases/download/testing_1.2.0.11/Penumbra.zip", + "DownloadLinkTesting": "https://github.com/xivdev/Penumbra/releases/download/testing_1.2.0.12/Penumbra.zip", "DownloadLinkUpdate": "https://github.com/xivdev/Penumbra/releases/download/1.1.1.2/Penumbra.zip", "IconUrl": "https://raw.githubusercontent.com/xivdev/Penumbra/master/images/icon.png" } From 8c34c18643b34586346a902f6b0ffdcca6cc2907 Mon Sep 17 00:00:00 2001 From: pmgr <26606291+pmgr@users.noreply.github.com> Date: Sun, 21 Jul 2024 16:34:27 +0100 Subject: [PATCH 2/6] Add scuffed pap handling --- .../Hooks/ResourceLoading/MappedCodeReader.cs | 13 ++ .../Hooks/ResourceLoading/PapHandler.cs | 23 +++ .../Hooks/ResourceLoading/PapRewriter.cs | 181 ++++++++++++++++ .../Hooks/ResourceLoading/PeSigScanner.cs | 194 ++++++++++++++++++ .../Hooks/ResourceLoading/ResourceLoader.cs | 26 +++ Penumbra/Penumbra.csproj | 13 +- 6 files changed, 446 insertions(+), 4 deletions(-) create mode 100644 Penumbra/Interop/Hooks/ResourceLoading/MappedCodeReader.cs create mode 100644 Penumbra/Interop/Hooks/ResourceLoading/PapHandler.cs create mode 100644 Penumbra/Interop/Hooks/ResourceLoading/PapRewriter.cs create mode 100644 Penumbra/Interop/Hooks/ResourceLoading/PeSigScanner.cs diff --git a/Penumbra/Interop/Hooks/ResourceLoading/MappedCodeReader.cs b/Penumbra/Interop/Hooks/ResourceLoading/MappedCodeReader.cs new file mode 100644 index 00000000..81712cca --- /dev/null +++ b/Penumbra/Interop/Hooks/ResourceLoading/MappedCodeReader.cs @@ -0,0 +1,13 @@ +using Iced.Intel; + +namespace Penumbra.Interop.Hooks.ResourceLoading; + +public class MappedCodeReader(UnmanagedMemoryAccessor data, long offset) : CodeReader +{ + public override int ReadByte() { + if (offset >= data.Capacity) + return -1; + + return data.ReadByte(offset++); + } +} diff --git a/Penumbra/Interop/Hooks/ResourceLoading/PapHandler.cs b/Penumbra/Interop/Hooks/ResourceLoading/PapHandler.cs new file mode 100644 index 00000000..29d77d83 --- /dev/null +++ b/Penumbra/Interop/Hooks/ResourceLoading/PapHandler.cs @@ -0,0 +1,23 @@ +using Penumbra.GameData; + +namespace Penumbra.Interop.Hooks.ResourceLoading; + +public sealed class PapHandler(PapRewriter.PapResourceHandlerPrototype papResourceHandler) : IDisposable +{ + private readonly PapRewriter _papRewriter = new(papResourceHandler); + + public void Enable() + { + _papRewriter.Rewrite(Sigs.LoadAlwaysResidentMotionPacks); + _papRewriter.Rewrite(Sigs.LoadWeaponDependentResidentMotionPacks); + _papRewriter.Rewrite(Sigs.LoadInitialResidentMotionPacks); + _papRewriter.Rewrite(Sigs.LoadMotionPacks); + _papRewriter.Rewrite(Sigs.LoadMotionPacks2); + _papRewriter.Rewrite(Sigs.LoadMigratoryMotionPack); + } + + public void Dispose() + { + _papRewriter.Dispose(); + } +} diff --git a/Penumbra/Interop/Hooks/ResourceLoading/PapRewriter.cs b/Penumbra/Interop/Hooks/ResourceLoading/PapRewriter.cs new file mode 100644 index 00000000..cb437d9e --- /dev/null +++ b/Penumbra/Interop/Hooks/ResourceLoading/PapRewriter.cs @@ -0,0 +1,181 @@ +using Dalamud.Hooking; +using Iced.Intel; +using Penumbra.String.Classes; + +namespace Penumbra.Interop.Hooks.ResourceLoading; + +public sealed class PapRewriter(PapRewriter.PapResourceHandlerPrototype papResourceHandler) : IDisposable +{ + public unsafe delegate int PapResourceHandlerPrototype(void* self, byte* path, int length); + + private PeSigScanner Scanner { get; } = new(); + private Dictionary Hooks { get; }= []; + private List NativeAllocList { get; } = []; + private PapResourceHandlerPrototype PapResourceHandler { get; } = papResourceHandler; + + public void Rewrite(string sig) + { + if (!Scanner.TryScanText(sig, out var addr)) + { + throw new Exception($"Sig is fucked: {sig}"); + } + + var funcInstructions = Scanner.GetFunctionInstructions(addr).ToList(); + + var hookPoints = ScanPapHookPoints(funcInstructions).ToList(); + + foreach (var hookPoint in hookPoints) + { + var stackAccesses = ScanStackAccesses(funcInstructions, hookPoint).ToList(); + + var stringLoc = NativeAlloc(Utf8GamePath.MaxGamePathLength); + + { + // We'll need to grab our true hook point; the location where we can change the path at our leisure. + // This is going to be the first call instruction after our 'hookPoint', so, we'll find that. + // Pretty scuffed, this might need a refactoring at some point. + // We're doing it by skipping to our hookPoint's address in the list of instructions inside the function; then getting next CALL + var detourPoint = funcInstructions.Skip( + funcInstructions.FindIndex(instr => instr.IP == hookPoint.IP) + 1 + ).First(instr => instr.Mnemonic == Mnemonic.Call); + + // We'll also remove all the 'hookPoints' from 'stackAccesses'. + // We're handling the char *path redirection here, so we don't want this to hit the later code + foreach (var hp in hookPoints) + { + stackAccesses.RemoveAll(instr => instr.IP == hp.IP); + } + + var pDetour = Marshal.GetFunctionPointerForDelegate(PapResourceHandler); + var targetRegister = hookPoint.Op0Register.ToString().ToLower(); + var hookAddr = new IntPtr((long)detourPoint.IP); + + var caveLoc = NativeAlloc(16); + var hook = new AsmHook( + hookAddr, + [ + "use64", + $"mov {targetRegister}, 0x{stringLoc:x8}", // Move our char *path into the relevant register (rdx) + + // After this asm stub, we have a call to Crc32(); since r9 is a volatile, unused register, we can use it ourselves + // We're essentially storing the original 2 arguments ('this', 'path'), in case they get mangled in our call + // We technically don't need to save rdx ('path'), since it'll be stringLoc, but eh + $"mov r9, 0x{caveLoc:x8}", + "mov [r9], rcx", + "mov [r9+0x8], rdx", + + // We can use 'rax' here too since it's also volatile, and it'll be overwritten by Crc32()'s return anyway + $"mov rax, 0x{pDetour:x8}", // Get a pointer to our detour in place + "call rax", // Call detour + + // Do the reverse process and retrieve the stored stuff + $"mov r9, 0x{caveLoc:x8}", + "mov rcx, [r9]", + "mov rdx, [r9+0x8]", + + // Plop 'rax' (our return value, the path size) into r8, so it's the third argument for the subsequent Crc32() call + "mov r8, rax", + ], "Pap Redirection" + ); + + Hooks.Add(hookAddr, hook); + hook.Enable(); + } + + // Now we're adjusting every single reference to the stack allocated 'path' to our substantially bigger 'stringLoc' + foreach (var stackAccess in stackAccesses) + { + var hookAddr = new IntPtr((long)stackAccess.IP + stackAccess.Length); + + if (Hooks.ContainsKey(hookAddr)) + { + // Hook already exists, means there's reuse of the same stack address across 2 GetResourceAsync; just skip + continue; + } + + var targetRegister = stackAccess.Op0Register.ToString().ToLower(); + var hook = new AsmHook( + hookAddr, + [ + "use64", + $"mov {targetRegister}, 0x{stringLoc:x8}", + ], "Pap Stack Accesses" + ); + + Hooks.Add(hookAddr, hook); + hook.Enable(); + } + } + + + + } + + private static IEnumerable ScanStackAccesses(IEnumerable instructions, Instruction hookPoint) + { + return instructions.Where(instr => + instr.Code == hookPoint.Code + && instr.Op0Kind == hookPoint.Op0Kind + && instr.Op1Kind == hookPoint.Op1Kind + && instr.MemoryBase == hookPoint.MemoryBase + && instr.MemoryDisplacement64 == hookPoint.MemoryDisplacement64) + .GroupBy(instr => instr.IP) + .Select(grp => grp.First()); + } + + // This is utterly fucked and hardcoded, but, again, it works + // Might be a neat idea for a more versatile kind of signature though + private static IEnumerable ScanPapHookPoints(List funcInstructions) + { + for (var i = 0; i < funcInstructions.Count - 8; i++) + { + if (funcInstructions[i .. (i + 8)] is + [ + {Code : Code.Lea_r64_m}, + {Code : Code.Lea_r64_m}, + {Mnemonic: Mnemonic.Call}, + {Code : Code.Lea_r64_m}, + {Mnemonic: Mnemonic.Call}, + {Code : Code.Lea_r64_m}, + .., + ] + ) + { + yield return funcInstructions[i]; + } + } + } + + private unsafe IntPtr NativeAlloc(nuint size) + { + var caveLoc = new IntPtr(NativeMemory.Alloc(size)); + NativeAllocList.Add(caveLoc); + + return caveLoc; + } + + private static unsafe void NativeFree(IntPtr mem) + { + NativeMemory.Free(mem.ToPointer()); + } + + public void Dispose() + { + Scanner.Dispose(); + + foreach (var hook in Hooks.Values) + { + hook.Disable(); + hook.Dispose(); + } + + Hooks.Clear(); + + foreach (var mem in NativeAllocList) + { + NativeFree(mem); + } + + NativeAllocList.Clear(); + } +} diff --git a/Penumbra/Interop/Hooks/ResourceLoading/PeSigScanner.cs b/Penumbra/Interop/Hooks/ResourceLoading/PeSigScanner.cs new file mode 100644 index 00000000..231e04f3 --- /dev/null +++ b/Penumbra/Interop/Hooks/ResourceLoading/PeSigScanner.cs @@ -0,0 +1,194 @@ +using System.IO.MemoryMappedFiles; +using Iced.Intel; +using PeNet; +using Decoder = Iced.Intel.Decoder; + +namespace Penumbra.Interop.Hooks.ResourceLoading; + +// A good chunk of this was blatantly stolen from Dalamud's SigScanner 'cause I could not be faffed, maybe I'll rewrite it later +public class PeSigScanner : IDisposable +{ + private MemoryMappedFile File { get; } + + private uint TextSectionStart { get; } + private uint TextSectionSize { get; } + + private IntPtr ModuleBaseAddress { get; } + private uint TextSectionVirtualAddress { get; } + + private MemoryMappedViewAccessor TextSection { get; } + + + public PeSigScanner() + { + var mainModule = Process.GetCurrentProcess().MainModule!; + var fileName = mainModule.FileName; + ModuleBaseAddress = mainModule.BaseAddress; + + if (fileName == null) + { + throw new Exception("Can't get main module path, the fuck is going on?"); + } + + File = MemoryMappedFile.CreateFromFile(fileName, FileMode.Open, null, 0, MemoryMappedFileAccess.Read); + + using var fileStream = File.CreateViewStream(0, 0, MemoryMappedFileAccess.Read); + var pe = new PeFile(fileStream); + + var textSection = pe.ImageSectionHeaders!.First(header => header.Name == ".text"); + + TextSectionStart = textSection.PointerToRawData; + TextSectionSize = textSection.SizeOfRawData; + TextSectionVirtualAddress = textSection.VirtualAddress; + + TextSection = File.CreateViewAccessor(TextSectionStart, TextSectionSize, MemoryMappedFileAccess.Read); + } + + + private IntPtr ScanText(string signature) + { + var scanRet = Scan(TextSection, signature); + + var instrByte = Marshal.ReadByte(scanRet); + + if (instrByte is 0xE8 or 0xE9) + scanRet = ReadJmpCallSig(scanRet); + + return scanRet; + } + + private static IntPtr ReadJmpCallSig(IntPtr sigLocation) + { + var jumpOffset = Marshal.ReadInt32(sigLocation, 1); + return IntPtr.Add(sigLocation, 5 + jumpOffset); + } + + public bool TryScanText(string signature, out IntPtr result) + { + try + { + result = ScanText(signature); + return true; + } + catch (KeyNotFoundException) + { + result = IntPtr.Zero; + return false; + } + } + + private IntPtr Scan(MemoryMappedViewAccessor section, string signature) + { + var (needle, mask) = ParseSignature(signature); + + var index = IndexOf(section, needle, mask); + if (index < 0) + throw new KeyNotFoundException($"Can't find a signature of {signature}"); + return new IntPtr(ModuleBaseAddress + index - section.PointerOffset + TextSectionVirtualAddress); + } + + private static (byte[] Needle, bool[] Mask) ParseSignature(string signature) + { + signature = signature.Replace(" ", string.Empty); + if (signature.Length % 2 != 0) + throw new ArgumentException("Signature without whitespaces must be divisible by two.", nameof(signature)); + + var needleLength = signature.Length / 2; + var needle = new byte[needleLength]; + var mask = new bool[needleLength]; + for (var i = 0; i < needleLength; i++) + { + var hexString = signature.Substring(i * 2, 2); + if (hexString == "??" || hexString == "**") + { + needle[i] = 0; + mask[i] = true; + continue; + } + + needle[i] = byte.Parse(hexString, NumberStyles.AllowHexSpecifier); + mask[i] = false; + } + + return (needle, mask); + } + + private static unsafe int IndexOf(MemoryMappedViewAccessor section, byte[] needle, bool[] mask) + { + if (needle.Length > section.Capacity) return -1; + var badShift = BuildBadCharTable(needle, mask); + var last = needle.Length - 1; + var offset = 0; + var maxOffset = section.Capacity - needle.Length; + + byte* buffer = null; + section.SafeMemoryMappedViewHandle.AcquirePointer(ref buffer); + try + { + while (offset <= maxOffset) + { + int position; + for (position = last; needle[position] == *(buffer + position + offset) || mask[position]; position--) + { + if (position == 0) + return offset; + } + + offset += badShift[*(buffer + offset + last)]; + } + } + finally + { + section.SafeMemoryMappedViewHandle.ReleasePointer(); + } + + return -1; + } + + + private static int[] BuildBadCharTable(byte[] needle, bool[] mask) + { + int idx; + var last = needle.Length - 1; + var badShift = new int[256]; + for (idx = last; idx > 0 && !mask[idx]; --idx) + { + } + + var diff = last - idx; + if (diff == 0) diff = 1; + + for (idx = 0; idx <= 255; ++idx) + badShift[idx] = diff; + for (idx = last - diff; idx < last; ++idx) + badShift[needle[idx]] = last - idx; + return badShift; + } + + // Detects function termination; this is done in a really stupid way that will possibly break if looked at wrong, but it'll work for now + // If this shits itself, go bother Winter to implement proper CFG + basic block detection + public IEnumerable GetFunctionInstructions(IntPtr addr) + { + var fileOffset = addr - TextSectionVirtualAddress - ModuleBaseAddress; + + var codeReader = new MappedCodeReader(TextSection, fileOffset); + var decoder = Decoder.Create(64, codeReader, (ulong)addr.ToInt64()); + + do + { + decoder.Decode(out var instr); + + // Yes, this is catastrophically bad, but it works for some cases okay + if (instr.Mnemonic == Mnemonic.Int3) + break; + + yield return instr; + } while (true); + } + + public void Dispose() + { + TextSection.Dispose(); + File.Dispose(); + } +} diff --git a/Penumbra/Interop/Hooks/ResourceLoading/ResourceLoader.cs b/Penumbra/Interop/Hooks/ResourceLoading/ResourceLoader.cs index 195a8b9e..bc28c200 100644 --- a/Penumbra/Interop/Hooks/ResourceLoading/ResourceLoader.cs +++ b/Penumbra/Interop/Hooks/ResourceLoading/ResourceLoader.cs @@ -16,6 +16,8 @@ public unsafe class ResourceLoader : IDisposable, IService private readonly ResourceService _resources; private readonly FileReadService _fileReadService; private readonly TexMdlService _texMdlService; + + private readonly PapHandler _papHandler; private ResolveData _resolvedData = ResolveData.Invalid; @@ -30,6 +32,29 @@ public unsafe class ResourceLoader : IDisposable, IService _resources.ResourceHandleIncRef += IncRefProtection; _resources.ResourceHandleDecRef += DecRefProtection; _fileReadService.ReadSqPack += ReadSqPackDetour; + + _papHandler = new PapHandler(PapResourceHandler); + _papHandler.Enable(); + } + + private int PapResourceHandler(void* self, byte* path, int length) + { + Utf8GamePath.FromPointer(path, out var gamePath); + + var (resolvedPath, _) = _incMode.Value + ? (null, ResolveData.Invalid) + : _resolvedData.Valid + ? (_resolvedData.ModCollection.ResolvePath(gamePath), _resolvedData) + : ResolvePath(gamePath, ResourceCategory.Chara, ResourceType.Pap); + + if (!resolvedPath.HasValue || !Utf8GamePath.FromString(resolvedPath.Value.FullName, out var utf8ResolvedPath)) + { + return length; + } + + NativeMemory.Copy(utf8ResolvedPath.Path.Path, path, (nuint)utf8ResolvedPath.Length); + path[utf8ResolvedPath.Length] = 0; + return utf8ResolvedPath.Length; } /// Load a resource for a given path and a specific collection. @@ -84,6 +109,7 @@ public unsafe class ResourceLoader : IDisposable, IService _resources.ResourceHandleIncRef -= IncRefProtection; _resources.ResourceHandleDecRef -= DecRefProtection; _fileReadService.ReadSqPack -= ReadSqPackDetour; + _papHandler.Dispose(); } private void ResourceHandler(ref ResourceCategory category, ref ResourceType type, ref int hash, ref Utf8GamePath path, diff --git a/Penumbra/Penumbra.csproj b/Penumbra/Penumbra.csproj index 2e53bd22..70208737 100644 --- a/Penumbra/Penumbra.csproj +++ b/Penumbra/Penumbra.csproj @@ -23,10 +23,10 @@ PROFILING; - - - - + + + + @@ -68,6 +68,10 @@ $(DalamudLibPath)Newtonsoft.Json.dll False + + $(DalamudLibPath)Iced.dll + False + lib\OtterTex.dll @@ -79,6 +83,7 @@ + From 8351b74b21c5dfaca5691bc7e2956715573e7fa5 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sun, 21 Jul 2024 22:23:31 +0200 Subject: [PATCH 3/6] Update GameData and packages. --- Penumbra.GameData | 2 +- Penumbra/packages.lock.json | 30 +++++++++++++++++++++++++++++- 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/Penumbra.GameData b/Penumbra.GameData index 9f1816f1..f13818fd 160000 --- a/Penumbra.GameData +++ b/Penumbra.GameData @@ -1 +1 @@ -Subproject commit 9f1816f1b75003d01c5576769831c10f3d8948a7 +Subproject commit f13818fd85b436d0a0f66293fe7c6b60d4bffe3c diff --git a/Penumbra/packages.lock.json b/Penumbra/packages.lock.json index b431e595..42539e78 100644 --- a/Penumbra/packages.lock.json +++ b/Penumbra/packages.lock.json @@ -11,6 +11,16 @@ "Unosquare.Swan.Lite": "3.0.0" } }, + "PeNet": { + "type": "Direct", + "requested": "[4.0.5, )", + "resolved": "4.0.5", + "contentHash": "/OUfRnMG8STVuK8kTpdfm+WQGTDoUiJnI845kFw4QrDmv2gYourmSnH84pqVjHT1YHBSuRfCzfioIpHGjFJrGA==", + "dependencies": { + "PeNet.Asn1": "2.0.1", + "System.Security.Cryptography.Pkcs": "8.0.0" + } + }, "SharpCompress": { "type": "Direct", "requested": "[0.33.0, )", @@ -56,6 +66,11 @@ "resolved": "8.0.0", "contentHash": "cjWrLkJXK0rs4zofsK4bSdg+jhDLTaxrkXu4gS6Y7MAlCvRyNNgwY/lJi5RDlQOnSZweHqoyvgvbdvQsRIW+hg==" }, + "PeNet.Asn1": { + "type": "Transitive", + "resolved": "2.0.1", + "contentHash": "YR2O2YokSAYB+7CXkCDN3bd6/p0K3/AicCPkOJHKUz500v1D/hulCuVlggguqNc3M0LgSfOZKGvVYg2ud1GA9A==" + }, "SharpGLTF.Runtime": { "type": "Transitive", "resolved": "1.0.0-alpha0030", @@ -64,6 +79,19 @@ "SharpGLTF.Core": "1.0.0-alpha0030" } }, + "System.Formats.Asn1": { + "type": "Transitive", + "resolved": "8.0.0", + "contentHash": "AJukBuLoe3QeAF+mfaRKQb2dgyrvt340iMBHYv+VdBzCUM06IxGlvl0o/uPOS7lHnXPN6u8fFRHSHudx5aTi8w==" + }, + "System.Security.Cryptography.Pkcs": { + "type": "Transitive", + "resolved": "8.0.0", + "contentHash": "ULmp3xoOwNYjOYp4JZ2NK/6NdTgiN1GQXzVVN1njQ7LOZ0d0B9vyMnhyqbIi9Qw4JXj1JgCsitkTShboHRx7Eg==", + "dependencies": { + "System.Formats.Asn1": "8.0.0" + } + }, "System.ValueTuple": { "type": "Transitive", "resolved": "4.5.0", @@ -94,7 +122,7 @@ "type": "Project", "dependencies": { "OtterGui": "[1.0.0, )", - "Penumbra.Api": "[5.0.0, )", + "Penumbra.Api": "[5.2.0, )", "Penumbra.String": "[1.0.4, )" } }, From 0db70c89b105ec8b76a0c3a6764ab4c566e016f7 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sun, 21 Jul 2024 22:58:03 +0200 Subject: [PATCH 4/6] Some cleanup of PeSigScanner. --- .../Hooks/ResourceLoading/PapHandler.cs | 4 +- .../Hooks/ResourceLoading/PeSigScanner.cs | 118 +++++++++--------- 2 files changed, 57 insertions(+), 65 deletions(-) diff --git a/Penumbra/Interop/Hooks/ResourceLoading/PapHandler.cs b/Penumbra/Interop/Hooks/ResourceLoading/PapHandler.cs index 29d77d83..f0fd8b0e 100644 --- a/Penumbra/Interop/Hooks/ResourceLoading/PapHandler.cs +++ b/Penumbra/Interop/Hooks/ResourceLoading/PapHandler.cs @@ -17,7 +17,5 @@ public sealed class PapHandler(PapRewriter.PapResourceHandlerPrototype papResour } public void Dispose() - { - _papRewriter.Dispose(); - } + => _papRewriter.Dispose(); } diff --git a/Penumbra/Interop/Hooks/ResourceLoading/PeSigScanner.cs b/Penumbra/Interop/Hooks/ResourceLoading/PeSigScanner.cs index 231e04f3..f5dd2d45 100644 --- a/Penumbra/Interop/Hooks/ResourceLoading/PeSigScanner.cs +++ b/Penumbra/Interop/Hooks/ResourceLoading/PeSigScanner.cs @@ -5,65 +5,56 @@ using Decoder = Iced.Intel.Decoder; namespace Penumbra.Interop.Hooks.ResourceLoading; -// A good chunk of this was blatantly stolen from Dalamud's SigScanner 'cause I could not be faffed, maybe I'll rewrite it later -public class PeSigScanner : IDisposable +// A good chunk of this was blatantly stolen from Dalamud's SigScanner 'cause Winter could not be faffed, Winter will definitely not rewrite it later +public unsafe class PeSigScanner : IDisposable { - private MemoryMappedFile File { get; } + private readonly MemoryMappedFile _file; + private readonly MemoryMappedViewAccessor _textSection; + + private readonly nint _moduleBaseAddress; + private readonly uint _textSectionVirtualAddress; + - private uint TextSectionStart { get; } - private uint TextSectionSize { get; } - - private IntPtr ModuleBaseAddress { get; } - private uint TextSectionVirtualAddress { get; } - - private MemoryMappedViewAccessor TextSection { get; } - - public PeSigScanner() { var mainModule = Process.GetCurrentProcess().MainModule!; - var fileName = mainModule.FileName; - ModuleBaseAddress = mainModule.BaseAddress; + var fileName = mainModule.FileName; + _moduleBaseAddress = mainModule.BaseAddress; if (fileName == null) - { - throw new Exception("Can't get main module path, the fuck is going on?"); - } - - File = MemoryMappedFile.CreateFromFile(fileName, FileMode.Open, null, 0, MemoryMappedFileAccess.Read); + throw new Exception("Unable to obtain main module path. This should not happen."); - using var fileStream = File.CreateViewStream(0, 0, MemoryMappedFileAccess.Read); + _file = MemoryMappedFile.CreateFromFile(fileName, FileMode.Open, null, 0, MemoryMappedFileAccess.Read); + + using var fileStream = _file.CreateViewStream(0, 0, MemoryMappedFileAccess.Read); var pe = new PeFile(fileStream); var textSection = pe.ImageSectionHeaders!.First(header => header.Name == ".text"); - TextSectionStart = textSection.PointerToRawData; - TextSectionSize = textSection.SizeOfRawData; - TextSectionVirtualAddress = textSection.VirtualAddress; + var textSectionStart = textSection.PointerToRawData; + var textSectionSize = textSection.SizeOfRawData; + _textSectionVirtualAddress = textSection.VirtualAddress; - TextSection = File.CreateViewAccessor(TextSectionStart, TextSectionSize, MemoryMappedFileAccess.Read); + _textSection = _file.CreateViewAccessor(textSectionStart, textSectionSize, MemoryMappedFileAccess.Read); } - private IntPtr ScanText(string signature) + private nint ScanText(string signature) { - var scanRet = Scan(TextSection, signature); - - var instrByte = Marshal.ReadByte(scanRet); - - if (instrByte is 0xE8 or 0xE9) + var scanRet = Scan(_textSection, signature); + if (*(byte*)scanRet is 0xE8 or 0xE9) scanRet = ReadJmpCallSig(scanRet); return scanRet; } - - private static IntPtr ReadJmpCallSig(IntPtr sigLocation) + + private static nint ReadJmpCallSig(nint sigLocation) { - var jumpOffset = Marshal.ReadInt32(sigLocation, 1); - return IntPtr.Add(sigLocation, 5 + jumpOffset); + var jumpOffset = *(int*)(sigLocation + 1); + return sigLocation + 5 + jumpOffset; } - - public bool TryScanText(string signature, out IntPtr result) + + public bool TryScanText(string signature, out nint result) { try { @@ -72,21 +63,22 @@ public class PeSigScanner : IDisposable } catch (KeyNotFoundException) { - result = IntPtr.Zero; + result = nint.Zero; return false; } } - - private IntPtr Scan(MemoryMappedViewAccessor section, string signature) + + private nint Scan(MemoryMappedViewAccessor section, string signature) { var (needle, mask) = ParseSignature(signature); - - var index = IndexOf(section, needle, mask); + + var index = IndexOf(section, needle, mask); if (index < 0) throw new KeyNotFoundException($"Can't find a signature of {signature}"); - return new IntPtr(ModuleBaseAddress + index - section.PointerOffset + TextSectionVirtualAddress); + + return (nint)(_moduleBaseAddress + index - section.PointerOffset + _textSectionVirtualAddress); } - + private static (byte[] Needle, bool[] Mask) ParseSignature(string signature) { signature = signature.Replace(" ", string.Empty); @@ -99,7 +91,7 @@ public class PeSigScanner : IDisposable for (var i = 0; i < needleLength; i++) { var hexString = signature.Substring(i * 2, 2); - if (hexString == "??" || hexString == "**") + if (hexString is "??" or "**") { needle[i] = 0; mask[i] = true; @@ -112,10 +104,12 @@ public class PeSigScanner : IDisposable return (needle, mask); } - - private static unsafe int IndexOf(MemoryMappedViewAccessor section, byte[] needle, bool[] mask) + + private static int IndexOf(MemoryMappedViewAccessor section, byte[] needle, bool[] mask) { - if (needle.Length > section.Capacity) return -1; + if (needle.Length > section.Capacity) + return -1; + var badShift = BuildBadCharTable(needle, mask); var last = needle.Length - 1; var offset = 0; @@ -144,19 +138,19 @@ public class PeSigScanner : IDisposable return -1; } - - + + private static int[] BuildBadCharTable(byte[] needle, bool[] mask) { int idx; var last = needle.Length - 1; var badShift = new int[256]; for (idx = last; idx > 0 && !mask[idx]; --idx) - { - } + { } - var diff = last - idx; - if (diff == 0) diff = 1; + var diff = last - idx; + if (diff == 0) + diff = 1; for (idx = 0; idx <= 255; ++idx) badShift[idx] = diff; @@ -164,16 +158,16 @@ public class PeSigScanner : IDisposable badShift[needle[idx]] = last - idx; return badShift; } - + // Detects function termination; this is done in a really stupid way that will possibly break if looked at wrong, but it'll work for now // If this shits itself, go bother Winter to implement proper CFG + basic block detection - public IEnumerable GetFunctionInstructions(IntPtr addr) + public IEnumerable GetFunctionInstructions(nint address) { - var fileOffset = addr - TextSectionVirtualAddress - ModuleBaseAddress; - - var codeReader = new MappedCodeReader(TextSection, fileOffset); - var decoder = Decoder.Create(64, codeReader, (ulong)addr.ToInt64()); - + var fileOffset = address - _textSectionVirtualAddress - _moduleBaseAddress; + + var codeReader = new MappedCodeReader(_textSection, fileOffset); + var decoder = Decoder.Create(64, codeReader, (ulong)address.ToInt64()); + do { decoder.Decode(out var instr); @@ -188,7 +182,7 @@ public class PeSigScanner : IDisposable public void Dispose() { - TextSection.Dispose(); - File.Dispose(); + _textSection.Dispose(); + _file.Dispose(); } } From ee5a21f7a20dc459b1ec0d903c008f616fbcde3c Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sun, 21 Jul 2024 22:58:24 +0200 Subject: [PATCH 5/6] Add pap requested event, some cleanup. --- .../Hooks/ResourceLoading/ResourceLoader.cs | 22 +++++---- .../UI/ResourceWatcher/ResourceWatcher.cs | 47 ++++++++++++------- 2 files changed, 44 insertions(+), 25 deletions(-) diff --git a/Penumbra/Interop/Hooks/ResourceLoading/ResourceLoader.cs b/Penumbra/Interop/Hooks/ResourceLoading/ResourceLoader.cs index bc28c200..cf87aa2b 100644 --- a/Penumbra/Interop/Hooks/ResourceLoading/ResourceLoader.cs +++ b/Penumbra/Interop/Hooks/ResourceLoading/ResourceLoader.cs @@ -16,10 +16,10 @@ public unsafe class ResourceLoader : IDisposable, IService private readonly ResourceService _resources; private readonly FileReadService _fileReadService; private readonly TexMdlService _texMdlService; - - private readonly PapHandler _papHandler; + private readonly PapHandler _papHandler; - private ResolveData _resolvedData = ResolveData.Invalid; + private ResolveData _resolvedData = ResolveData.Invalid; + public event Action? PapRequested; public ResourceLoader(ResourceService resources, FileReadService fileReadService, TexMdlService texMdlService) { @@ -36,24 +36,28 @@ public unsafe class ResourceLoader : IDisposable, IService _papHandler = new PapHandler(PapResourceHandler); _papHandler.Enable(); } - + private int PapResourceHandler(void* self, byte* path, int length) { - Utf8GamePath.FromPointer(path, out var gamePath); - + if (!Utf8GamePath.FromPointer(path, out var gamePath)) + return length; + var (resolvedPath, _) = _incMode.Value ? (null, ResolveData.Invalid) : _resolvedData.Valid ? (_resolvedData.ModCollection.ResolvePath(gamePath), _resolvedData) : ResolvePath(gamePath, ResourceCategory.Chara, ResourceType.Pap); - - if (!resolvedPath.HasValue || !Utf8GamePath.FromString(resolvedPath.Value.FullName, out var utf8ResolvedPath)) + + + if (!resolvedPath.HasValue || !Utf8GamePath.FromByteString(resolvedPath.Value.InternalName, out var utf8ResolvedPath)) { + PapRequested?.Invoke(gamePath, gamePath, _resolvedData); return length; } - + NativeMemory.Copy(utf8ResolvedPath.Path.Path, path, (nuint)utf8ResolvedPath.Length); path[utf8ResolvedPath.Length] = 0; + PapRequested?.Invoke(gamePath, utf8ResolvedPath, _resolvedData); return utf8ResolvedPath.Length; } diff --git a/Penumbra/UI/ResourceWatcher/ResourceWatcher.cs b/Penumbra/UI/ResourceWatcher/ResourceWatcher.cs index 935f11e3..a00b33c7 100644 --- a/Penumbra/UI/ResourceWatcher/ResourceWatcher.cs +++ b/Penumbra/UI/ResourceWatcher/ResourceWatcher.cs @@ -36,31 +36,47 @@ public sealed class ResourceWatcher : IDisposable, ITab, IUiService private Regex? _logRegex; private int _newMaxEntries; - public unsafe ResourceWatcher(ActorManager actors, Configuration config, ResourceService resources, ResourceLoader loader, ResourceHandleDestructor destructor) + public unsafe ResourceWatcher(ActorManager actors, Configuration config, ResourceService resources, ResourceLoader loader, + ResourceHandleDestructor destructor) { - _actors = actors; - _config = config; - _ephemeral = config.Ephemeral; - _resources = resources; - _destructor = destructor; - _loader = loader; - _table = new ResourceWatcherTable(config.Ephemeral, _records); - _resources.ResourceRequested += OnResourceRequested; + _actors = actors; + _config = config; + _ephemeral = config.Ephemeral; + _resources = resources; + _destructor = destructor; + _loader = loader; + _table = new ResourceWatcherTable(config.Ephemeral, _records); + _resources.ResourceRequested += OnResourceRequested; _destructor.Subscribe(OnResourceDestroyed, ResourceHandleDestructor.Priority.ResourceWatcher); - _loader.ResourceLoaded += OnResourceLoaded; - _loader.FileLoaded += OnFileLoaded; + _loader.ResourceLoaded += OnResourceLoaded; + _loader.FileLoaded += OnFileLoaded; + _loader.PapRequested += OnPapRequested; UpdateFilter(_ephemeral.ResourceLoggingFilter, false); _newMaxEntries = _config.MaxResourceWatcherRecords; } + private void OnPapRequested(Utf8GamePath original) + { + if (_ephemeral.EnableResourceLogging && FilterMatch(original.Path, out var match)) + Penumbra.Log.Information($"[ResourceLoader] [REQ] {match} was requested asynchronously."); + + if (!_ephemeral.EnableResourceWatcher) + return; + + var record = Record.CreateRequest(original.Path, false); + if (!_ephemeral.OnlyAddMatchingResources || _table.WouldBeVisible(record)) + _newRecords.Enqueue(record); + } + public unsafe void Dispose() { Clear(); _records.TrimExcess(); - _resources.ResourceRequested -= OnResourceRequested; + _resources.ResourceRequested -= OnResourceRequested; _destructor.Unsubscribe(OnResourceDestroyed); - _loader.ResourceLoaded -= OnResourceLoaded; - _loader.FileLoaded -= OnFileLoaded; + _loader.ResourceLoaded -= OnResourceLoaded; + _loader.FileLoaded -= OnFileLoaded; + _loader.PapRequested -= OnPapRequested; } private void Clear() @@ -200,8 +216,7 @@ public sealed class ResourceWatcher : IDisposable, ITab, IUiService private unsafe void OnResourceRequested(ref ResourceCategory category, ref ResourceType type, ref int hash, ref Utf8GamePath path, - Utf8GamePath original, - GetResourceParameters* parameters, ref bool sync, ref ResourceHandle* returnValue) + Utf8GamePath original, GetResourceParameters* parameters, ref bool sync, ref ResourceHandle* returnValue) { if (_ephemeral.EnableResourceLogging && FilterMatch(original.Path, out var match)) Penumbra.Log.Information($"[ResourceLoader] [REQ] {match} was requested {(sync ? "synchronously." : "asynchronously.")}"); From ceaa9ca29a05125fd08cd76e664145cac37c7343 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sun, 21 Jul 2024 23:26:09 +0200 Subject: [PATCH 6/6] Some further cleanup. --- .../Hooks/ResourceLoading/PapHandler.cs | 25 +- .../Hooks/ResourceLoading/PapRewriter.cs | 230 ++++++++---------- .../Hooks/ResourceLoading/ResourceLoader.cs | 4 +- 3 files changed, 127 insertions(+), 132 deletions(-) diff --git a/Penumbra/Interop/Hooks/ResourceLoading/PapHandler.cs b/Penumbra/Interop/Hooks/ResourceLoading/PapHandler.cs index f0fd8b0e..65add13c 100644 --- a/Penumbra/Interop/Hooks/ResourceLoading/PapHandler.cs +++ b/Penumbra/Interop/Hooks/ResourceLoading/PapHandler.cs @@ -5,17 +5,26 @@ namespace Penumbra.Interop.Hooks.ResourceLoading; public sealed class PapHandler(PapRewriter.PapResourceHandlerPrototype papResourceHandler) : IDisposable { private readonly PapRewriter _papRewriter = new(papResourceHandler); - + public void Enable() { - _papRewriter.Rewrite(Sigs.LoadAlwaysResidentMotionPacks); - _papRewriter.Rewrite(Sigs.LoadWeaponDependentResidentMotionPacks); - _papRewriter.Rewrite(Sigs.LoadInitialResidentMotionPacks); - _papRewriter.Rewrite(Sigs.LoadMotionPacks); - _papRewriter.Rewrite(Sigs.LoadMotionPacks2); - _papRewriter.Rewrite(Sigs.LoadMigratoryMotionPack); + ReadOnlySpan signatures = + [ + Sigs.LoadAlwaysResidentMotionPacks, + Sigs.LoadWeaponDependentResidentMotionPacks, + Sigs.LoadInitialResidentMotionPacks, + Sigs.LoadMotionPacks, + Sigs.LoadMotionPacks2, + Sigs.LoadMigratoryMotionPack, + ]; + + var stopwatch = Stopwatch.StartNew(); + foreach (var sig in signatures) + _papRewriter.Rewrite(sig); + Penumbra.Log.Debug( + $"[PapHandler] Rewrote {signatures.Length} .pap functions for inlined GetResourceAsync in {stopwatch.ElapsedMilliseconds} ms."); } - + public void Dispose() => _papRewriter.Dispose(); } diff --git a/Penumbra/Interop/Hooks/ResourceLoading/PapRewriter.cs b/Penumbra/Interop/Hooks/ResourceLoading/PapRewriter.cs index cb437d9e..af16d706 100644 --- a/Penumbra/Interop/Hooks/ResourceLoading/PapRewriter.cs +++ b/Penumbra/Interop/Hooks/ResourceLoading/PapRewriter.cs @@ -1,5 +1,6 @@ using Dalamud.Hooking; using Iced.Intel; +using OtterGui; using Penumbra.String.Classes; namespace Penumbra.Interop.Hooks.ResourceLoading; @@ -7,175 +8,160 @@ namespace Penumbra.Interop.Hooks.ResourceLoading; public sealed class PapRewriter(PapRewriter.PapResourceHandlerPrototype papResourceHandler) : IDisposable { public unsafe delegate int PapResourceHandlerPrototype(void* self, byte* path, int length); - - private PeSigScanner Scanner { get; } = new(); - private Dictionary Hooks { get; }= []; - private List NativeAllocList { get; } = []; - private PapResourceHandlerPrototype PapResourceHandler { get; } = papResourceHandler; + + private readonly PeSigScanner _scanner = new(); + private readonly Dictionary _hooks = []; + private readonly List _nativeAllocList = []; public void Rewrite(string sig) { - if (!Scanner.TryScanText(sig, out var addr)) - { - throw new Exception($"Sig is fucked: {sig}"); - } + if (!_scanner.TryScanText(sig, out var address)) + throw new Exception($"Signature [{sig}] could not be found."); - var funcInstructions = Scanner.GetFunctionInstructions(addr).ToList(); - - var hookPoints = ScanPapHookPoints(funcInstructions).ToList(); + var funcInstructions = _scanner.GetFunctionInstructions(address).ToArray(); + var hookPoints = ScanPapHookPoints(funcInstructions).ToList(); foreach (var hookPoint in hookPoints) { - var stackAccesses = ScanStackAccesses(funcInstructions, hookPoint).ToList(); + var stackAccesses = ScanStackAccesses(funcInstructions, hookPoint).ToList(); + var stringAllocation = NativeAlloc(Utf8GamePath.MaxGamePathLength); - var stringLoc = NativeAlloc(Utf8GamePath.MaxGamePathLength); + // We'll need to grab our true hook point; the location where we can change the path at our leisure. + // This is going to be the first call instruction after our 'hookPoint', so, we'll find that. + // Pretty scuffed, this might need a refactoring at some point. + // We're doing it by skipping to our hookPoint's address in the list of instructions inside the function; then getting next CALL + var skipIndex = funcInstructions.IndexOf(instr => instr.IP == hookPoint.IP) + 1; + var detourPoint = funcInstructions.Skip(skipIndex) + .First(instr => instr.Mnemonic == Mnemonic.Call); - { - // We'll need to grab our true hook point; the location where we can change the path at our leisure. - // This is going to be the first call instruction after our 'hookPoint', so, we'll find that. - // Pretty scuffed, this might need a refactoring at some point. - // We're doing it by skipping to our hookPoint's address in the list of instructions inside the function; then getting next CALL - var detourPoint = funcInstructions.Skip( - funcInstructions.FindIndex(instr => instr.IP == hookPoint.IP) + 1 - ).First(instr => instr.Mnemonic == Mnemonic.Call); + // We'll also remove all the 'hookPoints' from 'stackAccesses'. + // We're handling the char *path redirection here, so we don't want this to hit the later code + foreach (var hp in hookPoints) + stackAccesses.RemoveAll(instr => instr.IP == hp.IP); - // We'll also remove all the 'hookPoints' from 'stackAccesses'. - // We're handling the char *path redirection here, so we don't want this to hit the later code - foreach (var hp in hookPoints) - { - stackAccesses.RemoveAll(instr => instr.IP == hp.IP); - } + var detourPointer = Marshal.GetFunctionPointerForDelegate(papResourceHandler); + var targetRegister = hookPoint.Op0Register.ToString().ToLower(); + var hookAddress = new IntPtr((long)detourPoint.IP); - var pDetour = Marshal.GetFunctionPointerForDelegate(PapResourceHandler); - var targetRegister = hookPoint.Op0Register.ToString().ToLower(); - var hookAddr = new IntPtr((long)detourPoint.IP); + var caveAllocation = NativeAlloc(16); + var hook = new AsmHook( + hookAddress, + [ + "use64", + $"mov {targetRegister}, 0x{stringAllocation:x8}", // Move our char *path into the relevant register (rdx) - var caveLoc = NativeAlloc(16); - var hook = new AsmHook( - hookAddr, - [ - "use64", - $"mov {targetRegister}, 0x{stringLoc:x8}", // Move our char *path into the relevant register (rdx) - - // After this asm stub, we have a call to Crc32(); since r9 is a volatile, unused register, we can use it ourselves - // We're essentially storing the original 2 arguments ('this', 'path'), in case they get mangled in our call - // We technically don't need to save rdx ('path'), since it'll be stringLoc, but eh - $"mov r9, 0x{caveLoc:x8}", - "mov [r9], rcx", - "mov [r9+0x8], rdx", - - // We can use 'rax' here too since it's also volatile, and it'll be overwritten by Crc32()'s return anyway - $"mov rax, 0x{pDetour:x8}", // Get a pointer to our detour in place - "call rax", // Call detour - - // Do the reverse process and retrieve the stored stuff - $"mov r9, 0x{caveLoc:x8}", - "mov rcx, [r9]", - "mov rdx, [r9+0x8]", - - // Plop 'rax' (our return value, the path size) into r8, so it's the third argument for the subsequent Crc32() call - "mov r8, rax", - ], "Pap Redirection" - ); + // After this asm stub, we have a call to Crc32(); since r9 is a volatile, unused register, we can use it ourselves + // We're essentially storing the original 2 arguments ('this', 'path'), in case they get mangled in our call + // We technically don't need to save rdx ('path'), since it'll be stringLoc, but eh + $"mov r9, 0x{caveAllocation:x8}", + "mov [r9], rcx", + "mov [r9+0x8], rdx", - Hooks.Add(hookAddr, hook); - hook.Enable(); - } + // We can use 'rax' here too since it's also volatile, and it'll be overwritten by Crc32()'s return anyway + $"mov rax, 0x{detourPointer:x8}", // Get a pointer to our detour in place + "call rax", // Call detour + + // Do the reverse process and retrieve the stored stuff + $"mov r9, 0x{caveAllocation:x8}", + "mov rcx, [r9]", + "mov rdx, [r9+0x8]", + + // Plop 'rax' (our return value, the path size) into r8, so it's the third argument for the subsequent Crc32() call + "mov r8, rax", + ], "Pap Redirection" + ); + + _hooks.Add(hookAddress, hook); + hook.Enable(); // Now we're adjusting every single reference to the stack allocated 'path' to our substantially bigger 'stringLoc' - foreach (var stackAccess in stackAccesses) - { - var hookAddr = new IntPtr((long)stackAccess.IP + stackAccess.Length); - - if (Hooks.ContainsKey(hookAddr)) - { - // Hook already exists, means there's reuse of the same stack address across 2 GetResourceAsync; just skip - continue; - } - - var targetRegister = stackAccess.Op0Register.ToString().ToLower(); - var hook = new AsmHook( - hookAddr, - [ - "use64", - $"mov {targetRegister}, 0x{stringLoc:x8}", - ], "Pap Stack Accesses" - ); - - Hooks.Add(hookAddr, hook); - hook.Enable(); - } + UpdatePathAddresses(stackAccesses, stringAllocation); + } + } + + private void UpdatePathAddresses(IEnumerable stackAccesses, nint stringAllocation) + { + foreach (var stackAccess in stackAccesses) + { + var hookAddress = new IntPtr((long)stackAccess.IP + stackAccess.Length); + + // Hook already exists, means there's reuse of the same stack address across 2 GetResourceAsync; just skip + if (_hooks.ContainsKey(hookAddress)) + continue; + + var targetRegister = stackAccess.Op0Register.ToString().ToLower(); + var hook = new AsmHook( + hookAddress, + [ + "use64", + $"mov {targetRegister}, 0x{stringAllocation:x8}", + ], "Pap Stack Accesses" + ); + + _hooks.Add(hookAddress, hook); + hook.Enable(); } - - - } private static IEnumerable ScanStackAccesses(IEnumerable instructions, Instruction hookPoint) { return instructions.Where(instr => - instr.Code == hookPoint.Code - && instr.Op0Kind == hookPoint.Op0Kind - && instr.Op1Kind == hookPoint.Op1Kind - && instr.MemoryBase == hookPoint.MemoryBase - && instr.MemoryDisplacement64 == hookPoint.MemoryDisplacement64) - .GroupBy(instr => instr.IP) - .Select(grp => grp.First()); + instr.Code == hookPoint.Code + && instr.Op0Kind == hookPoint.Op0Kind + && instr.Op1Kind == hookPoint.Op1Kind + && instr.MemoryBase == hookPoint.MemoryBase + && instr.MemoryDisplacement64 == hookPoint.MemoryDisplacement64) + .GroupBy(instr => instr.IP) + .Select(grp => grp.First()); } // This is utterly fucked and hardcoded, but, again, it works // Might be a neat idea for a more versatile kind of signature though - private static IEnumerable ScanPapHookPoints(List funcInstructions) + private static IEnumerable ScanPapHookPoints(Instruction[] funcInstructions) { - for (var i = 0; i < funcInstructions.Count - 8; i++) + for (var i = 0; i < funcInstructions.Length - 8; i++) { - if (funcInstructions[i .. (i + 8)] is + if (funcInstructions.AsSpan(i, 8) is [ - {Code : Code.Lea_r64_m}, - {Code : Code.Lea_r64_m}, - {Mnemonic: Mnemonic.Call}, - {Code : Code.Lea_r64_m}, - {Mnemonic: Mnemonic.Call}, - {Code : Code.Lea_r64_m}, + { Code : Code.Lea_r64_m }, + { Code : Code.Lea_r64_m }, + { Mnemonic: Mnemonic.Call }, + { Code : Code.Lea_r64_m }, + { Mnemonic: Mnemonic.Call }, + { Code : Code.Lea_r64_m }, .., ] ) - { yield return funcInstructions[i]; - } } } - private unsafe IntPtr NativeAlloc(nuint size) + private unsafe nint NativeAlloc(nuint size) { - var caveLoc = new IntPtr(NativeMemory.Alloc(size)); - NativeAllocList.Add(caveLoc); + var caveLoc = (nint)NativeMemory.Alloc(size); + _nativeAllocList.Add(caveLoc); return caveLoc; } - private static unsafe void NativeFree(IntPtr mem) - { - NativeMemory.Free(mem.ToPointer()); - } + private static unsafe void NativeFree(nint mem) + => NativeMemory.Free((void*)mem); public void Dispose() { - Scanner.Dispose(); - - foreach (var hook in Hooks.Values) + _scanner.Dispose(); + + foreach (var hook in _hooks.Values) { hook.Disable(); hook.Dispose(); } - - Hooks.Clear(); - - foreach (var mem in NativeAllocList) - { - NativeFree(mem); - } - NativeAllocList.Clear(); + _hooks.Clear(); + + foreach (var mem in _nativeAllocList) + NativeFree(mem); + + _nativeAllocList.Clear(); } } diff --git a/Penumbra/Interop/Hooks/ResourceLoading/ResourceLoader.cs b/Penumbra/Interop/Hooks/ResourceLoading/ResourceLoader.cs index cf87aa2b..002846fa 100644 --- a/Penumbra/Interop/Hooks/ResourceLoading/ResourceLoader.cs +++ b/Penumbra/Interop/Hooks/ResourceLoading/ResourceLoader.cs @@ -51,13 +51,13 @@ public unsafe class ResourceLoader : IDisposable, IService if (!resolvedPath.HasValue || !Utf8GamePath.FromByteString(resolvedPath.Value.InternalName, out var utf8ResolvedPath)) { - PapRequested?.Invoke(gamePath, gamePath, _resolvedData); + PapRequested?.Invoke(gamePath); return length; } NativeMemory.Copy(utf8ResolvedPath.Path.Path, path, (nuint)utf8ResolvedPath.Length); path[utf8ResolvedPath.Length] = 0; - PapRequested?.Invoke(gamePath, utf8ResolvedPath, _resolvedData); + PapRequested?.Invoke(gamePath); return utf8ResolvedPath.Length; }