using System; using System.Runtime.InteropServices; using Dalamud.Game.Text.SeStringHandling; using Dalamud.Hooking; namespace NominaOcculta { internal class GameFunctions : IDisposable { private static class Signatures { internal const string GenerateName = "E8 ?? ?? ?? ?? 48 8D 8B ?? ?? ?? ?? E8 ?? ?? ?? ?? 48 85 C0 74 1B 48 8D 8B ?? ?? ?? ?? E8 ?? ?? ?? ?? 48 8B 8B ?? ?? ?? ?? 48 8B D0 E8 ?? ?? ?? ?? 48 8B CB 48 8B 7C 24"; internal const string Utf8StringCtor = "E8 ?? ?? ?? ?? 44 2B F7"; internal const string Utf8StringDtor = "80 79 21 00 75 12"; internal const string AtkTextNodeSetText = "E8 ?? ?? ?? ?? 49 8B FC"; internal const string LoadExd = "40 53 56 57 48 81 EC ?? ?? ?? ?? 48 8B 05 ?? ?? ?? ?? 48 33 C4 48 89 84 24 ?? ?? ?? ?? 41 0F B6 D9"; } private delegate IntPtr GenerateNameDelegate(int race, int clan, int gender, IntPtr first, IntPtr last); private delegate IntPtr Utf8StringCtorDelegate(IntPtr memory); private delegate void Utf8StringDtorDelegate(IntPtr memory); private delegate void AtkTextNodeSetTextDelegate(IntPtr node, IntPtr text); private delegate byte LoadExdDelegate(IntPtr a1, string sheetName, byte a3, byte a4); private delegate IntPtr GetExcelModuleDelegate(IntPtr uiModule); private Utf8StringCtorDelegate Utf8StringCtor { get; } private Utf8StringDtorDelegate Utf8StringDtor { get; } private GenerateNameDelegate InternalGenerateName { get; } private LoadExdDelegate LoadExd { get; } private Hook AtkTextNodeSetTextHook { get; } internal delegate void AtkTextNodeSetTextEventDelegate(IntPtr node, IntPtr text, ref SeString? overwrite); internal event AtkTextNodeSetTextEventDelegate? AtkTextNodeSetText; private Plugin Plugin { get; } private IntPtr First { get; } private IntPtr Last { get; } internal GameFunctions(Plugin plugin) { this.Plugin = plugin; this.Utf8StringCtor = Marshal.GetDelegateForFunctionPointer(this.Plugin.SigScanner.ScanText(Signatures.Utf8StringCtor)); this.Utf8StringDtor = Marshal.GetDelegateForFunctionPointer(this.Plugin.SigScanner.ScanText(Signatures.Utf8StringDtor)); this.InternalGenerateName = Marshal.GetDelegateForFunctionPointer(this.Plugin.SigScanner.ScanText(Signatures.GenerateName)); this.LoadExd = Marshal.GetDelegateForFunctionPointer(this.Plugin.SigScanner.ScanText(Signatures.LoadExd)); this.AtkTextNodeSetTextHook = new Hook( this.Plugin.SigScanner.ScanText(Signatures.AtkTextNodeSetText), this.OnAtkTextNodeSetText ); this.AtkTextNodeSetTextHook.Enable(); this.First = Marshal.AllocHGlobal(128); this.Last = Marshal.AllocHGlobal(128); this.Utf8StringCtor(this.First); this.Utf8StringCtor(this.Last); } public void Dispose() { this.Utf8StringDtor(this.Last); this.Utf8StringDtor(this.First); Marshal.FreeHGlobal(this.Last); Marshal.FreeHGlobal(this.First); this.AtkTextNodeSetTextHook.Dispose(); } private unsafe void OnAtkTextNodeSetText(IntPtr node, IntPtr text) { SeString? overwrite = null; this.AtkTextNodeSetText?.Invoke(node, text, ref overwrite); if (overwrite != null) { fixed (byte* newText = overwrite.Encode().Terminate()) { this.AtkTextNodeSetTextHook.Original(node, (IntPtr) newText); } return; } this.AtkTextNodeSetTextHook.Original(node, text); } public string? GenerateName(int race, int clan, int gender) { this.InternalGenerateName(race, clan, gender, this.First, this.Last); var first = Marshal.PtrToStringUTF8(Marshal.ReadIntPtr(this.First)); var last = Marshal.PtrToStringUTF8(Marshal.ReadIntPtr(this.Last)); if (string.IsNullOrEmpty(first) || string.IsNullOrEmpty(last)) { return null; } return $"{first} {last}"; } public unsafe void LoadSheet(string name) { var ui = this.Plugin.Common.Functions.GetUiModule(); var getExcelModulePtr = *(*(IntPtr**) ui + 5); var getExcelModule = Marshal.GetDelegateForFunctionPointer(getExcelModulePtr); var excelModule = getExcelModule(ui); var exdModule = *(IntPtr*) (excelModule + 8); var excel = *(IntPtr*) (exdModule + 0x20); this.LoadExd(excel, name, 0, 1); } } }