From 3449762eb353693326d9f57500d1a47512731c21 Mon Sep 17 00:00:00 2001 From: Haselnussbomber Date: Wed, 21 Jan 2026 14:48:11 +0100 Subject: [PATCH] Rework NetworkMonitorWidget, remove GameNetwork --- Dalamud/Game/Network/GameNetwork.cs | 147 ----------- .../Network/GameNetworkAddressResolver.cs | 20 -- .../Game/Network/Internal/NetworkHandlers.cs | 5 +- .../Game/Network/NetworkMessageDirection.cs | 17 -- .../Data/Widgets/NetworkMonitorWidget.cs | 235 +++++++++--------- Dalamud/Plugin/Services/IGameNetwork.cs | 27 -- 6 files changed, 119 insertions(+), 332 deletions(-) delete mode 100644 Dalamud/Game/Network/GameNetwork.cs delete mode 100644 Dalamud/Game/Network/GameNetworkAddressResolver.cs delete mode 100644 Dalamud/Game/Network/NetworkMessageDirection.cs delete mode 100644 Dalamud/Plugin/Services/IGameNetwork.cs diff --git a/Dalamud/Game/Network/GameNetwork.cs b/Dalamud/Game/Network/GameNetwork.cs deleted file mode 100644 index b8c91b235..000000000 --- a/Dalamud/Game/Network/GameNetwork.cs +++ /dev/null @@ -1,147 +0,0 @@ -using System.Runtime.InteropServices; - -using Dalamud.Configuration.Internal; -using Dalamud.Hooking; -using Dalamud.Utility; - -using FFXIVClientStructs.FFXIV.Client.Network; - -using Serilog; - -namespace Dalamud.Game.Network; - -/// -/// This class handles interacting with game network events. -/// -[ServiceManager.EarlyLoadedService] -internal sealed unsafe class GameNetwork : IInternalDisposableService -{ - private readonly GameNetworkAddressResolver address; - private readonly Hook processZonePacketDownHook; - private readonly Hook processZonePacketUpHook; - - private readonly HitchDetector hitchDetectorUp; - private readonly HitchDetector hitchDetectorDown; - - [ServiceManager.ServiceDependency] - private readonly DalamudConfiguration configuration = Service.Get(); - - [ServiceManager.ServiceConstructor] - private unsafe GameNetwork(TargetSigScanner sigScanner) - { - this.hitchDetectorUp = new HitchDetector("GameNetworkUp", this.configuration.GameNetworkUpHitch); - this.hitchDetectorDown = new HitchDetector("GameNetworkDown", this.configuration.GameNetworkDownHitch); - - this.address = new GameNetworkAddressResolver(); - this.address.Setup(sigScanner); - - var onReceivePacketAddress = (nint)PacketDispatcher.StaticVirtualTablePointer->OnReceivePacket; - - Log.Verbose("===== G A M E N E T W O R K ====="); - Log.Verbose($"OnReceivePacket address {Util.DescribeAddress(onReceivePacketAddress)}"); - Log.Verbose($"ProcessZonePacketUp address {Util.DescribeAddress(this.address.ProcessZonePacketUp)}"); - - this.processZonePacketDownHook = Hook.FromAddress(onReceivePacketAddress, this.ProcessZonePacketDownDetour); - this.processZonePacketUpHook = Hook.FromAddress(this.address.ProcessZonePacketUp, this.ProcessZonePacketUpDetour); - - this.processZonePacketDownHook.Enable(); - this.processZonePacketUpHook.Enable(); - } - - /// - /// The delegate type of a network message event. - /// - /// The pointer to the raw data. - /// The operation ID code. - /// The source actor ID. - /// The taret actor ID. - /// The direction of the packed. - public delegate void OnNetworkMessageDelegate(nint dataPtr, ushort opCode, uint sourceActorId, uint targetActorId, NetworkMessageDirection direction); - - [UnmanagedFunctionPointer(CallingConvention.ThisCall)] - private delegate byte ProcessZonePacketUpDelegate(IntPtr a1, IntPtr dataPtr, IntPtr a3, byte a4); - - /// - /// Event that is called when a network message is sent/received. - /// - public event OnNetworkMessageDelegate? NetworkMessage; - - /// - void IInternalDisposableService.DisposeService() - { - this.processZonePacketDownHook.Dispose(); - this.processZonePacketUpHook.Dispose(); - } - - private void ProcessZonePacketDownDetour(PacketDispatcher* dispatcher, uint targetId, IntPtr dataPtr) - { - this.hitchDetectorDown.Start(); - - // Go back 0x10 to get back to the start of the packet header - dataPtr -= 0x10; - - foreach (var d in Delegate.EnumerateInvocationList(this.NetworkMessage)) - { - try - { - d.Invoke( - dataPtr + 0x20, - (ushort)Marshal.ReadInt16(dataPtr, 0x12), - 0, - targetId, - NetworkMessageDirection.ZoneDown); - } - catch (Exception ex) - { - string header; - try - { - var data = new byte[32]; - Marshal.Copy(dataPtr, data, 0, 32); - header = BitConverter.ToString(data); - } - catch (Exception) - { - header = "failed"; - } - - Log.Error(ex, "Exception on ProcessZonePacketDown hook. Header: " + header); - } - } - - this.processZonePacketDownHook.Original(dispatcher, targetId, dataPtr + 0x10); - this.hitchDetectorDown.Stop(); - } - - private byte ProcessZonePacketUpDetour(IntPtr a1, IntPtr dataPtr, IntPtr a3, byte a4) - { - this.hitchDetectorUp.Start(); - - try - { - // Call events - // TODO: Implement actor IDs - this.NetworkMessage?.Invoke(dataPtr + 0x20, (ushort)Marshal.ReadInt16(dataPtr), 0x0, 0x0, NetworkMessageDirection.ZoneUp); - } - catch (Exception ex) - { - string header; - try - { - var data = new byte[32]; - Marshal.Copy(dataPtr, data, 0, 32); - header = BitConverter.ToString(data); - } - catch (Exception) - { - header = "failed"; - } - - Log.Error(ex, "Exception on ProcessZonePacketUp hook. Header: " + header); - } - - this.hitchDetectorUp.Stop(); - - return this.processZonePacketUpHook.Original(a1, dataPtr, a3, a4); - } -} diff --git a/Dalamud/Game/Network/GameNetworkAddressResolver.cs b/Dalamud/Game/Network/GameNetworkAddressResolver.cs deleted file mode 100644 index 48abc2d97..000000000 --- a/Dalamud/Game/Network/GameNetworkAddressResolver.cs +++ /dev/null @@ -1,20 +0,0 @@ -using Dalamud.Plugin.Services; - -namespace Dalamud.Game.Network; - -/// -/// The address resolver for the class. -/// -internal sealed class GameNetworkAddressResolver : BaseAddressResolver -{ - /// - /// Gets the address of the ProcessZonePacketUp method. - /// - public IntPtr ProcessZonePacketUp { get; private set; } - - /// - protected override void Setup64Bit(ISigScanner sig) - { - this.ProcessZonePacketUp = sig.ScanText("48 89 5C 24 ?? 48 89 74 24 ?? 4C 89 64 24 ?? 55 41 56 41 57 48 8B EC 48 83 EC 70"); // unnamed in cs - } -} diff --git a/Dalamud/Game/Network/Internal/NetworkHandlers.cs b/Dalamud/Game/Network/Internal/NetworkHandlers.cs index 5ca7da54a..d3a53b4f2 100644 --- a/Dalamud/Game/Network/Internal/NetworkHandlers.cs +++ b/Dalamud/Game/Network/Internal/NetworkHandlers.cs @@ -55,10 +55,7 @@ internal unsafe class NetworkHandlers : IInternalDisposableService private bool disposing; [ServiceManager.ServiceConstructor] - private NetworkHandlers( - GameNetwork gameNetwork, - TargetSigScanner sigScanner, - HappyHttpClient happyHttpClient) + private NetworkHandlers(TargetSigScanner sigScanner, HappyHttpClient happyHttpClient) { this.uploader = new UniversalisMarketBoardUploader(happyHttpClient); diff --git a/Dalamud/Game/Network/NetworkMessageDirection.cs b/Dalamud/Game/Network/NetworkMessageDirection.cs deleted file mode 100644 index 87cce5173..000000000 --- a/Dalamud/Game/Network/NetworkMessageDirection.cs +++ /dev/null @@ -1,17 +0,0 @@ -namespace Dalamud.Game.Network; - -/// -/// This represents the direction of a network message. -/// -public enum NetworkMessageDirection -{ - /// - /// A zone down message. - /// - ZoneDown, - - /// - /// A zone up message. - /// - ZoneUp, -} diff --git a/Dalamud/Interface/Internal/Windows/Data/Widgets/NetworkMonitorWidget.cs b/Dalamud/Interface/Internal/Windows/Data/Widgets/NetworkMonitorWidget.cs index ae173578a..fcba37310 100644 --- a/Dalamud/Interface/Internal/Windows/Data/Widgets/NetworkMonitorWidget.cs +++ b/Dalamud/Interface/Internal/Windows/Data/Widgets/NetworkMonitorWidget.cs @@ -1,27 +1,32 @@ using System.Collections.Concurrent; -using System.Collections.Generic; using System.Linq; using System.Text.RegularExpressions; using Dalamud.Bindings.ImGui; -using Dalamud.Game.Network; +using Dalamud.Game; +using Dalamud.Hooking; using Dalamud.Interface.Utility; using Dalamud.Interface.Utility.Raii; -using Dalamud.Memory; -using ImGuiTable = Dalamud.Interface.Utility.ImGuiTable; +using FFXIVClientStructs.FFXIV.Application.Network; +using FFXIVClientStructs.FFXIV.Client.Game.Object; +using FFXIVClientStructs.FFXIV.Client.Game.UI; +using FFXIVClientStructs.FFXIV.Client.Network; namespace Dalamud.Interface.Internal.Windows.Data.Widgets; /// /// Widget to display the current packets. /// -internal class NetworkMonitorWidget : IDataWindowWidget +internal unsafe class NetworkMonitorWidget : IDataWindowWidget { private readonly ConcurrentQueue packets = new(); + private Hook? hookDown; + private Hook? hookUp; + private bool trackNetwork; - private int trackedPackets; + private int trackedPackets = 20; private Regex? trackedOpCodes; private string filterString = string.Empty; private Regex? untrackedOpCodes; @@ -30,15 +35,16 @@ internal class NetworkMonitorWidget : IDataWindowWidget /// Finalizes an instance of the class. ~NetworkMonitorWidget() { - if (this.trackNetwork) - { - this.trackNetwork = false; - var network = Service.GetNullable(); - if (network != null) - { - network.NetworkMessage -= this.OnNetworkMessage; - } - } + this.hookDown?.Dispose(); + this.hookUp?.Dispose(); + } + + private delegate byte ZoneClientSendPacketDelegate(ZoneClient* thisPtr, nint packet, uint a3, uint a4, byte a5); + + private enum NetworkMessageDirection + { + ZoneDown, + ZoneUp, } /// @@ -53,27 +59,31 @@ internal class NetworkMonitorWidget : IDataWindowWidget /// public void Load() { - this.trackNetwork = false; - this.trackedPackets = 20; - this.trackedOpCodes = null; - this.filterString = string.Empty; - this.packets.Clear(); + this.hookDown = Hook.FromAddress( + (nint)PacketDispatcher.StaticVirtualTablePointer->OnReceivePacket, + this.OnReceivePacketDetour); + + // TODO: switch to ZoneClient.SendPacket from CS + if (Service.Get().TryScanText("E8 ?? ?? ?? ?? 4C 8B 44 24 ?? E9", out var address)) + this.hookUp = Hook.FromAddress(address, this.SendPacketDetour); + this.Ready = true; } /// public void Draw() { - var network = Service.Get(); if (ImGui.Checkbox("Track Network Packets"u8, ref this.trackNetwork)) { if (this.trackNetwork) { - network.NetworkMessage += this.OnNetworkMessage; + this.hookDown?.Enable(); + this.hookUp?.Enable(); } else { - network.NetworkMessage -= this.OnNetworkMessage; + this.hookDown?.Disable(); + this.hookUp?.Disable(); } } @@ -88,131 +98,122 @@ internal class NetworkMonitorWidget : IDataWindowWidget this.packets.Clear(); } - this.DrawFilterInput(); - this.DrawNegativeFilterInput(); + DrawFilterInput("##Filter"u8, "Regex Filter OpCodes..."u8, ref this.filterString, ref this.trackedOpCodes); + DrawFilterInput("##NegativeFilter"u8, "Regex Filter Against OpCodes..."u8, ref this.negativeFilterString, ref this.untrackedOpCodes); - ImGuiTable.DrawTable(string.Empty, this.packets, this.DrawNetworkPacket, ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.RowBg, "Direction", "OpCode", "Hex", "Target", "Source", "Data"); - } + using var table = ImRaii.Table("NetworkMonitorTableV2"u8, 5, ImGuiTableFlags.Borders | ImGuiTableFlags.ScrollY | ImGuiTableFlags.RowBg | ImGuiTableFlags.Resizable | ImGuiTableFlags.NoSavedSettings); + if (!table) return; - private void DrawNetworkPacket(NetworkPacketData data) - { - ImGui.TableNextColumn(); - ImGui.Text(data.Direction.ToString()); + ImGui.TableSetupColumn("Time"u8, ImGuiTableColumnFlags.WidthFixed, 100); + ImGui.TableSetupColumn("Direction"u8, ImGuiTableColumnFlags.WidthFixed, 100); + ImGui.TableSetupColumn("OpCode"u8, ImGuiTableColumnFlags.WidthFixed, 100); + ImGui.TableSetupColumn("OpCode (Hex)"u8, ImGuiTableColumnFlags.WidthFixed, 100); + ImGui.TableSetupColumn("Target EntityId"u8, ImGuiTableColumnFlags.WidthStretch); + ImGui.TableSetupScrollFreeze(0, 1); + ImGui.TableHeadersRow(); - ImGui.TableNextColumn(); - ImGui.Text(data.OpCode.ToString()); - - ImGui.TableNextColumn(); - ImGui.Text($"0x{data.OpCode:X4}"); - - ImGui.TableNextColumn(); - ImGui.Text(data.TargetActorId > 0 ? $"0x{data.TargetActorId:X}" : string.Empty); - - ImGui.TableNextColumn(); - ImGui.Text(data.SourceActorId > 0 ? $"0x{data.SourceActorId:X}" : string.Empty); - - ImGui.TableNextColumn(); - if (data.Data.Count > 0) + foreach (var packet in this.packets.Reverse()) { - ImGui.Text(string.Join(" ", data.Data.Select(b => b.ToString("X2")))); - } - else - { - ImGui.Dummy(ImGui.GetContentRegionAvail() with { Y = 0 }); + ImGui.TableNextColumn(); + ImGui.Text(packet.Time.ToLongTimeString()); + + ImGui.TableNextColumn(); + ImGui.Text(packet.Direction.ToString()); + + ImGui.TableNextColumn(); + WidgetUtil.DrawCopyableText(packet.OpCode.ToString()); + + ImGui.TableNextColumn(); + WidgetUtil.DrawCopyableText($"0x{packet.OpCode:X4}"); + + ImGui.TableNextColumn(); + if (packet.TargetActorId > 0) + { + WidgetUtil.DrawCopyableText($"{packet.TargetActorId:X}"); + + if (packet.TargetActorId == PlayerState.Instance()->EntityId) + { + ImGui.SameLine(0, ImGui.GetStyle().ItemInnerSpacing.X); + ImGui.Text("(Local Player)"); + } + else + { + var obj = GameObjectManager.Instance()->Objects.GetObjectByEntityId(packet.TargetActorId); + if (obj != null) + { + ImGui.SameLine(0, ImGui.GetStyle().ItemInnerSpacing.X); + ImGui.Text($"({obj->NameString})"); + } + } + } } } - private void DrawFilterInput() + private static void DrawFilterInput(ReadOnlySpan label, ReadOnlySpan hint, ref string filterString, ref Regex? regex) { - var invalidRegEx = this.filterString.Length > 0 && this.trackedOpCodes == null; + var invalidRegEx = filterString.Length > 0 && regex == 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"u8, "Regex Filter OpCodes..."u8, ref this.filterString, 1024)) + + ImGui.SetNextItemWidth(-1); + if (!ImGui.InputTextWithHint(label, hint, ref filterString, 1024)) + return; + + if (filterString.Length == 0) { + regex = null; return; } - if (this.filterString.Length == 0) + try { - this.trackedOpCodes = null; + regex = new Regex(filterString, RegexOptions.Compiled | RegexOptions.ExplicitCapture); } - else + catch { - try - { - this.trackedOpCodes = new Regex(this.filterString, RegexOptions.Compiled | RegexOptions.ExplicitCapture); - } - catch - { - this.trackedOpCodes = null; - } + regex = null; } } - private void DrawNegativeFilterInput() + private void OnReceivePacketDetour(PacketDispatcher* thisPtr, uint targetId, nint packet) { - 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"u8, "Regex Filter Against OpCodes..."u8, ref this.negativeFilterString, 1024)) - { + var opCode = *(ushort*)(packet + 2); + this.RecordPacket(new NetworkPacketData(DateTime.Now, opCode, NetworkMessageDirection.ZoneDown, targetId)); + this.hookDown.OriginalDisposeSafe(thisPtr, targetId, packet); + } + + private byte SendPacketDetour(ZoneClient* thisPtr, nint packet, uint a3, uint a4, byte a5) + { + var opCode = *(ushort*)packet; + this.RecordPacket(new NetworkPacketData(DateTime.Now, opCode, NetworkMessageDirection.ZoneUp, 0)); + return this.hookUp.OriginalDisposeSafe(thisPtr, packet, a3, a4, a5); + } + + private bool ShouldTrackPacket(ushort opCode) + { + return (this.trackedOpCodes == null || this.trackedOpCodes.IsMatch(this.OpCodeToString(opCode))) + && (this.untrackedOpCodes == null || !this.untrackedOpCodes.IsMatch(this.OpCodeToString(opCode))); + } + + private void RecordPacket(NetworkPacketData packet) + { + if (!this.ShouldTrackPacket(packet.OpCode)) return; - } - if (this.negativeFilterString.Length == 0) + this.packets.Enqueue(packet); + + while (this.packets.Count > this.trackedPackets) { - this.untrackedOpCodes = null; - } - else - { - try - { - this.untrackedOpCodes = new Regex(this.negativeFilterString, RegexOptions.Compiled | RegexOptions.ExplicitCapture); - } - catch - { - this.untrackedOpCodes = null; - } + this.packets.TryDequeue(out _); } } - private void OnNetworkMessage(nint dataPtr, ushort opCode, uint sourceActorId, uint targetActorId, NetworkMessageDirection direction) - { - 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) - { - this.packets.TryDequeue(out _); - } - } - } - - private int GetSizeFromOpCode(ushort opCode) - => 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) => $"{opCode}\0{opCode:X}"; #pragma warning disable SA1313 - private readonly record struct NetworkPacketData(ushort OpCode, NetworkMessageDirection Direction, uint SourceActorId, uint TargetActorId) + private readonly record struct NetworkPacketData(DateTime Time, ushort OpCode, NetworkMessageDirection Direction, uint TargetActorId); #pragma warning restore SA1313 - { - public readonly IReadOnlyList Data = []; - - 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); - } } diff --git a/Dalamud/Plugin/Services/IGameNetwork.cs b/Dalamud/Plugin/Services/IGameNetwork.cs deleted file mode 100644 index 4abf20834..000000000 --- a/Dalamud/Plugin/Services/IGameNetwork.cs +++ /dev/null @@ -1,27 +0,0 @@ -using Dalamud.Game.Network; - -namespace Dalamud.Plugin.Services; - -/// -/// This class handles interacting with game network events. -/// -[Obsolete("Will be removed in a future release. Use packet handler hooks instead.", true)] -public interface IGameNetwork : IDalamudService -{ - // TODO(v9): we shouldn't be passing pointers to the actual data here - - /// - /// The delegate type of a network message event. - /// - /// The pointer to the raw data. - /// The operation ID code. - /// The source actor ID. - /// The taret actor ID. - /// The direction of the packed. - public delegate void OnNetworkMessageDelegate(nint dataPtr, ushort opCode, uint sourceActorId, uint targetActorId, NetworkMessageDirection direction); - - /// - /// Event that is called when a network message is sent/received. - /// - public event OnNetworkMessageDelegate NetworkMessage; -}