// // CharacterBinding.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 NosSmooth.LocalBinding.Errors; using NosSmooth.LocalBinding.Options; using NosSmooth.LocalBinding.Structs; using Reloaded.Hooks.Definitions; using Reloaded.Hooks.Definitions.X86; using Remora.Results; namespace NosSmooth.LocalBinding.Objects; /// /// The nostale binding of a character. /// public class CharacterBinding { [Function ( new[] { FunctionAttribute.Register.eax, FunctionAttribute.Register.edx, FunctionAttribute.Register.ecx }, FunctionAttribute.Register.eax, FunctionAttribute.StackCleanup.Callee )] private delegate bool WalkDelegate(IntPtr playerManagerPtr, int position, short unknown0 = 0, int unknown1 = 1); [Function ( new[] { FunctionAttribute.Register.eax, FunctionAttribute.Register.edx, FunctionAttribute.Register.ecx }, FunctionAttribute.Register.eax, FunctionAttribute.StackCleanup.Callee )] private delegate bool FollowEntityDelegate ( IntPtr playerManagerPtr, IntPtr entityPtr, int unknown1 = 0, int unknown2 = 1 ); [Function ( new[] { FunctionAttribute.Register.eax, FunctionAttribute.Register.edx }, FunctionAttribute.Register.eax, FunctionAttribute.StackCleanup.Callee )] private delegate void UnfollowEntityDelegate(IntPtr playerManagerPtr, int unknown = 0); /// /// Create the network binding with finding the network object and functions. /// /// The binding manager. /// The options for the binding. /// A network binding or an error. public static Result Create(NosBindingManager bindingManager, CharacterBindingOptions options) { var process = Process.GetCurrentProcess(); var characterObjectAddress = bindingManager.Scanner.CompiledFindPattern(options.CharacterObjectPattern); if (!characterObjectAddress.Found) { return new BindingNotFoundError(options.CharacterObjectPattern, "CharacterBinding"); } var walkFunctionAddress = bindingManager.Scanner.CompiledFindPattern(options.WalkFunctionPattern); if (!walkFunctionAddress.Found) { return new BindingNotFoundError(options.WalkFunctionPattern, "CharacterBinding.Walk"); } var followEntityAddress = bindingManager.Scanner.CompiledFindPattern(options.FollowEntityPattern); if (!followEntityAddress.Found) { return new BindingNotFoundError(options.FollowEntityPattern, "CharacterBinding.FollowEntity"); } var unfollowEntityAddress = bindingManager.Scanner.CompiledFindPattern(options.UnfollowEntityPattern); if (!unfollowEntityAddress.Found) { return new BindingNotFoundError(options.UnfollowEntityPattern, "CharacterBinding.UnfollowEntity"); } var walkFunction = bindingManager.Hooks.CreateFunction (walkFunctionAddress.Offset + (int)process.MainModule!.BaseAddress); var walkWrapper = walkFunction.GetWrapper(); var followEntityFunction = bindingManager.Hooks.CreateFunction (followEntityAddress.Offset + (int)process.MainModule!.BaseAddress); var followEntityWrapper = followEntityFunction.GetWrapper(); var unfollowEntityFunction = bindingManager.Hooks.CreateFunction (unfollowEntityAddress.Offset + (int)process.MainModule!.BaseAddress); var unfollowEntityWrapper = unfollowEntityFunction.GetWrapper(); var binding = new CharacterBinding ( bindingManager, (IntPtr)(characterObjectAddress.Offset + (int)process.MainModule!.BaseAddress + 0x06), walkWrapper, followEntityWrapper, unfollowEntityWrapper ); if (options.HookWalk) { binding._walkHook = walkFunction .Hook(binding.WalkDetour); binding._originalWalk = binding._walkHook.OriginalFunction; } if (options.HookFollowEntity) { binding._followHook = followEntityFunction.Hook(binding.FollowEntityDetour); binding._originalFollowEntity = binding._followHook.OriginalFunction; } if (options.HookUnfollowEntity) { binding._unfollowHook = unfollowEntityFunction.Hook(binding.UnfollowEntityDetour); binding._originalUnfollowEntity = binding._unfollowHook.OriginalFunction; } binding._walkHook?.Activate(); binding._followHook?.Activate(); binding._unfollowHook?.Activate(); return binding; } private readonly NosBindingManager _bindingManager; private readonly IntPtr _characterAddress; private IHook? _walkHook; private IHook? _followHook; private IHook? _unfollowHook; private FollowEntityDelegate _originalFollowEntity; private UnfollowEntityDelegate _originalUnfollowEntity; private WalkDelegate _originalWalk; private CharacterBinding ( NosBindingManager bindingManager, IntPtr characterAddress, WalkDelegate originalWalk, FollowEntityDelegate originalFollowEntity, UnfollowEntityDelegate originalUnfollowEntity ) { _bindingManager = bindingManager; _characterAddress = characterAddress; _originalWalk = originalWalk; _originalFollowEntity = originalFollowEntity; _originalUnfollowEntity = originalUnfollowEntity; } /// /// Event that is called when walk was called by NosTale. /// /// /// The walk must be hooked for this event to be called. /// public event Func? WalkCall; /// /// Event that is called when entity follow or unfollow was called. /// /// /// The follow/unfollow entity must be hooked for this event to be called. /// public event Func? FollowEntityCall; /// /// Disable all the hooks that are currently enabled. /// /// A result that may or may not have succeeded. public Result DisableHooks() { _walkHook?.Disable(); return Result.FromSuccess(); } private IntPtr GetCharacterAddress() { IntPtr characterAddress = _characterAddress; _bindingManager.Memory.Read(characterAddress, out characterAddress); _bindingManager.Memory.Read(characterAddress, out characterAddress); return characterAddress; } /// /// Walk to the given position. /// /// The x coordinate. /// The y coordinate. /// A result that may or may not have succeeded. public Result Walk(ushort x, ushort y) { int param = (y << 16) | x; try { return _originalWalk(GetCharacterAddress(), param); } catch (Exception e) { return e; } } private bool WalkDetour(IntPtr characterObject, int position, short unknown0, int unknown1) { var result = WalkCall?.Invoke((ushort)(position & 0xFFFF), (ushort)((position >> 16) & 0xFFFF)); if (result ?? true) { return _originalWalk(characterObject, position, unknown0, unknown1); } return false; } /// /// Follow the entity. /// /// The entity. /// A result that may or may not have succeeded. public Result FollowEntity(MapBaseObj? entity) => FollowEntity(entity?.Address ?? IntPtr.Zero); /// /// Follow the entity. /// /// The entity address. /// A result that may or may not have succeeded. public Result FollowEntity(IntPtr entityAddress) { try { _originalFollowEntity(GetCharacterAddress(), entityAddress); } catch (Exception e) { return e; } return Result.FromSuccess(); } /// /// Stop following entity. /// /// A result that may or may not have succeeded. public Result UnfollowEntity() { try { _originalUnfollowEntity(GetCharacterAddress()); } catch (Exception e) { return e; } return Result.FromSuccess(); } private bool FollowEntityDetour ( IntPtr playerManagerPtr, IntPtr entityPtr, int unknown1, int unknown2 ) { var result = FollowEntityCall?.Invoke(new MapBaseObj(_bindingManager.Memory, entityPtr)); if (result ?? true) { return _originalFollowEntity(playerManagerPtr, entityPtr, unknown1, unknown2); } return false; } private void UnfollowEntityDetour(IntPtr playerManagerPtr, int unknown) { var result = FollowEntityCall?.Invoke(null); if (result ?? true) { _originalUnfollowEntity(playerManagerPtr, unknown); } } }