mirror of
https://github.com/goatcorp/Dalamud.git
synced 2025-12-13 12:14:16 +01:00
commit
397816ef7b
6 changed files with 129 additions and 19 deletions
|
|
@ -14,29 +14,42 @@ namespace Dalamud.Game.Chat {
|
||||||
PlayerLink = 0x27
|
PlayerLink = 0x27
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// in all likelihood these are flags of some kind, but these are the only 2 values I've noticed
|
||||||
|
public enum ItemQuality {
|
||||||
|
NormalQuality = 0xF2,
|
||||||
|
HighQuality = 0xF6
|
||||||
|
}
|
||||||
|
|
||||||
private const int START_BYTE = 0x02;
|
private const int START_BYTE = 0x02;
|
||||||
private const int END_BYTE = 0x03;
|
private const int END_BYTE = 0x03;
|
||||||
|
|
||||||
public static (string Output, List<SeStringPayloadContainer> Payloads) Parse(string input) {
|
public static (string Output, List<SeStringPayloadContainer> Payloads) Parse(byte[] bytes)
|
||||||
|
{
|
||||||
var output = new List<byte>();
|
var output = new List<byte>();
|
||||||
var payloads = new List<SeStringPayloadContainer>();
|
var payloads = new List<SeStringPayloadContainer>();
|
||||||
|
|
||||||
var bytes = Encoding.UTF8.GetBytes(input);
|
|
||||||
using (var stream = new MemoryStream(bytes))
|
using (var stream = new MemoryStream(bytes))
|
||||||
using (var reader = new BinaryReader(stream)) {
|
using (var reader = new BinaryReader(stream))
|
||||||
while (stream.Position < bytes.Length) {
|
{
|
||||||
|
while (stream.Position < bytes.Length)
|
||||||
|
{
|
||||||
var b = stream.ReadByte();
|
var b = stream.ReadByte();
|
||||||
|
|
||||||
if (b == START_BYTE)
|
if (b == START_BYTE)
|
||||||
ProcessPacket(reader, output, payloads);
|
ProcessPacket(reader, output, payloads);
|
||||||
else
|
else
|
||||||
output.Add((byte) b);
|
output.Add((byte)b);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return (Encoding.UTF8.GetString(output.ToArray()), payloads);
|
return (Encoding.UTF8.GetString(output.ToArray()), payloads);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static (string Output, List<SeStringPayloadContainer> Payloads) Parse(string input) {
|
||||||
|
var bytes = Encoding.UTF8.GetBytes(input);
|
||||||
|
return Parse(bytes);
|
||||||
|
}
|
||||||
|
|
||||||
private static void ProcessPacket(BinaryReader reader, List<byte> output,
|
private static void ProcessPacket(BinaryReader reader, List<byte> output,
|
||||||
List<SeStringPayloadContainer> payloads) {
|
List<SeStringPayloadContainer> payloads) {
|
||||||
var type = reader.ReadByte();
|
var type = reader.ReadByte();
|
||||||
|
|
@ -54,11 +67,28 @@ namespace Dalamud.Game.Chat {
|
||||||
|
|
||||||
switch ((SeStringPayloadType) type) {
|
switch ((SeStringPayloadType) type) {
|
||||||
case SeStringPayloadType.PlayerLink:
|
case SeStringPayloadType.PlayerLink:
|
||||||
if (payload[0] == (byte) PlayerLinkType.ItemLink)
|
if (payload[0] == (byte)PlayerLinkType.ItemLink)
|
||||||
payloads.Add(new SeStringPayloadContainer {
|
{
|
||||||
|
int itemId;
|
||||||
|
bool isHQ = payload[1] == (byte)ItemQuality.HighQuality;
|
||||||
|
if (isHQ)
|
||||||
|
{
|
||||||
|
// hq items have an extra 0x0F byte before the ID, and the ID is 0x4240 above the actual item ID
|
||||||
|
// This _seems_ consistent but I really don't know
|
||||||
|
itemId = (payload[3] << 8 | payload[4]) - 0x4240;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
itemId = (payload[2] << 8 | payload[3]);
|
||||||
|
}
|
||||||
|
|
||||||
|
payloads.Add(new SeStringPayloadContainer
|
||||||
|
{
|
||||||
Type = SeStringPayloadType.PlayerLink,
|
Type = SeStringPayloadType.PlayerLink,
|
||||||
Param1 = BitConverter.ToInt16(payload, 4)
|
Param1 = (itemId, isHQ)
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -82,6 +82,8 @@ namespace Dalamud.Game.Chat {
|
||||||
Echo = 56,
|
Echo = 56,
|
||||||
SystemError = 58,
|
SystemError = 58,
|
||||||
GatheringSystemMessage = 60,
|
GatheringSystemMessage = 60,
|
||||||
|
// not sure if this is used for anything else
|
||||||
|
RetainerSale = 71,
|
||||||
|
|
||||||
[XivChatTypeInfo("Crossworld Linkshell 2", "cw2", 0xFF1E90FF)]
|
[XivChatTypeInfo("Crossworld Linkshell 2", "cw2", 0xFF1E90FF)]
|
||||||
CrossLinkShell2 = 101,
|
CrossLinkShell2 = 101,
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
|
||||||
using System.Drawing;
|
using System.Drawing;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
|
@ -54,6 +54,34 @@ namespace Dalamud.Game {
|
||||||
new Regex(@"(http|ftp|https)://([\w_-]+(?:(?:\.[\w_-]+)+))([\w.,@?^=%&:/~+#-]*[\w@?^=%&/~+#-])?",
|
new Regex(@"(http|ftp|https)://([\w_-]+(?:(?:\.[\w_-]+)+))([\w.,@?^=%&:/~+#-]*[\w@?^=%&/~+#-])?",
|
||||||
RegexOptions.Compiled);
|
RegexOptions.Compiled);
|
||||||
|
|
||||||
|
private readonly Dictionary<ClientLanguage, Regex[]> retainerSaleRegexes = new Dictionary<ClientLanguage, Regex[]>() {
|
||||||
|
{
|
||||||
|
ClientLanguage.Japanese, new Regex[] {
|
||||||
|
new Regex(@"^(?:.+)マーケットに(?<origValue>[\d,.]+)ギルで出品した(?<item>.*)×(?<count>[\d,.]+)が売れ、(?<value>[\d,.]+)ギルを入手しました。$", RegexOptions.Compiled),
|
||||||
|
new Regex(@"^(?:.+)マーケットに(?<origValue>[\d,.]+)ギルで出品した(?<item>.*)が売れ、(?<value>[\d,.]+)ギルを入手しました。$", RegexOptions.Compiled)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ClientLanguage.English, new Regex[]
|
||||||
|
{
|
||||||
|
new Regex(@"^(?<item>.+) you put up for sale in the (?:.+) markets (?:have|has) sold for (?<value>[\d,.]+) gil \(after fees\)\.$", RegexOptions.Compiled)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ClientLanguage.German, new Regex[]
|
||||||
|
{
|
||||||
|
new Regex(@"^Dein Gehilfe hat (?<item>.+) auf dem Markt von (?:.+) für (?<value>[\d,.]+) Gil verkauft\.$", RegexOptions.Compiled),
|
||||||
|
new Regex(@"^Dein Gehilfe hat (?<item>.+) auf dem Markt von (?:.+) verkauft und (?<value>[\d,.]+) Gil erhalten\.$", RegexOptions.Compiled)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ClientLanguage.French, new Regex[]
|
||||||
|
{
|
||||||
|
new Regex(@"^Un servant a vendu (?<item>.+) pour (?<value>[\d,.]+) gil à (?:.+)\.$", RegexOptions.Compiled)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
private bool hasSeenLoadingMsg;
|
private bool hasSeenLoadingMsg;
|
||||||
|
|
||||||
public ChatHandlers(Dalamud dalamud) {
|
public ChatHandlers(Dalamud dalamud) {
|
||||||
|
|
@ -65,8 +93,8 @@ namespace Dalamud.Game {
|
||||||
|
|
||||||
public string LastLink { get; private set; }
|
public string LastLink { get; private set; }
|
||||||
|
|
||||||
private void ChatOnOnChatMessage(XivChatType type, uint senderId, string sender, ref string message,
|
private void ChatOnOnChatMessage(XivChatType type, uint senderId, string sender, byte[] rawMessage,
|
||||||
ref bool isHandled) {
|
ref string message, ref bool isHandled) {
|
||||||
|
|
||||||
if (type == XivChatType.Notice && !this.hasSeenLoadingMsg) {
|
if (type == XivChatType.Notice && !this.hasSeenLoadingMsg) {
|
||||||
this.dalamud.Framework.Gui.Chat.Print($"XIVLauncher in-game addon v{Assembly.GetAssembly(typeof(ChatHandlers)).GetName().Version} loaded.");
|
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;
|
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<int, bool>)(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()
|
Task.Run(() => this.dalamud.BotManager.ProcessChatMessage(type, originalMessage, sender).GetAwaiter()
|
||||||
.GetResult());
|
.GetResult());
|
||||||
|
|
||||||
|
|
||||||
if ((this.HandledChatTypeColors.ContainsKey(type) || type == XivChatType.Say || type == XivChatType.Shout ||
|
if ((this.HandledChatTypeColors.ContainsKey(type) || type == XivChatType.Say || type == XivChatType.Shout ||
|
||||||
|
|
|
||||||
|
|
@ -50,8 +50,8 @@ namespace Dalamud.Game.Command {
|
||||||
dalamud.Framework.Gui.Chat.OnChatMessage += OnChatMessage;
|
dalamud.Framework.Gui.Chat.OnChatMessage += OnChatMessage;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnChatMessage(XivChatType type, uint senderId, string sender, ref string message,
|
private void OnChatMessage(XivChatType type, uint senderId, string sender, byte[] rawMessage,
|
||||||
ref bool isHandled) {
|
ref string message, ref bool isHandled) {
|
||||||
if (type == XivChatType.GatheringSystemMessage && senderId == 0) {
|
if (type == XivChatType.GatheringSystemMessage && senderId == 0) {
|
||||||
var cmdMatch = this.CommandRegex.Match(message).Groups["command"];
|
var cmdMatch = this.CommandRegex.Match(message).Groups["command"];
|
||||||
if (cmdMatch.Success) {
|
if (cmdMatch.Success) {
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ namespace Dalamud.Game.Internal.Gui {
|
||||||
IntPtr message,
|
IntPtr message,
|
||||||
uint senderId, IntPtr parameter);
|
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);
|
ref bool isHandled);
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -84,16 +84,19 @@ namespace Dalamud.Game.Internal.Gui {
|
||||||
IntPtr retVal = IntPtr.Zero;
|
IntPtr retVal = IntPtr.Zero;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
ByteWrapper messageBytes = new ByteWrapper();
|
||||||
|
|
||||||
var senderName = StdString.ReadFromPointer(pSenderName);
|
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($"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);
|
var originalMessage = string.Copy(message);
|
||||||
|
|
||||||
// Call events
|
// Call events
|
||||||
var isHandled = false;
|
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;
|
var messagePtr = pMessage;
|
||||||
OwnedStdString allocatedString = null;
|
OwnedStdString allocatedString = null;
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,14 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
using Serilog;
|
||||||
|
|
||||||
namespace Dalamud.Game.Internal.Libc {
|
namespace Dalamud.Game.Internal.Libc {
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Interation with std::string
|
/// Interation with std::string
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static class StdString {
|
public static class StdString {
|
||||||
public static string ReadFromPointer(IntPtr cstring) {
|
public static string ReadFromPointer(IntPtr cstring, ByteWrapper bytes = null) {
|
||||||
unsafe {
|
unsafe {
|
||||||
if (cstring == IntPtr.Zero) {
|
if (cstring == IntPtr.Zero) {
|
||||||
throw new ArgumentNullException(nameof(cstring));
|
throw new ArgumentNullException(nameof(cstring));
|
||||||
|
|
@ -25,9 +26,27 @@ namespace Dalamud.Game.Internal.Libc {
|
||||||
while (*(pInner + count) != 0) {
|
while (*(pInner + count) != 0) {
|
||||||
count += 1;
|
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);
|
return new string(pInner, 0, count, Encoding.UTF8);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Wrapper so that we can use an optional byte[] as a parameter
|
||||||
|
/// </summary>
|
||||||
|
public class ByteWrapper
|
||||||
|
{
|
||||||
|
public byte[] Bytes { get; set; } = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue