~ruther/NosSmooth.Local

d5b3c3ffb40aa86f582a0f26fc0376caa245f220 — Rutherther 2 years ago a48ee3a + bf76304
Merge pull request #18 from Rutherther/feat/optional-binding

Add support for optional modules and hooks
38 files changed, 1079 insertions(+), 521 deletions(-)

A src/Core/NosSmooth.LocalBinding/Errors/NeededModulesNotInitializedError.cs
A src/Core/NosSmooth.LocalBinding/Errors/OptionalNotPresentError.cs
M src/Core/NosSmooth.LocalBinding/Extensions/ServiceCollectionExtensions.cs
M src/Core/NosSmooth.LocalBinding/Hooks/IHookManager.cs
M src/Core/NosSmooth.LocalBinding/Hooks/INostaleHook.cs
M src/Core/NosSmooth.LocalBinding/Hooks/Implementations/CancelableNostaleHook.cs
M src/Core/NosSmooth.LocalBinding/Hooks/Implementations/EntityFocusHook.cs
M src/Core/NosSmooth.LocalBinding/Hooks/Implementations/EntityFollowHook.cs
M src/Core/NosSmooth.LocalBinding/Hooks/Implementations/EntityUnfollowHook.cs
M src/Core/NosSmooth.LocalBinding/Hooks/Implementations/HookManager.cs
M src/Core/NosSmooth.LocalBinding/Hooks/Implementations/PacketReceiveHook.cs
M src/Core/NosSmooth.LocalBinding/Hooks/Implementations/PacketSendHook.cs
M src/Core/NosSmooth.LocalBinding/Hooks/Implementations/PeriodicHook.cs
M src/Core/NosSmooth.LocalBinding/Hooks/Implementations/PetWalkHook.cs
M src/Core/NosSmooth.LocalBinding/Hooks/Implementations/PlayerWalkHook.cs
M src/Core/NosSmooth.LocalBinding/NosBindingManager.cs
M src/Core/NosSmooth.LocalBinding/NosBrowserManager.cs
M src/Core/NosSmooth.LocalBinding/NosThreadSynchronizer.cs
A src/Core/NosSmooth.LocalBinding/Optional.cs
A src/Core/NosSmooth.LocalBinding/OptionalUtilities.cs
M src/Core/NosSmooth.LocalBinding/Structs/ControlManager.cs
M src/Core/NosSmooth.LocalBinding/Structs/MapBaseObj.cs
M src/Core/NosSmooth.LocalBinding/Structs/NostaleList.cs
M src/Core/NosSmooth.LocalBinding/Structs/SceneManager.cs
M src/Core/NosSmooth.LocalClient/CommandHandlers/Attack/AttackCommandHandler.cs
M src/Core/NosSmooth.LocalClient/CommandHandlers/Walk/PetWalkCommandHandler.cs
M src/Core/NosSmooth.LocalClient/NostaleLocalClient.cs
M src/Core/NosSmooth.LocalClient/UserActionDetector.cs
M src/Extensions/NosSmooth.Extensions.SharedBinding/Hooks/SharedHookManager.cs
M src/Extensions/NosSmooth.Extensions.SharedBinding/Hooks/SingleHook.cs
M src/Extensions/NosSmooth.Extensions.SharedBinding/Hooks/SingleHookManager.cs
M src/Extensions/NosSmooth.Extensions.SharedBinding/Hooks/Specific/PeriodicHook.cs
M src/Samples/External/ExternalBrowser/Program.cs
M src/Samples/HighLevel/SimplePiiBot/Commands/EntityCommands.cs
M src/Samples/HighLevel/SimplePiiBot/HostedService.cs
M src/Samples/LowLevel/InterceptNameChanger/NameChanger.cs
M src/Samples/LowLevel/SimpleChat/SimpleChat.cs
M src/Samples/LowLevel/WalkCommands/Startup.cs
A src/Core/NosSmooth.LocalBinding/Errors/NeededModulesNotInitializedError.cs => src/Core/NosSmooth.LocalBinding/Errors/NeededModulesNotInitializedError.cs +21 -0
@@ 0,0 1,21 @@
//
//  NeededModulesNotInitializedError.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.Errors;

/// <summary>
/// A modules that are needed for the given operation are not loaded.
/// </summary>
/// <param name="additionalMessage">The message to show.</param>
/// <param name="modules">The modules.</param>
public record NeededModulesNotInitializedError
    (
        string additionalMessage,
        params string[] modules
    )
    : ResultError($"{additionalMessage}. Some needed modules ({string.Join(", ", modules)}) are not loaded");
\ No newline at end of file

A src/Core/NosSmooth.LocalBinding/Errors/OptionalNotPresentError.cs => src/Core/NosSmooth.LocalBinding/Errors/OptionalNotPresentError.cs +11 -0
@@ 0,0 1,11 @@
//
//  OptionalNotPresentError.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.Errors;

public record OptionalNotPresentError(string TypeName) : ResultError($"The optional {TypeName} is not present.");
\ No newline at end of file

M src/Core/NosSmooth.LocalBinding/Extensions/ServiceCollectionExtensions.cs => src/Core/NosSmooth.LocalBinding/Extensions/ServiceCollectionExtensions.cs +34 -9
@@ 37,14 37,22 @@ public static class ServiceCollectionExtensions
            .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<IHookManager>().PacketReceive)
            .AddSingleton(p => p.GetRequiredService<IHookManager>().PacketSend)
            .AddSingleton(p => p.GetRequiredService<IHookManager>().EntityFollow)
            .AddSingleton(p => p.GetRequiredService<IHookManager>().EntityUnfollow)
            .AddSingleton(p => p.GetRequiredService<IHookManager>().EntityFocus)
            .AddSingleton(p => p.GetRequiredService<IHookManager>().PlayerWalk)
            .AddSingleton(p => p.GetRequiredService<IHookManager>().PetWalk)
            .AddSingleton(p => p.GetRequiredService<IHookManager>().Periodic)
            .AddSingleton(p => p.GetRequiredService<IHookManager>().PacketReceive.Get())
            .AddSingleton(p => p.GetRequiredService<IHookManager>().PacketSend.Get())
            .AddSingleton(p => p.GetRequiredService<IHookManager>().EntityFollow.Get())
            .AddSingleton(p => p.GetRequiredService<IHookManager>().EntityUnfollow.Get())
            .AddSingleton(p => p.GetRequiredService<IHookManager>().EntityFocus.Get())
            .AddSingleton(p => p.GetRequiredService<IHookManager>().PlayerWalk.Get())
            .AddSingleton(p => p.GetRequiredService<IHookManager>().PetWalk.Get())
            .AddSingleton(p => p.GetRequiredService<IHookManager>().Periodic.Get())
            .AddSingleton(p => p.GetRequiredService<NosBrowserManager>().PlayerManager)
            .AddSingleton(p => p.GetRequiredService<NosBrowserManager>().SceneManager)
            .AddSingleton(p => p.GetRequiredService<NosBrowserManager>().PetManagerList)


@@ 61,7 69,24 @@ public static class ServiceCollectionExtensions
            .AddSingleton(p => p.GetRequiredService<IHookManager>().EntityFocus)
            .AddSingleton(p => p.GetRequiredService<IHookManager>().EntityFollow)
            .AddSingleton(p => p.GetRequiredService<IHookManager>().EntityUnfollow)
            .AddSingleton(p => p.GetRequiredService<IHookManager>().Periodic);
            .AddSingleton(p => p.GetRequiredService<IHookManager>().Periodic)
            .AddSingleton(p => p.GetRequiredService<NosBrowserManager>().PlayerManager.Get())
            .AddSingleton(p => p.GetRequiredService<NosBrowserManager>().SceneManager.Get())
            .AddSingleton(p => p.GetRequiredService<NosBrowserManager>().PetManagerList.Get())
            .AddSingleton(p => p.GetRequiredService<NosBrowserManager>().SceneManager.Get())
            .AddSingleton(p => p.GetRequiredService<NosBrowserManager>().PetManagerList.Get())
            .AddSingleton(p => p.GetRequiredService<NosBrowserManager>().PlayerManager.Get())
            .AddSingleton(p => p.GetRequiredService<NosBrowserManager>().NetworkManager.Get())
            .AddSingleton(p => p.GetRequiredService<NosBrowserManager>().UnitManager.Get())
            .AddSingleton(p => p.GetRequiredService<NosBrowserManager>().NtClient.Get())
            .AddSingleton(p => p.GetRequiredService<IHookManager>().PacketReceive.Get())
            .AddSingleton(p => p.GetRequiredService<IHookManager>().PacketSend.Get())
            .AddSingleton(p => p.GetRequiredService<IHookManager>().PlayerWalk.Get())
            .AddSingleton(p => p.GetRequiredService<IHookManager>().PetWalk.Get())
            .AddSingleton(p => p.GetRequiredService<IHookManager>().EntityFocus.Get())
            .AddSingleton(p => p.GetRequiredService<IHookManager>().EntityFollow.Get())
            .AddSingleton(p => p.GetRequiredService<IHookManager>().EntityUnfollow.Get())
            .AddSingleton(p => p.GetRequiredService<IHookManager>().Periodic.Get());
    }

    /// <summary>

