~ruther/NosTale-PacketLogger

3d61d60c86f00f5dbf409bc1b6e6a26ad13d37f1 — Rutherther 2 years ago cbdc868
feat: rewrite NostaleProcesses to work with ManagementEventWatcher events instead of polling
2 files changed, 124 insertions(+), 72 deletions(-)

M src/PacketLogger/Models/NostaleProcesses.cs
M src/PacketLogger/Views/DocumentView.axaml
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}" />

Do not follow this link