~ruther/NosSmooth.Local

cf76dfcdbe24fa6871034a08eab1bcd8dcc69a7e — Rutherther 2 years ago 79806cc
feat(binding): convert hooks and modules to optionals
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

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 => WrapperFunction.IsPresent;

    /// <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 => OriginalFunction;

    /// <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 +87 -20
@@ 53,6 53,7 @@ public class NosBrowserManager
            .GetProcesses()
            .Where(IsProcessNostaleProcess);

    private readonly Dictionary<Type, NostaleObject> _modules;
    private readonly PlayerManagerOptions _playerManagerOptions;
    private readonly SceneManagerOptions _sceneManagerOptions;
    private readonly PetManagerOptions _petManagerOptions;


@@ 65,6 66,7 @@ public class NosBrowserManager
    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 120,7 @@ public class NosBrowserManager
        NtClientOptions? ntClientOptions = default
    )
    {
        _modules = new Dictionary<Type, NostaleObject>();
        _playerManagerOptions = playerManagerOptions ?? new PlayerManagerOptions();
        _sceneManagerOptions = sceneManagerOptions ?? new SceneManagerOptions();
        _petManagerOptions = petManagerOptions ?? new PetManagerOptions();


@@ 153,7 156,7 @@ public class NosBrowserManager
    /// Gets the network manager.
    /// </summary>
    /// <exception cref="InvalidOperationException">Thrown if the browser is not initialized or there was an error with initialization of network manager.</exception>
    public NetworkManager NetworkManager
    public Optional<NetworkManager> NetworkManager
    {
        get
        {


@@ 173,7 176,7 @@ public class NosBrowserManager
    /// 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
    public Optional<UnitManager> UnitManager
    {
        get
        {


@@ 195,20 198,13 @@ public class NosBrowserManager
    /// <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;
        }
    }
    public Optional<bool> IsInGame => PlayerManager.Map(manager => manager.Player.Address != nuint.Zero);

    /// <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
    public Optional<NtClient> NtClient
    {
        get
        {


@@ 228,7 224,7 @@ public class NosBrowserManager
    /// 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
    public Optional<PlayerManager> PlayerManager
    {
        get
        {


@@ 248,7 244,7 @@ public class NosBrowserManager
    /// 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
    public Optional<SceneManager> SceneManager
    {
        get
        {


@@ 268,7 264,7 @@ public class NosBrowserManager
    /// 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
    public Optional<PetManagerList> PetManagerList
    {
        get
        {


@@ 298,10 294,11 @@ public class NosBrowserManager
            return (Result)new NotNostaleProcessError(Process);
        }

        _initialized = true;
        List<IResult> errorResults = new List<IResult>();
        if (_unitManager is null)
        {
            var unitManagerResult = UnitManager.Create(this, _unitManagerOptions);
            var unitManagerResult = Structs.UnitManager.Create(this, _unitManagerOptions);
            if (!unitManagerResult.IsSuccess)
            {
                errorResults.Add


@@ 319,7 316,7 @@ public class NosBrowserManager

        if (_networkManager is null)
        {
            var networkManagerResult = NetworkManager.Create(this, _networkManagerOptions);
            var networkManagerResult = Structs.NetworkManager.Create(this, _networkManagerOptions);
            if (!networkManagerResult.IsSuccess)
            {
                errorResults.Add


@@ 337,7 334,7 @@ public class NosBrowserManager

        if (_playerManager is null)
        {
            var playerManagerResult = PlayerManager.Create(this, _playerManagerOptions);
            var playerManagerResult = Structs.PlayerManager.Create(this, _playerManagerOptions);
            if (!playerManagerResult.IsSuccess)
            {
                errorResults.Add


@@ 355,7 352,7 @@ public class NosBrowserManager

        if (_sceneManager is null)
        {
            var sceneManagerResult = SceneManager.Create(this, _sceneManagerOptions);
            var sceneManagerResult = Structs.SceneManager.Create(this, _sceneManagerOptions);
            if (!sceneManagerResult.IsSuccess)
            {
                errorResults.Add


@@ 373,7 370,7 @@ public class NosBrowserManager

        if (_petManagerList is null)
        {
            var petManagerResult = PetManagerList.Create(this, _petManagerOptions);
            var petManagerResult = Structs.PetManagerList.Create(this, _petManagerOptions);
            if (!petManagerResult.IsSuccess)
            {
                errorResults.Add


@@ 391,7 388,7 @@ public class NosBrowserManager

        if (_ntClient is null)
        {
            var ntClientResult = NtClient.Create(this, _ntClientOptions);
            var ntClientResult = Structs.NtClient.Create(this, _ntClientOptions);
            if (!ntClientResult.IsSuccess)
            {
                errorResults.Add


@@ 414,4 411,74 @@ public class NosBrowserManager
            _ => (Result)new AggregateError(errorResults)
        };
    }

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

    /// <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)
        {
            throw new InvalidOperationException
            (
                $"Could not get {typeof(TModule)}. The browser manager is not initialized. Did you forget to call NosBrowserManager.Initialize?"
            );
        }

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

        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)
        {
            throw new InvalidOperationException
            (
                $"Could not get {moduleType.Name}. The browser manager is not initialized. Did you forget to call NosBrowserManager.Initialize?"
            );
        }

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

        return nosObject;
    }
}
\ 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)

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.

Do not follow this link