M src/Core/NosSmooth.LocalBinding/Hooks/IHookManager.cs => src/Core/NosSmooth.LocalBinding/Hooks/IHookManager.cs +40 -8
@@ 56,37 56,37 @@ public interface IHookManager
    /// <summary>
    /// Gets the packet send hook.
    /// </summary>
    public IPacketSendHook PacketSend { get; }
    public Optional<IPacketSendHook> PacketSend { get; }

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

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

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

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

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

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

    /// <summary>
    /// Gets the periodic function hook.


@@ 95,7 95,7 @@ public interface IHookManager
    /// May be any function that is called periodically.
    /// This is used for synchronizing using <see cref="NosThreadSynchronizer"/>.
    /// </remarks>
    public IPeriodicHook Periodic { get; }
    public Optional<IPeriodicHook> Periodic { get; }

    /// <summary>
    /// Gets all of the hooks.


@@ 139,4 139,36 @@ public interface IHookManager
    /// Enable all hooks.
    /// </summary>
    public void EnableAll();

    /// <summary>
    /// Checks whether hook of the given type is loaded (there were no errors in finding the function).
    /// </summary>
    /// <typeparam name="THook">The type of the hook.</typeparam>
    /// <returns>Whether the hook is loaded/present.</returns>
    public bool IsHookLoaded<THook>()
        where THook : INostaleHook;

    /// <summary>
    /// Checks whether hook of the given type is loaded (there were no errors in finding the function)
    /// and that the wrapper function is present/usable.
    /// </summary>
    /// <typeparam name="THook">The type of the hook.</typeparam>
    /// <returns>Whether the hook is loaded/present and usable.</returns>
    public bool IsHookUsable<THook>()
        where THook : INostaleHook;

    /// <summary>
    /// Checks whether hook of the given type is loaded (there were no errors in finding the function).
    /// </summary>
    /// <param name="hookType">The type of the hook.</typeparam>
    /// <returns>Whether the hook is loaded/present.</returns>
    public bool IsHookLoaded(Type hookType);

    /// <summary>
    /// Checks whether hook of the given type is loaded (there were no errors in finding the function)
    /// and that the wrapper function is present/usable.
    /// </summary>
    /// <param name="hookType">The type of the hook.</typeparam>
    /// <returns>Whether the hook is loaded/present and usable.</returns>
    public bool IsHookUsable(Type hookType);
}
\ No newline at end of file

M src/Core/NosSmooth.LocalBinding/Hooks/INostaleHook.cs => src/Core/NosSmooth.LocalBinding/Hooks/INostaleHook.cs +6 -1
@@ 22,7 22,7 @@ public interface INostaleHook<TFunction, TWrapperFunction, TEventArgs> : INostal
    /// <summary>
    /// Gets the wrapper function delegate.
    /// </summary>
    public TWrapperFunction WrapperFunction { get; }
    public Optional<TWrapperFunction> WrapperFunction { get; }

    /// <summary>
    /// Gets the original function delegate.


@@ 41,6 41,11 @@ public interface INostaleHook<TFunction, TWrapperFunction, TEventArgs> : INostal
public interface INostaleHook
{
    /// <summary>
    /// Gets whether the wrapper function is present and usable.
    /// </summary>
    public bool IsUsable { get; }

    /// <summary>
    /// Gets the name of the function.
    /// </summary>
    /// <remarks>

M src/Core/NosSmooth.LocalBinding/Hooks/Implementations/CancelableNostaleHook.cs => src/Core/NosSmooth.LocalBinding/Hooks/Implementations/CancelableNostaleHook.cs +4 -1
@@ 64,7 64,7 @@ public abstract class CancelableNostaleHook<TFunction, TWrapperFunction, TEventA
    public bool IsEnabled => Hook.Hook.IsEnabled;

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

    /// <inheritdoc/>
    public TFunction OriginalFunction


@@ 84,6 84,9 @@ public abstract class CancelableNostaleHook<TFunction, TWrapperFunction, TEventA
    public event EventHandler<TEventArgs>? Called;

    /// <inheritdoc />
    public bool IsUsable => WrapperFunction.IsPresent;

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

    /// <inheritdoc />

M src/Core/NosSmooth.LocalBinding/Hooks/Implementations/EntityFocusHook.cs => src/Core/NosSmooth.LocalBinding/Hooks/Implementations/EntityFocusHook.cs +17 -13
@@ 30,7 30,7 @@ internal class EntityFocusHook : CancelableNostaleHook<IEntityFocusHook.EntityFo
        var hook = CreateHook
        (
            bindingManager,
            () => new EntityFocusHook(browserManager.UnitManager),
            () => new EntityFocusHook(browserManager.Memory, browserManager.UnitManager),
            hook => hook.Detour,
            options
        );


@@ 38,10 38,12 @@ internal class EntityFocusHook : CancelableNostaleHook<IEntityFocusHook.EntityFo
        return hook;
    }

    private readonly UnitManager _unitManager;
    private readonly Optional<UnitManager> _unitManager;
    private readonly IMemory _memory;

    private EntityFocusHook(UnitManager unitManager)
    private EntityFocusHook(IMemory memory, Optional<UnitManager> unitManager)
    {
        _memory = memory;
        _unitManager = unitManager;
    }



@@ 49,18 51,20 @@ internal class EntityFocusHook : CancelableNostaleHook<IEntityFocusHook.EntityFo
    public override string Name => IHookManager.EntityFocusName;

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

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

    private nuint Detour
    (


@@ 68,7 72,7 @@ internal class EntityFocusHook : CancelableNostaleHook<IEntityFocusHook.EntityFo
        nuint entityPtr
    )
    {
        var entity = new MapBaseObj(_unitManager.Memory, entityPtr);
        var entity = new MapBaseObj(_memory, entityPtr);
        var entityArgs = new EntityEventArgs(entity);
        return HandleCall(entityArgs);
    }

M src/Core/NosSmooth.LocalBinding/Hooks/Implementations/EntityFollowHook.cs => src/Core/NosSmooth.LocalBinding/Hooks/Implementations/EntityFollowHook.cs +19 -8
@@ 6,6 6,7 @@

using NosSmooth.LocalBinding.EventArgs;
using NosSmooth.LocalBinding.Structs;
using Reloaded.Memory.Sources;
using Remora.Results;

namespace NosSmooth.LocalBinding.Hooks.Implementations;


@@ 29,7 30,7 @@ internal class EntityFollowHook : CancelableNostaleHook<IEntityFollowHook.Entity
        var hook = CreateHook
        (
            bindingManager,
            () => new EntityFollowHook(browserManager.PlayerManager),
            () => new EntityFollowHook(browserManager.Memory, browserManager.PlayerManager),
            hook => hook.Detour,
            options
        );


@@ 37,10 38,12 @@ internal class EntityFollowHook : CancelableNostaleHook<IEntityFollowHook.Entity
        return hook;
    }

    private readonly PlayerManager _playerManager;
    private readonly Optional<PlayerManager> _playerManager;
    private readonly IMemory _memory;

    private EntityFollowHook(PlayerManager playerManager)
    private EntityFollowHook(IMemory memory, Optional<PlayerManager> playerManager)
    {
        _memory = memory;
        _playerManager = playerManager;
    }



@@ 48,12 51,20 @@ internal class EntityFollowHook : CancelableNostaleHook<IEntityFollowHook.Entity
    public override string Name => IHookManager.EntityFollowName;

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

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


@@ 69,7 80,7 @@ internal class EntityFollowHook : CancelableNostaleHook<IEntityFollowHook.Entity
        int unknown2 = 1
    )
    {
        var entity = new MapBaseObj(_playerManager.Memory, entityPtr);
        var entity = new MapBaseObj(_memory, entityPtr);
        var entityArgs = new EntityEventArgs(entity);
        return HandleCall(entityArgs);
    }

M src/Core/NosSmooth.LocalBinding/Hooks/Implementations/EntityUnfollowHook.cs => src/Core/NosSmooth.LocalBinding/Hooks/Implementations/EntityUnfollowHook.cs +4 -4
@@ 37,9 37,9 @@ internal class EntityUnfollowHook : CancelableNostaleHook<IEntityUnfollowHook.En
        return hook;
    }

    private readonly PlayerManager _playerManager;
    private readonly Optional<PlayerManager> _playerManager;

    private EntityUnfollowHook(PlayerManager playerManager)
    private EntityUnfollowHook(Optional<PlayerManager> playerManager)
    {
        _playerManager = playerManager;
    }


@@ 48,8 48,8 @@ internal class EntityUnfollowHook : CancelableNostaleHook<IEntityUnfollowHook.En
    public override string Name => IHookManager.EntityUnfollowName;

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

    /// <inheritdoc />
    protected override IEntityUnfollowHook.EntityUnfollowDelegate WrapWithCalling(IEntityUnfollowHook.EntityUnfollowDelegate function)

M src/Core/NosSmooth.LocalBinding/Hooks/Implementations/HookManager.cs => src/Core/NosSmooth.LocalBinding/Hooks/Implementations/HookManager.cs +54 -11
@@ 15,7 15,8 @@ namespace NosSmooth.LocalBinding.Hooks.Implementations;
internal class HookManager : IHookManager
{
    private readonly HookManagerOptions _options;
    private Dictionary<string, INostaleHook> _hooks;
    private readonly Dictionary<string, INostaleHook> _hooks;
    private bool _initialized;

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


@@ 28,28 29,29 @@ internal class HookManager : IHookManager
    }

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

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

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

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

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

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

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

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

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


@@ 57,6 59,7 @@ internal class HookManager : IHookManager
    /// <inheritdoc/>
    public IResult Initialize(NosBindingManager bindingManager, NosBrowserManager browserManager)
    {
        _initialized = true;
        if (_hooks.Count > 0)
        { // already initialized
            return Result.FromSuccess();


@@ 151,15 154,55 @@ internal class HookManager : IHookManager
        }
    }

    private T GetHook<T>(string name)
    /// <inheritdoc/>
    public bool IsHookLoaded<THook>()
        where THook : INostaleHook
        => IsHookLoaded(typeof(THook));

    /// <inheritdoc/>
    public bool IsHookUsable<THook>()
        where THook : INostaleHook
        => IsHookUsable(typeof(THook));

    /// <inheritdoc/>
    public bool IsHookLoaded(Type hookType)
        => GetHook(hookType).IsPresent;

    /// <inheritdoc/>
    public bool IsHookUsable(Type hookType)
        => GetHook(hookType).TryGet(out var h) && h.IsUsable;

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

        if (!_hooks.ContainsKey(name) || _hooks[name] is not T typed)
        {
            return Optional<T>.Empty;
        }

        return typed;
    }

    private Optional<INostaleHook> GetHook(Type hookType)
    {
        if (!_initialized)
        {
            throw new InvalidOperationException
                ($"Could not load hook {hookType.Name}. Did you forget to call IHookManager.Initialize?");
        }

        var hook = _hooks.Values.FirstOrDefault(x => x.GetType() == hookType);
        if (hook is null)
        {
            return Optional<INostaleHook>.Empty;
        }

        return new Optional<INostaleHook>(hook);
    }
}
\ No newline at end of file

M src/Core/NosSmooth.LocalBinding/Hooks/Implementations/PacketReceiveHook.cs => src/Core/NosSmooth.LocalBinding/Hooks/Implementations/PacketReceiveHook.cs +23 -15
@@ 8,6 8,7 @@ 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;


@@ 29,20 30,22 @@ internal class PacketReceiveHook : CancelableNostaleHook<IPacketReceiveHook.Pack
        (NosBindingManager bindingManager, NosBrowserManager browserManager, HookOptions<IPacketReceiveHook> options)
    {
        var hook = CreateHook
            (
                bindingManager,
                () => new PacketReceiveHook(browserManager.NetworkManager),
                (hook) => hook.Detour,
                options
            );
        (
            bindingManager,
            () => new PacketReceiveHook(browserManager.Memory, browserManager.NetworkManager),
            (hook) => hook.Detour,
            options
        );

        return hook;
    }

    private NetworkManager _networkManager;
    private readonly IMemory _memory;
    private Optional<NetworkManager> _networkManager;

    private PacketReceiveHook(NetworkManager networkManager)
    private PacketReceiveHook(IMemory memory, Optional<NetworkManager> networkManager)
    {
        _memory = memory;
        _networkManager = networkManager;
    }



@@ 50,15 53,20 @@ internal class PacketReceiveHook : CancelableNostaleHook<IPacketReceiveHook.Pack
    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());
    };
    public override Optional<IPacketReceiveHook.PacketReceiveWrapperDelegate> WrapperFunction
        => _networkManager.Map<IPacketReceiveHook.PacketReceiveWrapperDelegate>
        (
            networkManager => (packetString) =>
            {
                var packetObject = networkManager.GetAddressForPacketReceive();
                using var nostaleString = NostaleStringA.Create(_memory, packetString);
                OriginalFunction(packetObject, nostaleString.Get());
            }
        );

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

M src/Core/NosSmooth.LocalBinding/Hooks/Implementations/PacketSendHook.cs => src/Core/NosSmooth.LocalBinding/Hooks/Implementations/PacketSendHook.cs +21 -15
@@ 30,33 30,39 @@ internal class PacketSendHook : CancelableNostaleHook<IPacketSendHook.PacketSend
        (NosBindingManager bindingManager, NosBrowserManager browserManager, HookOptions<IPacketSendHook> options)
    {
        var hook = CreateHook
            (
                bindingManager,
                () => new PacketSendHook(browserManager.NetworkManager),
                (sendHook) => sendHook.Detour,
                options
            );
        (
            bindingManager,
            () => new PacketSendHook(browserManager.Memory, browserManager.NetworkManager),
            (sendHook) => sendHook.Detour,
            options
        );

        return hook;
    }

    private PacketSendHook(NetworkManager networkManager)
    private readonly IMemory _memory;
    private readonly Optional<NetworkManager> _networkManager;

    private PacketSendHook(IMemory memory, Optional<NetworkManager> networkManager)
    {
        _memory = memory;
        _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());
    };
    public override Optional<IPacketSendHook.PacketSendWrapperDelegate> WrapperFunction =>
        _networkManager.Map<IPacketSendHook.PacketSendWrapperDelegate>
        (
            networkManager => (packetString) =>
            {
                var packetObject = networkManager.GetAddressForPacketSend();
                using var nostaleString = NostaleStringA.Create(_memory, packetString);
                OriginalFunction(packetObject, nostaleString.Get());
            }
        );

    /// <inheritdoc />
    protected override IPacketSendHook.PacketSendDelegate WrapWithCalling(IPacketSendHook.PacketSendDelegate function)

M src/Core/NosSmooth.LocalBinding/Hooks/Implementations/PeriodicHook.cs => src/Core/NosSmooth.LocalBinding/Hooks/Implementations/PeriodicHook.cs +4 -1
@@ 40,6 40,9 @@ internal class PeriodicHook : IPeriodicHook

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

    /// <inheritdoc/>
    public bool IsUsable => true;

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



@@ 47,7 50,7 @@ internal class PeriodicHook : IPeriodicHook
    public bool IsEnabled => _hook.Hook.IsEnabled;

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

    /// <inheritdoc/>
    public IPeriodicHook.PeriodicDelegate OriginalFunction => throw new InvalidOperationException

M src/Core/NosSmooth.LocalBinding/Hooks/Implementations/PetWalkHook.cs => src/Core/NosSmooth.LocalBinding/Hooks/Implementations/PetWalkHook.cs +10 -3
@@ 48,8 48,8 @@ internal class PetWalkHook : CancelableNostaleHook<IPetWalkHook.PetWalkDelegate,
    public override string Name => IHookManager.PetWalkName;

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

    /// <inheritdoc />
    protected override IPetWalkHook.PetWalkDelegate WrapWithCalling(IPetWalkHook.PetWalkDelegate function)


@@ 63,7 63,14 @@ internal class PetWalkHook : CancelableNostaleHook<IPetWalkHook.PetWalkDelegate,
            ) =>
            {
                CallingFromNosSmooth = true;
                var res = function(petManagerPtr, position, un0, un1, un2);
                var res = function
                (
                    petManagerPtr,
                    position,
                    un0,
                    un1,
                    un2
                );
                CallingFromNosSmooth = false;
                return res;
            };

M src/Core/NosSmooth.LocalBinding/Hooks/Implementations/PlayerWalkHook.cs => src/Core/NosSmooth.LocalBinding/Hooks/Implementations/PlayerWalkHook.cs +12 -8
@@ 37,22 37,26 @@ internal class PlayerWalkHook : CancelableNostaleHook<IPlayerWalkHook.WalkDelega
        return hook;
    }

    private PlayerWalkHook(PlayerManager playerManager)
    private readonly Optional<PlayerManager> _playerManager;

    private PlayerWalkHook(Optional<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;
    };
    public override Optional<IPlayerWalkHook.WalkWrapperDelegate> WrapperFunction
        => _playerManager.Map<IPlayerWalkHook.WalkWrapperDelegate>
        (
            playerManager => (x, y) =>
            {
                var playerManagerObject = playerManager.Address;
                return OriginalFunction(playerManagerObject, (y << 16) | x) == 1;
            }
        );

    /// <inheritdoc />
    protected override IPlayerWalkHook.WalkDelegate WrapWithCalling(IPlayerWalkHook.WalkDelegate function)

M src/Core/NosSmooth.LocalBinding/NosBindingManager.cs => src/Core/NosSmooth.LocalBinding/NosBindingManager.cs +9 -0
@@ 12,6 12,7 @@ using NosSmooth.LocalBinding.Hooks;
using NosSmooth.LocalBinding.Hooks.Implementations;
using NosSmooth.LocalBinding.Objects;
using NosSmooth.LocalBinding.Options;
using NosSmooth.LocalBinding.Structs;
using Reloaded.Hooks;
using Reloaded.Hooks.Definitions;
using Reloaded.Hooks.Definitions.Helpers;


@@ 98,6 99,14 @@ public class NosBindingManager : IDisposable
        };
    }

    /// <summary>
    /// Gets whether a hook or browser module is present.
    /// </summary>
    /// <typeparam name="TModule">The type of the module.</typeparam>
    /// <returns>Whether the module is present.</returns>
    public bool IsModulePresent<TModule>()
        => _hookManager.IsHookUsable(typeof(TModule)) || _browserManager.IsModuleLoaded(typeof(TModule));

    /// <inheritdoc />
    public void Dispose()
    {

M src/Core/NosSmooth.LocalBinding/NosBrowserManager.cs => src/Core/NosSmooth.LocalBinding/NosBrowserManager.cs +113 -193
@@ 53,18 53,14 @@ public class NosBrowserManager
            .GetProcesses()
            .Where(IsProcessNostaleProcess);

    private readonly Dictionary<Type, NostaleObject> _modules;
    private readonly PlayerManagerOptions _playerManagerOptions;
    private readonly SceneManagerOptions _sceneManagerOptions;
    private readonly PetManagerOptions _petManagerOptions;
    private readonly NetworkManagerOptions _networkManagerOptions;
    private readonly UnitManagerOptions _unitManagerOptions;
    private readonly NtClientOptions _ntClientOptions;
    private PlayerManager? _playerManager;
    private SceneManager? _sceneManager;
    private PetManagerList? _petManagerList;
    private NetworkManager? _networkManager;
    private UnitManager? _unitManager;
    private NtClient? _ntClient;
    private bool _initialized;

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


@@ 118,6 114,7 @@ public class NosBrowserManager
        NtClientOptions? ntClientOptions = default
    )
    {
        _modules = new Dictionary<Type, NostaleObject>();
        _playerManagerOptions = playerManagerOptions ?? new PlayerManagerOptions();
        _sceneManagerOptions = sceneManagerOptions ?? new SceneManagerOptions();
        _petManagerOptions = petManagerOptions ?? new PetManagerOptions();


@@ 150,139 147,47 @@ public class NosBrowserManager
    public bool IsNostaleProcess => NosBrowserManager.IsProcessNostaleProcess(Process);

    /// <summary>
    /// Gets the network manager.
    /// Gets whether the player is currently in game.
    /// </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;
        }
    }
    /// <remarks>
    /// It may be unsafe to access some data if the player is not in game.
    /// </remarks>
    public Optional<bool> IsInGame => PlayerManager.Map(manager => manager.Player.Address != nuint.Zero);

    /// <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;
        }
    }
    public Optional<NetworkManager> NetworkManager => GetModule<NetworkManager>();

    /// <summary>
    /// Gets whether the player is currently in game.
    /// Gets the network manager.
    /// </summary>
    /// <remarks>
    /// It may be unsafe to access some data if the player is not in game.
    /// </remarks>
    public bool IsInGame
    {
        get
        {
            var player = PlayerManager.Player;
            return player.Address != nuint.Zero;
        }
    }
    /// <exception cref="InvalidOperationException">Thrown if the browser is not initialized or there was an error with initialization of unit manager.</exception>
    public Optional<UnitManager> UnitManager => GetModule<UnitManager>();

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

            return _ntClient;
        }
    }
    public Optional<NtClient> NtClient => GetModule<NtClient>();

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

            return _playerManager;
        }
    }
    public Optional<PlayerManager> PlayerManager => GetModule<PlayerManager>();

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

            return _sceneManager;
        }
    }
    public Optional<SceneManager> SceneManager => GetModule<SceneManager>();

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

            return _petManagerList;
        }
    }
    public Optional<PetManagerList> PetManagerList => GetModule<PetManagerList>();

    /// <summary>
    /// Initialize the nos browser modules.


@@ 298,120 203,135 @@ public class NosBrowserManager
            return (Result)new NotNostaleProcessError(Process);
        }

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

            _unitManager = unitManagerResult.Entity;
            return val;
        }

        if (_networkManager is null)
        {
            var networkManagerResult = NetworkManager.Create(this, _networkManagerOptions);
            if (!networkManagerResult.IsSuccess)
            {
                errorResults.Add
                (
                    Result.FromError
                    (
                        new CouldNotInitializeModuleError(typeof(NetworkManager), networkManagerResult.Error),
                        networkManagerResult
                    )
                );
            }
        _initialized = true;
        return HandleResults
        (
            (typeof(UnitManager), () => Structs.UnitManager.Create(this, _unitManagerOptions).Map(Map)),
            (typeof(NetworkManager), () => Structs.NetworkManager.Create(this, _networkManagerOptions).Map(Map)),
            (typeof(PlayerManager), () => Structs.PlayerManager.Create(this, _playerManagerOptions).Map(Map)),
            (typeof(SceneManager), () => Structs.SceneManager.Create(this, _sceneManagerOptions).Map(Map)),
            (typeof(PetManagerList), () => Structs.PetManagerList.Create(this, _petManagerOptions).Map(Map)),
            (typeof(NtClient), () => Structs.NtClient.Create(this, _ntClientOptions).Map(Map))
        );
    }

            _networkManager = networkManagerResult.Entity;
        }
    /// <summary>
    /// Gets whether a hook or browser module is present/loaded.
    /// Returns false in case pattern was not found.
    /// </summary>
    /// <typeparam name="TModule">The type of the module.</typeparam>
    /// <returns>Whether the module is present.</returns>
    public bool IsModuleLoaded<TModule>()
        where TModule : NostaleObject
        => IsModuleLoaded(typeof(TModule));

    /// <summary>
    /// Gets whether a hook or browser module is present/loaded.
    /// Returns false in case pattern was not found.
    /// </summary>
    /// <param name="moduleType">The type of the module.</typeparam>
    /// <returns>Whether the module is present.</returns>
    public bool IsModuleLoaded(Type moduleType)
    {
        return GetModule(moduleType).IsPresent;
    }

        if (_playerManager is null)
    /// <summary>
    /// Get module of the specified type.
    /// </summary>
    /// <typeparam name="TModule">The type of the module.</typeparam>
    /// <returns>The module.</returns>
    /// <exception cref="InvalidOperationException">Thrown in case the manager was not initialized.</exception>
    public Optional<TModule> GetModule<TModule>()
        where TModule : NostaleObject
    {
        if (!_initialized)
        {
            var playerManagerResult = PlayerManager.Create(this, _playerManagerOptions);
            if (!playerManagerResult.IsSuccess)
            {
                errorResults.Add
                (
                    Result.FromError
                    (
                        new CouldNotInitializeModuleError(typeof(PlayerManager), playerManagerResult.Error),
                        playerManagerResult
                    )
                );
            }
            throw new InvalidOperationException
            (
                $"Could not get {typeof(TModule)}. The browser manager is not initialized. Did you forget to call NosBrowserManager.Initialize?"
            );
        }

            _playerManager = playerManagerResult.Entity;
        if (!_modules.TryGetValue(typeof(TModule), out var nosObject) || nosObject is not TModule typed)
        {
            return Optional<TModule>.Empty;
        }

        if (_sceneManager is null)
        return typed;
    }

    /// <summary>
    /// Get module of the specified type.
    /// </summary>
    /// <param name="moduleType">The type of the module.</typeparam>
    /// <returns>The module.</returns>
    /// <exception cref="InvalidOperationException">Thrown in case the manager was not initialized.</exception>
    public Optional<NostaleObject> GetModule(Type moduleType)
    {
        if (!_initialized)
        {
            var sceneManagerResult = SceneManager.Create(this, _sceneManagerOptions);
            if (!sceneManagerResult.IsSuccess)
            {
                errorResults.Add
                (
                    Result.FromError
                    (
                        new CouldNotInitializeModuleError(typeof(SceneManager), sceneManagerResult.Error),
                        sceneManagerResult
                    )
                );
            }
            throw new InvalidOperationException
            (
                $"Could not get {moduleType.Name}. The browser manager is not initialized. Did you forget to call NosBrowserManager.Initialize?"
            );
        }

            _sceneManager = sceneManagerResult.Entity;
        if (!_modules.TryGetValue(moduleType, out var nosObject))
        {
            return Optional<NostaleObject>.Empty;
        }

        if (_petManagerList is null)
        return nosObject;
    }

    private Result HandleResults(params (Type Type, Func<Result<NostaleObject>> Builder)[] objects)
    {
        Result<NostaleObject> HandleSafe(Func<Result<NostaleObject>> builder)
        {
            var petManagerResult = PetManagerList.Create(this, _petManagerOptions);
            if (!petManagerResult.IsSuccess)
            try
            {
                errorResults.Add
                (
                    Result.FromError
                    (
                        new CouldNotInitializeModuleError(typeof(PetManagerList), petManagerResult.Error),
                        petManagerResult
                    )
                );
                return builder();
            }
            catch (Exception e)
            {
                return e;
            }

            _petManagerList = petManagerResult.Entity;
        }

        if (_ntClient is null)
        List<IResult> errorResults = new List<IResult>();
        foreach (var obj in objects)
        {
            var ntClientResult = NtClient.Create(this, _ntClientOptions);
            if (!ntClientResult.IsSuccess)
            var createdResult = HandleSafe(obj.Builder);

            if (!createdResult.IsSuccess)
            {
                errorResults.Add
                (
                    Result.FromError
                    (
                        new CouldNotInitializeModuleError(typeof(NtClient), ntClientResult.Error),
                        ntClientResult
                        new CouldNotInitializeModuleError(obj.Type, createdResult.Error),
                        createdResult
                    )
                );
            }

            _ntClient = ntClientResult.Entity;
            else if (createdResult.IsDefined(out var created))
            {
                _modules.Add(obj.Type, created);
            }
        }

        return errorResults.Count switch
        {
            0 => Result.FromSuccess(),
            1 => errorResults[0],
            _ => (Result)new AggregateError(errorResults)
            1 => (Result)errorResults[0],
            _ => new AggregateError(errorResults)
        };
    }
}
\ No newline at end of file

