~ruther/NosSmooth.Local

25299b8f7815d3f72b3486f636b67031a68a788c — Rutherther 2 years ago 311bc03 + 1499c7b
Merge pull request #8 from Rutherther/feat/shared-binding

Add possibility to share bindings, packets, client with multiple dlls
83 files changed, 3132 insertions(+), 1579 deletions(-)

M NosSmooth.Local.sln
A README.md
M src/Core/NosSmooth.LocalBinding/Extensions/ServiceCollectionExtensions.cs
R src/Core/NosSmooth.LocalBinding/{HookOptions.cs => Hooks/HookOptions.cs}
R src/Core/NosSmooth.LocalBinding/{HookOptionsBuilder.cs => Hooks/HookOptionsBuilder.cs}
R src/Core/NosSmooth.LocalBinding/{HooksConfigBuilder.cs => Hooks/HooksConfigBuilder.cs}
A src/Core/NosSmooth.LocalBinding/Hooks/IEntityFocusHook.cs
A src/Core/NosSmooth.LocalBinding/Hooks/IEntityFollowHook.cs
A src/Core/NosSmooth.LocalBinding/Hooks/IEntityUnfollowHook.cs
A src/Core/NosSmooth.LocalBinding/Hooks/IHookManager.cs
A src/Core/NosSmooth.LocalBinding/Hooks/INostaleHook.cs
A src/Core/NosSmooth.LocalBinding/Hooks/IPacketReceiveHook.cs
A src/Core/NosSmooth.LocalBinding/Hooks/IPacketSendHook.cs
A src/Core/NosSmooth.LocalBinding/Hooks/IPeriodicHook.cs
A src/Core/NosSmooth.LocalBinding/Hooks/IPetWalkHook.cs
A src/Core/NosSmooth.LocalBinding/Hooks/IPlayerWalkHook.cs
A src/Core/NosSmooth.LocalBinding/Hooks/Implementations/CancelableNostaleHook.cs
A src/Core/NosSmooth.LocalBinding/Hooks/Implementations/EntityFocusHook.cs
A src/Core/NosSmooth.LocalBinding/Hooks/Implementations/EntityFollowHook.cs
A src/Core/NosSmooth.LocalBinding/Hooks/Implementations/EntityUnfollowHook.cs
A src/Core/NosSmooth.LocalBinding/Hooks/Implementations/HookManager.cs
R src/Core/NosSmooth.LocalBinding/{NosAsmHook.cs => Hooks/Implementations/NosAsmHook.cs}
A src/Core/NosSmooth.LocalBinding/Hooks/Implementations/PacketReceiveHook.cs
A src/Core/NosSmooth.LocalBinding/Hooks/Implementations/PacketSendHook.cs
A src/Core/NosSmooth.LocalBinding/Hooks/Implementations/PeriodicHook.cs
A src/Core/NosSmooth.LocalBinding/Hooks/Implementations/PetWalkHook.cs
A src/Core/NosSmooth.LocalBinding/Hooks/Implementations/PlayerWalkHook.cs
M src/Core/NosSmooth.LocalBinding/IsExternalInit.cs
M src/Core/NosSmooth.LocalBinding/NosBindingManager.cs
M src/Core/NosSmooth.LocalBinding/NosBrowserManager.cs
M src/Core/NosSmooth.LocalBinding/NosSmooth.LocalBinding.csproj
M src/Core/NosSmooth.LocalBinding/NosThreadSynchronizer.cs
D src/Core/NosSmooth.LocalBinding/Objects/NetworkBinding.cs
D src/Core/NosSmooth.LocalBinding/Objects/PeriodicBinding.cs
D src/Core/NosSmooth.LocalBinding/Objects/PetManagerBinding.cs
D src/Core/NosSmooth.LocalBinding/Objects/PlayerManagerBinding.cs
D src/Core/NosSmooth.LocalBinding/Objects/UnitManagerBinding.cs
D src/Core/NosSmooth.LocalBinding/Options/CharacterBindingOptions.cs
A src/Core/NosSmooth.LocalBinding/Options/HookManagerOptions.cs
R src/Core/NosSmooth.LocalBinding/Options/{NetworkBindingOptions.cs => NetworkManagerOptions.cs}
D src/Core/NosSmooth.LocalBinding/Options/PetManagerBindingOptions.cs
R src/Core/NosSmooth.LocalBinding/Options/{UnitManagerBindingOptions.cs => UnitManagerOptions.cs}
A src/Core/NosSmooth.LocalBinding/Structs/MapBaseList.cs
M src/Core/NosSmooth.LocalBinding/Structs/MapBaseObj.cs
A src/Core/NosSmooth.LocalBinding/Structs/NetworkManager.cs
M src/Core/NosSmooth.LocalBinding/Structs/NostaleList.cs
M src/Core/NosSmooth.LocalBinding/Structs/NostaleObject.cs
M src/Core/NosSmooth.LocalBinding/Structs/PetManagerList.cs
M src/Core/NosSmooth.LocalBinding/Structs/SceneManager.cs
A src/Core/NosSmooth.LocalBinding/Structs/UnitManager.cs
M src/Core/NosSmooth.LocalClient/CommandHandlers/Attack/AttackCommandHandler.cs
M src/Core/NosSmooth.LocalClient/CommandHandlers/Walk/PetWalkCommandHandler.cs
M src/Core/NosSmooth.LocalClient/CommandHandlers/Walk/PlayerWalkCommandHandler.cs
M src/Core/NosSmooth.LocalClient/NostaleLocalClient.cs
M src/Core/NosSmooth.LocalClient/UserActionDetector.cs
R src/{Core/NosSmooth.LocalBinding/Options/PeriodicBindingOptions.cs => Extensions/NosSmooth.Extensions.SharedBinding/EventArgs/HookStateEventArgs.cs}
A src/Extensions/NosSmooth.Extensions.SharedBinding/Extensions/ServiceCollectionExtensions.cs
A src/Extensions/NosSmooth.Extensions.SharedBinding/Hooks/SharedHookManager.cs
A src/Extensions/NosSmooth.Extensions.SharedBinding/Hooks/SingleHook.cs
A src/Extensions/NosSmooth.Extensions.SharedBinding/Hooks/SingleHookManager.cs
A src/Extensions/NosSmooth.Extensions.SharedBinding/Hooks/Specific/EntityFocusHook.cs
A src/Extensions/NosSmooth.Extensions.SharedBinding/Hooks/Specific/EntityFollowHook.cs
A src/Extensions/NosSmooth.Extensions.SharedBinding/Hooks/Specific/EntityUnfollowHook.cs
A src/Extensions/NosSmooth.Extensions.SharedBinding/Hooks/Specific/PacketReceiveHook.cs
A src/Extensions/NosSmooth.Extensions.SharedBinding/Hooks/Specific/PacketSendHook.cs
A src/Extensions/NosSmooth.Extensions.SharedBinding/Hooks/Specific/PeriodicHook.cs
A src/Extensions/NosSmooth.Extensions.SharedBinding/Hooks/Specific/PetWalkHook.cs
A src/Extensions/NosSmooth.Extensions.SharedBinding/Hooks/Specific/PlayerWalkHook.cs
A src/Extensions/NosSmooth.Extensions.SharedBinding/NosSmooth.Extensions.SharedBinding.csproj
A src/Extensions/NosSmooth.Extensions.SharedBinding/SharedManager.cs
A src/Extensions/NosSmooth.Extensions.SharedBinding/SharedOptions.cs
M src/Samples/External/ExternalBrowser/Program.cs
M src/Samples/HighLevel/SimplePiiBot/Commands/EntityCommands.cs
M src/Samples/HighLevel/SimplePiiBot/DllMain.cs
M src/Samples/HighLevel/SimplePiiBot/SimplePiiBot.csproj
M src/Samples/LowLevel/InterceptNameChanger/InterceptNameChanger.csproj
M src/Samples/LowLevel/InterceptNameChanger/NameChangeInterceptor.cs
M src/Samples/LowLevel/InterceptNameChanger/NameChanger.cs
D src/Samples/LowLevel/InterceptNameChanger/Properties/AssemblyInfo.cs
M src/Samples/LowLevel/SimpleChat/SimpleChat.cs
M src/Samples/LowLevel/SimpleChat/SimpleChat.csproj
M src/Samples/LowLevel/WalkCommands/Startup.cs
M src/Samples/LowLevel/WalkCommands/WalkCommands.csproj
M NosSmooth.Local.sln => NosSmooth.Local.sln +7 -0
@@ 43,6 43,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "HighLevel", "HighLevel", "{
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SimplePiiBot", "src\Samples\HighLevel\SimplePiiBot\SimplePiiBot.csproj", "{A93B5A9F-68FC-4A17-AFAC-DAB22B617A50}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NosSmooth.Extensions.SharedBinding", "src\Extensions\NosSmooth.Extensions.SharedBinding\NosSmooth.Extensions.SharedBinding.csproj", "{59A3D740-88E1-49F4-8AED-859A866F3D7E}"
EndProject
Global
	GlobalSection(SolutionConfigurationPlatforms) = preSolution
		Debug|Any CPU = Debug|Any CPU


@@ 63,6 65,7 @@ Global
		{7190312B-CFEE-49D3-8DAB-542C31071E87} = {D19F45EC-8E59-4F7C-A4C4-D0BD11C8C32B}
		{8B99B1BD-0738-40C1-9961-4DBC7AF3E8CE} = {004AF2D8-8D02-4BDF-BD18-752C54D65F1C}
		{A93B5A9F-68FC-4A17-AFAC-DAB22B617A50} = {8B99B1BD-0738-40C1-9961-4DBC7AF3E8CE}
		{59A3D740-88E1-49F4-8AED-859A866F3D7E} = {987E4CD9-DBFE-40FF-90F7-AAD060CFC781}
	EndGlobalSection
	GlobalSection(ProjectConfigurationPlatforms) = postSolution
		{CA2873D8-BD0B-4583-818D-B94A3C2ABBA3}.Debug|Any CPU.ActiveCfg = Debug|Win32


@@ 109,5 112,9 @@ Global
		{A93B5A9F-68FC-4A17-AFAC-DAB22B617A50}.Debug|Any CPU.Build.0 = Debug|Any CPU
		{A93B5A9F-68FC-4A17-AFAC-DAB22B617A50}.Release|Any CPU.ActiveCfg = Release|Any CPU
		{A93B5A9F-68FC-4A17-AFAC-DAB22B617A50}.Release|Any CPU.Build.0 = Release|Any CPU
		{59A3D740-88E1-49F4-8AED-859A866F3D7E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
		{59A3D740-88E1-49F4-8AED-859A866F3D7E}.Debug|Any CPU.Build.0 = Debug|Any CPU
		{59A3D740-88E1-49F4-8AED-859A866F3D7E}.Release|Any CPU.ActiveCfg = Release|Any CPU
		{59A3D740-88E1-49F4-8AED-859A866F3D7E}.Release|Any CPU.Build.0 = Release|Any CPU
	EndGlobalSection
EndGlobal

A README.md => README.md +20 -0
@@ 0,0 1,20 @@
# NosSmooth.Local
NosSmooth is a multi-platform library for NosTale
packets, data, game state and such.
See the main repository at [NosSmooth](https://github.com/Rutherther/NosSmooth).
NosSmooth.Local contains libraries for the regular NosTale client
such as memory mapping and NosSmooth client implementation for
handling the packets.

See the samples in the `src/Samples` folder.

## Features

### Bindings (hooks, memory mapping)
Hooks, options, mapping, errors

### Client
commands implementations, initialization

### Injector
copy Inject, regular injector cannot be used bcs .NET 5
\ No newline at end of file

M src/Core/NosSmooth.LocalBinding/Extensions/ServiceCollectionExtensions.cs => src/Core/NosSmooth.LocalBinding/Extensions/ServiceCollectionExtensions.cs +27 -8
@@ 5,7 5,10 @@
//  Licensed under the MIT license. See LICENSE file in the project root for full license information.

using Microsoft.Extensions.DependencyInjection;
using NosSmooth.LocalBinding.Hooks;
using NosSmooth.LocalBinding.Hooks.Implementations;
using NosSmooth.LocalBinding.Objects;
using NosSmooth.LocalBinding.Options;
using NosSmooth.LocalBinding.Structs;

namespace NosSmooth.LocalBinding.Extensions;


@@ 19,8 22,9 @@ public static class ServiceCollectionExtensions
    /// Adds bindings to Nostale objects along with <see cref="NosBindingManager"/> to initialize those.
    /// </summary>
    /// <remarks>
    /// Adds <see cref="PlayerManagerBinding"/> and <see cref="NetworkBinding"/>.
    /// You have to initialize these using <see cref="NosBindingManager"/>
    /// This adds <see cref="NosBindingManager"/>, <see cref="NosBrowserManager"/>,
    /// <see cref="IHookManager"/> and their siblings.
    /// You have to initialize the bindings using <see cref="NosBindingManager"/>
    /// prior to requesting them from the provider, otherwise an exception
    /// will be thrown.
    /// </remarks>


@@ 32,16 36,31 @@ public static class ServiceCollectionExtensions
            .AddSingleton<NosBindingManager>()
            .AddSingleton<NosBrowserManager>()
            .AddSingleton<NosThreadSynchronizer>()
            .AddSingleton<IHookManager, HookManager>()
            .AddSingleton<IPacketReceiveHook>(p => p.GetRequiredService<IHookManager>().PacketReceive)
            .AddSingleton<IPacketSendHook>(p => p.GetRequiredService<IHookManager>().PacketSend)
            .AddSingleton<IEntityFollowHook>(p => p.GetRequiredService<IHookManager>().EntityFollow)
            .AddSingleton<IEntityUnfollowHook>(p => p.GetRequiredService<IHookManager>().EntityUnfollow)
            .AddSingleton<IEntityFocusHook>(p => p.GetRequiredService<IHookManager>().EntityFocus)
            .AddSingleton<IPlayerWalkHook>(p => p.GetRequiredService<IHookManager>().PlayerWalk)
            .AddSingleton<IPetWalkHook>(p => p.GetRequiredService<IHookManager>().PetWalk)
            .AddSingleton<IPeriodicHook>(p => p.GetRequiredService<IHookManager>().Periodic)
            .AddSingleton(p => p.GetRequiredService<NosBrowserManager>().PlayerManager)
            .AddSingleton(p => p.GetRequiredService<NosBrowserManager>().SceneManager)
            .AddSingleton(p => p.GetRequiredService<NosBrowserManager>().PetManagerList)
            .AddSingleton(p => p.GetRequiredService<NosBrowserManager>().SceneManager)
            .AddSingleton(p => p.GetRequiredService<NosBrowserManager>().PetManagerList)
            .AddSingleton(p => p.GetRequiredService<NosBindingManager>().PlayerManager)
            .AddSingleton(p => p.GetRequiredService<NosBindingManager>().Periodic)
            .AddSingleton(p => p.GetRequiredService<NosBindingManager>().PetManager)
            .AddSingleton(p => p.GetRequiredService<NosBindingManager>().UnitManager)
            .AddSingleton(p => p.GetRequiredService<NosBindingManager>().Network);
            .AddSingleton(p => p.GetRequiredService<NosBrowserManager>().PlayerManager)
            .AddSingleton(p => p.GetRequiredService<NosBrowserManager>().NetworkManager)
            .AddSingleton(p => p.GetRequiredService<NosBrowserManager>().UnitManager)
            .AddSingleton(p => p.GetRequiredService<IHookManager>().PacketReceive)
            .AddSingleton(p => p.GetRequiredService<IHookManager>().PacketSend)
            .AddSingleton(p => p.GetRequiredService<IHookManager>().PlayerWalk)
            .AddSingleton(p => p.GetRequiredService<IHookManager>().PetWalk)
            .AddSingleton(p => p.GetRequiredService<IHookManager>().EntityFocus)
            .AddSingleton(p => p.GetRequiredService<IHookManager>().EntityFollow)
            .AddSingleton(p => p.GetRequiredService<IHookManager>().EntityUnfollow)
            .AddSingleton(p => p.GetRequiredService<IHookManager>().Periodic);
    }

    /// <summary>


@@ 52,7 71,7 @@ public static class ServiceCollectionExtensions
    /// <returns>The collection.</returns>
    public static IServiceCollection ConfigureHooks(this IServiceCollection serviceCollection, Action<HooksConfigBuilder> configure)
    {
        var builder = new HooksConfigBuilder();
        var builder = new HooksConfigBuilder(new HookManagerOptions());
        configure(builder);
        builder.Apply(serviceCollection);
        return serviceCollection;

R src/Core/NosSmooth.LocalBinding/HookOptions.cs => src/Core/NosSmooth.LocalBinding/Hooks/HookOptions.cs +24 -2
@@ 4,12 4,34 @@
//  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;
namespace NosSmooth.LocalBinding.Hooks;

/// <summary>
/// A configuration for hooking a function.
/// </summary>
/// <param name="Name">The name of the hook.</param>
/// <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
public record HookOptions
(
    string Name,
    bool Hook,
    string MemoryPattern,
    int Offset
);

/// <summary>
/// A configuration for hooking a function.
/// </summary>
/// <param name="Name">The name of the hook.</param>
/// <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<TFunction>
(
    string Name,
    bool Hook,
    string MemoryPattern,
    int Offset
) : HookOptions(Name, Hook, MemoryPattern, Offset);
\ No newline at end of file

R src/Core/NosSmooth.LocalBinding/HookOptionsBuilder.cs => src/Core/NosSmooth.LocalBinding/Hooks/HookOptionsBuilder.cs +12 -22
@@ 4,48 4,38 @@
//  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;
namespace NosSmooth.LocalBinding.Hooks;

/// <summary>
/// Builder for <see cref="HookOptions"/>.
/// </summary>
public class HookOptionsBuilder
/// <typeparam name="T">The type of hook options.</typeparam>
public class HookOptionsBuilder<T>
{
    private string _name;
    private bool _hook;
    private string _pattern;
    private int _offset;

    /// <summary>
    /// Initializes a new instance of the <see cref="HookOptionsBuilder"/> class.
    /// Initializes a new instance of the <see cref="HookOptionsBuilder{T}"/> class.
    /// </summary>
    /// <param name="options">The options.</param>
    internal HookOptionsBuilder(HookOptions options)
    internal HookOptionsBuilder(HookOptions<T> options)
    {
        _name = options.Name;
        _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)
    public HookOptionsBuilder<T> Hook(bool hook = true)
    {
        _hook = hook;
        return this;


@@ 57,7 47,7 @@ public class HookOptionsBuilder
    /// </summary>
    /// <param name="pattern">The memory pattern.</param>
    /// <returns>This builder.</returns>
    public HookOptionsBuilder MemoryPattern(string pattern)
    public HookOptionsBuilder<T> MemoryPattern(string pattern)
    {
        _pattern = pattern;
        return this;


@@ 68,7 58,7 @@ public class HookOptionsBuilder
    /// </summary>
    /// <param name="offset">The offset.</param>
    /// <returns>This builder.</returns>
    public HookOptionsBuilder Offset(int offset)
    public HookOptionsBuilder<T> Offset(int offset)
    {
        _offset = offset;
        return this;


@@ 78,6 68,6 @@ public class HookOptionsBuilder
    /// Create hook options from this builder.
    /// </summary>
    /// <returns>The options.</returns>
    internal HookOptions Build()
        => new HookOptions(_hook, _pattern, _offset);
    internal HookOptions<T> Build()
        => new HookOptions<T>(_name, _hook, _pattern, _offset);
}
\ No newline at end of file

R src/Core/NosSmooth.LocalBinding/HooksConfigBuilder.cs => src/Core/NosSmooth.LocalBinding/Hooks/HooksConfigBuilder.cs +37 -56
@@ 7,7 7,7 @@
using Microsoft.Extensions.DependencyInjection;
using NosSmooth.LocalBinding.Options;

namespace NosSmooth.LocalBinding;
namespace NosSmooth.LocalBinding.Hooks;

/// <summary>
/// Provides user-friendly builder for configuring


@@ 27,28 27,29 @@ namespace NosSmooth.LocalBinding;
/// </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;
    private readonly HookOptionsBuilder _periodicHook;
    private readonly HookOptionsBuilder<IPacketSendHook> _packetSendHook;
    private readonly HookOptionsBuilder<IPacketReceiveHook> _packetReceiveHook;
    private readonly HookOptionsBuilder<IPlayerWalkHook> _playerWalkHook;
    private readonly HookOptionsBuilder<IPetWalkHook> _petWalkHook;
    private readonly HookOptionsBuilder<IEntityFocusHook> _entityFocusHook;
    private readonly HookOptionsBuilder<IEntityFollowHook> _entityFollowHook;
    private readonly HookOptionsBuilder<IEntityUnfollowHook> _entityUnfollowHook;
    private readonly HookOptionsBuilder<IPeriodicHook> _periodicHook;

    /// <summary>
    /// Initializes a new instance of the <see cref="HooksConfigBuilder"/> class.
    /// </summary>
    internal HooksConfigBuilder()
    /// <param name="options">The default options to build from.</param>
    internal HooksConfigBuilder(HookManagerOptions options)
    {
        _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);
        _periodicHook = new HookOptionsBuilder(new PeriodicBindingOptions().PeriodicHook);
        _packetSendHook = new HookOptionsBuilder<IPacketSendHook>(options.PacketSendHook);
        _packetReceiveHook = new HookOptionsBuilder<IPacketReceiveHook>(options.PacketReceiveHook);
        _playerWalkHook = new HookOptionsBuilder<IPlayerWalkHook>(options.PlayerWalkHook);
        _petWalkHook = new HookOptionsBuilder<IPetWalkHook>(options.PetWalkHook);
        _entityFocusHook = new HookOptionsBuilder<IEntityFocusHook>(options.EntityFocusHook);
        _entityFollowHook = new HookOptionsBuilder<IEntityFollowHook>(options.EntityFollowHook);
        _entityUnfollowHook = new HookOptionsBuilder<IEntityUnfollowHook>(options.EntityUnfollowHook);
        _periodicHook = new HookOptionsBuilder<IPeriodicHook>(options.PeriodicHook);
    }

    /// <summary>


@@ 103,7 104,7 @@ public class HooksConfigBuilder
    /// </summary>
    /// <param name="configure">The configuring action.</param>
    /// <returns>This builder.</returns>
    public HooksConfigBuilder HookPeriodic(Action<HookOptionsBuilder>? configure = default)
    public HooksConfigBuilder HookPeriodic(Action<HookOptionsBuilder<IPeriodicHook>>? configure = default)
    {
        if (configure is not null)
        {


@@ 125,7 126,7 @@ public class HooksConfigBuilder
    /// </summary>
    /// <param name="configure">The configuring action.</param>
    /// <returns>This builder.</returns>
    public HooksConfigBuilder HookPacketSend(Action<HookOptionsBuilder>? configure = default)
    public HooksConfigBuilder HookPacketSend(Action<HookOptionsBuilder<IPacketSendHook>>? configure = default)
    {
        if (configure is not null)
        {


@@ 147,7 148,7 @@ public class HooksConfigBuilder
    /// </summary>
    /// <param name="configure">The configuring action.</param>
    /// <returns>This builder.</returns>
    public HooksConfigBuilder HookPacketReceive(Action<HookOptionsBuilder>? configure = default)
    public HooksConfigBuilder HookPacketReceive(Action<HookOptionsBuilder<IPacketReceiveHook>>? configure = default)
    {
        if (configure is not null)
        {


@@ 169,7 170,7 @@ public class HooksConfigBuilder
    /// </summary>
    /// <param name="configure">The configuring action.</param>
    /// <returns>This builder.</returns>
    public HooksConfigBuilder HookPlayerWalk(Action<HookOptionsBuilder>? configure = default)
    public HooksConfigBuilder HookPlayerWalk(Action<HookOptionsBuilder<IPlayerWalkHook>>? configure = default)
    {
        if (configure is not null)
        {


@@ 191,7 192,7 @@ public class HooksConfigBuilder
    /// </summary>
    /// <param name="configure">The configuring action.</param>
    /// <returns>This builder.</returns>
    public HooksConfigBuilder HookPetWalk(Action<HookOptionsBuilder>? configure = default)
    public HooksConfigBuilder HookPetWalk(Action<HookOptionsBuilder<IPetWalkHook>>? configure = default)
    {
        if (configure is not null)
        {


@@ 213,7 214,7 @@ public class HooksConfigBuilder
    /// </summary>
    /// <param name="configure">The configuring action.</param>
    /// <returns>This builder.</returns>
    public HooksConfigBuilder HookEntityFocus(Action<HookOptionsBuilder>? configure = default)
    public HooksConfigBuilder HookEntityFocus(Action<HookOptionsBuilder<IEntityFocusHook>>? configure = default)
    {
        if (configure is not null)
        {


@@ 235,7 236,7 @@ public class HooksConfigBuilder
    /// </summary>
    /// <param name="configure">The configuring action.</param>
    /// <returns>This builder.</returns>
    public HooksConfigBuilder HookEntityFollow(Action<HookOptionsBuilder>? configure = default)
    public HooksConfigBuilder HookEntityFollow(Action<HookOptionsBuilder<IEntityFollowHook>>? configure = default)
    {
        if (configure is not null)
        {


@@ 257,7 258,7 @@ public class HooksConfigBuilder
    /// </summary>
    /// <param name="configure">The configuring action.</param>
    /// <returns>This builder.</returns>
    public HooksConfigBuilder HookEntityUnfollow(Action<HookOptionsBuilder>? configure = default)
    public HooksConfigBuilder HookEntityUnfollow(Action<HookOptionsBuilder<IEntityUnfollowHook>>? configure = default)
    {
        if (configure is not null)
        {


@@ 278,38 279,18 @@ public class HooksConfigBuilder
    /// <param name="serviceCollection">The service collection.</param>
    internal void Apply(IServiceCollection serviceCollection)
    {
        serviceCollection.Configure<CharacterBindingOptions>
        serviceCollection.Configure<HookManagerOptions>
        (
            characterOptions =>
            o =>
            {
                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();
                o.PeriodicHook = _periodicHook.Build();
                o.EntityFocusHook = _entityFocusHook.Build();
                o.EntityFollowHook = _entityFollowHook.Build();
                o.EntityUnfollowHook = _entityUnfollowHook.Build();
                o.PacketSendHook = _packetSendHook.Build();
                o.PacketReceiveHook = _packetReceiveHook.Build();
                o.PetWalkHook = _petWalkHook.Build();
                o.PlayerWalkHook = _playerWalkHook.Build();
            }
        );
    }

A src/Core/NosSmooth.LocalBinding/Hooks/IEntityFocusHook.cs => src/Core/NosSmooth.LocalBinding/Hooks/IEntityFocusHook.cs +42 -0
@@ 0,0 1,42 @@
//
//  IEntityFocusHook.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 NosSmooth.LocalBinding.EventArgs;
using NosSmooth.LocalBinding.Structs;
using Reloaded.Hooks.Definitions.X86;

namespace NosSmooth.LocalBinding.Hooks;

/// <summary>
/// A hook of UnitManager.EntityFocus.
/// </summary>
public interface IEntityFocusHook : INostaleHook<IEntityFocusHook.EntityFocusDelegate,
    IEntityFocusHook.EntityFocusWrapperDelegate, EntityEventArgs>
{
    /// <summary>
    /// NosTale entity focus function to hook.
    /// </summary>
    /// <param name="unitManagerPtr">The unit manager object.</param>
    /// <param name="entityPtr">The entity object.</param>
    /// <returns>1 to proceed to NosTale function, 0 to block the call.</returns>
    [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 }
    )]
    public delegate nuint EntityFocusDelegate(nuint unitManagerPtr, nuint entityPtr);

    /// <summary>
    /// Entity focus function.
    /// </summary>
    /// <remarks>
    /// In case entity is null, unfocus any entity.
    /// </remarks>
    /// <param name="entity">The entity object.</param>
    public delegate void EntityFocusWrapperDelegate(MapBaseObj? entity);
}
\ No newline at end of file

A src/Core/NosSmooth.LocalBinding/Hooks/IEntityFollowHook.cs => src/Core/NosSmooth.LocalBinding/Hooks/IEntityFollowHook.cs +50 -0
@@ 0,0 1,50 @@
//
//  IEntityFollowHook.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 NosSmooth.LocalBinding.EventArgs;
using NosSmooth.LocalBinding.Structs;
using Reloaded.Hooks.Definitions.X86;

namespace NosSmooth.LocalBinding.Hooks;

/// <summary>
/// A hook of CharacterManager.EntityFollow.
/// </summary>
public interface IEntityFollowHook : INostaleHook<IEntityFollowHook.EntityFollowDelegate,
    IEntityFollowHook.EntityFollowWrapperDelegate, EntityEventArgs>
{
    /// <summary>
    /// NosTale entity follow function to hook.
    /// </summary>
    /// <param name="playerManagerPtr">The player manager object.</param>
    /// <param name="entityPtr">The entity object.</param>
    /// <param name="unknown1">Unknown 1. TODO.</param>
    /// <param name="unknown2">Unknown 2. TODO.</param>
    /// <returns>1 to proceed to NosTale function, 0 to block the call.</returns>
    [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 }
    )]
    public delegate nuint EntityFollowDelegate
    (
        nuint playerManagerPtr,
        nuint entityPtr,
        int unknown1 = 0,
        int unknown2 = 1
    );

    /// <summary>
    /// Entity follow function.
    /// </summary>
    /// <remarks>
    /// Does not support unfollow. For unfollow, use <see cref="IEntityUnfollowHook"/>.
    /// </remarks>
    /// <param name="entity">The entity object.</param>
    public delegate void EntityFollowWrapperDelegate(MapBaseObj entity);
}
\ No newline at end of file

A src/Core/NosSmooth.LocalBinding/Hooks/IEntityUnfollowHook.cs => src/Core/NosSmooth.LocalBinding/Hooks/IEntityUnfollowHook.cs +37 -0
@@ 0,0 1,37 @@
//
//  IEntityUnfollowHook.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 NosSmooth.LocalBinding.EventArgs;
using Reloaded.Hooks.Definitions.X86;

namespace NosSmooth.LocalBinding.Hooks;

/// <summary>
/// A hook of CharacterManager.EntityFollow.
/// </summary>
public interface IEntityUnfollowHook : INostaleHook<IEntityUnfollowHook.EntityUnfollowDelegate,
    IEntityUnfollowHook.EntityUnfollowWrapperDelegate, EntityEventArgs>
{
    /// <summary>
    /// NosTale entity follow function to hook.
    /// </summary>
    /// <param name="playerManagerPtr">The player manager object.</param>
    /// <param name="unknown">Unknown. TODO.</param>
    /// <returns>1 to proceed to NosTale function, 0 to block the call.</returns>
    [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 }
    )]
    public delegate nuint EntityUnfollowDelegate(nuint playerManagerPtr, int unknown = 0);

    /// <summary>
    /// Entity unfollow function.
    /// </summary>
    public delegate void EntityUnfollowWrapperDelegate();
}
\ No newline at end of file

A src/Core/NosSmooth.LocalBinding/Hooks/IHookManager.cs => src/Core/NosSmooth.LocalBinding/Hooks/IHookManager.cs +142 -0
@@ 0,0 1,142 @@
//
//  IHookManager.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 Remora.Results;

namespace NosSmooth.LocalBinding.Hooks;

/// <summary>
/// A manager holding all NosTale hooks with actions to execute on all of them.
/// </summary>
public interface IHookManager
{
    /// <summary>
    /// A name of packet send hook.
    /// </summary>
    public const string PacketSendName = "NetworkManager.PacketSend";

    /// <summary>
    /// A name of packet receive hook.
    /// </summary>
    public const string PacketReceiveName = "NetworkManager.PacketReceive";

    /// <summary>
    /// A name of character walk hook.
    /// </summary>
    public const string CharacterWalkName = "CharacterManager.Walk";

    /// <summary>
    /// A name of pet walk hook.
    /// </summary>
    public const string PetWalkName = "PetManager.Walk";

    /// <summary>
    /// A name of entity follow hook.
    /// </summary>
    public const string EntityFollowName = "CharacterManager.EntityFollow";

    /// <summary>
    /// A name of entity unfollow hook.
    /// </summary>
    public const string EntityUnfollowName = "CharacterManager.EntityUnfollow";

    /// <summary>
    /// A name of entity focus hook.
    /// </summary>
    public const string EntityFocusName = "UnitManager.EntityFocus";

    /// <summary>
    /// A name of periodic hook.
    /// </summary>
    public const string PeriodicName = "Periodic";

    /// <summary>
    /// Gets the packet send hook.
    /// </summary>
    public IPacketSendHook PacketSend { get; }

    /// <summary>
    /// Gets the packet receive hook.
    /// </summary>
    public IPacketReceiveHook PacketReceive { get; }

    /// <summary>
    /// Gets the player walk hook.
    /// </summary>
    public IPlayerWalkHook PlayerWalk { get; }

    /// <summary>
    /// Gets the entity follow hook.
    /// </summary>
    public IEntityFollowHook EntityFollow { get; }

    /// <summary>
    /// Gets the entity unfollow hook.
    /// </summary>
    public IEntityUnfollowHook EntityUnfollow { get; }

    /// <summary>
    /// Gets the player walk hook.
    /// </summary>
    public IPetWalkHook PetWalk { get; }

    /// <summary>
    /// Gets the entity focus hook.
    /// </summary>
    public IEntityFocusHook EntityFocus { get; }

    /// <summary>
    /// Gets the periodic function hook.
    /// </summary>
    /// <remarks>
    /// May be any function that is called periodically.
    /// This is used for synchronizing using <see cref="NosThreadSynchronizer"/>.
    /// </remarks>
    public IPeriodicHook Periodic { get; }

    /// <summary>
    /// Gets all of the hooks.
    /// </summary>
    public IReadOnlyList<INostaleHook> Hooks { get; }

    /// <summary>
    /// Initializes all hooks.
    /// </summary>
    /// <param name="bindingManager">The binding manager.</param>
    /// <param name="browserManager">The browser manager.</param>
    /// <returns>A result that may or may not have failed.</returns>
    public IResult Initialize(NosBindingManager bindingManager, NosBrowserManager browserManager);

    /// <summary>
    /// Enable hooks from the given list.
    /// </summary>
    /// <remarks>
    /// Use constants from <see cref="IHookManager"/>,
    /// such as IHookManager.PacketSendName.
    /// </remarks>
    /// <param name="names">The hooks to enable.</param>
    public void Enable(IEnumerable<string> names);

    /// <summary>
    /// Disable hooks from the given list.
    /// </summary>
    /// <remarks>
    /// Use constants from <see cref="IHookManager"/>,
    /// such as IHookManager.PacketSendName.
    /// </remarks>
    /// <param name="names">The hooks to disable.</param>
    public void Disable(IEnumerable<string> names);

    /// <summary>
    /// Disable all hooks.
    /// </summary>
    public void DisableAll();

    /// <summary>
    /// Enable all hooks.
    /// </summary>
    public void EnableAll();
}
\ No newline at end of file

A src/Core/NosSmooth.LocalBinding/Hooks/INostaleHook.cs => src/Core/NosSmooth.LocalBinding/Hooks/INostaleHook.cs +74 -0
@@ 0,0 1,74 @@
//
//  INostaleHook.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 Remora.Results;

namespace NosSmooth.LocalBinding.Hooks;

/// <summary>
/// A hook of a NosTale function.
/// </summary>
/// <typeparam name="TFunction">The function delegate.</typeparam>
/// <typeparam name="TWrapperFunction">A wrapper function that abstracts the call to original function. May get the neccessary object to call the function and accept only relevant arguments.</typeparam>
/// <typeparam name="TEventArgs">The event args used in case of a call.</typeparam>
public interface INostaleHook<TFunction, TWrapperFunction, TEventArgs> : INostaleHook
    where TFunction : Delegate
    where TWrapperFunction : Delegate
    where TEventArgs : System.EventArgs
{
    /// <summary>
    /// Gets the wrapper function delegate.
    /// </summary>
    public TWrapperFunction WrapperFunction { get; }

    /// <summary>
    /// Gets the original function delegate.
    /// </summary>
    public TFunction OriginalFunction { get; }

    /// <summary>
    /// An event fired in case the function is called from NosTale.
    /// </summary>
    public event EventHandler<TEventArgs>? Called;
}

/// <summary>
/// A hook of a NosTale function.
/// </summary>
public interface INostaleHook
{
    /// <summary>
    /// Gets the name of the function.
    /// </summary>
    /// <remarks>
    /// Usually denoted as Type.Method,
    /// for example PlayerManager.Walk.
    /// </remarks>
    public string Name { get; }

    /// <summary>
    /// Gets whether the hook is currently enabled.
    /// </summary>
    public bool IsEnabled { get; }

    /// <summary>
    /// Enable the hook.
    /// </summary>
    /// <remarks>
    /// If it already is enabled, does nothing.
    /// </remarks>
    /// <returns>A result that may or may not have succeeded.</returns>
    public Result Enable();

    /// <summary>
    /// Disable the hook.
    /// </summary>
    /// <remarks>
    /// If it already is disabled, does nothing.
    /// </remarks>
    /// <returns>A result that may or may not have succeeded.</returns>
    public Result Disable();
}
\ No newline at end of file

A src/Core/NosSmooth.LocalBinding/Hooks/IPacketReceiveHook.cs => src/Core/NosSmooth.LocalBinding/Hooks/IPacketReceiveHook.cs +39 -0
@@ 0,0 1,39 @@
//
//  IPacketReceiveHook.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 NosSmooth.LocalBinding.EventArgs;
using NosSmooth.LocalBinding.Objects;
using Reloaded.Hooks.Definitions.X86;

namespace NosSmooth.LocalBinding.Hooks;

/// <summary>
/// A hook of NetworkManager.PacketSend.
/// </summary>
public interface IPacketReceiveHook : INostaleHook<IPacketReceiveHook.PacketReceiveDelegate,
    IPacketReceiveHook.PacketReceiveWrapperDelegate, PacketEventArgs>
{
    /// <summary>
    /// NosTale packet send function to hook.
    /// </summary>
    /// <param name="packetObject">The packet object.</param>
    /// <param name="packetString">Pointer to <see cref="NostaleStringA"/>.Get() object.</param>
    /// <returns>1 to proceed to NosTale function, 0 to block the call.</returns>
    [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 }
    )]
    public delegate nuint PacketReceiveDelegate(nuint packetObject, nuint packetString);

    /// <summary>
    /// Packet send function.
    /// </summary>
    /// <param name="packetString">The string to send.</param>
    public delegate void PacketReceiveWrapperDelegate(string packetString);
}
\ No newline at end of file

A src/Core/NosSmooth.LocalBinding/Hooks/IPacketSendHook.cs => src/Core/NosSmooth.LocalBinding/Hooks/IPacketSendHook.cs +38 -0
@@ 0,0 1,38 @@
//
//  IPacketSendHook.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 NosSmooth.LocalBinding.EventArgs;
using NosSmooth.LocalBinding.Objects;
using Reloaded.Hooks.Definitions.X86;

namespace NosSmooth.LocalBinding.Hooks;

/// <summary>
/// A hook of NetworkManager.PacketSend.
/// </summary>
public interface IPacketSendHook : INostaleHook<IPacketSendHook.PacketSendDelegate, IPacketSendHook.PacketSendWrapperDelegate, PacketEventArgs>
{
    /// <summary>
    /// NosTale packet send function to hook.
    /// </summary>
    /// <param name="packetObject">The packet object.</param>
    /// <param name="packetString">Pointer to <see cref="NostaleStringA"/>.Get() object.</param>
    /// <returns>1 to proceed to NosTale function, 0 to block the call.</returns>
    [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 }
    )]
    public delegate nuint PacketSendDelegate(nuint packetObject, nuint packetString);

    /// <summary>
    /// Packet send function.
    /// </summary>
    /// <param name="packetString">The string to send.</param>
    public delegate void PacketSendWrapperDelegate(string packetString);
}
\ No newline at end of file

A src/Core/NosSmooth.LocalBinding/Hooks/IPeriodicHook.cs => src/Core/NosSmooth.LocalBinding/Hooks/IPeriodicHook.cs +29 -0
@@ 0,0 1,29 @@
//
//  IPeriodicHook.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 Reloaded.Hooks.Definitions.X86;

namespace NosSmooth.LocalBinding.Hooks;

/// <summary>
/// A hook of a periodic function,
/// preferably called every frame.
/// </summary>
public interface
    IPeriodicHook : INostaleHook<IPeriodicHook.PeriodicDelegate, IPeriodicHook.PeriodicDelegate, System.EventArgs>
{
    /// <summary>
    /// NosTale periodic function to hook.
    /// </summary>
    [Function
    (
        new FunctionAttribute.Register[0],
        FunctionAttribute.Register.eax,
        FunctionAttribute.StackCleanup.Callee,
        new[] { FunctionAttribute.Register.ebx, FunctionAttribute.Register.esi, FunctionAttribute.Register.edi, FunctionAttribute.Register.ebp }
    )]
    public delegate void PeriodicDelegate();
}
\ No newline at end of file

A src/Core/NosSmooth.LocalBinding/Hooks/IPetWalkHook.cs => src/Core/NosSmooth.LocalBinding/Hooks/IPetWalkHook.cs +43 -0
@@ 0,0 1,43 @@
//
//  IPetWalkHook.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 NosSmooth.LocalBinding.EventArgs;
using NosSmooth.LocalBinding.Structs;
using Reloaded.Hooks.Definitions.X86;

namespace NosSmooth.LocalBinding.Hooks;

/// <summary>
/// A hook of PetManager.Walk.
/// </summary>
public interface IPetWalkHook : INostaleHook<IPetWalkHook.PetWalkDelegate, IPetWalkHook.PetWalkWrapperDelegate, PetWalkEventArgs>
{
    /// <summary>
    /// NosTale walk function to hook.
    /// </summary>
    /// <param name="petManagerPtr">The pointer to a pet manager object.</param>
    /// <param name="position">The position to walk to. First 4 bits are x (most significant), next 4 bits are y.</param>
    /// <param name="unknown0">Unknown 1. TODO.</param>
    /// <param name="unknown1">Unknown 2. TODO.</param>
    /// <returns>1 to proceed to NosTale function, 0 to block the call.</returns>
    [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 }
    )]
    public delegate nuint PetWalkDelegate(nuint petManagerPtr, int position, short unknown0 = 0, int unknown1 = 1);

    /// <summary>
    /// Pet walk function.
    /// </summary>
    /// <param name="manager">The pet manager.</param>
    /// <param name="x">The x coordinate to walk to.</param>
    /// <param name="y">The y coordinate to walk to.</param>
    /// <returns>A bool signaling whether the operation was successful.</returns>
    public delegate bool PetWalkWrapperDelegate(PetManager manager, ushort x, ushort y);
}
\ No newline at end of file

A src/Core/NosSmooth.LocalBinding/Hooks/IPlayerWalkHook.cs => src/Core/NosSmooth.LocalBinding/Hooks/IPlayerWalkHook.cs +41 -0
@@ 0,0 1,41 @@
//
//  IPlayerWalkHook.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 NosSmooth.LocalBinding.EventArgs;
using Reloaded.Hooks.Definitions.X86;

namespace NosSmooth.LocalBinding.Hooks;

/// <summary>
/// A hook of CharacterManager.Walk.
/// </summary>
public interface IPlayerWalkHook : INostaleHook<IPlayerWalkHook.WalkDelegate, IPlayerWalkHook.WalkWrapperDelegate, WalkEventArgs>
{
    /// <summary>
    /// NosTale walk function to hook.
    /// </summary>
    /// <param name="playerManagerPtr">The pointer to a player manager object.</param>
    /// <param name="position">The position to walk to. First 4 bits are x (most significant), next 4 bits are y.</param>
    /// <param name="unknown0">Unknown 1. TODO.</param>
    /// <param name="unknown1">Unknown 2. TODO.</param>
    /// <returns>1 to proceed to NosTale function, 0 to block the call.</returns>
    [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 }
    )]
    public delegate nuint WalkDelegate(nuint playerManagerPtr, int position, short unknown0 = 0, int unknown1 = 1);

    /// <summary>
    /// Player walk function.
    /// </summary>
    /// <param name="x">The x coordinate to go to.</param>
    /// <param name="y">The y coordinate to go to.</param>
    /// <returns>A bool signaling whether the operation was successful.</returns>
    public delegate bool WalkWrapperDelegate(ushort x, ushort y);
}

A src/Core/NosSmooth.LocalBinding/Hooks/Implementations/CancelableNostaleHook.cs => src/Core/NosSmooth.LocalBinding/Hooks/Implementations/CancelableNostaleHook.cs +135 -0
@@ 0,0 1,135 @@
//
//  CancelableNostaleHook.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.ComponentModel;
using System.Diagnostics;
using NosSmooth.LocalBinding.Extensions;
using Remora.Results;

namespace NosSmooth.LocalBinding.Hooks.Implementations;

/// <summary>
/// A hook of a NosTale function
/// that may be cancelled (not propagated to NosTale) when fired.
/// </summary>
/// <typeparam name="TFunction">The function delegate.</typeparam>
/// <typeparam name="TWrapperFunction">A wrapper function that abstracts the call to original function. May get the neccessary object to call the function and accept only relevant arguments.</typeparam>
/// <typeparam name="TEventArgs">The event args used in case of a call.</typeparam>
public abstract class CancelableNostaleHook<TFunction, TWrapperFunction, TEventArgs> : INostaleHook<TFunction, TWrapperFunction, TEventArgs>
    where TFunction : Delegate
    where TWrapperFunction : Delegate
    where TEventArgs : CancelEventArgs
{
    /// <summary>
    /// Creates the hook instance.
    /// </summary>
    /// <param name="bindingManager">The binding manager to create the hook.</param>
    /// <param name="new">Create new hook object.</param>
    /// <param name="detour">The function that obtains detour.</param>
    /// <param name="options">The options.</param>
    /// <typeparam name="T">The hook type.</typeparam>
    /// <returns>The hook, or failed result.</returns>
    protected static Result<T> CreateHook<T>(NosBindingManager bindingManager, Func<T> @new, Func<T, TFunction> detour, HookOptions options)
        where T : CancelableNostaleHook<TFunction, TWrapperFunction, TEventArgs>
    {
        var nosHook = @new();

        var hookResult = bindingManager.CreateCustomAsmHookFromPattern<TFunction>
            (nosHook.Name, detour(nosHook), options);
        if (!hookResult.IsDefined(out var hook))
        {
            return Result<T>.FromError(hookResult);
        }

        nosHook.Hook = hook;
        return nosHook;
    }

    private TFunction? _originalFunction;

    /// <summary>
    /// Gets the hook.
    /// </summary>
    protected NosAsmHook<TFunction> Hook { get; set; } = null!;

    /// <summary>
    /// Set to true at start of WrapWithCalling, set to false at end of WrapWithCalling.
    /// </summary>
    protected bool CallingFromNosSmooth { get; set; }

    /// <inheritdoc />
    public bool IsEnabled => Hook.Hook.IsEnabled;

    /// <inheritdoc />
    public abstract TWrapperFunction WrapperFunction { get; }

    /// <inheritdoc/>
    public TFunction OriginalFunction
    {
        get
        {
            if (_originalFunction is null)
            {
                _originalFunction = WrapWithCalling(Hook.OriginalFunction.GetWrapper());
            }

            return _originalFunction;
        }
    }

    /// <inheritdoc />
    public event EventHandler<TEventArgs>? Called;

    /// <inheritdoc />
    public abstract string Name { get; }

    /// <inheritdoc />
    public Result Enable()
    {
        Hook.Hook.EnableOrActivate();
        return Result.FromSuccess();
    }

    /// <inheritdoc />
    public Result Disable()
    {
        Hook.Hook.Disable();
        return Result.FromSuccess();
    }

    /// <summary>
    /// Wrap the target function with setting CallingFromNosSmooth to true.
    /// </summary>
    /// <example>
    /// protected MyFun WrapWithCalling(MyFun fun) {
    ///     return (a, b) => {
    ///       CallingFromNosSmooth = true;
    ///       var res = fun(a, b);
    ///       CallingFromNosSmooth = false;
    ///       return res;
    ///     }
    /// }.
    /// </example>
    /// <param name="function">The function to wrap.</param>
    /// <returns>The wrapped function.</returns>
    protected abstract TFunction WrapWithCalling(TFunction function);

    /// <summary>
    /// Calls the event, returns whether to proceed.
    /// </summary>
    /// <param name="args">The event arguments.</param>
    /// <returns>Whether to proceed.</returns>
    protected nuint HandleCall(TEventArgs args)
    {
        if (CallingFromNosSmooth)
        { // this is a call from NosSmooth, do not invoke the event.
            return 1;
        }

        Called?.Invoke(this, args);
        return args.Cancel ? 0 : (nuint)1;
    }
}
\ No newline at end of file

A src/Core/NosSmooth.LocalBinding/Hooks/Implementations/EntityFocusHook.cs => src/Core/NosSmooth.LocalBinding/Hooks/Implementations/EntityFocusHook.cs +75 -0
@@ 0,0 1,75 @@
//
//  EntityFocusHook.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 NosSmooth.LocalBinding.EventArgs;
using NosSmooth.LocalBinding.Structs;
using Reloaded.Memory.Sources;
using Remora.Results;

namespace NosSmooth.LocalBinding.Hooks.Implementations;

/// <summary>
/// A hook of NetworkManager.EntityFocus.
/// </summary>
internal class EntityFocusHook : CancelableNostaleHook<IEntityFocusHook.EntityFocusDelegate,
    IEntityFocusHook.EntityFocusWrapperDelegate, EntityEventArgs>, IEntityFocusHook
{
    /// <summary>
    /// Create the packet send hook.
    /// </summary>
    /// <param name="bindingManager">The binding manager.</param>
    /// <param name="browserManager">The browser manager.</param>
    /// <param name="options">The options for the binding.</param>
    /// <returns>A packet send hook or an error.</returns>
    public static Result<EntityFocusHook> Create
        (NosBindingManager bindingManager, NosBrowserManager browserManager, HookOptions<IEntityFocusHook> options)
    {
        var hook = CreateHook
        (
            bindingManager,
            () => new EntityFocusHook(browserManager.UnitManager),
            hook => hook.Detour,
            options
        );

        return hook;
    }

    private readonly UnitManager _unitManager;

    private EntityFocusHook(UnitManager unitManager)
    {
        _unitManager = unitManager;
    }

    /// <inheritdoc />
    public override string Name => IHookManager.EntityFocusName;

    /// <inheritdoc />
    public override IEntityFocusHook.EntityFocusWrapperDelegate WrapperFunction => (entity) => OriginalFunction
        (_unitManager.Address, entity?.Address ?? 0);

    /// <inheritdoc />
    protected override IEntityFocusHook.EntityFocusDelegate WrapWithCalling(IEntityFocusHook.EntityFocusDelegate function)
        => (unitManagerPtr, entityPtr) =>
            {
                CallingFromNosSmooth = true;
                var res = function(unitManagerPtr, entityPtr);
                CallingFromNosSmooth = false;
                return res;
            };

    private nuint Detour
    (
        nuint unitManagerPtr,
        nuint entityPtr
    )
    {
        var entity = new MapBaseObj(_unitManager.Memory, entityPtr);
        var entityArgs = new EntityEventArgs(entity);
        return HandleCall(entityArgs);
    }
}
\ No newline at end of file

A src/Core/NosSmooth.LocalBinding/Hooks/Implementations/EntityFollowHook.cs => src/Core/NosSmooth.LocalBinding/Hooks/Implementations/EntityFollowHook.cs +76 -0
@@ 0,0 1,76 @@
//
//  EntityFollowHook.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 NosSmooth.LocalBinding.EventArgs;
using NosSmooth.LocalBinding.Structs;
using Remora.Results;

namespace NosSmooth.LocalBinding.Hooks.Implementations;

/// <summary>
/// A hook of NetworkManager.EntityFollow.
/// </summary>
internal class EntityFollowHook : CancelableNostaleHook<IEntityFollowHook.EntityFollowDelegate,
    IEntityFollowHook.EntityFollowWrapperDelegate, EntityEventArgs>, IEntityFollowHook
{
    /// <summary>
    /// Create the packet send hook.
    /// </summary>
    /// <param name="bindingManager">The binding manager.</param>
    /// <param name="browserManager">The browser manager.</param>
    /// <param name="options">The options for the binding.</param>
    /// <returns>A packet send hook or an error.</returns>
    public static Result<EntityFollowHook> Create
        (NosBindingManager bindingManager, NosBrowserManager browserManager, HookOptions<IEntityFollowHook> options)
    {
        var hook = CreateHook
        (
            bindingManager,
            () => new EntityFollowHook(browserManager.PlayerManager),
            hook => hook.Detour,
            options
        );

        return hook;
    }

    private readonly PlayerManager _playerManager;

    private EntityFollowHook(PlayerManager playerManager)
    {
        _playerManager = playerManager;
    }

    /// <inheritdoc />
    public override string Name => IHookManager.EntityFollowName;

    /// <inheritdoc />
    public override IEntityFollowHook.EntityFollowWrapperDelegate WrapperFunction => (entity) => OriginalFunction
        (_playerManager.Address, entity?.Address ?? 0);

    /// <inheritdoc />
    protected override IEntityFollowHook.EntityFollowDelegate WrapWithCalling(IEntityFollowHook.EntityFollowDelegate function)
        => (playerManagerPtr, entityPtr, un0, un1) =>
            {
                CallingFromNosSmooth = true;
                var res = function(playerManagerPtr, entityPtr, un0, un1);
                CallingFromNosSmooth = false;
                return res;
            };

    private nuint Detour
    (
        nuint playerManagerPtr,
        nuint entityPtr,
        int unknown1 = 0,
        int unknown2 = 1
    )
    {
        var entity = new MapBaseObj(_playerManager.Memory, entityPtr);
        var entityArgs = new EntityEventArgs(entity);
        return HandleCall(entityArgs);
    }
}
\ No newline at end of file

A src/Core/NosSmooth.LocalBinding/Hooks/Implementations/EntityUnfollowHook.cs => src/Core/NosSmooth.LocalBinding/Hooks/Implementations/EntityUnfollowHook.cs +70 -0
@@ 0,0 1,70 @@
//
//  EntityUnfollowHook.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 NosSmooth.LocalBinding.EventArgs;
using NosSmooth.LocalBinding.Structs;
using Remora.Results;

namespace NosSmooth.LocalBinding.Hooks.Implementations;

/// <summary>
/// A hook of NetworkManager.EntityUnfollow.
/// </summary>
internal class EntityUnfollowHook : CancelableNostaleHook<IEntityUnfollowHook.EntityUnfollowDelegate,
    IEntityUnfollowHook.EntityUnfollowWrapperDelegate, EntityEventArgs>, IEntityUnfollowHook
{
    /// <summary>
    /// Create the packet send hook.
    /// </summary>
    /// <param name="bindingManager">The binding manager.</param>
    /// <param name="browserManager">The browser manager.</param>
    /// <param name="options">The options for the binding.</param>
    /// <returns>A packet send hook or an error.</returns>
    public static Result<EntityUnfollowHook> Create
        (NosBindingManager bindingManager, NosBrowserManager browserManager, HookOptions<IEntityUnfollowHook> options)
    {
        var hook = CreateHook
        (
            bindingManager,
            () => new EntityUnfollowHook(browserManager.PlayerManager),
            hook => hook.Detour,
            options
        );

        return hook;
    }

    private readonly PlayerManager _playerManager;

    private EntityUnfollowHook(PlayerManager playerManager)
    {
        _playerManager = playerManager;
    }

    /// <inheritdoc />
    public override string Name => IHookManager.EntityUnfollowName;

    /// <inheritdoc />
    public override IEntityUnfollowHook.EntityUnfollowWrapperDelegate WrapperFunction
        => () => OriginalFunction(_playerManager.Address);

    /// <inheritdoc />
    protected override IEntityUnfollowHook.EntityUnfollowDelegate WrapWithCalling(IEntityUnfollowHook.EntityUnfollowDelegate function)
        => (playerManagerPtr, un) =>
            {
                CallingFromNosSmooth = true;
                var res = function(playerManagerPtr, un);
                CallingFromNosSmooth = false;
                return res;
            };

    private nuint Detour
    (
        nuint playerManagerPtr,
        int unknown = 0
    )
        => HandleCall(new EntityEventArgs(null));
}
\ No newline at end of file

A src/Core/NosSmooth.LocalBinding/Hooks/Implementations/HookManager.cs => src/Core/NosSmooth.LocalBinding/Hooks/Implementations/HookManager.cs +167 -0
@@ 0,0 1,167 @@
//
//  HookManager.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 Microsoft.Extensions.Options;
using NosSmooth.LocalBinding.Options;
using Remora.Results;

namespace NosSmooth.LocalBinding.Hooks.Implementations;

/// <inheritdoc />
internal class HookManager : IHookManager
{
    private readonly HookManagerOptions _options;
    private List<INostaleHook> _hooks;

    /// <summary>
    /// Initializes a new instance of the <see cref="HookManager"/> class.
    /// </summary>
    /// <param name="options">The hook manager options.</param>
    public HookManager(IOptions<HookManagerOptions> options)
    {
        _options = options.Value;
        _hooks = new List<INostaleHook>();
    }

    /// <inheritdoc/>
    public IPacketSendHook PacketSend => GetHook<IPacketSendHook>(IHookManager.PacketSendName);

    /// <inheritdoc/>
    public IPacketReceiveHook PacketReceive => GetHook<IPacketReceiveHook>(IHookManager.PacketReceiveName);

    /// <inheritdoc/>
    public IPlayerWalkHook PlayerWalk => GetHook<IPlayerWalkHook>(IHookManager.CharacterWalkName);

    /// <inheritdoc/>
    public IEntityFollowHook EntityFollow => GetHook<IEntityFollowHook>(IHookManager.EntityFollowName);

    /// <inheritdoc/>
    public IEntityUnfollowHook EntityUnfollow => GetHook<IEntityUnfollowHook>(IHookManager.EntityUnfollowName);

    /// <inheritdoc/>
    public IPetWalkHook PetWalk => GetHook<IPetWalkHook>(IHookManager.PetWalkName);

    /// <inheritdoc/>
    public IEntityFocusHook EntityFocus => GetHook<IEntityFocusHook>(IHookManager.EntityFocusName);

    /// <inheritdoc/>
    public IPeriodicHook Periodic => GetHook<IPeriodicHook>(IHookManager.PeriodicName);

    /// <inheritdoc/>
    public IReadOnlyList<INostaleHook> Hooks => _hooks.AsReadOnly();

    /// <inheritdoc/>
    public IResult Initialize(NosBindingManager bindingManager, NosBrowserManager browserManager)
    {
        if (_hooks.Count > 0)
        { // already initialized
            return Result.FromSuccess();
        }

        return HandleResults
        (
            () => PeriodicHook.Create(bindingManager, _options.PeriodicHook).Map(MapHook),
            () => EntityFocusHook.Create(bindingManager, browserManager, _options.EntityFocusHook).Map(MapHook),
            () => EntityFollowHook.Create(bindingManager, browserManager, _options.EntityFollowHook).Map(MapHook),
            () => EntityUnfollowHook.Create(bindingManager, browserManager, _options.EntityUnfollowHook).Map(MapHook),
            () => PlayerWalkHook.Create(bindingManager, browserManager, _options.PlayerWalkHook).Map(MapHook),
            () => PetWalkHook.Create(bindingManager, _options.PetWalkHook).Map(MapHook),
            () => PacketSendHook.Create(bindingManager, browserManager, _options.PacketSendHook).Map(MapHook),
            () => PacketReceiveHook.Create(bindingManager, browserManager, _options.PacketReceiveHook).Map(MapHook)
        );
    }

    private INostaleHook MapHook<T>(T original)
        where T : INostaleHook
    {
        return original;
    }

    private IResult HandleResults(params Func<Result<INostaleHook>>[] functions)
    {
        List<IResult> errorResults = new List<IResult>();
        foreach (var func in functions)
        {
            try
            {
                var result = func();
                if (result.IsSuccess)
                {
                    _hooks.Add(result.Entity);
                }
                else
                {
                    errorResults.Add(result);
                }
            }
            catch (Exception e)
            {
                errorResults.Add((Result)e);
            }

        }

        return errorResults.Count switch
        {
            0 => Result.FromSuccess(),
            1 => errorResults[0],
            _ => (Result)new AggregateError(errorResults)
        };
    }

    /// <inheritdoc/>
    public void Enable(IEnumerable<string> names)
    {
        foreach (var hook in Hooks
            .Where(x => names.Contains(x.Name)))
        {
            hook.Enable();
        }
    }

    /// <inheritdoc/>
    public void Disable(IEnumerable<string> names)
    {
        foreach (var hook in Hooks
            .Where(x => names.Contains(x.Name)))
        {
            hook.Disable();
        }
    }

    /// <inheritdoc/>
    public void DisableAll()
    {
        foreach (var hook in Hooks)
        {
            hook.Disable();
        }
    }

    /// <inheritdoc/>
    public void EnableAll()
    {
        foreach (var hook in Hooks)
        {
            hook.Enable();
        }
    }

    private T GetHook<T>(string name)
        where T : INostaleHook
    {
        var hook = _hooks.FirstOrDefault(x => x.Name == name);

        if (hook is null || hook is not T typed)
        {
            throw new InvalidOperationException
                ($"Could not load hook {name}. Did you forget to call IHookManager.Initialize?");
        }

        return typed;
    }
}
\ No newline at end of file

R src/Core/NosSmooth.LocalBinding/NosAsmHook.cs => src/Core/NosSmooth.LocalBinding/Hooks/Implementations/NosAsmHook.cs +1 -1
@@ 6,7 6,7 @@

using Reloaded.Hooks.Definitions;

namespace NosSmooth.LocalBinding;
namespace NosSmooth.LocalBinding.Hooks.Implementations;

/// <summary>
/// An assembly hook data.

A src/Core/NosSmooth.LocalBinding/Hooks/Implementations/PacketReceiveHook.cs => src/Core/NosSmooth.LocalBinding/Hooks/Implementations/PacketReceiveHook.cs +81 -0
@@ 0,0 1,81 @@
//
//  PacketReceiveHook.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.Runtime.InteropServices;
using NosSmooth.LocalBinding.EventArgs;
using NosSmooth.LocalBinding.Objects;
using NosSmooth.LocalBinding.Structs;
using Remora.Results;

namespace NosSmooth.LocalBinding.Hooks.Implementations;

/// <summary>
/// A hook of NetworkManager.PacketSend.
/// </summary>
internal class PacketReceiveHook : CancelableNostaleHook<IPacketReceiveHook.PacketReceiveDelegate,
    IPacketReceiveHook.PacketReceiveWrapperDelegate, PacketEventArgs>, IPacketReceiveHook
{
    /// <summary>
    /// Create the packet send hook.
    /// </summary>
    /// <param name="bindingManager">The binding manager.</param>
    /// <param name="browserManager">The browser manager.</param>
    /// <param name="options">The options for the binding.</param>
    /// <returns>A packet send hook or an error.</returns>
    public static Result<PacketReceiveHook> Create
        (NosBindingManager bindingManager, NosBrowserManager browserManager, HookOptions<IPacketReceiveHook> options)
    {
        var hook = CreateHook
            (
                bindingManager,
                () => new PacketReceiveHook(browserManager.NetworkManager),
                (hook) => hook.Detour,
                options
            );

        return hook;
    }

    private NetworkManager _networkManager;

    private PacketReceiveHook(NetworkManager networkManager)
    {
        _networkManager = networkManager;
    }

    /// <inheritdoc />
    public override string Name => IHookManager.PacketReceiveName;

    /// <inheritdoc />
    public override IPacketReceiveHook.PacketReceiveWrapperDelegate WrapperFunction => (packetString) =>
    {
        var packetObject = _networkManager.GetAddressForPacketReceive();
        using var nostaleString = NostaleStringA.Create(_networkManager.Memory, packetString);
        OriginalFunction(packetObject, nostaleString.Get());
    };

    /// <inheritdoc />
    protected override IPacketReceiveHook.PacketReceiveDelegate WrapWithCalling(IPacketReceiveHook.PacketReceiveDelegate function)
        => (packetObject, packetString) =>
        {
            CallingFromNosSmooth = true;
            var res = function(packetObject, packetString);
            CallingFromNosSmooth = false;
            return res;
        };

    private nuint Detour(nuint packetObject, nuint packetString)
    {
        var packet = Marshal.PtrToStringAnsi((IntPtr)packetString);
        if (packet is null)
        { // ?
            return 1;
        }

        var packetArgs = new PacketEventArgs(packet);
        return HandleCall(packetArgs);
    }
}
\ No newline at end of file

A src/Core/NosSmooth.LocalBinding/Hooks/Implementations/PacketSendHook.cs => src/Core/NosSmooth.LocalBinding/Hooks/Implementations/PacketSendHook.cs +82 -0
@@ 0,0 1,82 @@
//
//  PacketSendHook.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.Runtime.InteropServices;
using NosSmooth.LocalBinding.EventArgs;
using NosSmooth.LocalBinding.Objects;
using NosSmooth.LocalBinding.Structs;
using Reloaded.Memory.Sources;
using Remora.Results;

namespace NosSmooth.LocalBinding.Hooks.Implementations;

/// <summary>
/// A hook of NetworkManager.PacketSend.
/// </summary>
internal class PacketSendHook : CancelableNostaleHook<IPacketSendHook.PacketSendDelegate,
    IPacketSendHook.PacketSendWrapperDelegate, PacketEventArgs>, IPacketSendHook
{
    /// <summary>
    /// Create the packet send hook.
    /// </summary>
    /// <param name="bindingManager">The binding manager.</param>
    /// <param name="browserManager">The browser manager.</param>
    /// <param name="options">The options for the binding.</param>
    /// <returns>A packet send hook or an error.</returns>
    public static Result<PacketSendHook> Create
        (NosBindingManager bindingManager, NosBrowserManager browserManager, HookOptions<IPacketSendHook> options)
    {
        var hook = CreateHook
            (
                bindingManager,
                () => new PacketSendHook(browserManager.NetworkManager),
                (sendHook) => sendHook.Detour,
                options
            );

        return hook;
    }

    private PacketSendHook(NetworkManager networkManager)
    {
        _networkManager = networkManager;
    }

    private readonly NetworkManager _networkManager;

    /// <inheritdoc />
    public override string Name => IHookManager.PacketSendName;

    /// <inheritdoc />
    public override IPacketSendHook.PacketSendWrapperDelegate WrapperFunction => (packetString) =>
    {
        var packetObject = _networkManager.GetAddressForPacketSend();
        using var nostaleString = NostaleStringA.Create(_networkManager.Memory, packetString);
        OriginalFunction(packetObject, nostaleString.Get());
    };

    /// <inheritdoc />
    protected override IPacketSendHook.PacketSendDelegate WrapWithCalling(IPacketSendHook.PacketSendDelegate function)
        => (packetObject, packetString) =>
        {
            CallingFromNosSmooth = true;
            var res = function(packetObject, packetString);
            CallingFromNosSmooth = false;
            return res;
        };

    private nuint Detour(nuint packetObject, nuint packetString)
    {
        var packet = Marshal.PtrToStringAnsi((IntPtr)packetString);
        if (packet is null)
        { // ?
            return 1;
        }

        var packetArgs = new PacketEventArgs(packet);
        return HandleCall(packetArgs);
    }
}
\ No newline at end of file

A src/Core/NosSmooth.LocalBinding/Hooks/Implementations/PeriodicHook.cs => src/Core/NosSmooth.LocalBinding/Hooks/Implementations/PeriodicHook.cs +77 -0
@@ 0,0 1,77 @@
//
//  PeriodicHook.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.Extensions;
using Remora.Results;

namespace NosSmooth.LocalBinding.Hooks.Implementations;

/// <inheritdoc />
internal class PeriodicHook : IPeriodicHook
{
    /// <summary>
    /// Create the periodic hook.
    /// </summary>
    /// <param name="bindingManager">The binding manager.</param>
    /// <param name="options">The options for the binding.</param>
    /// <returns>A packet send hook or an error.</returns>
    public static Result<PeriodicHook> Create(NosBindingManager bindingManager, HookOptions<IPeriodicHook> options)
    {
        var periodicHook = new PeriodicHook();

        var hookResult = bindingManager.CreateCustomAsmHookFromPattern<IPeriodicHook.PeriodicDelegate>
            (periodicHook.Name, periodicHook.Detour, options, false);
        if (!hookResult.IsDefined(out var hook))
        {
            return Result<PeriodicHook>.FromError(hookResult);
        }

        periodicHook._hook = hook;
        return periodicHook;
    }

    private PeriodicHook()
    {
    }

    private NosAsmHook<IPeriodicHook.PeriodicDelegate> _hook = null!;

    /// <inheritdoc />
    public string Name => IHookManager.PeriodicName;

    /// <inheritdoc />
    public bool IsEnabled => _hook.Hook.IsEnabled;

    /// <inheritdoc />
    public IPeriodicHook.PeriodicDelegate WrapperFunction => OriginalFunction;

    /// <inheritdoc/>
    public IPeriodicHook.PeriodicDelegate OriginalFunction => throw new InvalidOperationException
        ("Calling NosTale periodic function from NosSmooth is not allowed.");

    /// <inheritdoc />
    public event EventHandler<System.EventArgs>? Called;

    /// <inheritdoc />
    public Result Enable()
    {
        _hook.Hook.EnableOrActivate();
        return Result.FromSuccess();
    }

    /// <inheritdoc />
    public Result Disable()
    {
        _hook.Hook.Disable();
        return Result.FromSuccess();
    }

    private void Detour()
    {
        Called?.Invoke(this, System.EventArgs.Empty);
    }
}
\ No newline at end of file

A src/Core/NosSmooth.LocalBinding/Hooks/Implementations/PetWalkHook.cs => src/Core/NosSmooth.LocalBinding/Hooks/Implementations/PetWalkHook.cs +83 -0
@@ 0,0 1,83 @@
//
//  PetWalkHook.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 NosSmooth.LocalBinding.EventArgs;
using NosSmooth.LocalBinding.Structs;
using Reloaded.Memory.Sources;
using Remora.Results;

namespace NosSmooth.LocalBinding.Hooks.Implementations;

/// <summary>
/// A hook of NetworkManager.PetWalk.
/// </summary>
internal class PetWalkHook : CancelableNostaleHook<IPetWalkHook.PetWalkDelegate,
    IPetWalkHook.PetWalkWrapperDelegate, PetWalkEventArgs>, IPetWalkHook
{
    /// <summary>
    /// Create the packet send hook.
    /// </summary>
    /// <param name="bindingManager">The binding manager.</param>
    /// <param name="options">The options for the binding.</param>
    /// <returns>A packet send hook or an error.</returns>
    public static Result<PetWalkHook> Create
        (NosBindingManager bindingManager, HookOptions<IPetWalkHook> options)
    {
        var hook = CreateHook
        (
            bindingManager,
            () => new PetWalkHook(bindingManager.Memory),
            hook => hook.Detour,
            options
        );

        return hook;
    }

    private IMemory _memory;

    private PetWalkHook(IMemory memory)
    {
        _memory = memory;
    }

    /// <inheritdoc />
    public override string Name => IHookManager.PetWalkName;

    /// <inheritdoc />
    public override IPetWalkHook.PetWalkWrapperDelegate WrapperFunction
        => (p, x, y) => OriginalFunction(p.Address, (y << 16) | x) == 1;

    /// <inheritdoc />
    protected override IPetWalkHook.PetWalkDelegate WrapWithCalling(IPetWalkHook.PetWalkDelegate function)
        =>
            (
                petManagerPtr,
                position,
                un0,
                un1
            ) =>
            {
                CallingFromNosSmooth = true;
                var res = function(petManagerPtr, position, un0, un1);
                CallingFromNosSmooth = false;
                return res;
            };

    private nuint Detour
    (
        nuint petManagerPtr,
        int position,
        short un0,
        int un1
    )
    {
        var petManager = new PetManager(_memory, petManagerPtr);
        var walkArgs = new PetWalkEventArgs
            (petManager, (ushort)(position & 0xFFFF), (ushort)((position >> 16) & 0xFFFF));
        return HandleCall(walkArgs);
    }
}
\ No newline at end of file

A src/Core/NosSmooth.LocalBinding/Hooks/Implementations/PlayerWalkHook.cs => src/Core/NosSmooth.LocalBinding/Hooks/Implementations/PlayerWalkHook.cs +84 -0
@@ 0,0 1,84 @@
//
//  PlayerWalkHook.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 NosSmooth.LocalBinding.EventArgs;
using NosSmooth.LocalBinding.Structs;
using Remora.Results;

namespace NosSmooth.LocalBinding.Hooks.Implementations;

/// <summary>
/// A hook of NetworkManager.PlayerWalk.
/// </summary>
internal class PlayerWalkHook : CancelableNostaleHook<IPlayerWalkHook.WalkDelegate,
    IPlayerWalkHook.WalkWrapperDelegate, WalkEventArgs>, IPlayerWalkHook
{
    /// <summary>
    /// Create the packet send hook.
    /// </summary>
    /// <param name="bindingManager">The binding manager.</param>
    /// <param name="browserManager">The browser manager.</param>
    /// <param name="options">The options for the binding.</param>
    /// <returns>A packet send hook or an error.</returns>
    public static Result<PlayerWalkHook> Create
        (NosBindingManager bindingManager, NosBrowserManager browserManager, HookOptions<IPlayerWalkHook> options)
    {
        var hook = CreateHook
        (
            bindingManager,
            () => new PlayerWalkHook(browserManager.PlayerManager),
            hook => hook.Detour,
            options
        );

        return hook;
    }

    private PlayerWalkHook(PlayerManager playerManager)
    {
        _playerManager = playerManager;
    }

    private PlayerManager _playerManager;

    /// <inheritdoc />
    public override string Name => IHookManager.CharacterWalkName;

    /// <inheritdoc />
    public override IPlayerWalkHook.WalkWrapperDelegate WrapperFunction => (x, y) =>
    {
        var playerManagerObject = _playerManager.Address;
        return OriginalFunction(playerManagerObject,  (y << 16) | x) == 1;
    };

    /// <inheritdoc />
    protected override IPlayerWalkHook.WalkDelegate WrapWithCalling(IPlayerWalkHook.WalkDelegate function)
        =>
            (
                playerManagerPtr,
                position,
                un0,
                un1
            ) =>
            {
                CallingFromNosSmooth = true;
                var res = function(playerManagerPtr, position, un0, un1);
                CallingFromNosSmooth = false;
                return res;
            };

    private nuint Detour
    (
        nuint playerManagerPtr,
        int position,
        short un0,
        int un1
    )
    {
        var walkArgs = new WalkEventArgs((ushort)(position & 0xFFFF), (ushort)((position >> 16) & 0xFFFF));
        return HandleCall(walkArgs);
    }
}
\ No newline at end of file

M src/Core/NosSmooth.LocalBinding/IsExternalInit.cs => src/Core/NosSmooth.LocalBinding/IsExternalInit.cs +1 -1
@@ 4,7 4,7 @@
//  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 System.Runtime.CompilerServices
namespace NosSmooth.LocalBinding
{
    /// <summary>
    /// Dummy.

M src/Core/NosSmooth.LocalBinding/NosBindingManager.cs => src/Core/NosSmooth.LocalBinding/NosBindingManager.cs +74 -341
@@ 5,13 5,18 @@
//  Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System.Diagnostics;
using System.Reflection;
using Microsoft.Extensions.Options;
using NosSmooth.LocalBinding.Errors;
using NosSmooth.LocalBinding.Hooks;
using NosSmooth.LocalBinding.Hooks.Implementations;
using NosSmooth.LocalBinding.Objects;
using NosSmooth.LocalBinding.Options;
using Reloaded.Hooks;
using Reloaded.Hooks.Definitions;
using Reloaded.Hooks.Definitions.Helpers;
using Reloaded.Hooks.Definitions.Internal;
using Reloaded.Hooks.Definitions.X86;
using Reloaded.Hooks.Tools;
using Reloaded.Hooks.X86;
using Reloaded.Memory.Sigscan;


@@ 26,46 31,24 @@ namespace NosSmooth.LocalBinding;
public class NosBindingManager : IDisposable
{
    private readonly NosBrowserManager _browserManager;
    private readonly PetManagerBindingOptions _petManagerBindingOptions;
    private readonly CharacterBindingOptions _characterBindingOptions;
    private readonly NetworkBindingOptions _networkBindingOptions;
    private readonly UnitManagerBindingOptions _unitManagerBindingOptions;
    private readonly PeriodicBindingOptions _periodicBindingOptions;

    private NetworkBinding? _networkBinding;
    private PlayerManagerBinding? _characterBinding;
    private UnitManagerBinding? _unitManagerBinding;
    private PetManagerBinding? _petManagerBinding;
    private PeriodicBinding? _periodicBinding;
    private readonly IHookManager _hookManager;

    /// <summary>
    /// Initializes a new instance of the <see cref="NosBindingManager"/> class.
    /// </summary>
    /// <param name="browserManager">The NosTale browser manager.</param>
    /// <param name="characterBindingOptions">The character binding options.</param>
    /// <param name="networkBindingOptions">The network binding options.</param>
    /// <param name="sceneManagerBindingOptions">The scene manager binding options.</param>
    /// <param name="petManagerBindingOptions">The pet manager binding options.</param>
    /// <param name="periodicBindingOptions">The periodic binding options.</param>
    /// <param name="hookManager">The hook manager.</param>
    public NosBindingManager
    (
        NosBrowserManager browserManager,
        IOptions<CharacterBindingOptions> characterBindingOptions,
        IOptions<NetworkBindingOptions> networkBindingOptions,
        IOptions<UnitManagerBindingOptions> sceneManagerBindingOptions,
        IOptions<PetManagerBindingOptions> petManagerBindingOptions,
        IOptions<PeriodicBindingOptions> periodicBindingOptions
        IHookManager hookManager
    )
    {
        _browserManager = browserManager;
        _hookManager = hookManager;
        Hooks = new ReloadedHooks();
        Memory = new Memory();
        Scanner = new Scanner(Process.GetCurrentProcess(), Process.GetCurrentProcess().MainModule);
        _characterBindingOptions = characterBindingOptions.Value;
        _networkBindingOptions = networkBindingOptions.Value;
        _unitManagerBindingOptions = sceneManagerBindingOptions.Value;
        _petManagerBindingOptions = petManagerBindingOptions.Value;
        _periodicBindingOptions = periodicBindingOptions.Value;
    }

    /// <summary>


@@ 84,116 67,11 @@ public class NosBindingManager : IDisposable
    internal IMemory Memory { get; }

    /// <summary>
    /// Gets the periodic binding.
    /// </summary>
    /// <exception cref="InvalidOperationException">Thrown if the manager is not initialized yet.</exception>
    public PeriodicBinding Periodic
    {
        get
        {
            if (_periodicBinding is null)
            {
                throw new InvalidOperationException
                (
                    "Could not get periodic. The binding manager is not initialized. Did you forget to call NosBindingManager.Initialize?"
                );
            }

            return _periodicBinding;
        }
    }

    /// <summary>
    /// Gets the network binding.
    /// </summary>
    /// <exception cref="InvalidOperationException">Thrown if the manager is not initialized yet.</exception>
    public NetworkBinding Network
    {
        get
        {
            if (_networkBinding is null)
            {
                throw new InvalidOperationException
                (
                    "Could not get network. The binding manager is not initialized. Did you forget to call NosBindingManager.Initialize?"
                );
            }

            return _networkBinding;
        }
    }

    /// <summary>
    /// Gets the character binding.
    /// </summary>
    /// <exception cref="InvalidOperationException">Thrown if the manager is not initialized yet.</exception>
    public PlayerManagerBinding PlayerManager
    {
        get
        {
            if (_characterBinding is null)
            {
                throw new InvalidOperationException
                (
                    "Could not get character. The binding manager is not initialized. Did you forget to call NosBindingManager.Initialize?"
                );
            }

            return _characterBinding;
        }
    }

    /// <summary>
    /// Gets the character binding.
    /// </summary>
    /// <exception cref="InvalidOperationException">Thrown if the manager is not initialized yet.</exception>
    public UnitManagerBinding UnitManager
    {
        get
        {
            if (_unitManagerBinding is null)
            {
                throw new InvalidOperationException
                (
                    "Could not get scene manager. The binding manager is not initialized. Did you forget to call NosBindingManager.Initialize?"
                );
            }

            return _unitManagerBinding;
        }
    }

    /// <summary>
    /// Gets the pet manager binding.
    /// </summary>
    /// <exception cref="InvalidOperationException">Thrown if the manager is not initialized yet.</exception>
    public PetManagerBinding PetManager
    {
        get
        {
            if (_petManagerBinding is null)
            {
                throw new InvalidOperationException
                (
                    "Could not get pet manager. The binding manager is not initialized. Did you forget to call NosBindingManager.Initialize?"
                );
            }

            return _petManagerBinding;
        }
    }

    /// <summary>
    /// Initialize the existing bindings and hook NosTale functions.
    /// </summary>
    /// <returns>A result that may or may not have succeeded.</returns>
    public IResult Initialize()
    {
        if (_networkBinding is not null)
        { // already initialized
            return Result.FromSuccess();
        }

        List<IResult> errorResults = new List<IResult>();
        var browserInitializationResult = _browserManager.Initialize();
        if (!browserInitializationResult.IsSuccess)


@@ 206,163 84,10 @@ public class NosBindingManager : IDisposable
            errorResults.Add(browserInitializationResult);
        }

        try
        {
            var periodicBinding = PeriodicBinding.Create
            (
                this,
                _periodicBindingOptions
            );
            if (!periodicBinding.IsSuccess)
            {
                errorResults.Add
                (
                    Result.FromError
                    (
                        new CouldNotInitializeModuleError(typeof(PeriodicBinding), periodicBinding.Error),
                        periodicBinding
                    )
                );
            }
            _periodicBinding = periodicBinding.Entity;
        }
        catch (Exception e)
        var hookManagerInitializationResult = _hookManager.Initialize(this, _browserManager);
        if (!hookManagerInitializationResult.IsSuccess)
        {
            errorResults.Add
            (
                Result.FromError
                (
                    new CouldNotInitializeModuleError(typeof(PeriodicBinding), new ExceptionError(e)),
                    (Result)new ExceptionError(e)
                )
            );
        }

        try
        {
            var network = NetworkBinding.Create(this, _networkBindingOptions);
            if (!network.IsSuccess)
            {
                errorResults.Add
                (
                    Result.FromError
                    (
                        new CouldNotInitializeModuleError(typeof(NetworkBinding), network.Error),
                        network
                    )
                );
            }

            _networkBinding = network.Entity;
        }
        catch (Exception e)
        {
            errorResults.Add
            (
                Result.FromError
                (
                    new CouldNotInitializeModuleError(typeof(NetworkBinding), new ExceptionError(e)),
                    (Result)new ExceptionError(e)
                )
            );
        }

        try
        {
            var playerManagerBinding = PlayerManagerBinding.Create
            (
                this,
                _browserManager.PlayerManager,
                _characterBindingOptions
            );
            if (!playerManagerBinding.IsSuccess)
            {
                errorResults.Add
                (
                    Result.FromError
                    (
                        new CouldNotInitializeModuleError(typeof(PlayerManagerBinding), playerManagerBinding.Error),
                        playerManagerBinding
                    )
                );
            }
            _characterBinding = playerManagerBinding.Entity;
        }
        catch (Exception e)
        {
            errorResults.Add
            (
                Result.FromError
                (
                    new CouldNotInitializeModuleError(typeof(PlayerManagerBinding), new ExceptionError(e)),
                    (Result)new ExceptionError(e)
                )
            );
        }

        try
        {
            var unitManagerBinding = UnitManagerBinding.Create
            (
                this,
                _unitManagerBindingOptions
            );
            if (!unitManagerBinding.IsSuccess)
            {
                errorResults.Add
                (
                    Result.FromError
                    (
                        new CouldNotInitializeModuleError(typeof(UnitManagerBinding), unitManagerBinding.Error),
                        unitManagerBinding
                    )
                );
            }
            _unitManagerBinding = unitManagerBinding.Entity;
        }
        catch (Exception e)
        {
            errorResults.Add
            (
                Result.FromError
                (
                    new CouldNotInitializeModuleError(typeof(UnitManagerBinding), new ExceptionError(e)),
                    (Result)new ExceptionError(e)
                )
            );
        }

        try
        {
            var petManagerBinding = PetManagerBinding.Create
            (
                this,
                _browserManager.PetManagerList,
                _petManagerBindingOptions
            );
            if (!petManagerBinding.IsSuccess)
            {
                errorResults.Add
                (
                    Result.FromError
                    (
                        new CouldNotInitializeModuleError(typeof(PetManagerBinding), petManagerBinding.Error),
                        petManagerBinding
                    )
                );
            }
            _petManagerBinding = petManagerBinding.Entity;
        }
        catch (Exception e)
        {
            errorResults.Add
            (
                Result.FromError
                (
                    new CouldNotInitializeModuleError(typeof(UnitManagerBinding), new ExceptionError(e)),
                    (Result)new ExceptionError(e)
                )
            );
            errorResults.Add(hookManagerInitializationResult);
        }

        return errorResults.Count switch


@@ 373,33 98,11 @@ public class NosBindingManager : IDisposable
        };
    }

    /// <summary>
    /// Disable the currently enabled NosTale hooks.
    /// </summary>
    public void DisableHooks()
    {
        _networkBinding?.DisableHooks();
        _characterBinding?.DisableHooks();
        _unitManagerBinding?.DisableHooks();
        _petManagerBinding?.DisableHooks();
    }

    /// <summary>
    /// Enable all NosTale hooks.
    /// </summary>
    public void EnableHooks()
    {
        _networkBinding?.EnableHooks();
        _characterBinding?.EnableHooks();
        _unitManagerBinding?.EnableHooks();
        _petManagerBinding?.EnableHooks();
    }

    /// <inheritdoc />
    public void Dispose()
    {
        Scanner.Dispose();
        DisableHooks();
        _hookManager.DisableAll();
    }

    /// <summary>


@@ 457,6 160,7 @@ public class NosBindingManager : IDisposable
    /// <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="options">The options for the function hook. (pattern, offset, whether to activate).</param>
    /// <param name="cancellable">Whether the call may be cancelled.</param>
    /// <typeparam name="TFunction">The type of the function.</typeparam>
    /// <returns>The hook object or an error.</returns>
    internal Result<NosAsmHook<TFunction>>


@@ 464,8 168,10 @@ public class NosBindingManager : IDisposable
        (
            string name,
            TFunction callbackFunction,
            HookOptions options
            HookOptions options,
            bool cancellable = true
        )
        where TFunction : Delegate
    {
        var walkFunctionAddress = Scanner.FindPattern(options.MemoryPattern);
        if (!walkFunctionAddress.Found)


@@ 481,42 187,69 @@ public class NosBindingManager : IDisposable
            var reverseWrapper = Hooks.CreateReverseWrapper<TFunction>(callbackFunction);
            var callDetour = Utilities.GetAbsoluteCallMnemonics
                (reverseWrapper.WrapperPointer.ToUnsigned(), IntPtr.Size == 8);
            if (!Misc.TryGetAttribute<TFunction, FunctionAttribute>(out var attribute))
            {
                return new ArgumentInvalidError(nameof(TFunction), "The function does not have a function attribute.");
            }
            var stackArgumentsCount = Utilities.GetNumberofParameters<TFunction>() - attribute.SourceRegisters.Length;
            if (stackArgumentsCount < 0)
            {
                stackArgumentsCount = 0;
            }

            var hook = Hooks.CreateAsmHook
            (
                new[]
            var asmInstructions = new List<string>();

            asmInstructions.AddRange(new[]
            {
                "use32",
                "pushad",
                "pushfd",
            });

            for (int i = 0; i < stackArgumentsCount; i++)
            { // TODO make it into asm loop
                asmInstructions.AddRange(new[]
                {
                    "use32",
                    "pushad",
                    "pushfd",
                    "push edx",
                    "push eax",
                    $"add esp, {36 + (4 * stackArgumentsCount)}",
                    "pop ebx",
                    "sub esp, 0x04",
                    $"sub esp, {36 + (4 * stackArgumentsCount)}",
                    "push ebx",
                });
            }

                    // call managed function
                    callDetour,
            asmInstructions.Add(callDetour);

                    // check result
                    // 1 means continue executing
                    // 0 means do not permit the call
                    "test eax, eax",
                    "jnz rest",
            if (cancellable)
            {
                asmInstructions.AddRange
                (
                    new[]
                    {
                        // check result
                        // 1 means continue executing
                        // 0 means do not permit the call
                        "test eax, eax",
                        "jnz rest",

                        // returned 0, going to return early
                        "popfd",
                        "popad",

                        $"ret {4 * stackArgumentsCount}", // return early
                    }
                );
            }

                    // returned 0, going to return early
                    "pop eax",
                    "pop edx",
                    "popfd",
                    "popad",
                    "ret", // return early
            asmInstructions.AddRange(new[]
            {
                // returned 1, going to execute the function
                "rest:",
                "popfd",
                "popad"
            });

                    // returned 1, going to execute the function
                    "rest:",
                    "pop eax",
                    "pop edx",
                    "popfd",
                    "popad"
                },
                address
            );
            var hook = Hooks.CreateAsmHook(asmInstructions.ToArray(), address);

            if (options.Hook)
            {

M src/Core/NosSmooth.LocalBinding/NosBrowserManager.cs => src/Core/NosSmooth.LocalBinding/NosBrowserManager.cs +96 -4
@@ 56,9 56,13 @@ public class NosBrowserManager
    private readonly PlayerManagerOptions _playerManagerOptions;
    private readonly SceneManagerOptions _sceneManagerOptions;
    private readonly PetManagerOptions _petManagerOptions;
    private readonly NetworkManagerOptions _networkManagerOptions;
    private readonly UnitManagerOptions _unitManagerOptions;
    private PlayerManager? _playerManager;
    private SceneManager? _sceneManager;
    private PetManagerList? _petManagerList;
    private NetworkManager? _networkManager;
    private UnitManager? _unitManager;

    /// <summary>
    /// Initializes a new instance of the <see cref="NosBrowserManager"/> class.


@@ 66,18 70,24 @@ public class NosBrowserManager
    /// <param name="playerManagerOptions">The options for obtaining player manager.</param>
    /// <param name="sceneManagerOptions">The scene manager options.</param>
    /// <param name="petManagerOptions">The pet manager options.</param>
    /// <param name="networkManagerOptions">The network manager options.</param>
    /// <param name="unitManagerOptions">The unit manager options.</param>
    public NosBrowserManager
    (
        IOptions<PlayerManagerOptions> playerManagerOptions,
        IOptions<SceneManagerOptions> sceneManagerOptions,
        IOptions<PetManagerOptions> petManagerOptions
        IOptions<PetManagerOptions> petManagerOptions,
        IOptions<NetworkManagerOptions> networkManagerOptions,
        IOptions<UnitManagerOptions> unitManagerOptions
    )
        : this
        (
            Process.GetCurrentProcess(),
            playerManagerOptions.Value,
            sceneManagerOptions.Value,
            petManagerOptions.Value
            petManagerOptions.Value,
            networkManagerOptions.Value,
            unitManagerOptions.Value
        )
    {
    }


@@ 89,19 99,25 @@ public class NosBrowserManager
    /// <param name="playerManagerOptions">The options for obtaining player manager.</param>
    /// <param name="sceneManagerOptions">The scene manager options.</param>
    /// <param name="petManagerOptions">The pet manager options.</param>
    /// <param name="networkManagerOptions">The network manager options.</param>
    /// <param name="unitManagerOptions">The unit manager options.</param>
    public NosBrowserManager
    (
        Process process,
        PlayerManagerOptions playerManagerOptions,
        SceneManagerOptions sceneManagerOptions,
        PetManagerOptions petManagerOptions
        PetManagerOptions petManagerOptions,
        NetworkManagerOptions networkManagerOptions,
        UnitManagerOptions unitManagerOptions
    )
    {
        _playerManagerOptions = playerManagerOptions;
        _sceneManagerOptions = sceneManagerOptions;
        _petManagerOptions = petManagerOptions;
        _networkManagerOptions = networkManagerOptions;
        _unitManagerOptions = unitManagerOptions;
        Process = process;
        Memory = new ExternalMemory(process);
        Memory = Process.Id == Process.GetCurrentProcess().Id ? new Memory() : new ExternalMemory(process);
        Scanner = new Scanner(process, process.MainModule);
    }



@@ 126,6 142,46 @@ public class NosBrowserManager
    public bool IsNostaleProcess => NosBrowserManager.IsProcessNostaleProcess(Process);

    /// <summary>
    /// Gets the network manager.
    /// </summary>
    /// <exception cref="InvalidOperationException">Thrown if the browser is not initialized or there was an error with initialization of network manager.</exception>
    public NetworkManager NetworkManager
    {
        get
        {
            if (_networkManager is null)
            {
                throw new InvalidOperationException
                (
                    "Could not get network manager. The browser manager is not initialized. Did you forget to call NosBrowserManager.Initialize?"
                );
            }

            return _networkManager;
        }
    }

    /// <summary>
    /// Gets the network manager.
    /// </summary>
    /// <exception cref="InvalidOperationException">Thrown if the browser is not initialized or there was an error with initialization of unit manager.</exception>
    public UnitManager UnitManager
    {
        get
        {
            if (_unitManager is null)
            {
                throw new InvalidOperationException
                (
                    "Could not get unit manager. The browser manager is not initialized. Did you forget to call NosBrowserManager.Initialize?"
                );
            }

            return _unitManager;
        }
    }

    /// <summary>
    /// Gets whether the player is currently in game.
    /// </summary>
    /// <remarks>


@@ 215,6 271,42 @@ public class NosBrowserManager
        }

        List<IResult> errorResults = new List<IResult>();
        if (_unitManager is null)
        {
            var unitManagerResult = UnitManager.Create(this, _unitManagerOptions);
            if (!unitManagerResult.IsSuccess)
            {
                errorResults.Add
                (
                    Result.FromError
                    (
                        new CouldNotInitializeModuleError(typeof(UnitManager), unitManagerResult.Error),
                        unitManagerResult
                    )
                );
            }

            _unitManager = unitManagerResult.Entity;
        }

        if (_networkManager is null)
        {
            var networkManagerResult = NetworkManager.Create(this, _networkManagerOptions);
            if (!networkManagerResult.IsSuccess)
            {
                errorResults.Add
                (
                    Result.FromError
                    (
                        new CouldNotInitializeModuleError(typeof(NetworkManager), networkManagerResult.Error),
                        networkManagerResult
                    )
                );
            }

            _networkManager = networkManagerResult.Entity;
        }

        if (_playerManager is null)
        {
            var playerManagerResult = PlayerManager.Create(this, _playerManagerOptions);

M src/Core/NosSmooth.LocalBinding/NosSmooth.LocalBinding.csproj => src/Core/NosSmooth.LocalBinding/NosSmooth.LocalBinding.csproj +1 -0
@@ 9,6 9,7 @@

    <ItemGroup>
      <PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="7.0.0" />
      <PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="7.0.0" />
      <PackageReference Include="Microsoft.Extensions.Options" Version="7.0.0" />
      <PackageReference Include="Reloaded.Hooks" Version="4.2.1" />
      <PackageReference Include="Reloaded.Memory.Sigscan" Version="3.1.6" />

M src/Core/NosSmooth.LocalBinding/NosThreadSynchronizer.cs => src/Core/NosSmooth.LocalBinding/NosThreadSynchronizer.cs +39 -15
@@ 5,10 5,9 @@
//  Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System.Collections.Concurrent;
using System.Net;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using NosSmooth.LocalBinding.Objects;
using NosSmooth.LocalBinding.Hooks;
using NosSmooth.LocalBinding.Options;
using Remora.Results;



@@ 19,18 18,26 @@ namespace NosSmooth.LocalBinding;
/// </summary>
public class NosThreadSynchronizer
{
    private readonly PeriodicBinding _periodicBinding;
    private readonly IPeriodicHook _periodicHook;
    private readonly ILogger<NosThreadSynchronizer> _logger;
    private readonly NosThreadSynchronizerOptions _options;
    private readonly ConcurrentQueue<SyncOperation> _queuedOperations;

    /// <summary>
    /// Initializes a new instance of the <see cref="NosThreadSynchronizer"/> class.
    /// </summary>
    /// <param name="periodicBinding">The periodic function binding.</param>
    /// <param name="periodicHook">The periodic hook.</param>
    /// <param name="logger">The logger.</param>
    /// <param name="options">The options.</param>
    public NosThreadSynchronizer(PeriodicBinding periodicBinding, IOptions<NosThreadSynchronizerOptions> options)
    public NosThreadSynchronizer
    (
        IPeriodicHook periodicHook,
        ILogger<NosThreadSynchronizer> logger,
        IOptions<NosThreadSynchronizerOptions> options
    )
    {
        _periodicBinding = periodicBinding;
        _periodicHook = periodicHook;
        _logger = logger;
        _options = options.Value;
        _queuedOperations = new ConcurrentQueue<SyncOperation>();
    }


@@ 40,7 47,7 @@ public class NosThreadSynchronizer
    /// </summary>
    public void StartSynchronizer()
    {
        _periodicBinding.PeriodicCall += PeriodicCall;
        _periodicHook.Called += PeriodicCall;
    }

    /// <summary>


@@ 48,7 55,7 @@ public class NosThreadSynchronizer
    /// </summary>
    public void StopSynchronizer()
    {
        _periodicBinding.PeriodicCall -= PeriodicCall;
        _periodicHook.Called -= PeriodicCall;
    }

    private void PeriodicCall(object? owner, System.EventArgs eventArgs)


@@ 65,13 72,13 @@ public class NosThreadSynchronizer
    {
        try
        {
            var result = operation.action();
            var result = operation.Action();
            operation.Result = result;
        }
        catch (Exception e)
        {
            // TODO: log?
            operation.Result = e;
            _logger.LogError(e, "Synchronizer obtained an exception");
            operation.Result = (Result)e;
        }

        if (operation.CancellationTokenSource is not null)


@@ 115,6 122,23 @@ public class NosThreadSynchronizer
    /// <returns>The result of the action.</returns>
    public async Task<Result> SynchronizeAsync(Func<Result> action, CancellationToken ct = default)
    {
        return (Result)await CommonSynchronizeAsync(() => action(), ct);
    }

    /// <summary>
    /// Synchronizes to NosTale thread, executes the given action and returns its result.
    /// </summary>
    /// <param name="action">The action to execute.</param>
    /// <param name="ct">The cancellation token used for cancelling the operation.</param>
    /// <returns>The result of the action.</returns>
    /// <typeparam name="T">The type of the result.</typeparam>
    public async Task<Result<T>> SynchronizeAsync<T>(Func<Result<T>> action, CancellationToken ct = default)
    {
        return (Result<T>)await CommonSynchronizeAsync(() => action(), ct);
    }

    private async Task<IResult> CommonSynchronizeAsync(Func<IResult> action, CancellationToken ct = default)
    {
        var linkedSource = CancellationTokenSource.CreateLinkedTokenSource(ct);
        var syncOperation = new SyncOperation(action, linkedSource);
        _queuedOperations.Enqueue(syncOperation);


@@ 132,14 156,14 @@ public class NosThreadSynchronizer
        }
        catch (Exception e)
        {
            return new ExceptionError(e);
            return (Result)new ExceptionError(e);
        }

        return syncOperation.Result ?? Result.FromSuccess();
    }

    private record SyncOperation(Func<Result> action, CancellationTokenSource? CancellationTokenSource)
    private record SyncOperation(Func<IResult> Action, CancellationTokenSource? CancellationTokenSource)
    {
        public Result? Result { get; set; }
        public IResult? Result { get; set; }
    }
}
\ No newline at end of file

D src/Core/NosSmooth.LocalBinding/Objects/NetworkBinding.cs => src/Core/NosSmooth.LocalBinding/Objects/NetworkBinding.cs +0 -227
@@ 1,227 0,0 @@
//
//  NetworkBinding.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.Collections.Concurrent;
using System.Diagnostics;
using System.Runtime.InteropServices;
using NosSmooth.LocalBinding.Errors;
using NosSmooth.LocalBinding.EventArgs;
using NosSmooth.LocalBinding.Extensions;
using NosSmooth.LocalBinding.Options;
using Reloaded.Hooks.Definitions;
using Reloaded.Hooks.Definitions.X86;
using Remora.Results;

namespace NosSmooth.LocalBinding.Objects;

/// <summary>
/// The binding to nostale network object.
/// </summary>
public class NetworkBinding
{
    [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 PacketSendDelegate(nuint packetObject, nuint packetString);

    [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 PacketReceiveDelegate(nuint packetObject, nuint packetString);

    /// <summary>
    /// Create the network binding with finding the network object and functions.
    /// </summary>
    /// <param name="bindingManager">The binding manager.</param>
    /// <param name="options">The options for the binding.</param>
    /// <returns>A network binding or an error.</returns>
    public static Result<NetworkBinding> Create(NosBindingManager bindingManager, NetworkBindingOptions options)
    {
        var process = Process.GetCurrentProcess();
        var networkObjectAddress = bindingManager.Scanner.FindPattern(options.NetworkObjectPattern);
        if (!networkObjectAddress.Found)
        {
            return new BindingNotFoundError(options.NetworkObjectPattern, "NetworkBinding");
        }

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

        var sendHookResult = bindingManager.CreateCustomAsmHookFromPattern<PacketSendDelegate>
            ("NetworkBinding.SendPacket", binding.SendPacketDetour, options.PacketSendHook);
        if (!sendHookResult.IsDefined(out var sendHook))
        {
            return Result<NetworkBinding>.FromError(sendHookResult);
        }

        var receiveHookResult = bindingManager.CreateCustomAsmHookFromPattern<PacketReceiveDelegate>
            ("NetworkBinding.ReceivePacket", binding.ReceivePacketDetour, options.PacketReceiveHook);
        if (!receiveHookResult.IsDefined(out var receiveHook))
        {
            return Result<NetworkBinding>.FromError(receiveHookResult);
        }

        binding._sendHook = sendHook;
        binding._receiveHook = receiveHook;
        return binding;
    }

    private readonly NosBindingManager _bindingManager;
    private readonly nuint _networkManagerAddress;
    private NosAsmHook<PacketSendDelegate> _sendHook = null!;
    private NosAsmHook<PacketReceiveDelegate> _receiveHook = null!;
    private bool _callingReceive;
    private bool _callingSend;

    private NetworkBinding
    (
        NosBindingManager bindingManager,
        nuint networkManagerAddress
    )
    {
        _bindingManager = bindingManager;
        _networkManagerAddress = networkManagerAddress;
    }

    /// <summary>
    /// Event that is called when packet send was called by NosTale.
    /// </summary>
    /// <remarks>
    /// The send must be hooked for this event to be called.
    /// </remarks>
    public event EventHandler<PacketEventArgs>? PacketSendCall;

    /// <summary>
    /// Event that is called when packet receive was called by NosTale.
    /// </summary>
    /// <remarks>
    /// The receive must be hooked for this event to be called.
    /// </remarks>
    public event EventHandler<PacketEventArgs>? PacketReceiveCall;

    /// <summary>
    /// Send the given packet.
    /// </summary>
    /// <param name="packet">The packet to send.</param>
    /// <returns>A result that may or may not have succeeded.</returns>
    public Result SendPacket(string packet)
    {
        try
        {
            _callingSend = true;
            using var nostaleString = NostaleStringA.Create(_bindingManager.Memory, packet);
            _sendHook.OriginalFunction.GetWrapper()(GetManagerAddress(false), nostaleString.Get());
            _callingSend = false;
        }
        catch (Exception e)
        {
            return e;
        }

        return Result.FromSuccess();
    }

    /// <summary>
    /// Receive the given packet.
    /// </summary>
    /// <param name="packet">The packet to receive.</param>
    /// <returns>A result that may or may not have succeeded.</returns>
    public Result ReceivePacket(string packet)
    {
        try
        {
            _callingReceive = true;
            using var nostaleString = NostaleStringA.Create(_bindingManager.Memory, packet);
            _receiveHook.OriginalFunction.GetWrapper()(GetManagerAddress(true), nostaleString.Get());
            _callingReceive = false;
        }
        catch (Exception e)
        {
            return e;
        }

        return Result.FromSuccess();
    }

    /// <summary>
    /// Enable all networking hooks.
    /// </summary>
    public void EnableHooks()
    {
        _receiveHook.Hook.EnableOrActivate();
        _sendHook.Hook.EnableOrActivate();
    }

    /// <summary>
    /// Disable all the hooks that are currently enabled.
    /// </summary>
    public void DisableHooks()
    {
        _receiveHook.Hook.Disable();
        _sendHook.Hook.Disable();
    }

    private nuint GetManagerAddress(bool third)
    {
        nuint networkManager = _networkManagerAddress;
        _bindingManager.Memory.Read(networkManager, out networkManager);
        _bindingManager.Memory.Read(networkManager, out networkManager);
        _bindingManager.Memory.Read(networkManager, out networkManager);

        if (third)
        {
            _bindingManager.Memory.Read(networkManager + 0x34, out networkManager);
        }

        return networkManager;
    }

    private nuint SendPacketDetour(nuint packetObject, nuint packetString)
    {
        if (_callingSend)
        {
            return 1;
        }

        var packet = Marshal.PtrToStringAnsi((IntPtr)packetString);
        if (packet is null)
        { // ?
            return 1;
        }
        var packetArgs = new PacketEventArgs(packet);
        PacketSendCall?.Invoke(this, packetArgs);
        return packetArgs.Cancel ? 0 : (nuint)1;
    }

    private nuint ReceivePacketDetour(nuint packetObject, nuint packetString)
    {
        if (_callingReceive)
        {
            return 1;
        }

        var packet = Marshal.PtrToStringAnsi((IntPtr)packetString);
        if (packet is null)
        { // ?
            return 1;
        }

        var packetArgs = new PacketEventArgs(packet);
        PacketReceiveCall?.Invoke(this, packetArgs);
        return packetArgs.Cancel ? 0 : (nuint)1;
    }
}
\ No newline at end of file

D src/Core/NosSmooth.LocalBinding/Objects/PeriodicBinding.cs => src/Core/NosSmooth.LocalBinding/Objects/PeriodicBinding.cs +0 -82
@@ 1,82 0,0 @@
//
//  PeriodicBinding.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 Reloaded.Hooks.Definitions.X86;
using Remora.Results;

namespace NosSmooth.LocalBinding.Objects;

/// <summary>
/// Binds to a periodic function to allow synchronizing.
/// </summary>
public class PeriodicBinding
{
    [Function
    (
        new FunctionAttribute.Register[0],
        FunctionAttribute.Register.eax,
        FunctionAttribute.StackCleanup.Callee,
        new[] { FunctionAttribute.Register.ebx, FunctionAttribute.Register.esi, FunctionAttribute.Register.edi, FunctionAttribute.Register.ebp, FunctionAttribute.Register.eax, FunctionAttribute.Register.edx, FunctionAttribute.Register.ecx }
    )]
    private delegate void PeriodicDelegate();

    /// <summary>
    /// Create the periodic binding with finding the periodic function.
    /// </summary>
    /// <param name="bindingManager">The binding manager.</param>
    /// <param name="options">The options for the binding.</param>
    /// <returns>A periodic binding or an error.</returns>
    public static Result<PeriodicBinding> Create(NosBindingManager bindingManager, PeriodicBindingOptions options)
    {
        var binding = new PeriodicBinding();

        var periodicHookResult = bindingManager.CreateCustomAsmHookFromPattern<PeriodicDelegate>
            ("PeriodicBinding.Periodic", binding.PeriodicDetour, options.PeriodicHook);
        if (!periodicHookResult.IsDefined(out var periodicHook))
        {
            return Result<PeriodicBinding>.FromError(periodicHookResult);
        }

        binding._periodicHook = periodicHook;
        return binding;
    }

    private NosAsmHook<PeriodicDelegate>? _periodicHook;

    private PeriodicBinding()
    {
    }

    /// <summary>
    /// An action called on every period.
    /// </summary>
    public event EventHandler? PeriodicCall;

    /// <summary>
    /// Enable all networking hooks.
    /// </summary>
    public void EnableHooks()
    {
        _periodicHook?.Hook.EnableOrActivate();
    }

    /// <summary>
    /// Disable all the hooks that are currently enabled.
    /// </summary>
    public void DisableHooks()
    {
        _periodicHook?.Hook.Disable();
    }

    private void PeriodicDetour()
    {
        PeriodicCall?.Invoke(this, System.EventArgs.Empty);
    }
}
\ No newline at end of file

D src/Core/NosSmooth.LocalBinding/Objects/PetManagerBinding.cs => src/Core/NosSmooth.LocalBinding/Objects/PetManagerBinding.cs +0 -168
@@ 1,168 0,0 @@
//
//  PetManagerBinding.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 NosSmooth.LocalBinding.EventArgs;
using NosSmooth.LocalBinding.Extensions;
using NosSmooth.LocalBinding.Options;
using NosSmooth.LocalBinding.Structs;
using Reloaded.Hooks.Definitions;
using Reloaded.Hooks.Definitions.X86;
using Reloaded.Memory.Sources;
using Remora.Results;

namespace NosSmooth.LocalBinding.Objects;

/// <summary>
/// The binding to NosTale pet manager.
/// </summary>
public class PetManagerBinding
{
    private readonly IMemory _memory;

    /// <summary>
    /// Create nostale pet manager binding.
    /// </summary>
    /// <param name="bindingManager">The binding manager.</param>
    /// <param name="petManagerList">The list of the pet managers.</param>
    /// <param name="options">The options.</param>
    /// <returns>A pet manager binding or and error.</returns>
    public static Result<PetManagerBinding> Create
        (NosBindingManager bindingManager, PetManagerList petManagerList, PetManagerBindingOptions options)
    {
        var petManager = new PetManagerBinding(bindingManager.Memory, petManagerList);
        var hookResult = bindingManager.CreateHookFromPattern<PetWalkDelegate>
        (
            "PetManagerBinding.PetWalk",
            petManager.PetWalkDetour,
            options.PetWalkHook
        );

        if (!hookResult.IsSuccess)
        {
            return Result<PetManagerBinding>.FromError(hookResult);
        }

        petManager._petWalkHook = hookResult.Entity;
        return petManager;
    }

    [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 PetWalkDelegate
    (
        nuint petManagerPtr,
        uint position,
        short unknown0 = 0,
        int unknown1 = 1,
        int unknown2 = 1
    );

    private IHook<PetWalkDelegate> _petWalkHook = null!;

    private PetManagerBinding(IMemory memory, PetManagerList petManagerList)
    {
        _memory = memory;
        PetManagerList = petManagerList;
    }

    /// <summary>
    /// Event that is called when walk was called by NosTale.
    /// </summary>
    /// <remarks>
    /// The walk must be hooked for this event to be called.
    /// </remarks>
    public event EventHandler<PetWalkEventArgs>? PetWalkCall;

    /// <summary>
    /// Gets the hook of the pet walk function.
    /// </summary>
    public IHook PetWalkHook => _petWalkHook;

    /// <summary>
    /// Gets pet manager list.
    /// </summary>
    public PetManagerList PetManagerList { get; }

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

    /// <summary>
    /// Enable all PetManager hooks.
    /// </summary>
    public void EnableHooks()
    {
        _petWalkHook.EnableOrActivate();
    }

    /// <summary>
    /// Walk the given pet to the given location.
    /// </summary>
    /// <param name="selector">Index of the pet to walk. -1 for every pet currently available.</param>
    /// <param name="x">The x coordinate.</param>
    /// <param name="y">The y coordinate.</param>
    /// <returns>A result returned from NosTale or an error.</returns>
    public Result<bool> PetWalk(int selector, short x, short y)
    {
        uint position = Convert.ToUInt32(((ushort)y << 16) | (ushort)x);
        if (PetManagerList.Length < selector + 1)
        {
            return new NotFoundError("Could not find the pet using the given selector.");
        }

        if (selector == -1)
        {
            bool lastResult = true;
            for (int i = 0; i < PetManagerList.Length; i++)
            {
                lastResult = _petWalkHook.OriginalFunction(PetManagerList[i].Address, position);
            }

            return lastResult;
        }

        return _petWalkHook.OriginalFunction(PetManagerList[selector].Address, position);
    }

    private bool PetWalkDetour
    (
        nuint petManagerPtr,
        uint position,
        short unknown0 = 0,
        int unknown1 = 1,
        int unknown2 = 1
    )
    {
        var x = (ushort)(position & 0xFFFF);
        var y = (ushort)((position >> 16) & 0xFFFF);
        var petManager = new PetManager(_memory, petManagerPtr);
        var petWalkEventArgs = new PetWalkEventArgs(petManager, x, y);
        PetWalkCall?.Invoke(this, petWalkEventArgs);

        if (!petWalkEventArgs.Cancel)
        {
            return _petWalkHook.OriginalFunction
            (
                petManagerPtr,
                position,
                unknown0,
                unknown1,
                unknown2
            );
        }

        return false;
    }
}
\ No newline at end of file

D src/Core/NosSmooth.LocalBinding/Objects/PlayerManagerBinding.cs => src/Core/NosSmooth.LocalBinding/Objects/PlayerManagerBinding.cs +0 -260
@@ 1,260 0,0 @@
//
//  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.ComponentModel;
using System.Diagnostics;
using NosSmooth.LocalBinding.Errors;
using NosSmooth.LocalBinding.EventArgs;
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;

/// <summary>
/// The nostale binding of a character.
/// </summary>
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);

    /// <summary>
    /// Create the network binding with finding the network object and functions.
    /// </summary>
    /// <param name="bindingManager">The binding manager.</param>
    /// <param name="playerManager">The player manager.</param>
    /// <param name="options">The options for the binding.</param>
    /// <returns>A network binding or an error.</returns>
    public static Result<PlayerManagerBinding> Create(NosBindingManager bindingManager, PlayerManager playerManager, CharacterBindingOptions options)
    {
        var binding = new PlayerManagerBinding
        (
            bindingManager,
            playerManager
        );

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

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

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

        binding._walkHook = walkHook;
        binding._followHook = entityFollowHook;
        binding._unfollowHook = entityUnfollowHook;
        return binding;
    }

    private readonly NosBindingManager _bindingManager;

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

    private PlayerManagerBinding
    (
        NosBindingManager bindingManager,
        PlayerManager playerManager
    )
    {
        PlayerManager = playerManager;
        _bindingManager = bindingManager;
    }

    /// <summary>
    /// Gets the player manager.
    /// </summary>
    public PlayerManager PlayerManager { get; }

    /// <summary>
    /// Event that is called when walk was called by NosTale.
    /// </summary>
    /// <remarks>
    /// The walk must be hooked for this event to be called.
    /// </remarks>
    public event EventHandler<WalkEventArgs>? WalkCall;

    /// <summary>
    /// Event that is called when entity follow or unfollow was called.
    /// </summary>
    /// <remarks>
    /// The follow/unfollow entity must be hooked for this event to be called.
    /// </remarks>
    public event EventHandler<EntityEventArgs>? FollowEntityCall;

    /// <summary>
    /// Disable all PlayerManager hooks.
    /// </summary>
    public void DisableHooks()
    {
        _followHook.Disable();
        _unfollowHook.Disable();
        _walkHook.Disable();
    }

    /// <summary>
    /// Enable all PlayerManager hooks.
    /// </summary>
    public void EnableHooks()
    {
        _followHook.EnableOrActivate();
        _unfollowHook.EnableOrActivate();
        _walkHook.EnableOrActivate();
    }

    /// <summary>
    /// Walk to the given position.
    /// </summary>
    /// <param name="x">The x coordinate.</param>
    /// <param name="y">The y coordinate.</param>
    /// <returns>A result that may or may not have succeeded.</returns>
    public Result<bool> 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 walkEventArgs = new WalkEventArgs((ushort)(position & 0xFFFF), (ushort)((position >> 16) & 0xFFFF));
        WalkCall?.Invoke(this, walkEventArgs);
        if (!walkEventArgs.Cancel)
        {
            return _walkHook.OriginalFunction(characterObject, position, unknown0, unknown1);
        }

        return false;
    }

    /// <summary>
    /// Follow the entity.
    /// </summary>
    /// <param name="entity">The entity.</param>
    /// <returns>A result that may or may not have succeeded.</returns>
    public Result FollowEntity(MapBaseObj? entity)
        => FollowEntity(entity?.Address ?? nuint.Zero);

    /// <summary>
    /// Follow the entity.
    /// </summary>
    /// <param name="entityAddress">The entity address.</param>
    /// <returns>A result that may or may not have succeeded.</returns>
    public Result FollowEntity(nuint entityAddress)
    {
        try
        {
            _followHook.OriginalFunction(PlayerManager.Address, entityAddress);
        }
        catch (Exception e)
        {
            return e;
        }

        return Result.FromSuccess();
    }

    /// <summary>
    /// Stop following entity.
    /// </summary>
    /// <returns>A result that may or may not have succeeded.</returns>
    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 entityEventArgs = new EntityEventArgs(new MapBaseObj(_bindingManager.Memory, entityPtr));
        FollowEntityCall?.Invoke(this, entityEventArgs);
        if (!entityEventArgs.Cancel)
        {
            return _followHook.OriginalFunction(playerManagerPtr, entityPtr, unknown1, unknown2);
        }

        return false;
    }

    private void UnfollowEntityDetour(nuint playerManagerPtr, int unknown)
    {
        var entityEventArgs = new EntityEventArgs(null);
        FollowEntityCall?.Invoke(this, entityEventArgs);
        if (!entityEventArgs.Cancel)
        {
            _unfollowHook.OriginalFunction(playerManagerPtr, unknown);
        }
    }
}
\ No newline at end of file

D src/Core/NosSmooth.LocalBinding/Objects/UnitManagerBinding.cs => src/Core/NosSmooth.LocalBinding/Objects/UnitManagerBinding.cs +0 -172
@@ 1,172 0,0 @@
//
//  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.EventArgs;
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;

/// <summary>
/// The nostale binding of a scene manager.
/// </summary>
/// <remarks>
/// The scene manager holds addresses to entities, mouse position, ....
/// </remarks>
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);

    /// <summary>
    /// Create the scene manager binding.
    /// </summary>
    /// <param name="bindingManager">The binding manager.</param>
    /// <param name="bindingOptions">The options for the binding.</param>
    /// <returns>A network binding or an error.</returns>
    public static Result<UnitManagerBinding> 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<FocusEntityDelegate>
            ("UnitManager.EntityFocus", binding.FocusEntityDetour, bindingOptions.EntityFocusHook);
        if (!entityFocusHookResult.IsDefined(out var entityFocusHook))
        {
            return Result<UnitManagerBinding>.FromError(entityFocusHookResult);
        }

        binding._focusHook = entityFocusHook;
        return binding;
    }

    private readonly int _staticUnitManagerAddress;
    private readonly int[] _unitManagerOffsets;

    private readonly NosBindingManager _bindingManager;

    private NosAsmHook<FocusEntityDelegate> _focusHook = null!;

    private bool _callingFocus;

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

    /// <summary>
    /// Gets the address of unit manager.
    /// </summary>
    public nuint Address => _bindingManager.Memory.FollowStaticAddressOffsets
        (_staticUnitManagerAddress, _unitManagerOffsets);

    /// <summary>
    /// Event that is called when entity focus was called by NosTale.
    /// </summary>
    /// <remarks>
    /// The focus entity must be hooked for this event to be called.
    /// </remarks>
    public event EventHandler<EntityEventArgs>? EntityFocusCall;

    /// <summary>
    /// Focus the entity.
    /// </summary>
    /// <param name="entity">The entity.</param>
    /// <returns>A result that may or may not have succeeded.</returns>
    public Result FocusEntity(MapBaseObj? entity)
        => FocusEntity(entity?.Address ?? nuint.Zero);

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

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

    /// <summary>
    /// Focus the entity.
    /// </summary>
    /// <param name="entityAddress">The entity address.</param>
    /// <returns>A result that may or may not have succeeded.</returns>
    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);
        }

        var entityEventArgs = new EntityEventArgs(obj);
        EntityFocusCall?.Invoke(this, entityEventArgs);

        return entityEventArgs.Cancel ? 0 : (nuint)1;
    }
}
\ No newline at end of file

D src/Core/NosSmooth.LocalBinding/Options/CharacterBindingOptions.cs => src/Core/NosSmooth.LocalBinding/Options/CharacterBindingOptions.cs +0 -32
@@ 1,32 0,0 @@
//
//  CharacterBindingOptions.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 NosSmooth.LocalBinding.Objects;

namespace NosSmooth.LocalBinding.Options;

/// <summary>
/// Options for <see cref="PlayerManagerBinding"/>.
/// </summary>
public class CharacterBindingOptions
{
    /// <summary>
    /// Gets or sets the configuration for player walk function hook.
    /// </summary>
    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 configuration for entity follow function hook.
    /// </summary>
    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 configuration for entity unfollow function hook.
    /// </summary>
    public HookOptions EntityUnfollowHook { get; set; }
        = new HookOptions(false, "80 78 14 00 74 1A", 0);
}
\ No newline at end of file

A src/Core/NosSmooth.LocalBinding/Options/HookManagerOptions.cs => src/Core/NosSmooth.LocalBinding/Options/HookManagerOptions.cs +69 -0
@@ 0,0 1,69 @@
//
//  HookManagerOptions.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 NosSmooth.LocalBinding.Hooks;

namespace NosSmooth.LocalBinding.Options;

/// <summary>
/// Options for <see cref="IHookManager"/>.
/// </summary>
public class HookManagerOptions
{
    /// <summary>
    /// Gets or sets the configuration for player walk function hook.
    /// </summary>
    public HookOptions<IPlayerWalkHook> PlayerWalkHook { get; set; }
        = new(IHookManager.CharacterWalkName, false, "55 8B EC 83 C4 EC 53 56 57 66 89 4D FA", 0);

    /// <summary>
    /// Gets or sets the configuration for entity follow function hook.
    /// </summary>
    public HookOptions<IEntityFollowHook> EntityFollowHook { get; set; }
        = new(IHookManager.EntityFollowName, false, "55 8B EC 51 53 56 57 88 4D FF 8B F2 8B F8", 0);

    /// <summary>
    /// Gets or sets the configuration for entity unfollow function hook.
    /// </summary>
    public HookOptions<IEntityUnfollowHook> EntityUnfollowHook { get; set; }
        = new(IHookManager.EntityUnfollowName, false, "80 78 14 00 74 1A", 0);

    /// <summary>
    /// Gets or sets the configuration for packet receive function hook.
    /// </summary>
    public HookOptions<IPacketReceiveHook> PacketReceiveHook { get; set; }
        = new
        (
            IHookManager.PacketReceiveName,
            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 the configuration for packet send function hook.
    /// </summary>
    public HookOptions<IPacketSendHook> PacketSendHook { get; set; }
        = new(IHookManager.PacketSendName, true, "53 56 8B F2 8B D8 EB 04", 0);

    /// <summary>
    /// Gets or sets the configuration for pet walk function hook.
    /// </summary>
    public HookOptions<IPetWalkHook> PetWalkHook { get; set; }
        = new(IHookManager.PetWalkName, false, "55 8b ec 83 c4 e4 53 56 57 8b f9 89 55 fc 8b d8 c6 45 fb 00", 0);

    /// <summary>
    /// Gets or sets the configuration for any periodic function hook.
    /// </summary>
    public HookOptions<IPeriodicHook> PeriodicHook { get; set; }
        = new(IHookManager.PeriodicName, true, "55 8B EC 53 56 83 C4", 0);

    /// <summary>
    /// Gets or sets the configuration for entity focus function hook.
    /// </summary>
    public HookOptions<IEntityFocusHook> EntityFocusHook { get; set; }
        = new(IHookManager.EntityFocusName, false, "73 00 00 00 55 8b ec b9 05 00 00 00", 4);
}
\ No newline at end of file

R src/Core/NosSmooth.LocalBinding/Options/NetworkBindingOptions.cs => src/Core/NosSmooth.LocalBinding/Options/NetworkManagerOptions.cs +10 -15
@@ 1,31 1,21 @@
//
//  NetworkBindingOptions.cs
//  NetworkManagerOptions.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 NosSmooth.LocalBinding.Hooks;
using NosSmooth.LocalBinding.Objects;
using NosSmooth.LocalBinding.Structs;

namespace NosSmooth.LocalBinding.Options;

/// <summary>
/// Options for <see cref="NetworkBinding"/>.
/// Options for <see cref="NetworkManager"/>.
/// </summary>
public class NetworkBindingOptions
public class NetworkManagerOptions
{
    /// <summary>
    /// Gets or sets the configuration for packet receive function hook.
    /// </summary>
    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 the configuration for packet send function hook.
    /// </summary>
    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.
    /// </summary>
    /// <remarks>


@@ 33,4 23,9 @@ 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 offset of NetworkObject.
    /// </summary>
    public int NetworkObjectOffset { get; set; } = 1;
}
\ No newline at end of file

D src/Core/NosSmooth.LocalBinding/Options/PetManagerBindingOptions.cs => src/Core/NosSmooth.LocalBinding/Options/PetManagerBindingOptions.cs +0 -21
@@ 1,21 0,0 @@
//
//  PetManagerBindingOptions.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 NosSmooth.LocalBinding.Objects;

namespace NosSmooth.LocalBinding.Options;

/// <summary>
/// Options for <see cref="PetManagerBinding"/>.
/// </summary>
public class PetManagerBindingOptions
{
    /// <summary>
    /// Gets or sets the configuration for pet walk function hook.
    /// </summary>
    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

R src/Core/NosSmooth.LocalBinding/Options/UnitManagerBindingOptions.cs => src/Core/NosSmooth.LocalBinding/Options/UnitManagerOptions.cs +5 -9
@@ 1,17 1,19 @@
//
//  UnitManagerBindingOptions.cs
//  UnitManagerOptions.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 NosSmooth.LocalBinding.Hooks;
using NosSmooth.LocalBinding.Objects;
using NosSmooth.LocalBinding.Structs;

namespace NosSmooth.LocalBinding.Options;

/// <summary>
/// Options for <see cref="UnitManagerBinding"/>.
/// Options for <see cref="UnitManager"/>.
/// </summary>
public class UnitManagerBindingOptions
public class UnitManagerOptions
{
    /// <summary>
    /// Gets or sets the pattern to static address of unit manager.


@@ 24,10 26,4 @@ public class UnitManagerBindingOptions
    /// </summary>
    public int[] UnitManagerOffsets { get; set; }
        = { 1, 0 };

    /// <summary>
    /// Gets or sets the configuration for entity focus function hook.
    /// </summary>
    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

A src/Core/NosSmooth.LocalBinding/Structs/MapBaseList.cs => src/Core/NosSmooth.LocalBinding/Structs/MapBaseList.cs +29 -0
@@ 0,0 1,29 @@
//
//  MapBaseList.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 Reloaded.Memory.Sources;

namespace NosSmooth.LocalBinding.Structs;

/// <inheritdoc />
public class MapBaseList : NostaleList<MapBaseObj>
{
    /// <summary>
    /// Initializes a new instance of the <see cref="MapBaseList"/> class.
    /// </summary>
    /// <param name="memory">The memory.</param>
    /// <param name="objListPointer">The list pointer.</param>
    public MapBaseList(IMemory memory, nuint objListPointer)
        : base(memory, objListPointer)
    {
    }

    /// <inheritdoc />
    protected override MapBaseObj CreateNew(IMemory memory, nuint address)
    {
        return new MapBaseObj(memory, address);
    }
}
\ No newline at end of file

M src/Core/NosSmooth.LocalBinding/Structs/MapBaseObj.cs => src/Core/NosSmooth.LocalBinding/Structs/MapBaseObj.cs +0 -7
@@ 16,13 16,6 @@ public class MapBaseObj : NostaleObject
    /// <summary>
    /// Initializes a new instance of the <see cref="MapBaseObj"/> class.
    /// </summary>
    public MapBaseObj()
    {
    }

    /// <summary>
    /// Initializes a new instance of the <see cref="MapBaseObj"/> class.
    /// </summary>
    /// <param name="memory">The memory.</param>
    /// <param name="mapObjPointer">The map object pointer.</param>
    public MapBaseObj(IMemory memory, nuint mapObjPointer)

A src/Core/NosSmooth.LocalBinding/Structs/NetworkManager.cs => src/Core/NosSmooth.LocalBinding/Structs/NetworkManager.cs +92 -0
@@ 0,0 1,92 @@
//
//  NetworkManager.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 NosSmooth.LocalBinding.Errors;
using NosSmooth.LocalBinding.Extensions;
using NosSmooth.LocalBinding.Options;
using Reloaded.Memory.Sources;
using Remora.Results;

namespace NosSmooth.LocalBinding.Structs;

/// <summary>
/// An object used for PacketSend and PacketReceive functions.
/// </summary>
public class NetworkManager : NostaleObject
{
    /// <summary>
    /// Create <see cref="PlayerManager"/> instance.
    /// </summary>
    /// <param name="nosBrowserManager">The NosTale process browser.</param>
    /// <param name="options">The options.</param>
    /// <returns>The player manager or an error.</returns>
    public static Result<NetworkManager> Create(NosBrowserManager nosBrowserManager, NetworkManagerOptions options)
    {
        var networkObjectAddress = nosBrowserManager.Scanner.FindPattern(options.NetworkObjectPattern);
        if (!networkObjectAddress.Found)
        {
            return new BindingNotFoundError(options.NetworkObjectPattern, "NetworkBinding");
        }

        if (nosBrowserManager.Process.MainModule is null)
        {
            return new NotFoundError("Cannot find the main module of the target process.");
        }

        var staticAddress = (nuint)(nosBrowserManager.Process.MainModule.BaseAddress + networkObjectAddress.Offset
            + options.NetworkObjectOffset);
        return new NetworkManager(nosBrowserManager.Memory, staticAddress);
    }

    private readonly IMemory _memory;
    private readonly nuint _staticNetworkManagerAddress;

    /// <summary>
    /// Initializes a new instance of the <see cref="NetworkManager"/> class.
    /// </summary>
    /// <param name="memory">The memory.</param>
    /// <param name="staticNetworkManagerAddress">The pointer to the beginning of the player manager structure.</param>
    public NetworkManager(IMemory memory, nuint staticNetworkManagerAddress)
        : base(memory, staticNetworkManagerAddress)
    {
        _memory = memory;
        _staticNetworkManagerAddress = staticNetworkManagerAddress;
    }

    /// <summary>
    /// Gets the address to the player manager.
    /// </summary>
    public override nuint Address => GetManagerAddress(false);

    private nuint GetManagerAddress(bool third)
    {
        nuint networkManager = _staticNetworkManagerAddress;
        _memory.Read(networkManager, out networkManager);
        _memory.Read(networkManager, out networkManager);
        _memory.Read(networkManager, out networkManager);

        if (third)
        {
            _memory.Read(networkManager + 0x34, out networkManager);
        }

        return networkManager;
    }

    /// <summary>
    /// Gets an address pointer used in PacketSend function.
    /// </summary>
    /// <returns>Pointer to the object.</returns>
    public nuint GetAddressForPacketSend()
        => GetManagerAddress(false);

    /// <summary>
    /// Gets an address pointer used in PacketReceive function.
    /// </summary>
    /// <returns>Pointer to the object.</returns>
    public nuint GetAddressForPacketReceive()
        => GetManagerAddress(true);
}
\ No newline at end of file

M src/Core/NosSmooth.LocalBinding/Structs/NostaleList.cs => src/Core/NosSmooth.LocalBinding/Structs/NostaleList.cs +13 -7
@@ 5,6 5,8 @@
//  Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System.Collections;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices.ObjectiveC;
using Reloaded.Memory.Pointers;
using Reloaded.Memory.Sources;



@@ 14,8 16,8 @@ namespace NosSmooth.LocalBinding.Structs;
/// A class representing a list from nostale.
/// </summary>
/// <typeparam name="T">The type.</typeparam>
public class NostaleList<T> : IEnumerable<T>
    where T : NostaleObject, new()
public abstract class NostaleList<T> : IEnumerable<T>
    where T : NostaleObject
{
    private readonly IMemory _memory;



@@ 52,11 54,7 @@ public class NostaleList<T> : IEnumerable<T>
            _memory.SafeRead(Address + 0x04, out int arrayAddress);
            _memory.SafeRead((nuint)arrayAddress + (nuint)(0x04 * index), out int objectAddress);

            return new T
            {
                Memory = _memory,
                Address = (nuint)objectAddress
            };
            return CreateNew(_memory, (nuint)objectAddress);
        }
    }



@@ 72,6 70,14 @@ public class NostaleList<T> : IEnumerable<T>
        }
    }

    /// <summary>
    /// Create a new instance of <typeparamref name="T"/> with the given memory and address.
    /// </summary>
    /// <param name="memory">The memory.</param>
    /// <param name="address">The address.</param>
    /// <returns>The new object.</returns>
    protected abstract T CreateNew(IMemory memory, nuint address);

    /// <inheritdoc/>
    public IEnumerator<T> GetEnumerator()
    {

M src/Core/NosSmooth.LocalBinding/Structs/NostaleObject.cs => src/Core/NosSmooth.LocalBinding/Structs/NostaleObject.cs +0 -7
@@ 16,13 16,6 @@ public abstract class NostaleObject
    /// <summary>
    /// Initializes a new instance of the <see cref="NostaleObject"/> class.
    /// </summary>
    internal NostaleObject()
    {
    }

    /// <summary>
    /// Initializes a new instance of the <see cref="NostaleObject"/> class.
    /// </summary>
    /// <param name="memory">The memory.</param>
    /// <param name="address">The address in the memory.</param>
    protected NostaleObject(IMemory memory, nuint address)

M src/Core/NosSmooth.LocalBinding/Structs/PetManagerList.cs => src/Core/NosSmooth.LocalBinding/Structs/PetManagerList.cs +6 -0
@@ 50,4 50,10 @@ public class PetManagerList : NostaleList<PetManager>
        : base(memory, memory.FollowStaticAddressOffsets(staticPetManagerListAddress, staticPetManagerOffsets))
    {
    }

    /// <inheritdoc />
    protected override PetManager CreateNew(IMemory memory, nuint address)
    {
        return new PetManager(memory, address);
    }
}
\ No newline at end of file

M src/Core/NosSmooth.LocalBinding/Structs/SceneManager.cs => src/Core/NosSmooth.LocalBinding/Structs/SceneManager.cs +4 -4
@@ 65,22 65,22 @@ public class SceneManager
    /// <summary>
    /// Gets the player list.
    /// </summary>
    public NostaleList<MapBaseObj> PlayerList => new NostaleList<MapBaseObj>(_memory, ReadPtr(Address + 0xC));
    public NostaleList<MapBaseObj> PlayerList => new MapBaseList(_memory, ReadPtr(Address + 0xC));

    /// <summary>
    /// Gets the monster list.
    /// </summary>
    public NostaleList<MapBaseObj> MonsterList => new NostaleList<MapBaseObj>(_memory, ReadPtr(Address + 0x10));
    public NostaleList<MapBaseObj> MonsterList => new MapBaseList(_memory, ReadPtr(Address + 0x10));

    /// <summary>
    /// Gets the npc list.
    /// </summary>
    public NostaleList<MapBaseObj> NpcList => new NostaleList<MapBaseObj>(_memory, ReadPtr(Address + 0x14));
    public NostaleList<MapBaseObj> NpcList => new MapBaseList(_memory, ReadPtr(Address + 0x14));

    /// <summary>
    /// Gets the item list.
    /// </summary>
    public NostaleList<MapBaseObj> ItemList => new NostaleList<MapBaseObj>(_memory, ReadPtr(Address + 0x18));
    public NostaleList<MapBaseObj> ItemList => new MapBaseList(_memory, ReadPtr(Address + 0x18));

    /// <summary>
    /// Gets the entity that is currently being followed by the player.

A src/Core/NosSmooth.LocalBinding/Structs/UnitManager.cs => src/Core/NosSmooth.LocalBinding/Structs/UnitManager.cs +66 -0
@@ 0,0 1,66 @@
//
//  UnitManager.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 NosSmooth.LocalBinding.Errors;
using NosSmooth.LocalBinding.Extensions;
using NosSmooth.LocalBinding.Options;
using Reloaded.Memory.Sources;
using Remora.Results;

namespace NosSmooth.LocalBinding.Structs;

/// <summary>
/// An object used for PacketSend and PacketReceive functions.
/// </summary>
public class UnitManager : NostaleObject
{
        /// <summary>
    /// Create <see cref="PlayerManager"/> instance.
    /// </summary>
    /// <param name="nosBrowserManager">The NosTale process browser.</param>
    /// <param name="options">The options.</param>
    /// <returns>The player manager or an error.</returns>
    public static Result<UnitManager> Create(NosBrowserManager nosBrowserManager, UnitManagerOptions options)
    {
        var unitObjectAddress = nosBrowserManager.Scanner.FindPattern(options.UnitManagerPattern);
        if (!unitObjectAddress.Found)
        {
            return new BindingNotFoundError(options.UnitManagerPattern, "UnitManager");
        }

        if (nosBrowserManager.Process.MainModule is null)
        {
            return new NotFoundError("Cannot find the main module of the target process.");
        }

        var staticAddress = (int)(nosBrowserManager.Process.MainModule.BaseAddress + unitObjectAddress.Offset);
        return new UnitManager(nosBrowserManager.Memory, staticAddress, options.UnitManagerOffsets);
    }

    private readonly IMemory _memory;
    private readonly int _staticUnitManagerAddress;
    private readonly int[] _optionsUnitManagerOffsets;

    /// <summary>
    /// Initializes a new instance of the <see cref="UnitManager"/> class.
    /// </summary>
    /// <param name="memory">The memory.</param>
    /// <param name="staticUnitManagerAddress">The pointer to the beginning of the player manager structure.</param>
    /// <param name="optionsUnitManagerOffsets">The unit manager offsets.</param>
    public UnitManager(IMemory memory, int staticUnitManagerAddress, int[] optionsUnitManagerOffsets)
        : base(memory, (nuint)staticUnitManagerAddress)
    {
        _memory = memory;
        _staticUnitManagerAddress = staticUnitManagerAddress;
        _optionsUnitManagerOffsets = optionsUnitManagerOffsets;
    }

    /// <summary>
    /// Gets the address to the player manager.
    /// </summary>
    public override nuint Address => _memory.FollowStaticAddressOffsets
        (_staticUnitManagerAddress, _optionsUnitManagerOffsets);
}
\ No newline at end of file

M src/Core/NosSmooth.LocalClient/CommandHandlers/Attack/AttackCommandHandler.cs => src/Core/NosSmooth.LocalClient/CommandHandlers/Attack/AttackCommandHandler.cs +6 -5
@@ 10,6 10,7 @@ using NosSmooth.Core.Commands.Attack;
using NosSmooth.Core.Commands.Control;
using NosSmooth.Core.Extensions;
using NosSmooth.LocalBinding;
using NosSmooth.LocalBinding.Hooks;
using NosSmooth.LocalBinding.Objects;
using NosSmooth.LocalBinding.Structs;
using Remora.Results;


@@ 23,7 24,7 @@ public class AttackCommandHandler : ICommandHandler<AttackCommand>
{
    private readonly INostaleClient _nostaleClient;
    private readonly NosThreadSynchronizer _synchronizer;
    private readonly UnitManagerBinding _unitManagerBinding;
    private readonly IEntityFocusHook _entityFocusHook;
    private readonly SceneManager _sceneManager;

    /// <summary>


@@ 31,19 32,19 @@ public class AttackCommandHandler : ICommandHandler<AttackCommand>
    /// </summary>
    /// <param name="nostaleClient">The NosTale client.</param>
    /// <param name="synchronizer">The thread synchronizer.</param>
    /// <param name="unitManagerBinding">The unit manager binding.</param>
    /// <param name="entityFocusHook">The entity focus hook.</param>
    /// <param name="sceneManager">The scene manager.</param>
    public AttackCommandHandler
    (
        INostaleClient nostaleClient,
        NosThreadSynchronizer synchronizer,
        UnitManagerBinding unitManagerBinding,
        IEntityFocusHook entityFocusHook,
        SceneManager sceneManager
    )
    {
        _nostaleClient = nostaleClient;
        _synchronizer = synchronizer;
        _unitManagerBinding = unitManagerBinding;
        _entityFocusHook = entityFocusHook;
        _sceneManager = sceneManager;
    }



@@ 55,7 56,7 @@ public class AttackCommandHandler : ICommandHandler<AttackCommand>
            var entityResult = _sceneManager.FindEntity(command.TargetId.Value);
            if (entityResult.IsDefined(out var entity))
            {
                _synchronizer.EnqueueOperation(() => _unitManagerBinding.FocusEntity(entity));
                _synchronizer.EnqueueOperation(() => _entityFocusHook.WrapperFunction(entity));
            }
        }


M src/Core/NosSmooth.LocalClient/CommandHandlers/Walk/PetWalkCommandHandler.cs => src/Core/NosSmooth.LocalClient/CommandHandlers/Walk/PetWalkCommandHandler.cs +25 -8
@@ 9,7 9,10 @@ using NosSmooth.Core.Client;
using NosSmooth.Core.Commands;
using NosSmooth.Core.Commands.Control;
using NosSmooth.Core.Commands.Walking;
using NosSmooth.LocalBinding;
using NosSmooth.LocalBinding.Hooks;
using NosSmooth.LocalBinding.Objects;
using NosSmooth.LocalBinding.Structs;
using Remora.Results;

namespace NosSmooth.LocalClient.CommandHandlers.Walk;


@@ 24,7 27,9 @@ public class PetWalkCommandHandler : ICommandHandler<PetWalkCommand>
    /// </summary>
    public const string PetWalkControlGroup = "PetWalk";

    private readonly PetManagerBinding _petManagerBinding;
    private readonly IPetWalkHook _petWalkHook;
    private readonly PetManagerList _petManagerList;
    private readonly NosThreadSynchronizer _threadSynchronizer;
    private readonly UserActionDetector _userActionDetector;
    private readonly INostaleClient _nostaleClient;
    private readonly WalkCommandHandlerOptions _options;


@@ 32,20 37,26 @@ public class PetWalkCommandHandler : ICommandHandler<PetWalkCommand>
    /// <summary>
    /// Initializes a new instance of the <see cref="PetWalkCommandHandler"/> class.
    /// </summary>
    /// <param name="petManagerBinding">The character object binding.</param>
    /// <param name="petWalkHook">The pet walk hook.</param>
    /// <param name="petManagerList">The pet manager list.</param>
    /// <param name="threadSynchronizer">The thread synchronizer.</param>
    /// <param name="userActionDetector">The user action detector.</param>
    /// <param name="nostaleClient">The nostale client.</param>
    /// <param name="options">The options.</param>
    public PetWalkCommandHandler
    (
        PetManagerBinding petManagerBinding,
        IPetWalkHook petWalkHook,
        PetManagerList petManagerList,
        NosThreadSynchronizer threadSynchronizer,
        UserActionDetector userActionDetector,
        INostaleClient nostaleClient,
        IOptions<WalkCommandHandlerOptions> options
    )
    {
        _options = options.Value;
        _petManagerBinding = petManagerBinding;
        _petWalkHook = petWalkHook;
        _petManagerList = petManagerList;
        _threadSynchronizer = threadSynchronizer;
        _userActionDetector = userActionDetector;
        _nostaleClient = nostaleClient;
    }


