mirror of
https://github.com/xivdev/Penumbra.git
synced 2025-12-16 05:34:25 +01:00
Some further cleanup.
This commit is contained in:
parent
ee5a21f7a2
commit
ceaa9ca29a
3 changed files with 127 additions and 132 deletions
|
|
@ -5,17 +5,26 @@ namespace Penumbra.Interop.Hooks.ResourceLoading;
|
||||||
public sealed class PapHandler(PapRewriter.PapResourceHandlerPrototype papResourceHandler) : IDisposable
|
public sealed class PapHandler(PapRewriter.PapResourceHandlerPrototype papResourceHandler) : IDisposable
|
||||||
{
|
{
|
||||||
private readonly PapRewriter _papRewriter = new(papResourceHandler);
|
private readonly PapRewriter _papRewriter = new(papResourceHandler);
|
||||||
|
|
||||||
public void Enable()
|
public void Enable()
|
||||||
{
|
{
|
||||||
_papRewriter.Rewrite(Sigs.LoadAlwaysResidentMotionPacks);
|
ReadOnlySpan<string> signatures =
|
||||||
_papRewriter.Rewrite(Sigs.LoadWeaponDependentResidentMotionPacks);
|
[
|
||||||
_papRewriter.Rewrite(Sigs.LoadInitialResidentMotionPacks);
|
Sigs.LoadAlwaysResidentMotionPacks,
|
||||||
_papRewriter.Rewrite(Sigs.LoadMotionPacks);
|
Sigs.LoadWeaponDependentResidentMotionPacks,
|
||||||
_papRewriter.Rewrite(Sigs.LoadMotionPacks2);
|
Sigs.LoadInitialResidentMotionPacks,
|
||||||
_papRewriter.Rewrite(Sigs.LoadMigratoryMotionPack);
|
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()
|
public void Dispose()
|
||||||
=> _papRewriter.Dispose();
|
=> _papRewriter.Dispose();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
using Dalamud.Hooking;
|
using Dalamud.Hooking;
|
||||||
using Iced.Intel;
|
using Iced.Intel;
|
||||||
|
using OtterGui;
|
||||||
using Penumbra.String.Classes;
|
using Penumbra.String.Classes;
|
||||||
|
|
||||||
namespace Penumbra.Interop.Hooks.ResourceLoading;
|
namespace Penumbra.Interop.Hooks.ResourceLoading;
|
||||||
|
|
@ -7,175 +8,160 @@ namespace Penumbra.Interop.Hooks.ResourceLoading;
|
||||||
public sealed class PapRewriter(PapRewriter.PapResourceHandlerPrototype papResourceHandler) : IDisposable
|
public sealed class PapRewriter(PapRewriter.PapResourceHandlerPrototype papResourceHandler) : IDisposable
|
||||||
{
|
{
|
||||||
public unsafe delegate int PapResourceHandlerPrototype(void* self, byte* path, int length);
|
public unsafe delegate int PapResourceHandlerPrototype(void* self, byte* path, int length);
|
||||||
|
|
||||||
private PeSigScanner Scanner { get; } = new();
|
private readonly PeSigScanner _scanner = new();
|
||||||
private Dictionary<IntPtr, AsmHook> Hooks { get; }= [];
|
private readonly Dictionary<nint, AsmHook> _hooks = [];
|
||||||
private List<IntPtr> NativeAllocList { get; } = [];
|
private readonly List<nint> _nativeAllocList = [];
|
||||||
private PapResourceHandlerPrototype PapResourceHandler { get; } = papResourceHandler;
|
|
||||||
|
|
||||||
public void Rewrite(string sig)
|
public void Rewrite(string sig)
|
||||||
{
|
{
|
||||||
if (!Scanner.TryScanText(sig, out var addr))
|
if (!_scanner.TryScanText(sig, out var address))
|
||||||
{
|
throw new Exception($"Signature [{sig}] could not be found.");
|
||||||
throw new Exception($"Sig is fucked: {sig}");
|
|
||||||
}
|
|
||||||
|
|
||||||
var funcInstructions = Scanner.GetFunctionInstructions(addr).ToList();
|
var funcInstructions = _scanner.GetFunctionInstructions(address).ToArray();
|
||||||
|
var hookPoints = ScanPapHookPoints(funcInstructions).ToList();
|
||||||
var hookPoints = ScanPapHookPoints(funcInstructions).ToList();
|
|
||||||
|
|
||||||
foreach (var hookPoint in hookPoints)
|
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 also remove all the 'hookPoints' from 'stackAccesses'.
|
||||||
// We'll need to grab our true hook point; the location where we can change the path at our leisure.
|
// We're handling the char *path redirection here, so we don't want this to hit the later code
|
||||||
// This is going to be the first call instruction after our 'hookPoint', so, we'll find that.
|
foreach (var hp in hookPoints)
|
||||||
// Pretty scuffed, this might need a refactoring at some point.
|
stackAccesses.RemoveAll(instr => instr.IP == hp.IP);
|
||||||
// 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'.
|
var detourPointer = Marshal.GetFunctionPointerForDelegate(papResourceHandler);
|
||||||
// We're handling the char *path redirection here, so we don't want this to hit the later code
|
var targetRegister = hookPoint.Op0Register.ToString().ToLower();
|
||||||
foreach (var hp in hookPoints)
|
var hookAddress = new IntPtr((long)detourPoint.IP);
|
||||||
{
|
|
||||||
stackAccesses.RemoveAll(instr => instr.IP == hp.IP);
|
|
||||||
}
|
|
||||||
|
|
||||||
var pDetour = Marshal.GetFunctionPointerForDelegate(PapResourceHandler);
|
var caveAllocation = NativeAlloc(16);
|
||||||
var targetRegister = hookPoint.Op0Register.ToString().ToLower();
|
var hook = new AsmHook(
|
||||||
var hookAddr = new IntPtr((long)detourPoint.IP);
|
hookAddress,
|
||||||
|
[
|
||||||
|
"use64",
|
||||||
|
$"mov {targetRegister}, 0x{stringAllocation:x8}", // Move our char *path into the relevant register (rdx)
|
||||||
|
|
||||||
var caveLoc = NativeAlloc(16);
|
// After this asm stub, we have a call to Crc32(); since r9 is a volatile, unused register, we can use it ourselves
|
||||||
var hook = new AsmHook(
|
// We're essentially storing the original 2 arguments ('this', 'path'), in case they get mangled in our call
|
||||||
hookAddr,
|
// We technically don't need to save rdx ('path'), since it'll be stringLoc, but eh
|
||||||
[
|
$"mov r9, 0x{caveAllocation:x8}",
|
||||||
"use64",
|
"mov [r9], rcx",
|
||||||
$"mov {targetRegister}, 0x{stringLoc:x8}", // Move our char *path into the relevant register (rdx)
|
"mov [r9+0x8], 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);
|
// We can use 'rax' here too since it's also volatile, and it'll be overwritten by Crc32()'s return anyway
|
||||||
hook.Enable();
|
$"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'
|
// Now we're adjusting every single reference to the stack allocated 'path' to our substantially bigger 'stringLoc'
|
||||||
foreach (var stackAccess in stackAccesses)
|
UpdatePathAddresses(stackAccesses, stringAllocation);
|
||||||
{
|
}
|
||||||
var hookAddr = new IntPtr((long)stackAccess.IP + stackAccess.Length);
|
}
|
||||||
|
|
||||||
if (Hooks.ContainsKey(hookAddr))
|
private void UpdatePathAddresses(IEnumerable<Instruction> stackAccesses, nint stringAllocation)
|
||||||
{
|
{
|
||||||
// Hook already exists, means there's reuse of the same stack address across 2 GetResourceAsync; just skip
|
foreach (var stackAccess in stackAccesses)
|
||||||
continue;
|
{
|
||||||
}
|
var hookAddress = new IntPtr((long)stackAccess.IP + stackAccess.Length);
|
||||||
|
|
||||||
var targetRegister = stackAccess.Op0Register.ToString().ToLower();
|
// Hook already exists, means there's reuse of the same stack address across 2 GetResourceAsync; just skip
|
||||||
var hook = new AsmHook(
|
if (_hooks.ContainsKey(hookAddress))
|
||||||
hookAddr,
|
continue;
|
||||||
[
|
|
||||||
"use64",
|
var targetRegister = stackAccess.Op0Register.ToString().ToLower();
|
||||||
$"mov {targetRegister}, 0x{stringLoc:x8}",
|
var hook = new AsmHook(
|
||||||
], "Pap Stack Accesses"
|
hookAddress,
|
||||||
);
|
[
|
||||||
|
"use64",
|
||||||
Hooks.Add(hookAddr, hook);
|
$"mov {targetRegister}, 0x{stringAllocation:x8}",
|
||||||
hook.Enable();
|
], "Pap Stack Accesses"
|
||||||
}
|
);
|
||||||
|
|
||||||
|
_hooks.Add(hookAddress, hook);
|
||||||
|
hook.Enable();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static IEnumerable<Instruction> ScanStackAccesses(IEnumerable<Instruction> instructions, Instruction hookPoint)
|
private static IEnumerable<Instruction> ScanStackAccesses(IEnumerable<Instruction> instructions, Instruction hookPoint)
|
||||||
{
|
{
|
||||||
return instructions.Where(instr =>
|
return instructions.Where(instr =>
|
||||||
instr.Code == hookPoint.Code
|
instr.Code == hookPoint.Code
|
||||||
&& instr.Op0Kind == hookPoint.Op0Kind
|
&& instr.Op0Kind == hookPoint.Op0Kind
|
||||||
&& instr.Op1Kind == hookPoint.Op1Kind
|
&& instr.Op1Kind == hookPoint.Op1Kind
|
||||||
&& instr.MemoryBase == hookPoint.MemoryBase
|
&& instr.MemoryBase == hookPoint.MemoryBase
|
||||||
&& instr.MemoryDisplacement64 == hookPoint.MemoryDisplacement64)
|
&& instr.MemoryDisplacement64 == hookPoint.MemoryDisplacement64)
|
||||||
.GroupBy(instr => instr.IP)
|
.GroupBy(instr => instr.IP)
|
||||||
.Select(grp => grp.First());
|
.Select(grp => grp.First());
|
||||||
}
|
}
|
||||||
|
|
||||||
// This is utterly fucked and hardcoded, but, again, it works
|
// This is utterly fucked and hardcoded, but, again, it works
|
||||||
// Might be a neat idea for a more versatile kind of signature though
|
// Might be a neat idea for a more versatile kind of signature though
|
||||||
private static IEnumerable<Instruction> ScanPapHookPoints(List<Instruction> funcInstructions)
|
private static IEnumerable<Instruction> 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 },
|
||||||
{Code : Code.Lea_r64_m},
|
{ Code : Code.Lea_r64_m },
|
||||||
{Mnemonic: Mnemonic.Call},
|
{ Mnemonic: Mnemonic.Call },
|
||||||
{Code : Code.Lea_r64_m},
|
{ Code : Code.Lea_r64_m },
|
||||||
{Mnemonic: Mnemonic.Call},
|
{ Mnemonic: Mnemonic.Call },
|
||||||
{Code : Code.Lea_r64_m},
|
{ Code : Code.Lea_r64_m },
|
||||||
..,
|
..,
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
{
|
|
||||||
yield return funcInstructions[i];
|
yield return funcInstructions[i];
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private unsafe IntPtr NativeAlloc(nuint size)
|
private unsafe nint NativeAlloc(nuint size)
|
||||||
{
|
{
|
||||||
var caveLoc = new IntPtr(NativeMemory.Alloc(size));
|
var caveLoc = (nint)NativeMemory.Alloc(size);
|
||||||
NativeAllocList.Add(caveLoc);
|
_nativeAllocList.Add(caveLoc);
|
||||||
|
|
||||||
return caveLoc;
|
return caveLoc;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static unsafe void NativeFree(IntPtr mem)
|
private static unsafe void NativeFree(nint mem)
|
||||||
{
|
=> NativeMemory.Free((void*)mem);
|
||||||
NativeMemory.Free(mem.ToPointer());
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
Scanner.Dispose();
|
_scanner.Dispose();
|
||||||
|
|
||||||
foreach (var hook in Hooks.Values)
|
foreach (var hook in _hooks.Values)
|
||||||
{
|
{
|
||||||
hook.Disable();
|
hook.Disable();
|
||||||
hook.Dispose();
|
hook.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
Hooks.Clear();
|
|
||||||
|
|
||||||
foreach (var mem in NativeAllocList)
|
|
||||||
{
|
|
||||||
NativeFree(mem);
|
|
||||||
}
|
|
||||||
|
|
||||||
NativeAllocList.Clear();
|
_hooks.Clear();
|
||||||
|
|
||||||
|
foreach (var mem in _nativeAllocList)
|
||||||
|
NativeFree(mem);
|
||||||
|
|
||||||
|
_nativeAllocList.Clear();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -51,13 +51,13 @@ public unsafe class ResourceLoader : IDisposable, IService
|
||||||
|
|
||||||
if (!resolvedPath.HasValue || !Utf8GamePath.FromByteString(resolvedPath.Value.InternalName, out var utf8ResolvedPath))
|
if (!resolvedPath.HasValue || !Utf8GamePath.FromByteString(resolvedPath.Value.InternalName, out var utf8ResolvedPath))
|
||||||
{
|
{
|
||||||
PapRequested?.Invoke(gamePath, gamePath, _resolvedData);
|
PapRequested?.Invoke(gamePath);
|
||||||
return length;
|
return length;
|
||||||
}
|
}
|
||||||
|
|
||||||
NativeMemory.Copy(utf8ResolvedPath.Path.Path, path, (nuint)utf8ResolvedPath.Length);
|
NativeMemory.Copy(utf8ResolvedPath.Path.Path, path, (nuint)utf8ResolvedPath.Length);
|
||||||
path[utf8ResolvedPath.Length] = 0;
|
path[utf8ResolvedPath.Length] = 0;
|
||||||
PapRequested?.Invoke(gamePath, utf8ResolvedPath, _resolvedData);
|
PapRequested?.Invoke(gamePath);
|
||||||
return utf8ResolvedPath.Length;
|
return utf8ResolvedPath.Length;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue