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