//
// 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)
};
}
}