A src/PacketLogger/Models/Packets/ClientPacketProvider.cs => src/PacketLogger/Models/Packets/ClientPacketProvider.cs +135 -0
@@ 0,0 1,135 @@
+//
+// ClientPacketProvider.cs
+//
+// Copyright (c) František Boháček. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System;
+using System.ComponentModel;
+using System.Threading;
+using System.Threading.Tasks;
+using DynamicData;
+using DynamicData.Binding;
+using NosSmooth.Comms.Local;
+using NosSmooth.Core.Client;
+using NosSmooth.Core.Packets;
+using NosSmooth.PacketSerializer.Abstractions.Attributes;
+using ReactiveUI;
+using Remora.Results;
+
+namespace PacketLogger.Models.Packets;
+
+/// <summary>
+/// A packet provider using <see cref="INostaleClient"/>.
+/// </summary>
+public abstract class ClientPacketProvider : ReactiveObject, IPacketProvider
+{
+ private readonly IDisposable _cleanUp;
+ private readonly NostaleProcess _process;
+ private readonly INostaleClient _client;
+ private readonly CancellationTokenSource _ctSource;
+ private long _currentIndex;
+ private Task<Result>? _runTask;
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="ClientPacketProvider"/> class.
+ /// </summary>
+ /// <param name="process">The process.</param>
+ /// <param name="client">The nostale client.</param>
+ public ClientPacketProvider(NostaleProcess process, INostaleClient client)
+ {
+ _ctSource = new CancellationTokenSource();
+ _process = process;
+ _client = client;
+ Packets = new SourceList<PacketInfo>();
+ _cleanUp = process.WhenPropertyChanged(x => x.CharacterString)
+ .Subscribe
+ (
+ _ => this.RaisePropertyChanged(nameof(Name))
+ );
+ }
+
+ /// <inheritdoc />
+ public event PropertyChangedEventHandler? PropertyChanged;
+
+ /// <inheritdoc />
+ public string Name => (_process.BrowserManager.IsInGame
+ ? _process.BrowserManager.PlayerManager.Player.Name
+ : null) ?? $"Not in game ({_process.Process.Id})";
+
+ /// <inheritdoc />
+ public abstract bool IsOpen { get; }
+
+ /// <inheritdoc />
+ public SourceList<PacketInfo> Packets { get; }
+
+ /// <inheritdoc />
+ public bool LogReceived { get; set; } = true;
+
+ /// <inheritdoc />
+ public bool LogSent { get; set; } = true;
+
+ /// <inheritdoc />
+ public Task<Result> Open()
+ {
+ _runTask = Task.Run(() => _client.RunAsync(_ctSource.Token));
+ return Task.FromResult(Result.FromSuccess());
+ }
+
+ /// <inheritdoc />
+ public virtual Task<Result> Close()
+ {
+ _ctSource.Cancel();
+ if (_runTask is not null)
+ {
+ return _runTask;
+ }
+
+ return Task.FromResult(Result.FromSuccess());
+ }
+
+ /// <inheritdoc />
+ public void Clear()
+ {
+ Packets.Clear();
+ }
+
+ /// <inheritdoc />
+ public Task<Result> SendPacket(string packetString, CancellationToken ct = default)
+ => _client.SendPacketAsync(packetString, ct);
+
+ /// <inheritdoc />
+ public Task<Result> ReceivePacket(string packetString, CancellationToken ct = default)
+ => _client.ReceivePacketAsync(packetString, ct);
+
+ /// <summary>
+ /// Add the given packets from an event.
+ /// </summary>
+ /// <param name="packetArgs">The packet event args.</param>
+ /// <typeparam name="TPacket">The type of the deserialized packet.</typeparam>
+ internal void AddPacket(PacketEventArgs packetArgs)
+ {
+ var index = Interlocked.Increment(ref _currentIndex);
+ if ((packetArgs.Source == PacketSource.Server && LogReceived)
+ || (packetArgs.Source == PacketSource.Client && LogSent))
+ {
+ Packets.Add(new PacketInfo(index, DateTime.Now, packetArgs.Source, packetArgs.PacketString));
+ }
+ }
+
+ /// <inheritdoc />
+ public void Dispose()
+ {
+ }
+
+ /// <summary>
+ /// A dispose used instead of <see cref="Dispose"/>
+ /// to prevent the service provider disposing.
+ /// </summary>
+ public void CustomDispose()
+ {
+ _ctSource.Dispose();
+ _cleanUp.Dispose();
+ Packets.Dispose();
+ }
+}<
\ No newline at end of file
M src/PacketLogger/Models/Packets/CommsPacketProvider.cs => src/PacketLogger/Models/Packets/CommsPacketProvider.cs +4 -77
@@ 22,10 22,8 @@ namespace PacketLogger.Models.Packets;
/// <summary>
/// A packet provider using a connection to a nostale client.
/// </summary>
-public class CommsPacketProvider : ReactiveObject, IPacketProvider
+public class CommsPacketProvider : ClientPacketProvider
{
- private readonly IDisposable _cleanUp;
- private readonly NostaleProcess _process;
private readonly Comms _comms;
private long _currentIndex;
@@ 35,89 33,18 @@ public class CommsPacketProvider : ReactiveObject, IPacketProvider
/// <param name="process">The process.</param>
/// <param name="comms">The comms.</param>
public CommsPacketProvider(NostaleProcess process, Comms comms)
+ : base(process, comms.Client)
{
- _process = process;
_comms = comms;
- Packets = new SourceList<PacketInfo>();
- _cleanUp = process.WhenPropertyChanged(x => x.CharacterString)
- .Subscribe
- (
- _ => this.RaisePropertyChanged(nameof(Name))
- );
}
/// <inheritdoc />
- public event PropertyChangedEventHandler? PropertyChanged;
+ public override bool IsOpen => _comms.Connection.Connection.State == ConnectionState.Open;
/// <inheritdoc />
- public string Name => (_process.BrowserManager.IsInGame
- ? _process.BrowserManager.PlayerManager.Player.Name
- : null) ?? $"Not in game ({_process.Process.Id})";
-
- /// <inheritdoc />
- public bool IsOpen => _comms.Connection.Connection.State == ConnectionState.Open;
-
- /// <inheritdoc />
- public SourceList<PacketInfo> Packets { get; }
-
- /// <inheritdoc />
- public bool LogReceived { get; set; } = true;
-
- /// <inheritdoc />
- public bool LogSent { get; set; } = true;
-
- /// <inheritdoc />
- public Task<Result> Open()
- => Task.FromResult(Result.FromSuccess());
-
- /// <inheritdoc />
- public Task<Result> Close()
+ public override Task<Result> Close()
{
_comms.Connection.Connection.Disconnect();
return Task.FromResult(Result.FromSuccess());
}
-
- /// <inheritdoc />
- public void Clear()
- {
- Packets.Clear();
- }
-
- /// <inheritdoc />
- public Task<Result> SendPacket(string packetString, CancellationToken ct = default)
- => _comms.Client.SendPacketAsync(packetString, ct);
-
- /// <inheritdoc />
- public Task<Result> ReceivePacket(string packetString, CancellationToken ct = default)
- => _comms.Client.ReceivePacketAsync(packetString, ct);
-
- /// <summary>
- /// Add the given packets from an event.
- /// </summary>
- /// <param name="packetArgs">The packet event args.</param>
- /// <typeparam name="TPacket">The type of the deserialized packet.</typeparam>
- internal void AddPacket(PacketEventArgs packetArgs)
- {
- var index = Interlocked.Increment(ref _currentIndex);
- if ((packetArgs.Source == PacketSource.Server && LogReceived)
- || (packetArgs.Source == PacketSource.Client && LogSent))
- {
- Packets.Add(new PacketInfo(index, DateTime.Now, packetArgs.Source, packetArgs.PacketString));
- }
- }
-
- /// <inheritdoc />
- public void Dispose()
- {
- }
-
- /// <summary>
- /// A dispose used instead of <see cref="Dispose"/>
- /// to prevent the service provider disposing.
- /// </summary>
- public void CustomDispose()
- {
- _cleanUp.Dispose();
- Packets.Dispose();
- }
}=
\ No newline at end of file
M src/PacketLogger/Models/Packets/PacketResponder.cs => src/PacketLogger/Models/Packets/PacketResponder.cs +2 -2
@@ 19,13 19,13 @@ namespace PacketLogger.Models.Packets;
/// <inheritdoc />
public class PacketResponder : IRawPacketResponder
{
- private readonly CommsPacketProvider _provider;
+ private readonly ClientPacketProvider _provider;
/// <summary>
/// Initializes a new instance of the <see cref="PacketResponder"/> class.
/// </summary>
/// <param name="provider">The provider.</param>
- public PacketResponder(CommsPacketProvider provider)
+ public PacketResponder(ClientPacketProvider provider)
{
_provider = provider;
}
A src/PacketLogger/Models/Packets/PcapPacketProvider.cs => src/PacketLogger/Models/Packets/PcapPacketProvider.cs +35 -0
@@ 0,0 1,35 @@
+//
+// PcapPacketProvider.cs
+//
+// Copyright (c) František Boháček. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System.Threading.Tasks;
+using NosSmooth.Core.Client;
+using ReactiveUI;
+using Remora.Results;
+
+namespace PacketLogger.Models.Packets;
+
+/// <summary>
+/// A packet provider using pcap.
+/// </summary>
+public class PcapPacketProvider : ClientPacketProvider
+{
+ /// <summary>
+ /// Initializes a new instance of the <see cref="PcapPacketProvider"/> class.
+ /// </summary>
+ /// <param name="process">The nostale process.</param>
+ /// <param name="client">The pcap client.</param>
+ public PcapPacketProvider(NostaleProcess process, INostaleClient client)
+ : base(process, client)
+ {
+ }
+
+ /// <inheritdoc />
+ public override bool IsOpen => true;
+
+ /// <inheritdoc />
+ public override Task<Result> Close()
+ => Task.FromResult(Result.FromSuccess());
+}<
\ No newline at end of file
M src/PacketLogger/PacketLogger.csproj => src/PacketLogger/PacketLogger.csproj +4 -2
@@ 1,6 1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
- <OutputType>WinExe</OutputType>
+ <OutputType>Exe</OutputType>
<TargetFramework>net7.0</TargetFramework>
<Nullable>enable</Nullable>
<BuiltInComInteropSupport>true</BuiltInComInteropSupport>
@@ 32,8 32,10 @@
<PrivateAssets>None</PrivateAssets>
</PackageReference>
<PackageReference Include="NosSmooth.Core" Version="4.0.2" />
- <PackageReference Include="NosSmooth.LocalBinding" Version="1.0.0" />
+ <PackageReference Include="NosSmooth.Cryptography" Version="1.0.0-main4152899734" />
+ <PackageReference Include="NosSmooth.LocalBinding" Version="1.1.0-main4152737026" />
<PackageReference Include="NosSmooth.PacketSerializer.Abstractions" Version="1.3.1" />
+ <PackageReference Include="NosSmooth.Pcap" Version="1.0.0-main4152899734" />
<PackageReference Include="Projektanker.Icons.Avalonia" Version="5.8.0" />
<PackageReference Include="Projektanker.Icons.Avalonia.MaterialDesign" Version="5.8.0" />
<PackageReference Include="PropertyChanged.Fody" Version="4.1.0">
M src/PacketLogger/ViewModels/DockFactory.cs => src/PacketLogger/ViewModels/DockFactory.cs +7 -0
@@ 30,6 30,7 @@ namespace PacketLogger.ViewModels;
public class DockFactory : Factory, IDisposable
{
private readonly StatefulRepository _repository;
+ private readonly IServiceProvider _services;
private readonly FilterProfiles _filterProfiles;
private readonly ObservableCollection<IPacketProvider> _providers;
private readonly NostaleProcesses _processes;
@@ 41,6 42,7 @@ public class DockFactory : Factory, IDisposable
/// <summary>
/// Initializes a new instance of the <see cref="DockFactory"/> class.
/// </summary>
+ /// <param name="services">The services.</param>
/// <param name="filterProfiles">The filter profiles.</param>
/// <param name="providers">The providers.</param>
/// <param name="processes">The nostale processes.</param>
@@ 48,6 50,7 @@ public class DockFactory : Factory, IDisposable
/// <param name="repository">The repository.</param>
public DockFactory
(
+ IServiceProvider services,
FilterProfiles filterProfiles,
ObservableCollection<IPacketProvider> providers,
NostaleProcesses processes,
@@ 55,6 58,7 @@ public class DockFactory : Factory, IDisposable
StatefulRepository repository
)
{
+ _services = services;
_filterProfiles = filterProfiles;
_providers = providers;
_processes = processes;
@@ 101,6 105,7 @@ public class DockFactory : Factory, IDisposable
var document = new DocumentViewModel
(
+ _services,
_filterProfiles,
_injector,
_repository,
@@ 144,6 149,7 @@ public class DockFactory : Factory, IDisposable
var index = documentDock.VisibleDockables?.Count + 1;
var document = new DocumentViewModel
(
+ _services,
_filterProfiles,
_injector,
_repository,
@@ 167,6 173,7 @@ public class DockFactory : Factory, IDisposable
{
var initialTab = new DocumentViewModel
(
+ _services,
_filterProfiles,
_injector,
_repository,
M src/PacketLogger/ViewModels/DocumentViewModel.cs => src/PacketLogger/ViewModels/DocumentViewModel.cs +51 -3
@@ 13,17 13,20 @@ using System.Reactive;
using System.Reactive.Disposables;
using System.Reactive.Linq;
using System.Reflection;
+using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Avalonia.Controls;
using Avalonia.Controls.ApplicationLifetimes;
using Dock.Model.Mvvm.Controls;
using DynamicData.Binding;
+using Microsoft.Extensions.DependencyInjection;
using NosSmooth.Comms.Data.Messages;
using NosSmooth.Comms.Local;
using NosSmooth.Core.Contracts;
using NosSmooth.Core.Extensions;
using NosSmooth.Core.Stateful;
+using NosSmooth.Pcap;
using PacketLogger.Models;
using PacketLogger.Models.Filters;
using PacketLogger.Models.Packets;
@@ 48,6 51,7 @@ public class DocumentViewModel : Document, INotifyPropertyChanged, IDisposable
/// <summary>
/// Initializes a new instance of the <see cref="DocumentViewModel"/> class.
/// </summary>
+ /// <param name="services">The services.</param>
/// <param name="filterProfiles">The filter profiles.</param>
/// <param name="injector">The injector.</param>
/// <param name="repository">The repository.</param>
@@ 57,6 61,7 @@ public class DocumentViewModel : Document, INotifyPropertyChanged, IDisposable
/// <param name="onDocumentUnloaded">The action to call on document unloaded/closed.</param>
public DocumentViewModel
(
+ IServiceProvider services,
FilterProfiles filterProfiles,
CommsInjector injector,
StatefulRepository repository,
@@ 140,7 145,7 @@ public class DocumentViewModel : Document, INotifyPropertyChanged, IDisposable
var provider = new CommsPacketProvider(process, connection);
_packetProvider = provider;
- repository.SetEntity<CommsPacketProvider>(connection.Client, provider);
+ repository.SetEntity<ClientPacketProvider>(connection.Client, provider);
var contractResult = await connection.Connection.ContractHanshake
(new HandshakeRequest("PacketLogger", true, false))
@@ 184,6 189,42 @@ public class DocumentViewModel : Document, INotifyPropertyChanged, IDisposable
}
);
+ OpenPcap = ReactiveCommand.CreateFromTask<NostaleProcess>
+ (
+ async process =>
+ {
+ Loading = true;
+ var encryptionKey = process.BrowserManager.IsInGame ? process.BrowserManager.NtClient.EncryptionKey : 0;
+ var client = ActivatorUtilities.CreateInstance<PcapNostaleClient>
+ (services, process.Process, encryptionKey, Encoding.Default);
+
+ var provider = new PcapPacketProvider(process, client);
+ _packetProvider = provider;
+ repository.SetEntity<ClientPacketProvider>(client, provider);
+
+ _cleanUp = process.WhenPropertyChanged(x => x.CharacterString)
+ .ObserveOn(RxApp.MainThreadScheduler)
+ .Do
+ (
+ _ =>
+ {
+ Title = (process.BrowserManager.IsInGame
+ ? process.BrowserManager.PlayerManager.Player.Name
+ : null) ?? $"Not in game ({process.Process.Id})";
+ }
+ )
+ .Subscribe();
+
+ await provider.Open();
+
+ NestedViewModel = new PacketLogViewModel(provider, filterProfiles);
+ Title = provider.Name;
+ Loading = false;
+ Loaded = true;
+ onDocumentLoaded(this);
+ }
+ );
+
OpenSender = ReactiveCommand.Create<IPacketProvider>
(
provider =>
@@ 198,13 239,15 @@ public class DocumentViewModel : Document, INotifyPropertyChanged, IDisposable
ClearError = ReactiveCommand.Create(() => Error = null);
- OpenSettings = ReactiveCommand.Create(
+ OpenSettings = ReactiveCommand.Create
+ (
() =>
{
Title = "Settings";
NestedViewModel = new SettingsViewModel(filterProfiles);
Loaded = true;
- });
+ }
+ );
}
/// <summary>
@@ 278,6 321,11 @@ public class DocumentViewModel : Document, INotifyPropertyChanged, IDisposable
public ReactiveCommand<NostaleProcess, Unit> OpenProcess { get; }
/// <summary>
+ /// Gets the command for opening a process / connecting to a process.
+ /// </summary>
+ public ReactiveCommand<NostaleProcess, Unit> OpenPcap { get; }
+
+ /// <summary>
/// Get open settings command.
/// </summary>
public ReactiveCommand<Unit, Unit> OpenSettings { get; }
M src/PacketLogger/ViewModels/MainWindowViewModel.cs => src/PacketLogger/ViewModels/MainWindowViewModel.cs +4 -1
@@ 25,6 25,7 @@ using NosSmooth.Core.Extensions;
using NosSmooth.PacketSerializer.Abstractions.Attributes;
using NosSmooth.PacketSerializer.Extensions;
using NosSmooth.PacketSerializer.Packets;
+using NosSmooth.Pcap;
using PacketLogger.Models;
using PacketLogger.Models.Filters;
using PacketLogger.Models.Packets;
@@ 50,9 51,11 @@ public class MainWindowViewModel : ViewModelBase, INotifyPropertyChanged
.AddSingleton<DockFactory>()
.AddSingleton<NostaleProcesses>()
.AddSingleton<ObservableCollection<IPacketProvider>>(_ => Providers)
+ .AddSingleton<ProcessTcpManager>()
+ .AddSingleton<PcapNostaleManager>()
.AddNostaleCore()
.AddStatefulInjector()
- .AddStatefulEntity<CommsPacketProvider>()
+ .AddStatefulEntity<ClientPacketProvider>()
.AddLocalComms()
.AddPacketResponder(typeof(PacketResponder))
.BuildServiceProvider();
M src/PacketLogger/Views/DocumentView.axaml => src/PacketLogger/Views/DocumentView.axaml +12 -1
@@ 88,7 88,18 @@
<Button Content="Connect"
Command="{Binding $parent[UserControl].DataContext.OpenProcess}"
IsEnabled="{Binding !$parent[UserControl].DataContext.Loading}"
- CommandParameter="{Binding }" />
+ CommandParameter="{Binding}" />
+ </DataTemplate>
+ </DataGridTemplateColumn.CellTemplate>
+ </DataGridTemplateColumn>
+
+ <DataGridTemplateColumn Header="Pcap">
+ <DataGridTemplateColumn.CellTemplate>
+ <DataTemplate>
+ <Button Content="Sniff"
+ Command="{Binding $parent[UserControl].DataContext.OpenPcap}"
+ IsEnabled="{Binding !$parent[UserControl].DataContext.Loading}"
+ CommandParameter="{Binding}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>