@@ 53,17 64,23 @@ public class PetWalkCommandHandler : ICommandHandler<PetWalkCommand>
    /// <inheritdoc/>
    public async Task<Result> HandleCommand(PetWalkCommand command, CancellationToken ct = default)
    {
        if (_petManagerBinding.PetManagerList.Length < command.PetSelector + 1)
        if (_petManagerList.Length < command.PetSelector + 1)
        {
            return new NotFoundError("Could not find the pet using the given selector.");
        }
        var petManager = _petManagerBinding.PetManagerList[command.PetSelector];
        var petManager = _petManagerList[command.PetSelector];

        var handler = new ControlCommandWalkHandler
        (
            _nostaleClient,
            async (x, y, ct) => await _userActionDetector.NotUserActionAsync
                (() => _petManagerBinding.PetWalk(command.PetSelector, x, y), ct),
            async (x, y, ct) =>
                await _threadSynchronizer.SynchronizeAsync
                (
                    () => _userActionDetector.NotUserAction<Result<bool>>
                    (
                        () => _petWalkHook.WrapperFunction(petManager, (ushort)x, (ushort)y)
                    )
                ),
            petManager,
            _options
        );

M src/Core/NosSmooth.LocalClient/CommandHandlers/Walk/PlayerWalkCommandHandler.cs => src/Core/NosSmooth.LocalClient/CommandHandlers/Walk/PlayerWalkCommandHandler.cs +22 -7
@@ 10,7 10,10 @@ using NosSmooth.Core.Commands;
using NosSmooth.Core.Commands.Control;
using NosSmooth.Core.Commands.Walking;
using NosSmooth.Core.Extensions;
using NosSmooth.LocalBinding;
using NosSmooth.LocalBinding.Hooks;
using NosSmooth.LocalBinding.Objects;
using NosSmooth.LocalBinding.Structs;
using Remora.Results;

namespace NosSmooth.LocalClient.CommandHandlers.Walk;


@@ 25,7 28,9 @@ public class PlayerWalkCommandHandler : ICommandHandler<PlayerWalkCommand>
    /// </summary>
    public const string PlayerWalkControlGroup = "PlayerWalk";

    private readonly PlayerManagerBinding _playerManagerBinding;
    private readonly PlayerManager _playerManager;
    private readonly IPlayerWalkHook _playerWalkHook;
    private readonly NosThreadSynchronizer _threadSynchronizer;
    private readonly UserActionDetector _userActionDetector;
    private readonly INostaleClient _nostaleClient;
    private readonly WalkCommandHandlerOptions _options;


@@ 33,20 38,26 @@ public class PlayerWalkCommandHandler : ICommandHandler<PlayerWalkCommand>
    /// <summary>
    /// Initializes a new instance of the <see cref="PlayerWalkCommandHandler"/> class.
    /// </summary>
    /// <param name="playerManagerBinding">The character object binding.</param>
    /// <param name="playerManager">The player manager.</param>
    /// <param name="playerWalkHook">The player walk hook.</param>
    /// <param name="threadSynchronizer">The thread synchronizer.</param>
    /// <param name="userActionDetector">The user action detector.</param>
    /// <param name="nostaleClient">The nostale client.</param>
    /// <param name="options">The options.</param>
    public PlayerWalkCommandHandler
    (
        PlayerManagerBinding playerManagerBinding,
        PlayerManager playerManager,
        IPlayerWalkHook playerWalkHook,
        NosThreadSynchronizer threadSynchronizer,
        UserActionDetector userActionDetector,
        INostaleClient nostaleClient,
        IOptions<WalkCommandHandlerOptions> options
    )
    {
        _options = options.Value;
        _playerManagerBinding = playerManagerBinding;
        _playerManager = playerManager;
        _playerWalkHook = playerWalkHook;
        _threadSynchronizer = threadSynchronizer;
        _userActionDetector = userActionDetector;
        _nostaleClient = nostaleClient;
    }


@@ 57,9 68,13 @@ public class PlayerWalkCommandHandler : ICommandHandler<PlayerWalkCommand>
        var handler = new ControlCommandWalkHandler
        (
            _nostaleClient,
            async (x, y, ct) =>
                await _userActionDetector.NotUserWalkAsync(_playerManagerBinding, x, y, ct),
            _playerManagerBinding.PlayerManager,
            async (x, y, ct)
                => await _threadSynchronizer.SynchronizeAsync
                (
                    () => _userActionDetector.NotUserWalk(_playerWalkHook, x, y),
                    ct
                ),
            _playerManager,
            _options
        );


M src/Core/NosSmooth.LocalClient/NostaleLocalClient.cs => src/Core/NosSmooth.LocalClient/NostaleLocalClient.cs +21 -24
@@ 14,6 14,7 @@ using NosSmooth.Core.Extensions;
using NosSmooth.Core.Packets;
using NosSmooth.LocalBinding;
using NosSmooth.LocalBinding.EventArgs;
using NosSmooth.LocalBinding.Hooks;
using NosSmooth.LocalBinding.Objects;
using NosSmooth.LocalBinding.Structs;
using NosSmooth.Packets;


@@ 34,9 35,7 @@ namespace NosSmooth.LocalClient;
public class NostaleLocalClient : BaseNostaleClient
{
    private readonly NosThreadSynchronizer _synchronizer;
    private readonly NetworkBinding _networkBinding;
    private readonly PlayerManagerBinding _playerManagerBinding;
    private readonly PetManagerBinding _petManagerBinding;
    private readonly IHookManager _hookManager;
    private readonly ControlCommands _controlCommands;
    private readonly IPacketSerializer _packetSerializer;
    private readonly PacketHandler _packetHandler;


@@ 51,9 50,7 @@ public class NostaleLocalClient : BaseNostaleClient
    /// Initializes a new instance of the <see cref="NostaleLocalClient"/> class.
    /// </summary>
    /// <param name="synchronizer">The thread synchronizer.</param>
    /// <param name="networkBinding">The network binding.</param>
    /// <param name="playerManagerBinding">The player manager binding.</param>
    /// <param name="petManagerBinding">The pet manager binding.</param>
    /// <param name="hookManager">The hook manager.</param>
    /// <param name="controlCommands">The control commands.</param>
    /// <param name="commandProcessor">The command processor.</param>
    /// <param name="packetSerializer">The packet serializer.</param>


@@ 65,9 62,7 @@ public class NostaleLocalClient : BaseNostaleClient
    public NostaleLocalClient
    (
        NosThreadSynchronizer synchronizer,
        NetworkBinding networkBinding,
        PlayerManagerBinding playerManagerBinding,
        PetManagerBinding petManagerBinding,
        IHookManager hookManager,
        ControlCommands controlCommands,
        CommandProcessor commandProcessor,
        IPacketSerializer packetSerializer,


@@ 81,9 76,7 @@ public class NostaleLocalClient : BaseNostaleClient
    {
        _options = options.Value;
        _synchronizer = synchronizer;
        _networkBinding = networkBinding;
        _playerManagerBinding = playerManagerBinding;
        _petManagerBinding = petManagerBinding;
        _hookManager = hookManager;
        _controlCommands = controlCommands;
        _packetSerializer = packetSerializer;
        _packetHandler = packetHandler;


@@ 98,12 91,12 @@ public class NostaleLocalClient : BaseNostaleClient
        _stopRequested = stopRequested;
        _logger.LogInformation("Starting local client");
        _synchronizer.StartSynchronizer();
        _networkBinding.PacketSendCall += SendCallCallback;
        _networkBinding.PacketReceiveCall += ReceiveCallCallback;
        _hookManager.PacketSend.Called += SendCallCallback;
        _hookManager.PacketReceive.Called += ReceiveCallCallback;

        _playerManagerBinding.FollowEntityCall += FollowEntity;
        _playerManagerBinding.WalkCall += Walk;
        _petManagerBinding.PetWalkCall += PetWalk;
        _hookManager.EntityFollow.Called += FollowEntity;
        _hookManager.PlayerWalk.Called += Walk;
        _hookManager.PetWalk.Called += PetWalk;

        try
        {


@@ 114,11 107,15 @@ public class NostaleLocalClient : BaseNostaleClient
            // ignored
        }

        _networkBinding.PacketSendCall -= SendCallCallback;
        _networkBinding.PacketReceiveCall -= ReceiveCallCallback;
        _playerManagerBinding.FollowEntityCall -= FollowEntity;
        _playerManagerBinding.WalkCall -= Walk;
        _petManagerBinding.PetWalkCall -= PetWalk;
        _hookManager.PacketSend.Called -= SendCallCallback;
        _hookManager.PacketReceive.Called -= ReceiveCallCallback;

        _hookManager.EntityFollow.Called -= FollowEntity;
        _hookManager.PlayerWalk.Called -= Walk;
        _hookManager.PetWalk.Called -= PetWalk;

        // the hooks are not needed anymore.
        _hookManager.DisableAll();

        return Result.FromSuccess();
    }


@@ 187,7 184,7 @@ public class NostaleLocalClient : BaseNostaleClient
    {
        _synchronizer.EnqueueOperation
        (
            () => _networkBinding.SendPacket(packetString)
            () => _hookManager.PacketSend.WrapperFunction(packetString)
        );
        _logger.LogDebug($"Sending client packet {packetString}");
    }


@@ 196,7 193,7 @@ public class NostaleLocalClient : BaseNostaleClient
    {
        _synchronizer.EnqueueOperation
        (
            () => _networkBinding.ReceivePacket(packetString)
            () => _hookManager.PacketReceive.WrapperFunction(packetString)
        );
        _logger.LogDebug($"Receiving client packet {packetString}");
    }

M src/Core/NosSmooth.LocalClient/UserActionDetector.cs => src/Core/NosSmooth.LocalClient/UserActionDetector.cs +52 -6
@@ 5,6 5,7 @@
//  Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System.Data.SqlTypes;
using NosSmooth.LocalBinding.Hooks;
using NosSmooth.LocalBinding.Objects;
using NosSmooth.LocalBinding.Structs;
using Remora.Results;


@@ 50,6 51,36 @@ public class UserActionDetector
    /// Execute an action that makes sure walk won't be treated as a user action.
    /// </summary>
    /// <param name="action">The action to execute.</param>
    /// <param name="ct">The cancellation token for cancelling the operation.</param>
    /// <returns>Return value of the action.</returns>
    public async Task NotUserActionAsync(Action action, CancellationToken ct = default)
    {
        await _semaphore.WaitAsync(ct);
        _handlingDisabled = true;
        action();
        _handlingDisabled = false;
        _semaphore.Release();
    }

    /// <summary>
    /// Execute an action that makes sure walk won't be treated as a user action.
    /// </summary>
    /// <param name="action">The action to execute.</param>
    /// <returns>A result that may or may not have succeeded.</returns>
    public Result NotUserAction(Func<Result> action)
    {
        _semaphore.Wait();
        _handlingDisabled = true;
        var result = action();
        _handlingDisabled = false;
        _semaphore.Release();
        return result;
    }

    /// <summary>
    /// Execute an action that makes sure walk won't be treated as a user action.
    /// </summary>
    /// <param name="action">The action to execute.</param>
    /// <typeparam name="T">The return type.</typeparam>
    /// <returns>Return value of the action.</returns>
    public T NotUserAction<T>(Func<T> action)


@@ 65,23 96,38 @@ public class UserActionDetector
    /// <summary>
    /// Execute walk action and make sure walk won't be treated as a user action.
    /// </summary>
    /// <param name="playerManager">The player manager.</param>
    /// <param name="walkHook">The walk hook.</param>
    /// <param name="x">The x coordinate.</param>
    /// <param name="y">The y coordinate.</param>
    /// <returns>The result of the walk.</returns>
    public Result<bool> NotUserWalk(IPlayerWalkHook walkHook, short x, short y)
        => NotUserAction
        (
            () =>
            {
                _lastWalkPosition = ((ushort)x, (ushort)y);
                return walkHook.WrapperFunction((ushort)x, (ushort)y);
            }
        );

    /// <summary>
    /// Execute walk action and make sure walk won't be treated as a user action.
    /// </summary>
    /// <param name="walkHook">The walk hook.</param>
    /// <param name="x">The x coordinate.</param>
    /// <param name="y">The y coordinate.</param>
    /// <param name="ct">The cancellation token used for cancelling the operation.</param>
    /// <returns>The result of the walk.</returns>
    public async Task<Result<bool>> NotUserWalkAsync(PlayerManagerBinding playerManager, short x, short y, CancellationToken ct = default)
    {
        return await NotUserActionAsync
    public async Task<Result<bool>> NotUserWalkAsync(IPlayerWalkHook walkHook, short x, short y, CancellationToken ct = default)
        => await NotUserActionAsync
        (
            () =>
            {
                _lastWalkPosition = ((ushort)x, (ushort)y);
                return playerManager.Walk(x, y);
                return walkHook.WrapperFunction((ushort)x, (ushort)y);
            },
            ct
        );
    }

    /// <summary>
    /// Checks whether the given Walk call

R src/Core/NosSmooth.LocalBinding/Options/PeriodicBindingOptions.cs => src/Extensions/NosSmooth.Extensions.SharedBinding/EventArgs/HookStateEventArgs.cs +15 -11
@@ 1,21 1,25 @@
//
//  PeriodicBindingOptions.cs
//  HookStateEventArgs.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 NosSmooth.LocalBinding.Objects;
namespace NosSmooth.Extensions.SharedBinding.EventArgs;

namespace NosSmooth.LocalBinding.Options;

/// <summary>
/// Options for <see cref="PeriodicBinding"/>.
/// </summary>
public class PeriodicBindingOptions
/// <inheritdoc />
public class HookStateEventArgs : System.EventArgs
{
    /// <summary>
    /// Gets or sets the configuration for any periodic function hook.
    /// Initializes a new instance of the <see cref="HookStateEventArgs"/> class.
    /// </summary>
    /// <param name="enabled">The new state.</param>
    public HookStateEventArgs(bool enabled)
    {
        Enabled = enabled;
    }

    /// <summary>
    /// Gets the new state.
    /// </summary>
    public HookOptions PeriodicHook { get; set; }
        = new HookOptions(true, "55 8B EC 53 56 83 C4", 0);
    public bool Enabled { get; }
}
\ No newline at end of file

A src/Extensions/NosSmooth.Extensions.SharedBinding/Extensions/ServiceCollectionExtensions.cs => src/Extensions/NosSmooth.Extensions.SharedBinding/Extensions/ServiceCollectionExtensions.cs +104 -0
@@ 0,0 1,104 @@
//
//  ServiceCollectionExtensions.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 Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using NosSmooth.Data.NOSFiles;
using NosSmooth.Extensions.SharedBinding.Hooks;
using NosSmooth.LocalBinding;
using NosSmooth.LocalBinding.Extensions;
using NosSmooth.LocalBinding.Hooks;
using NosSmooth.PacketSerializer.Packets;

namespace NosSmooth.Extensions.SharedBinding.Extensions;

/// <summary>
/// Extension methods for <see cref="IServiceProvider"/>.
/// </summary>
public static class ServiceCollectionExtensions
{
    /// <summary>
    /// Adds shared <see cref="IHookManager"/>.
    /// </summary>
    /// <param name="serviceCollection">The collection.</param>
    /// <returns>The same collection.</returns>
    public static IServiceCollection ShareHooks(this IServiceCollection serviceCollection)
    {
        var originalHookManager = serviceCollection
            .Last(x => x.ServiceType == typeof(IHookManager));

        var sharedHookManager = ServiceDescriptor.Singleton<SharedHookManager>
        (
            p =>
            {
                var sharedHookManager = p.GetRequiredService<SharedManager>().GetShared<IHookManager>(p);
                return new SharedHookManager(sharedHookManager);
            }
        );

        return serviceCollection
            .Configure<SharedOptions>(o => o.AddDescriptor(originalHookManager))
            .Configure<SharedOptions>(o => o.AddDescriptor(sharedHookManager))
            .AddSingleton<SharedHookManager>(p => SharedManager.Instance.GetShared<SharedHookManager>(p))
            .Replace(ServiceDescriptor.Singleton<IHookManager, SingleHookManager>());
    }

    /// <summary>
    /// Replaces <typeparamref name="T"/>
    /// with shared equivalent. That allows for multiple programs injected inside NosTale.
    /// </summary>
    /// <param name="serviceCollection">The collection.</param>
    /// <typeparam name="T">The shared type.</typeparam>
    /// <returns>The same collection.</returns>
    public static IServiceCollection Share<T>(this IServiceCollection serviceCollection)
        where T : class
    {
        var original = serviceCollection
            .Last(x => x.ServiceType == typeof(T));

        return serviceCollection
            .Configure<SharedOptions>(o => o.AddDescriptor(original))
            .Replace
            (
                ServiceDescriptor.Singleton<T>(p => SharedManager.Instance.GetShared<T>(p))
            );
    }

    /// <summary>
    /// Tries to replace <see cref="T"/>
    /// with shared equivalent. That allows for multiple programs injected inside NosTale.
    /// </summary>
    /// <param name="serviceCollection">The collection.</param>
    /// <typeparam name="T">The shared type.</typeparam>
    /// <returns>The same collection.</returns>
    public static IServiceCollection TryShare<T>(this IServiceCollection serviceCollection)
        where T : class
    {
        if (serviceCollection.Any(x => x.ServiceType == typeof(T)))
        {
            return serviceCollection.Share<T>();
        }

        return serviceCollection;
    }

    /// <summary>
    /// Replaces some NosSmooth types with their shared equivalents.
    /// That allows for multiple programs injected inside NosTale.
    /// </summary>
    /// <param name="serviceCollection">The collection.</param>
    /// <returns>The same collection.</returns>
    public static IServiceCollection ShareNosSmooth(this IServiceCollection serviceCollection)
    {
        return serviceCollection
            .AddSingleton<SharedManager>(p => SharedManager.Instance)
            .ShareHooks()
            .TryShare<NosBrowserManager>()
            .TryShare<IPacketTypesRepository>()
            .TryShare<NostaleDataFilesManager>();
    }
}
\ No newline at end of file

A src/Extensions/NosSmooth.Extensions.SharedBinding/Hooks/SharedHookManager.cs => src/Extensions/NosSmooth.Extensions.SharedBinding/Hooks/SharedHookManager.cs +174 -0
@@ 0,0 1,174 @@
//
//  SharedHookManager.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 NosSmooth.Extensions.SharedBinding.Hooks.Specific;
using NosSmooth.LocalBinding;
using NosSmooth.LocalBinding.Hooks;
using NosSmooth.LocalBinding.Options;
using Remora.Results;

namespace NosSmooth.Extensions.SharedBinding.Hooks;

/// <summary>
/// A hook manager managing <see cref="SingleHookManager"/>s of all of the instances.
/// </summary>
public class SharedHookManager
{
    private readonly IHookManager _underlyingManager;

    private bool _initialized;
    private Dictionary<string, int> _hookedCount;

    /// <summary>
    /// Initializes a new instance of the <see cref="SharedHookManager"/> class.
    /// </summary>
    /// <param name="underlyingManager">The underlying hook manager.</param>
    /// <param name="bindingManager">The binding manager.</param>
    /// <param name="browserManager">The browser manager.</param>
    public SharedHookManager
    (
        IHookManager underlyingManager
    )
    {
        _hookedCount = new Dictionary<string, int>();
        _underlyingManager = underlyingManager;
    }

    /// <summary>
    /// Initialize a shared NosSmooth instance.
    /// </summary>
    /// <param name="bindingManager">The binding manager.</param>
    /// <param name="browserManager">The browser manager.</param>
    /// <param name="options">The initial options to be respected.</param>
    /// <returns>The dictionary containing all of the hooks.</returns>
    public Result<Dictionary<string, INostaleHook>> InitializeInstance
        (NosBindingManager bindingManager, NosBrowserManager browserManager, HookManagerOptions options)
    {
        if (!_initialized)
        {
            _underlyingManager.Initialize(bindingManager, browserManager);
            _initialized = true;
        }

        var hooks = new Dictionary<string, INostaleHook>();

        // TODO: initialize using reflection
        hooks.Add
        (
            _underlyingManager.Periodic.Name,
            InitializeSingleHook
            (
                new PeriodicHook(_underlyingManager.Periodic),
                options.PeriodicHook
            )
        );

        hooks.Add
        (
            _underlyingManager.EntityFocus.Name,
            InitializeSingleHook
            (
                new EntityFocusHook(_underlyingManager.EntityFocus),
                options.EntityFocusHook
            )
        );

        hooks.Add
        (
            _underlyingManager.EntityFollow.Name,
            InitializeSingleHook
            (
                new EntityFollowHook(_underlyingManager.EntityFollow),
                options.EntityFollowHook
            )
        );

        hooks.Add
        (
            _underlyingManager.EntityUnfollow.Name,
            InitializeSingleHook
            (
                new EntityUnfollowHook(_underlyingManager.EntityUnfollow),
                options.EntityUnfollowHook
            )
        );

        hooks.Add
        (
            _underlyingManager.PacketReceive.Name,
            InitializeSingleHook
            (
                new PacketReceiveHook(_underlyingManager.PacketReceive),
                options.PacketReceiveHook
            )
        );

        hooks.Add
        (
            _underlyingManager.PacketSend.Name,
            InitializeSingleHook
            (
                new PacketSendHook(_underlyingManager.PacketSend),
                options.PacketSendHook
            )
        );

        hooks.Add
        (
            _underlyingManager.PetWalk.Name,
            InitializeSingleHook
            (
                new PetWalkHook(_underlyingManager.PetWalk),
                options.PetWalkHook
            )
        );

        hooks.Add
        (
            _underlyingManager.PlayerWalk.Name,
            InitializeSingleHook
            (
                new PlayerWalkHook(_underlyingManager.PlayerWalk),
                options.PlayerWalkHook
            )
        );

        return hooks;
    }

    private INostaleHook<TFunction, TWrapperFunction, TEventArgs> InitializeSingleHook<TFunction, TWrapperFunction,
        TEventArgs>(SingleHook<TFunction, TWrapperFunction, TEventArgs> hook, HookOptions options)
        where TFunction : Delegate
        where TWrapperFunction : Delegate
        where TEventArgs : System.EventArgs
    {
        hook.StateChanged += (_, state) =>
        {
            if (!_hookedCount.ContainsKey(hook.Name))
            {
                _hookedCount[hook.Name] = 0;
            }

            _hookedCount[hook.Name] += state.Enabled ? 1 : -1;

            if (state.Enabled)
            {
                _underlyingManager.Enable(new[] { hook.Name });
            }
            else if (_hookedCount[hook.Name] == 0)
            {
                _underlyingManager.Disable(new[] { hook.Name });
            }
        };

        if (options.Hook)
        {
            hook.Enable();
        }

        return hook;
    }
}
\ No newline at end of file

A src/Extensions/NosSmooth.Extensions.SharedBinding/Hooks/SingleHook.cs => src/Extensions/NosSmooth.Extensions.SharedBinding/Hooks/SingleHook.cs +86 -0
@@ 0,0 1,86 @@
//
//  SingleHook.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.ComponentModel;
using NosSmooth.Extensions.SharedBinding.EventArgs;
using NosSmooth.LocalBinding.Hooks;
using Remora.Results;

namespace NosSmooth.Extensions.SharedBinding.Hooks;

/// <summary>
/// A hook for a single instance of NosSmooth sharing with the rest of application.
/// </summary>
/// <typeparam name="TFunction">The function delegate.</typeparam>
/// <typeparam name="TWrapperFunction">A wrapper function that abstracts the call to original function. May get the neccessary object to call the function and accept only relevant arguments.</typeparam>
/// <typeparam name="TEventArgs">The event args used in case of a call.</typeparam>
public class SingleHook<TFunction, TWrapperFunction, TEventArgs> : INostaleHook<TFunction, TWrapperFunction, TEventArgs>
    where TFunction : Delegate
    where TWrapperFunction : Delegate
    where TEventArgs : System.EventArgs
{
    private readonly INostaleHook<TFunction, TWrapperFunction, TEventArgs> _underlyingHook;

    /// <summary>
    /// Initializes a new instance of the <see cref="SingleHook{TFunction, TWrapperFunction, TEventArgs}"/> class.
    /// </summary>
    /// <param name="underlyingHook">The underlying hook.</param>
    public SingleHook(INostaleHook<TFunction, TWrapperFunction, TEventArgs> underlyingHook)
    {
        _underlyingHook = underlyingHook;
    }

    /// <summary>
    /// Called upon Enable or Disable.
    /// </summary>
    public event EventHandler<HookStateEventArgs>? StateChanged;

    /// <inheritdoc />
    public string Name => _underlyingHook.Name;

    /// <inheritdoc />
    public bool IsEnabled { get; private set; }

    /// <inheritdoc />
    public Result Enable()
    {
        if (!IsEnabled)
        {
            IsEnabled = true;
            StateChanged?.Invoke(this, new HookStateEventArgs(true));
            _underlyingHook.Called += FireCalled;
        }

        return Result.FromSuccess();
    }

    /// <inheritdoc />
    public Result Disable()
    {
        if (IsEnabled)
        {
            IsEnabled = true;
            StateChanged?.Invoke(this, new HookStateEventArgs(false));
            _underlyingHook.Called -= FireCalled;
        }

        return Result.FromSuccess();
    }

    private void FireCalled(object? owner, TEventArgs eventArgs)
    {
        Called?.Invoke(this, eventArgs);
    }

    /// <inheritdoc />
    public TWrapperFunction WrapperFunction => _underlyingHook.WrapperFunction;

    /// <inheritdoc />
    public TFunction OriginalFunction => _underlyingHook.OriginalFunction;

    /// <inheritdoc />
    public event EventHandler<TEventArgs>? Called;
}
\ No newline at end of file

A src/Extensions/NosSmooth.Extensions.SharedBinding/Hooks/SingleHookManager.cs => src/Extensions/NosSmooth.Extensions.SharedBinding/Hooks/SingleHookManager.cs +125 -0
@@ 0,0 1,125 @@
//
//  SingleHookManager.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.Options;
using NosSmooth.LocalBinding;
using NosSmooth.LocalBinding.Hooks;
using NosSmooth.LocalBinding.Options;
using Remora.Results;

namespace NosSmooth.Extensions.SharedBinding.Hooks;

/// <summary>
/// A hook manager for a single NosSmooth instance using shared data.
/// </summary>
public class SingleHookManager : IHookManager
{
    private readonly SharedHookManager _sharedHookManager;
    private readonly HookManagerOptions _options;
    private Dictionary<string, INostaleHook> _hooks;

    /// <summary>
    /// Initializes a new instance of the <see cref="SingleHookManager"/> class.
    /// </summary>
    /// <param name="sharedHookManager">The shared hook manager.</param>
    /// <param name="options">The hook options.</param>
    public SingleHookManager(SharedHookManager sharedHookManager, IOptions<HookManagerOptions> options)
    {
        _hooks = new Dictionary<string, INostaleHook>();
        _sharedHookManager = sharedHookManager;
        _options = options.Value;
    }

    /// <inheritdoc />
    public IPacketSendHook PacketSend => GetHook<IPacketSendHook>(IHookManager.PacketSendName);

    /// <inheritdoc />
    public IPacketReceiveHook PacketReceive => GetHook<IPacketReceiveHook>(IHookManager.PacketReceiveName);

    /// <inheritdoc />
    public IPlayerWalkHook PlayerWalk => GetHook<IPlayerWalkHook>(IHookManager.CharacterWalkName);

    /// <inheritdoc />
    public IEntityFollowHook EntityFollow => GetHook<IEntityFollowHook>(IHookManager.EntityFollowName);

    /// <inheritdoc />
    public IEntityUnfollowHook EntityUnfollow => GetHook<IEntityUnfollowHook>(IHookManager.EntityUnfollowName);

    /// <inheritdoc />
    public IPetWalkHook PetWalk => GetHook<IPetWalkHook>(IHookManager.PetWalkName);

    /// <inheritdoc />
    public IEntityFocusHook EntityFocus => GetHook<IEntityFocusHook>(IHookManager.EntityFocusName);

    /// <inheritdoc />
    public IPeriodicHook Periodic => GetHook<IPeriodicHook>(IHookManager.PeriodicName);

    /// <inheritdoc />
    public IReadOnlyList<INostaleHook> Hooks => _hooks.Values.ToList();

    /// <inheritdoc />
    public IResult Initialize(NosBindingManager bindingManager, NosBrowserManager browserManager)
    {
        var hooksResult = _sharedHookManager.InitializeInstance(bindingManager, browserManager, _options);
        if (!hooksResult.IsDefined(out var hooks))
        {
            return hooksResult;
        }

        _hooks = hooks;
        return Result.FromSuccess();
    }

    /// <inheritdoc />
    public void Enable(IEnumerable<string> names)
    {
        foreach (var name in names)
        {
            var hook = GetHook<INostaleHook>(name);
            hook.Enable();
        }
    }

    /// <inheritdoc />
    public void Disable(IEnumerable<string> names)
    {
        foreach (var name in names)
        {
            var hook = GetHook<INostaleHook>(name);
            hook.Disable();
        }
    }

    /// <inheritdoc />
    public void DisableAll()
    {
        foreach (var hook in _hooks.Values)
        {
            hook.Disable();
        }
    }

    /// <inheritdoc />
    public void EnableAll()
    {
        foreach (var hook in _hooks.Values)
        {
            hook.Enable();
        }
    }

    private T GetHook<T>(string name)
        where T : INostaleHook
    {
        if (!_hooks.ContainsKey(name) || _hooks[name] is not T typed)
        {
            throw new InvalidOperationException
                ($"Could not load hook {name}. Did you forget to call IHookManager.Initialize?");
        }

        return typed;
    }
}
\ No newline at end of file

A src/Extensions/NosSmooth.Extensions.SharedBinding/Hooks/Specific/EntityFocusHook.cs => src/Extensions/NosSmooth.Extensions.SharedBinding/Hooks/Specific/EntityFocusHook.cs +24 -0
@@ 0,0 1,24 @@
//
//  EntityFocusHook.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 NosSmooth.LocalBinding.EventArgs;
using NosSmooth.LocalBinding.Hooks;

namespace NosSmooth.Extensions.SharedBinding.Hooks.Specific;

/// <inheritdoc />
internal class EntityFocusHook : SingleHook<IEntityFocusHook.EntityFocusDelegate,
    IEntityFocusHook.EntityFocusWrapperDelegate, EntityEventArgs>, IEntityFocusHook
{
    /// <summary>
    /// Initializes a new instance of the <see cref="EntityFocusHook"/> class.
    /// </summary>
    /// <param name="underlyingHook">The underlying hook.</param>
    public EntityFocusHook(INostaleHook<IEntityFocusHook.EntityFocusDelegate, IEntityFocusHook.EntityFocusWrapperDelegate, EntityEventArgs> underlyingHook)
        : base(underlyingHook)
    {
    }
}
\ No newline at end of file

A src/Extensions/NosSmooth.Extensions.SharedBinding/Hooks/Specific/EntityFollowHook.cs => src/Extensions/NosSmooth.Extensions.SharedBinding/Hooks/Specific/EntityFollowHook.cs +24 -0
@@ 0,0 1,24 @@
//
//  EntityFollowHook.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 NosSmooth.LocalBinding.EventArgs;
using NosSmooth.LocalBinding.Hooks;

namespace NosSmooth.Extensions.SharedBinding.Hooks.Specific;

/// <inheritdoc />
internal class EntityFollowHook : SingleHook<IEntityFollowHook.EntityFollowDelegate,
    IEntityFollowHook.EntityFollowWrapperDelegate, EntityEventArgs>, IEntityFollowHook
{
    /// <summary>
    /// Initializes a new instance of the <see cref="EntityFollowHook"/> class.
    /// </summary>
    /// <param name="underlyingHook">The underlying hook.</param>
    public EntityFollowHook(INostaleHook<IEntityFollowHook.EntityFollowDelegate, IEntityFollowHook.EntityFollowWrapperDelegate, EntityEventArgs> underlyingHook)
        : base(underlyingHook)
    {
    }
}
\ No newline at end of file

A src/Extensions/NosSmooth.Extensions.SharedBinding/Hooks/Specific/EntityUnfollowHook.cs => src/Extensions/NosSmooth.Extensions.SharedBinding/Hooks/Specific/EntityUnfollowHook.cs +24 -0
@@ 0,0 1,24 @@
//
//  EntityUnfollowHook.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 NosSmooth.LocalBinding.EventArgs;
using NosSmooth.LocalBinding.Hooks;

namespace NosSmooth.Extensions.SharedBinding.Hooks.Specific;

/// <inheritdoc />
internal class EntityUnfollowHook : SingleHook<IEntityUnfollowHook.EntityUnfollowDelegate,
    IEntityUnfollowHook.EntityUnfollowWrapperDelegate, EntityEventArgs>, IEntityUnfollowHook
{
    /// <summary>
    /// Initializes a new instance of the <see cref="EntityUnfollowHook"/> class.
    /// </summary>
    /// <param name="underlyingHook">The underlying hook.</param>
    public EntityUnfollowHook(INostaleHook<IEntityUnfollowHook.EntityUnfollowDelegate, IEntityUnfollowHook.EntityUnfollowWrapperDelegate, EntityEventArgs> underlyingHook)
        : base(underlyingHook)
    {
    }
}
\ No newline at end of file

A src/Extensions/NosSmooth.Extensions.SharedBinding/Hooks/Specific/PacketReceiveHook.cs => src/Extensions/NosSmooth.Extensions.SharedBinding/Hooks/Specific/PacketReceiveHook.cs +24 -0
@@ 0,0 1,24 @@
//
//  PacketReceiveHook.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 NosSmooth.LocalBinding.EventArgs;
using NosSmooth.LocalBinding.Hooks;

namespace NosSmooth.Extensions.SharedBinding.Hooks.Specific;

/// <inheritdoc />
internal class PacketReceiveHook : SingleHook<IPacketReceiveHook.PacketReceiveDelegate,
    IPacketReceiveHook.PacketReceiveWrapperDelegate, PacketEventArgs>, IPacketReceiveHook
{
    /// <summary>
    /// Initializes a new instance of the <see cref="PacketReceiveHook"/> class.
    /// </summary>
    /// <param name="underlyingHook">The underlying hook.</param>
    public PacketReceiveHook(INostaleHook<IPacketReceiveHook.PacketReceiveDelegate, IPacketReceiveHook.PacketReceiveWrapperDelegate, PacketEventArgs> underlyingHook)
        : base(underlyingHook)
    {
    }
}
\ No newline at end of file

A src/Extensions/NosSmooth.Extensions.SharedBinding/Hooks/Specific/PacketSendHook.cs => src/Extensions/NosSmooth.Extensions.SharedBinding/Hooks/Specific/PacketSendHook.cs +24 -0
@@ 0,0 1,24 @@
//
//  PacketSendHook.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 NosSmooth.LocalBinding.EventArgs;
using NosSmooth.LocalBinding.Hooks;

namespace NosSmooth.Extensions.SharedBinding.Hooks.Specific;

/// <inheritdoc />
internal class PacketSendHook : SingleHook<IPacketSendHook.PacketSendDelegate,
    IPacketSendHook.PacketSendWrapperDelegate, PacketEventArgs>, IPacketSendHook
{
    /// <summary>
    /// Initializes a new instance of the <see cref="PacketSendHook"/> class.
    /// </summary>
    /// <param name="underlyingHook">The underlying hook.</param>
    public PacketSendHook(INostaleHook<IPacketSendHook.PacketSendDelegate, IPacketSendHook.PacketSendWrapperDelegate, PacketEventArgs> underlyingHook)
        : base(underlyingHook)
    {
    }
}
\ No newline at end of file

A src/Extensions/NosSmooth.Extensions.SharedBinding/Hooks/Specific/PeriodicHook.cs => src/Extensions/NosSmooth.Extensions.SharedBinding/Hooks/Specific/PeriodicHook.cs +26 -0
@@ 0,0 1,26 @@
//
//  PeriodicHook.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 NosSmooth.LocalBinding.Hooks;

namespace NosSmooth.Extensions.SharedBinding.Hooks.Specific;

/// <summary>
/// A hook of a periodic function,
/// preferably called every frame.
/// </summary>
internal class PeriodicHook :
    SingleHook<IPeriodicHook.PeriodicDelegate, IPeriodicHook.PeriodicDelegate, System.EventArgs>, IPeriodicHook
{
    /// <summary>
    /// Initializes a new instance of the <see cref="PeriodicHook"/> class.
    /// </summary>
    /// <param name="underlyingHook">The underlying hook.</param>
    public PeriodicHook(INostaleHook<IPeriodicHook.PeriodicDelegate, IPeriodicHook.PeriodicDelegate, System.EventArgs> underlyingHook)
        : base(underlyingHook)
    {
    }
}
\ No newline at end of file

A src/Extensions/NosSmooth.Extensions.SharedBinding/Hooks/Specific/PetWalkHook.cs => src/Extensions/NosSmooth.Extensions.SharedBinding/Hooks/Specific/PetWalkHook.cs +23 -0
@@ 0,0 1,23 @@
//
//  PetWalkHook.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 NosSmooth.LocalBinding.EventArgs;
using NosSmooth.LocalBinding.Hooks;

namespace NosSmooth.Extensions.SharedBinding.Hooks.Specific;

/// <inheritdoc />
internal class PetWalkHook : SingleHook<IPetWalkHook.PetWalkDelegate, IPetWalkHook.PetWalkWrapperDelegate, PetWalkEventArgs>, IPetWalkHook
{
    /// <summary>
    /// Initializes a new instance of the <see cref="PetWalkHook"/> class.
    /// </summary>
    /// <param name="underlyingHook">The underlying hook.</param>
    public PetWalkHook(INostaleHook<IPetWalkHook.PetWalkDelegate, IPetWalkHook.PetWalkWrapperDelegate, PetWalkEventArgs> underlyingHook)
        : base(underlyingHook)
    {
    }
}
\ No newline at end of file

A src/Extensions/NosSmooth.Extensions.SharedBinding/Hooks/Specific/PlayerWalkHook.cs => src/Extensions/NosSmooth.Extensions.SharedBinding/Hooks/Specific/PlayerWalkHook.cs +23 -0
@@ 0,0 1,23 @@
//
//  PlayerWalkHook.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 NosSmooth.LocalBinding.EventArgs;
using NosSmooth.LocalBinding.Hooks;

namespace NosSmooth.Extensions.SharedBinding.Hooks.Specific;

/// <inheritdoc />
internal class PlayerWalkHook : SingleHook<IPlayerWalkHook.WalkDelegate, IPlayerWalkHook.WalkWrapperDelegate, WalkEventArgs>, IPlayerWalkHook
{
    /// <summary>
    /// Initializes a new instance of the <see cref="PlayerWalkHook"/> class.
    /// </summary>
    /// <param name="underlyingHook">The underlying hook.</param>
    public PlayerWalkHook(INostaleHook<IPlayerWalkHook.WalkDelegate, IPlayerWalkHook.WalkWrapperDelegate, WalkEventArgs> underlyingHook)
        : base(underlyingHook)
    {
    }
}
\ No newline at end of file

A src/Extensions/NosSmooth.Extensions.SharedBinding/NosSmooth.Extensions.SharedBinding.csproj => src/Extensions/NosSmooth.Extensions.SharedBinding/NosSmooth.Extensions.SharedBinding.csproj +19 -0
@@ 0,0 1,19 @@
<Project Sdk="Microsoft.NET.Sdk">

    <PropertyGroup>
        <TargetFramework>net7.0</TargetFramework>
        <ImplicitUsings>enable</ImplicitUsings>
        <Nullable>enable</Nullable>
    </PropertyGroup>

    <ItemGroup>
      <PackageReference Include="NosSmooth.Core" Version="3.0.1" />
      <PackageReference Include="NosSmooth.Data.NOSFiles" Version="2.0.2" />
      <PackageReference Include="NosSmooth.PacketSerializer" Version="2.0.0" />
    </ItemGroup>

    <ItemGroup>
      <ProjectReference Include="..\..\Core\NosSmooth.LocalBinding\NosSmooth.LocalBinding.csproj" />
    </ItemGroup>

</Project>

A src/Extensions/NosSmooth.Extensions.SharedBinding/SharedManager.cs => src/Extensions/NosSmooth.Extensions.SharedBinding/SharedManager.cs +93 -0
@@ 0,0 1,93 @@
//
//  SharedManager.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 Microsoft.Extensions.Options;
using NosSmooth.Data.NOSFiles;
using NosSmooth.LocalBinding;
using NosSmooth.PacketSerializer.Packets;

namespace NosSmooth.Extensions.SharedBinding;

/// <summary>
/// Manager for sharing <see cref="NosBindingManager"/>,
/// <see cref="NostaleDataFilesManager"/> and
/// <see cref="IPacketTypesRepository"/>.
/// </summary>
public class SharedManager
{
    private static SharedManager? _instance;
    private Dictionary<Type, object> _sharedData = new();

    /// <summary>
    /// A singleton instance.
    /// One per process.
    /// </summary>
    public static SharedManager Instance
    {
        get
        {
            if (_instance is null)
            {
                _instance = new SharedManager();
            }

            return _instance;
        }
    }

    private SharedManager()
    {
    }

    /// <summary>
    /// Get shared equivalent of the given type.
    /// </summary>
    /// <param name="services">The service provider.</param>
    /// <typeparam name="T">The type to get shared instance of.</typeparam>
    /// <returns>The shared instance.</returns>
    /// <exception cref="InvalidOperationException">Thrown in case the type is not shared.</exception>
    public T GetShared<T>(IServiceProvider services)
        where T : class
    {
        if (!_sharedData.ContainsKey(typeof(T)))
        {
            _sharedData[typeof(T)] = CreateShared<T>(services);
        }

        return (T)_sharedData[typeof(T)];
    }

    private T CreateShared<T>(IServiceProvider services)
        where T : class
    {
        var options = services.GetRequiredService<IOptions<SharedOptions>>();
        var descriptor = options.Value.GetDescriptor(typeof(T));

        if (descriptor is null)
        {
            throw new InvalidOperationException
                ($"Could not find {typeof(T)} in the service provider when trying to make a shared instance.");
        }

        if (descriptor.ImplementationInstance is not null)
        {
            return (T)descriptor.ImplementationInstance;
        }

        if (descriptor.ImplementationFactory is not null)
        {
            return (T)descriptor.ImplementationFactory(services);
        }

        if (descriptor.ImplementationType is not null)
        {
            return (T)ActivatorUtilities.CreateInstance(services, descriptor.ImplementationType);
        }

        return ActivatorUtilities.CreateInstance<T>(services);
    }
}
\ No newline at end of file

A src/Extensions/NosSmooth.Extensions.SharedBinding/SharedOptions.cs => src/Extensions/NosSmooth.Extensions.SharedBinding/SharedOptions.cs +45 -0
@@ 0,0 1,45 @@
//
//  SharedOptions.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.Data.NOSFiles;
using NosSmooth.LocalBinding;
using NosSmooth.PacketSerializer.Packets;

namespace NosSmooth.Extensions.SharedBinding;

/// <summary>
/// Options for <see cref="SharedManager"/>.
/// </summary>
internal class SharedOptions
{
    private Dictionary<Type, ServiceDescriptor> _descriptors = new();

    /// <summary>
    /// Add service descriptor for given type.
    /// </summary>
    /// <param name="descriptor">The service descriptor.</param>
    public void AddDescriptor(ServiceDescriptor descriptor)
    {
        var type = descriptor.ServiceType;
        if (_descriptors.ContainsKey(type))
        {
            return;
        }

        _descriptors[type] = descriptor;
    }

    /// <summary>
    /// Get descriptor for the given type.
    /// </summary>
    /// <param name="type">The type of the descriptor.</param>
    /// <returns>A descriptor.</returns>
    public ServiceDescriptor GetDescriptor(Type type)
    {
        return _descriptors[type];
    }
}
\ No newline at end of file

M src/Samples/External/ExternalBrowser/Program.cs => src/Samples/External/ExternalBrowser/Program.cs +13 -2
@@ 8,6 8,7 @@ using System.Diagnostics;
using NosSmooth.Core.Extensions;
using NosSmooth.LocalBinding;
using NosSmooth.LocalBinding.Options;
using NosSmooth.LocalBinding.Structs;

namespace ExternalBrowser;



@@ 25,6 26,8 @@ public class Program
        var playerManagerOptions = new PlayerManagerOptions();
        var sceneManagerOptions = new SceneManagerOptions();
        var petManagerOptions = new PetManagerOptions();
        var networkManagerOptions = new NetworkManagerOptions();
        var unitManagerOptions = new UnitManagerOptions();

        foreach (var argument in arguments)
        {


@@ 45,7 48,14 @@ public class Program
            foreach (var process in processes)
            {
                var externalBrowser = new NosBrowserManager
                    (process, playerManagerOptions, sceneManagerOptions, petManagerOptions);
                (
                    process,
                    playerManagerOptions,
                    sceneManagerOptions,
                    petManagerOptions,
                    networkManagerOptions,
                    unitManagerOptions
                );

                if (!externalBrowser.IsNostaleProcess)
                {


@@ 68,7 78,8 @@ public class Program
                    continue;
                }

                Console.WriteLine($"Player in process {process.Id} is named {externalBrowser.PlayerManager.Player.Name}");
                Console.WriteLine
                    ($"Player in process {process.Id} is named {externalBrowser.PlayerManager.Player.Name}");
            }
        }
    }

M src/Samples/HighLevel/SimplePiiBot/Commands/EntityCommands.cs => src/Samples/HighLevel/SimplePiiBot/Commands/EntityCommands.cs +21 -12
@@ 9,6 9,7 @@ using NosSmooth.Extensions.Combat.Errors;
using NosSmooth.Game;
using NosSmooth.Game.Apis;
using NosSmooth.LocalBinding;
using NosSmooth.LocalBinding.Hooks;
using NosSmooth.LocalBinding.Objects;
using NosSmooth.LocalBinding.Structs;
using NosSmooth.Packets.Enums.Chat;


@@ 24,36 25,32 @@ namespace SimplePiiBot.Commands;
public class EntityCommands : CommandGroup
{
    private readonly Game _game;
    private readonly IHookManager _hookManager;
    private readonly NosThreadSynchronizer _synchronizer;
    private readonly UnitManagerBinding _unitManagerBinding;
    private readonly SceneManager _sceneManager;
    private readonly PlayerManagerBinding _playerManagerBinding;
    private readonly FeedbackService _feedbackService;

    /// <summary>
    /// Initializes a new instance of the <see cref="EntityCommands"/> class.
    /// </summary>
    /// <param name="game">The game.</param>
    /// <param name="hookManager">The hook manager.</param>
    /// <param name="synchronizer">The thread synchronizer.</param>
    /// <param name="unitManagerBinding">The scene manager binding.</param>
    /// <param name="sceneManager">The scene manager.</param>
    /// <param name="playerManagerBinding">The character binding.</param>
    /// <param name="feedbackService">The feedback service.</param>
    public EntityCommands
    (
        Game game,
        IHookManager hookManager,
        NosThreadSynchronizer synchronizer,
        UnitManagerBinding unitManagerBinding,
        SceneManager sceneManager,
        PlayerManagerBinding playerManagerBinding,
        FeedbackService feedbackService
    )
    {
        _game = game;
        _hookManager = hookManager;
        _synchronizer = synchronizer;
        _unitManagerBinding = unitManagerBinding;
        _sceneManager = sceneManager;
        _playerManagerBinding = playerManagerBinding;
        _feedbackService = feedbackService;
    }



@@ 117,7 114,11 @@ public class EntityCommands : CommandGroup

        return await _synchronizer.SynchronizeAsync
        (
            () => _unitManagerBinding.FocusEntity(entityResult.Entity),
            () =>
            {
                _hookManager.EntityFocus.WrapperFunction(entityResult.Entity);
                return Result.FromSuccess();
            },
            CancellationToken
        );
    }


@@ 131,14 132,18 @@ public class EntityCommands : CommandGroup
    public async Task<Result> HandleFollowAsync(int entityId)
    {
        var entityResult = _sceneManager.FindEntity(entityId);
        if (!entityResult.IsSuccess)
        if (!entityResult.IsDefined(out var entity))
        {
            return Result.FromError(entityResult);
        }

        return await _synchronizer.SynchronizeAsync
        (
            () => _playerManagerBinding.FollowEntity(entityResult.Entity),
            () =>
            {
                _hookManager.EntityFollow.WrapperFunction(entity);
                return Result.FromSuccess();
            },
            CancellationToken
        );
    }


@@ 152,7 157,11 @@ public class EntityCommands : CommandGroup
    {
        return await _synchronizer.SynchronizeAsync
        (
            () => _playerManagerBinding.UnfollowEntity(),
            () =>
            {
                _hookManager.EntityUnfollow.WrapperFunction();
                return Result.FromSuccess();
            },
            CancellationToken
        );
    }

M src/Samples/HighLevel/SimplePiiBot/DllMain.cs => src/Samples/HighLevel/SimplePiiBot/DllMain.cs +2 -0
@@ 13,6 13,7 @@ using NosSmooth.Core.Extensions;
using NosSmooth.Data.NOSFiles.Extensions;
using NosSmooth.Extensions.Combat.Extensions;
using NosSmooth.Extensions.Pathfinding.Extensions;
using NosSmooth.Extensions.SharedBinding.Extensions;
using NosSmooth.Game.Extensions;
using NosSmooth.LocalClient.Extensions;
using Remora.Commands.Extensions;


@@ 81,6 82,7 @@ public class DllMain
                        .AddNostaleGame()
                        .AddLocalClient()
                        .AddNostaleDataFiles()
                        .ShareNosSmooth()
                        .AddNostaleCombat()
                        .AddNostalePathfinding()
                        .AddSingleton<Bot>()

M src/Samples/HighLevel/SimplePiiBot/SimplePiiBot.csproj => src/Samples/HighLevel/SimplePiiBot/SimplePiiBot.csproj +1 -0
@@ 31,6 31,7 @@

    <ItemGroup>
        <ProjectReference Include="..\..\..\Extensions\NosSmooth.ChatCommands\NosSmooth.ChatCommands.csproj" />
        <ProjectReference Include="..\..\..\Extensions\NosSmooth.Extensions.SharedBinding\NosSmooth.Extensions.SharedBinding.csproj" />
    </ItemGroup>

</Project>

M src/Samples/LowLevel/InterceptNameChanger/InterceptNameChanger.csproj => src/Samples/LowLevel/InterceptNameChanger/InterceptNameChanger.csproj +4 -0
@@ 4,6 4,7 @@
    <Nullable>enable</Nullable>
    <LangVersion>10</LangVersion>
    <GenerateRuntimeConfigurationFiles>true</GenerateRuntimeConfigurationFiles>
    <EnableDynamicLoading>true</EnableDynamicLoading>
  </PropertyGroup>
  <ItemGroup>
    <PackageReference Include="Costura.Fody" Version="5.7.0">


@@ 18,8 19,11 @@
    <PackageReference Include="Microsoft.Extensions.Logging" Version="7.0.0" />
    <PackageReference Include="Microsoft.Extensions.Logging.Console" Version="7.0.0" />
    <PackageReference Include="NosSmooth.Core" Version="3.0.1" />
    <PackageReference Include="NosSmooth.Packets" Version="2.3.4" />
    <PackageReference Include="Remora.Results" Version="7.2.3" />
  </ItemGroup>
  <ItemGroup>
    <ProjectReference Include="..\..\..\Core\NosSmooth.LocalClient\NosSmooth.LocalClient.csproj" />
    <ProjectReference Include="..\..\..\Extensions\NosSmooth.Extensions.SharedBinding\NosSmooth.Extensions.SharedBinding.csproj" />
  </ItemGroup>
</Project>
\ No newline at end of file

M src/Samples/LowLevel/InterceptNameChanger/NameChangeInterceptor.cs => src/Samples/LowLevel/InterceptNameChanger/NameChangeInterceptor.cs +3 -2
@@ 4,6 4,7 @@
//  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;
using Microsoft.Extensions.Logging;
using NosSmooth.Core.Client;
using NosSmooth.LocalClient;


@@ 61,10 62,10 @@ namespace InterceptNameChanger
        /// <inheritdoc/>
        public bool InterceptReceive(ref string packet)
        {
            if (packet.StartsWith("c_info"))
            if (packet.StartsWith("c_info "))
            {
                var oldPart = packet.Substring(packet.IndexOf(' ', 7));
                var result = _client.ReceivePacketAsync($"c_info {_name} " + oldPart)
                var result = _client.ReceivePacketAsync($"c_info {_name}" + oldPart)
                    .GetAwaiter().GetResult(); // Change the name

                if (!result.IsSuccess)

M src/Samples/LowLevel/InterceptNameChanger/NameChanger.cs => src/Samples/LowLevel/InterceptNameChanger/NameChanger.cs +2 -0
@@ 9,6 9,7 @@ using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using NosSmooth.Core.Client;
using NosSmooth.Core.Extensions;
using NosSmooth.Extensions.SharedBinding.Extensions;
using NosSmooth.LocalBinding;
using NosSmooth.LocalClient;
using NosSmooth.LocalClient.Extensions;


@@ 33,6 34,7 @@ namespace InterceptNameChanger
        {
            var provider = new ServiceCollection()
                .AddLocalClient()
                .ShareNosSmooth()

                // .AddPacketResponder<SayResponder>()
                .AddLogging

D src/Samples/LowLevel/InterceptNameChanger/Properties/AssemblyInfo.cs => src/Samples/LowLevel/InterceptNameChanger/Properties/AssemblyInfo.cs +0 -41
@@ 1,41 0,0 @@
//
//  AssemblyInfo.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.Reflection;
using System.Runtime.InteropServices;

// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("InterceptNameChanger")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("InterceptNameChanger")]
[assembly: AssemblyCopyright("Copyright ©  2021")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]

// Setting ComVisible to false makes the types in this assembly not visible
// to COM components.  If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]

// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("F96F3AA0-131E-4B6B-AB21-BBE2DEBCEF3A")]

// Version information for an assembly consists of the following four values:
//
//      Major Version
//      Minor Version
//      Build Number
//      Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]
\ No newline at end of file

M src/Samples/LowLevel/SimpleChat/SimpleChat.cs => src/Samples/LowLevel/SimpleChat/SimpleChat.cs +2 -0
@@ 8,6 8,7 @@ using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using NosSmooth.Core.Client;
using NosSmooth.Core.Extensions;
using NosSmooth.Extensions.SharedBinding.Extensions;
using NosSmooth.LocalBinding;
using NosSmooth.LocalClient.Extensions;
using NosSmooth.Packets.Enums;


@@ 31,6 32,7 @@ public class SimpleChat
    {
        var provider = new ServiceCollection()
            .AddLocalClient()
            .ShareNosSmooth()
            .AddPacketResponder<SayResponder>()
            .AddLogging
            (

M src/Samples/LowLevel/SimpleChat/SimpleChat.csproj => src/Samples/LowLevel/SimpleChat/SimpleChat.csproj +1 -0
@@ 22,5 22,6 @@
  </ItemGroup>
  <ItemGroup>
    <ProjectReference Include="..\..\..\Core\NosSmooth.LocalClient\NosSmooth.LocalClient.csproj" />
    <ProjectReference Include="..\..\..\Extensions\NosSmooth.Extensions.SharedBinding\NosSmooth.Extensions.SharedBinding.csproj" />
  </ItemGroup>
</Project>
\ No newline at end of file

M src/Samples/LowLevel/WalkCommands/Startup.cs => src/Samples/LowLevel/WalkCommands/Startup.cs +2 -0
@@ 12,6 12,7 @@ using NosSmooth.Core.Extensions;
using NosSmooth.Data.NOSFiles;
using NosSmooth.Data.NOSFiles.Extensions;
using NosSmooth.Extensions.Pathfinding.Extensions;
using NosSmooth.Extensions.SharedBinding.Extensions;
using NosSmooth.LocalBinding;
using NosSmooth.LocalBinding.Extensions;
using NosSmooth.LocalClient;


@@ 32,6 33,7 @@ public class Startup
    {
        var collection = new ServiceCollection()
            .AddLocalClient()
            .ShareNosSmooth()

            // hook pet and player walk to
            // recognize user action's and

M src/Samples/LowLevel/WalkCommands/WalkCommands.csproj => src/Samples/LowLevel/WalkCommands/WalkCommands.csproj +4 -0
@@ 39,6 39,9 @@
    <PackageReference Include="NosSmooth.Extensions.Pathfinding">
      <Version>1.1.1</Version>
    </PackageReference>
    <PackageReference Include="NosSmooth.Packets">
      <Version>2.3.4</Version>
    </PackageReference>
    <PackageReference Include="Remora.Results">
      <Version>7.2.3</Version>
    </PackageReference>


@@ 46,5 49,6 @@
  <ItemGroup>
    <ProjectReference Include="..\..\..\Core\NosSmooth.LocalClient\NosSmooth.LocalClient.csproj" />
    <ProjectReference Include="..\..\..\Extensions\NosSmooth.ChatCommands\NosSmooth.ChatCommands.csproj" />
    <ProjectReference Include="..\..\..\Extensions\NosSmooth.Extensions.SharedBinding\NosSmooth.Extensions.SharedBinding.csproj" />
  </ItemGroup>
</Project>
\ No newline at end of file

Do not follow this link