M src/Core/NosSmooth.LocalBinding/NosThreadSynchronizer.cs => src/Core/NosSmooth.LocalBinding/NosThreadSynchronizer.cs +9 -5
@@ 7,6 7,7 @@
using System.Collections.Concurrent;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using NosSmooth.LocalBinding.Errors;
using NosSmooth.LocalBinding.Hooks;
using NosSmooth.LocalBinding.Options;
using Remora.Results;


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


@@ 32,7 33,7 @@ public class NosThreadSynchronizer
    /// <param name="options">The options.</param>
    public NosThreadSynchronizer
    (
        IPeriodicHook periodicHook,
        Optional<IPeriodicHook> periodicHook,
        ILogger<NosThreadSynchronizer> logger,
        IOptions<NosThreadSynchronizerOptions> options
    )


@@ 51,9 52,12 @@ public class NosThreadSynchronizer
    /// <summary>
    /// Start the synchronizer operation.
    /// </summary>
    public void StartSynchronizer()
    /// <returns>The result, successful if periodic hook is present.</returns>
    public Result StartSynchronizer()
    {
        _periodicHook.Called += PeriodicCall;
        return _periodicHook.TryDo(h => h.Called += PeriodicCall)
            ? Result.FromSuccess()
            : new NeededModulesNotInitializedError("Could not start synchronizer, because the periodic hook is not present", IHookManager.PeriodicName);
    }

    /// <summary>


@@ 61,7 65,7 @@ public class NosThreadSynchronizer
    /// </summary>
    public void StopSynchronizer()
    {
        _periodicHook.Called -= PeriodicCall;
        _periodicHook.TryDo(h => h.Called -= PeriodicCall);
    }

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

A src/Core/NosSmooth.LocalBinding/Optional.cs => src/Core/NosSmooth.LocalBinding/Optional.cs +211 -0
@@ 0,0 1,211 @@
//
//  Optional.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.CodeAnalysis;
using NosSmooth.LocalBinding.Errors;
using Remora.Results;

namespace NosSmooth.LocalBinding;

/// <summary>
/// An optional, used mainly for hooks and binding modules,
/// to make it possible to check whether a module is loaded
/// in runtime.
/// </summary>
/// <typeparam name="T">The type of underlying value.</typeparam>
public class Optional<T>
    where T : notnull
{
    /// <summary>
    /// An empty optional (no value present).
    /// </summary>
    public static readonly Optional<T> Empty = new();

    /// <summary>
    /// Initializes a new instance of the <see cref="Optional{T}"/> class.
    /// </summary>
    /// <param name="value">The underlying value of the optional. Not present when null.</param>
    public Optional(T? value = default)
    {
        Value = value;
    }

    /// <summary>
    /// Gets whether the value is present.
    /// </summary>
    [MemberNotNullWhen(true, "Value")]
    public bool IsPresent => Value is not null;

    /// <summary>
    /// Gets the underlying value.
    /// </summary>
    public T? Value { get; }

    /// <summary>
    /// Tries to get the underlying value, if it's present.
    /// If it's not present, value won't be set.
    /// </summary>
    /// <param name="value">The underlying value.</param>
    /// <returns>Whether the value is present.</returns>
    public bool TryGet([NotNullWhen(true)] out T? value)
    {
        value = Value;
        return IsPresent;
    }

    /// <summary>
    /// Tries to execute an action on the value, if it exists.
    /// </summary>
    /// <remarks>
    /// Does nothing, if the value does not exist.
    /// </remarks>
    /// <param name="action">The action to execute.</param>
    /// <returns>Whether the value is present.</returns>
    public bool TryDo(Action<T> action)
    {
        if (IsPresent)
        {
            action(Value);
        }

        return IsPresent;
    }

    /// <summary>
    /// Gets something from the underlying value,
    /// exposing it as optional that is empty,
    /// in case this optional is empty as well.
    /// </summary>
    /// <param name="get">The function to obtain something from the value with.</param>
    /// <typeparam name="TU">The return type.</typeparam>
    /// <returns>An optional, present if this optional's value is present.</returns>
    public Optional<TU> Map<TU>(Func<T, TU> get)
        where TU : notnull
    {
        if (IsPresent)
        {
            return get(Value);
        }

        return Optional<TU>.Empty;
    }

    /// <summary>
    /// Gets something from the underlying value like <see cref="Map{TU}"/>.
    /// </summary>
    /// <remarks>
    /// Returns <see cref="OptionalNotPresentError"/> in case this value is not present
    /// instead of returning an optional.
    /// </remarks>
    /// <param name="get">The function to obtain something from the value with.</param>
    /// <typeparam name="TU">The return type.</typeparam>
    /// <returns>A result, successful in case this optional's value is present.</returns>
    public Result<TU> MapResult<TU>(Func<T, TU> get)
        where TU : notnull
    {
        if (IsPresent)
        {
            return OptionalUtilities.TryGet(() => get(Value));
        }

        return new OptionalNotPresentError(nameof(T));
    }

    /// <summary>
    /// Does something on the underlying value like <see cref="TryDo"/>, but returns a result
    /// in case the value is not present.
    /// </summary>
    /// <param name="get">The function to execute on the value.</param>
    /// <typeparam name="TU">The return type.</typeparam>
    /// <returns>A result, successful in case this optional's value is present.</returns>
    public Result MapResult(Action<T> get)
    {
        if (IsPresent)
        {
            return OptionalUtilities.Try(() => get(Value));
        }

        return new OptionalNotPresentError(nameof(T));
    }

    /// <summary>
    /// Gets something from the underlying value like <see cref="Map{TU}"/>.
    /// </summary>
    /// <remarks>
    /// Returns <see cref="OptionalNotPresentError"/> in case this value is not present
    /// instead of returning an optional.
    ///
    /// The get function returns a result that will be returned if this optional is present.
    /// </remarks>
    /// <param name="get">The function to obtain something from the value with.</param>
    /// <typeparam name="TU">The return type.</typeparam>
    /// <returns>A result from the function, <see cref="OptionalNotPresentError"/> in case this optional is not present.</returns>
    public Result<TU> MapResult<TU>(Func<T, Result<TU>> get)
        where TU : notnull
    {
        if (IsPresent)
        {
            return get(Value);
        }

        return new OptionalNotPresentError(nameof(T));
    }

    /// <summary>
    /// Does something on the underlying value like <see cref="TryDo"/>, but returns a result
    /// in case the value is not present.
    /// </summary>
    /// <remarks>
    /// Returns <see cref="OptionalNotPresentError"/> in case this value is not present
    /// instead of returning an optional.
    ///
    /// The get function returns a result that will be returned if this optional is present.
    /// </remarks>
    /// <param name="get">The function to obtain something from the value with.</param>
    /// <typeparam name="TU">The return type.</typeparam>
    /// <returns>A result from the function, <see cref="OptionalNotPresentError"/> in case this optional is not present.</returns>
    public Result MapResult(Func<T, Result> get)
    {
        if (IsPresent)
        {
            return get(Value);
        }

        return new OptionalNotPresentError(nameof(T));
    }

    /// <summary>
    /// Forcefully gets the underlying value.
    /// If it's not present, <see cref="InvalidOperationException"/> will be thrown.
    /// </summary>
    /// <remarks>
    /// Try to use other methods that return results where possible as they are easier to handle.
    /// </remarks>
    /// <returns>The underlying value.</returns>
    /// <exception cref="InvalidOperationException">Thrown in case the value is not present.</exception>
    public T Get()
    {
        if (!IsPresent)
        {
            throw new InvalidOperationException
            (
                $"Could not get {nameof(T)}. Did you forget to call initialization or was there an error?"
            );
        }

        return Value;
    }

    /// <summary>
    /// Cast a value to optional.
    /// </summary>
    /// <param name="val">The value to cast.</param>
    /// <returns>The created optional.</returns>
    public static implicit operator Optional<T>(T val)
    {
        return new Optional<T>(val);
    }
}
\ No newline at end of file

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

/// <summary>
/// A utilities that work with members that may not be present or may throw an exception.
/// </summary>
public static class OptionalUtilities
{
    /// <summary>
    /// Tries to get value from the function, capturing an exception into Result.
    /// </summary>
    /// <param name="get">The function to obtain the value from.</param>
    /// <typeparam name="T">The type of the value.</typeparam>
    /// <returns>The value, or exception error if an exception has been thrown.</returns>
    public static Result<T> TryGet<T>(Func<T> get)
    {
        try
        {
            return get();
        }
        catch (Exception e)
        {
            return e;
        }
    }

    /// <summary>
    /// Tries to get value from the function, capturing an exception into Result.
    /// </summary>
    /// <param name="get">The function to obtain the value from.</param>
    /// <typeparam name="T">The type of the value.</typeparam>
    /// <returns>The value, or exception error if an exception has been thrown.</returns>
    public static Result<T> TryIGet<T>(Func<Result<T>> get)
    {
        try
        {
            return get();
        }
        catch (Exception e)
        {
            return e;
        }
    }

