diff --git a/Dalamud/Game/ChatHandlers.cs b/Dalamud/Game/ChatHandlers.cs index 5512a5c30..dc7f8e6c9 100644 --- a/Dalamud/Game/ChatHandlers.cs +++ b/Dalamud/Game/ChatHandlers.cs @@ -1,5 +1,5 @@ +using System; using System.Collections.Generic; -using System.Diagnostics; using System.Drawing; using System.Linq; using System.Reflection; @@ -54,6 +54,34 @@ namespace Dalamud.Game { new Regex(@"(http|ftp|https)://([\w_-]+(?:(?:\.[\w_-]+)+))([\w.,@?^=%&:/~+#-]*[\w@?^=%&/~+#-])?", RegexOptions.Compiled); + private readonly Dictionary retainerSaleRegexes = new Dictionary() { + { + ClientLanguage.Japanese, new Regex[] { + new Regex(@"^(?:.+)マーケットに(?[\d,.]+)ギルで出品した(?.*)×(?[\d,.]+)が売れ、(?[\d,.]+)ギルを入手しました。$", RegexOptions.Compiled), + new Regex(@"^(?:.+)マーケットに(?[\d,.]+)ギルで出品した(?.*)が売れ、(?[\d,.]+)ギルを入手しました。$", RegexOptions.Compiled) + } + }, + { + ClientLanguage.English, new Regex[] + { + new Regex(@"^(?.+) you put up for sale in the (?:.+) markets (?:have|has) sold for (?[\d,.]+) gil \(after fees\)\.$", RegexOptions.Compiled) + } + }, + { + ClientLanguage.German, new Regex[] + { + new Regex(@"^Dein Gehilfe hat (?.+) auf dem Markt von (?:.+) für (?[\d,.]+) Gil verkauft\.$", RegexOptions.Compiled), + new Regex(@"^Dein Gehilfe hat (?.+) auf dem Markt von (?:.+) verkauft und (?[\d,.]+) Gil erhalten\.$", RegexOptions.Compiled) + } + }, + { + ClientLanguage.French, new Regex[] + { + new Regex(@"^Un servant a vendu (?.+) pour (?[\d,.]+) gil à (?:.+)\.$", RegexOptions.Compiled) + } + } + }; + private bool hasSeenLoadingMsg; public ChatHandlers(Dalamud dalamud) { @@ -65,8 +93,8 @@ namespace Dalamud.Game { public string LastLink { get; private set; } - private void ChatOnOnChatMessage(XivChatType type, uint senderId, string sender, ref string message, - ref bool isHandled) { + private void ChatOnOnChatMessage(XivChatType type, uint senderId, string sender, byte[] rawMessage, + ref string message, ref bool isHandled) { if (type == XivChatType.Notice && !this.hasSeenLoadingMsg) { this.dalamud.Framework.Gui.Chat.Print($"XIVLauncher in-game addon v{Assembly.GetAssembly(typeof(ChatHandlers)).GetName().Version} loaded."); @@ -96,8 +124,36 @@ namespace Dalamud.Game { return; } + if (type == XivChatType.RetainerSale) + { + foreach (var regex in retainerSaleRegexes[dalamud.StartInfo.Language]) + { + var matchInfo = regex.Match(message); + + // we no longer really need to do/validate the item matching since we read the id from the byte array + // but we'd be checking the main match anyway + var itemInfo = matchInfo.Groups["item"]; + if (!itemInfo.Success) + continue; + //var itemName = SeString.Parse(itemInfo.Value).Output; + var (itemId, isHQ) = (ValueTuple)(SeString.Parse(rawMessage).Payloads[0].Param1); + + Log.Debug($"Probable retainer sale: {message}, decoded item {itemId}, HQ {isHQ}"); + + int itemValue = 0; + var valueInfo = matchInfo.Groups["value"]; + // not sure if using a culture here would work correctly, so just strip symbols instead + if (!valueInfo.Success || !int.TryParse(valueInfo.Value.Replace(",", "").Replace(".", ""), out itemValue)) + continue; + + Task.Run(() => this.dalamud.BotManager.ProcessRetainerSale(itemId, itemValue, isHQ)); + break; + } + } + + Task.Run(() => this.dalamud.BotManager.ProcessChatMessage(type, originalMessage, sender).GetAwaiter() - .GetResult()); + .GetResult()); if ((this.HandledChatTypeColors.ContainsKey(type) || type == XivChatType.Say || type == XivChatType.Shout || diff --git a/Dalamud/Game/Command/CommandManager.cs b/Dalamud/Game/Command/CommandManager.cs index b7b590ac4..023a0d061 100644 --- a/Dalamud/Game/Command/CommandManager.cs +++ b/Dalamud/Game/Command/CommandManager.cs @@ -50,8 +50,8 @@ namespace Dalamud.Game.Command { dalamud.Framework.Gui.Chat.OnChatMessage += OnChatMessage; } - private void OnChatMessage(XivChatType type, uint senderId, string sender, ref string message, - ref bool isHandled) { + private void OnChatMessage(XivChatType type, uint senderId, string sender, byte[] rawMessage, + ref string message, ref bool isHandled) { if (type == XivChatType.GatheringSystemMessage && senderId == 0) { var cmdMatch = this.CommandRegex.Match(message).Groups["command"]; if (cmdMatch.Success) { diff --git a/Dalamud/Game/Internal/Gui/ChatGui.cs b/Dalamud/Game/Internal/Gui/ChatGui.cs index 4d254a71c..a5cbef50c 100644 --- a/Dalamud/Game/Internal/Gui/ChatGui.cs +++ b/Dalamud/Game/Internal/Gui/ChatGui.cs @@ -14,7 +14,7 @@ namespace Dalamud.Game.Internal.Gui { IntPtr message, uint senderId, IntPtr parameter); - public delegate void OnMessageDelegate(XivChatType type, uint senderId, string sender, ref string message, + public delegate void OnMessageDelegate(XivChatType type, uint senderId, string sender, byte[] rawMessage, ref string message, ref bool isHandled); @@ -84,16 +84,19 @@ namespace Dalamud.Game.Internal.Gui { IntPtr retVal = IntPtr.Zero; try { + ByteWrapper messageBytes = new ByteWrapper(); + var senderName = StdString.ReadFromPointer(pSenderName); - var message = StdString.ReadFromPointer(pMessage); + var message = StdString.ReadFromPointer(pMessage, messageBytes); Log.Debug($"HandlePrintMessageDetour {manager} - [{chattype}] [{BitConverter.ToString(Encoding.UTF8.GetBytes(message)).Replace("-", " ")}] {message} from {senderName}"); + // Log.Debug($"Got message bytes {BitConverter.ToString(messageBytes.Bytes).Replace("-", " ")}"); var originalMessage = string.Copy(message); // Call events var isHandled = false; - OnChatMessage?.Invoke(chattype, senderid, senderName, ref message, ref isHandled); + OnChatMessage?.Invoke(chattype, senderid, senderName, messageBytes.Bytes, ref message, ref isHandled); var messagePtr = pMessage; OwnedStdString allocatedString = null; diff --git a/Dalamud/Game/Internal/Libc/StdString.cs b/Dalamud/Game/Internal/Libc/StdString.cs index c3d6f315b..6ced3ffa3 100644 --- a/Dalamud/Game/Internal/Libc/StdString.cs +++ b/Dalamud/Game/Internal/Libc/StdString.cs @@ -1,13 +1,14 @@ -using System; +using System; using System.Runtime.InteropServices; using System.Text; +using Serilog; namespace Dalamud.Game.Internal.Libc { /// /// Interation with std::string /// public static class StdString { - public static string ReadFromPointer(IntPtr cstring) { + public static string ReadFromPointer(IntPtr cstring, ByteWrapper bytes = null) { unsafe { if (cstring == IntPtr.Zero) { throw new ArgumentNullException(nameof(cstring)); @@ -25,9 +26,27 @@ namespace Dalamud.Game.Internal.Libc { while (*(pInner + count) != 0) { count += 1; } + + // raw copy if requested, as the string conversion returned from this function is lossy + if (bytes != null) + { + bytes.Bytes = new byte[count]; + for (int i = 0; i < count; i++) + { + bytes.Bytes[i] = (byte)pInner[i]; + } + } return new string(pInner, 0, count, Encoding.UTF8); } } } + + /// + /// Wrapper so that we can use an optional byte[] as a parameter + /// + public class ByteWrapper + { + public byte[] Bytes { get; set; } = null; + } }