From 98221471d51f754648b2789962cace2c2b2140c1 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Wed, 28 Jun 2023 16:02:40 +0200 Subject: [PATCH 1/2] Add a simple network monitor to xldata. --- .../Internal/Windows/Data/DataKindEnum.cs | 7 +- .../Internal/Windows/Data/DataWindow.cs | 1 + .../Windows/Data/Widgets/DataShareWidget.cs | 2 +- .../Data/Widgets/NetworkMonitorWidget.cs | 172 ++++++++++++++++++ 4 files changed, 180 insertions(+), 2 deletions(-) create mode 100644 Dalamud/Interface/Internal/Windows/Data/Widgets/NetworkMonitorWidget.cs diff --git a/Dalamud/Interface/Internal/Windows/Data/DataKindEnum.cs b/Dalamud/Interface/Internal/Windows/Data/DataKindEnum.cs index 99c6cb6e9..d7c4eb095 100644 --- a/Dalamud/Interface/Internal/Windows/Data/DataKindEnum.cs +++ b/Dalamud/Interface/Internal/Windows/Data/DataKindEnum.cs @@ -154,5 +154,10 @@ internal enum DataKind /// /// Data Share. /// - DataShare, + Data_Share, + + /// + /// Network Monitor. + /// + Network_Monitor, } diff --git a/Dalamud/Interface/Internal/Windows/Data/DataWindow.cs b/Dalamud/Interface/Internal/Windows/Data/DataWindow.cs index f392d3912..9d8dc1e93 100644 --- a/Dalamud/Interface/Internal/Windows/Data/DataWindow.cs +++ b/Dalamud/Interface/Internal/Windows/Data/DataWindow.cs @@ -48,6 +48,7 @@ internal class DataWindow : Window new DtrBarWidget(), new UIColorWidget(), new DataShareWidget(), + new NetworkMonitorWidget(), }; private readonly Dictionary dataKindNames = new(); diff --git a/Dalamud/Interface/Internal/Windows/Data/Widgets/DataShareWidget.cs b/Dalamud/Interface/Internal/Windows/Data/Widgets/DataShareWidget.cs index 6ec741fe8..ec7124042 100644 --- a/Dalamud/Interface/Internal/Windows/Data/Widgets/DataShareWidget.cs +++ b/Dalamud/Interface/Internal/Windows/Data/Widgets/DataShareWidget.cs @@ -9,7 +9,7 @@ namespace Dalamud.Interface.Internal.Windows.Data; internal class DataShareWidget : IDataWindowWidget { /// - public DataKind DataKind { get; init; } = DataKind.DataShare; + public DataKind DataKind { get; init; } = DataKind.Data_Share; /// public bool Ready { get; set; } diff --git a/Dalamud/Interface/Internal/Windows/Data/Widgets/NetworkMonitorWidget.cs b/Dalamud/Interface/Internal/Windows/Data/Widgets/NetworkMonitorWidget.cs new file mode 100644 index 000000000..488f46fed --- /dev/null +++ b/Dalamud/Interface/Internal/Windows/Data/Widgets/NetworkMonitorWidget.cs @@ -0,0 +1,172 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Text.RegularExpressions; + +using Dalamud.Data; +using Dalamud.Game.Network; +using Dalamud.Interface.Raii; +using Dalamud.Memory; +using ImGuiNET; + +namespace Dalamud.Interface.Internal.Windows.Data; + +/// +/// Widget to display the current packets. +/// +internal class NetworkMonitorWidget : IDataWindowWidget +{ + private readonly record struct NetworkPacketData(ushort OpCode, NetworkMessageDirection Direction, uint SourceActorId, uint TargetActorId) + { + public readonly IReadOnlyList Data = Array.Empty(); + + public NetworkPacketData(NetworkMonitorWidget widget, ushort opCode, NetworkMessageDirection direction, uint sourceActorId, uint targetActorId, nint dataPtr) + : this(opCode, direction, sourceActorId, targetActorId) + => this.Data = MemoryHelper.Read(dataPtr, widget.GetSizeFromOpCode(opCode), false); + } + + private readonly ConcurrentQueue packets = new(); + private readonly Dictionary opCodeDict = new(); + + private bool trackNetwork; + private int trackedPackets; + private Regex? trackedOpCodes; + private string filterString = string.Empty; + + /// Finalizes an instance of the class. + ~NetworkMonitorWidget() + { + if (this.trackNetwork) + { + this.trackNetwork = false; + var network = Service.GetNullable(); + if (network != null) + { + network.NetworkMessage -= this.OnNetworkMessage; + } + } + } + + /// + public DataKind DataKind { get; init; } = DataKind.Network_Monitor; + + /// + public bool Ready { get; set; } + + /// + public void Load() + { + this.trackNetwork = false; + this.trackedPackets = 20; + this.trackedOpCodes = null; + this.filterString = string.Empty; + this.packets.Clear(); + this.Ready = true; + var dataManager = Service.Get(); + foreach (var (name, code) in dataManager.ClientOpCodes.Concat(dataManager.ServerOpCodes)) + this.opCodeDict.TryAdd(code, (name, this.GetSizeFromName(name))); + } + + /// + public void Draw() + { + var network = Service.Get(); + if (ImGui.Checkbox("Track Network Packets", ref this.trackNetwork)) + { + if (this.trackNetwork) + { + network.NetworkMessage += this.OnNetworkMessage; + } + else + { + network.NetworkMessage -= this.OnNetworkMessage; + } + } + + ImGui.SetNextItemWidth(ImGui.GetContentRegionAvail().X / 2); + if (ImGui.DragInt("Stored Number of Packets", ref this.trackedPackets, 0.1f, 1, 512)) + { + this.trackedPackets = Math.Clamp(this.trackedPackets, 1, 512); + } + + this.DrawFilterInput(); + + ImGuiTable.DrawTable(string.Empty, this.packets, this.DrawNetworkPacket, ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.RowBg, "Direction", "OpCode", "Source", "Target", "Data"); + } + + private void DrawNetworkPacket(NetworkPacketData data) + { + ImGui.TableNextColumn(); + ImGui.TextUnformatted(data.Direction.ToString()); + + ImGui.TableNextColumn(); + ImGui.TextUnformatted(this.opCodeDict.TryGetValue(data.OpCode, out var pair) ? pair.Item1 : data.OpCode.ToString()); + + ImGui.TableNextColumn(); + ImGui.TextUnformatted($"0x{data.SourceActorId:X}"); + + ImGui.TableNextColumn(); + ImGui.TextUnformatted($"0x{data.TargetActorId:X}"); + + ImGui.TableNextColumn(); + if (data.Data.Count > 0) + { + ImGui.TextUnformatted(string.Join(" ", data.Data.Select(b => b.ToString("X2")))); + } + } + + private void DrawFilterInput() + { + var invalidRegEx = this.filterString.Length > 0 && this.trackedOpCodes == null; + using var style = ImRaii.PushStyle(ImGuiStyleVar.FrameBorderSize, 2 * ImGuiHelpers.GlobalScale, invalidRegEx); + using var color = ImRaii.PushColor(ImGuiCol.Border, 0xFF0000FF, invalidRegEx); + ImGui.SetNextItemWidth(ImGui.GetContentRegionAvail().X); + if (!ImGui.InputTextWithHint("##Filter", "Regex Filter OpCodes...", ref this.filterString, 256)) + { + return; + } + + if (this.filterString.Length == 0) + { + this.trackedOpCodes = null; + } + else + { + try + { + this.trackedOpCodes = new Regex(this.filterString, RegexOptions.Compiled | RegexOptions.ExplicitCapture); + } + catch + { + this.trackedOpCodes = null; + } + } + } + + private void OnNetworkMessage(nint dataPtr, ushort opCode, uint sourceActorId, uint targetActorId, NetworkMessageDirection direction) + { + if (this.trackedOpCodes == null || this.trackedOpCodes.IsMatch(this.OpCodeToString(opCode))) + { + this.packets.Enqueue(new NetworkPacketData(this, opCode, direction, sourceActorId, targetActorId, dataPtr)); + while (this.packets.Count > this.trackedPackets) + { + this.packets.TryDequeue(out _); + } + } + } + + private int GetSizeFromOpCode(ushort opCode) + => this.opCodeDict.TryGetValue(opCode, out var pair) ? pair.Item2 : 0; + + /// Add known packet-name -> packet struct size associations here to copy the byte data for such packets. > + private int GetSizeFromName(string name) + => name switch + { + _ => 0, + }; + + /// The filter should find opCodes by number (decimal and hex) and name, if existing. + private string OpCodeToString(ushort opCode) + => this.opCodeDict.TryGetValue(opCode, out var pair) ? $"{pair.Item1}\0{opCode}\0{opCode:X}" : $"{opCode}\0{opCode:X}"; +} From ed21ba8b08b40ada8ecb2b1e208192783f92c494 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sat, 1 Jul 2023 11:53:23 +0200 Subject: [PATCH 2/2] Improve network monitor somewhat. Add negative filtering. --- .../Data/Widgets/NetworkMonitorWidget.cs | 68 ++++++++++++++++--- 1 file changed, 59 insertions(+), 9 deletions(-) diff --git a/Dalamud/Interface/Internal/Windows/Data/Widgets/NetworkMonitorWidget.cs b/Dalamud/Interface/Internal/Windows/Data/Widgets/NetworkMonitorWidget.cs index 488f46fed..ce1559fc8 100644 --- a/Dalamud/Interface/Internal/Windows/Data/Widgets/NetworkMonitorWidget.cs +++ b/Dalamud/Interface/Internal/Windows/Data/Widgets/NetworkMonitorWidget.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; +using System.Numerics; using System.Text.RegularExpressions; using Dalamud.Data; @@ -27,12 +28,14 @@ internal class NetworkMonitorWidget : IDataWindowWidget } private readonly ConcurrentQueue packets = new(); - private readonly Dictionary opCodeDict = new(); + private readonly Dictionary opCodeDict = new(); private bool trackNetwork; private int trackedPackets; private Regex? trackedOpCodes; private string filterString = string.Empty; + private Regex? untrackedOpCodes; + private string negativeFilterString = string.Empty; /// Finalizes an instance of the class. ~NetworkMonitorWidget() @@ -91,8 +94,9 @@ internal class NetworkMonitorWidget : IDataWindowWidget } this.DrawFilterInput(); + this.DrawNegativeFilterInput(); - ImGuiTable.DrawTable(string.Empty, this.packets, this.DrawNetworkPacket, ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.RowBg, "Direction", "OpCode", "Source", "Target", "Data"); + ImGuiTable.DrawTable(string.Empty, this.packets, this.DrawNetworkPacket, ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.RowBg, "Direction", "Known Name", "OpCode", "Hex", "Target", "Source", "Data"); } private void DrawNetworkPacket(NetworkPacketData data) @@ -101,19 +105,36 @@ internal class NetworkMonitorWidget : IDataWindowWidget ImGui.TextUnformatted(data.Direction.ToString()); ImGui.TableNextColumn(); - ImGui.TextUnformatted(this.opCodeDict.TryGetValue(data.OpCode, out var pair) ? pair.Item1 : data.OpCode.ToString()); + if (this.opCodeDict.TryGetValue(data.OpCode, out var pair)) + { + ImGui.TextUnformatted(pair.Name); + } + else + { + ImGui.Dummy(new Vector2(150 * ImGuiHelpers.GlobalScale, 0)); + } ImGui.TableNextColumn(); - ImGui.TextUnformatted($"0x{data.SourceActorId:X}"); + ImGui.TextUnformatted(data.OpCode.ToString()); ImGui.TableNextColumn(); - ImGui.TextUnformatted($"0x{data.TargetActorId:X}"); + ImGui.TextUnformatted($"0x{data.OpCode:X4}"); + + ImGui.TableNextColumn(); + ImGui.TextUnformatted(data.TargetActorId > 0 ? $"0x{data.TargetActorId:X}" : string.Empty); + + ImGui.TableNextColumn(); + ImGui.TextUnformatted(data.SourceActorId > 0 ? $"0x{data.SourceActorId:X}" : string.Empty); ImGui.TableNextColumn(); if (data.Data.Count > 0) { ImGui.TextUnformatted(string.Join(" ", data.Data.Select(b => b.ToString("X2")))); } + else + { + ImGui.Dummy(ImGui.GetContentRegionAvail() with { Y = 0 }); + } } private void DrawFilterInput() @@ -122,7 +143,7 @@ internal class NetworkMonitorWidget : IDataWindowWidget using var style = ImRaii.PushStyle(ImGuiStyleVar.FrameBorderSize, 2 * ImGuiHelpers.GlobalScale, invalidRegEx); using var color = ImRaii.PushColor(ImGuiCol.Border, 0xFF0000FF, invalidRegEx); ImGui.SetNextItemWidth(ImGui.GetContentRegionAvail().X); - if (!ImGui.InputTextWithHint("##Filter", "Regex Filter OpCodes...", ref this.filterString, 256)) + if (!ImGui.InputTextWithHint("##Filter", "Regex Filter OpCodes...", ref this.filterString, 1024)) { return; } @@ -144,9 +165,38 @@ internal class NetworkMonitorWidget : IDataWindowWidget } } + private void DrawNegativeFilterInput() + { + var invalidRegEx = this.negativeFilterString.Length > 0 && this.untrackedOpCodes == null; + using var style = ImRaii.PushStyle(ImGuiStyleVar.FrameBorderSize, 2 * ImGuiHelpers.GlobalScale, invalidRegEx); + using var color = ImRaii.PushColor(ImGuiCol.Border, 0xFF0000FF, invalidRegEx); + ImGui.SetNextItemWidth(ImGui.GetContentRegionAvail().X); + if (!ImGui.InputTextWithHint("##NegativeFilter", "Regex Filter Against OpCodes...", ref this.negativeFilterString, 1024)) + { + return; + } + + if (this.negativeFilterString.Length == 0) + { + this.untrackedOpCodes = null; + } + else + { + try + { + this.untrackedOpCodes = new Regex(this.negativeFilterString, RegexOptions.Compiled | RegexOptions.ExplicitCapture); + } + catch + { + this.untrackedOpCodes = null; + } + } + } + private void OnNetworkMessage(nint dataPtr, ushort opCode, uint sourceActorId, uint targetActorId, NetworkMessageDirection direction) { - if (this.trackedOpCodes == null || this.trackedOpCodes.IsMatch(this.OpCodeToString(opCode))) + if ((this.trackedOpCodes == null || this.trackedOpCodes.IsMatch(this.OpCodeToString(opCode))) + && (this.untrackedOpCodes == null || !this.untrackedOpCodes.IsMatch(this.OpCodeToString(opCode)))) { this.packets.Enqueue(new NetworkPacketData(this, opCode, direction, sourceActorId, targetActorId, dataPtr)); while (this.packets.Count > this.trackedPackets) @@ -157,7 +207,7 @@ internal class NetworkMonitorWidget : IDataWindowWidget } private int GetSizeFromOpCode(ushort opCode) - => this.opCodeDict.TryGetValue(opCode, out var pair) ? pair.Item2 : 0; + => this.opCodeDict.TryGetValue(opCode, out var pair) ? pair.Size : 0; /// Add known packet-name -> packet struct size associations here to copy the byte data for such packets. > private int GetSizeFromName(string name) @@ -168,5 +218,5 @@ internal class NetworkMonitorWidget : IDataWindowWidget /// The filter should find opCodes by number (decimal and hex) and name, if existing. private string OpCodeToString(ushort opCode) - => this.opCodeDict.TryGetValue(opCode, out var pair) ? $"{pair.Item1}\0{opCode}\0{opCode:X}" : $"{opCode}\0{opCode:X}"; + => this.opCodeDict.TryGetValue(opCode, out var pair) ? $"{opCode}\0{opCode:X}\0{pair.Name}" : $"{opCode}\0{opCode:X}"; }