    /// <summary>
    /// Tries to execute an action.
    /// </summary>
    /// <param name="get">The action.</param>
    /// <returns>A result, successful if no exception has been thrown.</returns>
    public static Result Try(Action get)
    {
        try
        {
            get();
            return Result.FromSuccess();
        }
        catch (Exception e)
        {
            return e;
        }
    }
}
\ No newline at end of file

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

using Reloaded.Memory.Sources;
using Remora.Results;

namespace NosSmooth.LocalBinding.Structs;


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

using Reloaded.Memory.Sources;
using Remora.Results;

namespace NosSmooth.LocalBinding.Structs;


M src/Core/NosSmooth.LocalBinding/Structs/NostaleList.cs => src/Core/NosSmooth.LocalBinding/Structs/NostaleList.cs +3 -7
@@ 9,6 9,7 @@ using System.Runtime.CompilerServices;
using System.Runtime.InteropServices.ObjectiveC;
using Reloaded.Memory.Pointers;
using Reloaded.Memory.Sources;
using Remora.Results;

namespace NosSmooth.LocalBinding.Structs;



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


@@ 27,17 28,12 @@ public abstract class NostaleList<T> : IEnumerable<T>
    /// <param name="memory">The memory.</param>
    /// <param name="objListPointer">The object list pointer.</param>
    public NostaleList(IMemory memory, nuint objListPointer)
        : base(memory, objListPointer)
    {
        _memory = memory;
        Address = objListPointer;
    }

    /// <summary>
    /// Gets the address.
    /// </summary>
    protected nuint Address { get; }

    /// <summary>
    /// Gets the element at the given index.
    /// </summary>
    /// <param name="index">The index of the element.</param>

M src/Core/NosSmooth.LocalBinding/Structs/SceneManager.cs => src/Core/NosSmooth.LocalBinding/Structs/SceneManager.cs +3 -2
@@ 15,7 15,7 @@ namespace NosSmooth.LocalBinding.Structs;
/// <summary>
/// Represents nostale scene manager struct.
/// </summary>
public class SceneManager
public class SceneManager : NostaleObject
{
    /// <summary>
    /// Create <see cref="PlayerManager"/> instance.


@@ 51,6 51,7 @@ public class SceneManager
    /// <param name="staticSceneManagerAddress">The pointer to the scene manager.</param>
    /// <param name="sceneManagerOffsets">The offsets from the static scene manager address.</param>
    public SceneManager(IMemory memory, int staticSceneManagerAddress, int[] sceneManagerOffsets)
        : base(memory, nuint.Zero)
    {
        _memory = memory;
        _staticSceneManagerAddress = staticSceneManagerAddress;


@@ 60,7 61,7 @@ public class SceneManager
    /// <summary>
    /// Gets the address of the scene manager.
    /// </summary>
    public nuint Address => _memory.FollowStaticAddressOffsets(_staticSceneManagerAddress, _sceneManagerOffsets);
    public override nuint Address => _memory.FollowStaticAddressOffsets(_staticSceneManagerAddress, _sceneManagerOffsets);

    /// <summary>
    /// Gets the player list.

M src/Core/NosSmooth.LocalClient/CommandHandlers/Attack/AttackCommandHandler.cs => src/Core/NosSmooth.LocalClient/CommandHandlers/Attack/AttackCommandHandler.cs +4 -1
@@ 56,7 56,10 @@ public class AttackCommandHandler : ICommandHandler<AttackCommand>
            var entityResult = _sceneManager.FindEntity(command.TargetId.Value);
            if (entityResult.IsDefined(out var entity))
            {
                _synchronizer.EnqueueOperation(() => _entityFocusHook.WrapperFunction(entity));
                if (_entityFocusHook.WrapperFunction.IsPresent)
                {
                    _synchronizer.EnqueueOperation(() => _entityFocusHook.WrapperFunction.Get()(entity));
                }
            }
        }


M src/Core/NosSmooth.LocalClient/CommandHandlers/Walk/PetWalkCommandHandler.cs => src/Core/NosSmooth.LocalClient/CommandHandlers/Walk/PetWalkCommandHandler.cs +3 -2
@@ 78,8 78,9 @@ public class PetWalkCommandHandler : ICommandHandler<PetWalkCommand>
                (
                    () => _userActionDetector.NotUserAction<Result<bool>>
                    (
                        () => _petWalkHook.WrapperFunction(petManager, (ushort)x, (ushort)y)
                    )
                        () => _petWalkHook.WrapperFunction.Get()(petManager, (ushort)x, (ushort)y)
                    ),
                    ct
                ),
            petManager,
            _options

M src/Core/NosSmooth.LocalClient/NostaleLocalClient.cs => src/Core/NosSmooth.LocalClient/NostaleLocalClient.cs +72 -27
@@ 13,6 13,7 @@ using NosSmooth.Core.Commands.Control;
using NosSmooth.Core.Extensions;
using NosSmooth.Core.Packets;
using NosSmooth.LocalBinding;
using NosSmooth.LocalBinding.Errors;
using NosSmooth.LocalBinding.EventArgs;
using NosSmooth.LocalBinding.Hooks;
using NosSmooth.LocalBinding.Objects;


@@ 85,15 86,26 @@ public class NostaleLocalClient : BaseNostaleClient
    /// <inheritdoc />
    public override async Task<Result> RunAsync(CancellationToken stopRequested = default)
    {
        if (!_hookManager.IsHookLoaded<IPacketSendHook>() || !_hookManager.IsHookLoaded<IPacketReceiveHook>())
        {
            return new NeededModulesNotInitializedError
                ("Client cannot run", IHookManager.PacketSendName, IHookManager.PacketReceiveName);
        }

        _stopRequested = stopRequested;
        _logger.LogInformation("Starting local client");
        _synchronizer.StartSynchronizer();
        _hookManager.PacketSend.Called += SendCallCallback;
        _hookManager.PacketReceive.Called += ReceiveCallCallback;
        var synchronizerResult = _synchronizer.StartSynchronizer();
        if (!synchronizerResult.IsSuccess)
        {
            return synchronizerResult;
        }

        _hookManager.EntityFollow.Called += FollowEntity;
        _hookManager.PlayerWalk.Called += Walk;
        _hookManager.PetWalk.Called += PetWalk;
        _hookManager.PacketSend.Get().Called += SendCallCallback;
        _hookManager.PacketReceive.Get().Called += ReceiveCallCallback;

        _hookManager.EntityFollow.TryDo(follow => follow.Called += FollowEntity);
        _hookManager.PlayerWalk.TryDo(walk => walk.Called += Walk);
        _hookManager.PetWalk.TryDo(walk => walk.Called += PetWalk);

        try
        {


@@ 104,12 116,12 @@ public class NostaleLocalClient : BaseNostaleClient
            // ignored
        }

        _hookManager.PacketSend.Called -= SendCallCallback;
        _hookManager.PacketReceive.Called -= ReceiveCallCallback;
        _hookManager.PacketSend.Get().Called -= SendCallCallback;
        _hookManager.PacketReceive.Get().Called -= ReceiveCallCallback;

        _hookManager.EntityFollow.Called -= FollowEntity;
        _hookManager.PlayerWalk.Called -= Walk;
        _hookManager.PetWalk.Called -= PetWalk;
        _hookManager.EntityFollow.TryDo(follow => follow.Called -= FollowEntity);
        _hookManager.PlayerWalk.TryDo(walk => walk.Called -= Walk);
        _hookManager.PetWalk.TryDo(walk => walk.Called -= PetWalk);

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


@@ 120,17 132,59 @@ public class NostaleLocalClient : BaseNostaleClient
    /// <inheritdoc />
    public override async Task<Result> ReceivePacketAsync(string packetString, CancellationToken ct = default)
    {
        ReceivePacket(packetString);
        await ProcessPacketAsync(PacketSource.Server, packetString);
        return Result.FromSuccess();
        var result = _hookManager.PacketReceive.MapResult
        (
            receive => receive.WrapperFunction.MapResult
            (
                wrapperFunction =>
                {
                    _synchronizer.EnqueueOperation(() => wrapperFunction(packetString));
                    return Result.FromSuccess();
                }
            )
        );

        if (result.IsSuccess)
        {
            _logger.LogDebug($"Receiving client packet {packetString}");
            await ProcessPacketAsync(PacketSource.Server, packetString);
        }
        else
        {
            _logger.LogError("Could not receive packet");
            _logger.LogResultError(result);
        }

        return result;
    }

    /// <inheritdoc />
    public override async Task<Result> SendPacketAsync(string packetString, CancellationToken ct = default)
    {
        SendPacket(packetString);
        await ProcessPacketAsync(PacketSource.Client, packetString);
        return Result.FromSuccess();
        var result = _hookManager.PacketSend.MapResult
        (
            send => send.WrapperFunction.MapResult
            (
                wrapperFunction =>
                {
                    _synchronizer.EnqueueOperation(() => wrapperFunction(packetString));
                    return Result.FromSuccess();
                }
            )
        );

        if (result.IsSuccess)
        {
            _logger.LogDebug($"Sending client packet {packetString}");
            await ProcessPacketAsync(PacketSource.Server, packetString);
        }
        else
        {
            _logger.LogError("Could not send packet");
            _logger.LogResultError(result);
        }

        return result;
    }

    private void ReceiveCallCallback(object? owner, PacketEventArgs packetArgs)


@@ 181,20 235,11 @@ public class NostaleLocalClient : BaseNostaleClient
    {
        _synchronizer.EnqueueOperation
        (
            () => _hookManager.PacketSend.WrapperFunction(packetString)
            () => _hookManager.PacketSend.Get().WrapperFunction.Get()(packetString)
        );
        _logger.LogDebug($"Sending client packet {packetString}");
    }

    private void ReceivePacket(string packetString)
    {
        _synchronizer.EnqueueOperation
        (
            () => _hookManager.PacketReceive.WrapperFunction(packetString)
        );
        _logger.LogDebug($"Receiving client packet {packetString}");
    }

    private async Task ProcessPacketAsync(PacketSource type, string packetString)
    {
        try

M src/Core/NosSmooth.LocalClient/UserActionDetector.cs => src/Core/NosSmooth.LocalClient/UserActionDetector.cs +2 -2
@@ 106,7 106,7 @@ public class UserActionDetector
            () =>
            {
                _lastWalkPosition = ((ushort)x, (ushort)y);
                return walkHook.WrapperFunction((ushort)x, (ushort)y);
                return walkHook.WrapperFunction.MapResult(func => func((ushort)x, (ushort)y));
            }
        );



@@ 124,7 124,7 @@ public class UserActionDetector
            () =>
            {
                _lastWalkPosition = ((ushort)x, (ushort)y);
                return walkHook.WrapperFunction((ushort)x, (ushort)y);
                return walkHook.WrapperFunction.MapResult(func => func((ushort)x, (ushort)y));
            },
            ct
        );

