From 09416b18f49b413064947f69441329bd467ec080 Mon Sep 17 00:00:00 2001 From: Rutherther Date: Thu, 27 Jan 2022 14:32:19 +0100 Subject: [PATCH] refactor!(localbinding): add initialization method to NosTale browser instead of get methods --- .../Errors/CouldNotInitializeModuleError.cs | 17 ++ .../Errors/NotNostaleProcessError.cs | 17 ++ .../Extensions/ServiceCollectionExtensions.cs | 9 +- .../ExternalNosBrowser.cs | 126 -------- .../NosBindingManager.cs | 248 +++++++++++++--- .../NosBrowserManager.cs | 279 ++++++++++++++++++ 6 files changed, 525 insertions(+), 171 deletions(-) create mode 100644 Local/NosSmooth.LocalBinding/Errors/CouldNotInitializeModuleError.cs create mode 100644 Local/NosSmooth.LocalBinding/Errors/NotNostaleProcessError.cs delete mode 100644 Local/NosSmooth.LocalBinding/ExternalNosBrowser.cs create mode 100644 Local/NosSmooth.LocalBinding/NosBrowserManager.cs diff --git a/Local/NosSmooth.LocalBinding/Errors/CouldNotInitializeModuleError.cs b/Local/NosSmooth.LocalBinding/Errors/CouldNotInitializeModuleError.cs new file mode 100644 index 0000000..c63303a --- /dev/null +++ b/Local/NosSmooth.LocalBinding/Errors/CouldNotInitializeModuleError.cs @@ -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; + +/// +/// Could not initialize the given NosTale module. +/// +/// The module type that could not be initialized. +/// The error why the module could not be initialized. +public record CouldNotInitializeModuleError + (Type Module, IResultError UnderlyingError) : ResultError($"Could initialize a nostale module {Module.FullName}."); \ No newline at end of file diff --git a/Local/NosSmooth.LocalBinding/Errors/NotNostaleProcessError.cs b/Local/NosSmooth.LocalBinding/Errors/NotNostaleProcessError.cs new file mode 100644 index 0000000..9227b92 --- /dev/null +++ b/Local/NosSmooth.LocalBinding/Errors/NotNostaleProcessError.cs @@ -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; + +/// +/// The process you tried to browse is not a nostale process. +/// +/// +public record NotNostaleProcessError(Process process) : ResultError + ($"The process {process.ProcessName} ({process.Id} is not a NosTale process."); \ No newline at end of file diff --git a/Local/NosSmooth.LocalBinding/Extensions/ServiceCollectionExtensions.cs b/Local/NosSmooth.LocalBinding/Extensions/ServiceCollectionExtensions.cs index c51172b..21383f6 100644 --- a/Local/NosSmooth.LocalBinding/Extensions/ServiceCollectionExtensions.cs +++ b/Local/NosSmooth.LocalBinding/Extensions/ServiceCollectionExtensions.cs @@ -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() - .AddSingleton(p => p.GetRequiredService().NosBrowser) + .AddSingleton() + .AddSingleton(p => p.GetRequiredService().PlayerManager) + .AddSingleton(p => p.GetRequiredService().SceneManager) + .AddSingleton(p => p.GetRequiredService().PetManagerList) + .AddSingleton(p => p.GetRequiredService().SceneManager) + .AddSingleton(p => p.GetRequiredService().PetManagerList) .AddSingleton(p => p.GetRequiredService().PlayerManager) + .AddSingleton(p => p.GetRequiredService().PetManager) .AddSingleton(p => p.GetRequiredService().UnitManager) .AddSingleton(p => p.GetRequiredService().Network); } diff --git a/Local/NosSmooth.LocalBinding/ExternalNosBrowser.cs b/Local/NosSmooth.LocalBinding/ExternalNosBrowser.cs deleted file mode 100644 index 0e6de49..0000000 --- a/Local/NosSmooth.LocalBinding/ExternalNosBrowser.cs +++ /dev/null @@ -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; - -/// -/// Used for browsing a nostale process data. -/// -public class ExternalNosBrowser -{ - private readonly PlayerManagerOptions _playerManagerOptions; - private readonly SceneManagerOptions _sceneManagerOptions; - private readonly PetManagerOptions _petManagerOptions; - private PlayerManager? _playerManager; - private SceneManager? _sceneManager; - private PetManagerList? _petManagerList; - - /// - /// Initializes a new instance of the class. - /// - /// The process to browse. - /// The options for obtaining player manager. - /// The scene manager options. - /// The pet manager options. - 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); - } - - /// - /// The NosTale process. - /// - public Process Process { get; } - - /// - /// Gets the memory scanner. - /// - internal Scanner Scanner { get; } - - /// - /// Gets the current process memory. - /// - internal IMemory Memory { get; } - - /// - /// Get the player manager. - /// - /// The player manager or an error. - public Result GetPlayerManager() - { - if (_playerManager is null) - { - var playerManagerResult = PlayerManager.Create(this, _playerManagerOptions); - if (!playerManagerResult.IsSuccess) - { - return playerManagerResult; - } - - _playerManager = playerManagerResult.Entity; - } - - return _playerManager; - } - - /// - /// Get the player manager. - /// - /// The player manager or an error. - public Result GetSceneManager() - { - if (_sceneManager is null) - { - var sceneManagerResult = SceneManager.Create(this, _sceneManagerOptions); - if (!sceneManagerResult.IsSuccess) - { - return sceneManagerResult; - } - - _sceneManager = sceneManagerResult.Entity; - } - - return _sceneManager; - } - - /// - /// Get the pet manager list. - /// - /// The player manager or an error. - public Result 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 diff --git a/Local/NosSmooth.LocalBinding/NosBindingManager.cs b/Local/NosSmooth.LocalBinding/NosBindingManager.cs index d755f46..c4923c5 100644 --- a/Local/NosSmooth.LocalBinding/NosBindingManager.cs +++ b/Local/NosSmooth.LocalBinding/NosBindingManager.cs @@ -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; /// public class NosBindingManager : IDisposable { + private readonly NosBrowserManager _browserManager; + private readonly PetManagerBindingOptions _petManagerBindingOptions; private readonly CharacterBindingOptions _characterBindingOptions; private readonly NetworkBindingOptions _networkBindingOptions; private UnitManagerBindingOptions _unitManagerBindingOptions; @@ -28,46 +31,35 @@ public class NosBindingManager : IDisposable private NetworkBinding? _networkBinding; private PlayerManagerBinding? _characterBinding; private UnitManagerBinding? _unitManagerBinding; + private PetManagerBinding? _petManagerBinding; /// /// Initializes a new instance of the class. /// + /// The NosTale browser manager. /// The character binding options. /// The network binding options. /// The scene manager binding options. - /// The player manager options. - /// The scene manager options. - /// The pet manager options. + /// The pet manager binding options. public NosBindingManager ( + NosBrowserManager browserManager, IOptions characterBindingOptions, IOptions networkBindingOptions, IOptions sceneManagerBindingOptions, - IOptions playerManagerOptions, - IOptions sceneManagerOptions, - IOptions petManagerOptions + IOptions 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; } - /// - /// Gets the nos browser. - /// - public ExternalNosBrowser NosBrowser { get; } - /// /// Gets the memory scanner. /// @@ -143,49 +135,175 @@ public class NosBindingManager : IDisposable } } + /// + /// Gets the pet manager binding. + /// + /// Thrown if the manager is not initialized yet. + 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; + } + } + /// /// Initialize the existing bindings and hook NosTale functions. /// /// A result that may or may not have succeeded. - public Result Initialize() + public IResult Initialize() { - var network = NetworkBinding.Create(this, _networkBindingOptions); - if (!network.IsSuccess) + List errorResults = new List(); + 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) + }; } /// @@ -212,4 +330,46 @@ public class NosBindingManager : IDisposable Scanner.Dispose(); DisableHooks(); } + + /// + /// Create a hook object for the given pattern. + /// + /// The name of the binding. + /// The callback function to call instead of the original one. + /// The pattern. + /// The offset from the pattern. + /// Whether to activate the hook. + /// The type of the function. + /// The hook object or an error. + internal Result> CreateHookFromPattern + ( + 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>.FromSuccess + ( + Hooks.CreateHook + ( + callbackFunction, + walkFunctionAddress.Offset + (int)_browserManager.Process.MainModule!.BaseAddress + ) + ); + } + catch (Exception e) + { + return e; + } + } } \ No newline at end of file diff --git a/Local/NosSmooth.LocalBinding/NosBrowserManager.cs b/Local/NosSmooth.LocalBinding/NosBrowserManager.cs new file mode 100644 index 0000000..c11050a --- /dev/null +++ b/Local/NosSmooth.LocalBinding/NosBrowserManager.cs @@ -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; + +/// +/// Used for browsing a nostale process data. +/// +public class NosBrowserManager +{ + /// + /// Checks whether the given process is a NosTale client process. + /// + /// + /// This is just a guess based on presence of "NostaleData" directory. + /// + /// The process to check. + /// Whether the process is a NosTale client. + 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")); + } + + /// + /// Get all running nostale processes. + /// + /// The nostale processes. + public static IEnumerable 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; + + /// + /// Initializes a new instance of the class. + /// + /// The options for obtaining player manager. + /// The scene manager options. + /// The pet manager options. + public NosBrowserManager + ( + IOptions playerManagerOptions, + IOptions sceneManagerOptions, + IOptions petManagerOptions + ) + : this + ( + Process.GetCurrentProcess(), + playerManagerOptions.Value, + sceneManagerOptions.Value, + petManagerOptions.Value + ) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The process to browse. + /// The options for obtaining player manager. + /// The scene manager options. + /// The pet manager options. + 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); + } + + /// + /// The NosTale process. + /// + public Process Process { get; } + + /// + /// Gets the memory scanner. + /// + internal Scanner Scanner { get; } + + /// + /// Gets the current process memory. + /// + internal IMemory Memory { get; } + + /// + /// Gets whether this is a NosTale process or not. + /// + public bool IsNostaleProcess => NosBrowserManager.IsProcessNostaleProcess(Process); + + /// + /// Gets whether the player is currently in game. + /// + /// + /// It may be unsafe to access some data if the player is not in game. + /// + public bool IsInGame + { + get + { + var player = PlayerManager.Player; + return player.Address != IntPtr.Zero; + } + } + + /// + /// Gets the player manager. + /// + /// Thrown if the browser is not initialized or there was an error with initialization of player manager. + 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; + } + } + + /// + /// Gets the scene manager. + /// + /// Thrown if the browser is not initialized or there was an error with initialization of scene manager. + 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; + } + } + + /// + /// Gets the pet manager list. + /// + /// Thrown if the browser is not initialized or there was an error with initialization of pet manager list. + 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; + } + } + + /// + /// Initialize the nos browser modules. + /// + /// + /// Needed to use all of the classes from NosTale. + /// + /// A result that may or may not have succeeded. + public IResult Initialize() + { + if (!IsNostaleProcess) + { + return (Result)new NotNostaleProcessError(Process); + } + + List errorResults = new List(); + 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 -- 2.48.1