~ruther/NosSmooth.Local

bbad5288a9a78ffcd23ecf2cc5650432180be327 — Rutherther 2 years ago c2ab91b
feat(binding): add hook config builder for configuring what hooks to enable

Resolves #1. Use IServiceCollection.ConfigureHooks() for accessing the builder.
M src/Core/NosSmooth.LocalBinding/Extensions/ServiceCollectionExtensions.cs => src/Core/NosSmooth.LocalBinding/Extensions/ServiceCollectionExtensions.cs +14 -0
@@ 41,4 41,18 @@ public static class ServiceCollectionExtensions
            .AddSingleton(p => p.GetRequiredService<NosBindingManager>().UnitManager)
            .AddSingleton(p => p.GetRequiredService<NosBindingManager>().Network);
    }

    /// <summary>
    /// Configures what functions to hook and allows the user to make pattern, offset changes.
    /// </summary>
    /// <param name="serviceCollection">The service collection.</param>
    /// <param name="configure">Function for configuring the hook config.</param>
    /// <returns>The collection.</returns>
    public static IServiceCollection ConfigureHooks(this IServiceCollection serviceCollection, Action<HooksConfigBuilder> configure)
    {
        var builder = new HooksConfigBuilder();
        configure(builder);
        builder.Apply(serviceCollection);
        return serviceCollection;
    }
}
\ No newline at end of file

A src/Core/NosSmooth.LocalBinding/HookOptions.cs => src/Core/NosSmooth.LocalBinding/HookOptions.cs +15 -0
@@ 0,0 1,15 @@
//
//  HookOptions.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.

namespace NosSmooth.LocalBinding;

/// <summary>
/// A configuration for hooking a function.
/// </summary>
/// <param name="Hook">Whether to hook the function.</param>
/// <param name="MemoryPattern">The memory pattern in hex. Use ?? for any bytes.</param>
/// <param name="Offset">The offset to find the function at.</param>
public record HookOptions(bool Hook, string MemoryPattern, int Offset);
\ No newline at end of file

A src/Core/NosSmooth.LocalBinding/HookOptionsBuilder.cs => src/Core/NosSmooth.LocalBinding/HookOptionsBuilder.cs +83 -0
@@ 0,0 1,83 @@
//
//  HookOptionsBuilder.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.

namespace NosSmooth.LocalBinding;

/// <summary>
/// Builder for <see cref="HookOptions"/>.
/// </summary>
public class HookOptionsBuilder
{
    private bool _hook;
    private string _pattern;
    private int _offset;

    /// <summary>
    /// Initializes a new instance of the <see cref="HookOptionsBuilder"/> class.
    /// </summary>
    /// <param name="options">The options.</param>
    internal HookOptionsBuilder(HookOptions options)
    {
        _hook = options.Hook;
        _pattern = options.MemoryPattern;
        _offset = options.Offset;
    }

    /// <summary>
    /// Initializes a new instance of the <see cref="HookOptionsBuilder"/> class.
    /// </summary>
    /// <param name="hook">Whether to hook the function.</param>
    /// <param name="pattern">The default pattern.</param>
    /// <param name="offset">The default offset.</param>
    internal HookOptionsBuilder(bool hook, string pattern, int offset)
    {
        _offset = offset;
        _pattern = pattern;
        _hook = hook;
    }

    /// <summary>
    /// Configure whether to hook this function.
    /// Default true.
    /// </summary>
    /// <param name="hook">Whether to hook the function.</param>
    /// <returns>This builder.</returns>
    public HookOptionsBuilder Hook(bool hook = true)
    {
        _hook = hook;
        return this;
    }

    /// <summary>
    /// Configure the memory pattern.
    /// Use ?? for any bytes.
    /// </summary>
    /// <param name="pattern">The memory pattern.</param>
    /// <returns>This builder.</returns>
    public HookOptionsBuilder MemoryPattern(string pattern)
    {
        _pattern = pattern;
        return this;
    }

    /// <summary>
    /// Configure the offset from the pattern.
    /// </summary>
    /// <param name="offset">The offset.</param>
    /// <returns>This builder.</returns>
    public HookOptionsBuilder Offset(int offset)
    {
        _offset = offset;
        return this;
    }

    /// <summary>
    /// Create hook options from this builder.
    /// </summary>
    /// <returns>The options.</returns>
    internal HookOptions Build()
        => new HookOptions(_hook, _pattern, _offset);
}
\ No newline at end of file

A src/Core/NosSmooth.LocalBinding/HooksConfigBuilder.cs => src/Core/NosSmooth.LocalBinding/HooksConfigBuilder.cs +290 -0
@@ 0,0 1,290 @@
//
//  HooksConfigBuilder.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 Microsoft.Extensions.DependencyInjection;
using NosSmooth.LocalBinding.Options;

namespace NosSmooth.LocalBinding;

/// <summary>
/// Provides user-friendly builder for configuring
/// the hooks.
/// </summary>
/// <remarks>
/// Use this by invoking .ConfigureHooks on IServiceCollection.
///
/// May change hook's pattern or offset as well as enable or disable
/// hooking altogether.
///
/// By default, networking (packet send, packet receive) hooking is enabled,
/// everything else is disabled.
///
/// The methods may be chained, you may call HookAll() and then start disabling
/// the ones you don't need.
/// </remarks>
public class HooksConfigBuilder
{
    private readonly HookOptionsBuilder _packetSendHook;
    private readonly HookOptionsBuilder _packetReceiveHook;
    private readonly HookOptionsBuilder _playerWalkHook;
    private readonly HookOptionsBuilder _petWalkHook;
    private readonly HookOptionsBuilder _entityFocusHook;
    private readonly HookOptionsBuilder _entityFollowHook;
    private readonly HookOptionsBuilder _entityUnfollowHook;

    /// <summary>
    /// Initializes a new instance of the <see cref="HooksConfigBuilder"/> class.
    /// </summary>
    internal HooksConfigBuilder()
    {
        _playerWalkHook = new HookOptionsBuilder(new CharacterBindingOptions().WalkHook);
        _packetSendHook = new HookOptionsBuilder(new NetworkBindingOptions().PacketSendHook);
        _packetReceiveHook = new HookOptionsBuilder(new NetworkBindingOptions().PacketReceiveHook);
        _petWalkHook = new HookOptionsBuilder(new PetManagerBindingOptions().PetWalkHook);
        _entityFocusHook = new HookOptionsBuilder(new UnitManagerBindingOptions().EntityFocusHook);
        _entityFollowHook = new HookOptionsBuilder(new CharacterBindingOptions().EntityFollowHook);
        _entityUnfollowHook = new HookOptionsBuilder(new CharacterBindingOptions().EntityUnfollowHook);
    }

    /// <summary>
    /// Disable all hooks.
    /// </summary>
    /// <returns>This builder.</returns>
    public HooksConfigBuilder HookNone()
    {
        _packetSendHook.Hook(false);
        _packetReceiveHook.Hook(false);
        _playerWalkHook.Hook(false);
        _petWalkHook.Hook(false);
        _entityFocusHook.Hook(false);
        _entityFollowHook.Hook(false);
        _entityUnfollowHook.Hook(false);
        return this;
    }

    /// <summary>
    /// Enable all hooks.
    /// </summary>
    /// <returns>This builder.</returns>
    public HooksConfigBuilder HookAll()
    {
        _packetSendHook.Hook(true);
        _packetReceiveHook.Hook(true);
        _playerWalkHook.Hook(true);
        _petWalkHook.Hook(true);
        _entityFocusHook.Hook(true);
        _entityFollowHook.Hook(true);
        _entityUnfollowHook.Hook(true);
        return this;
    }

    /// <summary>
    /// Enable networking (packet send, packet receive) hooks.
    /// </summary>
    /// <returns>This builder.</returns>
    public HooksConfigBuilder HookNetworking()
    {
        _packetSendHook.Hook(true);
        _packetReceiveHook.Hook(true);
        return this;
    }

    /// <summary>
    /// Configure packet send hooks.
    /// Enables the hook. In case you just want to change the pattern or offset
    /// and do not want to enable the hook, call .Hook(false) in the builder.
    /// </summary>
    /// <param name="configure">The configuring action.</param>
    /// <returns>This builder.</returns>
    public HooksConfigBuilder HookPacketSend(Action<HookOptionsBuilder>? configure = default)
    {
        if (configure is not null)
        {
            _packetSendHook.Hook();
            configure(_packetSendHook);
        }
        else
        {
            _packetSendHook.Hook(true);
        }

        return this;
    }

    /// <summary>
    /// Configure packet receive hooks.
    /// Enables the hook. In case you just want to change the pattern or offset
    /// and do not want to enable the hook, call .Hook(false) in the builder.
    /// </summary>
    /// <param name="configure">The configuring action.</param>
    /// <returns>This builder.</returns>
    public HooksConfigBuilder HookPacketReceive(Action<HookOptionsBuilder>? configure = default)
    {
        if (configure is not null)
        {
            _packetReceiveHook.Hook();
            configure(_packetReceiveHook);
        }
        else
        {
            _packetReceiveHook.Hook(true);
        }

        return this;
    }

    /// <summary>
    /// Configure player walk hooks.
    /// Enables the hook. In case you just want to change the pattern or offset
    /// and do not want to enable the hook, call .Hook(false) in the builder.
    /// </summary>
    /// <param name="configure">The configuring action.</param>
    /// <returns>This builder.</returns>
    public HooksConfigBuilder HookPlayerWalk(Action<HookOptionsBuilder>? configure = default)
    {
        if (configure is not null)
        {
            _playerWalkHook.Hook();
            configure(_playerWalkHook);
        }
        else
        {
            _playerWalkHook.Hook(true);
        }

        return this;
    }

    /// <summary>
    /// Configure pet walk hooks.
    /// Enables the hook. In case you just want to change the pattern or offset
    /// and do not want to enable the hook, call .Hook(false) in the builder.
    /// </summary>
    /// <param name="configure">The configuring action.</param>
    /// <returns>This builder.</returns>
    public HooksConfigBuilder HookPetWalk(Action<HookOptionsBuilder>? configure = default)
    {
        if (configure is not null)
        {
            _petWalkHook.Hook();
            configure(_petWalkHook);
        }
        else
        {
            _petWalkHook.Hook(true);
        }

        return this;
    }

    /// <summary>
    /// Configure entity focus hooks.
    /// Enables the hook. In case you just want to change the pattern or offset
    /// and do not want to enable the hook, call .Hook(false) in the builder.
    /// </summary>
    /// <param name="configure">The configuring action.</param>
    /// <returns>This builder.</returns>
    public HooksConfigBuilder HookEntityFocus(Action<HookOptionsBuilder>? configure = default)
    {
        if (configure is not null)
        {
            _entityFocusHook.Hook();
            configure(_entityFocusHook);
        }
        else
        {
            _entityFocusHook.Hook(true);
        }

        return this;
    }

    /// <summary>
    /// Configure entity follow hooks.
    /// Enables the hook. In case you just want to change the pattern or offset
    /// and do not want to enable the hook, call .Hook(false) in the builder.
    /// </summary>
    /// <param name="configure">The configuring action.</param>
    /// <returns>This builder.</returns>
    public HooksConfigBuilder HookEntityFollow(Action<HookOptionsBuilder>? configure = default)
    {
        if (configure is not null)
        {
            _entityFollowHook.Hook();
            configure(_entityFollowHook);
        }
        else
        {
            _entityFollowHook.Hook(true);
        }

        return this;
    }

    /// <summary>
    /// Configure entity unfollow hooks.
    /// Enables the hook. In case you just want to change the pattern or offset
    /// and do not want to enable the hook, call .Hook(false) in the builder.
    /// </summary>
    /// <param name="configure">The configuring action.</param>
    /// <returns>This builder.</returns>
    public HooksConfigBuilder HookEntityUnfollow(Action<HookOptionsBuilder>? configure = default)
    {
        if (configure is not null)
        {
            _entityUnfollowHook.Hook();
            configure(_entityUnfollowHook);
        }
        else
        {
            _entityUnfollowHook.Hook(true);
        }

        return this;
    }

    /// <summary>
    /// Applies configurations to the given collection.
    /// </summary>
    /// <param name="serviceCollection">The service collection.</param>
    internal void Apply(IServiceCollection serviceCollection)
    {
        serviceCollection.Configure<CharacterBindingOptions>
        (
            characterOptions =>
            {
                characterOptions.WalkHook = _playerWalkHook.Build();
                characterOptions.EntityFollowHook = _entityFollowHook.Build();
                characterOptions.EntityUnfollowHook = _entityUnfollowHook.Build();
            }
        );

        serviceCollection.Configure<NetworkBindingOptions>
        (
            characterOptions =>
            {
                characterOptions.PacketReceiveHook = _packetReceiveHook.Build();
                characterOptions.PacketSendHook = _packetSendHook.Build();
            }
        );

        serviceCollection.Configure<PetManagerBindingOptions>
        (
            characterOptions =>
            {
                characterOptions.PetWalkHook = _petWalkHook.Build();
            }
        );

        serviceCollection.Configure<UnitManagerBindingOptions>
        (
            characterOptions =>
            {
                characterOptions.EntityFocusHook = _entityFocusHook.Build();
            }
        );
    }
}
\ No newline at end of file

M src/Core/NosSmooth.LocalBinding/NosBindingManager.cs => src/Core/NosSmooth.LocalBinding/NosBindingManager.cs +9 -12
@@ 307,7 307,7 @@ public class NosBindingManager : IDisposable
    }

    /// <summary>
    /// Disable the currently enabled nostale hooks.
    /// Disable the currently enabled NosTale hooks.
    /// </summary>
    /// <returns>A result that may or may not have succeeded.</returns>
    public Result DisableHooks()


