M src/PacketLogger/Models/NostaleProcesses.cs => src/PacketLogger/Models/NostaleProcesses.cs +119 -70
@@ 9,14 9,18 @@ using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Linq;
+using System.Management;
using System.Reactive.Concurrency;
using System.Reactive.Linq;
+using System.Runtime.InteropServices;
+using System.Security.Principal;
using System.Threading.Tasks;
using NosSmooth.Comms.Local;
using NosSmooth.Core.Extensions;
using NosSmooth.LocalBinding;
using NosSmooth.LocalBinding.Errors;
using NosSmooth.LocalBinding.Options;
+using NosSmooth.LocalBinding.Structs;
using ReactiveUI;
namespace PacketLogger.Models;
@@ 26,109 30,154 @@ namespace PacketLogger.Models;
/// </summary>
public class NostaleProcesses : IDisposable
{
- private readonly IDisposable _cleanUp;
- private readonly List<long> _errorfulProcesses;
+ private readonly IDisposable? _cleanUp;
+ private readonly ManagementEventWatcher? _processStartWatcher;
+ private readonly ManagementEventWatcher? _processStopWatcher;
/// <summary>
/// Initializes a new instance of the <see cref="NostaleProcesses"/> class.
/// </summary>
public NostaleProcesses()
{
- _errorfulProcesses = new List<long>();
Processes = new ObservableCollection<NostaleProcess>();
- _cleanUp = Observable.Timer(TimeSpan.Zero, TimeSpan.FromSeconds(1))
- .ObserveOn(RxApp.TaskpoolScheduler)
- .Subscribe
- (
- _ =>
+
+ if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
+ {
+ var principal = new WindowsPrincipal(WindowsIdentity.GetCurrent());
+ if (principal.IsInRole(WindowsBuiltInRole.Administrator))
+ {
+ _cleanUp = Observable.Timer(DateTimeOffset.Now, TimeSpan.FromSeconds(1))
+ .Subscribe(_ => UpdateNames());
+
+ Supported = true;
+ _processStartWatcher = new ManagementEventWatcher
+ (new WqlEventQuery("SELECT * FROM Win32_ProcessStartTrace"));
+ _processStartWatcher.EventArrived += HandleProcessOpenedEvent;
+ _processStartWatcher.Start();
+
+ _processStopWatcher = new ManagementEventWatcher
+ (new WqlEventQuery("SELECT * FROM Win32_ProcessStopTrace"));
+ _processStopWatcher.EventArrived += HandleProcessClosedEvent;
+ _processStopWatcher.Start();
+
+ // initial nostale processes
+ // rest is handled by events
+ foreach (var process in CommsInjector.FindNosTaleProcesses())
{
- try
- {
- Refresh().GetAwaiter().GetResult();
- }
- catch (Exception e)
- {
- Console.WriteLine(e);
- }
+ HandleProcessOpened(process.Id);
}
- );
+ }
+ }
}
- /// <summary>
- /// Gets NosTale processes.
- /// </summary>
- public ObservableCollection<NostaleProcess> Processes { get; }
+ private void HandleProcessOpenedEvent(object sender, EventArrivedEventArgs e)
+ {
+ if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
+ {
+ HandleProcessOpened(Convert.ToInt32(e.NewEvent.Properties["ProcessId"].Value));
+ }
+ }
- /// <inheritdoc />
- public void Dispose()
+ private void HandleProcessClosedEvent(object sender, EventArrivedEventArgs e)
{
- _cleanUp.Dispose();
+ if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
+ {
+ HandleProcessClosed(Convert.ToInt32(e.NewEvent.Properties["ProcessId"].Value));
+ }
}
- private async Task Refresh()
+ private void HandleProcessOpened(int processId)
{
- var nosTaleProcesses = CommsInjector.FindNosTaleProcesses().Select(x => x.Id).ToArray();
- var toRemove = new List<NostaleProcess>();
+ Process process;
+ try
+ {
+ process = Process.GetProcessById(processId);
+ }
+ catch (Exception)
+ {
+ return;
+ }
- foreach (var currentProcess in Processes)
+ if (!NosBrowserManager.IsProcessNostaleProcess(process))
{
- if (nosTaleProcesses.All(x => x != currentProcess.Process.Id))
- {
- toRemove.Add(currentProcess);
- }
- else
- {
- RxApp.MainThreadScheduler.Schedule(() => currentProcess.ObserveChanges());
- }
+ return;
}
- foreach (var remove in toRemove)
+ NosBrowserManager nosBrowserManager = new NosBrowserManager
+ (
+ process,
+ new PlayerManagerOptions(),
+ new SceneManagerOptions(),
+ new PetManagerOptions(),
+ new NetworkManagerOptions(),
+ new UnitManagerOptions()
+ );
+ var result = nosBrowserManager.Initialize();
+ if (!result.IsSuccess)
{
- RxApp.MainThreadScheduler.Schedule(() => Processes.Remove(remove));
+ Console.WriteLine
+ ($"Got an error when trying to initialize nos browser manager for {process.ProcessName}");
+ Console.WriteLine(result.ToFullString());
}
- foreach (var openProcess in nosTaleProcesses)
+ if (nosBrowserManager.IsModuleLoaded<PlayerManager>())
{
- if (Processes.All(x => x.Process.Id != openProcess) && !_errorfulProcesses.Contains(openProcess))
+ RxApp.MainThreadScheduler.Schedule
+ (() => Processes.Add(new NostaleProcess(process, nosBrowserManager)));
+ }
+ else
+ {
+ Console.WriteLine
+ (
+ $"Cannot add {process.ProcessName} to nostale processes as player manager was not found in memory."
+ );
+ }
+ }
+
+ private void HandleProcessClosed(int processId)
+ {
+ if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
+ {
+ var process = Processes.FirstOrDefault(x => x.Process.Id == processId);
+
+ if (process is not null)
{
- var process = Process.GetProcessById(openProcess);
- NosBrowserManager nosBrowserManager = new NosBrowserManager
- (
- process,
- new PlayerManagerOptions(),
- new SceneManagerOptions(),
- new PetManagerOptions(),
- new NetworkManagerOptions(),
- new UnitManagerOptions()
- );
- var result = nosBrowserManager.Initialize();
- if (result.IsSuccess || (result.Error is CouldNotInitializeModuleError moduleError
- && moduleError.Module.Name.Contains("NtClient")))
- {
- RxApp.MainThreadScheduler.Schedule
- (() => Processes.Add(new NostaleProcess(process, nosBrowserManager)));
- }
- else
- {
- _errorfulProcesses.Add(openProcess);
- Console.WriteLine(result.ToFullString());
- }
+ RxApp.MainThreadScheduler.Schedule
+ (() => Processes.Remove(process));
}
}
+ }
- var errorfulToRemove = new List<long>();
+ /// <summary>
+ /// Gets whether tracking and attaching to processes is supported for this run.
+ /// </summary>
+ /// <remarks>
+ /// Supported only on Windows run as elevated.
+ /// </remarks>
+ public bool Supported { get; }
+
+ /// <summary>
+ /// Gets NosTale processes.
+ /// </summary>
+ public ObservableCollection<NostaleProcess> Processes { get; }
- foreach (var errorfulProcess in _errorfulProcesses)
+ /// <inheritdoc />
+ public void Dispose()
+ {
+ if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
- if (nosTaleProcesses.All(x => x != errorfulProcess))
- {
- errorfulToRemove.Add(errorfulProcess);
- }
+ _processStartWatcher?.Stop();
+ _processStartWatcher?.Dispose();
+ _processStopWatcher?.Stop();
+ _processStopWatcher?.Dispose();
}
+ }
- foreach (var errorfulRemove in errorfulToRemove)
+ private void UpdateNames()
+ {
+ foreach (var process in Processes)
{
- _errorfulProcesses.Remove(errorfulRemove);
+ process.ObserveChanges();
}
}
}=
\ No newline at end of file
M src/PacketLogger/Views/DocumentView.axaml => src/PacketLogger/Views/DocumentView.axaml +5 -2
@@ 77,8 77,11 @@
<StackPanel Grid.Row="3" Grid.Column="0" Orientation="Vertical">
<TextBlock FontSize="30" Margin="0,0,0,5" Text="Connect to NosTale process" />
- <DataGrid CanUserReorderColumns="True" CanUserResizeColumns="True"
- Margin="0,0,30,0" Items="{Binding Processes}">
+ <TextBlock IsVisible="{Binding !Processes.Supported}"
+ TextWrapping="Wrap"
+ Text="Connecting to NosTale processes is supported only on Windows with elevated process (right click, 'Run as Administrator')." />
+ <DataGrid IsVisible="{Binding Processes.Supported}" CanUserReorderColumns="True" CanUserResizeColumns="True"
+ Margin="0,0,30,0" Items="{Binding Processes.Processes}">
<DataGrid.Columns>
<DataGridTextColumn Header="Character" Binding="{Binding CharacterString}" />
<DataGridTextColumn Header="Process" Binding="{Binding ProcessString}" />