M src/Extensions/NosSmooth.Extensions.SharedBinding/Hooks/SharedHookManager.cs => src/Extensions/NosSmooth.Extensions.SharedBinding/Hooks/SharedHookManager.cs +67 -36
@@ 26,8 26,6 @@ public class SharedHookManager
    /// 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


@@ 44,112 42,137 @@ public class SharedHookManager
    /// <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
    public (Dictionary<string, INostaleHook>, IResult) InitializeInstance
        (NosBindingManager bindingManager, NosBrowserManager browserManager, HookManagerOptions options)
    {
        IResult result = Result.FromSuccess();
        if (!_initialized)
        {
            var result = _underlyingManager.Initialize(bindingManager, browserManager);
            result = _underlyingManager.Initialize(bindingManager, browserManager);
            _initialized = true;

            if (!result.IsSuccess)
            {
                return Result<Dictionary<string, INostaleHook>>.FromError(result.Error);
            }
        }

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

        // TODO: initialize using reflection
        hooks.Add
        HandleAdd
        (
            _underlyingManager.Periodic.Name,
            hooks,
            IHookManager.PeriodicName,
            InitializeSingleHook
            (
                new PeriodicHook(_underlyingManager.Periodic),
                _underlyingManager.Periodic,
                u => new PeriodicHook(u),
                options.PeriodicHook
            )
        );

        hooks.Add
        HandleAdd
        (
            _underlyingManager.EntityFocus.Name,
            hooks,
            IHookManager.EntityFocusName,
            InitializeSingleHook
            (
                new EntityFocusHook(_underlyingManager.EntityFocus),
                _underlyingManager.EntityFocus,
                u => new EntityFocusHook(u),
                options.EntityFocusHook
            )
        );

        hooks.Add
        HandleAdd
        (
            _underlyingManager.EntityFollow.Name,
            hooks,
            IHookManager.EntityFollowName,
            InitializeSingleHook
            (
                new EntityFollowHook(_underlyingManager.EntityFollow),
                _underlyingManager.EntityFollow,
                u => new EntityFollowHook(u),
                options.EntityFollowHook
            )
        );

        hooks.Add
        HandleAdd
        (
            _underlyingManager.EntityUnfollow.Name,
            hooks,
            IHookManager.EntityUnfollowName,
            InitializeSingleHook
            (
                new EntityUnfollowHook(_underlyingManager.EntityUnfollow),
                _underlyingManager.EntityUnfollow,
                u => new EntityUnfollowHook(u),
                options.EntityUnfollowHook
            )
        );

        hooks.Add
        HandleAdd
        (
            _underlyingManager.PacketReceive.Name,
            hooks,
            IHookManager.PacketReceiveName,
            InitializeSingleHook
            (
                new PacketReceiveHook(_underlyingManager.PacketReceive),
                _underlyingManager.PacketReceive,
                u => new PacketReceiveHook(u),
                options.PacketReceiveHook
            )
        );

        hooks.Add
        HandleAdd
        (
            _underlyingManager.PacketSend.Name,
            hooks,
            IHookManager.PacketSendName,
            InitializeSingleHook
            (
                new PacketSendHook(_underlyingManager.PacketSend),
                _underlyingManager.PacketSend,
                u => new PacketSendHook(u),
                options.PacketSendHook
            )
        );

        hooks.Add
        HandleAdd
        (
            _underlyingManager.PetWalk.Name,
            hooks,
            IHookManager.PetWalkName,
            InitializeSingleHook
            (
                new PetWalkHook(_underlyingManager.PetWalk),
                _underlyingManager.PetWalk,
                u => new PetWalkHook(u),
                options.PetWalkHook
            )
        );

        hooks.Add
        HandleAdd
        (
            _underlyingManager.PlayerWalk.Name,
            hooks,
            IHookManager.CharacterWalkName,
            InitializeSingleHook
            (
                new PlayerWalkHook(_underlyingManager.PlayerWalk),
                _underlyingManager.PlayerWalk,
                u => new PlayerWalkHook(u),
                options.PlayerWalkHook
            )
        );

        return hooks;
        return (hooks, result);
    }

    private INostaleHook<TFunction, TWrapperFunction, TEventArgs> InitializeSingleHook<TFunction, TWrapperFunction,
        TEventArgs>(SingleHook<TFunction, TWrapperFunction, TEventArgs> hook, HookOptions options)
    private INostaleHook<TFunction, TWrapperFunction, TEventArgs>? InitializeSingleHook<THook, TFunction,
        TWrapperFunction,
        TEventArgs>
    (
        Optional<THook> hookOptional,
        Func<THook, SingleHook<TFunction, TWrapperFunction, TEventArgs>> hookCreator,
        HookOptions options
    )
        where THook : notnull
        where TFunction : Delegate
        where TWrapperFunction : Delegate
        where TEventArgs : System.EventArgs
    {
        if (!hookOptional.TryGet(out var underlyingHook))
        {
            return null;
        }

        var hook = hookCreator(underlyingHook);
        hook.StateChanged += (_, state) =>
        {
            if (!_hookedCount.ContainsKey(hook.Name))


@@ 176,4 199,12 @@ public class SharedHookManager

        return hook;
    }

    private void HandleAdd(Dictionary<string, INostaleHook> hooks, string name, INostaleHook? hook)
    {
        if (hook is not null)
        {
            hooks.Add(name, hook);
        }
    }
}
\ No newline at end of file

M src/Extensions/NosSmooth.Extensions.SharedBinding/Hooks/SingleHook.cs => src/Extensions/NosSmooth.Extensions.SharedBinding/Hooks/SingleHook.cs +89 -85
@@ 1,86 1,90 @@
//
//  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;
//
//  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;
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 bool IsUsable => _underlyingHook.IsUsable;

    /// <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 Optional<TWrapperFunction> WrapperFunction => _underlyingHook.WrapperFunction;

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

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

M src/Extensions/NosSmooth.Extensions.SharedBinding/Hooks/SingleHookManager.cs => src/Extensions/NosSmooth.Extensions.SharedBinding/Hooks/SingleHookManager.cs +58 -20
@@ 20,6 20,7 @@ public class SingleHookManager : IHookManager
    private readonly SharedHookManager _sharedHookManager;
    private readonly HookManagerOptions _options;
    private Dictionary<string, INostaleHook> _hooks;
    private bool _initialized;

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


@@ 34,28 35,28 @@ public class SingleHookManager : IHookManager
    }

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

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

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

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

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

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

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

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

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


@@ 63,14 64,10 @@ public class SingleHookManager : IHookManager
    /// <inheritdoc />
    public IResult Initialize(NosBindingManager bindingManager, NosBrowserManager browserManager)
    {
        var hooksResult = _sharedHookManager.InitializeInstance(bindingManager, browserManager, _options);
        if (!hooksResult.IsDefined(out var hooks))
        {
            return hooksResult;
        }

        _initialized = true;
        var (hooks, result) = _sharedHookManager.InitializeInstance(bindingManager, browserManager, _options);
        _hooks = hooks;
        return Result.FromSuccess();
        return result;
    }

    /// <inheritdoc />


@@ 79,7 76,7 @@ public class SingleHookManager : IHookManager
        foreach (var name in names)
        {
            var hook = GetHook<INostaleHook>(name);
            hook.Enable();
            hook.TryDo(h => h.Enable());
        }
    }



@@ 89,7 86,7 @@ public class SingleHookManager : IHookManager
        foreach (var name in names)
        {
            var hook = GetHook<INostaleHook>(name);
            hook.Disable();
            hook.TryDo(h => h.Disable());
        }
    }



