diff --git a/ChatTwo/GameFunctions.cs b/ChatTwo/GameFunctions.cs deleted file mode 100755 index 53b8c5b..0000000 --- a/ChatTwo/GameFunctions.cs +++ /dev/null @@ -1,523 +0,0 @@ -using System.Runtime.InteropServices; -using System.Text; -using ChatTwo.Code; -using ChatTwo.Util; -using Dalamud.Game.Text.SeStringHandling; -using Dalamud.Hooking; -using Dalamud.Logging; -using Dalamud.Memory; -using FFXIVClientStructs.FFXIV.Client.System.Framework; -using FFXIVClientStructs.FFXIV.Client.System.String; -using FFXIVClientStructs.FFXIV.Client.UI; -using FFXIVClientStructs.FFXIV.Client.UI.Agent; -using FFXIVClientStructs.FFXIV.Client.UI.Misc; -using FFXIVClientStructs.FFXIV.Client.UI.Shell; -using FFXIVClientStructs.FFXIV.Component.GUI; -using Lumina.Excel.GeneratedSheets; -using Siggingway; - -namespace ChatTwo; - -internal unsafe class GameFunctions : IDisposable { - private static class Signatures { - internal const string ChatLogRefresh = "40 53 56 57 48 81 EC ?? ?? ?? ?? 48 8B 05 ?? ?? ?? ?? 48 33 C4 48 89 84 24 ?? ?? ?? ?? 49 8B F0 8B FA"; - internal const string ChangeChannelName = "E8 ?? ?? ?? ?? BA ?? ?? ?? ?? 48 8D 4D B0 48 8B F8 E8 ?? ?? ?? ?? 41 8B D6"; - internal const string IsMentorA1 = "48 8D 0D ?? ?? ?? ?? E8 ?? ?? ?? ?? 84 C0 74 71 0F B6 86"; - internal const string ResolveTextCommandPlaceholder = "E8 ?? ?? ?? ?? 49 8D 4F 18 4C 8B E0"; - - internal const string CurrentChatEntryOffset = "8B 77 ?? 8D 46 01 89 47 14 81 FE ?? ?? ?? ?? 72 03 FF 47"; - } - - #region Functions - - [Signature("E8 ?? ?? ?? ?? 0F B7 44 37 ??", Fallibility = Fallibility.Fallible)] - private readonly delegate* unmanaged _changeChatChannel = null!; - - [Signature("4C 8B 81 ?? ?? ?? ?? 4D 85 C0 74 17", Fallibility = Fallibility.Fallible)] - private readonly delegate* unmanaged _getContentIdForChatEntry = null!; - - [Signature("E8 ?? ?? ?? ?? 8B FD 8B CD", Fallibility = Fallibility.Fallible)] - private readonly delegate* unmanaged _indexer = null!; - - [Signature("E8 ?? ?? ?? ?? 33 C0 EB 51", Fallibility = Fallibility.Fallible)] - private readonly delegate* unmanaged _inviteToParty = null!; - - [Signature("E8 ?? ?? ?? ?? E9 ?? ?? ?? ?? 48 8B CB E8 ?? ?? ?? ?? 45 33 C9", Fallibility = Fallibility.Fallible)] - private readonly delegate* unmanaged _inviteToNoviceNetwork = null!; - - [Signature("E8 ?? ?? ?? ?? EB 35 BA", Fallibility = Fallibility.Fallible)] - private readonly delegate* unmanaged _tryOn = null!; - - [Signature("E8 ?? ?? ?? ?? EB 7B 49 8B 06", Fallibility = Fallibility.Fallible)] - private readonly delegate* unmanaged _linkItem = null!; - - [Signature("E8 ?? ?? ?? ?? EB 3F 83 F8 FE", Fallibility = Fallibility.Fallible)] - private readonly delegate* unmanaged _itemComparison = null!; - - [Signature("E8 ?? ?? ?? ?? E9 ?? ?? ?? ?? 41 B4 01", Fallibility = Fallibility.Fallible)] - private readonly delegate* unmanaged _searchForRecipesUsingItem = null!; - - [Signature("E8 ?? ?? ?? ?? EB 45 45 33 C9", Fallibility = Fallibility.Fallible)] - private readonly delegate* unmanaged _searchForItem = null!; - - [Signature("E8 ?? ?? ?? ?? 84 C0 74 0D B0 02", Fallibility = Fallibility.Fallible)] - private readonly delegate* unmanaged _isMentor = null!; - - [Signature("E8 ?? ?? ?? ?? E9 ?? ?? ?? ?? 49 8B 56 20", Fallibility = Fallibility.Fallible)] - private readonly delegate* unmanaged _promote = null!; - - [Signature("E8 ?? ?? ?? ?? EB 66 49 8B 4E 20", Fallibility = Fallibility.Fallible)] - private readonly delegate* unmanaged _kick = null!; - - #endregion - - #region Hooks - - private delegate byte ChatLogRefreshDelegate(IntPtr log, ushort eventId, AtkValue* value); - - private delegate IntPtr ChangeChannelNameDelegate(IntPtr agent); - - private delegate IntPtr ResolveTextCommandPlaceholderDelegate(IntPtr a1, byte* placeholderText, byte a3, byte a4); - - [Signature(Signatures.ChatLogRefresh, DetourName = nameof(ChatLogRefreshDetour))] - private Hook? ChatLogRefreshHook { get; init; } - - [Signature(Signatures.ChangeChannelName, DetourName = nameof(ChangeChannelNameDetour))] - private Hook? ChangeChannelNameHook { get; init; } - - [Signature(Signatures.ResolveTextCommandPlaceholder, DetourName = nameof(ResolveTextCommandPlaceholderDetour))] - private Hook? ResolveTextCommandPlaceholderHook { get; init; } - - #endregion - - #pragma warning disable 0649 - - [Signature(Signatures.CurrentChatEntryOffset, Offset = 2)] - private readonly byte? _currentChatEntryOffset; - - [Signature(Signatures.IsMentorA1, ScanType = ScanType.StaticAddress)] - private readonly IntPtr? _isMentorA1; - - #pragma warning restore 0649 - - internal const int HqItemOffset = 1_000_000; - - private Plugin Plugin { get; } - - internal delegate void ChatActivatedEventDelegate(string? input); - - internal event ChatActivatedEventDelegate? ChatActivated; - - internal (InputChannel channel, List name) ChatChannel { get; private set; } - - internal GameFunctions(Plugin plugin) { - this.Plugin = plugin; - - Siggingway.Siggingway.Initialise(this.Plugin.SigScanner, this); - - this.ChatLogRefreshHook?.Enable(); - this.ChangeChannelNameHook?.Enable(); - this.ResolveTextCommandPlaceholderHook?.Enable(); - - this.Plugin.ClientState.Login += this.Login; - this.Login(null, null); - } - - public void Dispose() { - this.Plugin.ClientState.Login -= this.Login; - this.ResolveTextCommandPlaceholderHook?.Dispose(); - this.ChangeChannelNameHook?.Dispose(); - this.ChatLogRefreshHook?.Dispose(); - this.ChatActivated = null; - - Marshal.FreeHGlobal(this._placeholderNamePtr); - } - - private void Login(object? sender, EventArgs? e) { - if (this.ChangeChannelNameHook == null) { - return; - } - - var agent = Framework.Instance()->GetUiModule()->GetAgentModule()->GetAgentByInternalId(AgentId.ChatLog); - if (agent == null) { - return; - } - - this.ChangeChannelNameDetour((IntPtr) agent); - } - - internal uint? GetCurrentChatLogEntryIndex() { - if (this._currentChatEntryOffset == null) { - return null; - } - - var log = (IntPtr) Framework.Instance()->GetUiModule()->GetRaptureLogModule(); - return *(uint*) (log + this._currentChatEntryOffset.Value); - } - - internal ulong? GetContentIdForChatLogEntry(uint index) { - if (this._getContentIdForChatEntry == null) { - return null; - } - - return this._getContentIdForChatEntry(Framework.Instance()->GetUiModule()->GetRaptureLogModule(), index); - } - - internal void InviteToParty(string name, ushort world) { - if (this._inviteToParty == null || this._indexer == null) { - return; - } - - var uiModule = Framework.Instance()->GetUiModule(); - // 6.05: 20D722 - var func = (delegate*) uiModule->vfunc[33]; - var toIndex = func(uiModule); - var a1 = this._indexer(toIndex, 1); - - fixed (byte* namePtr = name.ToTerminatedBytes()) { - // can specify content id if we have it, but there's no need - this._inviteToParty(a1, 0, namePtr, world); - } - } - - internal void SendFriendRequest(string name, ushort world) { - var row = this.Plugin.DataManager.GetExcelSheet()!.GetRow(world); - if (row == null) { - return; - } - - var worldName = row.Name.RawString; - this._replacementName = $"{name}@{worldName}"; - this.Plugin.Common.Functions.Chat.SendMessage($"/friendlist add {this._placeholder}"); - } - - internal void InviteToNoviceNetwork(string name, ushort world) { - if (this._inviteToNoviceNetwork == null || this._indexer == null) { - return; - } - - var uiModule = Framework.Instance()->GetUiModule(); - // 6.05: 20D722 - var func = (delegate*) uiModule->vfunc[33]; - var toIndex = func(uiModule); - // 6.05: 20E4CB - var a1 = this._indexer(toIndex, 0x11); - - fixed (byte* namePtr = name.ToTerminatedBytes()) { - // can specify content id if we have it, but there's no need - this._inviteToNoviceNetwork(a1, 0, world, namePtr); - } - } - - internal void SetChatChannel(InputChannel channel, string? tellTarget = null) { - if (this._changeChatChannel == null) { - return; - } - - var bytes = Encoding.UTF8.GetBytes(tellTarget ?? ""); - var target = new Utf8String(); - fixed (byte* tellTargetPtr = bytes) { - var zero = stackalloc byte[1]; - zero[0] = 0; - - target.StringPtr = tellTargetPtr == null ? zero : tellTargetPtr; - target.StringLength = bytes.Length; - this._changeChatChannel(RaptureShellModule.Instance, (int) (channel + 1), channel.LinkshellIndex(), &target, 1); - } - } - - internal static void SetAddonInteractable(string name, bool interactable) { - var unitManager = AtkStage.GetSingleton()->RaptureAtkUnitManager; - - var addon = (IntPtr) unitManager->GetAddonByName(name); - if (addon == IntPtr.Zero) { - return; - } - - var flags = (uint*) (addon + 0x180); - if (interactable) { - *flags &= ~(1u << 22); - } else { - *flags |= 1 << 22; - } - } - - internal static void SetChatInteractable(bool interactable) { - for (var i = 0; i < 4; i++) { - SetAddonInteractable($"ChatLogPanel_{i}", interactable); - } - - SetAddonInteractable("ChatLog", interactable); - } - - internal static bool IsAddonInteractable(string name) { - var unitManager = AtkStage.GetSingleton()->RaptureAtkUnitManager; - - var addon = (IntPtr) unitManager->GetAddonByName(name); - if (addon == IntPtr.Zero) { - return false; - } - - var flags = (uint*) (addon + 0x180); - return (*flags & (1 << 22)) == 0; - } - - internal static void OpenItemTooltip(uint id) { - var atkStage = AtkStage.GetSingleton(); - var agent = Framework.Instance()->GetUiModule()->GetAgentModule()->GetAgentByInternalId(AgentId.ItemDetail); - var addon = atkStage->RaptureAtkUnitManager->GetAddonByName("ItemDetail"); - if (agent == null || addon == null) { - // atkStage ain't gonna be null or we have bigger problems - return; - } - - var agentPtr = (IntPtr) agent; - - // addresses mentioned here are 6.01 - // see the call near the end of AgentItemDetail.Update - // offsets valid as of 6.01 - - // 8BFC49: sets some shit - *(uint*) (agentPtr + 0x20) = 22; - // 8C04C8: switch goes down to default, which is what we want - *(byte*) (agentPtr + 0x118) = 1; - // 8BFCF6: item id when hovering over item in chat - *(uint*) (agentPtr + 0x11C) = id; - // 8BFCE4: always 0 when hovering over item in chat - *(uint*) (agentPtr + 0x120) = 0; - // 8C0B55: skips a check to do with inventory - *(byte*) (agentPtr + 0x128) &= 0xEF; - // 8BFC7C: when set to 1, lets everything continue (one frame) - *(byte*) (agentPtr + 0x146) = 1; - // 8BFC89: skips early return - *(byte*) (agentPtr + 0x14A) = 0; - - // this just probably needs to be set - agent->AddonId = (uint) addon->ID; - - // vcall from E8 ?? ?? ?? ?? 0F B7 C0 48 83 C4 60 - var vf5 = (delegate**) ((IntPtr) addon->VTable + 40); - // E8872D: lets vf5 actually run - *(byte*) ((IntPtr) atkStage + 0x2B4) |= 2; - (*vf5)(addon, 0, 15); - } - - internal static void CloseItemTooltip() { - // hide addon first to prevent the "addon close" sound - var addon = AtkStage.GetSingleton()->RaptureAtkUnitManager->GetAddonByName("ItemDetail"); - if (addon != null) { - addon->Hide(true); - } - - var agent = Framework.Instance()->GetUiModule()->GetAgentModule()->GetAgentByInternalId(AgentId.ItemDetail); - if (agent != null) { - agent->Hide(); - } - } - - private byte ChatLogRefreshDetour(IntPtr log, ushort eventId, AtkValue* value) { - if (eventId == 0x31 && value != null && value->UInt is 0x05 or 0x0C) { - string? eventInput = null; - - var str = value + 2; - if (str != null && str->String != null) { - var input = MemoryHelper.ReadStringNullTerminated((IntPtr) str->String); - if (input.Length > 0) { - eventInput = input; - } - } - - try { - this.ChatActivated?.Invoke(eventInput); - } catch (Exception ex) { - PluginLog.LogError(ex, "Error in ChatActivated event"); - } - - return 0; - } - - return this.ChatLogRefreshHook!.Original(log, eventId, value); - } - - private IntPtr ChangeChannelNameDetour(IntPtr agent) { - // Last ShB patch - // +0x40 = chat channel (byte or uint?) - // channel is 17 (maybe 18?) for tells - // +0x48 = pointer to channel name string - var ret = this.ChangeChannelNameHook!.Original(agent); - if (agent == IntPtr.Zero) { - return ret; - } - - // E8 ?? ?? ?? ?? 8D 48 F7 - // RaptureShellModule + 0xFD0 - var shellModule = (IntPtr) Framework.Instance()->GetUiModule()->GetRaptureShellModule(); - if (shellModule == IntPtr.Zero) { - return ret; - } - - var channel = *(uint*) (shellModule + 0xFD0); - - // var channel = *(uint*) (agent + 0x40); - if (channel is 17 or 18) { - channel = 0; - } - - SeString? name = null; - var namePtrPtr = (byte**) (agent + 0x48); - if (namePtrPtr != null) { - var namePtr = *namePtrPtr; - name = MemoryHelper.ReadSeStringNullTerminated((IntPtr) namePtr); - if (name.Payloads.Count == 0) { - name = null; - } - } - - if (name == null) { - return ret; - } - - var nameChunks = ChunkUtil.ToChunks(name, null).ToList(); - if (nameChunks.Count > 0 && nameChunks[0] is TextChunk text) { - text.Content = text.Content.TrimStart('\uE01E').TrimStart(); - } - - this.ChatChannel = ((InputChannel) channel, nameChunks); - - return ret; - } - - // These context menu things come from AgentChatLog.vf0 at the bottom - // 0x10000: item comparison - // 0x10001: try on - // 0x10002: search for item - // 0x10003: link - // 0x10005: copy item name - // 0x10006: search recipes using this material - - internal void TryOn(uint itemId, byte stainId) { - if (this._tryOn == null) { - return; - } - - this._tryOn(0xFF, itemId, stainId, 0, 0); - } - - internal void LinkItem(uint itemId) { - if (this._linkItem == null) { - return; - } - - var agent = Framework.Instance()->GetUiModule()->GetAgentModule()->GetAgentByInternalId(AgentId.ChatLog); - this._linkItem(agent, itemId); - } - - internal void OpenItemComparison(uint itemId) { - if (this._itemComparison == null) { - return; - } - - var agent = Framework.Instance()->GetUiModule()->GetAgentModule()->GetAgentByInternalId(AgentId.ItemCompare); - this._itemComparison(agent, 0x4D, itemId, 0); - } - - internal void SearchForRecipesUsingItem(uint itemId) { - if (this._searchForRecipesUsingItem == null) { - return; - } - - var uiModule = Framework.Instance()->GetUiModule(); - var vf35 = (delegate* unmanaged) uiModule->vfunc[35]; - var a1 = vf35(uiModule); - this._searchForRecipesUsingItem(a1, itemId); - } - - internal void SearchForItem(uint itemId) { - if (this._searchForItem == null) { - return; - } - - var itemFinder = Framework.Instance()->GetUiModule()->GetItemFinderModule(); - this._searchForItem(itemFinder, itemId, 1); - } - - internal static void OpenPartyFinder() { - // this whole method: 6.05: 84433A - var lfg = Framework.Instance()->GetUiModule()->GetAgentModule()->GetAgentByInternalId(AgentId.LookingForGroup); - if (lfg->IsAgentActive()) { - var addonId = lfg->GetAddonID(); - var atkModule = Framework.Instance()->GetUiModule()->GetRaptureAtkModule(); - var atkModuleVtbl = (void**) atkModule->AtkModule.vtbl; - var vf27 = (delegate* unmanaged) atkModuleVtbl[27]; - vf27(atkModule, addonId, 1); - } else { - // 6.05: 8443DD - if (*(uint*) ((IntPtr) lfg + 0x2AB8) > 0) { - lfg->Hide(); - } else { - lfg->Show(); - } - } - } - - internal bool IsMentor() { - if (this._isMentor == null || this._isMentorA1 == null || this._isMentorA1.Value == IntPtr.Zero) { - return false; - } - - return this._isMentor(this._isMentorA1.Value) > 0; - } - - internal void KickFromParty(string name, ulong contentId) { - if (this._kick == null) { - return; - } - - var agent = Framework.Instance()->GetUiModule()->GetAgentModule()->GetAgentByInternalId(AgentId.SocialPartyMember); - if (agent == null) { - return; - } - - fixed (byte* namePtr = name.ToTerminatedBytes()) { - this._kick(agent, namePtr, 0, contentId); - } - } - - internal void Promote(string name, ulong contentId) { - if (this._promote == null) { - return; - } - - var agent = Framework.Instance()->GetUiModule()->GetAgentModule()->GetAgentByInternalId(AgentId.SocialPartyMember); - if (agent == null) { - return; - } - - fixed (byte* namePtr = name.ToTerminatedBytes()) { - this._promote(agent, namePtr, 0, contentId); - } - } - - private readonly IntPtr _placeholderNamePtr = Marshal.AllocHGlobal(128); - private readonly string _placeholder = $"<{Guid.NewGuid():N}>"; - private string? _replacementName; - - private IntPtr ResolveTextCommandPlaceholderDetour(IntPtr a1, byte* placeholderText, byte a3, byte a4) { - if (this._replacementName == null) { - goto Original; - } - - var placeholder = MemoryHelper.ReadStringNullTerminated((IntPtr) placeholderText); - if (placeholder != this._placeholder) { - goto Original; - } - - MemoryHelper.WriteString(this._placeholderNamePtr, this._replacementName); - this._replacementName = null; - - return this._placeholderNamePtr; - - Original: - return this.ResolveTextCommandPlaceholderHook!.Original(a1, placeholderText, a3, a4); - } -} diff --git a/ChatTwo/GameFunctions/Chat.cs b/ChatTwo/GameFunctions/Chat.cs new file mode 100755 index 0000000..1b5daa5 --- /dev/null +++ b/ChatTwo/GameFunctions/Chat.cs @@ -0,0 +1,177 @@ +using System.Text; +using ChatTwo.Code; +using ChatTwo.Util; +using Dalamud.Game.Text.SeStringHandling; +using Dalamud.Hooking; +using Dalamud.Logging; +using Dalamud.Memory; +using FFXIVClientStructs.FFXIV.Client.System.Framework; +using FFXIVClientStructs.FFXIV.Client.System.String; +using FFXIVClientStructs.FFXIV.Client.UI.Agent; +using FFXIVClientStructs.FFXIV.Client.UI.Misc; +using FFXIVClientStructs.FFXIV.Client.UI.Shell; +using FFXIVClientStructs.FFXIV.Component.GUI; +using Siggingway; + +namespace ChatTwo.GameFunctions; + +internal sealed unsafe class Chat : IDisposable { + [Signature("E8 ?? ?? ?? ?? 0F B7 44 37 ??", Fallibility = Fallibility.Fallible)] + private readonly delegate* unmanaged _changeChatChannel = null!; + + [Signature("4C 8B 81 ?? ?? ?? ?? 4D 85 C0 74 17", Fallibility = Fallibility.Fallible)] + private readonly delegate* unmanaged _getContentIdForChatEntry = null!; + + private delegate byte ChatLogRefreshDelegate(IntPtr log, ushort eventId, AtkValue* value); + + private delegate IntPtr ChangeChannelNameDelegate(IntPtr agent); + + [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; } + + [Signature( + "E8 ?? ?? ?? ?? BA ?? ?? ?? ?? 48 8D 4D B0 48 8B F8 E8 ?? ?? ?? ?? 41 8B D6", + DetourName = nameof(ChangeChannelNameDetour) + )] + private Hook? ChangeChannelNameHook { get; init; } + + internal delegate void ChatActivatedEventDelegate(string? input); + + internal event ChatActivatedEventDelegate? Activated; + + private Plugin Plugin { get; } + internal (InputChannel channel, List name) Channel { get; private set; } + + internal Chat(Plugin plugin) { + this.Plugin = plugin; + Siggingway.Siggingway.Initialise(this.Plugin.SigScanner, this); + + this.ChatLogRefreshHook?.Enable(); + this.ChangeChannelNameHook?.Enable(); + + this.Plugin.ClientState.Login += this.Login; + this.Login(null, null); + } + + public void Dispose() { + this.Plugin.ClientState.Login -= this.Login; + + this.ChangeChannelNameHook?.Dispose(); + this.ChatLogRefreshHook?.Dispose(); + + this.Activated = null; + } + + private void Login(object? sender, EventArgs? e) { + if (this.ChangeChannelNameHook == null) { + return; + } + + var agent = Framework.Instance()->GetUiModule()->GetAgentModule()->GetAgentByInternalId(AgentId.ChatLog); + if (agent == null) { + return; + } + + this.ChangeChannelNameDetour((IntPtr) agent); + } + + private IntPtr ChangeChannelNameDetour(IntPtr agent) { + // Last ShB patch + // +0x40 = chat channel (byte or uint?) + // channel is 17 (maybe 18?) for tells + // +0x48 = pointer to channel name string + var ret = this.ChangeChannelNameHook!.Original(agent); + if (agent == IntPtr.Zero) { + return ret; + } + + // E8 ?? ?? ?? ?? 8D 48 F7 + // RaptureShellModule + 0xFD0 + var shellModule = (IntPtr) Framework.Instance()->GetUiModule()->GetRaptureShellModule(); + if (shellModule == IntPtr.Zero) { + return ret; + } + + var channel = *(uint*) (shellModule + 0xFD0); + + // var channel = *(uint*) (agent + 0x40); + if (channel is 17 or 18) { + channel = 0; + } + + SeString? name = null; + var namePtrPtr = (byte**) (agent + 0x48); + if (namePtrPtr != null) { + var namePtr = *namePtrPtr; + name = MemoryHelper.ReadSeStringNullTerminated((IntPtr) namePtr); + if (name.Payloads.Count == 0) { + name = null; + } + } + + if (name == null) { + return ret; + } + + var nameChunks = ChunkUtil.ToChunks(name, null).ToList(); + if (nameChunks.Count > 0 && nameChunks[0] is TextChunk text) { + text.Content = text.Content.TrimStart('\uE01E').TrimStart(); + } + + this.Channel = ((InputChannel) channel, nameChunks); + + return ret; + } + + private byte ChatLogRefreshDetour(IntPtr log, ushort eventId, AtkValue* value) { + if (eventId == 0x31 && value != null && value->UInt is 0x05 or 0x0C) { + string? eventInput = null; + + var str = value + 2; + if (str != null && str->String != null) { + var input = MemoryHelper.ReadStringNullTerminated((IntPtr) str->String); + if (input.Length > 0) { + eventInput = input; + } + } + + try { + this.Activated?.Invoke(eventInput); + } catch (Exception ex) { + PluginLog.LogError(ex, "Error in ChatActivated event"); + } + + return 0; + } + + return this.ChatLogRefreshHook!.Original(log, eventId, value); + } + + internal ulong? GetContentIdForEntry(uint index) { + if (this._getContentIdForChatEntry == null) { + return null; + } + + return this._getContentIdForChatEntry(Framework.Instance()->GetUiModule()->GetRaptureLogModule(), index); + } + + internal void SetChannel(InputChannel channel, string? tellTarget = null) { + if (this._changeChatChannel == null) { + return; + } + + var bytes = Encoding.UTF8.GetBytes(tellTarget ?? ""); + var target = new Utf8String(); + fixed (byte* tellTargetPtr = bytes) { + var zero = stackalloc byte[1]; + zero[0] = 0; + + target.StringPtr = tellTargetPtr == null ? zero : tellTargetPtr; + target.StringLength = bytes.Length; + this._changeChatChannel(RaptureShellModule.Instance, (int) (channel + 1), channel.LinkshellIndex(), &target, 1); + } + } +} diff --git a/ChatTwo/GameFunctions/Context.cs b/ChatTwo/GameFunctions/Context.cs new file mode 100755 index 0000000..b0c166b --- /dev/null +++ b/ChatTwo/GameFunctions/Context.cs @@ -0,0 +1,107 @@ +using ChatTwo.Util; +using FFXIVClientStructs.FFXIV.Client.System.Framework; +using FFXIVClientStructs.FFXIV.Client.UI; +using FFXIVClientStructs.FFXIV.Client.UI.Agent; +using FFXIVClientStructs.FFXIV.Component.GUI; +using Siggingway; + +namespace ChatTwo.GameFunctions; + +internal sealed unsafe class Context { + [Signature("E8 ?? ?? ?? ?? E9 ?? ?? ?? ?? 48 8B CB E8 ?? ?? ?? ?? 45 33 C9", Fallibility = Fallibility.Fallible)] + private readonly delegate* unmanaged _inviteToNoviceNetwork = null!; + + [Signature("E8 ?? ?? ?? ?? EB 35 BA", Fallibility = Fallibility.Fallible)] + private readonly delegate* unmanaged _tryOn = null!; + + [Signature("E8 ?? ?? ?? ?? EB 7B 49 8B 06", Fallibility = Fallibility.Fallible)] + private readonly delegate* unmanaged _linkItem = null!; + + [Signature("E8 ?? ?? ?? ?? EB 3F 83 F8 FE", Fallibility = Fallibility.Fallible)] + private readonly delegate* unmanaged _itemComparison = null!; + + [Signature("E8 ?? ?? ?? ?? E9 ?? ?? ?? ?? 41 B4 01", Fallibility = Fallibility.Fallible)] + private readonly delegate* unmanaged _searchForRecipesUsingItem = null!; + + [Signature("E8 ?? ?? ?? ?? EB 45 45 33 C9", Fallibility = Fallibility.Fallible)] + private readonly delegate* unmanaged _searchForItem = null!; + + private Plugin Plugin { get; } + + internal Context(Plugin plugin) { + this.Plugin = plugin; + Siggingway.Siggingway.Initialise(this.Plugin.SigScanner, this); + } + + internal void InviteToNoviceNetwork(string name, ushort world) { + if (this._inviteToNoviceNetwork == null || this.Plugin.Functions.Indexer == null) { + return; + } + + var uiModule = Framework.Instance()->GetUiModule(); + // 6.05: 20D722 + var func = (delegate*) uiModule->vfunc[33]; + var toIndex = func(uiModule); + // 6.05: 20E4CB + var a1 = this.Plugin.Functions.Indexer(toIndex, 0x11); + + fixed (byte* namePtr = name.ToTerminatedBytes()) { + // can specify content id if we have it, but there's no need + this._inviteToNoviceNetwork(a1, 0, world, namePtr); + } + } + + // These context menu things come from AgentChatLog.vf0 at the bottom + // 0x10000: item comparison + // 0x10001: try on + // 0x10002: search for item + // 0x10003: link + // 0x10005: copy item name + // 0x10006: search recipes using this material + + internal void TryOn(uint itemId, byte stainId) { + if (this._tryOn == null) { + return; + } + + this._tryOn(0xFF, itemId, stainId, 0, 0); + } + + internal void LinkItem(uint itemId) { + if (this._linkItem == null) { + return; + } + + var agent = Framework.Instance()->GetUiModule()->GetAgentModule()->GetAgentByInternalId(AgentId.ChatLog); + this._linkItem(agent, itemId); + } + + internal void OpenItemComparison(uint itemId) { + if (this._itemComparison == null) { + return; + } + + var agent = Framework.Instance()->GetUiModule()->GetAgentModule()->GetAgentByInternalId(AgentId.ItemCompare); + this._itemComparison(agent, 0x4D, itemId, 0); + } + + internal void SearchForRecipesUsingItem(uint itemId) { + if (this._searchForRecipesUsingItem == null) { + return; + } + + var uiModule = Framework.Instance()->GetUiModule(); + var vf35 = (delegate* unmanaged) uiModule->vfunc[35]; + var a1 = vf35(uiModule); + this._searchForRecipesUsingItem(a1, itemId); + } + + internal void SearchForItem(uint itemId) { + if (this._searchForItem == null) { + return; + } + + var itemFinder = Framework.Instance()->GetUiModule()->GetItemFinderModule(); + this._searchForItem(itemFinder, itemId, 1); + } +} diff --git a/ChatTwo/GameFunctions/GameFunctions.cs b/ChatTwo/GameFunctions/GameFunctions.cs new file mode 100755 index 0000000..8b8e387 --- /dev/null +++ b/ChatTwo/GameFunctions/GameFunctions.cs @@ -0,0 +1,234 @@ +using System.Runtime.InteropServices; +using Dalamud.Hooking; +using Dalamud.Memory; +using FFXIVClientStructs.FFXIV.Client.System.Framework; +using FFXIVClientStructs.FFXIV.Client.UI; +using FFXIVClientStructs.FFXIV.Client.UI.Agent; +using FFXIVClientStructs.FFXIV.Component.GUI; +using Lumina.Excel.GeneratedSheets; +using Siggingway; + +namespace ChatTwo.GameFunctions; + +internal unsafe class GameFunctions : IDisposable { + private static class Signatures { + internal const string IsMentorA1 = "48 8D 0D ?? ?? ?? ?? E8 ?? ?? ?? ?? 84 C0 74 71 0F B6 86"; + internal const string ResolveTextCommandPlaceholder = "E8 ?? ?? ?? ?? 49 8D 4F 18 4C 8B E0"; + + internal const string CurrentChatEntryOffset = "8B 77 ?? 8D 46 01 89 47 14 81 FE ?? ?? ?? ?? 72 03 FF 47"; + } + + #region Functions + + [Signature("E8 ?? ?? ?? ?? 8B FD 8B CD", Fallibility = Fallibility.Fallible)] + internal readonly delegate* unmanaged Indexer = null!; + + [Signature("E8 ?? ?? ?? ?? 84 C0 74 0D B0 02", Fallibility = Fallibility.Fallible)] + private readonly delegate* unmanaged _isMentor = null!; + + #endregion + + #region Hooks + + private delegate IntPtr ResolveTextCommandPlaceholderDelegate(IntPtr a1, byte* placeholderText, byte a3, byte a4); + + [Signature(Signatures.ResolveTextCommandPlaceholder, DetourName = nameof(ResolveTextCommandPlaceholderDetour))] + private Hook? ResolveTextCommandPlaceholderHook { get; init; } + + #endregion + + #pragma warning disable 0649 + + [Signature(Signatures.CurrentChatEntryOffset, Offset = 2)] + private readonly byte? _currentChatEntryOffset; + + [Signature(Signatures.IsMentorA1, ScanType = ScanType.StaticAddress)] + private readonly IntPtr? _isMentorA1; + + #pragma warning restore 0649 + + internal const int HqItemOffset = 1_000_000; + + private Plugin Plugin { get; } + internal Party Party { get; } + internal Chat Chat { get; } + internal Context Context { get; } + + internal GameFunctions(Plugin plugin) { + this.Plugin = plugin; + this.Party = new Party(this.Plugin); + this.Chat = new Chat(this.Plugin); + this.Context = new Context(this.Plugin); + + Siggingway.Siggingway.Initialise(this.Plugin.SigScanner, this); + + this.ResolveTextCommandPlaceholderHook?.Enable(); + } + + public void Dispose() { + this.Chat.Dispose(); + + this.ResolveTextCommandPlaceholderHook?.Dispose(); + + Marshal.FreeHGlobal(this._placeholderNamePtr); + } + + internal uint? GetCurrentChatLogEntryIndex() { + if (this._currentChatEntryOffset == null) { + return null; + } + + var log = (IntPtr) Framework.Instance()->GetUiModule()->GetRaptureLogModule(); + return *(uint*) (log + this._currentChatEntryOffset.Value); + } + + internal void SendFriendRequest(string name, ushort world) { + var row = this.Plugin.DataManager.GetExcelSheet()!.GetRow(world); + if (row == null) { + return; + } + + var worldName = row.Name.RawString; + this._replacementName = $"{name}@{worldName}"; + this.Plugin.Common.Functions.Chat.SendMessage($"/friendlist add {this._placeholder}"); + } + + internal static void SetAddonInteractable(string name, bool interactable) { + var unitManager = AtkStage.GetSingleton()->RaptureAtkUnitManager; + + var addon = (IntPtr) unitManager->GetAddonByName(name); + if (addon == IntPtr.Zero) { + return; + } + + var flags = (uint*) (addon + 0x180); + if (interactable) { + *flags &= ~(1u << 22); + } else { + *flags |= 1 << 22; + } + } + + internal static void SetChatInteractable(bool interactable) { + for (var i = 0; i < 4; i++) { + SetAddonInteractable($"ChatLogPanel_{i}", interactable); + } + + SetAddonInteractable("ChatLog", interactable); + } + + internal static bool IsAddonInteractable(string name) { + var unitManager = AtkStage.GetSingleton()->RaptureAtkUnitManager; + + var addon = (IntPtr) unitManager->GetAddonByName(name); + if (addon == IntPtr.Zero) { + return false; + } + + var flags = (uint*) (addon + 0x180); + return (*flags & (1 << 22)) == 0; + } + + internal static void OpenItemTooltip(uint id) { + var atkStage = AtkStage.GetSingleton(); + var agent = Framework.Instance()->GetUiModule()->GetAgentModule()->GetAgentByInternalId(AgentId.ItemDetail); + var addon = atkStage->RaptureAtkUnitManager->GetAddonByName("ItemDetail"); + if (agent == null || addon == null) { + // atkStage ain't gonna be null or we have bigger problems + return; + } + + var agentPtr = (IntPtr) agent; + + // addresses mentioned here are 6.01 + // see the call near the end of AgentItemDetail.Update + // offsets valid as of 6.01 + + // 8BFC49: sets some shit + *(uint*) (agentPtr + 0x20) = 22; + // 8C04C8: switch goes down to default, which is what we want + *(byte*) (agentPtr + 0x118) = 1; + // 8BFCF6: item id when hovering over item in chat + *(uint*) (agentPtr + 0x11C) = id; + // 8BFCE4: always 0 when hovering over item in chat + *(uint*) (agentPtr + 0x120) = 0; + // 8C0B55: skips a check to do with inventory + *(byte*) (agentPtr + 0x128) &= 0xEF; + // 8BFC7C: when set to 1, lets everything continue (one frame) + *(byte*) (agentPtr + 0x146) = 1; + // 8BFC89: skips early return + *(byte*) (agentPtr + 0x14A) = 0; + + // this just probably needs to be set + agent->AddonId = (uint) addon->ID; + + // vcall from E8 ?? ?? ?? ?? 0F B7 C0 48 83 C4 60 + var vf5 = (delegate**) ((IntPtr) addon->VTable + 40); + // E8872D: lets vf5 actually run + *(byte*) ((IntPtr) atkStage + 0x2B4) |= 2; + (*vf5)(addon, 0, 15); + } + + internal static void CloseItemTooltip() { + // hide addon first to prevent the "addon close" sound + var addon = AtkStage.GetSingleton()->RaptureAtkUnitManager->GetAddonByName("ItemDetail"); + if (addon != null) { + addon->Hide(true); + } + + var agent = Framework.Instance()->GetUiModule()->GetAgentModule()->GetAgentByInternalId(AgentId.ItemDetail); + if (agent != null) { + agent->Hide(); + } + } + + internal static void OpenPartyFinder() { + // this whole method: 6.05: 84433A + var lfg = Framework.Instance()->GetUiModule()->GetAgentModule()->GetAgentByInternalId(AgentId.LookingForGroup); + if (lfg->IsAgentActive()) { + var addonId = lfg->GetAddonID(); + var atkModule = Framework.Instance()->GetUiModule()->GetRaptureAtkModule(); + var atkModuleVtbl = (void**) atkModule->AtkModule.vtbl; + var vf27 = (delegate* unmanaged) atkModuleVtbl[27]; + vf27(atkModule, addonId, 1); + } else { + // 6.05: 8443DD + if (*(uint*) ((IntPtr) lfg + 0x2AB8) > 0) { + lfg->Hide(); + } else { + lfg->Show(); + } + } + } + + internal bool IsMentor() { + if (this._isMentor == null || this._isMentorA1 == null || this._isMentorA1.Value == IntPtr.Zero) { + return false; + } + + return this._isMentor(this._isMentorA1.Value) > 0; + } + + private readonly IntPtr _placeholderNamePtr = Marshal.AllocHGlobal(128); + private readonly string _placeholder = $"<{Guid.NewGuid():N}>"; + private string? _replacementName; + + private IntPtr ResolveTextCommandPlaceholderDetour(IntPtr a1, byte* placeholderText, byte a3, byte a4) { + if (this._replacementName == null) { + goto Original; + } + + var placeholder = MemoryHelper.ReadStringNullTerminated((IntPtr) placeholderText); + if (placeholder != this._placeholder) { + goto Original; + } + + MemoryHelper.WriteString(this._placeholderNamePtr, this._replacementName); + this._replacementName = null; + + return this._placeholderNamePtr; + + Original: + return this.ResolveTextCommandPlaceholderHook!.Original(a1, placeholderText, a3, a4); + } +} diff --git a/ChatTwo/GameFunctions/Party.cs b/ChatTwo/GameFunctions/Party.cs new file mode 100755 index 0000000..167402e --- /dev/null +++ b/ChatTwo/GameFunctions/Party.cs @@ -0,0 +1,73 @@ +using ChatTwo.Util; +using FFXIVClientStructs.FFXIV.Client.System.Framework; +using FFXIVClientStructs.FFXIV.Client.UI; +using FFXIVClientStructs.FFXIV.Client.UI.Agent; +using FFXIVClientStructs.FFXIV.Component.GUI; +using Siggingway; + +namespace ChatTwo.GameFunctions; + +internal sealed unsafe class Party { + [Signature("E8 ?? ?? ?? ?? 33 C0 EB 51", Fallibility = Fallibility.Fallible)] + private readonly delegate* unmanaged _inviteToParty = null!; + + [Signature("E8 ?? ?? ?? ?? E9 ?? ?? ?? ?? 49 8B 56 20", Fallibility = Fallibility.Fallible)] + private readonly delegate* unmanaged _promote = null!; + + [Signature("E8 ?? ?? ?? ?? EB 66 49 8B 4E 20", Fallibility = Fallibility.Fallible)] + private readonly delegate* unmanaged _kick = null!; + + private Plugin Plugin { get; } + + internal Party(Plugin plugin) { + this.Plugin = plugin; + Siggingway.Siggingway.Initialise(this.Plugin.SigScanner, this); + } + + internal void Invite(string name, ushort world) { + if (this._inviteToParty == null || this.Plugin.Functions.Indexer == null) { + return; + } + + var uiModule = Framework.Instance()->GetUiModule(); + // 6.05: 20D722 + var func = (delegate*) uiModule->vfunc[33]; + var toIndex = func(uiModule); + var a1 = this.Plugin.Functions.Indexer(toIndex, 1); + + fixed (byte* namePtr = name.ToTerminatedBytes()) { + // can specify content id if we have it, but there's no need + this._inviteToParty(a1, 0, namePtr, world); + } + } + + internal void Kick(string name, ulong contentId) { + if (this._kick == null) { + return; + } + + var agent = Framework.Instance()->GetUiModule()->GetAgentModule()->GetAgentByInternalId(AgentId.SocialPartyMember); + if (agent == null) { + return; + } + + fixed (byte* namePtr = name.ToTerminatedBytes()) { + this._kick(agent, namePtr, 0, contentId); + } + } + + internal void Promote(string name, ulong contentId) { + if (this._promote == null) { + return; + } + + var agent = Framework.Instance()->GetUiModule()->GetAgentModule()->GetAgentByInternalId(AgentId.SocialPartyMember); + if (agent == null) { + return; + } + + fixed (byte* namePtr = name.ToTerminatedBytes()) { + this._promote(agent, namePtr, 0, contentId); + } + } +} diff --git a/ChatTwo/PayloadHandler.cs b/ChatTwo/PayloadHandler.cs index 4a83c54..5ecb6a2 100755 --- a/ChatTwo/PayloadHandler.cs +++ b/ChatTwo/PayloadHandler.cs @@ -32,7 +32,7 @@ internal sealed class PayloadHandler { this.DrawPopups(); if (this._handleTooltips && ++this._hoverCounter - this._lastHoverCounter > 1) { - GameFunctions.CloseItemTooltip(); + GameFunctions.GameFunctions.CloseItemTooltip(); this._hoveredItem = 0; this._hoverCounter = this._lastHoverCounter = 0; this._handleTooltips = false; @@ -91,7 +91,7 @@ internal sealed class PayloadHandler { } case ItemPayload item: { if (this.Ui.Plugin.Config.NativeItemTooltips) { - GameFunctions.OpenItemTooltip(item.ItemId); + GameFunctions.GameFunctions.OpenItemTooltip(item.ItemId); this._handleTooltips = true; if (this._hoveredItem != item.ItemId) { @@ -177,7 +177,7 @@ internal sealed class PayloadHandler { } case RawPayload raw: { if (Equals(raw, ChunkUtil.PeriodicRecruitmentLink)) { - GameFunctions.OpenPartyFinder(); + GameFunctions.GameFunctions.OpenPartyFinder(); } break; @@ -236,30 +236,30 @@ internal sealed class PayloadHandler { this.Log.DrawChunks(ChunkUtil.ToChunks(name, null).ToList(), false); ImGui.Separator(); - var realItemId = (uint) (item.ItemId + (item.IsHQ ? GameFunctions.HqItemOffset : 0)); + var realItemId = (uint) (item.ItemId + (item.IsHQ ? GameFunctions.GameFunctions.HqItemOffset : 0)); if (item.Item.EquipSlotCategory.Row != 0) { if (ImGui.Selectable("Try On")) { - this.Ui.Plugin.Functions.TryOn(realItemId, 0); + this.Ui.Plugin.Functions.Context.TryOn(realItemId, 0); } if (ImGui.Selectable("Item Comparison")) { - this.Ui.Plugin.Functions.OpenItemComparison(realItemId); + this.Ui.Plugin.Functions.Context.OpenItemComparison(realItemId); } } if (item.Item.ItemSearchCategory.Value?.Category == 3) { if (ImGui.Selectable("Search Recipes Using This Material")) { - this.Ui.Plugin.Functions.SearchForRecipesUsingItem(item.ItemId); + this.Ui.Plugin.Functions.Context.SearchForRecipesUsingItem(item.ItemId); } } if (ImGui.Selectable("Search for Item")) { - this.Ui.Plugin.Functions.SearchForItem(realItemId); + this.Ui.Plugin.Functions.Context.SearchForItem(realItemId); } if (ImGui.Selectable("Link")) { - this.Ui.Plugin.Functions.LinkItem(realItemId); + this.Ui.Plugin.Functions.Context.LinkItem(realItemId); } if (ImGui.Selectable("Copy Item Name")) { @@ -289,16 +289,16 @@ internal sealed class PayloadHandler { var isInParty = member != default; if (isLeader) { if (!isInParty && ImGui.Selectable("Invite to Party")) { - this.Ui.Plugin.Functions.InviteToParty(player.PlayerName, (ushort) player.World.RowId); + this.Ui.Plugin.Functions.Party.Invite(player.PlayerName, (ushort) player.World.RowId); } if (isInParty && member != null) { if (ImGui.Selectable("Promote")) { - this.Ui.Plugin.Functions.Promote(player.PlayerName, (ulong) member.ContentId); + this.Ui.Plugin.Functions.Party.Promote(player.PlayerName, (ulong) member.ContentId); } if (ImGui.Selectable("Kick from Party")) { - this.Ui.Plugin.Functions.KickFromParty(player.PlayerName, (ulong) member.ContentId); + this.Ui.Plugin.Functions.Party.Kick(player.PlayerName, (ulong) member.ContentId); } } } @@ -309,7 +309,7 @@ internal sealed class PayloadHandler { } if (this.Ui.Plugin.Functions.IsMentor() && ImGui.Selectable("Invite to Novice Network")) { - this.Ui.Plugin.Functions.InviteToNoviceNetwork(player.PlayerName, (ushort) player.World.RowId); + this.Ui.Plugin.Functions.Context.InviteToNoviceNetwork(player.PlayerName, (ushort) player.World.RowId); } } diff --git a/ChatTwo/Plugin.cs b/ChatTwo/Plugin.cs index f3e91d7..7a4bcb0 100755 --- a/ChatTwo/Plugin.cs +++ b/ChatTwo/Plugin.cs @@ -51,7 +51,7 @@ public sealed class Plugin : IDalamudPlugin { internal Configuration Config { get; } internal XivCommonBase Common { get; } internal TextureCache TextureCache { get; } - internal GameFunctions Functions { get; } + internal GameFunctions.GameFunctions Functions { get; } internal Store Store { get; } internal PluginUi Ui { get; } @@ -60,7 +60,7 @@ public sealed class Plugin : IDalamudPlugin { this.Config = this.Interface!.GetPluginConfig() as Configuration ?? new Configuration(); this.Common = new XivCommonBase(); this.TextureCache = new TextureCache(this.DataManager!); - this.Functions = new GameFunctions(this); + this.Functions = new GameFunctions.GameFunctions(this); this.Store = new Store(this); this.Ui = new PluginUi(this); @@ -70,7 +70,7 @@ public sealed class Plugin : IDalamudPlugin { public void Dispose() { this.Framework.Update -= this.FrameworkUpdate; - GameFunctions.SetChatInteractable(true); + GameFunctions.GameFunctions.SetChatInteractable(true); this.Ui.Dispose(); this.Store.Dispose(); @@ -97,8 +97,8 @@ public sealed class Plugin : IDalamudPlugin { } foreach (var name in ChatAddonNames) { - if (GameFunctions.IsAddonInteractable(name)) { - GameFunctions.SetAddonInteractable(name, false); + if (GameFunctions.GameFunctions.IsAddonInteractable(name)) { + GameFunctions.GameFunctions.SetAddonInteractable(name, false); } } } diff --git a/ChatTwo/Store.cs b/ChatTwo/Store.cs index 8266f24..e63ba29 100755 --- a/ChatTwo/Store.cs +++ b/ChatTwo/Store.cs @@ -52,7 +52,7 @@ internal class Store : IDisposable { return; } - var contentId = this.Plugin.Functions.GetContentIdForChatLogEntry(entry.Item1); + var contentId = this.Plugin.Functions.Chat.GetContentIdForEntry(entry.Item1); entry.Item2.ContentId = contentId ?? 0; } diff --git a/ChatTwo/Ui/ChatLog.cs b/ChatTwo/Ui/ChatLog.cs index 78caf80..90edcd6 100755 --- a/ChatTwo/Ui/ChatLog.cs +++ b/ChatTwo/Ui/ChatLog.cs @@ -29,15 +29,15 @@ internal sealed class ChatLog : IUiComponent { this._fontIcon = this.Ui.Plugin.DataManager.GetImGuiTexture("common/font/fonticon_ps5.tex"); - this.Ui.Plugin.Functions.ChatActivated += this.ChatActivated; + this.Ui.Plugin.Functions.Chat.Activated += this.Activated; } public void Dispose() { - this.Ui.Plugin.Functions.ChatActivated -= this.ChatActivated; + this.Ui.Plugin.Functions.Chat.Activated -= this.Activated; this._fontIcon?.Dispose(); } - private void ChatActivated(string? input) { + private void Activated(string? input) { this.Activate = true; if (input != null && !this.Chat.Contains(input)) { this.Chat += input; @@ -88,7 +88,7 @@ internal sealed class ChatLog : IUiComponent { if (activeTab is { Channel: { } channel }) { ImGui.TextUnformatted(channel.ToChatType().Name()); } else { - this.DrawChunks(this.Ui.Plugin.Functions.ChatChannel.name); + this.DrawChunks(this.Ui.Plugin.Functions.Chat.Channel.name); } } finally { ImGui.PopStyleVar(); @@ -114,7 +114,7 @@ internal sealed class ChatLog : IUiComponent { ?.RawString ?? channel.ToString(); if (ImGui.Selectable(name)) { - this.Ui.Plugin.Functions.SetChatChannel(channel); + this.Ui.Plugin.Functions.Chat.SetChannel(channel); } } @@ -127,7 +127,7 @@ internal sealed class ChatLog : IUiComponent { var buttonWidth = afterIcon.X - beforeIcon.X; var inputWidth = ImGui.GetContentRegionAvail().X - buttonWidth; - var inputType = this.Ui.Plugin.Functions.ChatChannel.channel.ToChatType(); + var inputType = this.Ui.Plugin.Functions.Chat.Channel.channel.ToChatType(); var inputColour = this.Ui.Plugin.Config.ChatColours.TryGetValue(inputType, out var inputCol) ? inputCol : inputType.DefaultColour(); diff --git a/ChatTwo/Ui/Settings.cs b/ChatTwo/Ui/Settings.cs index 7eb4d92..7874aa0 100755 --- a/ChatTwo/Ui/Settings.cs +++ b/ChatTwo/Ui/Settings.cs @@ -197,7 +197,7 @@ internal sealed class Settings : IUiComponent { } if (!this._hideChat && hideChatChanged) { - GameFunctions.SetChatInteractable(true); + GameFunctions.GameFunctions.SetChatInteractable(true); } this.Initialise();