diff --git a/XIVChatCommon/Message.cs b/XIVChatCommon/Message.cs index 4822559..3285097 100644 --- a/XIVChatCommon/Message.cs +++ b/XIVChatCommon/Message.cs @@ -101,6 +101,222 @@ namespace XIVChatCommon { this.code = code; } + public ChatType Parent() { + switch (this.Type) { + case ChatType.Say: + case ChatType.GmSay: + return ChatType.Say; + case ChatType.Shout: + case ChatType.GmShout: + return ChatType.Shout; + case ChatType.TellOutgoing: + case ChatType.TellIncoming: + case ChatType.GmTell: + return ChatType.TellOutgoing; + case ChatType.Party: + case ChatType.CrossParty: + case ChatType.GmParty: + return ChatType.Party; + case ChatType.Linkshell1: + case ChatType.GmLinkshell1: + return ChatType.Linkshell1; + case ChatType.Linkshell2: + case ChatType.GmLinkshell2: + return ChatType.Linkshell2; + case ChatType.Linkshell3: + case ChatType.GmLinkshell3: + return ChatType.Linkshell3; + case ChatType.Linkshell4: + case ChatType.GmLinkshell4: + return ChatType.Linkshell4; + case ChatType.Linkshell5: + case ChatType.GmLinkshell5: + return ChatType.Linkshell5; + case ChatType.Linkshell6: + case ChatType.GmLinkshell6: + return ChatType.Linkshell6; + case ChatType.Linkshell7: + case ChatType.GmLinkshell7: + return ChatType.Linkshell7; + case ChatType.Linkshell8: + case ChatType.GmLinkshell8: + return ChatType.Linkshell8; + case ChatType.FreeCompany: + case ChatType.GmFreeCompany: + return ChatType.FreeCompany; + case ChatType.NoviceNetwork: + case ChatType.GmNoviceNetwork: + return ChatType.NoviceNetwork; + case ChatType.CustomEmote: + return ChatType.CustomEmote; + case ChatType.StandardEmote: + return ChatType.StandardEmote; + case ChatType.Yell: + case ChatType.GmYell: + return ChatType.Yell; + case ChatType.GainBuff: + case ChatType.LoseBuff: + return ChatType.GainBuff; + case ChatType.GainDebuff: + case ChatType.LoseDebuff: + return ChatType.GainDebuff; + case ChatType.System: + case ChatType.Alarm: + case ChatType.RetainerSale: + case ChatType.PeriodicRecruitmentNotification: + case ChatType.Sign: + case ChatType.Orchestrion: + case ChatType.MessageBook: + return ChatType.System; + case ChatType.NpcDialogue: + case ChatType.NpcAnnouncement: + return ChatType.NpcDialogue; + case ChatType.LootRoll: + case ChatType.RandomNumber: + return ChatType.LootRoll; + case ChatType.FreeCompanyAnnouncement: + case ChatType.FreeCompanyLoginLogout: + return ChatType.FreeCompanyAnnouncement; + case ChatType.PvpTeamAnnouncement: + case ChatType.PvpTeamLoginLogout: + return ChatType.PvpTeamAnnouncement; + default: + return this.Type; + } + } + + //public string ConfigKey() { + // switch (this.Type) { + // case ChatType.Say: + // case ChatType.GmSay: + // return "ColorSay"; + // case ChatType.Shout: + // case ChatType.GmShout: + // return "ColorShout"; + // case ChatType.TellOutgoing: + // case ChatType.TellIncoming: + // case ChatType.GmTell: + // return "ColorTell"; + // case ChatType.Party: + // case ChatType.CrossParty: + // case ChatType.GmParty: + // return "ColorParty"; + // case ChatType.Alliance: + // return "ColorAlliance"; + // case ChatType.Linkshell1: + // case ChatType.GmLinkshell1: + // return "ColorLS1"; + // case ChatType.Linkshell2: + // case ChatType.GmLinkshell2: + // return "ColorLS2"; + // case ChatType.Linkshell3: + // case ChatType.GmLinkshell3: + // return "ColorLS3"; + // case ChatType.Linkshell4: + // case ChatType.GmLinkshell4: + // return "ColorLS4"; + // case ChatType.Linkshell5: + // case ChatType.GmLinkshell5: + // return "ColorLS5"; + // case ChatType.Linkshell6: + // case ChatType.GmLinkshell6: + // return "ColorLS6"; + // case ChatType.Linkshell7: + // case ChatType.GmLinkshell7: + // return "ColorLS7"; + // case ChatType.Linkshell8: + // case ChatType.GmLinkshell8: + // return "ColorLS8"; + // case ChatType.FreeCompany: + // case ChatType.GmFreeCompany: + // return "ColorFCompany"; + // case ChatType.NoviceNetwork: + // case ChatType.GmNoviceNetwork: + // return "ColorBeginner"; + // case ChatType.CustomEmote: + // return "ColorEmoteUser"; + // case ChatType.StandardEmote: + // return "ColorEmote"; + // case ChatType.Yell: + // case ChatType.GmYell: + // return "ColorYell"; + // case ChatType.PvpTeam: + // return "ColorPvPGroup"; + // case ChatType.CrossLinkshell1: + // return "ColorCWLS"; + // case ChatType.Damage: + // return "ColorAttackSuccess"; + // case ChatType.Miss: + // return "ColorAttackFailure"; + // case ChatType.Action: + // return "ColorAction"; + // case ChatType.Item: + // return "ColorItem"; + // case ChatType.Healing: + // return "ColorCureGive"; + // case ChatType.GainBuff: + // case ChatType.GainDebuff: + // return "ColorBuffGive"; + // case ChatType.LoseBuff: + // case ChatType.LoseDebuff: + // return "ColorDebuffGive"; + // case ChatType.Echo: + // return "ColorEcho"; + // case ChatType.System: + // case ChatType.Alarm: + // case ChatType.RetainerSale: + // case ChatType.PeriodicRecruitmentNotification: + // case ChatType.Sign: + // case ChatType.Orchestrion: + // case ChatType.MessageBook: + // return "ColorSysMsg"; + // case ChatType.BattleSystem: + // return "ColorSysBattle"; + // case ChatType.GatheringSystem: + // return "ColorSysGathering"; + // case ChatType.Error: + // return "ColorSysError"; + // case ChatType.NpcDialogue: + // case ChatType.NpcAnnouncement: + // return "ColorNpcSay"; + // case ChatType.LootNotice: + // return "ColorItemNotice"; + // case ChatType.Progress: + // return "ColorGrowup"; + // case ChatType.LootRoll: + // case ChatType.RandomNumber: + // return "ColorLoot"; + // case ChatType.Crafting: + // return "ColorCraft"; + // case ChatType.Gathering: + // return "ColorGathering"; + // case ChatType.FreeCompanyAnnouncement: + // case ChatType.FreeCompanyLoginLogout: + // return "ColorFCAnnounce"; + // case ChatType.NoviceNetworkSystem: + // return "ColorBeginnerAnnounce"; + // case ChatType.PvpTeamAnnouncement: + // case ChatType.PvpTeamLoginLogout: + // return "ColorPvPGroupAnnounce"; + // case ChatType.CrossLinkshell2: + // return "ColorCWLS2"; + // case ChatType.CrossLinkshell3: + // return "ColorCWLS3"; + // case ChatType.CrossLinkshell4: + // return "ColorCWLS4"; + // case ChatType.CrossLinkshell5: + // return "ColorCWLS5"; + // case ChatType.CrossLinkshell6: + // return "ColorCWLS6"; + // case ChatType.CrossLinkshell7: + // return "ColorCWLS7"; + // case ChatType.CrossLinkshell8: + // return "ColorCWLS8"; + // default: + // return null; + // } + //} + public NameFormatting NameFormat() { switch (this.Type) { case ChatType.Say: diff --git a/XIVChatPlugin/GameFunctions.cs b/XIVChatPlugin/GameFunctions.cs index 19a9882..74ad698 100644 --- a/XIVChatPlugin/GameFunctions.cs +++ b/XIVChatPlugin/GameFunctions.cs @@ -17,6 +17,7 @@ namespace XIVChatPlugin { private delegate byte RequestFriendListDelegate(IntPtr manager); private delegate int FormatFriendListNameDelegate(long a1, long a2, long a3, int a4, IntPtr data, long a6); private delegate IntPtr OnReceiveFriendListChunkDelegate(IntPtr a1, IntPtr data); + private delegate IntPtr GetColourInfoDelegate(IntPtr handler, uint lookupResult); private readonly Hook friendListHook; private readonly Hook formatHook; @@ -24,8 +25,11 @@ namespace XIVChatPlugin { private readonly GetUIModuleDelegate GetUIModule; private readonly EasierProcessChatBoxDelegate _EasierProcessChatBox; + private readonly GetColourInfoDelegate GetColourInfo; private readonly IntPtr uiModulePtr; + private readonly IntPtr colourHandler; + private readonly IntPtr colourLookup; private IntPtr friendListManager = IntPtr.Zero; private bool requestingFriendList = false; @@ -43,10 +47,15 @@ namespace XIVChatPlugin { var friendListPtr = this.plugin.Interface.TargetModuleScanner.ScanText("40 53 48 81 EC 80 0F 00 00 48 8B 05 ?? ?? ?? ?? 48 33 C4 48 89 84 24 ?? ?? ?? ?? 48 8B D9 48 8B 0D ?? ?? ?? ?? E8 ?? ?? ?? ?? 48 85 C0 0F 84 ?? ?? ?? ?? 44 0F B6 43 ?? 33 C9"); var formatPtr = this.plugin.Interface.TargetModuleScanner.ScanText("48 89 5C 24 ?? 48 89 6C 24 ?? 48 89 74 24 ?? 41 56 48 83 EC 30 48 8B 6C 24 ??"); var recvChunkPtr = this.plugin.Interface.TargetModuleScanner.ScanText("48 89 5C 24 ?? 56 48 83 EC 20 48 8B 0D ?? ?? ?? ?? 48 8B F2"); + var getColourPtr = this.plugin.Interface.TargetModuleScanner.ScanText("48 89 5C 24 ?? 48 89 6C 24 ?? 48 89 74 24 ?? 57 48 83 EC 20 8B F2 48 8D B9 ?? ?? ?? ??"); + this.uiModulePtr = this.plugin.Interface.TargetModuleScanner.GetStaticAddressFromSig("48 8B 0D ?? ?? ?? ?? 48 8D 54 24 ?? 48 83 C1 10 E8 ?? ?? ?? ??"); + this.colourHandler = this.plugin.Interface.TargetModuleScanner.GetStaticAddressFromSig("48 8B 05 ?? ?? ?? ?? 48 8B A8 ?? ?? ?? ?? 48 85 ED 0F 84 ?? ?? ?? ??"); + this.colourLookup = this.plugin.Interface.TargetModuleScanner.GetStaticAddressFromSig("48 8D 0D ?? ?? ?? ?? 8B 14 ?? 85 D2 7E ?? 48 8B 0D ?? ?? ?? ?? 48 83 C1 10 E8 ?? ?? ?? ?? 8B 70 ?? 41 8D 4D ??"); this.GetUIModule = Marshal.GetDelegateForFunctionPointer(getUIModulePtr); this._EasierProcessChatBox = Marshal.GetDelegateForFunctionPointer(easierProcessChatBoxPtr); + this.GetColourInfo = Marshal.GetDelegateForFunctionPointer(getColourPtr); this.friendListHook = new Hook(friendListPtr, new RequestFriendListDelegate(this.OnRequestFriendList)); this.formatHook = new Hook(formatPtr, new FormatFriendListNameDelegate(this.OnFormatFriendList)); @@ -57,6 +66,35 @@ namespace XIVChatPlugin { this.receiveChunkHook.Enable(); } + // This function looks up a channel's user-defined colour. + // + // If this function would ever return 0, it returns null instead. + public uint? GetChannelColour(ChatCode channel) { + // Colours are retrieved by looking up their code in a lookup table. Some codes share a colour, so they're lumped into a parent code here. + // Only codes >= 10 (say) have configurable colours. + // After getting the lookup value for the code, it is passed into a function with a handler which returns a pointer. + // This pointer + 32 is the RGB value. This functions returns RGBA with A always max. + + var parent = channel.Parent(); + + switch (parent) { + case ChatType.Debug: + case ChatType.Urgent: + case ChatType.Notice: + return channel.DefaultColour(); + } + + var lookupResult = (uint)Marshal.ReadInt32(this.colourLookup, (int)parent * 4); + var info = this.GetColourInfo(Marshal.ReadIntPtr(this.colourHandler) + 16, lookupResult); + var rgb = (uint)Marshal.ReadInt32(info, 32) & 0xFFFFFF; + + if (rgb == 0) { + return null; + } + + return 0xFF | (rgb << 8); + } + public void ProcessChatBox(string message) { IntPtr uiModule = this.GetUIModule(Marshal.ReadIntPtr(this.uiModulePtr)); diff --git a/XIVChatPlugin/Server.cs b/XIVChatPlugin/Server.cs index 0917e0f..834a7ff 100644 --- a/XIVChatPlugin/Server.cs +++ b/XIVChatPlugin/Server.cs @@ -190,24 +190,26 @@ namespace XIVChatPlugin { var chunks = new List(); + var colour = this.plugin.Functions.GetChannelColour(chatCode) ?? chatCode.DefaultColour(); + if (sender.Payloads.Count > 0) { // FIXME: can't get format straight from game until Lumina stops returning LogKind.Format as a string (it's an SeString) // var format = this.FormatFor(chatCode.Type); var format = chatCode.NameFormat(); if (format != null && format.IsPresent) { chunks.Add(new TextChunk { - FallbackColour = chatCode.DefaultColour(), + FallbackColour = colour, Content = format.Before, }); - chunks.AddRange(ToChunks(sender, chatCode.DefaultColour())); + chunks.AddRange(ToChunks(sender, colour)); chunks.Add(new TextChunk { - FallbackColour = chatCode.DefaultColour(), + FallbackColour = colour, Content = format.After, }); } } - chunks.AddRange(ToChunks(message, chatCode.DefaultColour())); + chunks.AddRange(ToChunks(message, colour)); var msg = new ServerMessage { Timestamp = DateTime.UtcNow,