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