@@ 336,24 336,20 @@ public class NosBindingManager : IDisposable
    /// </summary>
    /// <param name="name">The name of the binding.</param>
    /// <param name="callbackFunction">The callback function to call instead of the original one.</param>
    /// <param name="pattern">The pattern.</param>
    /// <param name="offset">The offset from the pattern.</param>
    /// <param name="enableHook">Whether to activate the hook.</param>
    /// <param name="options">The options for the function hook. (pattern, offset, whether to activate).</param>
    /// <typeparam name="TFunction">The type of the function.</typeparam>
    /// <returns>The hook object or an error.</returns>
    internal Result<IHook<TFunction>> CreateHookFromPattern<TFunction>
    (
        string name,
        TFunction callbackFunction,
        string pattern,
        int offset = 0,
        bool enableHook = true
        HookOptions options
    )
    {
        var walkFunctionAddress = Scanner.FindPattern(pattern);
        var walkFunctionAddress = Scanner.FindPattern(options.MemoryPattern);
        if (!walkFunctionAddress.Found)
        {
            return new BindingNotFoundError(pattern, name);
            return new BindingNotFoundError(options.MemoryPattern, name);
        }

        try


@@ 361,11 357,12 @@ public class NosBindingManager : IDisposable
            var hook = Hooks.CreateHook
            (
                callbackFunction,
                walkFunctionAddress.Offset + (int)_browserManager.Process.MainModule!.BaseAddress + offset
                walkFunctionAddress.Offset + (int)_browserManager.Process.MainModule!.BaseAddress + options.Offset
            );
            if (enableHook)
            hook.Activate();
            if (!options.Hook)
            {
                hook.Activate();
                hook.Disable();
            }

            return Result<IHook<TFunction>>.FromSuccess(hook);

M src/Core/NosSmooth.LocalBinding/Objects/NetworkBinding.cs => src/Core/NosSmooth.LocalBinding/Objects/NetworkBinding.cs +20 -48
@@ 52,72 52,44 @@ public class NetworkBinding
            return new BindingNotFoundError(options.NetworkObjectPattern, "NetworkBinding");
        }

        var packetSendAddress = bindingManager.Scanner.FindPattern(options.SendFunctionPattern);
        if (!packetSendAddress.Found)
        {
            return new BindingNotFoundError(options.SendFunctionPattern, "NetworkBinding.SendPacket");
        }

        var packetReceiveAddress = bindingManager.Scanner.FindPattern(options.ReceiveFunctionPattern);
        if (!packetReceiveAddress.Found)
        {
            return new BindingNotFoundError(options.ReceiveFunctionPattern, "NetworkBinding.ReceivePacket");
        }

        var sendFunction = bindingManager.Hooks.CreateFunction<PacketSendDelegate>
            (packetSendAddress.Offset + (int)process.MainModule!.BaseAddress);
        var sendWrapper = sendFunction.GetWrapper();

        var receiveFunction = bindingManager.Hooks.CreateFunction<PacketReceiveDelegate>
            (packetReceiveAddress.Offset + (int)process.MainModule!.BaseAddress);
        var receiveWrapper = receiveFunction.GetWrapper();

        var binding = new NetworkBinding
        (
            bindingManager,
            (nuint)(networkObjectAddress.Offset + (int)process.MainModule!.BaseAddress + 0x01),
            sendWrapper,
            receiveWrapper
            (nuint)(networkObjectAddress.Offset + (int)process.MainModule!.BaseAddress + 0x01)
        );

        if (options.HookSend)
        var sendHookResult = bindingManager.CreateHookFromPattern<PacketSendDelegate>
            ("NetworkBinding.SendPacket", binding.SendPacketDetour, options.PacketSendHook);
        if (!sendHookResult.IsDefined(out var sendHook))
        {
            binding._sendHook = sendFunction
                .Hook(binding.SendPacketDetour);
            binding._originalSend = binding._sendHook.OriginalFunction;
            return Result<NetworkBinding>.FromError(sendHookResult);
        }

        if (options.HookReceive)
        var receiveHookResult = bindingManager.CreateHookFromPattern<PacketReceiveDelegate>
            ("NetworkBinding.ReceivePacket", binding.ReceivePacketDetour, options.PacketReceiveHook);
        if (!receiveHookResult.IsDefined(out var receiveHook))
        {
            binding._receiveHook = receiveFunction
                .Hook(binding.ReceivePacketDetour);
            binding._originalReceive = binding._receiveHook.OriginalFunction;
            return Result<NetworkBinding>.FromError(receiveHookResult);
        }

        binding._sendHook?.Activate();
        binding._receiveHook?.Activate();
        binding._sendHook = sendHook;
        binding._receiveHook = receiveHook;
        return binding;
    }

    private readonly NosBindingManager _bindingManager;
    private readonly nuint _networkManagerAddress;
    private IHook<PacketSendDelegate>? _sendHook;
    private IHook<PacketReceiveDelegate>? _receiveHook;
    private PacketSendDelegate _originalSend;
    private PacketReceiveDelegate _originalReceive;
    private IHook<PacketSendDelegate> _sendHook = null!;
    private IHook<PacketReceiveDelegate> _receiveHook = null!;

    private NetworkBinding
    (
        NosBindingManager bindingManager,
        nuint networkManagerAddress,
        PacketSendDelegate originalSend,
        PacketReceiveDelegate originalReceive
        nuint networkManagerAddress
    )
    {
        _bindingManager = bindingManager;
        _networkManagerAddress = networkManagerAddress;
        _originalSend = originalSend;
        _originalReceive = originalReceive;
    }

    /// <summary>


