From 1ac87d8e49a6ebb1fa3b619a0538dc828ab7fd73 Mon Sep 17 00:00:00 2001 From: Rutherther Date: Sat, 11 Feb 2023 20:59:49 +0100 Subject: [PATCH] feat: add pcap support --- .../Models/Packets/ClientPacketProvider.cs | 135 ++++++++++++++++++ .../Models/Packets/CommsPacketProvider.cs | 81 +---------- .../Models/Packets/PacketResponder.cs | 4 +- .../Models/Packets/PcapPacketProvider.cs | 35 +++++ src/PacketLogger/PacketLogger.csproj | 6 +- src/PacketLogger/ViewModels/DockFactory.cs | 7 + .../ViewModels/DocumentViewModel.cs | 54 ++++++- .../ViewModels/MainWindowViewModel.cs | 5 +- src/PacketLogger/Views/DocumentView.axaml | 13 +- 9 files changed, 254 insertions(+), 86 deletions(-) create mode 100644 src/PacketLogger/Models/Packets/ClientPacketProvider.cs create mode 100644 src/PacketLogger/Models/Packets/PcapPacketProvider.cs diff --git a/src/PacketLogger/Models/Packets/ClientPacketProvider.cs b/src/PacketLogger/Models/Packets/ClientPacketProvider.cs new file mode 100644 index 0000000..c1bd4a0 --- /dev/null +++ b/src/PacketLogger/Models/Packets/ClientPacketProvider.cs @@ -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; + +/// +/// A packet provider using . +/// +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? _runTask; + + /// + /// Initializes a new instance of the class. + /// + /// The process. + /// The nostale client. + public ClientPacketProvider(NostaleProcess process, INostaleClient client) + { + _ctSource = new CancellationTokenSource(); + _process = process; + _client = client; + Packets = new SourceList(); + _cleanUp = process.WhenPropertyChanged(x => x.CharacterString) + .Subscribe + ( + _ => this.RaisePropertyChanged(nameof(Name)) + ); + } + + /// + public event PropertyChangedEventHandler? PropertyChanged; + + /// + public string Name => (_process.BrowserManager.IsInGame + ? _process.BrowserManager.PlayerManager.Player.Name + : null) ?? $"Not in game ({_process.Process.Id})"; + + /// + public abstract bool IsOpen { get; } + + /// + public SourceList Packets { get; } + + /// + public bool LogReceived { get; set; } = true; + + /// + public bool LogSent { get; set; } = true; + + /// + public Task Open() + { + _runTask = Task.Run(() => _client.RunAsync(_ctSource.Token)); + return Task.FromResult(Result.FromSuccess()); + } + + /// + public virtual Task Close() + { + _ctSource.Cancel(); + if (_runTask is not null) + { + return _runTask; + } + + return Task.FromResult(Result.FromSuccess()); + } + + /// + public void Clear() + { + Packets.Clear(); + } + + /// + public Task SendPacket(string packetString, CancellationToken ct = default) + => _client.SendPacketAsync(packetString, ct); + + /// + public Task ReceivePacket(string packetString, CancellationToken ct = default) + => _client.ReceivePacketAsync(packetString, ct); + + /// + /// Add the given packets from an event. + /// + /// The packet event args. + /// The type of the deserialized packet. + 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)); + } + } + + /// + public void Dispose() + { + } + + /// + /// A dispose used instead of + /// to prevent the service provider disposing. + /// + public void CustomDispose() + { + _ctSource.Dispose(); + _cleanUp.Dispose(); + Packets.Dispose(); + } +} \ No newline at end of file diff --git a/src/PacketLogger/Models/Packets/CommsPacketProvider.cs b/src/PacketLogger/Models/Packets/CommsPacketProvider.cs index 4b4686b..75380ea 100644 --- a/src/PacketLogger/Models/Packets/CommsPacketProvider.cs +++ b/src/PacketLogger/Models/Packets/CommsPacketProvider.cs @@ -22,10 +22,8 @@ namespace PacketLogger.Models.Packets; /// /// A packet provider using a connection to a nostale client. /// -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 /// The process. /// The comms. public CommsPacketProvider(NostaleProcess process, Comms comms) + : base(process, comms.Client) { - _process = process; _comms = comms; - Packets = new SourceList(); - _cleanUp = process.WhenPropertyChanged(x => x.CharacterString) - .Subscribe - ( - _ => this.RaisePropertyChanged(nameof(Name)) - ); } /// - public event PropertyChangedEventHandler? PropertyChanged; + public override bool IsOpen => _comms.Connection.Connection.State == ConnectionState.Open; /// - public string Name => (_process.BrowserManager.IsInGame - ? _process.BrowserManager.PlayerManager.Player.Name - : null) ?? $"Not in game ({_process.Process.Id})"; - - /// - public bool IsOpen => _comms.Connection.Connection.State == ConnectionState.Open; - - /// - public SourceList Packets { get; } - - /// - public bool LogReceived { get; set; } = true; - - /// - public bool LogSent { get; set; } = true; - - /// - public Task Open() - => Task.FromResult(Result.FromSuccess()); - - /// - public Task Close() + public override Task Close() { _comms.Connection.Connection.Disconnect(); return Task.FromResult(Result.FromSuccess()); } - - /// - public void Clear() - { - Packets.Clear(); - } - - /// - public Task SendPacket(string packetString, CancellationToken ct = default) - => _comms.Client.SendPacketAsync(packetString, ct); - - /// - public Task ReceivePacket(string packetString, CancellationToken ct = default) - => _comms.Client.ReceivePacketAsync(packetString, ct); - - /// - /// Add the given packets from an event. - /// - /// The packet event args. - /// The type of the deserialized packet. - 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)); - } - } - - /// - public void Dispose() - { - } - - /// - /// A dispose used instead of - /// to prevent the service provider disposing. - /// - public void CustomDispose() - { - _cleanUp.Dispose(); - Packets.Dispose(); - } } \ No newline at end of file diff --git a/src/PacketLogger/Models/Packets/PacketResponder.cs b/src/PacketLogger/Models/Packets/PacketResponder.cs index 83c2c81..1a0e585 100644 --- a/src/PacketLogger/Models/Packets/PacketResponder.cs +++ b/src/PacketLogger/Models/Packets/PacketResponder.cs @@ -19,13 +19,13 @@ namespace PacketLogger.Models.Packets; /// public class PacketResponder : IRawPacketResponder { - private readonly CommsPacketProvider _provider; + private readonly ClientPacketProvider _provider; /// /// Initializes a new instance of the class. /// /// The provider. - public PacketResponder(CommsPacketProvider provider) + public PacketResponder(ClientPacketProvider provider) { _provider = provider; } diff --git a/src/PacketLogger/Models/Packets/PcapPacketProvider.cs b/src/PacketLogger/Models/Packets/PcapPacketProvider.cs new file mode 100644 index 0000000..5aca0ab --- /dev/null +++ b/src/PacketLogger/Models/Packets/PcapPacketProvider.cs @@ -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; + +/// +/// A packet provider using pcap. +/// +public class PcapPacketProvider : ClientPacketProvider +{ + /// + /// Initializes a new instance of the class. + /// + /// The nostale process. + /// The pcap client. + public PcapPacketProvider(NostaleProcess process, INostaleClient client) + : base(process, client) + { + } + + /// + public override bool IsOpen => true; + + /// + public override Task Close() + => Task.FromResult(Result.FromSuccess()); +} \ No newline at end of file diff --git a/src/PacketLogger/PacketLogger.csproj b/src/PacketLogger/PacketLogger.csproj index 00488e1..8a434a5 100644 --- a/src/PacketLogger/PacketLogger.csproj +++ b/src/PacketLogger/PacketLogger.csproj @@ -1,6 +1,6 @@  - WinExe + Exe net7.0 enable true @@ -32,8 +32,10 @@ None - + + + diff --git a/src/PacketLogger/ViewModels/DockFactory.cs b/src/PacketLogger/ViewModels/DockFactory.cs index 0bfdc15..f54ae33 100644 --- a/src/PacketLogger/ViewModels/DockFactory.cs +++ b/src/PacketLogger/ViewModels/DockFactory.cs @@ -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 _providers; private readonly NostaleProcesses _processes; @@ -41,6 +42,7 @@ public class DockFactory : Factory, IDisposable /// /// Initializes a new instance of the class. /// + /// The services. /// The filter profiles. /// The providers. /// The nostale processes. @@ -48,6 +50,7 @@ public class DockFactory : Factory, IDisposable /// The repository. public DockFactory ( + IServiceProvider services, FilterProfiles filterProfiles, ObservableCollection 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, diff --git a/src/PacketLogger/ViewModels/DocumentViewModel.cs b/src/PacketLogger/ViewModels/DocumentViewModel.cs index a258be3..e7ffd19 100644 --- a/src/PacketLogger/ViewModels/DocumentViewModel.cs +++ b/src/PacketLogger/ViewModels/DocumentViewModel.cs @@ -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 /// /// Initializes a new instance of the class. /// + /// The services. /// The filter profiles. /// The injector. /// The repository. @@ -57,6 +61,7 @@ public class DocumentViewModel : Document, INotifyPropertyChanged, IDisposable /// The action to call on document unloaded/closed. 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(connection.Client, provider); + repository.SetEntity(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 + ( + async process => + { + Loading = true; + var encryptionKey = process.BrowserManager.IsInGame ? process.BrowserManager.NtClient.EncryptionKey : 0; + var client = ActivatorUtilities.CreateInstance + (services, process.Process, encryptionKey, Encoding.Default); + + var provider = new PcapPacketProvider(process, client); + _packetProvider = provider; + repository.SetEntity(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 ( 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; - }); + } + ); } /// @@ -277,6 +320,11 @@ public class DocumentViewModel : Document, INotifyPropertyChanged, IDisposable /// public ReactiveCommand OpenProcess { get; } + /// + /// Gets the command for opening a process / connecting to a process. + /// + public ReactiveCommand OpenPcap { get; } + /// /// Get open settings command. /// diff --git a/src/PacketLogger/ViewModels/MainWindowViewModel.cs b/src/PacketLogger/ViewModels/MainWindowViewModel.cs index 363fe54..7362969 100644 --- a/src/PacketLogger/ViewModels/MainWindowViewModel.cs +++ b/src/PacketLogger/ViewModels/MainWindowViewModel.cs @@ -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() .AddSingleton() .AddSingleton>(_ => Providers) + .AddSingleton() + .AddSingleton() .AddNostaleCore() .AddStatefulInjector() - .AddStatefulEntity() + .AddStatefulEntity() .AddLocalComms() .AddPacketResponder(typeof(PacketResponder)) .BuildServiceProvider(); diff --git a/src/PacketLogger/Views/DocumentView.axaml b/src/PacketLogger/Views/DocumentView.axaml index 8f8e2fe..5c0b7e2 100644 --- a/src/PacketLogger/Views/DocumentView.axaml +++ b/src/PacketLogger/Views/DocumentView.axaml @@ -88,7 +88,18 @@