Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 7 additions & 6 deletions src/Network/Analyzer.WinForms/MainForm.cs
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,9 @@ public MainForm()
this.connectedClientsListBox.DisplayMember = nameof(ICapturedConnection.Name);
this.connectedClientsListBox.Update();

this._analyzer = new PacketAnalyzer();
// The analyzer watches the packet definition files, so that changed definitions are
// applied without restarting the tool.
this._analyzer = new PacketAnalyzer(PacketDefinitionSet.GameServer, watchFiles: true);
this.Disposed += (_, _) => this._analyzer.Dispose();

this.clientVersionComboBox.SelectedIndexChanged += this.OnSelectedClientVersionChanged;
Expand Down Expand Up @@ -236,8 +238,9 @@ private void SetPacketDataSource()

private void OnPacketAnalyzingRequested(object? sender, Packet.AnalyzingRequestedEventArgs e)
{
e.ClientVersion = this._analyzer.ClientVersion;
(e.Message, e.Definition) = this._analyzer.ExtractShortInformation(e.Packet);
var clientVersion = this.SelectedClientVersion;
e.ClientVersion = clientVersion;
(e.Message, e.Definition) = this._analyzer.ExtractShortInformation(e.Packet, clientVersion);
}

private void OnUnfilteredListChanged(object? sender, ListChangedEventArgs e)
Expand Down Expand Up @@ -267,7 +270,6 @@ private void OnSelectedClientVersionChanged(object? o, EventArgs eventArgs)
listener.ClientVersion = this.SelectedClientVersion;
}

this._analyzer.ClientVersion = this.SelectedClientVersion;
if (this._unfilteredList is { } unfilteredList)
{
foreach (var packet in unfilteredList)
Expand Down Expand Up @@ -315,7 +317,6 @@ private void StartProxy(object sender, System.EventArgs e)
ClientVersion = this.SelectedClientVersion,
};

this._analyzer.ClientVersion = this.SelectedClientVersion;
this._clientListener.ClientConnected += this.ClientListenerOnClientConnected;
this._clientListener.Start();
this.btnStartProxy.Text = "Stop Proxy";
Expand Down Expand Up @@ -364,7 +365,7 @@ private void OnPacketSelected(object sender, EventArgs e)
if (rows.Count > 0 && this.packetGridView.SelectedRows[0].DataBoundItem is Packet packet)
{
this.rawDataTextBox.Text = packet.PacketData;
this.extractedInfoTextBox.Text = this._analyzer.ExtractInformation(packet);
this.extractedInfoTextBox.Text = this._analyzer.ExtractInformation(packet, this.SelectedClientVersion);
this.packetInfoGroup.Enabled = true;
}
else
Expand Down
6 changes: 6 additions & 0 deletions src/Network/Analyzer/MUnique.OpenMU.Network.Analyzer.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,12 @@
<Content Include="..\Packets\ServerToClient\ServerToClientPackets.xml" Link="ServerToClientPackets.xml">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="..\Packets\ConnectServer\ConnectServerPackets.xml" Link="ConnectServerPackets.xml">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="..\Packets\ChatServer\ChatServerPackets.xml" Link="ChatServerPackets.xml">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\MUnique.OpenMU.Network.csproj" />
Expand Down
187 changes: 111 additions & 76 deletions src/Network/Analyzer/PacketAnalyzer.cs

Large diffs are not rendered by default.

27 changes: 27 additions & 0 deletions src/Network/Analyzer/PacketDefinitionSet.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// <copyright file="PacketDefinitionSet.cs" company="MUnique">
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
// </copyright>

namespace MUnique.OpenMU.Network.Analyzer;

/// <summary>
/// The set of packet definitions which is used to analyze the traffic of a connection.
/// Which one applies depends on the server the client is connected to.
/// </summary>
public enum PacketDefinitionSet
{
/// <summary>
/// The packets which are exchanged between game client and game server.
/// </summary>
GameServer,

/// <summary>
/// The packets which are exchanged between game client and connect server.
/// </summary>
ConnectServer,

/// <summary>
/// The packets which are exchanged between game client and chat server.
/// </summary>
ChatServer,
}
131 changes: 131 additions & 0 deletions src/Network/CapturedPacketReader.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
// <copyright file="CapturedPacketReader.cs" company="MUnique">
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
// </copyright>

namespace MUnique.OpenMU.Network;

using System.Buffers;
using System.IO.Pipelines;
using Microsoft.Extensions.Logging;

/// <summary>
/// Handler for a captured data packet.
/// </summary>
/// <param name="packet">The complete data packet.</param>
internal delegate void PacketCapturedHandler(ReadOnlySpan<byte> packet);

/// <summary>
/// Splits the captured outgoing data of a connection into data packets again.
/// </summary>
/// <remarks>
/// One write to a <see cref="PipeWriter"/> is not necessarily one data packet: a bigger message
/// may be written in several chunks, and more than one packet may be written before the writer
/// gets flushed. The written data is therefore copied into an own pipe, which is read by this
/// class like any other packet source of the network layer.
/// </remarks>
internal sealed class CapturedPacketReader : PacketPipeReaderBase
{
private readonly PacketCapturedHandler _packetCaptured;

private readonly Action _completed;

private readonly ILogger _logger;

private readonly Pipe _pipe;

private bool _isCompleted;

/// <summary>
/// Initializes a new instance of the <see cref="CapturedPacketReader"/> class.
/// </summary>
/// <param name="packetCaptured">The handler which is called for each complete data packet.</param>
/// <param name="completed">Is called when this reader stopped reading, e.g. because the
/// captured data was malformed. The caller should then stop writing into the <see cref="Writer"/>.</param>
/// <param name="logger">The logger.</param>
public CapturedPacketReader(PacketCapturedHandler packetCaptured, Action completed, ILogger logger)
{
this._packetCaptured = packetCaptured;
this._completed = completed;
this._logger = logger;

// The capturing must never slow down or block the connection itself, so the writer is
// never paused. The reader just forwards the packets to the sinks, so it keeps up.
this._pipe = new Pipe(new PipeOptions(useSynchronizationContext: false, pauseWriterThreshold: 0, resumeWriterThreshold: 0));
this.Source = this._pipe.Reader;
}

/// <summary>
/// Gets the writer, into which the captured data is written.
/// </summary>
public PipeWriter Writer => this._pipe.Writer;

/// <summary>
/// Starts reading the captured data.
/// </summary>
public void Start()
{
_ = this.ReadCapturedDataAsync();
}

/// <summary>
/// Stops the reader by completing the <see cref="Writer"/>.
/// </summary>
public void Stop()
{
try
{
this._pipe.Writer.Complete();
}
catch (Exception ex)
{
this._logger.LogDebug(ex, "Error when completing the packet capture.");
}
}

/// <inheritdoc />
protected override ValueTask<bool> ReadPacketAsync(ReadOnlySequence<byte> packet)
{
if (packet.IsSingleSegment)
{
this._packetCaptured(packet.FirstSpan);
}
else
{
this._packetCaptured(packet.ToArray());
}

return ValueTask.FromResult(true);
}

/// <inheritdoc />
protected override async ValueTask OnCompleteAsync(Exception? exception)
{
if (this._isCompleted)
{
return;
}

this._isCompleted = true;
if (exception is not null)
{
this._logger.LogWarning(exception, "Error while reading the captured data packets. The capturing is stopped.");
}

await this._pipe.Reader.CompleteAsync(exception).ConfigureAwait(false);
this._completed();
}

private async Task ReadCapturedDataAsync()
{
try
{
await this.ReadSourceAsync().ConfigureAwait(false);
}
catch (Exception ex)
{
// ReadSourceAsync already reported it to OnCompleteAsync; the capturing of this
// connection is over, but the connection itself is not affected.
this._logger.LogDebug(ex, "The packet capture reader stopped.");
}
}
}
Loading
Loading