@@ 146,7 118,7 @@ public class NetworkBinding
        try
        {
            using var nostaleString = NostaleStringA.Create(_bindingManager.Memory, packet);
            _originalSend(GetManagerAddress(false), nostaleString.Get());
            _sendHook.OriginalFunction(GetManagerAddress(false), nostaleString.Get());
        }
        catch (Exception e)
        {


@@ 166,7 138,7 @@ public class NetworkBinding
        try
        {
            using var nostaleString = NostaleStringA.Create(_bindingManager.Memory, packet);
            _originalReceive(GetManagerAddress(true), nostaleString.Get());
            _receiveHook.OriginalFunction(GetManagerAddress(true), nostaleString.Get());
        }
        catch (Exception e)
        {


@@ 207,14 179,14 @@ public class NetworkBinding
        var packet = Marshal.PtrToStringAnsi((IntPtr)packetString);
        if (packet is null)
        { // ?
            _originalSend(packetObject, packetString);
            _sendHook.OriginalFunction(packetObject, packetString);
        }
        else
        {
            var result = PacketSend?.Invoke(packet);
            if (result ?? true)
            {
                _originalSend(packetObject, packetString);
                _sendHook.OriginalFunction(packetObject, packetString);
            }
        }
    }


@@ 226,7 198,7 @@ public class NetworkBinding
        var packet = Marshal.PtrToStringAnsi((IntPtr)packetString);
        if (packet is null)
        { // ?
            _originalReceive(packetObject, packetString);
            _receiveHook.OriginalFunction(packetObject, packetString);
        }
        else
        {


@@ 240,7 212,7 @@ public class NetworkBinding
                // TODO FIX THIS correctly
                if (_receivedCancel || !packet.StartsWith("cancel"))
                {
                    _originalReceive(packetObject, packetString);
                    _receiveHook.OriginalFunction(packetObject, packetString);
                }
                else
                {

M src/Core/NosSmooth.LocalBinding/Objects/PetManagerBinding.cs => src/Core/NosSmooth.LocalBinding/Objects/PetManagerBinding.cs +1 -2
@@ 35,8 35,7 @@ public class PetManagerBinding
        (
            "PetManagerBinding.PetWalk",
            petManager.PetWalkDetour,
            options.PetWalkPattern,
            enableHook: options.HookPetWalk
            options.PetWalkHook
        );

        if (!hookResult.IsSuccess)

M src/Core/NosSmooth.LocalBinding/Objects/PlayerManagerBinding.cs => src/Core/NosSmooth.LocalBinding/Objects/PlayerManagerBinding.cs +26 -69
@@ 61,96 61,53 @@ public class PlayerManagerBinding
    /// <returns>A network binding or an error.</returns>
    public static Result<PlayerManagerBinding> Create(NosBindingManager bindingManager, PlayerManager playerManager, CharacterBindingOptions options)
    {
        var process = Process.GetCurrentProcess();

        var walkFunctionAddress = bindingManager.Scanner.FindPattern(options.WalkFunctionPattern);
        if (!walkFunctionAddress.Found)
        {
            return new BindingNotFoundError(options.WalkFunctionPattern, "CharacterBinding.Walk");
        }

        var followEntityAddress = bindingManager.Scanner.FindPattern(options.FollowEntityPattern);
        if (!followEntityAddress.Found)
        {
            return new BindingNotFoundError(options.FollowEntityPattern, "CharacterBinding.FollowEntity");
        }

        var unfollowEntityAddress = bindingManager.Scanner.FindPattern(options.UnfollowEntityPattern);
        if (!unfollowEntityAddress.Found)
        {
            return new BindingNotFoundError(options.UnfollowEntityPattern, "CharacterBinding.UnfollowEntity");
        }

        var walkFunction = bindingManager.Hooks.CreateFunction<WalkDelegate>
            (walkFunctionAddress.Offset + (int)process.MainModule!.BaseAddress);
        var walkWrapper = walkFunction.GetWrapper();

        var followEntityFunction = bindingManager.Hooks.CreateFunction<FollowEntityDelegate>
            (followEntityAddress.Offset + (int)process.MainModule!.BaseAddress);
        var followEntityWrapper = followEntityFunction.GetWrapper();

        var unfollowEntityFunction = bindingManager.Hooks.CreateFunction<UnfollowEntityDelegate>
            (unfollowEntityAddress.Offset + (int)process.MainModule!.BaseAddress);
        var unfollowEntityWrapper = unfollowEntityFunction.GetWrapper();

        var binding = new PlayerManagerBinding
        (
            bindingManager,
            playerManager,
            walkWrapper,
            followEntityWrapper,
            unfollowEntityWrapper
            playerManager
        );

        if (options.HookWalk)
        var walkHookResult = bindingManager.CreateHookFromPattern<WalkDelegate>
            ("CharacterBinding.Walk", binding.WalkDetour, options.WalkHook);
        if (!walkHookResult.IsDefined(out var walkHook))
        {
            binding._walkHook = walkFunction
                .Hook(binding.WalkDetour);
            binding._originalWalk = binding._walkHook.OriginalFunction;
            return Result<PlayerManagerBinding>.FromError(walkHookResult);
        }

        if (options.HookFollowEntity)
        var entityFollowHookResult = bindingManager.CreateHookFromPattern<FollowEntityDelegate>
            ("CharacterBinding.EntityFollow", binding.FollowEntityDetour, options.EntityFollowHook);
        if (!entityFollowHookResult.IsDefined(out var entityFollowHook))
        {
            binding._followHook = followEntityFunction.Hook(binding.FollowEntityDetour);
            binding._originalFollowEntity = binding._followHook.OriginalFunction;
            return Result<PlayerManagerBinding>.FromError(entityFollowHookResult);
        }

        if (options.HookUnfollowEntity)
        var entityUnfollowHookResult = bindingManager.CreateHookFromPattern<UnfollowEntityDelegate>
            ("CharacterBinding.EntityUnfollow", binding.UnfollowEntityDetour, options.EntityUnfollowHook);
        if (!entityUnfollowHookResult.IsDefined(out var entityUnfollowHook))
        {
            binding._unfollowHook = unfollowEntityFunction.Hook(binding.UnfollowEntityDetour);
            binding._originalUnfollowEntity = binding._unfollowHook.OriginalFunction;
            return Result<PlayerManagerBinding>.FromError(entityUnfollowHookResult);
        }

        binding._walkHook?.Activate();
        binding._followHook?.Activate();
        binding._unfollowHook?.Activate();
        binding._walkHook = walkHook;
        binding._followHook = entityFollowHook;
        binding._unfollowHook = entityUnfollowHook;
        return binding;
    }

    private readonly NosBindingManager _bindingManager;

    private IHook<WalkDelegate>? _walkHook;
    private IHook<FollowEntityDelegate>? _followHook;
    private IHook<UnfollowEntityDelegate>? _unfollowHook;

    private FollowEntityDelegate _originalFollowEntity;
    private UnfollowEntityDelegate _originalUnfollowEntity;
    private WalkDelegate _originalWalk;
    private IHook<WalkDelegate> _walkHook = null!;
    private IHook<FollowEntityDelegate> _followHook = null!;
    private IHook<UnfollowEntityDelegate> _unfollowHook = null!;

    private PlayerManagerBinding
    (
        NosBindingManager bindingManager,
        PlayerManager playerManager,
        WalkDelegate originalWalk,
        FollowEntityDelegate originalFollowEntity,
        UnfollowEntityDelegate originalUnfollowEntity
        PlayerManager playerManager
    )
    {
        PlayerManager = playerManager;
        _bindingManager = bindingManager;
        _originalWalk = originalWalk;
        _originalFollowEntity = originalFollowEntity;
        _originalUnfollowEntity = originalUnfollowEntity;
    }

    /// <summary>


@@ 195,7 152,7 @@ public class PlayerManagerBinding
        int param = ((ushort)y << 16) | (ushort)x;
        try
        {
            return _originalWalk(PlayerManager.Address, param);
            return _walkHook.OriginalFunction(PlayerManager.Address, param);
        }
        catch (Exception e)
        {


@@ 208,7 165,7 @@ public class PlayerManagerBinding
        var result = WalkCall?.Invoke((ushort)(position & 0xFFFF), (ushort)((position >> 16) & 0xFFFF));
        if (result ?? true)
        {
            return _originalWalk(characterObject, position, unknown0, unknown1);
            return _walkHook.OriginalFunction(characterObject, position, unknown0, unknown1);
        }

        return false;


@@ 231,7 188,7 @@ public class PlayerManagerBinding
    {
        try
        {
            _originalFollowEntity(PlayerManager.Address, entityAddress);
            _followHook.OriginalFunction(PlayerManager.Address, entityAddress);
        }
        catch (Exception e)
        {


@@ 249,7 206,7 @@ public class PlayerManagerBinding
    {
        try
        {
            _originalUnfollowEntity(PlayerManager.Address);
            _unfollowHook.OriginalFunction(PlayerManager.Address);
        }
        catch (Exception e)
        {


@@ 270,7 227,7 @@ public class PlayerManagerBinding
        var result = FollowEntityCall?.Invoke(new MapBaseObj(_bindingManager.Memory, entityPtr));
        if (result ?? true)
        {
            return _originalFollowEntity(playerManagerPtr, entityPtr, unknown1, unknown2);
            return _followHook.OriginalFunction(playerManagerPtr, entityPtr, unknown1, unknown2);
        }

        return false;


@@ 281,7 238,7 @@ public class PlayerManagerBinding
        var result = FollowEntityCall?.Invoke(null);
        if (result ?? true)
        {
            _originalUnfollowEntity(playerManagerPtr, unknown);
            _unfollowHook.OriginalFunction(playerManagerPtr, unknown);
        }
    }
}
\ No newline at end of file

M src/Core/NosSmooth.LocalBinding/Objects/UnitManagerBinding.cs => src/Core/NosSmooth.LocalBinding/Objects/UnitManagerBinding.cs +29 -25
@@ 31,7 31,7 @@ public class UnitManagerBinding
        FunctionAttribute.StackCleanup.Callee,
        new[] { FunctionAttribute.Register.ebx, FunctionAttribute.Register.esi, FunctionAttribute.Register.edi, FunctionAttribute.Register.ebp }
    )]
    private delegate int FocusEntityDelegate(nuint unitManagerPtr, nuint entityPtr);
    private delegate nuint FocusEntityDelegate(nuint unitManagerPtr, nuint entityPtr);

    /// <summary>
    /// Create the scene manager binding.


@@ 50,31 50,21 @@ public class UnitManagerBinding
            return new BindingNotFoundError(bindingOptions.UnitManagerPattern, "UnitManagerBinding.UnitManager");
        }

        var focusEntityAddress = bindingManager.Scanner.FindPattern(bindingOptions.FocusEntityPattern);
        if (!focusEntityAddress.Found)
        {
            return new BindingNotFoundError(bindingOptions.FocusEntityPattern, "UnitManagerBinding.FocusEntity");
        }

        var focusEntityFunction = bindingManager.Hooks.CreateFunction<FocusEntityDelegate>
            (focusEntityAddress.Offset + (int)process.MainModule!.BaseAddress + 0x04);
        var focusEntityWrapper = focusEntityFunction.GetWrapper();

        var binding = new UnitManagerBinding
        (
            bindingManager,
            (int)process.MainModule!.BaseAddress + unitManagerStaticAddress.Offset,
            bindingOptions.UnitManagerOffsets,
            focusEntityWrapper
            bindingOptions.UnitManagerOffsets
        );

        if (bindingOptions.HookFocusEntity)
        var entityFocusHookResult = bindingManager.CreateHookFromPattern<FocusEntityDelegate>
            ("UnitManager.EntityFocus", binding.FocusEntityDetour, bindingOptions.EntityFocusHook);
        if (!entityFocusHookResult.IsDefined(out var entityFocusHook))
        {
            binding._focusHook = focusEntityFunction.Hook(binding.FocusEntityDetour);
            binding._originalFocusEntity = binding._focusHook.OriginalFunction;
            return Result<UnitManagerBinding>.FromError(entityFocusHookResult);
        }

        binding._focusHook?.Activate();
        binding._focusHook = entityFocusHook;
        return binding;
    }



@@ 82,19 72,16 @@ public class UnitManagerBinding
    private readonly int[] _unitManagerOffsets;

    private readonly NosBindingManager _bindingManager;
    private FocusEntityDelegate _originalFocusEntity;

    private IHook<FocusEntityDelegate>? _focusHook;
    private IHook<FocusEntityDelegate> _focusHook = null!;

    private UnitManagerBinding
    (
        NosBindingManager bindingManager,
        int staticUnitManagerAddress,
        int[] unitManagerOffsets,
        FocusEntityDelegate originalFocusEntity
        int[] unitManagerOffsets
    )
    {
        _originalFocusEntity = originalFocusEntity;
        _bindingManager = bindingManager;
        _staticUnitManagerAddress = staticUnitManagerAddress;
        _unitManagerOffsets = unitManagerOffsets;


@@ 123,6 110,22 @@ public class UnitManagerBinding
        => FocusEntity(entity?.Address ?? nuint.Zero);

    /// <summary>
    /// Disable all UnitManager hooks.
    /// </summary>
    public void DisableHooks()
    {
        _focusHook.Disable();
    }

    /// <summary>
    /// Enable all UnitManager hooks.
    /// </summary>
    public void EnableHooks()
    {
        _focusHook.Enable();
    }

    /// <summary>
    /// Focus the entity.
    /// </summary>
    /// <param name="entityAddress">The entity address.</param>


@@ 131,7 134,7 @@ public class UnitManagerBinding
    {
        try
        {
            _originalFocusEntity(Address, entityAddress);
            _focusHook.OriginalFunction(Address, entityAddress);
        }
        catch (Exception e)
        {


@@ 141,7 144,7 @@ public class UnitManagerBinding
        return Result.FromSuccess();
    }

    private int FocusEntityDetour(nuint unitManagerPtr, nuint entityId)
    private nuint FocusEntityDetour(nuint unitManagerPtr, nuint entityId)
    {
        MapBaseObj? obj = null;
        if (entityId != nuint.Zero)


@@ 150,9 153,10 @@ public class UnitManagerBinding
        }

        var result = EntityFocus?.Invoke(obj);

        if (result ?? true)
        {
            return _originalFocusEntity(unitManagerPtr, entityId);
            return _focusHook.OriginalFunction(unitManagerPtr, entityId);
        }

        return 0;

M src/Core/NosSmooth.LocalBinding/Options/CharacterBindingOptions.cs => src/Core/NosSmooth.LocalBinding/Options/CharacterBindingOptions.cs +8 -23
@@ 14,34 14,19 @@ namespace NosSmooth.LocalBinding.Options;
public class CharacterBindingOptions
{
    /// <summary>
    /// Gets or sets whether to hook the walk function.
    /// Gets or sets the configuration for player walk function hook.
    /// </summary>
    public bool HookWalk { get; set; } = true;
    public HookOptions WalkHook { get; set; } = new HookOptions(false, "55 8B EC 83 C4 EC 53 56 57 66 89 4D FA", 0);

    /// <summary>
    /// Gets or sets the pattern to find the walk function at.
    /// Gets or sets the configuration for entity follow function hook.
    /// </summary>
    public string WalkFunctionPattern { get; set; } = "55 8B EC 83 C4 EC 53 56 57 66 89 4D FA";
    public HookOptions EntityFollowHook { get; set; }
        = new HookOptions(false, "55 8B EC 51 53 56 57 88 4D FF 8B F2 8B F8", 0);

    /// <summary>
    /// Gets or sets the pattern to find the follow entity method at.
    /// Gets or sets the configuration for entity unfollow function hook.
    /// </summary>
    public string FollowEntityPattern { get; set; }
        = "55 8B EC 51 53 56 57 88 4D FF 8B F2 8B F8";

    /// <summary>
    /// Gets or sets the pattern to find the unfollow entity method at.
    /// </summary>
    public string UnfollowEntityPattern { get; set; }
        = "80 78 14 00 74 1A";

    /// <summary>
    /// Gets or sets whether to hook the follow entity function.
    /// </summary>
    public bool HookFollowEntity { get; set; } = true;

    /// <summary>
    /// Gets or sets whether to hook the unfollow entity function.
    /// </summary>
    public bool HookUnfollowEntity { get; set; } = true;
    public HookOptions EntityUnfollowHook { get; set; }
        = new HookOptions(false, "80 78 14 00 74 1A", 0);
}
\ No newline at end of file

M src/Core/NosSmooth.LocalBinding/Options/NetworkBindingOptions.cs => src/Core/NosSmooth.LocalBinding/Options/NetworkBindingOptions.cs +6 -14
@@ 14,14 14,16 @@ namespace NosSmooth.LocalBinding.Options;
public class NetworkBindingOptions
{
    /// <summary>
    /// Gets or sets whether to hook the send packet function.
    /// Gets or sets the configuration for packet receive function hook.
    /// </summary>
    public bool HookSend { get; set; } = true;
    public HookOptions PacketReceiveHook { get; set; }
        = new HookOptions(true, "55 8B EC 83 C4 ?? 53 56 57 33 C9 89 4D ?? 89 4D ?? 89 55 ?? 8B D8 8B 45 ??", 0);

    /// <summary>
    /// Gets or sets whether to hook the receive packet function.
    /// Gets or sets the configuration for packet send function hook.
    /// </summary>
    public bool HookReceive { get; set; } = true;
    public HookOptions PacketSendHook { get; set; }
        = new HookOptions(true, "53 56 8B F2 8B D8 EB 04", 0);

    /// <summary>
    /// Gets or sets the pattern to find the network object at.


@@ 31,14 33,4 @@ public class NetworkBindingOptions
    /// </remarks>
    public string NetworkObjectPattern { get; set; }
        = "A1 ?? ?? ?? ?? 8B 00 BA ?? ?? ?? ?? E8 ?? ?? ?? ?? E9 ?? ?? ?? ?? A1 ?? ?? ?? ?? 8B 00 8B 40 40";

    /// <summary>
    /// Gets or sets the pattern to find the send packet function at.
    /// </summary>
    public string SendFunctionPattern { get; set; } = "53 56 8B F2 8B D8 EB 04";

    /// <summary>
    /// Gets or sets the pattern to find the receive function at.
    /// </summary>
    public string ReceiveFunctionPattern { get; set; } = "55 8B EC 83 C4 ?? 53 56 57 33 C9 89 4D ?? 89 4D ?? 89 55 ?? 8B D8 8B 45 ??";
}
\ No newline at end of file

M src/Core/NosSmooth.LocalBinding/Options/PetManagerBindingOptions.cs => src/Core/NosSmooth.LocalBinding/Options/PetManagerBindingOptions.cs +3 -8
@@ 14,13 14,8 @@ namespace NosSmooth.LocalBinding.Options;
public class PetManagerBindingOptions
{
    /// <summary>
    /// Gets or sets the pattern of a pet walk function.
    /// Gets or sets the configuration for pet walk function hook.
    /// </summary>
    public string PetWalkPattern { get; set; }
        = "55 8b ec 83 c4 e4 53 56 57 8b f9 89 55 fc 8b d8 c6 45 fb 00";

    /// <summary>
    /// Gets or sets whether to hook the pet walk function.
    /// </summary>
    public bool HookPetWalk { get; set; } = true;
    public HookOptions PetWalkHook { get; set; }
        = new HookOptions(false, "55 8b ec 83 c4 e4 53 56 57 8b f9 89 55 fc 8b d8 c6 45 fb 00", 0);
}
\ No newline at end of file

M src/Core/NosSmooth.LocalBinding/Options/UnitManagerBindingOptions.cs => src/Core/NosSmooth.LocalBinding/Options/UnitManagerBindingOptions.cs +3 -8
@@ 26,13 26,8 @@ public class UnitManagerBindingOptions
        = { 1, 0 };

    /// <summary>
    /// Gets or sets the pattern to find the focus entity method at.
    /// Gets or sets the configuration for entity focus function hook.
    /// </summary>
    public string FocusEntityPattern { get; set; }
        = "73 00 00 00 55 8b ec b9 05 00 00 00";

    /// <summary>
    /// Gets or sets whether to hook the Focus entity function.
    /// </summary>
    public bool HookFocusEntity { get; set; } = true;
    public HookOptions EntityFocusHook { get; set; }
        = new HookOptions(false, "73 00 00 00 55 8b ec b9 05 00 00 00", 4);
}
\ No newline at end of file

M src/Samples/LowLevel/WalkCommands/Startup.cs => src/Samples/LowLevel/WalkCommands/Startup.cs +9 -0
@@ 13,6 13,7 @@ using NosSmooth.Data.NOSFiles;
using NosSmooth.Data.NOSFiles.Extensions;
using NosSmooth.Extensions.Pathfinding.Extensions;
using NosSmooth.LocalBinding;
using NosSmooth.LocalBinding.Extensions;
using NosSmooth.LocalClient;
using NosSmooth.LocalClient.Extensions;
using NosSmooth.Packets.Extensions;


@@ 31,6 32,14 @@ public class Startup
    {
        var collection = new ServiceCollection()
            .AddLocalClient()

            // hook pet and player walk to
            // recognize user action's and
            // disable walking in case user
            // decides to walk.
            .ConfigureHooks(h => h
                .HookPetWalk()
                .HookPlayerWalk())
            .AddNostaleDataFiles()
            .AddNostalePathfinding()
            .AddScoped<Commands.WalkCommands>()

Do not follow this link