mirror of
https://github.com/goatcorp/Dalamud.git
synced 2026-01-03 06:13:40 +01:00
Merge pull request #225 from Caraxi/dalamud-link
This commit is contained in:
commit
8d969255b9
8 changed files with 200 additions and 2 deletions
|
|
@ -155,6 +155,10 @@ namespace Dalamud.Game.Chat.SeStringHandling
|
|||
payload = new QuestPayload();
|
||||
break;
|
||||
|
||||
case EmbeddedInfoType.DalamudLink:
|
||||
payload = new DalamudLinkPayload();
|
||||
break;
|
||||
|
||||
case EmbeddedInfoType.LinkTerminator:
|
||||
// this has no custom handling and so needs to fallthrough to ensure it is captured
|
||||
default:
|
||||
|
|
@ -224,7 +228,7 @@ namespace Dalamud.Game.Chat.SeStringHandling
|
|||
UIGlow = 0x49
|
||||
}
|
||||
|
||||
protected enum EmbeddedInfoType
|
||||
public enum EmbeddedInfoType
|
||||
{
|
||||
PlayerName = 0x01,
|
||||
ItemLink = 0x03,
|
||||
|
|
@ -232,6 +236,8 @@ namespace Dalamud.Game.Chat.SeStringHandling
|
|||
QuestLink = 0x05,
|
||||
Status = 0x09,
|
||||
|
||||
DalamudLink = 0x0F, // Dalamud Custom
|
||||
|
||||
LinkTerminator = 0xCF // not clear but seems to always follow a link
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -51,6 +51,10 @@ namespace Dalamud.Game.Chat.SeStringHandling
|
|||
/// </summary>
|
||||
Quest,
|
||||
/// <summary>
|
||||
/// A SeString payload representing a custom clickable link for dalamud plugins
|
||||
/// </summary>
|
||||
DalamudLink,
|
||||
/// <summary>
|
||||
/// An SeString payload representing any data we don't handle.
|
||||
/// </summary>
|
||||
Unknown
|
||||
|
|
|
|||
|
|
@ -0,0 +1,48 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using JetBrains.Annotations;
|
||||
|
||||
namespace Dalamud.Game.Chat.SeStringHandling.Payloads {
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public class DalamudLinkPayload : Payload {
|
||||
public override PayloadType Type => PayloadType.DalamudLink;
|
||||
|
||||
public uint CommandId { get; internal set; } = 0;
|
||||
|
||||
[NotNull]
|
||||
public string Plugin { get; internal set; } = string.Empty;
|
||||
|
||||
protected override byte[] EncodeImpl() {
|
||||
var pluginBytes = Encoding.UTF8.GetBytes(Plugin);
|
||||
var commandBytes = MakeInteger(CommandId);
|
||||
var chunkLen = 3 + pluginBytes.Length + commandBytes.Length;
|
||||
|
||||
if (chunkLen > 255) {
|
||||
throw new Exception("Chunk is too long. Plugin name exceeds limits for DalamudLinkPayload");
|
||||
}
|
||||
|
||||
var bytes = new List<byte> {START_BYTE, (byte) SeStringChunkType.Interactable, (byte) chunkLen, (byte) EmbeddedInfoType.DalamudLink};
|
||||
bytes.Add((byte) pluginBytes.Length);
|
||||
bytes.AddRange(pluginBytes);
|
||||
bytes.AddRange(commandBytes);
|
||||
bytes.Add(END_BYTE);
|
||||
return bytes.ToArray();
|
||||
}
|
||||
|
||||
protected override void DecodeImpl(BinaryReader reader, long endOfStream) {
|
||||
Plugin = Encoding.UTF8.GetString(reader.ReadBytes(reader.ReadByte()));
|
||||
CommandId = GetInteger(reader);
|
||||
}
|
||||
|
||||
public override string ToString() {
|
||||
return $"{Type} - Plugin: {Plugin}, Command: {CommandId}";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -64,6 +64,14 @@ namespace Dalamud.Game.Chat.SeStringHandling.Payloads
|
|||
return $"{Type} - Data: {BitConverter.ToString(Data).Replace("-", " ")}";
|
||||
}
|
||||
|
||||
public override bool Equals(object obj) {
|
||||
if (obj is RawPayload rp) {
|
||||
if (rp.Data.Length != this.Data.Length) return false;
|
||||
return !Data.Where((t, i) => rp.Data[i] != t).Any();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
protected override byte[] EncodeImpl()
|
||||
{
|
||||
var chunkLen = this.data.Length + 1;
|
||||
|
|
|
|||
|
|
@ -24,6 +24,8 @@ namespace Dalamud.Game {
|
|||
|
||||
private readonly Dalamud dalamud;
|
||||
|
||||
private DalamudLinkPayload openInstallerWindowLink;
|
||||
|
||||
private readonly Dictionary<XivChatType, Color> HandledChatTypeColors = new Dictionary<XivChatType, Color> {
|
||||
{XivChatType.CrossParty, Color.DodgerBlue},
|
||||
{XivChatType.Party, Color.DodgerBlue},
|
||||
|
|
@ -87,6 +89,10 @@ namespace Dalamud.Game {
|
|||
|
||||
dalamud.Framework.Gui.Chat.OnCheckMessageHandled += OnCheckMessageHandled;
|
||||
dalamud.Framework.Gui.Chat.OnChatMessage += OnChatMessage;
|
||||
|
||||
this.openInstallerWindowLink = this.dalamud.Framework.Gui.Chat.AddChatLinkHandler("Dalamud", 1001, (i, m) => {
|
||||
this.dalamud.OpenPluginInstaller();
|
||||
});
|
||||
}
|
||||
|
||||
private void OnCheckMessageHandled(XivChatType type, uint senderid, ref SeString sender, ref SeString message, ref bool isHandled) {
|
||||
|
|
@ -233,7 +239,16 @@ namespace Dalamud.Game {
|
|||
}
|
||||
} else {
|
||||
this.dalamud.Framework.Gui.Chat.PrintChat(new XivChatEntry {
|
||||
MessageBytes = Encoding.UTF8.GetBytes(Loc.Localize("DalamudPluginUpdateRequired", "One or more of your plugins needs to be updated. Please use the /xlplugins command in-game to update them!")),
|
||||
MessageBytes = new SeString(new List<Payload>() {
|
||||
new TextPayload(Loc.Localize("DalamudPluginUpdateRequired", "One or more of your plugins needs to be updated. Please use the /xlplugins command in-game to update them!")),
|
||||
new TextPayload(" ["),
|
||||
new UIForegroundPayload(this.dalamud.Data, 500),
|
||||
this.openInstallerWindowLink,
|
||||
new TextPayload(Loc.Localize("DalamudInstallerHelp", "Open the plugin installer")),
|
||||
RawPayload.LinkTerminator,
|
||||
new UIForegroundPayload(this.dalamud.Data, 0),
|
||||
new TextPayload("]"),
|
||||
}).Encode(),
|
||||
Type = XivChatType.Urgent
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,11 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using Dalamud.Game.Chat;
|
||||
using Dalamud.Game.Chat.SeStringHandling;
|
||||
using Dalamud.Game.Chat.SeStringHandling.Payloads;
|
||||
using Dalamud.Game.Internal.Libc;
|
||||
using Dalamud.Hooking;
|
||||
using Serilog;
|
||||
|
|
@ -42,6 +44,8 @@ namespace Dalamud.Game.Internal.Gui {
|
|||
|
||||
private readonly Hook<PopulateItemLinkDelegate> populateItemLinkHook;
|
||||
|
||||
private readonly Hook<InteractableLinkClickedDelegate> interactableLinkClickedHook;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Delegates
|
||||
|
|
@ -55,6 +59,10 @@ namespace Dalamud.Game.Internal.Gui {
|
|||
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
|
||||
private delegate void PopulateItemLinkDelegate(IntPtr linkObjectPtr, IntPtr itemInfoPtr);
|
||||
|
||||
|
||||
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
|
||||
private delegate void InteractableLinkClickedDelegate(IntPtr managerPtr, IntPtr messagePtr);
|
||||
|
||||
#endregion
|
||||
|
||||
public int LastLinkedItemId { get; private set; }
|
||||
|
|
@ -81,16 +89,22 @@ namespace Dalamud.Game.Internal.Gui {
|
|||
new Hook<PopulateItemLinkDelegate>(Address.PopulateItemLinkObject,
|
||||
new PopulateItemLinkDelegate(HandlePopulateItemLinkDetour),
|
||||
this);
|
||||
this.interactableLinkClickedHook =
|
||||
new Hook<InteractableLinkClickedDelegate>(Address.InteractableLinkClicked,
|
||||
new InteractableLinkClickedDelegate(InteractableLinkClickedDetour));
|
||||
|
||||
}
|
||||
|
||||
public void Enable() {
|
||||
this.printMessageHook.Enable();
|
||||
this.populateItemLinkHook.Enable();
|
||||
this.interactableLinkClickedHook.Enable();
|
||||
}
|
||||
|
||||
public void Dispose() {
|
||||
this.printMessageHook.Dispose();
|
||||
this.populateItemLinkHook.Dispose();
|
||||
this.interactableLinkClickedHook.Dispose();
|
||||
}
|
||||
|
||||
private void HandlePopulateItemLinkDetour(IntPtr linkObjectPtr, IntPtr itemInfoPtr) {
|
||||
|
|
@ -168,6 +182,76 @@ namespace Dalamud.Game.Internal.Gui {
|
|||
return retVal;
|
||||
}
|
||||
|
||||
private readonly Dictionary<(string pluginName, uint commandId), Action<uint, SeString>> dalamudLinkHandlers = new Dictionary<(string, uint), Action<uint, SeString>>();
|
||||
|
||||
/// <summary>
|
||||
/// Create a link handler
|
||||
/// </summary>
|
||||
/// <param name="pluginName"></param>
|
||||
/// <param name="commandId"></param>
|
||||
/// <param name="commandAction"></param>
|
||||
/// <returns></returns>
|
||||
internal DalamudLinkPayload AddChatLinkHandler(string pluginName, uint commandId, Action<uint, SeString> commandAction) {
|
||||
var payload = new DalamudLinkPayload() {Plugin = pluginName, CommandId = commandId};
|
||||
this.dalamudLinkHandlers.Add((pluginName, commandId), commandAction);
|
||||
return payload;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remove a registered link handler
|
||||
/// </summary>
|
||||
/// <param name="pluginName"></param>
|
||||
/// <param name="commandId"></param>
|
||||
internal void RemoveChatLinkHandler(string pluginName, uint commandId) {
|
||||
if (this.dalamudLinkHandlers.ContainsKey((pluginName, commandId))) {
|
||||
this.dalamudLinkHandlers.Remove((pluginName, commandId));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remove all handlers owned by a plugin.
|
||||
/// </summary>
|
||||
/// <param name="pluginName"></param>
|
||||
internal void RemoveChatLinkHandler(string pluginName) {
|
||||
foreach (var handler in this.dalamudLinkHandlers.Keys.ToList().Where(k => k.pluginName == pluginName)) {
|
||||
this.dalamudLinkHandlers.Remove(handler);
|
||||
}
|
||||
}
|
||||
|
||||
private void InteractableLinkClickedDetour(IntPtr managerPtr, IntPtr messagePtr) {
|
||||
try {
|
||||
var interactableType = (Payload.EmbeddedInfoType)(Marshal.ReadByte(messagePtr, 0x1B) + 1);
|
||||
|
||||
if (interactableType != Payload.EmbeddedInfoType.DalamudLink) {
|
||||
this.interactableLinkClickedHook.Original(managerPtr, messagePtr);
|
||||
return;
|
||||
}
|
||||
|
||||
Log.Verbose($"InteractableLinkClicked: {Payload.EmbeddedInfoType.DalamudLink}");
|
||||
|
||||
var payloadPtr = Marshal.ReadIntPtr(messagePtr, 0x10);
|
||||
var messageSize = 0;
|
||||
while (Marshal.ReadByte(payloadPtr, messageSize) != 0) messageSize++;
|
||||
var payloadBytes = new byte[messageSize];
|
||||
Marshal.Copy(payloadPtr, payloadBytes, 0, messageSize);
|
||||
var seStr = this.dalamud.SeStringManager.Parse(payloadBytes);
|
||||
var terminatorIndex = seStr.Payloads.IndexOf(RawPayload.LinkTerminator);
|
||||
var payloads = terminatorIndex >= 0 ? seStr.Payloads.Take(terminatorIndex + 1).ToList() : seStr.Payloads;
|
||||
if (payloads.Count == 0) return;
|
||||
var linkPayload = payloads[0];
|
||||
if (linkPayload is DalamudLinkPayload link) {
|
||||
if (this.dalamudLinkHandlers.ContainsKey((link.Plugin, link.CommandId))) {
|
||||
Log.Verbose($"Sending DalamudLink to {link.Plugin}: {link.CommandId}");
|
||||
this.dalamudLinkHandlers[(link.Plugin, link.CommandId)].Invoke(link.CommandId, new SeString(payloads));
|
||||
} else {
|
||||
Log.Debug($"No DalamudLink registered for {link.Plugin} with ID of {link.CommandId}");
|
||||
}
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
Log.Error(ex, "Exception on InteractableLinkClicked hook");
|
||||
}
|
||||
}
|
||||
|
||||
// Copyright (c) 2008-2013 Hafthor Stefansson
|
||||
// Distributed under the MIT/X11 software license
|
||||
// Ref: http://www.opensource.org/licenses/mit-license.php.
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ namespace Dalamud.Game.Internal.Gui {
|
|||
|
||||
public IntPtr PrintMessage { get; private set; }
|
||||
public IntPtr PopulateItemLinkObject { get; private set; }
|
||||
public IntPtr InteractableLinkClicked { get; private set; }
|
||||
|
||||
public ChatGuiAddressResolver(IntPtr baseAddres) {
|
||||
BaseAddress = baseAddres;
|
||||
|
|
@ -89,6 +90,8 @@ namespace Dalamud.Game.Internal.Gui {
|
|||
|
||||
//PopulateItemLinkObject = sig.ScanText( "48 89 5C 24 08 57 48 83 EC 20 80 7A 06 00 48 8B DA 48 8B F9 74 14 48 8B CA E8 32 03 00 00 48 8B C8 E8 ?? ?? B0 FF 8B C8 EB 1D 0F B6 42 14 8B 4A"); 5.0
|
||||
PopulateItemLinkObject = sig.ScanText("48 89 5C 24 08 57 48 83 EC 20 80 7A 06 00 48 8B DA 48 8B F9 74 14 48 8B CA E8 32 03 00 00 48 8B C8 E8 ?? ?? ?? FF 8B C8 EB 1D 0F B6 42 14 8B 4A");
|
||||
|
||||
InteractableLinkClicked = sig.ScanText("E8 ?? ?? ?? ?? E9 ?? ?? ?? ?? 80 BB ?? ?? ?? ?? ?? 0F 85 ?? ?? ?? ?? 80 BB") + 9;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ using Dalamud.Configuration;
|
|||
using Dalamud.Data;
|
||||
using Dalamud.Game;
|
||||
using Dalamud.Game.Chat.SeStringHandling;
|
||||
using Dalamud.Game.Chat.SeStringHandling.Payloads;
|
||||
using Dalamud.Game.ClientState;
|
||||
using Dalamud.Game.Command;
|
||||
using Dalamud.Game.Internal;
|
||||
|
|
@ -90,6 +91,7 @@ namespace Dalamud.Plugin
|
|||
/// </summary>
|
||||
public void Dispose() {
|
||||
this.UiBuilder.Dispose();
|
||||
this.Framework.Gui.Chat.RemoveChatLinkHandler(this.pluginName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -129,6 +131,34 @@ namespace Dalamud.Plugin
|
|||
return this.configs.Load(this.pluginName);
|
||||
}
|
||||
|
||||
#region Chat Links
|
||||
|
||||
/// <summary>
|
||||
/// Register a chat link handler.
|
||||
/// </summary>
|
||||
/// <param name="commandId"></param>
|
||||
/// <param name="commandAction"></param>
|
||||
/// <returns>Returns an SeString payload for the link.</returns>
|
||||
public DalamudLinkPayload AddChatLinkHandler(uint commandId, Action<uint, SeString> commandAction) {
|
||||
return this.Framework.Gui.Chat.AddChatLinkHandler(this.pluginName, commandId, commandAction);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remove a chat link handler.
|
||||
/// </summary>
|
||||
/// <param name="commandId"></param>
|
||||
public void RemoveChatLinkHandler(uint commandId) {
|
||||
this.Framework.Gui.Chat.RemoveChatLinkHandler(this.pluginName, commandId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes all chat link handlers registered by the plugin.
|
||||
/// </summary>
|
||||
public void RemoveChatLinkHandler() {
|
||||
this.Framework.Gui.Chat.RemoveChatLinkHandler(this.pluginName);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region IPC
|
||||
|
||||
internal Action<string, ExpandoObject> anyPluginIpcAction;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue