~ruther/NosSmooth

09416b18f49b413064947f69441329bd467ec080 — Rutherther 3 years ago a14e7e9
refactor!(localbinding): add initialization method to NosTale browser instead of get methods
A Local/NosSmooth.LocalBinding/Errors/CouldNotInitializeModuleError.cs => Local/NosSmooth.LocalBinding/Errors/CouldNotInitializeModuleError.cs +17 -0
@@ 0,0 1,17 @@
//
//  CouldNotInitializeModuleError.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 Remora.Results;

namespace NosSmooth.LocalBinding.Errors;

/// <summary>
/// Could not initialize the given NosTale module.
/// </summary>
/// <param name="Module">The module type that could not be initialized.</param>
/// <param name="UnderlyingError">The error why the module could not be initialized.</param>
public record CouldNotInitializeModuleError
    (Type Module, IResultError UnderlyingError) : ResultError($"Could initialize a nostale module {Module.FullName}.");
\ No newline at end of file

A Local/NosSmooth.LocalBinding/Errors/NotNostaleProcessError.cs => Local/NosSmooth.LocalBinding/Errors/NotNostaleProcessError.cs +17 -0
@@ 0,0 1,17 @@
//
//  NotNostaleProcessError.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.Diagnostics;
using Remora.Results;

namespace NosSmooth.LocalBinding.Errors;

/// <summary>
/// The process you tried to browse is not a nostale process.
/// </summary>
/// <param name="process"></param>
public record NotNostaleProcessError(Process process) : ResultError
    ($"The process {process.ProcessName} ({process.Id} is not a NosTale process.");
\ No newline at end of file

M Local/NosSmooth.LocalBinding/Extensions/ServiceCollectionExtensions.cs => Local/NosSmooth.LocalBinding/Extensions/ServiceCollectionExtensions.cs +8 -1
@@ 6,6 6,7 @@

using Microsoft.Extensions.DependencyInjection;
using NosSmooth.LocalBinding.Objects;
using NosSmooth.LocalBinding.Structs;

namespace NosSmooth.LocalBinding.Extensions;



@@ 29,8 30,14 @@ public static class ServiceCollectionExtensions
    {
        return serviceCollection
            .AddSingleton<NosBindingManager>()
            .AddSingleton(p => p.GetRequiredService<NosBindingManager>().NosBrowser)
            .AddSingleton<NosBrowserManager>()
            .AddSingleton(p => p.GetRequiredService<NosBrowserManager>().PlayerManager)
            .AddSingleton(p => p.GetRequiredService<NosBrowserManager>().SceneManager)
            .AddSingleton(p => p.GetRequiredService<NosBrowserManager>().PetManagerList)
            .AddSingleton(p => p.GetRequiredService<NosBrowserManager>().SceneManager)
            .AddSingleton(p => p.GetRequiredService<NosBrowserManager>().PetManagerList)
            .AddSingleton(p => p.GetRequiredService<NosBindingManager>().PlayerManager)
            .AddSingleton(p => p.GetRequiredService<NosBindingManager>().PetManager)
            .AddSingleton(p => p.GetRequiredService<NosBindingManager>().UnitManager)
            .AddSingleton(p => p.GetRequiredService<NosBindingManager>().Network);
    }

D Local/NosSmooth.LocalBinding/ExternalNosBrowser.cs => Local/NosSmooth.LocalBinding/ExternalNosBrowser.cs +0 -126
@@ 1,126 0,0 @@
//
//  ExternalNosBrowser.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.Diagnostics;
using Microsoft.Extensions.Options;
using NosSmooth.LocalBinding.Options;
using NosSmooth.LocalBinding.Structs;
using Reloaded.Memory.Sigscan;
using Reloaded.Memory.Sources;
using Remora.Results;

namespace NosSmooth.LocalBinding;

/// <summary>
/// Used for browsing a nostale process data.
/// </summary>
public class ExternalNosBrowser
{
    private readonly PlayerManagerOptions _playerManagerOptions;
    private readonly SceneManagerOptions _sceneManagerOptions;
    private readonly PetManagerOptions _petManagerOptions;
    private PlayerManager? _playerManager;
    private SceneManager? _sceneManager;
    private PetManagerList? _petManagerList;

    /// <summary>
    /// Initializes a new instance of the <see cref="ExternalNosBrowser"/> class.
    /// </summary>
    /// <param name="process">The process to browse.</param>
    /// <param name="playerManagerOptions">The options for obtaining player manager.</param>
    /// <param name="sceneManagerOptions">The scene manager options.</param>
    /// <param name="petManagerOptions">The pet manager options.</param>
    public ExternalNosBrowser
    (
        Process process,
        PlayerManagerOptions playerManagerOptions,
        SceneManagerOptions sceneManagerOptions,
        PetManagerOptions petManagerOptions
    )
    {
        _playerManagerOptions = playerManagerOptions;
        _sceneManagerOptions = sceneManagerOptions;
        _petManagerOptions = petManagerOptions;
        Process = process;
        Memory = new ExternalMemory(process);
        Scanner = new Scanner(process, process.MainModule);
    }

    /// <summary>
    /// The NosTale process.
    /// </summary>
    public Process Process { get; }

    /// <summary>
    /// Gets the memory scanner.
    /// </summary>
    internal Scanner Scanner { get; }

    /// <summary>
    /// Gets the current process memory.
    /// </summary>
    internal IMemory Memory { get; }

    /// <summary>
    /// Get the player manager.
    /// </summary>
    /// <returns>The player manager or an error.</returns>
    public Result<PlayerManager> GetPlayerManager()
    {
        if (_playerManager is null)
        {
            var playerManagerResult = PlayerManager.Create(this, _playerManagerOptions);
            if (!playerManagerResult.IsSuccess)
            {
                return playerManagerResult;
            }

            _playerManager = playerManagerResult.Entity;
        }

        return _playerManager;
    }

    /// <summary>
    /// Get the player manager.
    /// </summary>
    /// <returns>The player manager or an error.</returns>
    public Result<SceneManager> GetSceneManager()
    {
        if (_sceneManager is null)
        {
            var sceneManagerResult = SceneManager.Create(this, _sceneManagerOptions);
            if (!sceneManagerResult.IsSuccess)
            {
                return sceneManagerResult;
            }

            _sceneManager = sceneManagerResult.Entity;
        }

        return _sceneManager;
    }

    /// <summary>
    /// Get the pet manager list.
    /// </summary>
    /// <returns>The player manager or an error.</returns>
    public Result<PetManagerList> GetPetManagerList()
    {
        if (_petManagerList is null)
        {
            var petManagerResult = PetManagerList.Create(this, _petManagerOptions);
            if (!petManagerResult.IsSuccess)
            {
                return petManagerResult;
            }

            _petManagerList = petManagerResult.Entity;
        }

        return _petManagerList;
    }
}
\ No newline at end of file

M Local/NosSmooth.LocalBinding/NosBindingManager.cs => Local/NosSmooth.LocalBinding/NosBindingManager.cs +204 -44
@@ 6,6 6,7 @@

using System.Diagnostics;
using Microsoft.Extensions.Options;
using NosSmooth.LocalBinding.Errors;
using NosSmooth.LocalBinding.Objects;
using NosSmooth.LocalBinding.Options;
using Reloaded.Hooks;


@@ 21,6 22,8 @@ namespace NosSmooth.LocalBinding;
/// </summary>
public class NosBindingManager : IDisposable
{
    private readonly NosBrowserManager _browserManager;
    private readonly PetManagerBindingOptions _petManagerBindingOptions;
    private readonly CharacterBindingOptions _characterBindingOptions;
    private readonly NetworkBindingOptions _networkBindingOptions;
    private UnitManagerBindingOptions _unitManagerBindingOptions;


@@ 28,47 31,36 @@ public class NosBindingManager : IDisposable
    private NetworkBinding? _networkBinding;
    private PlayerManagerBinding? _characterBinding;
    private UnitManagerBinding? _unitManagerBinding;
    private PetManagerBinding? _petManagerBinding;

    /// <summary>
    /// Initializes a new instance of the <see cref="NosBindingManager"/> class.
    /// </summary>
    /// <param name="browserManager">The NosTale browser manager.</param>
    /// <param name="characterBindingOptions">The character binding options.</param>
    /// <param name="networkBindingOptions">The network binding options.</param>
    /// <param name="sceneManagerBindingOptions">The scene manager binding options.</param>
    /// <param name="playerManagerOptions">The player manager options.</param>
    /// <param name="sceneManagerOptions">The scene manager options.</param>
    /// <param name="petManagerOptions">The pet manager options.</param>
    /// <param name="petManagerBindingOptions">The pet manager binding options.</param>
    public NosBindingManager
    (
        NosBrowserManager browserManager,
        IOptions<CharacterBindingOptions> characterBindingOptions,
        IOptions<NetworkBindingOptions> networkBindingOptions,
        IOptions<UnitManagerBindingOptions> sceneManagerBindingOptions,
        IOptions<PlayerManagerOptions> playerManagerOptions,
        IOptions<SceneManagerOptions> sceneManagerOptions,
        IOptions<PetManagerOptions> petManagerOptions
        IOptions<PetManagerBindingOptions> petManagerBindingOptions
    )
    {
        _browserManager = browserManager;
        Hooks = new ReloadedHooks();
        Memory = new Memory();
        Scanner = new Scanner(Process.GetCurrentProcess(), Process.GetCurrentProcess().MainModule);
        _characterBindingOptions = characterBindingOptions.Value;
        _networkBindingOptions = networkBindingOptions.Value;
        _unitManagerBindingOptions = sceneManagerBindingOptions.Value;
        NosBrowser = new ExternalNosBrowser
        (
            Process.GetCurrentProcess(),
            playerManagerOptions.Value,
            sceneManagerOptions.Value,
            petManagerOptions.Value
        );
        _petManagerBindingOptions = petManagerBindingOptions.Value;
    }

    /// <summary>
    /// Gets the nos browser.
    /// </summary>
    public ExternalNosBrowser NosBrowser { get; }

    /// <summary>
    /// Gets the memory scanner.
    /// </summary>
    internal Scanner Scanner { get; }


@@ 144,48 136,174 @@ public class NosBindingManager : IDisposable
    }

    /// <summary>
    /// Gets the pet manager binding.
    /// </summary>
    /// <exception cref="InvalidOperationException">Thrown if the manager is not initialized yet.</exception>
    public PetManagerBinding PetManager
    {
        get
        {
            if (_petManagerBinding is null)
            {
                throw new InvalidOperationException
                (
                    "Could not get pet manager. The binding manager is not initialized. Did you forget to call NosBindingManager.Initialize?"
                );
            }

            return _petManagerBinding;
        }
    }

    /// <summary>
    /// Initialize the existing bindings and hook NosTale functions.
    /// </summary>
    /// <returns>A result that may or may not have succeeded.</returns>
    public Result Initialize()
    public IResult Initialize()
    {
        var network = NetworkBinding.Create(this, _networkBindingOptions);
        if (!network.IsSuccess)
        List<IResult> errorResults = new List<IResult>();
        var browserInitializationResult = _browserManager.Initialize();
        if (!browserInitializationResult.IsSuccess)
        {
            return Result.FromError(network);
            if (browserInitializationResult.Error is NotNostaleProcessError)
            {
                return browserInitializationResult;
            }

            errorResults.Add(browserInitializationResult);
        }
        _networkBinding = network.Entity;

        var playerManager = NosBrowser.GetPlayerManager();
        if (!playerManager.IsSuccess)
        try
        {
            var network = NetworkBinding.Create(this, _networkBindingOptions);
            if (!network.IsSuccess)
            {
                errorResults.Add
                (
                    Result.FromError
                    (
                        new CouldNotInitializeModuleError(typeof(NetworkBinding), network.Error),
                        network
                    )
                );
            }

            _networkBinding = network.Entity;
        }
        catch (Exception e)
        {
            errorResults.Add(
                Result.FromError
                (
                    new CouldNotInitializeModuleError(typeof(NetworkBinding), new ExceptionError(e)),
                    (Result)new ExceptionError(e)
                ));
        }

        try
        {
            var playerManagerBinding = PlayerManagerBinding.Create
            (
                this,
                _browserManager.PlayerManager,
                _characterBindingOptions
            );
            if (!playerManagerBinding.IsSuccess)
            {
                errorResults.Add
                (
                    Result.FromError
                    (
                        new CouldNotInitializeModuleError(typeof(PlayerManagerBinding), playerManagerBinding.Error),
                        playerManagerBinding
                    )
                );
            }
            _characterBinding = playerManagerBinding.Entity;
        }
        catch (Exception e)
        {
            return Result.FromError(playerManager);
            errorResults.Add
            (
                Result.FromError
                (
                    new CouldNotInitializeModuleError(typeof(PlayerManagerBinding), new ExceptionError(e)),
                    (Result)new ExceptionError(e)
                )
            );
        }

        var playerManagerBinding = PlayerManagerBinding.Create
        (
            this,
            playerManager.Entity,
            _characterBindingOptions
        );
        if (!playerManagerBinding.IsSuccess)
        try
        {
            var unitManagerBinding = UnitManagerBinding.Create
            (
                this,
                _unitManagerBindingOptions
            );
            if (!unitManagerBinding.IsSuccess)
            {
                errorResults.Add
                (
                    Result.FromError
                    (
                        new CouldNotInitializeModuleError(typeof(UnitManagerBinding), unitManagerBinding.Error),
                        unitManagerBinding
                    )
                );
            }
            _unitManagerBinding = unitManagerBinding.Entity;
        }
        catch (Exception e)
        {
            return Result.FromError(playerManagerBinding);
            errorResults.Add
            (
                Result.FromError
                (
                    new CouldNotInitializeModuleError(typeof(UnitManagerBinding), new ExceptionError(e)),
                    (Result)new ExceptionError(e)
                )
            );
        }
        _characterBinding = playerManagerBinding.Entity;

        var unitManagerBinding = UnitManagerBinding.Create
        (
            this,
            _unitManagerBindingOptions
        );
        if (!unitManagerBinding.IsSuccess)
        try
        {
            return Result.FromError(unitManagerBinding);
            var petManagerBinding = PetManagerBinding.Create
            (
                this,
                _browserManager.PetManagerList,
                _petManagerBindingOptions
            );
            if (!petManagerBinding.IsSuccess)
            {
                errorResults.Add
                (
                    Result.FromError
                    (
                        new CouldNotInitializeModuleError(typeof(PetManagerBinding), petManagerBinding.Error),
                        petManagerBinding
                    )
                );
            }
            _petManagerBinding = petManagerBinding.Entity;
        }
        catch (Exception e)
        {
            errorResults.Add
            (
                Result.FromError
                (
                    new CouldNotInitializeModuleError(typeof(UnitManagerBinding), new ExceptionError(e)),
                    (Result)new ExceptionError(e)
                )
            );
        }
        _unitManagerBinding = unitManagerBinding.Entity;

        return Result.FromSuccess();
        return errorResults.Count switch
        {
            0 => Result.FromSuccess(),
            1 => errorResults[0],
            _ => (Result)new AggregateError(errorResults)
        };
    }

    /// <summary>


@@ 212,4 330,46 @@ public class NosBindingManager : IDisposable
        Scanner.Dispose();
        DisableHooks();
    }

    /// <summary>
    /// Create a hook object for the given pattern.
    /// </summary>
    /// <param name="name">The name of the binding.</param>
    /// <param name="callbackFunction">The callback function to call instead of the original one.</param>
    /// <param name="pattern">The pattern.</param>
    /// <param name="offset">The offset from the pattern.</param>
    /// <param name="hook">Whether to activate the hook.</param>
    /// <typeparam name="TFunction">The type of the function.</typeparam>
    /// <returns>The hook object or an error.</returns>
    internal Result<IHook<TFunction>> CreateHookFromPattern<TFunction>
    (
        string name,
        TFunction callbackFunction,
        string pattern,
        int offset = 0,
        bool hook = true
    )
    {
        var walkFunctionAddress = Scanner.CompiledFindPattern(pattern);
        if (!walkFunctionAddress.Found)
        {
            return new BindingNotFoundError(pattern, "PetManagerBinding.PetWalk");
        }

        try
        {
            return Result<IHook<TFunction>>.FromSuccess
            (
                Hooks.CreateHook
                (
                    callbackFunction,
                    walkFunctionAddress.Offset + (int)_browserManager.Process.MainModule!.BaseAddress
                )
            );
        }
        catch (Exception e)
        {
            return e;
        }
    }
}
\ No newline at end of file

A Local/NosSmooth.LocalBinding/NosBrowserManager.cs => Local/NosSmooth.LocalBinding/NosBrowserManager.cs +279 -0
@@ 0,0 1,279 @@
//
//  NosBrowserManager.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.Diagnostics;
using Microsoft.Extensions.Options;
using NosSmooth.LocalBinding.Errors;
using NosSmooth.LocalBinding.Options;
using NosSmooth.LocalBinding.Structs;
using Reloaded.Memory.Sigscan;
using Reloaded.Memory.Sources;
using Remora.Results;

namespace NosSmooth.LocalBinding;

/// <summary>
/// Used for browsing a nostale process data.
/// </summary>
public class NosBrowserManager
{
    /// <summary>
    /// Checks whether the given process is a NosTale client process.
    /// </summary>
    /// <remarks>
    /// This is just a guess based on presence of "NostaleData" directory.
    /// </remarks>
    /// <param name="process">The process to check.</param>
    /// <returns>Whether the process is a NosTale client.</returns>
    public static bool IsProcessNostaleProcess(Process process)
    {
        if (process.MainModule is null)
        {
            return false;
        }

        var processDirectory = Path.GetDirectoryName(process.MainModule.FileName);
        if (processDirectory is null)
        {
            return false;
        }

        return Directory.Exists(Path.Combine(processDirectory, "NostaleData"));
    }

    /// <summary>
    /// Get all running nostale processes.
    /// </summary>
    /// <returns>The nostale processes.</returns>
    public static IEnumerable<Process> GetAllNostaleProcesses()
        => Process
            .GetProcesses()
            .Where(IsProcessNostaleProcess);

    private readonly PlayerManagerOptions _playerManagerOptions;
    private readonly SceneManagerOptions _sceneManagerOptions;
    private readonly PetManagerOptions _petManagerOptions;
    private PlayerManager? _playerManager;
    private SceneManager? _sceneManager;
    private PetManagerList? _petManagerList;

    /// <summary>
    /// Initializes a new instance of the <see cref="NosBrowserManager"/> class.
    /// </summary>
    /// <param name="playerManagerOptions">The options for obtaining player manager.</param>
    /// <param name="sceneManagerOptions">The scene manager options.</param>
    /// <param name="petManagerOptions">The pet manager options.</param>
    public NosBrowserManager
    (
        IOptions<PlayerManagerOptions> playerManagerOptions,
        IOptions<SceneManagerOptions> sceneManagerOptions,
        IOptions<PetManagerOptions> petManagerOptions
    )
        : this
        (
            Process.GetCurrentProcess(),
            playerManagerOptions.Value,
            sceneManagerOptions.Value,
            petManagerOptions.Value
        )
    {
    }

    /// <summary>
    /// Initializes a new instance of the <see cref="NosBrowserManager"/> class.
    /// </summary>
    /// <param name="process">The process to browse.</param>
    /// <param name="playerManagerOptions">The options for obtaining player manager.</param>
    /// <param name="sceneManagerOptions">The scene manager options.</param>
    /// <param name="petManagerOptions">The pet manager options.</param>
    public NosBrowserManager
    (
        Process process,
        PlayerManagerOptions playerManagerOptions,
        SceneManagerOptions sceneManagerOptions,
        PetManagerOptions petManagerOptions
    )
    {
        _playerManagerOptions = playerManagerOptions;
        _sceneManagerOptions = sceneManagerOptions;
        _petManagerOptions = petManagerOptions;
        Process = process;
        Memory = new ExternalMemory(process);
        Scanner = new Scanner(process, process.MainModule);
    }

    /// <summary>
    /// The NosTale process.
    /// </summary>
    public Process Process { get; }

    /// <summary>
    /// Gets the memory scanner.
    /// </summary>
    internal Scanner Scanner { get; }

