private void *PrintMessageDetour(RaptureLogModule *raptureLogModule, XivChatType xivChatType, IntPtr senderName, IntPtr message, uint senderId, byte param) { try { if (chatTypes.Contains(xivChatType)) { // Need to hook it manually to handle changing the name until API4 var stdSender = StdString.ReadFromPointer(senderName); var parsedSender = SeString.Parse(stdSender.RawData); if (Parse(ref parsedSender)) { stdSender.RawData = parsedSender.Encode(); var allocatedString = Service.LibcFunction.NewString(stdSender.RawData); var retVal = printChatHook.Original(raptureLogModule, xivChatType, allocatedString.Address, message, senderId, param); allocatedString.Dispose(); return(retVal); } } } catch (Exception ex) { SimpleLog.Error(ex); } return(printChatHook.Original(raptureLogModule, xivChatType, senderName, message, senderId, param)); }
/// <summary> /// Initializes a new instance of the <see cref="PartyFinderListing"/> class. /// </summary> /// <param name="listing">The interop listing data.</param> internal PartyFinderListing(PartyFinderPacketListing listing) { var dataManager = Service <DataManager> .Get(); this.objective = listing.Objective; this.conditions = listing.Conditions; this.dutyFinderSettings = listing.DutyFinderSettings; this.lootRules = listing.LootRules; this.searchArea = listing.SearchArea; this.slots = listing.Slots.Select(accepting => new PartyFinderSlot(accepting)).ToArray(); this.jobsPresent = listing.JobsPresent; this.Id = listing.Id; this.ContentIdLower = listing.ContentIdLower; this.Name = SeString.Parse(listing.Name.TakeWhile(b => b != 0).ToArray()); this.Description = SeString.Parse(listing.Description.TakeWhile(b => b != 0).ToArray()); this.World = new Lazy <World>(() => dataManager.GetExcelSheet <World>().GetRow(listing.World)); this.HomeWorld = new Lazy <World>(() => dataManager.GetExcelSheet <World>().GetRow(listing.HomeWorld)); this.CurrentWorld = new Lazy <World>(() => dataManager.GetExcelSheet <World>().GetRow(listing.CurrentWorld)); this.Category = (DutyCategory)listing.Category; this.RawDuty = listing.Duty; this.Duty = new Lazy <ContentFinderCondition>(() => dataManager.GetExcelSheet <ContentFinderCondition>().GetRow(listing.Duty)); this.DutyType = (DutyType)listing.DutyType; this.BeginnersWelcome = listing.BeginnersWelcome == 1; this.SecondsRemaining = listing.SecondsRemaining; this.MinimumItemLevel = listing.MinimumItemLevel; this.Parties = listing.NumParties; this.SlotsAvailable = listing.NumSlots; this.JobsPresent = listing.JobsPresent .Select(id => new Lazy <ClassJob>( () => id == 0 ? null : dataManager.GetExcelSheet <ClassJob>().GetRow(id))) .ToArray(); }
public SeString ReadSeString(Utf8String xivString) { var len = (int)(xivString.BufUsed > int.MaxValue ? int.MaxValue : xivString.BufUsed); var bytes = new byte[len]; Marshal.Copy(new IntPtr(xivString.StringPtr), bytes, 0, len); return(SeString.Parse(bytes)); }
public void TestNewLinePayload() { var newLinePayloadBytes = new byte[] { 0x02, 0x10, 0x01, 0x03 }; var seString = SeString.Parse(newLinePayloadBytes); Assert.True(newLinePayloadBytes.SequenceEqual(seString.Encode())); }
/// <summary> /// Read an SeString from a specified memory address. /// </summary> /// <param name="memoryAddress">The memory address to read from.</param> /// <param name="maxLength">The maximum length of the string.</param> /// <returns>The read in string.</returns> public static SeString ReadSeString(IntPtr memoryAddress, int maxLength) { ReadRaw(memoryAddress, maxLength, out var buffer); var eos = Array.IndexOf(buffer, (byte)0); if (eos < 0) { return(SeString.Parse(buffer)); } else { var newBuffer = new byte[eos]; Buffer.BlockCopy(buffer, 0, newBuffer, 0, eos); return(SeString.Parse(newBuffer)); } }
public SeString ReadSeString(byte *ptr) { var offset = 0; while (true) { var b = *(ptr + offset); if (b == 0) { break; } offset += 1; } var bytes = new byte[offset]; Marshal.Copy(new IntPtr(ptr), bytes, 0, offset); return(SeString.Parse(bytes)); }
private void OnChatMessage(XivChatType type, uint senderId, ref StdString sender, ref StdString message, ref bool isHandled) { if (type == XivChatType.Notice && !this.hasSeenLoadingMsg) { var assemblyVersion = Assembly.GetAssembly(typeof(ChatHandlers)).GetName().Version.ToString(); this.dalamud.Framework.Gui.Chat.Print($"XIVLauncher in-game addon v{assemblyVersion} loaded."); foreach (var plugin in this.dalamud.PluginManager.Plugins) { this.dalamud.Framework.Gui.Chat.Print($" -> {plugin.Plugin.Name} v{plugin.Plugin.GetType().Assembly.GetName().Version} loaded."); } this.hasSeenLoadingMsg = true; if (string.IsNullOrEmpty(this.dalamud.Configuration.LastVersion) || !assemblyVersion.StartsWith(this.dalamud.Configuration.LastVersion)) { this.dalamud.Framework.Gui.Chat.PrintChat(new XivChatEntry { MessageBytes = Encoding.UTF8.GetBytes("The In-Game addon has been updated or was reinstalled successfully! Please check the discord for a full changelog."), Type = XivChatType.Urgent }); this.dalamud.Configuration.LastVersion = assemblyVersion; this.dalamud.Configuration.Save(this.dalamud.StartInfo.ConfigurationPath); } } #if !DEBUG && false if (!this.hasSeenLoadingMsg) { return; } #endif var matched = this.rmtRegex.IsMatch(message.Value); if (matched) { // This seems to be a RMT ad - let's not show it Log.Debug("Handled RMT ad"); isHandled = true; return; } var messageVal = message.Value; var senderVal = sender.Value; if (this.dalamud.Configuration.BadWords != null && this.dalamud.Configuration.BadWords.Any(x => messageVal.Contains(x))) { // This seems to be in the user block list - let's not show it Log.Debug("Blocklist triggered"); isHandled = true; return; } if (type == XivChatType.RetainerSale) { foreach (var regex in retainerSaleRegexes[dalamud.StartInfo.Language]) { var matchInfo = regex.Match(message.Value); // 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 itemLink = SeString.Parse(message.RawData).Payloads.First(x => x.Type == PayloadType.Item) as ItemPayload; if (itemLink == null) { Log.Error("itemLink was null. Msg: {0}", BitConverter.ToString(message.RawData)); break; } Log.Debug($"Probable retainer sale: {message}, decoded item {itemLink.ItemId}, HQ {itemLink.IsHQ}"); 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 var itemValue)) { continue; } Task.Run(() => this.dalamud.BotManager.ProcessRetainerSale(itemLink.ItemId, itemValue, itemLink.IsHQ)); break; } } var messageCopy = message; var senderCopy = sender; Task.Run(() => this.dalamud.BotManager.ProcessChatMessage(type, messageCopy, senderCopy)); // Handle all of this with SeString some day if ((this.HandledChatTypeColors.ContainsKey(type) || type == XivChatType.Say || type == XivChatType.Shout || type == XivChatType.Alliance || type == XivChatType.TellOutgoing || type == XivChatType.Yell) && !message.Value.Contains((char)0x02)) { var italicsStart = message.Value.IndexOf("*"); var italicsEnd = message.Value.IndexOf("*", italicsStart + 1); var messageString = message.Value; while (italicsEnd != -1) { var it = MakeItalics( messageString.Substring(italicsStart, italicsEnd - italicsStart + 1).Replace("*", "")); messageString = messageString.Remove(italicsStart, italicsEnd - italicsStart + 1); messageString = messageString.Insert(italicsStart, it); italicsStart = messageString.IndexOf("*"); italicsEnd = messageString.IndexOf("*", italicsStart + 1); } message.RawData = Encoding.UTF8.GetBytes(messageString); } var linkMatch = this.urlRegex.Match(message.Value); if (linkMatch.Value.Length > 0) { LastLink = linkMatch.Value; } }
public SeString Parse(byte[] bytes) => SeString.Parse(new ReadOnlySpan <byte>(bytes));
public unsafe SeString Parse(ReadOnlySpan <byte> data) => SeString.Parse(data);
public unsafe SeString Parse(byte *ptr, int len) => SeString.Parse(ptr, len);
public async Task ProcessChatMessage(XivChatType type, StdString message, StdString sender) { // Special case for outgoing tells, these should be sent under Incoming tells var wasOutgoingTell = false; if (type == XivChatType.TellOutgoing) { type = XivChatType.TellIncoming; wasOutgoingTell = true; } var chatTypeConfigs = this.config.ChatTypeConfigurations.Where(typeConfig => typeConfig.ChatType == type); if (!chatTypeConfigs.Any()) { return; } var chatTypeDetail = type.GetDetails(); var channels = chatTypeConfigs.Select(c => GetChannel(c.Channel).GetAwaiter().GetResult()); var parsedSender = SeString.Parse(sender.RawData); var playerLink = parsedSender.Payloads.FirstOrDefault(x => x.Type == PayloadType.Player) as PlayerPayload; string senderName; string senderWorld; if (playerLink == null) { // chat messages from the local player do not include a player link, and are just the raw name // but we should still track other instances to know if this is ever an issue otherwise // Special case 2 - When the local player talks in party/alliance, the name comes through as raw text, // but prefixed by their position number in the party (which for local player may always be 1) if (parsedSender.TextValue.EndsWith(this.dalamud.ClientState.LocalPlayer.Name)) { senderName = this.dalamud.ClientState.LocalPlayer.Name; } else { Log.Error("playerLink was null. Sender: {0}", BitConverter.ToString(sender.RawData)); senderName = wasOutgoingTell ? this.dalamud.ClientState.LocalPlayer.Name : parsedSender.TextValue; } senderWorld = this.dalamud.ClientState.LocalPlayer.HomeWorld.GameData.Name; } else { senderName = wasOutgoingTell ? this.dalamud.ClientState.LocalPlayer.Name : playerLink.PlayerName; senderWorld = playerLink.World.Name; } var rawMessage = SeString.Parse(message.RawData).TextValue; var avatarUrl = string.Empty; var lodestoneId = string.Empty; if (!this.config.DisableEmbeds && !string.IsNullOrEmpty(senderName)) { var searchResult = await GetCharacterInfo(senderName, senderWorld); lodestoneId = searchResult.LodestoneId; avatarUrl = searchResult.AvatarUrl; } Thread.Sleep(this.config.ChatDelayMs); var name = wasOutgoingTell ? "You" : senderName + (string.IsNullOrEmpty(senderWorld) || string.IsNullOrEmpty(senderName) ? "" : $" on {senderWorld}"); for (var chatTypeIndex = 0; chatTypeIndex < chatTypeConfigs.Count(); chatTypeIndex++) { if (!this.config.DisableEmbeds) { var embedBuilder = new EmbedBuilder { Author = new EmbedAuthorBuilder { IconUrl = avatarUrl, Name = name, Url = !string.IsNullOrEmpty(lodestoneId) ? "https://eu.finalfantasyxiv.com/lodestone/character/" + lodestoneId : null }, Description = rawMessage, Timestamp = DateTimeOffset.Now, Footer = new EmbedFooterBuilder { Text = type.GetDetails().FancyName }, Color = new Color((uint)(chatTypeConfigs.ElementAt(chatTypeIndex).Color & 0xFFFFFF)) }; if (this.config.CheckForDuplicateMessages) { var recentMsg = this.recentMessages.FirstOrDefault( msg => msg.Embeds.FirstOrDefault( embed => embed.Description == embedBuilder.Description && embed.Author.HasValue && embed.Author.Value.Name == embedBuilder.Author.Name && embed.Timestamp.HasValue && Math.Abs( (embed.Timestamp.Value.ToUniversalTime().Date - embedBuilder .Timestamp.Value.ToUniversalTime().Date) .Milliseconds) < 15000) != null); if (recentMsg != null) { Log.Verbose("Duplicate message: [{0}] {1}", embedBuilder.Author.Name, embedBuilder.Description); this.recentMessages.Remove(recentMsg); return; } } await channels.ElementAt(chatTypeIndex).SendMessageAsync(embed: embedBuilder.Build()); } else { var simpleMessage = $"{name}: {rawMessage}"; if (this.config.CheckForDuplicateMessages) { var recentMsg = this.recentMessages.FirstOrDefault( msg => msg.Content == simpleMessage); if (recentMsg != null) { Log.Verbose("Duplicate message: {0}", simpleMessage); this.recentMessages.Remove(recentMsg); return; } } await channels.ElementAt(chatTypeIndex).SendMessageAsync($"**[{chatTypeDetail.Slug}]{name}**: {rawMessage}"); } } }
/// <summary> /// Convert a Lumina SeString into a Dalamud SeString. /// This conversion re-parses the string. /// </summary> /// <param name="originalString">The original Lumina SeString.</param> /// <returns>The re-parsed Dalamud SeString.</returns> public static SeString ToDalamudString(this Lumina.Text.SeString originalString) => SeString.Parse(originalString.RawData);
private IntPtr HandlePrintMessageDetour(IntPtr manager, XivChatType chattype, IntPtr pSenderName, IntPtr pMessage, uint senderid, IntPtr parameter) { var retVal = IntPtr.Zero; try { var sender = StdString.ReadFromPointer(pSenderName); var message = StdString.ReadFromPointer(pMessage); var parsedSender = SeString.Parse(sender.RawData); var parsedMessage = SeString.Parse(message.RawData); //Log.Debug($"HandlePrintMessageDetour {manager} - [{chattype}] [{BitConverter.ToString(message.RawData).Replace("-", " ")}] {message.Value} from {senderName.Value}"); var originalMessageData = (byte[])message.RawData.Clone(); var oldEdited = parsedMessage.Encode(); // Call events var isHandled = false; OnChatMessage?.Invoke(chattype, senderid, ref parsedSender, ref parsedMessage, ref isHandled); OnChatMessageRaw?.Invoke(chattype, senderid, ref sender, ref message, ref isHandled); var newEdited = parsedMessage.Encode(); if (!FastByteArrayCompare(oldEdited, newEdited)) { Log.Verbose("SeString was edited, taking precedence over StdString edit."); message.RawData = newEdited; } Log.Debug($"\nOLD: {BitConverter.ToString(originalMessageData)}\nNEW: {BitConverter.ToString(newEdited)}"); var messagePtr = pMessage; OwnedStdString allocatedString = null; if (!FastByteArrayCompare(originalMessageData, message.RawData)) { allocatedString = this.dalamud.Framework.Libc.NewString(message.RawData); Log.Debug( $"HandlePrintMessageDetour String modified: {originalMessageData}({messagePtr}) -> {message}({allocatedString.Address})"); messagePtr = allocatedString.Address; } // Print the original chat if it's handled. if (!isHandled) { retVal = this.printMessageHook.Original(manager, chattype, pSenderName, messagePtr, senderid, parameter); } if (this.baseAddress == IntPtr.Zero) { this.baseAddress = manager; } allocatedString?.Dispose(); } catch (Exception ex) { Log.Error(ex, "Exception on OnChatMessage hook."); retVal = this.printMessageHook.Original(manager, chattype, pSenderName, pMessage, senderid, parameter); } return(retVal); }
private void OnChatMessage(XivChatType type, uint senderId, ref StdString sender, ref StdString message, ref bool isHandled) { if (type == XivChatType.Notice && !this.hasSeenLoadingMsg) { PrintWelcomeMessage(); } // For injections while logged in if (this.dalamud.ClientState.LocalPlayer != null && this.dalamud.ClientState.TerritoryType == 0 && !this.hasSeenLoadingMsg) { PrintWelcomeMessage(); } #if !DEBUG && false if (!this.hasSeenLoadingMsg) { return; } #endif var messageVal = message.Value; var senderVal = sender.Value; if (type == XivChatType.RetainerSale) { foreach (var regex in retainerSaleRegexes[dalamud.StartInfo.Language]) { var matchInfo = regex.Match(message.Value); // 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 itemLink = SeString.Parse(message.RawData).Payloads.First(x => x.Type == PayloadType.Item) as ItemPayload; if (itemLink == null) { Log.Error("itemLink was null. Msg: {0}", BitConverter.ToString(message.RawData)); break; } Log.Debug($"Probable retainer sale: {message}, decoded item {itemLink.Item.RowId}, HQ {itemLink.IsHQ}"); 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 var itemValue)) { continue; } Task.Run(() => this.dalamud.BotManager.ProcessRetainerSale((int)itemLink.Item.RowId, itemValue, itemLink.IsHQ)); break; } } var messageCopy = message; var senderCopy = sender; Task.Run(() => this.dalamud.BotManager.ProcessChatMessage(type, messageCopy, senderCopy)); // Handle all of this with SeString some day if ((this.HandledChatTypeColors.ContainsKey(type) || type == XivChatType.Say || type == XivChatType.Shout || type == XivChatType.Alliance || type == XivChatType.TellOutgoing || type == XivChatType.Yell) && !message.Value.Contains((char)0x02)) { var italicsStart = message.Value.IndexOf("*"); var italicsEnd = message.Value.IndexOf("*", italicsStart + 1); var messageString = message.Value; while (italicsEnd != -1) { var it = MakeItalics( messageString.Substring(italicsStart, italicsEnd - italicsStart + 1).Replace("*", "")); messageString = messageString.Remove(italicsStart, italicsEnd - italicsStart + 1); messageString = messageString.Insert(italicsStart, it); italicsStart = messageString.IndexOf("*"); italicsEnd = messageString.IndexOf("*", italicsStart + 1); } message.RawData = Encoding.UTF8.GetBytes(messageString); } var linkMatch = this.urlRegex.Match(message.Value); if (linkMatch.Value.Length > 0) { LastLink = linkMatch.Value; } }
public async Task ProcessChatMessage(XivChatType type, string message, string sender) { // Special case for outgoing tells, these should be sent under Incoming tells var wasOutgoingTell = false; if (type == XivChatType.TellOutgoing) { type = XivChatType.TellIncoming; sender = this.dalamud.ClientState.LocalPlayer.Name; wasOutgoingTell = true; } var chatTypeConfigs = this.config.ChatTypeConfigurations.Where(typeConfig => typeConfig.ChatType == type); if (!chatTypeConfigs.Any()) { return; } var chatTypeDetail = type.GetDetails(); var channels = chatTypeConfigs.Select(c => GetChannel(c.Channel).GetAwaiter().GetResult()); var senderSplit = sender.Split(new[] { this.worldIcon }, StringSplitOptions.None); var world = string.Empty; if (this.dalamud.ClientState.Actors.Length > 0) { world = this.dalamud.ClientState.LocalPlayer.CurrentWorld.Name; } if (senderSplit.Length == 2) { world = senderSplit[1]; sender = senderSplit[0]; } sender = SeString.Parse(sender).Output; message = SeString.Parse(message).Output; sender = RemoveAllNonLanguageCharacters(sender); var avatarUrl = ""; var lodestoneId = ""; if (!this.config.DisableEmbeds) { var searchResult = await GetCharacterInfo(sender, world); lodestoneId = searchResult.LodestoneId; avatarUrl = searchResult.AvatarUrl; } Thread.Sleep(this.config.ChatDelayMs); var name = wasOutgoingTell ? "You" : sender + (string.IsNullOrEmpty(world) || string.IsNullOrEmpty(sender) ? "" : $" on {world}"); for (var chatTypeIndex = 0; chatTypeIndex < chatTypeConfigs.Count(); chatTypeIndex++) { if (!this.config.DisableEmbeds) { var embedBuilder = new EmbedBuilder { Author = new EmbedAuthorBuilder { IconUrl = avatarUrl, Name = name, Url = !string.IsNullOrEmpty(lodestoneId) ? "https://eu.finalfantasyxiv.com/lodestone/character/" + lodestoneId : null }, Description = message, Timestamp = DateTimeOffset.Now, Footer = new EmbedFooterBuilder { Text = type.GetDetails().FancyName }, Color = new Color((uint)(chatTypeConfigs.ElementAt(chatTypeIndex).Color & 0xFFFFFF)) }; if (this.config.CheckForDuplicateMessages) { var recentMsg = this.recentMessages.FirstOrDefault( msg => msg.Embeds.FirstOrDefault( embed => embed.Description == embedBuilder.Description && embed.Author.HasValue && embed.Author.Value.Name == embedBuilder.Author.Name && embed.Timestamp.HasValue && Math.Abs( (embed.Timestamp.Value.ToUniversalTime().Date - embedBuilder .Timestamp.Value.ToUniversalTime().Date) .Milliseconds) < 15000) != null); if (recentMsg != null) { Log.Verbose("Duplicate message: [{0}] {1}", embedBuilder.Author.Name, embedBuilder.Description); this.recentMessages.Remove(recentMsg); return; } } await channels.ElementAt(chatTypeIndex).SendMessageAsync(embed: embedBuilder.Build()); } else { var simpleMessage = $"{name}: {message}"; if (this.config.CheckForDuplicateMessages) { var recentMsg = this.recentMessages.FirstOrDefault( msg => msg.Content == simpleMessage); if (recentMsg != null) { Log.Verbose("Duplicate message: {0}", simpleMessage); this.recentMessages.Remove(recentMsg); return; } } await channels.ElementAt(chatTypeIndex).SendMessageAsync($"**[{chatTypeDetail.Slug}]{name}**: {message}"); } } }
/// <summary> /// Read a null-terminated SeString from a specified memory address. /// </summary> /// <param name="memoryAddress">The memory address to read from.</param> /// <returns>The read in string.</returns> public static SeString ReadSeStringNullTerminated(IntPtr memoryAddress) { var buffer = ReadRawNullTerminated(memoryAddress); return(SeString.Parse(buffer)); }