From f76f0e91d0812b25a04252b532434c146a7dbdb8 Mon Sep 17 00:00:00 2001 From: Infi Date: Sun, 30 Jun 2024 05:07:11 +0200 Subject: [PATCH] Update for 7.0, part 1 --- ChatTwo/ChatTwo.csproj | 5 +- ChatTwo/EmoteCache.cs | 7 +- ChatTwo/GameFunctions/Chat.cs | 88 +++++++------------- ChatTwo/GameFunctions/ChatBox.cs | 98 +++++++++++++++++++++++ ChatTwo/GameFunctions/Context.cs | 6 +- ChatTwo/GameFunctions/GameFunctions.cs | 62 ++++++++++---- ChatTwo/IpcManager.cs | 13 ++- ChatTwo/Message.cs | 12 +-- ChatTwo/MessageManager.cs | 18 ++--- ChatTwo/PayloadHandler.cs | 13 +-- ChatTwo/Plugin.cs | 13 +-- ChatTwo/TextureCache.cs | 8 +- ChatTwo/Ui/ChatLogWindow.cs | 14 ++-- ChatTwo/Ui/Debugger.cs | 2 +- ChatTwo/Ui/LegacyMessageImporterWindow.cs | 1 - ChatTwo/Ui/SettingsTabs/Database.cs | 2 +- ChatTwo/Util/AutoTranslate.cs | 2 +- ChatTwo/Util/DatePicker.cs | 1 - ChatTwo/Util/WrapperUtil.cs | 1 - ChatTwo/packages.lock.json | 12 +-- 20 files changed, 232 insertions(+), 146 deletions(-) create mode 100644 ChatTwo/GameFunctions/ChatBox.cs diff --git a/ChatTwo/ChatTwo.csproj b/ChatTwo/ChatTwo.csproj index 09b74b0..24d6841 100755 --- a/ChatTwo/ChatTwo.csproj +++ b/ChatTwo/ChatTwo.csproj @@ -1,6 +1,6 @@ - 1.26.4 + 1.27.0 net8.0-windows enable enable @@ -49,14 +49,13 @@ - + - diff --git a/ChatTwo/EmoteCache.cs b/ChatTwo/EmoteCache.cs index ac40dfc..aec3bf3 100644 --- a/ChatTwo/EmoteCache.cs +++ b/ChatTwo/EmoteCache.cs @@ -1,7 +1,8 @@ using System.Numerics; using System.Text.Json; using System.Text.Json.Serialization; -using Dalamud.Interface.Internal; +using Dalamud.Interface.Textures; +using Dalamud.Interface.Textures.TextureWraps; using Dalamud.Utility; using ImGuiNET; using SixLabors.ImageSharp; @@ -208,7 +209,7 @@ public static class EmoteCache if (image.Length <= 0) return; - Texture = await Plugin.Interface.UiBuilder.LoadImageAsync(image); + Texture = await Plugin.TextureProvider.CreateFromImageAsync(image); IsLoaded = true; } catch (Exception ex) @@ -294,7 +295,7 @@ public static class EmoteCache var buffer = new byte[4 * frame.Width * frame.Height]; frame.CopyPixelDataTo(buffer); - var tex = await Plugin.Interface.UiBuilder.LoadImageRawAsync(buffer, frame.Width, frame.Height, 4); + var tex = await Plugin.TextureProvider.CreateFromRawAsync(RawImageSpecification.Rgba32(frame.Width, frame.Height), buffer); frames.Add((tex, delay)); } diff --git a/ChatTwo/GameFunctions/Chat.cs b/ChatTwo/GameFunctions/Chat.cs index 3aac8ff..dd8aafa 100755 --- a/ChatTwo/GameFunctions/Chat.cs +++ b/ChatTwo/GameFunctions/Chat.cs @@ -11,7 +11,7 @@ using Dalamud.Hooking; using Dalamud.Memory; using Dalamud.Plugin.Services; using Dalamud.Utility.Signatures; -using FFXIVClientStructs.FFXIV.Client.Network; +using FFXIVClientStructs.FFXIV.Application.Network; using FFXIVClientStructs.FFXIV.Client.System.Framework; using FFXIVClientStructs.FFXIV.Client.System.String; using FFXIVClientStructs.FFXIV.Client.UI; @@ -32,37 +32,25 @@ internal sealed unsafe class Chat : IDisposable [Signature("E8 ?? ?? ?? ?? 48 8D 4D A0 8B F8")] private readonly delegate* unmanaged GetKeybindNative = null!; - [Signature("E8 ?? ?? ?? ?? 48 8D 4D 50 E8 ?? ?? ?? ?? 48 8B 17")] + [Signature("48 89 5C 24 ?? 48 89 74 24 ?? 57 48 83 EC ?? 48 8D B9 ?? ?? ?? ?? 33 C0")] private readonly delegate* unmanaged PrintTellNative = null!; [Signature("E8 ?? ?? ?? ?? 48 8D 4C 24 ?? E8 ?? ?? ?? ?? 48 8D 8C 24 ?? ?? ?? ?? E8 ?? ?? ?? ?? B0 01")] private readonly delegate* unmanaged SendTellNative = null!; - // TODO Replace with CS version after https://github.com/aers/FFXIVClientStructs/pull/911 - [Signature("E8 ?? ?? ?? ?? EB 0A 48 8D 4C 24 ?? E8 ?? ?? ?? ?? 48 8D 8D")] - private readonly delegate* unmanaged SanitiseString = null!; - // Client::UI::AddonChatLog.OnRefresh [Signature("40 53 56 57 48 81 EC ?? ?? ?? ?? 48 8B 05 ?? ?? ?? ?? 48 33 C4 48 89 84 24 ?? ?? ?? ?? 49 8B F0 8B FA", DetourName = nameof(ChatLogRefreshDetour))] private Hook? ChatLogRefreshHook { get; init; } private delegate byte ChatLogRefreshDelegate(nint log, ushort eventId, AtkValue* value); - // TODO Replace all with delegate hooks in API X - private Hook? ChangeChannelNameHook { get; init; } - private delegate nint ChangeChannelNameDelegate(nint agent); - - private Hook? ReplyInSelectedChatModeHook { get; init; } - private delegate void ReplyInSelectedChatModeDelegate(AgentInterface* agent); - - private Hook? SetChatLogTellTargetHook { get; init; } - private delegate byte SetChatLogTellTarget(nint a1, Utf8String* name, Utf8String* a3, ushort world, ulong contentId, ushort a6, byte a7); - - private Hook? EurekaContextMenuTellHook { get; init; } - private delegate void EurekaContextMenuTellDelegate(RaptureShellModule* param1, Utf8String* playerName, Utf8String* worldName, ushort world, ulong contentId, ushort param6); + private Hook ChangeChannelNameHook { get; init; } + private Hook? ReplyInSelectedChatModeHook { get; init; } + private Hook? SetChatLogTellTargetHook { get; init; } + private Hook? EurekaContextMenuTellHook { get; init; } // Pointers - [Signature("48 8D 15 ?? ?? ?? ?? 0F B6 C8 48 8D 05", ScanType = ScanType.StaticAddress)] + [Signature("48 8D 35 ?? ?? ?? ?? 8B 05", ScanType = ScanType.StaticAddress)] private readonly char* CurrentCharacter = null!; // Events @@ -91,16 +79,16 @@ internal sealed unsafe class Chat : IDisposable ChatLogRefreshHook?.Enable(); - ChangeChannelNameHook = Plugin.GameInteropProvider.HookFromAddress(AgentChatLog.Addresses.ChangeChannelName.Value, ChangeChannelNameDetour); + ChangeChannelNameHook = Plugin.GameInteropProvider.HookFromAddress(AgentChatLog.MemberFunctionPointers.ChangeChannelName, ChangeChannelNameDetour); ChangeChannelNameHook.Enable(); - ReplyInSelectedChatModeHook = Plugin.GameInteropProvider.HookFromAddress(RaptureShellModule.Addresses.ReplyInSelectedChatMode.Value, ReplyInSelectedChatModeDetour); + ReplyInSelectedChatModeHook = Plugin.GameInteropProvider.HookFromAddress(RaptureShellModule.MemberFunctionPointers.ReplyInSelectedChatMode, ReplyInSelectedChatModeDetour); ReplyInSelectedChatModeHook.Enable(); - SetChatLogTellTargetHook = Plugin.GameInteropProvider.HookFromAddress(RaptureShellModule.Addresses.SetContextTellTarget.Value, SetChatLogTellTargetDetour); + SetChatLogTellTargetHook = Plugin.GameInteropProvider.HookFromAddress(RaptureShellModule.MemberFunctionPointers.SetContextTellTarget, SetChatLogTellTargetDetour); SetChatLogTellTargetHook.Enable(); - EurekaContextMenuTellHook = Plugin.GameInteropProvider.HookFromAddress(RaptureShellModule.Addresses.SetContextTellTargetInForay.Value, EurekaContextMenuTell); + EurekaContextMenuTellHook = Plugin.GameInteropProvider.HookFromAddress(RaptureShellModule.MemberFunctionPointers.SetContextTellTargetInForay, EurekaContextMenuTell); EurekaContextMenuTellHook.Enable(); Plugin.Framework.Update += InterceptKeybinds; @@ -124,19 +112,13 @@ internal sealed unsafe class Chat : IDisposable internal string? GetLinkshellName(uint idx) { - var instance = InfoProxyLinkShell.Instance(); - var lsInfo = instance->GetLinkshellInfo(idx); - if (lsInfo == null) - return null; - - // TODO APIX: use infoproxychat - var utf = instance->GetLinkshellName(*(ulong**)lsInfo); + var utf = InfoProxyChat.Instance()->GetLinkShellName(idx); return utf == null ? null : MemoryHelper.ReadStringNullTerminated((nint) utf); } internal string? GetCrossLinkshellName(uint idx) { - var utf = InfoProxyCrossWorldLinkShell.Instance()->GetCrossworldLinkshellName(idx); + var utf = InfoProxyCrossWorldLinkshell.Instance()->GetCrossworldLinkshellName(idx); return utf == null ? null : utf->ToString(); } @@ -346,14 +328,11 @@ internal sealed unsafe class Chat : IDisposable private void Login() { - if (ChangeChannelNameHook == null) - return; - - var agent = Framework.Instance()->GetUiModule()->GetAgentModule()->GetAgentByInternalId(AgentId.ChatLog); + var agent = AgentChatLog.Instance(); if (agent == null) return; - ChangeChannelNameDetour((nint) agent); + ChangeChannelNameDetour(agent); } private byte ChatLogRefreshDetour(nint log, ushort eventId, AtkValue* value) @@ -368,11 +347,12 @@ internal sealed unsafe class Chat : IDisposable { // FIXME: this whole system sucks // FIXME v2: I hate everything about this, but it works + Plugin.Framework.RunOnTick(() => { string? input = null; - var utf8Bytes = MemoryHelper.ReadRaw((nint) CurrentCharacter, 2); + var utf8Bytes = MemoryHelper.ReadRaw((nint)CurrentCharacter+0x4, 2); var chars = Encoding.UTF8.GetString(utf8Bytes).ToCharArray(); if (chars.Length == 0) return; @@ -381,6 +361,8 @@ internal sealed unsafe class Chat : IDisposable if (c != '\0' && !char.IsControl(c)) input = c.ToString(); + Plugin.Log.Information($"Input was {c}"); + try { Activated?.Invoke(new ChatActivatedArgs(new ChannelSwitchInfo(null)) { Input = input, }); @@ -415,7 +397,7 @@ internal sealed unsafe class Chat : IDisposable return 1; } - private nint ChangeChannelNameDetour(nint agent) + private byte* ChangeChannelNameDetour(AgentChatLog* agent) { // Last ShB patch // +0x40 = chat channel (byte or uint?) @@ -423,23 +405,17 @@ internal sealed unsafe class Chat : IDisposable // +0x48 = pointer to channel name string // +0xDA = player name string for tells // +0x120 = player world id for tells - var ret = ChangeChannelNameHook!.Original(agent); - if (agent == nint.Zero) + var ret = ChangeChannelNameHook.Original(agent); + if ((nint) agent == nint.Zero) return ret; var channel = (uint) RaptureShellModule.Instance()->ChatType; if (channel is 17 or 18) channel = (uint) InputChannel.Tell; - SeString? name = null; - var namePtrPtr = (byte**) (agent + 0x48); - if (namePtrPtr != null) - { - var namePtr = *namePtrPtr; - name = MemoryHelper.ReadSeStringNullTerminated((nint) namePtr); - if (name.Payloads.Count == 0) - name = null; - } + var name = SeString.Parse(agent->ChannelLabel); + if (name.Payloads.Count == 0) + name = null; if (name == null) return ret; @@ -452,7 +428,7 @@ internal sealed unsafe class Chat : IDisposable ushort worldId = 0; if (channel == (uint) InputChannel.Tell) { - playerName = MemoryHelper.ReadStringNullTerminated(agent + 0xDA); + playerName = MemoryHelper.ReadStringNullTerminated((nint) agent + 0xDA); worldId = *(ushort*) (agent + 0x120); Plugin.Log.Debug($"Detected tell target '{playerName}'@{worldId}"); } @@ -462,7 +438,7 @@ internal sealed unsafe class Chat : IDisposable return ret; } - private void ReplyInSelectedChatModeDetour(AgentInterface* agent) + private void ReplyInSelectedChatModeDetour(RaptureShellModule* agent) { var replyMode = AgentChatLog.Instance()->ReplyChannel; if (replyMode == -2) @@ -475,7 +451,7 @@ internal sealed unsafe class Chat : IDisposable ReplyInSelectedChatModeHook!.Original(agent); } - private byte SetChatLogTellTargetDetour(nint a1, Utf8String* name, Utf8String* a3, ushort world, ulong contentId, ushort reason, byte a7) + private bool SetChatLogTellTargetDetour(RaptureShellModule* a1, Utf8String* name, Utf8String* a3, ushort world, ulong contentId, ushort reason, bool a7) { if (name != null) { @@ -558,9 +534,7 @@ internal sealed unsafe class Chat : IDisposable private Keybind? GetKeybind(string id) { - var agent = (nint) Framework.Instance()->GetUiModule()->GetAgentModule()->GetAgentByInternalId(AgentId.Configkey); - if (agent == nint.Zero) - return null; + var agent = (nint) AgentModule.Instance()->GetAgentByInternalId(AgentId.Configkey); var a1 = *(void**) (agent + 0x78); if (a1 == null) @@ -611,8 +585,8 @@ internal sealed unsafe class Chat : IDisposable AutoTranslate.ReplaceWithPayload(ref decoded); using var decodedUtf8String = new Utf8String(decoded); + var logModule = RaptureLogModule.Instance(); var networkModule = Framework.Instance()->GetNetworkModuleProxy()->NetworkModule; - var logModule = Framework.Instance()->GetUiModule()->GetRaptureLogModule(); var ok = SendTellNative(networkModule, contentId, homeWorld, uName, encoded, (byte) reason, homeWorld); if (ok) @@ -638,7 +612,7 @@ internal sealed unsafe class Chat : IDisposable { var uC = Utf8String.FromString(c.ToString()); - SanitiseString(uC, 0x27F, nint.Zero); + uC->SanitizeString(0x27F, Utf8String.CreateEmpty()); var wasValid = uC->ToString().Length > 0; uC->Dtor(true); diff --git a/ChatTwo/GameFunctions/ChatBox.cs b/ChatTwo/GameFunctions/ChatBox.cs new file mode 100644 index 0000000..cc7c5bd --- /dev/null +++ b/ChatTwo/GameFunctions/ChatBox.cs @@ -0,0 +1,98 @@ +using System.Diagnostics.CodeAnalysis; +using System.Runtime.InteropServices; +using System.Text; +using Dalamud.Game; +using FFXIVClientStructs.FFXIV.Client.System.String; +using FFXIVClientStructs.FFXIV.Client.UI; + +namespace ChatTwo.GameFunctions; + +// From: https://git.anna.lgbt/anna/XivCommon/src/branch/main/XivCommon/Functions/Chat.cs +public class ChatCommon +{ + private static class Signatures + { + internal const string SendChat = "48 89 5C 24 ?? 57 48 83 EC 20 48 8B FA 48 8B D9 45 84 C9"; + } + + private delegate void ProcessChatBoxDelegate(IntPtr uiModule, IntPtr message, IntPtr unused, byte a4); + private ProcessChatBoxDelegate? ProcessChatBox { get; } + + internal ChatCommon(ISigScanner scanner) + { + if (scanner.TryScanText(Signatures.SendChat, out var processChatBoxPtr)) + { + ProcessChatBox = Marshal.GetDelegateForFunctionPointer(processChatBoxPtr); + } + } + + public unsafe void SendMessageUnsafe(byte[] message) + { + if (ProcessChatBox == null) + throw new InvalidOperationException("Could not find signature for chat sending"); + + var uiModule = (IntPtr)UIModule.Instance(); + + using var payload = new ChatPayload(message); + var mem1 = Marshal.AllocHGlobal(400); + Marshal.StructureToPtr(payload, mem1, false); + + ProcessChatBox(uiModule, mem1, IntPtr.Zero, 0); + + Marshal.FreeHGlobal(mem1); + } + + public void SendMessage(string message) + { + var bytes = Encoding.UTF8.GetBytes(message); + if (bytes.Length == 0) + throw new ArgumentException("message is empty", nameof(message)); + + if (bytes.Length > 500) + throw new ArgumentException("message is longer than 500 bytes", nameof(message)); + + if (message.Length != SanitiseText(message).Length) + throw new ArgumentException("message contained invalid characters", nameof(message)); + + SendMessageUnsafe(bytes); + } + + private static unsafe string SanitiseText(string text) + { + var uText = Utf8String.FromString(text); + + uText->SanitizeString( 0x27F, Utf8String.CreateEmpty()); + + var sanitised = uText->ToString(); + uText->Dtor(); + + return sanitised; + } + + [StructLayout(LayoutKind.Explicit)] + [SuppressMessage("ReSharper", "PrivateFieldCanBeConvertedToLocalVariable")] + private readonly struct ChatPayload : IDisposable + { + [FieldOffset(0)] private readonly IntPtr textPtr; + [FieldOffset(16)] private readonly ulong textLen; + [FieldOffset(8)] private readonly ulong unk1; + [FieldOffset(24)] private readonly ulong unk2; + + internal ChatPayload(byte[] stringBytes) + { + textPtr = Marshal.AllocHGlobal(stringBytes.Length + 30); + Marshal.Copy(stringBytes, 0, textPtr, stringBytes.Length); + Marshal.WriteByte(textPtr + stringBytes.Length, 0); + + textLen = (ulong)(stringBytes.Length + 1); + + unk1 = 64; + unk2 = 0; + } + + public void Dispose() + { + Marshal.FreeHGlobal(textPtr); + } + } +} \ No newline at end of file diff --git a/ChatTwo/GameFunctions/Context.cs b/ChatTwo/GameFunctions/Context.cs index 01a7dbb..67e2b63 100755 --- a/ChatTwo/GameFunctions/Context.cs +++ b/ChatTwo/GameFunctions/Context.cs @@ -1,7 +1,7 @@ using ChatTwo.Util; -using FFXIVClientStructs.FFXIV.Client.System.Framework; using FFXIVClientStructs.FFXIV.Client.UI.Agent; using FFXIVClientStructs.FFXIV.Client.UI.Info; +using FFXIVClientStructs.FFXIV.Client.UI.Misc; namespace ChatTwo.GameFunctions; @@ -27,7 +27,7 @@ internal sealed unsafe class Context internal static void OpenItemComparison(uint itemId) { - AgentItemComp.Instance()->CompareItem(0x4D, itemId, 0); + AgentItemComp.Instance()->CompareItem(0x4D, itemId, 0, 0); } internal static void SearchForRecipesUsingItem(uint itemId) @@ -37,6 +37,6 @@ internal sealed unsafe class Context internal static void SearchForItem(uint itemId) { - Framework.Instance()->GetUiModule()->GetItemFinderModule()->SearchForItem(itemId, true); + ItemFinderModule.Instance()->SearchForItem(itemId, true); } } diff --git a/ChatTwo/GameFunctions/GameFunctions.cs b/ChatTwo/GameFunctions/GameFunctions.cs index 86000e8..ab862d3 100755 --- a/ChatTwo/GameFunctions/GameFunctions.cs +++ b/ChatTwo/GameFunctions/GameFunctions.cs @@ -1,3 +1,4 @@ +using System.Globalization; using System.Runtime.InteropServices; using Dalamud.Game.ClientState.Conditions; using Dalamud.Game.Text.SeStringHandling.Payloads; @@ -5,7 +6,6 @@ using Dalamud.Hooking; using Dalamud.Memory; using Dalamud.Utility.Signatures; using FFXIVClientStructs.FFXIV.Client.Game.UI; -using FFXIVClientStructs.FFXIV.Client.System.Framework; using FFXIVClientStructs.FFXIV.Client.UI; using FFXIVClientStructs.FFXIV.Client.UI.Agent; using FFXIVClientStructs.FFXIV.Client.UI.Info; @@ -73,12 +73,12 @@ internal unsafe class GameFunctions : IDisposable var worldName = row.Name.RawString; ReplacementName = $"{name}@{worldName}"; - Plugin.Common.Functions.Chat.SendMessage($"/{commandName} add {Placeholder}"); + Plugin.Common.SendMessage($"/{commandName} add {Placeholder}"); } internal static void SetAddonInteractable(string name, bool interactable) { - var unitManager = AtkStage.GetSingleton()->RaptureAtkUnitManager; + var unitManager = AtkStage.Instance()->RaptureAtkUnitManager; var addon = (nint) unitManager->GetAddonByName(name); if (addon == nint.Zero) @@ -101,7 +101,7 @@ internal unsafe class GameFunctions : IDisposable internal static bool IsAddonInteractable(string name) { - var unitManager = AtkStage.GetSingleton()->RaptureAtkUnitManager; + var unitManager = AtkStage.Instance()->RaptureAtkUnitManager; var addon = (nint) unitManager->GetAddonByName(name); if (addon == nint.Zero) @@ -113,8 +113,8 @@ internal unsafe class GameFunctions : IDisposable internal static void OpenItemTooltip(uint id, ItemPayload.ItemKind itemKind) { - var atkStage = AtkStage.GetSingleton(); - var agent = Framework.Instance()->GetUiModule()->GetAgentModule()->GetAgentByInternalId(AgentId.ItemDetail); + var atkStage = AtkStage.Instance(); + var agent = AgentItemDetail.Instance(); var addon = atkStage->RaptureAtkUnitManager->GetAddonByName("ItemDetail"); // atkStage ain't gonna be null or we have bigger problems @@ -144,10 +144,10 @@ internal unsafe class GameFunctions : IDisposable *(byte*) (agentPtr + 0x14E) = 0; // this just probably needs to be set - agent->AddonId = addon->ID; + agent->AddonId = addon->Id; // vcall from E8 ?? ?? ?? ?? 0F B7 C0 48 83 C4 60 (FF 50 28 48 8B D3 48 8B CF) - var vf5 = (delegate* unmanaged*) ((nint) addon->VTable + 40); + var vf5 = (delegate* unmanaged*) ((nint) addon->VirtualTable + 40); // EA8BED: lets vf5 actually run *(byte*) ((nint) atkStage + 0x2B4) |= 2; (*vf5)(addon, 0, 15); @@ -156,11 +156,11 @@ internal unsafe class GameFunctions : IDisposable internal static void CloseItemTooltip() { // hide addon first to prevent the "addon close" sound - var addon = AtkStage.GetSingleton()->RaptureAtkUnitManager->GetAddonByName("ItemDetail"); + var addon = AtkStage.Instance()->RaptureAtkUnitManager->GetAddonByName("ItemDetail"); if (addon != null) addon->Hide(true, false, 0); - var agent = Framework.Instance()->GetUiModule()->GetAgentModule()->GetAgentByInternalId(AgentId.ItemDetail); + var agent = AgentItemDetail.Instance(); if (agent != null) { var eventData = stackalloc AtkValue[1]; @@ -174,12 +174,12 @@ internal unsafe class GameFunctions : IDisposable internal static void OpenPartyFinder() { // this whole method: 6.05: 84433A (FF 97 ?? ?? ?? ?? 41 B4 01) - var lfg = Framework.Instance()->GetUiModule()->GetAgentModule()->GetAgentByInternalId(AgentId.LookingForGroup); + var lfg = AgentLookingForGroup.Instance(); if (lfg->IsAgentActive()) { - var addonId = lfg->GetAddonID(); - var atkModule = Framework.Instance()->GetUiModule()->GetRaptureAtkModule(); - var atkModuleVtbl = (void**) atkModule->AtkModule.vtbl; + var addonId = lfg->GetAddonId(); + var atkModule = RaptureAtkModule.Instance(); + var atkModuleVtbl = (void**) atkModule->AtkModule.VirtualTable; var vf27 = (delegate* unmanaged) atkModuleVtbl[27]; vf27(atkModule, addonId, 1); } @@ -198,6 +198,36 @@ internal unsafe class GameFunctions : IDisposable return PlayerState.Instance()->IsMentor(); } + internal static InfoProxyCommonList.CharacterData[] GetFriends() + { + ChatTwo.Plugin.Log.Information($"Address {(nint)InfoProxyFriendList.Instance():X}"); + ChatTwo.Plugin.Log.Information($"Address CharaData {(nint)InfoProxyFriendList.Instance()->CharData:X}"); + var list = InfoProxyFriendList.Instance()->CharDataSpan.ToArray(); + foreach (var data in list) + { + ChatTwo.Plugin.Log.Information($"Data was: {data.NameString} {data.HomeWorld} {data.ContentId}"); + } + return list; + } + + internal static void OpenQuestLog(Quest quest) + { + var splits = quest.Id.RawString.Split("_"); + if (splits.Length != 2) + { + Plugin.ChatGui.Print("QuestId is wrongly formatted"); + return; + } + + if (!uint.TryParse(splits[1], NumberStyles.Any, CultureInfo.InvariantCulture, out var questId)) + { + Plugin.ChatGui.Print("Unable to parse quest id"); + return; + } + + AgentQuestJournal.Instance()->OpenForQuest(questId, 1); + } + internal static void OpenPartyFinder(uint id) { AgentLookingForGroup.Instance()->OpenListing(id); @@ -229,11 +259,11 @@ internal unsafe class GameFunctions : IDisposable internal static void ClickNoviceNetworkButton() { - var agent = Framework.Instance()->GetUiModule()->GetAgentModule()->GetAgentByInternalId(AgentId.ChatLog); + var agent = AgentChatLog.Instance(); // case 3 var value = new AtkValue { Type = ValueType.Int, Int = 3, }; var result = 0; - var vf0 = *(delegate* unmanaged*) agent->VTable; + var vf0 = *(delegate* unmanaged*) agent->VirtualTable; vf0(agent, &result, &value, 0, 0); } diff --git a/ChatTwo/IpcManager.cs b/ChatTwo/IpcManager.cs index 016dd19..cfeb433 100755 --- a/ChatTwo/IpcManager.cs +++ b/ChatTwo/IpcManager.cs @@ -7,7 +7,6 @@ namespace ChatTwo; internal sealed class IpcManager : IDisposable { - private DalamudPluginInterface Interface { get; } private ICallGateProvider RegisterGate { get; } private ICallGateProvider UnregisterGate { get; } private ICallGateProvider AvailableGate { get; } @@ -15,19 +14,17 @@ internal sealed class IpcManager : IDisposable internal List Registered { get; } = []; - public IpcManager(DalamudPluginInterface pluginInterface) + public IpcManager() { - Interface = pluginInterface; - - RegisterGate = Interface.GetIpcProvider("ChatTwo.Register"); + RegisterGate = Plugin.Interface.GetIpcProvider("ChatTwo.Register"); RegisterGate.RegisterFunc(Register); - AvailableGate = Interface.GetIpcProvider("ChatTwo.Available"); + AvailableGate = Plugin.Interface.GetIpcProvider("ChatTwo.Available"); - UnregisterGate = Interface.GetIpcProvider("ChatTwo.Unregister"); + UnregisterGate = Plugin.Interface.GetIpcProvider("ChatTwo.Unregister"); UnregisterGate.RegisterAction(Unregister); - InvokeGate = Interface.GetIpcProvider("ChatTwo.Invoke"); + InvokeGate = Plugin.Interface.GetIpcProvider("ChatTwo.Invoke"); AvailableGate.SendMessage(); } diff --git a/ChatTwo/Message.cs b/ChatTwo/Message.cs index aafcc0e..d517da0 100755 --- a/ChatTwo/Message.cs +++ b/ChatTwo/Message.cs @@ -279,15 +279,15 @@ internal partial class Message if (split == "") { var agentChat = AgentChatLog.Instance(); - var item = *(InventoryItem*)((nint)agentChat + 0x8A0); + var item = agentChat->LinkedItem; - if (item.ItemID == 0) + if (item.ItemId == 0) { AddChunkWithMessage(text.NewWithStyle(chunk.Source, chunk.Link, split)); continue; } - var kind = item.ItemID switch + var kind = item.ItemId switch { < 500_000 => ItemPayload.ItemKind.Normal, < 1_000_000 => ItemPayload.ItemKind.Collectible, @@ -296,10 +296,10 @@ internal partial class Message }; var name = kind != ItemPayload.ItemKind.EventItem - ? Plugin.DataManager.GetExcelSheet()!.GetRow(item.ItemID)!.Name.ToString() - : Plugin.DataManager.GetExcelSheet()!.GetRow(item.ItemID)!.Name.ToString(); + ? Plugin.DataManager.GetExcelSheet()!.GetRow(item.ItemId)!.Name.ToString() + : Plugin.DataManager.GetExcelSheet()!.GetRow(item.ItemId)!.Name.ToString(); - var link = new ItemPayload(item.ItemID, kind, $"{SeIconChar.LinkMarker.ToIconChar()}{name}"); + var link = new ItemPayload(item.ItemId, kind, $"{SeIconChar.LinkMarker.ToIconChar()}{name}"); AddChunkWithMessage(text.NewWithStyle(chunk.Source, link, link.DisplayName ?? "Unknown")); } else diff --git a/ChatTwo/MessageManager.cs b/ChatTwo/MessageManager.cs index 55a8452..a8261f0 100644 --- a/ChatTwo/MessageManager.cs +++ b/ChatTwo/MessageManager.cs @@ -6,7 +6,7 @@ using ChatTwo.Util; using Dalamud.Game.Text; using Dalamud.Game.Text.SeStringHandling; using Dalamud.Hooking; -using Dalamud.Interface.Internal.Notifications; +using Dalamud.Interface.ImGuiNotification; using Dalamud.Plugin.Services; using FFXIVClientStructs.FFXIV.Client.UI.Misc; using Lumina.Excel.GeneratedSheets; @@ -35,9 +35,7 @@ internal class MessageManager : IAsyncDisposable private readonly Thread PendingMessageThread; private readonly CancellationTokenSource PendingThreadCancellationToken = new(); - // TODO Replace with delegate in API X - private Hook? ContentIdResolverHook { get; init; } - private unsafe delegate void ContentIdResolverDelegate(RaptureLogModule* agent, ulong contentId, int messageIndex, ushort worldId, ushort chatType); + private Hook? ContentIdResolverHook { get; init; } internal ulong CurrentContentId { @@ -57,7 +55,7 @@ internal class MessageManager : IAsyncDisposable PendingMessageThread = new Thread(() => ProcessPendingMessages(PendingThreadCancellationToken.Token)); PendingMessageThread.Start(); - ContentIdResolverHook = Plugin.GameInteropProvider.HookFromAddress(RaptureLogModule.Addresses.AddMsgSourceEntry.Value, ContentIdResolver); + ContentIdResolverHook = Plugin.GameInteropProvider.HookFromAddress(RaptureLogModule.MemberFunctionPointers.AddMsgSourceEntry, ContentIdResolver); ContentIdResolverHook.Enable(); Plugin.ChatGui.ChatMessageUnhandled += ChatMessage; @@ -193,7 +191,7 @@ internal class MessageManager : IAsyncDisposable } public (SeString? Sender, SeString? Message) LastMessage = (null, null); - private void ChatMessage(XivChatType type, uint senderId, SeString sender, SeString message) + private void ChatMessage(XivChatType type, int timestamp, SeString sender, SeString message) { LastMessage = (sender, message); @@ -202,7 +200,7 @@ internal class MessageManager : IAsyncDisposable ReceiverId = CurrentContentId, ContentId = 0, Type = type, - SenderId = senderId, + Timestamp = timestamp, Sender = sender, Content = message, }; @@ -220,9 +218,9 @@ internal class MessageManager : IAsyncDisposable // message's content ID. If multiple messages are received in the same tick, // this will be called for each message immediately after ChatMessage is // called for each message. - private unsafe void ContentIdResolver(RaptureLogModule* agent, ulong contentId, int messageIndex, ushort worldId, ushort chatType) + private unsafe void ContentIdResolver(RaptureLogModule* agent, ulong contentId, ulong accountId, int messageIndex, ushort worldId, ushort chatType) { - ContentIdResolverHook?.Original(agent, contentId, messageIndex, worldId, chatType); + ContentIdResolverHook?.Original(agent, contentId, accountId, messageIndex, worldId, chatType); if (PendingSync.Count == 0) return; @@ -333,7 +331,7 @@ internal class MessageManager : IAsyncDisposable internal ulong ReceiverId { get; set; } internal ulong ContentId { get; set; } // 0 if unknown internal XivChatType Type { get; set; } - internal uint SenderId { get; set; } + internal int Timestamp { get; set; } internal SeString Sender { get; set; } internal SeString Content { get; set; } } diff --git a/ChatTwo/PayloadHandler.cs b/ChatTwo/PayloadHandler.cs index 73ff9d0..ed7631b 100755 --- a/ChatTwo/PayloadHandler.cs +++ b/ChatTwo/PayloadHandler.cs @@ -9,8 +9,8 @@ using Dalamud.Game.ClientState.Objects.SubKinds; using Dalamud.Game.Config; using Dalamud.Game.Text.SeStringHandling; using Dalamud.Game.Text.SeStringHandling.Payloads; -using Dalamud.Interface.Internal; -using Dalamud.Interface.Internal.Notifications; +using Dalamud.Interface.ImGuiNotification; +using Dalamud.Interface.Textures.TextureWraps; using Dalamud.Interface.Utility; using Dalamud.Interface.Utility.Raii; using Dalamud.Utility; @@ -379,7 +379,7 @@ public sealed class PayloadHandler { Plugin.GameGui.OpenMapWithMapLink(map); break; case QuestPayload quest: - LogWindow.Plugin.Common.Functions.Journal.OpenQuest(quest.Quest); + GameFunctions.GameFunctions.OpenQuestLog(quest.Quest); break; case DalamudLinkPayload link: ClickLinkPayload(chunk, payload, link); @@ -601,7 +601,8 @@ public sealed class PayloadHandler { } } - var isFriend = LogWindow.Plugin.Common.Functions.FriendList.List.Any(friend => friend.Name.TextValue == player.PlayerName && friend.HomeWorld == world.RowId); + var isFriend = GameFunctions.GameFunctions.GetFriends().Any(friend => friend.NameString == player.PlayerName && friend.HomeWorld == world.RowId); + Plugin.Log.Information($"Is Friend? {isFriend}"); if (!isFriend && ImGui.Selectable(Language.Context_SendFriendRequest)) LogWindow.Plugin.Functions.SendFriendRequest(player.PlayerName, (ushort) world.RowId); @@ -629,11 +630,11 @@ public sealed class PayloadHandler { // View Party Finder 0x2E } - private PlayerCharacter? FindCharacterForPayload(PlayerPayload payload) + private IPlayerCharacter? FindCharacterForPayload(PlayerPayload payload) { foreach (var obj in Plugin.ObjectTable) { - if (obj is not PlayerCharacter character) + if (obj is not IPlayerCharacter character) continue; if (character.Name.TextValue != payload.PlayerName) diff --git a/ChatTwo/Plugin.cs b/ChatTwo/Plugin.cs index 5d98fe8..d84b58c 100755 --- a/ChatTwo/Plugin.cs +++ b/ChatTwo/Plugin.cs @@ -1,10 +1,12 @@ using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Globalization; +using ChatTwo.GameFunctions; using ChatTwo.Ipc; using ChatTwo.Resources; using ChatTwo.Ui; using ChatTwo.Util; +using Dalamud.Game; using Dalamud.Game.ClientState.Conditions; using Dalamud.Game.ClientState.Objects; using Dalamud.Interface.Windowing; @@ -12,7 +14,6 @@ using Dalamud.IoC; using Dalamud.Plugin; using Dalamud.Plugin.Services; using ImGuiNET; -using XivCommon; namespace ChatTwo; @@ -22,7 +23,7 @@ public sealed class Plugin : IDalamudPlugin internal const string PluginName = "Chat 2"; [PluginService] internal static IPluginLog Log { get; private set; } = null!; - [PluginService] internal static DalamudPluginInterface Interface { get; private set; } = null!; + [PluginService] internal static IDalamudPluginInterface Interface { get; private set; } = null!; [PluginService] internal static IChatGui ChatGui { get; private set; } = null!; [PluginService] internal static IClientState ClientState { get; private set; } = null!; [PluginService] internal static ICommandManager CommandManager { get; private set; } = null!; @@ -39,6 +40,7 @@ public sealed class Plugin : IDalamudPlugin [PluginService] internal static IGameConfig GameConfig { get; private set; } = null!; [PluginService] internal static INotificationManager Notification { get; private set; } = null!; [PluginService] internal static IAddonLifecycle AddonLifecycle { get; private set; } = null!; + [PluginService] internal static ISigScanner Scanner { get; private set; } = null!; internal static Configuration Config = null!; @@ -53,7 +55,7 @@ public sealed class Plugin : IDalamudPlugin internal LegacyMessageImporterWindow LegacyMessageImporterWindow { get; } internal Commands Commands { get; } - internal XivCommonBase Common { get; } + internal ChatCommon Common { get; } internal TextureCache TextureCache { get; } internal GameFunctions.GameFunctions Functions { get; } internal MessageManager MessageManager { get; } @@ -80,10 +82,10 @@ public sealed class Plugin : IDalamudPlugin ImGuiUtil.Initialize(this); Commands = new Commands(this); - Common = new XivCommonBase(Interface); + Common = new ChatCommon(Scanner); TextureCache = new TextureCache(); Functions = new GameFunctions.GameFunctions(this); - Ipc = new IpcManager(Interface); + Ipc = new IpcManager(); ExtraChat = new ExtraChat(this); FontManager = new FontManager(this); @@ -165,7 +167,6 @@ public sealed class Plugin : IDalamudPlugin MessageManager?.DisposeAsync().AsTask().Wait(); Functions?.Dispose(); TextureCache?.Dispose(); - Common?.Dispose(); Commands?.Dispose(); EmoteCache.Dispose(); diff --git a/ChatTwo/TextureCache.cs b/ChatTwo/TextureCache.cs index 430c0df..a21c3d5 100755 --- a/ChatTwo/TextureCache.cs +++ b/ChatTwo/TextureCache.cs @@ -1,5 +1,5 @@ -using Dalamud.Interface.Internal; -using Dalamud.Plugin.Services; +using Dalamud.Interface.Textures; +using Dalamud.Interface.Textures.TextureWraps; using Lumina.Excel.GeneratedSheets; namespace ChatTwo; @@ -24,9 +24,7 @@ internal class TextureCache : IDisposable if (dict.ContainsKey((icon, hq))) return; - var tex = hq - ? Plugin.TextureProvider.GetIcon(icon, ITextureProvider.IconFlags.ItemHighQuality) - : Plugin.TextureProvider.GetIcon(icon); + var tex = Plugin.TextureProvider.GetFromGameIcon(new GameIconLookup(icon, hq)).GetWrapOrDefault(); if (tex != null) dict[(icon, hq)] = tex; } diff --git a/ChatTwo/Ui/ChatLogWindow.cs b/ChatTwo/Ui/ChatLogWindow.cs index 97b1b38..93f1db6 100644 --- a/ChatTwo/Ui/ChatLogWindow.cs +++ b/ChatTwo/Ui/ChatLogWindow.cs @@ -12,14 +12,15 @@ using Dalamud.Game.ClientState.Keys; using Dalamud.Game.Text.SeStringHandling; using Dalamud.Game.Text.SeStringHandling.Payloads; using Dalamud.Interface; -using Dalamud.Interface.Internal; using Dalamud.Interface.Style; +using Dalamud.Interface.Textures.TextureWraps; using Dalamud.Interface.Utility; using Dalamud.Interface.Utility.Raii; using Dalamud.Interface.Windowing; using Dalamud.Memory; using FFXIVClientStructs.FFXIV.Client.UI; using ImGuiNET; +using Lumina.Data.Files; using Lumina.Excel; using Lumina.Excel.GeneratedSheets; @@ -114,7 +115,7 @@ public sealed class ChatLogWindow : Window WorldSheet = Plugin.DataManager.GetExcelSheet()!; LogFilterSheet = Plugin.DataManager.GetExcelSheet()!; TextCommandSheet = Plugin.DataManager.GetExcelSheet()!; - FontIcon = Plugin.TextureProvider.GetTextureFromGame("common/font/fonticon_ps5.tex"); + FontIcon = Plugin.TextureProvider.CreateFromTexFile(Plugin.DataManager.GetFile("common/font/fonticon_ps5.tex")!); Plugin.Functions.Chat.Activated += Activated; Plugin.ClientState.Login += Login; @@ -804,7 +805,7 @@ public sealed class ChatLogWindow : Window // registers stub handlers and actually processes its commands in a // SendMessage detour. var bytes = Encoding.UTF8.GetBytes(channel.Value.Prefix()); - Plugin.Common.Functions.Chat.SendMessageUnsafe(bytes); + Plugin.Common.SendMessageUnsafe(bytes); return; } @@ -828,7 +829,7 @@ public sealed class ChatLogWindow : Window var world = WorldSheet.GetRow(target.World); if (world is { IsPublic: true }) { - if (reason == TellReason.Reply && Plugin.Common.Functions.FriendList.List.Any(friend => friend.ContentId == target.ContentId)) + if (reason == TellReason.Reply && GameFunctions.GameFunctions.GetFriends().Any(friend => friend.ContentId == target.ContentId)) reason = TellReason.Friend; var tellBytes = Encoding.UTF8.GetBytes(trimmed); @@ -854,7 +855,7 @@ public sealed class ChatLogWindow : Window var bytes = Encoding.UTF8.GetBytes(trimmed); AutoTranslate.ReplaceWithPayload(ref bytes); - Plugin.Common.Functions.Chat.SendMessageUnsafe(bytes); + Plugin.Common.SendMessageUnsafe(bytes); } Chat = string.Empty; @@ -989,9 +990,6 @@ public sealed class ChatLogWindow : Window if (i > 0) { var prevMessage = messages[i - 1]; - - // TODO: TryGetValue isn't always true for some strange reason - // This should be looked into, because default will be null for the prevHeight in that case prevMessage.Height.TryGetValue(tab.Identifier, out var prevHeight); if (prevHeight == null || (prevMessage.IsVisible.TryGetValue(tab.Identifier, out var prevVisible) && prevVisible)) { diff --git a/ChatTwo/Ui/Debugger.cs b/ChatTwo/Ui/Debugger.cs index 9e2d714..b85430c 100644 --- a/ChatTwo/Ui/Debugger.cs +++ b/ChatTwo/Ui/Debugger.cs @@ -41,7 +41,7 @@ public class DebuggerWindow : Window public override unsafe void Draw() { - var agent = (nint) Framework.Instance()->GetUiModule()->GetAgentModule()->GetAgentByInternalId(AgentId.ItemDetail); + var agent = (nint) AgentItemDetail.Instance(); ImGui.TextUnformatted($"Current Cursor Pos: {ChatLogWindow.CursorPos}"); if (ImGui.Selectable($"Agent Address: {agent:X}")) ImGui.SetClipboardText(agent.ToString("X")); diff --git a/ChatTwo/Ui/LegacyMessageImporterWindow.cs b/ChatTwo/Ui/LegacyMessageImporterWindow.cs index 8453ac4..76f5213 100644 --- a/ChatTwo/Ui/LegacyMessageImporterWindow.cs +++ b/ChatTwo/Ui/LegacyMessageImporterWindow.cs @@ -2,7 +2,6 @@ using ChatTwo.Util; using Dalamud.Interface.ImGuiNotification; using Dalamud.Interface.ImGuiNotification.EventArgs; -using Dalamud.Interface.Internal.Notifications; using Dalamud.Interface.Utility; using Dalamud.Interface.Utility.Raii; using Dalamud.Interface.Windowing; diff --git a/ChatTwo/Ui/SettingsTabs/Database.cs b/ChatTwo/Ui/SettingsTabs/Database.cs index 92410ea..6ce46b4 100755 --- a/ChatTwo/Ui/SettingsTabs/Database.cs +++ b/ChatTwo/Ui/SettingsTabs/Database.cs @@ -4,7 +4,7 @@ using ChatTwo.Resources; using ChatTwo.Util; using Dalamud.Game.Text.SeStringHandling; using Dalamud.Game.Text.SeStringHandling.Payloads; -using Dalamud.Interface.Internal.Notifications; +using Dalamud.Interface.ImGuiNotification; using Dalamud.Interface.Utility.Raii; using ImGuiNET; diff --git a/ChatTwo/Util/AutoTranslate.cs b/ChatTwo/Util/AutoTranslate.cs index 6ad9dbe..7681d62 100644 --- a/ChatTwo/Util/AutoTranslate.cs +++ b/ChatTwo/Util/AutoTranslate.cs @@ -1,7 +1,7 @@ using System.Diagnostics; using System.Runtime.InteropServices; using System.Text; -using Dalamud; +using Dalamud.Game; using Dalamud.Game.Text.SeStringHandling; using Dalamud.Game.Text.SeStringHandling.Payloads; using Dalamud.Utility; diff --git a/ChatTwo/Util/DatePicker.cs b/ChatTwo/Util/DatePicker.cs index 4d59c42..32fe13a 100644 --- a/ChatTwo/Util/DatePicker.cs +++ b/ChatTwo/Util/DatePicker.cs @@ -4,7 +4,6 @@ using ChatTwo.Resources; using Dalamud.Interface; using Dalamud.Interface.Colors; using Dalamud.Interface.ImGuiNotification; -using Dalamud.Interface.Internal.Notifications; using Dalamud.Interface.Utility; using Dalamud.Interface.Utility.Raii; using Dalamud.Utility; diff --git a/ChatTwo/Util/WrapperUtil.cs b/ChatTwo/Util/WrapperUtil.cs index c65dc8d..24d04a8 100644 --- a/ChatTwo/Util/WrapperUtil.cs +++ b/ChatTwo/Util/WrapperUtil.cs @@ -1,5 +1,4 @@ using Dalamud.Interface.ImGuiNotification; -using Dalamud.Interface.Internal.Notifications; namespace ChatTwo.Util; diff --git a/ChatTwo/packages.lock.json b/ChatTwo/packages.lock.json index dfeed34..0b3825f 100644 --- a/ChatTwo/packages.lock.json +++ b/ChatTwo/packages.lock.json @@ -4,9 +4,9 @@ "net8.0-windows7.0": { "DalamudPackager": { "type": "Direct", - "requested": "[2.1.12, )", - "resolved": "2.1.12", - "contentHash": "Sc0PVxvgg4NQjcI8n10/VfUQBAS4O+Fw2pZrAqBdRMbthYGeogzu5+xmIGCGmsEZ/ukMOBuAqiNiB5qA3MRalg==" + "requested": "[2.1.13, )", + "resolved": "2.1.13", + "contentHash": "rMN1omGe8536f4xLMvx9NwfvpAc9YFFfeXJ1t4P4PE6Gu8WCIoFliR1sh07hM+bfODmesk/dvMbji7vNI+B/pQ==" }, "LiteDB": { "type": "Direct", @@ -58,12 +58,6 @@ "resolved": "3.1.4", "contentHash": "lFIdxgGDA5iYkUMRFOze7BGLcdpoLFbR+a20kc1W7NepvzU7ejtxtWOg9RvgG7kb9tBoJ3ONYOK6kLil/dgF1w==" }, - "XivCommon": { - "type": "Direct", - "requested": "[9.0.0, )", - "resolved": "9.0.0", - "contentHash": "avaBp3FmSCi/PiQhntCeBDYOHejdyTWmFtz4pRBVQQ8vHkmRx+YTk1la9dkYBMlXxRXKckEdH1iI1Fu61JlE7w==" - }, "MessagePack.Annotations": { "type": "Transitive", "resolved": "2.5.140",