//
// UnitManagerBinding.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 Reloaded.Hooks.Internal.Testing;
using Remora.Results;
namespace NosSmooth.LocalBinding.Objects;
///
/// The nostale binding of a scene manager.
///
///
/// The scene manager holds addresses to entities, mouse position, ....
///
public class UnitManagerBinding
{
[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 nuint FocusEntityDelegate(nuint unitManagerPtr, nuint entityPtr);
///
/// Create the scene manager binding.
///
/// The binding manager.
/// The options for the binding.
/// A network binding or an error.
public static Result Create
(NosBindingManager bindingManager, UnitManagerBindingOptions bindingOptions)
{
var process = Process.GetCurrentProcess();
var unitManagerStaticAddress = bindingManager.Scanner.FindPattern(bindingOptions.UnitManagerPattern);
if (!unitManagerStaticAddress.Found)
{
return new BindingNotFoundError(bindingOptions.UnitManagerPattern, "UnitManagerBinding.UnitManager");
}
var binding = new UnitManagerBinding
(
bindingManager,
(int)process.MainModule!.BaseAddress + unitManagerStaticAddress.Offset,
bindingOptions.UnitManagerOffsets
);
var entityFocusHookResult = bindingManager.CreateCustomAsmHookFromPattern
("UnitManager.EntityFocus", binding.FocusEntityDetour, bindingOptions.EntityFocusHook);
if (!entityFocusHookResult.IsDefined(out var entityFocusHook))
{
return Result.FromError(entityFocusHookResult);
}
binding._focusHook = entityFocusHook;
return binding;
}
private readonly int _staticUnitManagerAddress;
private readonly int[] _unitManagerOffsets;
private readonly NosBindingManager _bindingManager;
private NosAsmHook _focusHook = null!;
private bool _callingFocus;
private UnitManagerBinding
(
NosBindingManager bindingManager,
int staticUnitManagerAddress,
int[] unitManagerOffsets
)
{
_bindingManager = bindingManager;
_staticUnitManagerAddress = staticUnitManagerAddress;
_unitManagerOffsets = unitManagerOffsets;
}
///
/// Gets the address of unit manager.
///
public nuint Address => _bindingManager.Memory.FollowStaticAddressOffsets
(_staticUnitManagerAddress, _unitManagerOffsets);
///
/// Event that is called when entity focus was called by NosTale.
///
///
/// The focus entity must be hooked for this event to be called.
///
public event Func? EntityFocus;
///
/// Focus the entity.
///
/// The entity.
/// A result that may or may not have succeeded.
public Result FocusEntity(MapBaseObj? entity)
=> FocusEntity(entity?.Address ?? nuint.Zero);
///
/// Disable all UnitManager hooks.
///
public void DisableHooks()
{
_focusHook.Hook.Disable();
}
///
/// Enable all UnitManager hooks.
///
public void EnableHooks()
{
_focusHook.Hook.EnableOrActivate();
}
///
/// Focus the entity.
///
/// The entity address.
/// A result that may or may not have succeeded.
public Result FocusEntity(nuint entityAddress)
{
try
{
_callingFocus = true;
_focusHook.OriginalFunction.GetWrapper()(Address, entityAddress);
_callingFocus = false;
}
catch (Exception e)
{
return e;
}
return Result.FromSuccess();
}
private nuint FocusEntityDetour(nuint unitManagerPtr, nuint entityId)
{
if (_callingFocus)
{
// in case this is being called from UnitManagerBinding.FocusEntity,
// do not handle.
return 1;
}
MapBaseObj? obj = null;
if (entityId != nuint.Zero)
{
obj = new MapBaseObj(_bindingManager.Memory, entityId);
}
return (EntityFocus?.Invoke(obj) ?? true) ? (nuint)1 : 0;
}
}