Update for 7.0, part 1

This commit is contained in:
Infi
2024-06-30 05:07:11 +02:00
parent 1acd532ef1
commit f76f0e91d0
20 changed files with 232 additions and 146 deletions
+31 -57
View File
@@ -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<nint, Utf8String*, nint, uint> 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<RaptureLogModule*, ushort, Utf8String*, Utf8String*, ulong, ushort, byte, int, byte, void> PrintTellNative = null!;
[Signature("E8 ?? ?? ?? ?? 48 8D 4C 24 ?? E8 ?? ?? ?? ?? 48 8D 8C 24 ?? ?? ?? ?? E8 ?? ?? ?? ?? B0 01")]
private readonly delegate* unmanaged<NetworkModule*, ulong, ushort, Utf8String*, Utf8String*, byte, ulong, bool> 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<Utf8String*, int, nint, void> 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<ChatLogRefreshDelegate>? ChatLogRefreshHook { get; init; }
private delegate byte ChatLogRefreshDelegate(nint log, ushort eventId, AtkValue* value);
// TODO Replace all with delegate hooks in API X
private Hook<ChangeChannelNameDelegate>? ChangeChannelNameHook { get; init; }
private delegate nint ChangeChannelNameDelegate(nint agent);
private Hook<ReplyInSelectedChatModeDelegate>? ReplyInSelectedChatModeHook { get; init; }
private delegate void ReplyInSelectedChatModeDelegate(AgentInterface* agent);
private Hook<SetChatLogTellTarget>? SetChatLogTellTargetHook { get; init; }
private delegate byte SetChatLogTellTarget(nint a1, Utf8String* name, Utf8String* a3, ushort world, ulong contentId, ushort a6, byte a7);
private Hook<EurekaContextMenuTellDelegate>? EurekaContextMenuTellHook { get; init; }
private delegate void EurekaContextMenuTellDelegate(RaptureShellModule* param1, Utf8String* playerName, Utf8String* worldName, ushort world, ulong contentId, ushort param6);
private Hook<AgentChatLog.Delegates.ChangeChannelName> ChangeChannelNameHook { get; init; }
private Hook<RaptureShellModule.Delegates.ReplyInSelectedChatMode>? ReplyInSelectedChatModeHook { get; init; }
private Hook<RaptureShellModule.Delegates.SetContextTellTarget>? SetChatLogTellTargetHook { get; init; }
private Hook<RaptureShellModule.Delegates.SetContextTellTargetInForay>? 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<ChangeChannelNameDelegate>(AgentChatLog.Addresses.ChangeChannelName.Value, ChangeChannelNameDetour);
ChangeChannelNameHook = Plugin.GameInteropProvider.HookFromAddress<AgentChatLog.Delegates.ChangeChannelName>(AgentChatLog.MemberFunctionPointers.ChangeChannelName, ChangeChannelNameDetour);
ChangeChannelNameHook.Enable();
ReplyInSelectedChatModeHook = Plugin.GameInteropProvider.HookFromAddress<ReplyInSelectedChatModeDelegate>(RaptureShellModule.Addresses.ReplyInSelectedChatMode.Value, ReplyInSelectedChatModeDetour);
ReplyInSelectedChatModeHook = Plugin.GameInteropProvider.HookFromAddress<RaptureShellModule.Delegates.ReplyInSelectedChatMode>(RaptureShellModule.MemberFunctionPointers.ReplyInSelectedChatMode, ReplyInSelectedChatModeDetour);
ReplyInSelectedChatModeHook.Enable();
SetChatLogTellTargetHook = Plugin.GameInteropProvider.HookFromAddress<SetChatLogTellTarget>(RaptureShellModule.Addresses.SetContextTellTarget.Value, SetChatLogTellTargetDetour);
SetChatLogTellTargetHook = Plugin.GameInteropProvider.HookFromAddress<RaptureShellModule.Delegates.SetContextTellTarget>(RaptureShellModule.MemberFunctionPointers.SetContextTellTarget, SetChatLogTellTargetDetour);
SetChatLogTellTargetHook.Enable();
EurekaContextMenuTellHook = Plugin.GameInteropProvider.HookFromAddress<EurekaContextMenuTellDelegate>(RaptureShellModule.Addresses.SetContextTellTargetInForay.Value, EurekaContextMenuTell);
EurekaContextMenuTellHook = Plugin.GameInteropProvider.HookFromAddress<RaptureShellModule.Delegates.SetContextTellTargetInForay>(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);
+98
View File
@@ -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<ProcessChatBoxDelegate>(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);
}
}
}
+3 -3
View File
@@ -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);
}
}
+46 -16
View File
@@ -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<AtkUnitBase*, byte, uint, void>*) ((nint) addon->VTable + 40);
var vf5 = (delegate* unmanaged<AtkUnitBase*, byte, uint, void>*) ((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<RaptureAtkModule*, ulong, ulong, byte>) 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<AgentInterface*, int*, AtkValue*, ulong, ulong, int*>*) agent->VTable;
var vf0 = *(delegate* unmanaged<AgentChatLog*, int*, AtkValue*, ulong, ulong, int*>*) agent->VirtualTable;
vf0(agent, &result, &value, 0, 0);
}