@@ 111,15 108,56 @@ public class SingleHookManager : IHookManager
        }
    }

    private T GetHook<T>(string name)
    /// <inheritdoc/>
    public bool IsHookLoaded<THook>()
        where THook : INostaleHook
        => IsHookLoaded(typeof(THook));

    /// <inheritdoc/>
    public bool IsHookUsable<THook>()
        where THook : INostaleHook
        => IsHookUsable(typeof(THook));

    /// <inheritdoc/>
    public bool IsHookLoaded(Type hookType)
        => GetHook(hookType).IsPresent;

    /// <inheritdoc/>
    public bool IsHookUsable(Type hookType)
        => GetHook(hookType).TryGet(out var h) && h.IsUsable;

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

        var hook = _hooks.Values.FirstOrDefault(x => x is T);
        if (hook is not T typed)
        {
            return Optional<T>.Empty;
        }

        return typed;
    }

    private Optional<INostaleHook> GetHook(Type hookType)
    {
        if (!_initialized)
        {
            throw new InvalidOperationException
                ($"Could not load hook {hookType.Name}. Did you forget to call IHookManager.Initialize?");
        }

        var hook = _hooks.Values.FirstOrDefault(hookType.IsInstanceOfType);
        if (hook is null)
        {
            return Optional<INostaleHook>.Empty;
        }

        return new Optional<INostaleHook>(hook);
    }
}
\ No newline at end of file

M src/Extensions/NosSmooth.Extensions.SharedBinding/Hooks/Specific/PeriodicHook.cs => src/Extensions/NosSmooth.Extensions.SharedBinding/Hooks/Specific/PeriodicHook.cs +26 -25
@@ 1,26 1,27 @@
//
//  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)
    {
    }
//
//  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;
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

M src/Samples/External/ExternalBrowser/Program.cs => src/Samples/External/ExternalBrowser/Program.cs +3 -3
@@ 55,17 55,17 @@ public class Program
                    Console.Error.WriteLine(initializationResult.ToFullString());
                }

                var length = externalBrowser.PetManagerList.Length;
                var length = externalBrowser.PetManagerList.Get().Length;
                Console.WriteLine(length);

                if (!externalBrowser.IsInGame)
                if (!externalBrowser.IsInGame.Get())
                {
                    Console.Error.WriteLine("The player is not in game, cannot get the name of the player.");
                    continue;
                }

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

M src/Samples/HighLevel/SimplePiiBot/Commands/EntityCommands.cs => src/Samples/HighLevel/SimplePiiBot/Commands/EntityCommands.cs +13 -16
@@ 107,18 107,17 @@ public class EntityCommands : CommandGroup
    public async Task<Result> HandleFocusAsync(int entityId)
    {
        var entityResult = _sceneManager.FindEntity(entityId);
        if (!entityResult.IsSuccess)
        if (!entityResult.IsDefined(out var entity))
        {
            return Result.FromError(entityResult);
        }

        return await _synchronizer.SynchronizeAsync
        (
            () =>
            {
                _hookManager.EntityFocus.WrapperFunction(entityResult.Entity);
                return Result.FromSuccess();
            },
            () => _hookManager.EntityFocus.MapResult
            (
                focus => focus.WrapperFunction.MapResult(wrapper => wrapper(entity))
            ),
            CancellationToken
        );
    }


@@ 139,11 138,10 @@ public class EntityCommands : CommandGroup

        return await _synchronizer.SynchronizeAsync
        (
            () =>
            {
                _hookManager.EntityFollow.WrapperFunction(entity);
                return Result.FromSuccess();
            },
            () => _hookManager.EntityFollow.MapResult
            (
                follow => follow.WrapperFunction.MapResult(wrapper => wrapper(entity))
            ),
            CancellationToken
        );
    }


@@ 157,11 155,10 @@ public class EntityCommands : CommandGroup
    {
        return await _synchronizer.SynchronizeAsync
        (
            () =>
            {
                _hookManager.EntityUnfollow.WrapperFunction();
                return Result.FromSuccess();
            },
            () => _hookManager.EntityUnfollow.MapResult
            (
                unfollow => unfollow.WrapperFunction.MapResult(wrapper => wrapper())
            ),
            CancellationToken
        );
    }

M src/Samples/HighLevel/SimplePiiBot/HostedService.cs => src/Samples/HighLevel/SimplePiiBot/HostedService.cs +10 -0
@@ 11,6 11,7 @@ using NosSmooth.Core.Client;
using NosSmooth.Core.Extensions;
using NosSmooth.Data.NOSFiles;
using NosSmooth.LocalBinding;
using NosSmooth.LocalBinding.Hooks;
using NosSmooth.PacketSerializer.Extensions;
using NosSmooth.PacketSerializer.Packets;
using OneOf.Types;


@@ 78,6 79,15 @@ public class HostedService : BackgroundService
        if (!bindingResult.IsSuccess)
        {
            _logger.LogResultError(bindingResult);
        }

        if (!_bindingManager.IsModulePresent<IPeriodicHook>() || !_bindingManager.IsModulePresent<IPacketSendHook>()
            || !_bindingManager.IsModulePresent<IPacketReceiveHook>())
        {
            _logger.LogError
            (
                "At least one of: periodic, packet receive, packet send has not been loaded correctly, the bot may not be used at all. Aborting"
            );
            return;
        }


M src/Samples/LowLevel/InterceptNameChanger/NameChanger.cs => src/Samples/LowLevel/InterceptNameChanger/NameChanger.cs +11 -0
@@ 11,6 11,7 @@ using NosSmooth.Core.Client;
using NosSmooth.Core.Extensions;
using NosSmooth.Extensions.SharedBinding.Extensions;
using NosSmooth.LocalBinding;
using NosSmooth.LocalBinding.Hooks;
using NosSmooth.LocalClient;
using NosSmooth.LocalClient.Extensions;
using NosSmooth.Packets.Enums;


@@ 63,6 64,16 @@ namespace InterceptNameChanger
                logger.LogResultError(initializeResult);
            }

            if (!bindingManager.IsModulePresent<IPeriodicHook>() || !bindingManager.IsModulePresent<IPacketSendHook>()
                || !bindingManager.IsModulePresent<IPacketReceiveHook>())
            {
                logger.LogError
                (
                    "At least one of: periodic, packet receive, packet send has not been loaded correctly, the bot may not be used at all. Aborting"
                );
                return;
            }
            
            var packetTypesRepository = provider.GetRequiredService<IPacketTypesRepository>();
            var packetAddResult = packetTypesRepository.AddDefaultPackets();
            if (!packetAddResult.IsSuccess)

M src/Samples/LowLevel/SimpleChat/SimpleChat.cs => src/Samples/LowLevel/SimpleChat/SimpleChat.cs +11 -0
@@ 10,6 10,7 @@ using NosSmooth.Core.Client;
using NosSmooth.Core.Extensions;
using NosSmooth.Extensions.SharedBinding.Extensions;
using NosSmooth.LocalBinding;
using NosSmooth.LocalBinding.Hooks;
using NosSmooth.LocalClient.Extensions;
using NosSmooth.Packets.Enums;
using NosSmooth.Packets.Enums.Chat;


@@ 57,6 58,16 @@ public class SimpleChat
            logger.LogError($"Could not initialize NosBindingManager.");
            logger.LogResultError(initializeResult);
        }
        
        if (!bindingManager.IsModulePresent<IPeriodicHook>() || !bindingManager.IsModulePresent<IPacketSendHook>()
            || !bindingManager.IsModulePresent<IPacketReceiveHook>())
        {
            logger.LogError
            (
                "At least one of: periodic, packet receive, packet send has not been loaded correctly, the bot may not be used at all. Aborting"
            );
            return;
        }

        var packetTypesRepository = provider.GetRequiredService<IPacketTypesRepository>();
        var packetAddResult = packetTypesRepository.AddDefaultPackets();

M src/Samples/LowLevel/WalkCommands/Startup.cs => src/Samples/LowLevel/WalkCommands/Startup.cs +11 -0
@@ 16,6 16,7 @@ using NosSmooth.Extensions.Pathfinding.Extensions;
using NosSmooth.Extensions.SharedBinding.Extensions;
using NosSmooth.LocalBinding;
using NosSmooth.LocalBinding.Extensions;
using NosSmooth.LocalBinding.Hooks;
using NosSmooth.LocalClient;
using NosSmooth.LocalClient.Extensions;
using NosSmooth.PacketSerializer.Extensions;


@@ 82,6 83,16 @@ public class Startup
            logger.LogError($"Could not initialize NosBindingManager.");
            logger.LogResultError(initializeResult);
        }
        
        if (!bindingManager.IsModulePresent<IPeriodicHook>() || !bindingManager.IsModulePresent<IPacketSendHook>()
            || !bindingManager.IsModulePresent<IPacketReceiveHook>())
        {
            logger.LogError
            (
                "At least one of: periodic, packet receive, packet send has not been loaded correctly, the bot may not be used at all. Aborting"
            );
            return;
        }

        var packetTypesRepository = provider.GetRequiredService<IPacketTypesRepository>();
        var packetAddResult = packetTypesRepository.AddDefaultPackets();

Do not follow this link