From 5c7c6fc7d64e7237b82ae210f4d71e4d0f7b3bbc Mon Sep 17 00:00:00 2001 From: goat Date: Tue, 15 Oct 2019 20:55:03 +0900 Subject: [PATCH] First resource manager hooks --- Dalamud.Injector/Program.cs | 15 +- Dalamud/Dalamud.cs | 4 + Dalamud/Game/Internal/Framework.cs | 7 + .../Game/Internal/Resource/ResourceManager.cs | 211 ++++++++++++++++++ .../ResourceManagerAddressResolver.cs | 20 ++ 5 files changed, 256 insertions(+), 1 deletion(-) create mode 100644 Dalamud/Game/Internal/Resource/ResourceManager.cs create mode 100644 Dalamud/Game/Internal/Resource/ResourceManagerAddressResolver.cs diff --git a/Dalamud.Injector/Program.cs b/Dalamud.Injector/Program.cs index 0bac232dc..dfa91a07f 100644 --- a/Dalamud.Injector/Program.cs +++ b/Dalamud.Injector/Program.cs @@ -22,7 +22,20 @@ namespace Dalamud.Injector { int pid = int.Parse(args[0]); Process process = null; - process = pid == -1 ? Process.GetProcessesByName("ffxiv_dx11")[0] : Process.GetProcessById(pid); + + switch (pid) { + case -1: + process = Process.GetProcessesByName("ffxiv_dx11")[0]; + break; + case -2: + process = Process.Start( + "C:\\Program Files (x86)\\SquareEnix\\FINAL FANTASY XIV - A Realm Reborn\\game\\ffxiv_dx11.exe", + "DEV.TestSID=5fa077c389a61c4a45ea35153162753d7cdb34268cc38c9e206859a7 DEV.UseSqPack=1 DEV.DataPathType=1 DEV.LobbyHost01=127.0.0.1 DEV.LobbyPort01=54994 DEV.LobbyHost02=127.0.0.1 DEV.LobbyPort02=54994 DEV.LobbyHost03=127.0.0.1 DEV.LobbyPort03=54994 DEV.LobbyHost04=127.0.0.1 DEV.LobbyPort04=54994 DEV.LobbyHost05=127.0.0.1 DEV.LobbyPort05=54994 DEV.LobbyHost06=127.0.0.1 DEV.LobbyPort06=54994 DEV.LobbyHost07=127.0.0.1 DEV.LobbyPort07=54994 DEV.LobbyHost08=127.0.0.1 DEV.LobbyPort08=54994 SYS.Region=0 language=1 version=1.0.0.0 DEV.MaxEntitledExpansionID=2 DEV.GMServerHost=127.0.0.1 DEV.GameQuitMessageBox=0"); + break; + default: + process = Process.GetProcessById(pid); + break; + } var startInfo = JsonConvert.DeserializeObject(Encoding.UTF8.GetString(Convert.FromBase64String(args[1]))); startInfo.WorkingDirectory = Directory.GetCurrentDirectory(); diff --git a/Dalamud/Dalamud.cs b/Dalamud/Dalamud.cs index 4a030f29b..ac2c028f1 100644 --- a/Dalamud/Dalamud.cs +++ b/Dalamud/Dalamud.cs @@ -37,7 +37,11 @@ namespace Dalamud { public readonly ClientState ClientState; + public readonly DalamudStartInfo StartInfo; + public Dalamud(DalamudStartInfo info) { + this.StartInfo = info; + this.baseDirectory = info.WorkingDirectory; this.unloadSignal = new ManualResetEvent(false); diff --git a/Dalamud/Game/Internal/Framework.cs b/Dalamud/Game/Internal/Framework.cs index 229b6817b..487ff699a 100644 --- a/Dalamud/Game/Internal/Framework.cs +++ b/Dalamud/Game/Internal/Framework.cs @@ -6,6 +6,7 @@ using Dalamud.Game.Internal.Libc; using Dalamud.Game.Internal.Network; using Dalamud.Hooking; using Serilog; +using Dalamud.Game.Internal.File; namespace Dalamud.Game.Internal { public sealed class Framework : IDisposable { @@ -29,6 +30,8 @@ namespace Dalamud.Game.Internal { public GameGui Gui { get; private set; } public GameNetwork Network { get; private set; } + + public ResourceManager Resource { get; private set; } public LibcFunction Libc { get; private set; } @@ -52,6 +55,8 @@ namespace Dalamud.Game.Internal { Gui = new GameGui(Address.GuiManager, scanner, dalamud); Network = new GameNetwork(dalamud, scanner); + + Resource = new ResourceManager(dalamud, scanner); } private void HookVTable() { @@ -70,6 +75,7 @@ namespace Dalamud.Game.Internal { public void Enable() { Gui.Enable(); Network.Enable(); + //Resource.Enable(); this.updateHook.Enable(); } @@ -77,6 +83,7 @@ namespace Dalamud.Game.Internal { public void Dispose() { Gui.Dispose(); Network.Dispose(); + //Resource.Dispose(); this.updateHook.Dispose(); } diff --git a/Dalamud/Game/Internal/Resource/ResourceManager.cs b/Dalamud/Game/Internal/Resource/ResourceManager.cs new file mode 100644 index 000000000..91196fcfd --- /dev/null +++ b/Dalamud/Game/Internal/Resource/ResourceManager.cs @@ -0,0 +1,211 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; +using Dalamud.Game.Internal.Libc; +using Dalamud.Hooking; +using Serilog; + +namespace Dalamud.Game.Internal.File +{ + public class ResourceManager { + [UnmanagedFunctionPointer(CallingConvention.ThisCall)] + private delegate IntPtr GetResourceAsyncDelegate(IntPtr manager, IntPtr a2, IntPtr a3, IntPtr a4, IntPtr a5, IntPtr a6, byte a7); + private readonly Hook getResourceAsyncHook; + + [UnmanagedFunctionPointer(CallingConvention.ThisCall)] + private delegate IntPtr GetResourceSyncDelegate(IntPtr manager, IntPtr a2, IntPtr a3, IntPtr a4, IntPtr a5, IntPtr a6); + private readonly Hook getResourceSyncHook; + + private ResourceManagerAddressResolver Address { get; } + private readonly Dalamud dalamud; + + class ResourceHandleHookInfo { + public string Path { get; set; } + public Stream DetourFile { get; set; } + } + + private Dictionary resourceHookMap = new Dictionary(); + + public ResourceManager(Dalamud dalamud, SigScanner scanner) { + this.dalamud = dalamud; + Address = new ResourceManagerAddressResolver(); + Address.Setup(scanner); + + Log.Verbose("===== R E S O U R C E M A N A G E R ====="); + Log.Verbose("GetResourceAsync address {GetResourceAsync}", Address.GetResourceAsync); + Log.Verbose("GetResourceSync address {GetResourceSync}", Address.GetResourceSync); + + this.getResourceAsyncHook = + new Hook(Address.GetResourceAsync, + new GetResourceAsyncDelegate(GetResourceAsyncDetour), + this); + + this.getResourceSyncHook = + new Hook(Address.GetResourceSync, + new GetResourceSyncDelegate(GetResourceSyncDetour), + this); + + } + + public void Enable() { + this.getResourceAsyncHook.Enable(); + this.getResourceSyncHook.Enable(); + } + + public void Dispose() { + this.getResourceAsyncHook.Dispose(); + this.getResourceSyncHook.Dispose(); + } + + private IntPtr GetResourceAsyncDetour(IntPtr manager, IntPtr a2, IntPtr a3, IntPtr a4, IntPtr a5, IntPtr a6, byte a7) { + + try { + var path = Marshal.PtrToStringAnsi(a5); + + var resourceHandle = this.getResourceAsyncHook.Original(manager, a2, a3, a4, IntPtr.Zero, a6, a7); + //var resourceHandle = IntPtr.Zero; + + Log.Verbose("GetResourceAsync CALL - this:{0} a2:{1} a3:{2} a4:{3} a5:{4} a6:{5} a7:{6} => RET:{7}", manager, a2, a3, a4, a5, a6, a7, resourceHandle); + + Log.Verbose($"->{path}"); + + HandleGetResourceHookAcquire(resourceHandle, path); + + return resourceHandle; + } catch (Exception ex) { + Log.Error(ex, "Exception on ReadResourceAsync hook."); + + return this.getResourceAsyncHook.Original(manager, a2, a3, a4, a5, a6, a7); + } + } + + private void DumpMem(IntPtr address, int len = 512) { + if (address == IntPtr.Zero) + return; + + var data = new byte[len]; + Marshal.Copy(address, data, 0, len); + + Log.Verbose($"MEMDMP at {address.ToInt64():X} for {len:X}\n{ByteArrayToHex(data)}"); + } + + private IntPtr GetResourceSyncDetour(IntPtr manager, IntPtr a2, IntPtr a3, IntPtr a4, IntPtr a5, IntPtr a6) { + + try { + var resourceHandle = this.getResourceSyncHook.Original(manager, a2, a3, a4, a5, a6); + + Log.Verbose("GetResourceSync CALL - this:{0} a2:{1} a3:{2} a4:{3} a5:{4} a6:{5} => RET:{6}", manager, a2, a3, a4, a5, a6, resourceHandle); + + var path = Marshal.PtrToStringAnsi(a5); + + Log.Verbose($"->{path}"); + + HandleGetResourceHookAcquire(resourceHandle, path); + + return resourceHandle; + } catch (Exception ex) { + Log.Error(ex, "Exception on ReadResourceSync hook."); + + return this.getResourceSyncHook.Original(manager, a2, a3, a4, a5, a6); + } + } + + private void HandleGetResourceHookAcquire(IntPtr handlePtr, string path) { + if (FilePathHasInvalidChars(path)) + return; + + if (this.resourceHookMap.ContainsKey(handlePtr)) { + Log.Verbose($"-> Handle {handlePtr.ToInt64():X}({path}) was cached!"); + return; + } + + var hookInfo = new ResourceHandleHookInfo { + Path = path + }; + + var hookPath = Path.Combine(this.dalamud.StartInfo.WorkingDirectory, "ResourceHook", path); + + if (System.IO.File.Exists(hookPath)) { + hookInfo.DetourFile = new FileStream(hookPath, FileMode.Open); + Log.Verbose("-> Added resource hook detour at {0}", hookPath); + } + + this.resourceHookMap.Add(handlePtr, hookInfo); + } + + public static bool FilePathHasInvalidChars(string path) + { + + return (!string.IsNullOrEmpty(path) && path.IndexOfAny(System.IO.Path.GetInvalidPathChars()) >= 0); + } + + public static string ByteArrayToHex(byte[] bytes, int offset = 0, int bytesPerLine = 16) + { + if (bytes == null) + { + return string.Empty; + } + + var hexChars = "0123456789ABCDEF".ToCharArray(); + + var offsetBlock = 8 + 3; + var byteBlock = offsetBlock + bytesPerLine * 3 + (bytesPerLine - 1) / 8 + 2; + var lineLength = byteBlock + bytesPerLine + Environment.NewLine.Length; + + var line = (new string(' ', lineLength - Environment.NewLine.Length) + Environment.NewLine).ToCharArray(); + var numLines = (bytes.Length + bytesPerLine - 1) / bytesPerLine; + + var sb = new StringBuilder(numLines * lineLength); + + for (var i = 0; i < bytes.Length; i += bytesPerLine) + { + var h = i + offset; + + line[0] = hexChars[(h >> 28) & 0xF]; + line[1] = hexChars[(h >> 24) & 0xF]; + line[2] = hexChars[(h >> 20) & 0xF]; + line[3] = hexChars[(h >> 16) & 0xF]; + line[4] = hexChars[(h >> 12) & 0xF]; + line[5] = hexChars[(h >> 8) & 0xF]; + line[6] = hexChars[(h >> 4) & 0xF]; + line[7] = hexChars[(h >> 0) & 0xF]; + + var hexColumn = offsetBlock; + var charColumn = byteBlock; + + for (var j = 0; j < bytesPerLine; j++) + { + if (j > 0 && (j & 7) == 0) + { + hexColumn++; + } + + if (i + j >= bytes.Length) + { + line[hexColumn] = ' '; + line[hexColumn + 1] = ' '; + line[charColumn] = ' '; + } + else + { + var by = bytes[i + j]; + line[hexColumn] = hexChars[(by >> 4) & 0xF]; + line[hexColumn + 1] = hexChars[by & 0xF]; + line[charColumn] = by < 32 ? '.' : (char)by; + } + + hexColumn += 3; + charColumn++; + } + + sb.Append(line); + } + + return sb.ToString().TrimEnd(Environment.NewLine.ToCharArray()); + } + } +} diff --git a/Dalamud/Game/Internal/Resource/ResourceManagerAddressResolver.cs b/Dalamud/Game/Internal/Resource/ResourceManagerAddressResolver.cs new file mode 100644 index 000000000..2be49b4ac --- /dev/null +++ b/Dalamud/Game/Internal/Resource/ResourceManagerAddressResolver.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Dalamud.Game.Internal.File +{ + class ResourceManagerAddressResolver : BaseAddressResolver + { + public IntPtr GetResourceAsync { get; private set; } + public IntPtr GetResourceSync { get; private set; } + + protected override void Setup64Bit(SigScanner sig) { + GetResourceAsync = sig.ScanText("48 89 5C 24 08 48 89 54 24 10 57 48 83 EC 20 B8 03 00 00 00 48 8B F9 86 82 A1 00 00 00 48 8B 5C 24 38 B8 01 00 00 00 87 83 90 00 00 00 85 C0 74"); + GetResourceSync = sig.ScanText("48 89 5C 24 08 48 89 6C 24 10 48 89 74 24 18 57 41 54 41 55 41 56 41 57 48 83 EC 30 48 8B F9 49 8B E9 48 83 C1 30 4D 8B F0 4C 8B EA FF 15 CE F6"); + //ReadResourceSync = sig.ScanText("48 89 74 24 18 57 48 83 EC 50 8B F2 49 8B F8 41 0F B7 50 02 8B CE E8 ?? ?? 7A FF 0F B7 57 02 8D 42 89 3D 5F 02 00 00 0F 87 60 01 00 00 4C 8D 05"); + } + } +}