// // PlayerManagerBinding.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.Extensions; 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 PlayerManagerBinding { [Function ( new[] { FunctionAttribute.Register.eax, FunctionAttribute.Register.edx, FunctionAttribute.Register.ecx }, FunctionAttribute.Register.eax, FunctionAttribute.StackCleanup.Callee, new[] { FunctionAttribute.Register.ebx, FunctionAttribute.Register.esi, FunctionAttribute.Register.edi, FunctionAttribute.Register.ebp } )] private delegate bool WalkDelegate(nuint 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, new[] { FunctionAttribute.Register.ebx, FunctionAttribute.Register.esi, FunctionAttribute.Register.edi, FunctionAttribute.Register.ebp } )] private delegate bool FollowEntityDelegate ( nuint playerManagerPtr, nuint entityPtr, int unknown1 = 0, int unknown2 = 1 ); [Function ( new[] { FunctionAttribute.Register.eax, FunctionAttribute.Register.edx }, FunctionAttribute.Register.eax, FunctionAttribute.StackCleanup.Callee, new[] { FunctionAttribute.Register.ebx, FunctionAttribute.Register.esi, FunctionAttribute.Register.edi, FunctionAttribute.Register.ebp } )] private delegate void UnfollowEntityDelegate(nuint playerManagerPtr, int unknown = 0); /// /// Create the network binding with finding the network object and functions. /// /// The binding manager. /// The player manager. /// The options for the binding. /// A network binding or an error. public static Result Create(NosBindingManager bindingManager, PlayerManager playerManager, CharacterBindingOptions options) { var binding = new PlayerManagerBinding ( bindingManager, playerManager ); var walkHookResult = bindingManager.CreateHookFromPattern ("CharacterBinding.Walk", binding.WalkDetour, options.WalkHook); if (!walkHookResult.IsDefined(out var walkHook)) { return Result.FromError(walkHookResult); } var entityFollowHookResult = bindingManager.CreateHookFromPattern ("CharacterBinding.EntityFollow", binding.FollowEntityDetour, options.EntityFollowHook); if (!entityFollowHookResult.IsDefined(out var entityFollowHook)) { return Result.FromError(entityFollowHookResult); } var entityUnfollowHookResult = bindingManager.CreateHookFromPattern ("CharacterBinding.EntityUnfollow", binding.UnfollowEntityDetour, options.EntityUnfollowHook); if (!entityUnfollowHookResult.IsDefined(out var entityUnfollowHook)) { return Result.FromError(entityUnfollowHookResult); } binding._walkHook = walkHook; binding._followHook = entityFollowHook; binding._unfollowHook = entityUnfollowHook; return binding; } private readonly NosBindingManager _bindingManager; private IHook _walkHook = null!; private IHook _followHook = null!; private IHook _unfollowHook = null!; private PlayerManagerBinding ( NosBindingManager bindingManager, PlayerManager playerManager ) { PlayerManager = playerManager; _bindingManager = bindingManager; } /// /// Gets the player manager. /// public PlayerManager PlayerManager { get; } /// /// 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 PlayerManager hooks. /// public void DisableHooks() { _followHook.Disable(); _unfollowHook.Disable(); _walkHook.Disable(); } /// /// Enable all PlayerManager hooks. /// public void EnableHooks() { _followHook.EnableOrActivate(); _unfollowHook.EnableOrActivate(); _walkHook.EnableOrActivate(); } /// /// Walk to the given position. /// /// The x coordinate. /// The y coordinate. /// A result that may or may not have succeeded. public Result Walk(short x, short y) { int param = ((ushort)y << 16) | (ushort)x; try { return _walkHook.OriginalFunction(PlayerManager.Address, param); } catch (Exception e) { return e; } } private bool WalkDetour(nuint characterObject, int position, short unknown0, int unknown1) { var result = WalkCall?.Invoke((ushort)(position & 0xFFFF), (ushort)((position >> 16) & 0xFFFF)); if (result ?? true) { return _walkHook.OriginalFunction(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 ?? nuint.Zero); /// /// Follow the entity. /// /// The entity address. /// A result that may or may not have succeeded. public Result FollowEntity(nuint entityAddress) { try { _followHook.OriginalFunction(PlayerManager.Address, 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 { _unfollowHook.OriginalFunction(PlayerManager.Address); } catch (Exception e) { return e; } return Result.FromSuccess(); } private bool FollowEntityDetour ( nuint playerManagerPtr, nuint entityPtr, int unknown1, int unknown2 ) { var result = FollowEntityCall?.Invoke(new MapBaseObj(_bindingManager.Memory, entityPtr)); if (result ?? true) { return _followHook.OriginalFunction(playerManagerPtr, entityPtr, unknown1, unknown2); } return false; } private void UnfollowEntityDetour(nuint playerManagerPtr, int unknown) { var result = FollowEntityCall?.Invoke(null); if (result ?? true) { _unfollowHook.OriginalFunction(playerManagerPtr, unknown); } } }