diff --git a/Dalamud.Injector/Dalamud.Injector.csproj b/Dalamud.Injector/Dalamud.Injector.csproj index 1499d105c..7ccda5c39 100644 --- a/Dalamud.Injector/Dalamud.Injector.csproj +++ b/Dalamud.Injector/Dalamud.Injector.csproj @@ -1,4 +1,4 @@ - + AnyCPU net471 @@ -14,10 +14,10 @@ true - 2.4.0.0 - 2.4.0.0 + 2.9.0.0 + 2.9.0.0 XIVLauncher addon injection - 2.4.0 + 2.9.0 diff --git a/Dalamud/Dalamud.cs b/Dalamud/Dalamud.cs index 051e2ceee..b0952d8da 100644 --- a/Dalamud/Dalamud.cs +++ b/Dalamud/Dalamud.cs @@ -2,7 +2,9 @@ using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; +using System.Text; using System.Threading; +using System.Threading.Tasks; using Dalamud.DiscordBot; using Dalamud.Game; using Dalamud.Game.Chat; @@ -172,6 +174,16 @@ namespace Dalamud { CommandManager.AddHandler("/xlbotjoin", new CommandInfo(OnBotJoinCommand) { HelpMessage = "Add the XIVLauncher discord bot you set up to your server." }); + + CommandManager.AddHandler("/xlbgmset", new CommandInfo(OnBgmSetCommand) + { + HelpMessage = "Set the Game background music. Usage: /bgmset " + }); + + CommandManager.AddHandler("/xlitem", new CommandInfo(OnItemLinkCommand) + { + HelpMessage = "Link an item by name. Usage: /item " + }); } private void OnUnloadCommand(string command, string arguments) { @@ -198,7 +210,7 @@ namespace Dalamud { var msg = string.Join(" ", parts.Take(1).ToArray()); Framework.Gui.Chat.PrintChat(new XivChatEntry { - Message = msg, + MessageBytes = Encoding.UTF8.GetBytes(msg), Name = "Xiv Launcher", Type = chatType }); @@ -419,5 +431,63 @@ namespace Dalamud { Framework.Gui.Chat.Print( "The XIVLauncher discord bot was not set up correctly or could not connect to discord. Please check the settings and the FAQ."); } + + private void OnBgmSetCommand(string command, string arguments) + { + Framework.Network.InjectBgmTest(int.Parse(arguments)); + } + + private void OnItemLinkCommand(string command, string arguments) { + Task.Run(async () => { + try { + dynamic results = await XivApi.Search(arguments, "Item", 1); + var itemId = (short) results.Results[0].ID; + var itemName = (string)results.Results[0].Name; + + var hexData = new byte[] { + 0x02, 0x13, 0x06, 0xFE, 0xFF, 0xF3, 0xF3, 0xF3, 0x03, 0x02, 0x27, 0x07, 0x03, 0xF2, 0x3A, 0x2F, + 0x02, 0x01, 0x03, 0x02, 0x13, 0x06, 0xFE, 0xFF, 0xFF, 0x7B, 0x1A, 0x03, 0xEE, 0x82, 0xBB, 0x02, + 0x13, 0x02, 0xEC, 0x03 + }; + + var endTag = new byte[] { + 0x02, 0x27, 0x07, 0xCF, 0x01, 0x01, 0x01, 0xFF, 0x01, 0x03, 0x02, 0x13, 0x02, 0xEC, 0x03 + }; + + BitConverter.GetBytes(itemId).Reverse().ToArray().CopyTo(hexData, 14); + + hexData = hexData.Concat(Encoding.UTF8.GetBytes(itemName)).Concat(endTag).ToArray(); + + Framework.Gui.Chat.PrintChat(new XivChatEntry { + MessageBytes = hexData + }); + } + catch { + Framework.Gui.Chat.PrintError("Could not find item."); + } + + }); + } + + public static byte[] StringToByteArray(String value) + { + byte[] bytes = new byte[value.Length * sizeof(char)]; + Buffer.BlockCopy(value.ToCharArray(), 0, bytes, 0, bytes.Length); + return bytes; + } + + public static String ByteArrayToString(byte[] value) + { + var chars = new char[value.Length / sizeof(char)]; + + var atValue = 0; + for (var i = 0; i < chars.Length; i++) { + chars[i] = BitConverter.ToChar(value, atValue); + + atValue += 2; + } + + return new string(chars); + } } } diff --git a/Dalamud/Dalamud.csproj b/Dalamud/Dalamud.csproj index 56911b73e..ec8fdbac5 100644 --- a/Dalamud/Dalamud.csproj +++ b/Dalamud/Dalamud.csproj @@ -14,9 +14,9 @@ true - 2.5.0.0 - 2.5.0 - 2.5.0.0 + 2.7.0.0 + 2.7.0 + 2.7.0.0 diff --git a/Dalamud/DiscordBot/DiscordBotManager.cs b/Dalamud/DiscordBot/DiscordBotManager.cs index f0a86cce0..e9195feb0 100644 --- a/Dalamud/DiscordBot/DiscordBotManager.cs +++ b/Dalamud/DiscordBot/DiscordBotManager.cs @@ -231,13 +231,13 @@ namespace Dalamud.DiscordBot { wasOutgoingTell = true; } - var chatTypeConfig = - this.config.ChatTypeConfigurations.FirstOrDefault(typeConfig => typeConfig.ChatType == type); + var chatTypeConfigs = + this.config.ChatTypeConfigurations.Where(typeConfig => typeConfig.ChatType == type); - if (chatTypeConfig == null) + if (!chatTypeConfigs.Any()) return; - var channel = await GetChannel(chatTypeConfig.Channel); + var channels = chatTypeConfigs.Select(c => GetChannel(c.Channel).GetAwaiter().GetResult()); var senderSplit = sender.Split(new[] {this.worldIcon}, StringSplitOptions.None); @@ -271,47 +271,52 @@ namespace Dalamud.DiscordBot { Log.Error(ex, "Could not get XIVAPI character search result."); } - var embedBuilder = new EmbedBuilder { - Author = new EmbedAuthorBuilder { - IconUrl = avatarUrl, - Name = wasOutgoingTell + Thread.Sleep(this.config.ChatDelayMs); + + for (var chatTypeIndex = 0; chatTypeIndex < chatTypeConfigs.Count(); chatTypeIndex++) { + var embedBuilder = new EmbedBuilder + { + Author = new EmbedAuthorBuilder + { + IconUrl = avatarUrl, + Name = wasOutgoingTell ? "You" : sender + (string.IsNullOrEmpty(world) || string.IsNullOrEmpty(sender) ? "" : $" on {world}"), - Url = lodestoneId != 0 ? "https://eu.finalfantasyxiv.com/lodestone/character/" + lodestoneId : null - }, - Description = message, - Timestamp = DateTimeOffset.Now, - Footer = new EmbedFooterBuilder {Text = type.GetDetails().FancyName}, - Color = new Color((uint) (chatTypeConfig.Color & 0xFFFFFF)) - }; + Url = lodestoneId != 0 ? "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 (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; + 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()); } - - - Thread.Sleep(this.config.ChatDelayMs); - - await channel.SendMessageAsync(embed: embedBuilder.Build()); } private async Task GetChannel(ChannelConfiguration channelConfig) { @@ -321,7 +326,7 @@ namespace Dalamud.DiscordBot { } private string RemoveAllNonLanguageCharacters(string input) { - return Regex.Replace(input, @"[^\p{L} ]", ""); + return Regex.Replace(input, @"[^\p{L} ']", ""); } public void Dispose() { diff --git a/Dalamud/Game/Chat/XivChatEntry.cs b/Dalamud/Game/Chat/XivChatEntry.cs index 8fd338d02..3f4075535 100644 --- a/Dalamud/Game/Chat/XivChatEntry.cs +++ b/Dalamud/Game/Chat/XivChatEntry.cs @@ -8,7 +8,7 @@ namespace Dalamud.Game.Chat { public string Name { get; set; } = string.Empty; - public string Message { get; set; } = string.Empty; + public byte[] MessageBytes { get; set; } public IntPtr Parameters { get; set; } } diff --git a/Dalamud/Game/ChatHandlers.cs b/Dalamud/Game/ChatHandlers.cs index ce9834571..5512a5c30 100644 --- a/Dalamud/Game/ChatHandlers.cs +++ b/Dalamud/Game/ChatHandlers.cs @@ -1,6 +1,8 @@ using System.Collections.Generic; +using System.Diagnostics; using System.Drawing; using System.Linq; +using System.Reflection; using System.Text; using System.Text.RegularExpressions; using System.Threading.Tasks; @@ -67,7 +69,7 @@ namespace Dalamud.Game { ref bool isHandled) { if (type == XivChatType.Notice && !this.hasSeenLoadingMsg) { - this.dalamud.Framework.Gui.Chat.Print("XIVLauncher in-game addon loaded."); + this.dalamud.Framework.Gui.Chat.Print($"XIVLauncher in-game addon v{Assembly.GetAssembly(typeof(ChatHandlers)).GetName().Version} loaded."); this.hasSeenLoadingMsg = true; } diff --git a/Dalamud/Game/ClientState/Actors/ActorTable.cs b/Dalamud/Game/ClientState/Actors/ActorTable.cs index e9b6e7278..586404fd1 100644 --- a/Dalamud/Game/ClientState/Actors/ActorTable.cs +++ b/Dalamud/Game/ClientState/Actors/ActorTable.cs @@ -42,15 +42,20 @@ namespace Dalamud.Game.ClientState.Actors { if (offset == IntPtr.Zero) return null; - var actorStruct = Marshal.PtrToStructure(offset); + try { + var actorStruct = Marshal.PtrToStructure(offset); - //Log.Debug("ActorTable[{0}]: {1} - {2} - {3}", index, tblIndex.ToString("X"), offset.ToString("X"), - // actorStruct.ObjectKind.ToString()); + //Log.Debug("ActorTable[{0}]: {1} - {2} - {3}", index, tblIndex.ToString("X"), offset.ToString("X"), + // actorStruct.ObjectKind.ToString()); - switch (actorStruct.ObjectKind) { - case ObjectKind.Player: return new PlayerCharacter(actorStruct); - case ObjectKind.BattleNpc: return new BattleNpc(actorStruct); - default: return new Actor(actorStruct); + switch (actorStruct.ObjectKind) + { + case ObjectKind.Player: return new PlayerCharacter(actorStruct); + case ObjectKind.BattleNpc: return new BattleNpc(actorStruct); + default: return new Actor(actorStruct); + } + } catch (AccessViolationException) { + return null; } } } diff --git a/Dalamud/Game/Internal/Framework.cs b/Dalamud/Game/Internal/Framework.cs index b970ac49a..119b9b2dc 100644 --- a/Dalamud/Game/Internal/Framework.cs +++ b/Dalamud/Game/Internal/Framework.cs @@ -90,6 +90,7 @@ namespace Dalamud.Game.Internal { private bool HandleFrameworkUpdate(IntPtr framework) { try { Gui.Chat.UpdateQueue(this); + Network.UpdateQueue(this); } catch (Exception ex) { Log.Error(ex, "Exception while handling Framework::Update hook."); } diff --git a/Dalamud/Game/Internal/Gui/ChatGui.cs b/Dalamud/Game/Internal/Gui/ChatGui.cs index 4b64f9364..ab59c4b00 100644 --- a/Dalamud/Game/Internal/Gui/ChatGui.cs +++ b/Dalamud/Game/Internal/Gui/ChatGui.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Runtime.InteropServices; +using System.Text; using Dalamud.Game.Chat; using Dalamud.Game.Internal.Libc; using Dalamud.Hooking; @@ -11,7 +12,7 @@ namespace Dalamud.Game.Internal.Gui { [UnmanagedFunctionPointer(CallingConvention.ThisCall)] private delegate void PrintMessageDelegate(IntPtr manager, XivChatType chatType, IntPtr senderName, IntPtr message, - uint senderId, byte isLocal); + uint senderId, IntPtr parameter); public delegate void OnMessageDelegate(XivChatType type, uint senderId, string sender, ref string message, ref bool isHandled); @@ -79,12 +80,12 @@ namespace Dalamud.Game.Internal.Gui { } private void HandlePrintMessageDetour(IntPtr manager, XivChatType chattype, IntPtr pSenderName, IntPtr pMessage, - uint senderid, byte isLocal) { + uint senderid, IntPtr parameter) { try { var senderName = StdString.ReadFromPointer(pSenderName); var message = StdString.ReadFromPointer(pMessage); - //Log.Debug($"HandlePrintMessageDetour {manager} - [{chattype}] [{BitConverter.ToString(Encoding.UTF8.GetBytes(message))}] {message} from {senderName}"); + Log.Debug($"HandlePrintMessageDetour {manager} - [{chattype}] [{BitConverter.ToString(Encoding.UTF8.GetBytes(message)).Replace("-", " ")}] {message} from {senderName}"); var originalMessage = string.Copy(message); @@ -93,10 +94,10 @@ namespace Dalamud.Game.Internal.Gui { OnChatMessage?.Invoke(chattype, senderid, senderName, ref message, ref isHandled); var messagePtr = pMessage; - OwnedStdString allocatedString = null; + OwnedStdString allocatedString = null; if (originalMessage != message) { - allocatedString = this.dalamud.Framework.Libc.NewString(message); + allocatedString = this.dalamud.Framework.Libc.NewString(Encoding.UTF8.GetBytes(message)); Log.Debug( $"HandlePrintMessageDetour String modified: {originalMessage}({messagePtr}) -> {message}({allocatedString.Address})"); messagePtr = allocatedString.Address; @@ -104,7 +105,7 @@ namespace Dalamud.Game.Internal.Gui { // Print the original chat if it's handled. if (!isHandled) - this.printMessageHook.Original(manager, chattype, pSenderName, messagePtr, senderid, isLocal); + this.printMessageHook.Original(manager, chattype, pSenderName, messagePtr, senderid, parameter); if (this.baseAddress == IntPtr.Zero) this.baseAddress = manager; @@ -112,7 +113,7 @@ namespace Dalamud.Game.Internal.Gui { allocatedString?.Dispose(); } catch (Exception ex) { Log.Error(ex, "Exception on OnChatMessage hook."); - this.printMessageHook.Original(manager, chattype, pSenderName, pMessage, senderid, isLocal); + this.printMessageHook.Original(manager, chattype, pSenderName, pMessage, senderid, parameter); } } @@ -127,13 +128,13 @@ namespace Dalamud.Game.Internal.Gui { public void Print(string message) { PrintChat(new XivChatEntry { - Message = message + MessageBytes = Encoding.UTF8.GetBytes(message) }); } public void PrintError(string message) { PrintChat(new XivChatEntry { - Message = message, + MessageBytes = Encoding.UTF8.GetBytes(message), Type = XivChatType.Urgent }); } @@ -146,13 +147,15 @@ namespace Dalamud.Game.Internal.Gui { var chat = this.chatQueue.Dequeue(); var sender = chat.Name ?? ""; - var message = chat.Message ?? ""; + var message = chat.MessageBytes ?? new byte[0]; if (this.baseAddress != IntPtr.Zero) - using (var senderVec = framework.Libc.NewString(sender)) - using (var messageVec = framework.Libc.NewString(message)) { + using (var senderVec = framework.Libc.NewString(Encoding.UTF8.GetBytes(sender))) + using (var messageVec = framework.Libc.NewString(message)) + { + Log.Verbose($"String allocated to {messageVec.Address.ToInt64():X}"); this.printMessageHook.Original(this.baseAddress, chat.Type, senderVec.Address, - messageVec.Address, chat.SenderId, 0); + messageVec.Address, chat.SenderId, chat.Parameters); } } } diff --git a/Dalamud/Game/Internal/Libc/LibcFunction.cs b/Dalamud/Game/Internal/Libc/LibcFunction.cs index 93e7fcebb..88261c3f7 100644 --- a/Dalamud/Game/Internal/Libc/LibcFunction.cs +++ b/Dalamud/Game/Internal/Libc/LibcFunction.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Runtime.InteropServices; using Serilog; @@ -6,7 +6,7 @@ namespace Dalamud.Game.Internal.Libc { public sealed class LibcFunction { // TODO: prolly callconv is not okay in x86 [UnmanagedFunctionPointer(CallingConvention.ThisCall)] - private delegate IntPtr StdStringFromCStringDelegate(IntPtr pStdString, [MarshalAs(UnmanagedType.LPUTF8Str)]string content, IntPtr size); + private delegate IntPtr StdStringFromCStringDelegate(IntPtr pStdString, [MarshalAs(UnmanagedType.LPArray)]byte[] content, IntPtr size); // TODO: prolly callconv is not okay in x86 [UnmanagedFunctionPointer(CallingConvention.ThisCall)] @@ -25,7 +25,7 @@ namespace Dalamud.Game.Internal.Libc { this.stdStringDeallocate = Marshal.GetDelegateForFunctionPointer(Address.StdStringDeallocate); } - public OwnedStdString NewString(string content) { + public OwnedStdString NewString(byte[] content) { Log.Verbose("Allocating"); // While 0x70 bytes in the memory should be enough in DX11 version, @@ -36,7 +36,7 @@ namespace Dalamud.Game.Internal.Libc { var npos = new IntPtr(0xFFFFFFFF); // assumed to be -1 (0xFFFFFFFF in x86, 0xFFFFFFFF_FFFFFFFF in amd64) var pReallocString = this.stdStringCtorCString(pString, content, npos); - Log.Verbose("Prev: {Prev} Now: {Now}", pString, pReallocString); + //Log.Verbose("Prev: {Prev} Now: {Now}", pString, pReallocString); return new OwnedStdString(pReallocString, DeallocateStdString); } diff --git a/Dalamud/Game/Internal/Network/GameNetwork.cs b/Dalamud/Game/Internal/Network/GameNetwork.cs index 25acfa562..f1928436c 100644 --- a/Dalamud/Game/Internal/Network/GameNetwork.cs +++ b/Dalamud/Game/Internal/Network/GameNetwork.cs @@ -1,4 +1,6 @@ using System; +using System.Collections.Generic; +using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using Dalamud.Hooking; using Serilog; @@ -11,6 +13,7 @@ namespace Dalamud.Game.Internal.Network { private readonly Hook processZonePacketHook; private GameNetworkAddressResolver Address { get; } + private IntPtr baseAddress; public delegate void OnZonePacketDelegate(IntPtr dataPtr); @@ -18,6 +21,8 @@ namespace Dalamud.Game.Internal.Network { private readonly Dalamud dalamud; + private readonly Queue zoneInjectQueue = new Queue(); + public GameNetwork(Dalamud dalamud, SigScanner scanner) { this.dalamud = dalamud; Address = new GameNetworkAddressResolver(); @@ -40,6 +45,8 @@ namespace Dalamud.Game.Internal.Network { } private void ProcessZonePacketDetour(IntPtr a, IntPtr b, IntPtr dataPtr) { + this.baseAddress = a; + // Call events this.OnZonePacket?.Invoke(dataPtr); @@ -60,5 +67,47 @@ namespace Dalamud.Game.Internal.Network { this.processZonePacketHook.Original(a, b, dataPtr); } } + + private void InjectZoneProtoPacket(byte[] data) { + this.zoneInjectQueue.Enqueue(data); + } + + private void InjectActorControl(short cat, int param1) { + var packetData = new byte[] { + 0x14, 0x00, 0x64, 0x01, 0x00, 0x00, 0x0E, 0x00, 0x17, 0x7C, 0xC5, 0x5D, 0x00, 0x00, 0x00, 0x00, + 0x05, 0x00, 0x48, 0xB2, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x43, 0x7F, 0x00, 0x00 + }; + + BitConverter.GetBytes((short) cat).CopyTo(packetData, 0x10); + + BitConverter.GetBytes((UInt32) param1).CopyTo(packetData, 0x14); + + InjectZoneProtoPacket(packetData); + } + + public void InjectBgmTest(int key) { + InjectActorControl(0xa1, key); + } + + /// + /// Process a chat queue. + /// + public void UpdateQueue(Framework framework) + { + while (this.zoneInjectQueue.Count > 0) + { + var packetData = this.zoneInjectQueue.Dequeue(); + + var unmanagedPacketData = Marshal.AllocHGlobal(packetData.Length); + Marshal.Copy(packetData, 0, unmanagedPacketData, packetData.Length); + + if (this.baseAddress != IntPtr.Zero) { + this.processZonePacketHook.Original(this.baseAddress, IntPtr.Zero, unmanagedPacketData); + } + + Marshal.FreeHGlobal(unmanagedPacketData); + } + } } }