// // 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 Dictionary _modules; private readonly PlayerManagerOptions _playerManagerOptions; private readonly SceneManagerOptions _sceneManagerOptions; private readonly PetManagerOptions _petManagerOptions; private readonly NetworkManagerOptions _networkManagerOptions; private readonly UnitManagerOptions _unitManagerOptions; private readonly NtClientOptions _ntClientOptions; private bool _initialized; /// /// Initializes a new instance of the class. /// /// The options for obtaining player manager. /// The scene manager options. /// The pet manager options. /// The network manager options. /// The unit manager options. /// The nt client options. public NosBrowserManager ( IOptionsSnapshot playerManagerOptions, IOptionsSnapshot sceneManagerOptions, IOptionsSnapshot petManagerOptions, IOptionsSnapshot networkManagerOptions, IOptionsSnapshot unitManagerOptions, IOptionsSnapshot ntClientOptions ) : this ( Process.GetCurrentProcess(), playerManagerOptions.Value, sceneManagerOptions.Value, petManagerOptions.Value, networkManagerOptions.Value, unitManagerOptions.Value, ntClientOptions.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. /// The network manager options. /// The unit manager options. /// The nt client options. public NosBrowserManager ( Process process, PlayerManagerOptions? playerManagerOptions = default, SceneManagerOptions? sceneManagerOptions = default, PetManagerOptions? petManagerOptions = default, NetworkManagerOptions? networkManagerOptions = default, UnitManagerOptions? unitManagerOptions = default, NtClientOptions? ntClientOptions = default ) { _modules = new Dictionary(); _playerManagerOptions = playerManagerOptions ?? new PlayerManagerOptions(); _sceneManagerOptions = sceneManagerOptions ?? new SceneManagerOptions(); _petManagerOptions = petManagerOptions ?? new PetManagerOptions(); _networkManagerOptions = networkManagerOptions ?? new NetworkManagerOptions(); _unitManagerOptions = unitManagerOptions ?? new UnitManagerOptions(); _ntClientOptions = ntClientOptions ?? new NtClientOptions(); Process = process; Memory = Process.Id == Process.GetCurrentProcess().Id ? new 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 Optional IsInGame => PlayerManager.Map(manager => manager.Player.Address != nuint.Zero); /// /// Gets the network manager. /// public Optional NetworkManager => GetModule(); /// /// Gets the network manager. /// /// Thrown if the browser is not initialized or there was an error with initialization of unit manager. public Optional UnitManager => GetModule(); /// /// Gets the nt client. /// /// Thrown if the browser is not initialized or there was an error with initialization of nt client. public Optional NtClient => GetModule(); /// /// Gets the player manager. /// /// Thrown if the browser is not initialized or there was an error with initialization of player manager. public Optional PlayerManager => GetModule(); /// /// Gets the scene manager. /// /// Thrown if the browser is not initialized or there was an error with initialization of scene manager. public Optional SceneManager => GetModule(); /// /// Gets the pet manager list. /// /// Thrown if the browser is not initialized or there was an error with initialization of pet manager list. public Optional PetManagerList => GetModule(); /// /// Initialize the nos browser modules. /// /// /// Needed to use all of the classes from NosTale. /// /// A result that may or may not have succeeded. public Result Initialize() { if (!IsNostaleProcess) { return new NotNostaleProcessError(Process); } NostaleObject Map(T val) where T : NostaleObject { return val; } _initialized = true; return HandleResults ( (typeof(UnitManager), () => Structs.UnitManager.Create(this, _unitManagerOptions).Map(Map)), (typeof(NetworkManager), () => Structs.NetworkManager.Create(this, _networkManagerOptions).Map(Map)), (typeof(PlayerManager), () => Structs.PlayerManager.Create(this, _playerManagerOptions).Map(Map)), (typeof(SceneManager), () => Structs.SceneManager.Create(this, _sceneManagerOptions).Map(Map)), (typeof(PetManagerList), () => Structs.PetManagerList.Create(this, _petManagerOptions).Map(Map)), (typeof(NtClient), () => Structs.NtClient.Create(this, _ntClientOptions).Map(Map)) ); } /// /// Gets whether a hook or browser module is present/loaded. /// Returns false in case pattern was not found. /// /// The type of the module. /// Whether the module is present. public bool IsModuleLoaded() where TModule : NostaleObject => IsModuleLoaded(typeof(TModule)); /// /// Gets whether a hook or browser module is present/loaded. /// Returns false in case pattern was not found. /// /// The type of the module. /// Whether the module is present. public bool IsModuleLoaded(Type moduleType) { return GetModule(moduleType).IsPresent; } /// /// Get module of the specified type. /// /// The type of the module. /// The module. /// Thrown in case the manager was not initialized. public Optional GetModule() where TModule : NostaleObject { if (!_initialized) { throw new InvalidOperationException ( $"Could not get {typeof(TModule)}. The browser manager is not initialized. Did you forget to call NosBrowserManager.Initialize?" ); } if (!_modules.TryGetValue(typeof(TModule), out var nosObject) || nosObject is not TModule typed) { return Optional.Empty; } return typed; } /// /// Get module of the specified type. /// /// The type of the module. /// The module. /// Thrown in case the manager was not initialized. public Optional GetModule(Type moduleType) { if (!_initialized) { throw new InvalidOperationException ( $"Could not get {moduleType.Name}. The browser manager is not initialized. Did you forget to call NosBrowserManager.Initialize?" ); } if (!_modules.TryGetValue(moduleType, out var nosObject)) { return Optional.Empty; } return nosObject; } private Result HandleResults(params (Type Type, Func> Builder)[] objects) { Result HandleSafe(Func> builder) { try { return builder(); } catch (Exception e) { return e; } } List errorResults = new List(); foreach (var obj in objects) { var createdResult = HandleSafe(obj.Builder); if (!createdResult.IsSuccess) { errorResults.Add ( Result.FromError ( new CouldNotInitializeModuleError(obj.Type, createdResult.Error), createdResult ) ); } else if (createdResult.IsDefined(out var created)) { _modules.Add(obj.Type, created); } } return errorResults.Count switch { 0 => Result.FromSuccess(), 1 => (Result)errorResults[0], _ => new AggregateError(errorResults) }; } }