//
// NosBindingManager.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 System.Reflection;
using Microsoft.Extensions.Options;
using NosSmooth.LocalBinding.Errors;
using NosSmooth.LocalBinding.Hooks;
using NosSmooth.LocalBinding.Hooks.Implementations;
using NosSmooth.LocalBinding.Objects;
using NosSmooth.LocalBinding.Options;
using NosSmooth.LocalBinding.Structs;
using Reloaded.Hooks;
using Reloaded.Hooks.Definitions;
using Reloaded.Hooks.Definitions.Helpers;
using Reloaded.Hooks.Definitions.Internal;
using Reloaded.Hooks.Definitions.X86;
using Reloaded.Hooks.Tools;
using Reloaded.Hooks.X86;
using Reloaded.Memory.Sigscan;
using Reloaded.Memory.Sources;
using Remora.Results;
namespace NosSmooth.LocalBinding;
///
/// Nostale entity binding manager.
///
public class NosBindingManager : IDisposable
{
private readonly NosBrowserManager _browserManager;
private readonly IHookManager _hookManager;
///
/// Initializes a new instance of the class.
///
/// The NosTale browser manager.
/// The hook manager.
public NosBindingManager
(
NosBrowserManager browserManager,
IHookManager hookManager
)
{
_browserManager = browserManager;
_hookManager = hookManager;
Hooks = new ReloadedHooks();
Memory = new Memory();
Scanner = new Scanner(Process.GetCurrentProcess(), Process.GetCurrentProcess().MainModule);
}
///
/// Gets the memory scanner.
///
internal Scanner Scanner { get; }
///
/// Gets the reloaded hooks.
///
internal IReloadedHooks Hooks { get; }
///
/// Gets the current process memory.
///
internal IMemory Memory { get; }
///
/// Initialize the existing bindings and hook NosTale functions.
///
/// A result that may or may not have succeeded.
public IResult Initialize()
{
List errorResults = new List();
var browserInitializationResult = _browserManager.Initialize();
if (!browserInitializationResult.IsSuccess)
{
if (browserInitializationResult.Error is NotNostaleProcessError)
{
return browserInitializationResult;
}
errorResults.Add(browserInitializationResult);
}
var hookManagerInitializationResult = _hookManager.Initialize(this, _browserManager);
if (!hookManagerInitializationResult.IsSuccess)
{
errorResults.Add(hookManagerInitializationResult);
}
return errorResults.Count switch
{
0 => Result.FromSuccess(),
1 => errorResults[0],
_ => (Result)new AggregateError(errorResults)
};
}
///
/// Gets whether a hook or browser module is present.
///
/// The type of the module.
/// Whether the module is present.
public bool IsModulePresent()
=> _hookManager.IsHookUsable(typeof(TModule)) || _browserManager.IsModuleLoaded(typeof(TModule));
///
public void Dispose()
{
Scanner.Dispose();
_hookManager.DisableAll();
}
///
/// Create a hook object for the given pattern.
///
/// The name of the binding.
/// The callback function to call instead of the original one.
/// The options for the function hook. (pattern, offset, whether to activate).
/// The type of the function.
/// The hook object or an error.
internal Result> CreateHookFromPattern
(
string name,
TFunction callbackFunction,
HookOptions options
)
{
var walkFunctionAddress = Scanner.FindPattern(options.MemoryPattern);
if (!walkFunctionAddress.Found)
{
return new BindingNotFoundError(options.MemoryPattern, name);
}
try
{
var hook = Hooks.CreateHook
(
callbackFunction,
walkFunctionAddress.Offset + (int)_browserManager.Process.MainModule!.BaseAddress + options.Offset
);
if (options.Hook)
{
hook.Activate();
}
return Result>.FromSuccess(hook);
}
catch (Exception e)
{
return e;
}
}
///
/// Create custom assembler hook.
///
///
/// Sometimes there are more requirements than Reloaded-Hooks handles
/// (or maybe I am just configuring it correctly).
///
/// For these cases this method is here. It adds a detour call at the beginning
/// of a function. The detour function should return 1 to continue,
/// 0 to return at the beginning.
///
/// The name of the binding.
/// The callback function to call instead of the original one.
/// The options for the function hook. (pattern, offset, whether to activate).
/// Whether the call may be cancelled.
/// The type of the function.
/// The hook object or an error.
internal Result>
CreateCustomAsmHookFromPattern
(
string name,
TFunction callbackFunction,
HookOptions options,
bool cancellable = true
)
where TFunction : Delegate
{
var walkFunctionAddress = Scanner.FindPattern(options.MemoryPattern);
if (!walkFunctionAddress.Found)
{
return new BindingNotFoundError(options.MemoryPattern, name);
}
try
{
var address = walkFunctionAddress.Offset + (int)_browserManager.Process.MainModule!.BaseAddress
+ options.Offset;
var wrapper = Hooks.CreateFunction(address);
var reverseWrapper = Hooks.CreateReverseWrapper(callbackFunction);
var callDetour = Utilities.GetAbsoluteCallMnemonics
(reverseWrapper.WrapperPointer.ToUnsigned(), IntPtr.Size == 8);
if (!Misc.TryGetAttribute(out var attribute))
{
return new ArgumentInvalidError(nameof(TFunction), "The function does not have a function attribute.");
}
var stackArgumentsCount = Utilities.GetNumberofParameters() - attribute.SourceRegisters.Length;
if (stackArgumentsCount < 0)
{
stackArgumentsCount = 0;
}
var asmInstructions = new List();
asmInstructions.AddRange(new[]
{
"use32",
"pushad",
"pushfd",
});
for (int i = 0; i < stackArgumentsCount; i++)
{ // TODO make it into asm loop
asmInstructions.AddRange(new[]
{
$"add esp, {36 + (4 * stackArgumentsCount)}",
"pop ebx",
"sub esp, 0x04",
$"sub esp, {36 + (4 * stackArgumentsCount)}",
"push ebx",
});
}
asmInstructions.Add(callDetour);
if (cancellable)
{
asmInstructions.AddRange
(
new[]
{
// check result
// 1 means continue executing
// 0 means do not permit the call
"test eax, eax",
"jnz rest",
// returned 0, going to return early
"popfd",
"popad",
$"ret {4 * stackArgumentsCount}", // return early
}
);
}
asmInstructions.AddRange(new[]
{
// returned 1, going to execute the function
"rest:",
"popfd",
"popad"
});
var hook = Hooks.CreateAsmHook(asmInstructions.ToArray(), address);
if (options.Hook)
{
hook.Activate();
}
return Result>.FromSuccess
(new NosAsmHook(reverseWrapper, wrapper, hook));
}
catch (Exception e)
{
return e;
}
}
}