    /// <summary>
    /// Gets the current process memory.
    /// </summary>
    internal IMemory Memory { get; }

    /// <summary>
    /// Gets whether this is a NosTale process or not.
    /// </summary>
    public bool IsNostaleProcess => NosBrowserManager.IsProcessNostaleProcess(Process);

    /// <summary>
    /// Gets whether the player is currently in game.
    /// </summary>
    /// <remarks>
    /// It may be unsafe to access some data if the player is not in game.
    /// </remarks>
    public bool IsInGame
    {
        get
        {
            var player = PlayerManager.Player;
            return player.Address != IntPtr.Zero;
        }
    }

    /// <summary>
    /// Gets the player manager.
    /// </summary>
    /// <exception cref="InvalidOperationException">Thrown if the browser is not initialized or there was an error with initialization of player manager.</exception>
    public PlayerManager PlayerManager
    {
        get
        {
            if (_playerManager is null)
            {
                throw new InvalidOperationException
                (
                    "Could not get player manager. The browser manager is not initialized. Did you forget to call NosBrowserManager.Initialize?"
                );
            }

            return _playerManager;
        }
    }

    /// <summary>
    /// Gets the scene manager.
    /// </summary>
    /// <exception cref="InvalidOperationException">Thrown if the browser is not initialized or there was an error with initialization of scene manager.</exception>
    public SceneManager SceneManager
    {
        get
        {
            if (_sceneManager is null)
            {
                throw new InvalidOperationException
                (
                    "Could not get scene manager. The browser manager is not initialized. Did you forget to call NosBrowserManager.Initialize?"
                );
            }

            return _sceneManager;
        }
    }

    /// <summary>
    /// Gets the pet manager list.
    /// </summary>
    /// <exception cref="InvalidOperationException">Thrown if the browser is not initialized or there was an error with initialization of pet manager list.</exception>
    public PetManagerList PetManagerList
    {
        get
        {
            if (_petManagerList is null)
            {
                throw new InvalidOperationException
                (
                    "Could not get pet manager list. The browser manager is not initialized. Did you forget to call NosBrowserManager.Initialize?"
                );
            }

            return _petManagerList;
        }
    }

    /// <summary>
    /// Initialize the nos browser modules.
    /// </summary>
    /// <remarks>
    /// Needed to use all of the classes from NosTale.
    /// </remarks>
    /// <returns>A result that may or may not have succeeded.</returns>
    public IResult Initialize()
    {
        if (!IsNostaleProcess)
        {
            return (Result)new NotNostaleProcessError(Process);
        }

        List<IResult> errorResults = new List<IResult>();
        if (_playerManager is null)
        {
            var playerManagerResult = PlayerManager.Create(this, _playerManagerOptions);
            if (!playerManagerResult.IsSuccess)
            {
                errorResults.Add
                (
                    Result.FromError
                    (
                        new CouldNotInitializeModuleError(typeof(PlayerManager), playerManagerResult.Error),
                        playerManagerResult
                    )
                );
            }

            _playerManager = playerManagerResult.Entity;
        }

        if (_sceneManager is null)
        {
            var sceneManagerResult = SceneManager.Create(this, _sceneManagerOptions);
            if (!sceneManagerResult.IsSuccess)
            {
                errorResults.Add
                (
                    Result.FromError
                    (
                        new CouldNotInitializeModuleError(typeof(SceneManager), sceneManagerResult.Error),
                        sceneManagerResult
                    )
                );
            }

            _sceneManager = sceneManagerResult.Entity;
        }

        if (_petManagerList is null)
        {
            var petManagerResult = PetManagerList.Create(this, _petManagerOptions);
            if (!petManagerResult.IsSuccess)
            {
                errorResults.Add
                (
                    Result.FromError
                    (
                        new CouldNotInitializeModuleError(typeof(PetManagerList), petManagerResult.Error),
                        petManagerResult
                    )
                );
            }

            _petManagerList = petManagerResult.Entity;
        }

        return errorResults.Count switch
        {
            0 => Result.FromSuccess(),
            1 => errorResults[0],
            _ => (Result)new AggregateError(errorResults)
        };
    }
}
\ No newline at end of file

Do not follow this link