cleanup and reformatting

This commit is contained in:
Infi
2024-04-11 06:44:23 +02:00
parent 9f771c37e3
commit 48fec7dfaa
9 changed files with 240 additions and 199 deletions
+7 -2
View File
@@ -2,9 +2,9 @@ name: Chat 2
author: Infi, Anna author: Infi, Anna
punchline: Electric Boogaloo - ♪ A whole new chat, a new fantastic chat window ♪ punchline: Electric Boogaloo - ♪ A whole new chat, a new fantastic chat window ♪
description: |- description: |-
Chat 2 is a complete rewrite of the in-game chat window as a plugin. Chat 2 is a complete rewrite of the in-game chat window as a plugin.
It supports: It supports:
- Unlimited tabs - Unlimited tabs
- Tabs that always send to a certain channel - Tabs that always send to a certain channel
- More flexible filtering - More flexible filtering
@@ -15,3 +15,8 @@ description: |-
- Screenshot mode (obfuscate names) - Screenshot mode (obfuscate names)
repo_url: https://github.com/Infiziert90/ChatTwo repo_url: https://github.com/Infiziert90/ChatTwo
accepts_feedback: true accepts_feedback: true
tags:
- Social
- UI
- Chat
- Replacement
+12 -6
View File
@@ -2,7 +2,8 @@ using LiteDB;
namespace ChatTwo.Code; namespace ChatTwo.Code;
internal class ChatCode { internal class ChatCode
{
private const ushort Clear7 = ~(~0 << 7); private const ushort Clear7 = ~(~0 << 7);
internal ushort Raw { get; } internal ushort Raw { get; }
@@ -12,7 +13,8 @@ internal class ChatCode {
internal ChatSource Target { get; } internal ChatSource Target { get; }
private ChatSource SourceFrom(ushort shift) => (ChatSource) (1 << ((Raw >> shift) & 0xF)); private ChatSource SourceFrom(ushort shift) => (ChatSource) (1 << ((Raw >> shift) & 0xF));
internal ChatCode(ushort raw) { internal ChatCode(ushort raw)
{
Raw = raw; Raw = raw;
Type = (ChatType) (Raw & Clear7); Type = (ChatType) (Raw & Clear7);
Source = SourceFrom(11); Source = SourceFrom(11);
@@ -20,14 +22,16 @@ internal class ChatCode {
} }
[BsonCtor] [BsonCtor]
public ChatCode(ushort raw, ChatType type, ChatSource source, ChatSource target) { public ChatCode(ushort raw, ChatType type, ChatSource source, ChatSource target)
{
Raw = raw; Raw = raw;
Type = type; Type = type;
Source = source; Source = source;
Target = target; Target = target;
} }
internal ChatType Parent() => Type switch { internal ChatType Parent() => Type switch
{
ChatType.Say => ChatType.Say, ChatType.Say => ChatType.Say,
ChatType.GmSay => ChatType.Say, ChatType.GmSay => ChatType.Say,
ChatType.Shout => ChatType.Shout, ChatType.Shout => ChatType.Shout,
@@ -84,8 +88,10 @@ internal class ChatCode {
_ => Type, _ => Type,
}; };
internal bool IsBattle() { internal bool IsBattle()
switch (Type) { {
switch (Type)
{
case ChatType.Damage: case ChatType.Damage:
case ChatType.Miss: case ChatType.Miss:
case ChatType.Action: case ChatType.Action:
+2 -1
View File
@@ -2,7 +2,8 @@ namespace ChatTwo.Code;
[System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1028:Enum Storage should be Int32")] [System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1028:Enum Storage should be Int32")]
[Flags] [Flags]
internal enum ChatSource : ushort { internal enum ChatSource : ushort
{
Self = 2, Self = 2,
PartyMember = 4, PartyMember = 4,
AllianceMember = 8, AllianceMember = 8,
+4 -2
View File
@@ -2,7 +2,8 @@ using ChatTwo.Resources;
namespace ChatTwo.Code; namespace ChatTwo.Code;
internal static class ChatSourceExt { internal static class ChatSourceExt
{
internal const ChatSource All = internal const ChatSource All =
ChatSource.Self ChatSource.Self
| ChatSource.PartyMember | ChatSource.PartyMember
@@ -16,7 +17,8 @@ internal static class ChatSourceExt {
| ChatSource.AlliancePet | ChatSource.AlliancePet
| ChatSource.OtherPet; | ChatSource.OtherPet;
internal static string Name(this ChatSource source) => source switch { internal static string Name(this ChatSource source) => source switch
{
ChatSource.Self => Language.ChatSource_Self, ChatSource.Self => Language.ChatSource_Self,
ChatSource.PartyMember => Language.ChatSource_PartyMember, ChatSource.PartyMember => Language.ChatSource_PartyMember,
ChatSource.AllianceMember => Language.ChatSource_AllianceMember, ChatSource.AllianceMember => Language.ChatSource_AllianceMember,
+3 -2
View File
@@ -1,7 +1,8 @@
namespace ChatTwo.Code; namespace ChatTwo.Code;
[System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1028:Enum Storage should be Int32")] [System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1028:Enum Storage should be Int32")]
internal enum ChatType : ushort { internal enum ChatType : ushort
{
Debug = 1, Debug = 1,
Urgent = 2, Urgent = 2,
Notice = 3, Notice = 3,
@@ -84,7 +85,7 @@ internal enum ChatType : ushort {
CrossLinkshell6 = 105, CrossLinkshell6 = 105,
CrossLinkshell7 = 106, CrossLinkshell7 = 106,
CrossLinkshell8 = 107, CrossLinkshell8 = 107,
// Custom types: // Custom types:
ExtraChatLinkshell1 = 1001, ExtraChatLinkshell1 = 1001,
ExtraChatLinkshell2 = 1002, ExtraChatLinkshell2 = 1002,
+33 -17
View File
@@ -3,14 +3,19 @@ using ChatTwo.Util;
namespace ChatTwo.Code; namespace ChatTwo.Code;
internal static class ChatTypeExt { internal static class ChatTypeExt
internal static IEnumerable<(string, ChatType[])> SortOrder => new[] { {
(Language.Options_Tabs_ChannelTypes_Special, new[] { internal static IEnumerable<(string, ChatType[])> SortOrder => new[]
{
(Language.Options_Tabs_ChannelTypes_Special,
[
ChatType.Debug, ChatType.Debug,
ChatType.Urgent, ChatType.Urgent,
ChatType.Notice, ChatType.Notice
}), ]),
(Language.Options_Tabs_ChannelTypes_Chat, new[] {
(Language.Options_Tabs_ChannelTypes_Chat,
[
ChatType.Say, ChatType.Say,
ChatType.Yell, ChatType.Yell,
ChatType.Shout, ChatType.Shout,
@@ -39,9 +44,11 @@ internal static class ChatTypeExt {
ChatType.Linkshell8, ChatType.Linkshell8,
ChatType.NoviceNetwork, ChatType.NoviceNetwork,
ChatType.StandardEmote, ChatType.StandardEmote,
ChatType.CustomEmote, ChatType.CustomEmote
}), ]),
(Language.Options_Tabs_ChannelTypes_Battle, new[] {
(Language.Options_Tabs_ChannelTypes_Battle, new[]
{
ChatType.Damage, ChatType.Damage,
ChatType.Miss, ChatType.Miss,
ChatType.Action, ChatType.Action,
@@ -52,7 +59,9 @@ internal static class ChatTypeExt {
ChatType.GainDebuff, ChatType.GainDebuff,
ChatType.LoseDebuff, ChatType.LoseDebuff,
}), }),
(Language.Options_Tabs_ChannelTypes_Announcements, new[] {
(Language.Options_Tabs_ChannelTypes_Announcements, new[]
{
ChatType.System, ChatType.System,
ChatType.BattleSystem, ChatType.BattleSystem,
ChatType.GatheringSystem, ChatType.GatheringSystem,
@@ -80,8 +89,10 @@ internal static class ChatTypeExt {
}), }),
}; };
internal static string Name(this ChatType type) { internal static string Name(this ChatType type)
return type switch { {
return type switch
{
ChatType.Debug => Language.ChatType_Debug, ChatType.Debug => Language.ChatType_Debug,
ChatType.Urgent => Language.ChatType_Urgent, ChatType.Urgent => Language.ChatType_Urgent,
ChatType.Notice => Language.ChatType_Notice, ChatType.Notice => Language.ChatType_Notice,
@@ -174,8 +185,10 @@ internal static class ChatTypeExt {
}; };
} }
internal static uint? DefaultColour(this ChatType type) { internal static uint? DefaultColour(this ChatType type)
switch (type) { {
switch (type)
{
case ChatType.Debug: case ChatType.Debug:
return ColourUtil.ComponentsToRgba(204, 204, 204); return ColourUtil.ComponentsToRgba(204, 204, 204);
case ChatType.Urgent: case ChatType.Urgent:
@@ -292,7 +305,8 @@ internal static class ChatTypeExt {
} }
} }
internal static InputChannel? ToInputChannel(this ChatType type) => type switch { internal static InputChannel? ToInputChannel(this ChatType type) => type switch
{
ChatType.TellOutgoing => InputChannel.Tell, ChatType.TellOutgoing => InputChannel.Tell,
ChatType.Say => InputChannel.Say, ChatType.Say => InputChannel.Say,
ChatType.Party => InputChannel.Party, ChatType.Party => InputChannel.Party,
@@ -321,7 +335,8 @@ internal static class ChatTypeExt {
_ => null, _ => null,
}; };
internal static bool IsGm(this ChatType type) => type switch { internal static bool IsGm(this ChatType type) => type switch
{
ChatType.GmTell => true, ChatType.GmTell => true,
ChatType.GmSay => true, ChatType.GmSay => true,
ChatType.GmShout => true, ChatType.GmShout => true,
@@ -340,7 +355,8 @@ internal static class ChatTypeExt {
_ => false, _ => false,
}; };
internal static bool HasSource(this ChatType type) => type switch { internal static bool HasSource(this ChatType type) => type switch
{
// Battle // Battle
ChatType.Damage => true, ChatType.Damage => true,
ChatType.Miss => true, ChatType.Miss => true,
+3 -2
View File
@@ -1,6 +1,7 @@
namespace ChatTwo.Code; namespace ChatTwo.Code;
internal enum InputChannel : uint { internal enum InputChannel : uint
{
Tell = 0, Tell = 0,
Say = 1, Say = 1,
Party = 2, Party = 2,
@@ -29,7 +30,7 @@ internal enum InputChannel : uint {
Linkshell6 = 24, Linkshell6 = 24,
Linkshell7 = 25, Linkshell7 = 25,
Linkshell8 = 26, Linkshell8 = 26,
// Custom channels: // Custom channels:
ExtraChatLinkshell1 = 1001, ExtraChatLinkshell1 = 1001,
ExtraChatLinkshell2 = 1002, ExtraChatLinkshell2 = 1002,
+46 -45
View File
@@ -3,8 +3,10 @@ using Lumina.Excel.GeneratedSheets;
namespace ChatTwo.Code; namespace ChatTwo.Code;
internal static class InputChannelExt { internal static class InputChannelExt
internal static ChatType ToChatType(this InputChannel input) => input switch { {
internal static ChatType ToChatType(this InputChannel input) => input switch
{
InputChannel.Tell => ChatType.TellOutgoing, InputChannel.Tell => ChatType.TellOutgoing,
InputChannel.Say => ChatType.Say, InputChannel.Say => ChatType.Say,
InputChannel.Party => ChatType.Party, InputChannel.Party => ChatType.Party,
@@ -41,7 +43,8 @@ internal static class InputChannelExt {
_ => throw new ArgumentOutOfRangeException(nameof(input), input, null), _ => throw new ArgumentOutOfRangeException(nameof(input), input, null),
}; };
public static uint LinkshellIndex(this InputChannel channel) => channel switch { public static uint LinkshellIndex(this InputChannel channel) => channel switch
{
InputChannel.Linkshell1 => 0, InputChannel.Linkshell1 => 0,
InputChannel.Linkshell2 => 1, InputChannel.Linkshell2 => 1,
InputChannel.Linkshell3 => 2, InputChannel.Linkshell3 => 2,
@@ -69,7 +72,8 @@ internal static class InputChannelExt {
_ => uint.MaxValue, _ => uint.MaxValue,
}; };
public static string Prefix(this InputChannel channel) => channel switch { public static string Prefix(this InputChannel channel) => channel switch
{
InputChannel.Tell => "/tell", InputChannel.Tell => "/tell",
InputChannel.Say => "/say", InputChannel.Say => "/say",
InputChannel.Party => "/party", InputChannel.Party => "/party",
@@ -106,52 +110,47 @@ internal static class InputChannelExt {
_ => "", _ => "",
}; };
public static IEnumerable<TextCommand>? TextCommands(this InputChannel channel, IDataManager data) { public static IEnumerable<TextCommand>? TextCommands(this InputChannel channel, IDataManager data)
var ids = channel switch { {
InputChannel.Tell => new uint[] { 104, 118 }, var ids = channel switch
InputChannel.Say => new uint[] { 102 }, {
InputChannel.Party => new uint[] { 105 }, InputChannel.Tell => [104, 118],
InputChannel.Alliance => new uint[] { 119 }, InputChannel.Say => [102],
InputChannel.Yell => new uint[] { 117 }, InputChannel.Party => [105],
InputChannel.Shout => new uint[] { 103 }, InputChannel.Alliance => [119],
InputChannel.FreeCompany => new uint[] { 115 }, InputChannel.Yell => [117],
InputChannel.PvpTeam => new uint[] { 91 }, InputChannel.Shout => [103],
InputChannel.NoviceNetwork => new uint[] { 101 }, InputChannel.FreeCompany => [115],
InputChannel.CrossLinkshell1 => new uint[] { 13 }, InputChannel.PvpTeam => [91],
InputChannel.CrossLinkshell2 => new uint[] { 14 }, InputChannel.NoviceNetwork => [101],
InputChannel.CrossLinkshell3 => new uint[] { 15 }, InputChannel.CrossLinkshell1 => [13],
InputChannel.CrossLinkshell4 => new uint[] { 16 }, InputChannel.CrossLinkshell2 => [14],
InputChannel.CrossLinkshell5 => new uint[] { 17 }, InputChannel.CrossLinkshell3 => [15],
InputChannel.CrossLinkshell6 => new uint[] { 18 }, InputChannel.CrossLinkshell4 => [16],
InputChannel.CrossLinkshell7 => new uint[] { 19 }, InputChannel.CrossLinkshell5 => [17],
InputChannel.CrossLinkshell8 => new uint[] { 20 }, InputChannel.CrossLinkshell6 => [18],
InputChannel.Linkshell1 => new uint[] { 107 }, InputChannel.CrossLinkshell7 => [19],
InputChannel.Linkshell2 => new uint[] { 108 }, InputChannel.CrossLinkshell8 => [20],
InputChannel.Linkshell3 => new uint[] { 109 }, InputChannel.Linkshell1 => [107],
InputChannel.Linkshell4 => new uint[] { 110 }, InputChannel.Linkshell2 => [108],
InputChannel.Linkshell5 => new uint[] { 111 }, InputChannel.Linkshell3 => [109],
InputChannel.Linkshell6 => new uint[] { 112 }, InputChannel.Linkshell4 => [110],
InputChannel.Linkshell7 => new uint[] { 113 }, InputChannel.Linkshell5 => [111],
InputChannel.Linkshell8 => new uint[] { 114 }, InputChannel.Linkshell6 => [112],
InputChannel.Linkshell7 => [113],
InputChannel.Linkshell8 => [114],
_ => Array.Empty<uint>(), _ => Array.Empty<uint>(),
}; };
if (ids.Length == 0) { if (ids.Length == 0)
return null; return null;
}
var cmds = data.GetExcelSheet<TextCommand>(); var cmds = data.GetExcelSheet<TextCommand>();
if (cmds == null) { return cmds == null ? null : ids.Select(id => cmds.GetRow(id)).Where(id => id != null).Cast<TextCommand>();
return null;
}
return ids
.Select(id => cmds.GetRow(id))
.Where(id => id != null)
.Cast<TextCommand>();
} }
internal static bool IsLinkshell(this InputChannel channel) => channel switch { internal static bool IsLinkshell(this InputChannel channel) => channel switch
{
InputChannel.Linkshell1 => true, InputChannel.Linkshell1 => true,
InputChannel.Linkshell2 => true, InputChannel.Linkshell2 => true,
InputChannel.Linkshell3 => true, InputChannel.Linkshell3 => true,
@@ -163,7 +162,8 @@ internal static class InputChannelExt {
_ => false, _ => false,
}; };
internal static bool IsCrossLinkshell(this InputChannel channel) => channel switch { internal static bool IsCrossLinkshell(this InputChannel channel) => channel switch
{
InputChannel.CrossLinkshell1 => true, InputChannel.CrossLinkshell1 => true,
InputChannel.CrossLinkshell2 => true, InputChannel.CrossLinkshell2 => true,
InputChannel.CrossLinkshell3 => true, InputChannel.CrossLinkshell3 => true,
@@ -174,8 +174,9 @@ internal static class InputChannelExt {
InputChannel.CrossLinkshell8 => true, InputChannel.CrossLinkshell8 => true,
_ => false, _ => false,
}; };
internal static bool IsExtraChatLinkshell(this InputChannel channel) => channel switch { internal static bool IsExtraChatLinkshell(this InputChannel channel) => channel switch
{
InputChannel.ExtraChatLinkshell1 => true, InputChannel.ExtraChatLinkshell1 => true,
InputChannel.ExtraChatLinkshell2 => true, InputChannel.ExtraChatLinkshell2 => true,
InputChannel.ExtraChatLinkshell3 => true, InputChannel.ExtraChatLinkshell3 => true,
+130 -122
View File
@@ -36,9 +36,6 @@ internal sealed unsafe class Chat : IDisposable {
[Signature("E8 ?? ?? ?? ?? 48 8D 4D A0 8B F8")] [Signature("E8 ?? ?? ?? ?? 48 8D 4D A0 8B F8")]
private readonly delegate* unmanaged<IntPtr, Utf8String*, IntPtr, uint> GetKeybindNative = null!; private readonly delegate* unmanaged<IntPtr, Utf8String*, IntPtr, uint> GetKeybindNative = null!;
[Signature("E8 ?? ?? ?? ?? 48 3B F0 74 35")]
private readonly delegate* unmanaged<AtkStage*, IntPtr> GetFocus = null!;
[Signature("44 8B 89 ?? ?? ?? ?? 4C 8B C1 45 85 C9")] [Signature("44 8B 89 ?? ?? ?? ?? 4C 8B C1 45 85 C9")]
private readonly delegate* unmanaged<void*, int, IntPtr> GetTellHistory = null!; private readonly delegate* unmanaged<void*, int, IntPtr> GetTellHistory = null!;
@@ -72,6 +69,10 @@ internal sealed unsafe class Chat : IDisposable {
[Signature("E8 ?? ?? ?? ?? EB 0A 48 8D 4C 24 ?? E8 ?? ?? ?? ?? 48 8D 8D")] [Signature("E8 ?? ?? ?? ?? EB 0A 48 8D 4C 24 ?? E8 ?? ?? ?? ?? 48 8D 8D")]
private readonly delegate* unmanaged<Utf8String*, int, IntPtr, void> SanitiseString = null!; private readonly delegate* unmanaged<Utf8String*, int, IntPtr, void> SanitiseString = null!;
// Currently Unused
[Signature("E8 ?? ?? ?? ?? 48 3B F0 74 35")]
private readonly delegate* unmanaged<AtkStage*, IntPtr> GetFocus = null!;
// Hooks // Hooks
private delegate byte ChatLogRefreshDelegate(IntPtr log, ushort eventId, AtkValue* value); private delegate byte ChatLogRefreshDelegate(IntPtr log, ushort eventId, AtkValue* value);
@@ -155,7 +156,8 @@ internal sealed unsafe class Chat : IDisposable {
internal bool UsesTellTempChannel { get; set; } internal bool UsesTellTempChannel { get; set; }
internal InputChannel? PreviousChannel { get; private set; } internal InputChannel? PreviousChannel { get; private set; }
internal Chat(Plugin plugin) { internal Chat(Plugin plugin)
{
Plugin = plugin; Plugin = plugin;
Plugin.GameInteropProvider.InitializeFromAttributes(this); Plugin.GameInteropProvider.InitializeFromAttributes(this);
@@ -203,21 +205,21 @@ internal sealed unsafe class Chat : IDisposable {
} }
internal string? GetCrossLinkshellName(uint idx) { internal string? GetCrossLinkshellName(uint idx) {
if (CrossLinkshellInfoProxyIdx is not { } proxyIdx) { if (CrossLinkshellInfoProxyIdx is not { } proxyIdx)
return null; return null;
}
var infoProxy = Plugin.Functions.GetInfoProxyByIndex(proxyIdx); var infoProxy = Plugin.Functions.GetInfoProxyByIndex(proxyIdx);
if (infoProxy == IntPtr.Zero) { if (infoProxy == IntPtr.Zero)
return null; return null;
}
var utf = GetCrossLinkshellNameNative(infoProxy, idx); var utf = GetCrossLinkshellNameNative(infoProxy, idx);
return utf == null ? null : utf->ToString(); return utf == null ? null : utf->ToString();
} }
internal ulong RotateLinkshellHistory(RotateMode mode) { internal ulong RotateLinkshellHistory(RotateMode mode)
if (mode == RotateMode.None && LinkshellCycleOffset != null) { {
if (mode == RotateMode.None && LinkshellCycleOffset != null)
{
// for the branch at 6.08: 5E1680 // for the branch at 6.08: 5E1680
var uiModule = (IntPtr) Framework.Instance()->GetUiModule(); var uiModule = (IntPtr) Framework.Instance()->GetUiModule();
*(int*) (uiModule + LinkshellCycleOffset.Value) = -1; *(int*) (uiModule + LinkshellCycleOffset.Value) = -1;
@@ -230,11 +232,11 @@ internal sealed unsafe class Chat : IDisposable {
private static ulong RotateLinkshellHistoryInternal(delegate* unmanaged<UIModule*, int, ulong> func, RotateMode mode) { private static ulong RotateLinkshellHistoryInternal(delegate* unmanaged<UIModule*, int, ulong> func, RotateMode mode) {
// ReSharper disable once ConditionIsAlwaysTrueOrFalse // ReSharper disable once ConditionIsAlwaysTrueOrFalse
if (func == null) { if (func == null)
return 0; return 0;
}
var idx = mode switch { var idx = mode switch
{
RotateMode.Forward => 1, RotateMode.Forward => 1,
RotateMode.Reverse => -1, RotateMode.Reverse => -1,
_ => 0, _ => 0,
@@ -248,9 +250,8 @@ internal sealed unsafe class Chat : IDisposable {
// //
// If this function would ever return 0, it returns null instead. // If this function would ever return 0, it returns null instead.
internal uint? GetChannelColour(ChatType type) { internal uint? GetChannelColour(ChatType type) {
if (GetColourInfo == null || ColourLookup == IntPtr.Zero) { if (GetColourInfo == null || ColourLookup == IntPtr.Zero)
return null; return null;
}
// 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. // 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. // Only codes >= 10 (say) have configurable colours.
@@ -272,9 +273,8 @@ internal sealed unsafe class Chat : IDisposable {
var info = GetColourInfo(framework + 16, lookupResult); var info = GetColourInfo(framework + 16, lookupResult);
var rgb = *(uint*) (info + 32) & 0xFFFFFF; var rgb = *(uint*) (info + 32) & 0xFFFFFF;
if (rgb == 0) { if (rgb == 0)
return null; return null;
}
return 0xFF | (rgb << 8); return 0xFF | (rgb << 8);
} }
@@ -350,154 +350,161 @@ internal sealed unsafe class Chat : IDisposable {
private int _graceFrames; private int _graceFrames;
private void CheckFocus() { private void CheckFocus()
void Decrement() { {
if (_graceFrames > 0) { void Decrement()
{
if (_graceFrames > 0)
_graceFrames -= 1; _graceFrames -= 1;
} else { else
_inputFocused = false; _inputFocused = false;
}
} }
// 6.08: CB8F27 // 6.08: CB8F27
var isTextInputActivePtr = *(bool**) ((IntPtr) AtkStage.GetSingleton() + 0x28) + 0x188E; var isTextInputActivePtr = *(bool**) ((IntPtr) AtkStage.GetSingleton() + 0x28) + 0x188E;
if (isTextInputActivePtr == null) { if (isTextInputActivePtr == null)
{
Decrement(); Decrement();
return; return;
} }
if (*isTextInputActivePtr) { if (*isTextInputActivePtr)
{
_inputFocused = true; _inputFocused = true;
_graceFrames = 60; _graceFrames = 60;
} else { }
else
{
Decrement(); Decrement();
} }
} }
private void UpdateKeybinds() { private void UpdateKeybinds()
foreach (var name in KeybindsToIntercept.Keys) { {
foreach (var name in KeybindsToIntercept.Keys)
{
var keybind = GetKeybind(name); var keybind = GetKeybind(name);
if (keybind is null) { if (keybind is null)
continue; continue;
}
_keybinds[name] = keybind; _keybinds[name] = keybind;
} }
} }
private void InterceptKeybinds(IFramework framework1) { private void InterceptKeybinds(IFramework framework1)
{
CheckFocus(); CheckFocus();
UpdateKeybinds(); UpdateKeybinds();
if (_inputFocused) { if (_inputFocused)
return; return;
}
var modifierState = (ModifierFlag) 0; var modifierState = (ModifierFlag) 0;
foreach (var modifier in Enum.GetValues<ModifierFlag>()) { foreach (var modifier in Enum.GetValues<ModifierFlag>())
{
var modifierKey = GetKeyForModifier(modifier); var modifierKey = GetKeyForModifier(modifier);
if (modifierKey != VirtualKey.NO_KEY && Plugin.KeyState[modifierKey]) { if (modifierKey != VirtualKey.NO_KEY && Plugin.KeyState[modifierKey])
modifierState |= modifier; modifierState |= modifier;
}
} }
var turnedOff = new Dictionary<VirtualKey, (uint, string)>(); var turnedOff = new Dictionary<VirtualKey, (uint, string)>();
foreach (var toIntercept in KeybindsToIntercept.Keys) { foreach (var toIntercept in KeybindsToIntercept.Keys)
if (!Keybinds.TryGetValue(toIntercept, out var keybind)) { {
if (!Keybinds.TryGetValue(toIntercept, out var keybind))
continue; continue;
}
void Intercept(VirtualKey key, ModifierFlag modifier) { void Intercept(VirtualKey key, ModifierFlag modifier)
if (!Plugin.KeyState.IsVirtualKeyValid(key)) { {
if (!Plugin.KeyState.IsVirtualKeyValid(key))
return; return;
}
var modifierPressed = Plugin.Config.KeybindMode switch { var modifierPressed = Plugin.Config.KeybindMode switch
{
KeybindMode.Strict => modifier == modifierState, KeybindMode.Strict => modifier == modifierState,
KeybindMode.Flexible => modifierState.HasFlag(modifier), KeybindMode.Flexible => modifierState.HasFlag(modifier),
_ => false, _ => false,
}; };
if (!modifierPressed) {
return;
}
if (!Plugin.KeyState[key]) { if (!modifierPressed)
return;
if (!Plugin.KeyState[key])
return; return;
}
var bits = BitOperations.PopCount((uint) modifier); var bits = BitOperations.PopCount((uint) modifier);
if (!turnedOff.TryGetValue(key, out var previousBits) || previousBits.Item1 < bits) { if (!turnedOff.TryGetValue(key, out var previousBits) || previousBits.Item1 < bits)
turnedOff[key] = ((uint) bits, toIntercept); turnedOff[key] = ((uint) bits, toIntercept);
}
} }
Intercept(keybind.Key1, keybind.Modifier1); Intercept(keybind.Key1, keybind.Modifier1);
Intercept(keybind.Key2, keybind.Modifier2); Intercept(keybind.Key2, keybind.Modifier2);
} }
foreach (var (key, (_, keybind)) in turnedOff) { foreach (var (key, (_, keybind)) in turnedOff)
{
Plugin.KeyState[key] = false; Plugin.KeyState[key] = false;
if (!KeybindsToIntercept.TryGetValue(keybind, out var info))
if (!KeybindsToIntercept.TryGetValue(keybind, out var info)) {
continue; continue;
}
try { try
Activated?.Invoke(new ChatActivatedArgs(info) { {
TellReason = TellReason.Reply, Activated?.Invoke(new ChatActivatedArgs(info) { TellReason = TellReason.Reply, });
}); }
} catch (Exception ex) { catch (Exception ex)
{
Plugin.Log.Error(ex, "Error in chat Activated event"); Plugin.Log.Error(ex, "Error in chat Activated event");
} }
} }
} }
private void Login() { private void Login() {
if (ChangeChannelNameHook == null) { if (ChangeChannelNameHook == null)
return; return;
}
var agent = Framework.Instance()->GetUiModule()->GetAgentModule()->GetAgentByInternalId(AgentId.ChatLog); var agent = Framework.Instance()->GetUiModule()->GetAgentModule()->GetAgentByInternalId(AgentId.ChatLog);
if (agent == null) { if (agent == null)
return; return;
}
ChangeChannelNameDetour((IntPtr) agent); ChangeChannelNameDetour((IntPtr) agent);
} }
private byte ChatLogRefreshDetour(IntPtr log, ushort eventId, AtkValue* value) { private byte ChatLogRefreshDetour(IntPtr log, ushort eventId, AtkValue* value)
if (eventId != 0x31 || value == null || value->UInt is not (0x05 or 0x0C)) { {
if (eventId != 0x31 || value == null || value->UInt is not (0x05 or 0x0C))
return ChatLogRefreshHook!.Original(log, eventId, value); return ChatLogRefreshHook!.Original(log, eventId, value);
}
string? input = null; string? input = null;
if (Plugin.GameConfig.TryGet(UiControlOption.DirectChat, out bool option) && option) { if (Plugin.GameConfig.TryGet(UiControlOption.DirectChat, out bool option) && option)
if (CurrentCharacter != null) { {
if (CurrentCharacter != null)
{
// FIXME: this whole system sucks // FIXME: this whole system sucks
var c = *CurrentCharacter; var c = *CurrentCharacter;
if (c != '\0' && !char.IsControl(c)) { if (c != '\0' && !char.IsControl(c))
input = c.ToString(); input = c.ToString();
}
} }
} }
string? addIfNotPresent = null; string? addIfNotPresent = null;
var str = value + 2; var str = value + 2;
if (str != null && ((int) str->Type & 0xF) == (int) ValueType.String && str->String != null) { if (str != null && ((int) str->Type & 0xF) == (int) ValueType.String && str->String != null)
{
var add = MemoryHelper.ReadStringNullTerminated((IntPtr) str->String); var add = MemoryHelper.ReadStringNullTerminated((IntPtr) str->String);
if (add.Length > 0) { if (add.Length > 0)
addIfNotPresent = add; addIfNotPresent = add;
}
} }
try { try
{
var args = new ChatActivatedArgs(new ChannelSwitchInfo(null)) { var args = new ChatActivatedArgs(new ChannelSwitchInfo(null)) {
AddIfNotPresent = addIfNotPresent, AddIfNotPresent = addIfNotPresent,
Input = input, Input = input,
}; };
Activated?.Invoke(args); Activated?.Invoke(args);
} catch (Exception ex) { }
catch (Exception ex)
{
Plugin.Log.Error(ex, "Error in chat Activated event"); Plugin.Log.Error(ex, "Error in chat Activated event");
} }
@@ -505,66 +512,60 @@ internal sealed unsafe class Chat : IDisposable {
return 1; return 1;
} }
private IntPtr ChangeChannelNameDetour(IntPtr agent) { private IntPtr ChangeChannelNameDetour(IntPtr agent)
{
// Last ShB patch // Last ShB patch
// +0x40 = chat channel (byte or uint?) // +0x40 = chat channel (byte or uint?)
// channel is 17 (maybe 18?) for tells // channel is 17 (maybe 18?) for tells
// +0x48 = pointer to channel name string // +0x48 = pointer to channel name string
var ret = ChangeChannelNameHook!.Original(agent); var ret = ChangeChannelNameHook!.Original(agent);
if (agent == IntPtr.Zero) { if (agent == IntPtr.Zero)
return ret; return ret;
}
// E8 ?? ?? ?? ?? 8D 48 F7 // E8 ?? ?? ?? ?? 8D 48 F7
// RaptureShellModule + 0xFD0 // RaptureShellModule + 0xFD0
var shellModule = (IntPtr) Framework.Instance()->GetUiModule()->GetRaptureShellModule(); var shellModule = (IntPtr) Framework.Instance()->GetUiModule()->GetRaptureShellModule();
if (shellModule == IntPtr.Zero) { if (shellModule == IntPtr.Zero)
return ret; return ret;
}
var channel = 0u; var channel = 0u;
if (ShellChannelOffset != null) { if (ShellChannelOffset != null)
channel = *(uint*) (shellModule + ShellChannelOffset.Value); channel = *(uint*) (shellModule + ShellChannelOffset.Value);
}
// var channel = *(uint*) (agent + 0x40); // var channel = *(uint*) (agent + 0x40);
if (channel is 17 or 18) { if (channel is 17 or 18)
channel = 0; channel = 0;
}
SeString? name = null; SeString? name = null;
var namePtrPtr = (byte**) (agent + 0x48); var namePtrPtr = (byte**) (agent + 0x48);
if (namePtrPtr != null) { if (namePtrPtr != null)
{
var namePtr = *namePtrPtr; var namePtr = *namePtrPtr;
name = MemoryHelper.ReadSeStringNullTerminated((IntPtr) namePtr); name = MemoryHelper.ReadSeStringNullTerminated((IntPtr) namePtr);
if (name.Payloads.Count == 0) { if (name.Payloads.Count == 0)
name = null; name = null;
}
} }
if (name == null) { if (name == null)
return ret; return ret;
}
var nameChunks = ChunkUtil.ToChunks(name, ChunkSource.None, null).ToList(); var nameChunks = ChunkUtil.ToChunks(name, ChunkSource.None, null).ToList();
if (nameChunks.Count > 0 && nameChunks[0] is TextChunk text) { if (nameChunks.Count > 0 && nameChunks[0] is TextChunk text)
text.Content = text.Content.TrimStart('\uE01E').TrimStart(); text.Content = text.Content.TrimStart('\uE01E').TrimStart();
}
Channel = ((InputChannel) channel, nameChunks); Channel = ((InputChannel) channel, nameChunks);
return ret; return ret;
} }
private void ReplyInSelectedChatModeDetour(AgentInterface* agent) { private void ReplyInSelectedChatModeDetour(AgentInterface* agent)
if (ReplyChannelOffset == null) { {
if (ReplyChannelOffset == null)
goto Original; goto Original;
}
var replyMode = *(int*) ((IntPtr) agent + ReplyChannelOffset.Value); var replyMode = *(int*) ((IntPtr) agent + ReplyChannelOffset.Value);
if (replyMode == -2) { if (replyMode == -2)
goto Original; goto Original;
}
SetChannel((InputChannel) replyMode); SetChannel((InputChannel) replyMode);
@@ -572,15 +573,21 @@ internal sealed unsafe class Chat : IDisposable {
ReplyInSelectedChatModeHook!.Original(agent); ReplyInSelectedChatModeHook!.Original(agent);
} }
private byte SetChatLogTellTargetDetour(IntPtr a1, Utf8String* name, Utf8String* a3, ushort world, ulong contentId, ushort reason, byte a7) { private byte SetChatLogTellTargetDetour(IntPtr a1, Utf8String* name, Utf8String* a3, ushort world, ulong contentId, ushort reason, byte a7)
if (name != null) { {
try { if (name != null)
{
try
{
var target = new TellTarget(name->ToString(), world, contentId, (TellReason) reason); var target = new TellTarget(name->ToString(), world, contentId, (TellReason) reason);
Activated?.Invoke(new ChatActivatedArgs(new ChannelSwitchInfo(InputChannel.Tell)) { Activated?.Invoke(new ChatActivatedArgs(new ChannelSwitchInfo(InputChannel.Tell))
{
TellReason = (TellReason) reason, TellReason = (TellReason) reason,
TellTarget = target, TellTarget = target,
}); });
} catch (Exception ex) { }
catch (Exception ex)
{
Plugin.Log.Error(ex, "Error in chat Activated event"); Plugin.Log.Error(ex, "Error in chat Activated event");
} }
} }
@@ -602,15 +609,16 @@ internal sealed unsafe class Chat : IDisposable {
EurekaContextMenuTellHook!.Original(param1, playerName, worldName, world, id, param6); EurekaContextMenuTellHook!.Original(param1, playerName, worldName, world, id, param6);
} }
internal ulong? GetContentIdForEntry(uint index) { internal ulong? GetContentIdForEntry(uint index)
if (GetContentIdForChatEntry == null) { {
if (GetContentIdForChatEntry == null)
return null; return null;
}
return GetContentIdForChatEntry(Framework.Instance()->GetUiModule()->GetRaptureLogModule(), index); return GetContentIdForChatEntry(Framework.Instance()->GetUiModule()->GetRaptureLogModule(), index);
} }
internal void SetChannel(InputChannel channel, string? tellTarget = null) { internal void SetChannel(InputChannel channel, string? tellTarget = null)
{
// ExtraChat linkshells aren't supported in game so we never want to // ExtraChat linkshells aren't supported in game so we never want to
// call the ChangeChatChannel function with them. // call the ChangeChatChannel function with them.
// //
@@ -651,23 +659,23 @@ internal sealed unsafe class Chat : IDisposable {
utfWorld->Dtor(true); utfWorld->Dtor(true);
} }
private static VirtualKey GetKeyForModifier(ModifierFlag modifierFlag) => modifierFlag switch { private static VirtualKey GetKeyForModifier(ModifierFlag modifierFlag) => modifierFlag switch
{
ModifierFlag.Shift => VirtualKey.SHIFT, ModifierFlag.Shift => VirtualKey.SHIFT,
ModifierFlag.Ctrl => VirtualKey.CONTROL, ModifierFlag.Ctrl => VirtualKey.CONTROL,
ModifierFlag.Alt => VirtualKey.MENU, ModifierFlag.Alt => VirtualKey.MENU,
_ => VirtualKey.NO_KEY, _ => VirtualKey.NO_KEY,
}; };
private Keybind? GetKeybind(string id) { private Keybind? GetKeybind(string id)
{
var agent = (IntPtr) Framework.Instance()->GetUiModule()->GetAgentModule()->GetAgentByInternalId(AgentId.Configkey); var agent = (IntPtr) Framework.Instance()->GetUiModule()->GetAgentModule()->GetAgentByInternalId(AgentId.Configkey);
if (agent == IntPtr.Zero) { if (agent == IntPtr.Zero)
return null; return null;
}
var a1 = *(void**) (agent + 0x78); var a1 = *(void**) (agent + 0x78);
if (a1 == null) { if (a1 == null)
return null; return null;
}
var outData = stackalloc byte[32]; var outData = stackalloc byte[32];
var idString = Utf8String.FromString(id); var idString = Utf8String.FromString(id);
@@ -675,16 +683,15 @@ internal sealed unsafe class Chat : IDisposable {
idString->Dtor(true); idString->Dtor(true);
var key1 = (VirtualKey) outData[0]; var key1 = (VirtualKey) outData[0];
if (key1 is VirtualKey.F23) { if (key1 is VirtualKey.F23)
key1 = VirtualKey.OEM_2; key1 = VirtualKey.OEM_2;
}
var key2 = (VirtualKey) outData[2]; var key2 = (VirtualKey) outData[2];
if (key2 is VirtualKey.F23) { if (key2 is VirtualKey.F23)
key2 = VirtualKey.OEM_2; key2 = VirtualKey.OEM_2;
}
return new Keybind { return new Keybind
{
Key1 = key1, Key1 = key1,
Modifier1 = (ModifierFlag) outData[1], Modifier1 = (ModifierFlag) outData[1],
Key2 = key2, Key2 = key2,
@@ -692,16 +699,15 @@ internal sealed unsafe class Chat : IDisposable {
}; };
} }
internal TellHistoryInfo? GetTellHistoryInfo(int index) { internal TellHistoryInfo? GetTellHistoryInfo(int index)
{
var acquaintanceModule = Framework.Instance()->GetUiModule()->GetAcquaintanceModule(); var acquaintanceModule = Framework.Instance()->GetUiModule()->GetAcquaintanceModule();
if (acquaintanceModule == null) { if (acquaintanceModule == null)
return null; return null;
}
var ptr = GetTellHistory(acquaintanceModule, index); var ptr = GetTellHistory(acquaintanceModule, index);
if (ptr == IntPtr.Zero) { if (ptr == IntPtr.Zero)
return null; return null;
}
var name = MemoryHelper.ReadStringNullTerminated(*(IntPtr*) ptr); var name = MemoryHelper.ReadStringNullTerminated(*(IntPtr*) ptr);
var world = *(ushort*) (ptr + 0xD0); var world = *(ushort*) (ptr + 0xD0);
@@ -710,7 +716,8 @@ internal sealed unsafe class Chat : IDisposable {
return new TellHistoryInfo(name, world, contentId); return new TellHistoryInfo(name, world, contentId);
} }
internal void SendTell(TellReason reason, ulong contentId, string name, ushort homeWorld, byte[] message) { internal void SendTell(TellReason reason, ulong contentId, string name, ushort homeWorld, byte[] message)
{
var uName = Utf8String.FromString(name); var uName = Utf8String.FromString(name);
var uMessage = Utf8String.FromSequence(message); var uMessage = Utf8String.FromSequence(message);
@@ -725,7 +732,8 @@ internal sealed unsafe class Chat : IDisposable {
uMessage->Dtor(true); uMessage->Dtor(true);
} }
internal bool IsCharValid(char c) { internal bool IsCharValid(char c)
{
var uC = Utf8String.FromString(c.ToString()); var uC = Utf8String.FromString(c.ToString());
SanitiseString(uC, 0x27F, IntPtr.Zero); SanitiseString(uC, 0x27F, IntPtr.Zero);