~ruther/NosSmooth

2d0fe0a749c4e7587d3e06cb59759707f34e499e — František Boháček 3 years ago 228e263
feat(localbinding): split obtaining objects from hooking completely
A Local/NosSmooth.LocalBinding/Extensions/MemoryExtensions.cs => Local/NosSmooth.LocalBinding/Extensions/MemoryExtensions.cs +33 -0
@@ 0,0 1,33 @@
//
//  MemoryExtensions.cs
//
//  Copyright (c) František Boháček. All rights reserved.
//  Licensed under the MIT license. See LICENSE file in the project root for full license information.

using Reloaded.Memory.Sources;

namespace NosSmooth.LocalBinding.Extensions;

/// <summary>
/// Extension methods for <see cref="IMemory"/>.
/// </summary>
public static class MemoryExtensions
{
    /// <summary>
    /// Follows the offsets to a 32-bit pointer.
    /// </summary>
    /// <param name="memory">The memory.</param>
    /// <param name="staticAddress">The static address to follow offsets from.</param>
    /// <param name="offsets">The offsets, first offset is the 0-th element.</param>
    /// <returns>A final address.</returns>
    public static IntPtr FollowStaticAddressOffsets(this IMemory memory, int staticAddress, int[] offsets)
    {
        int address = staticAddress;
        foreach (var offset in offsets)
        {
            memory.SafeRead((IntPtr)(address + offset), out address);
        }

        return (IntPtr)address;
    }
}
\ No newline at end of file

M Local/NosSmooth.LocalBinding/ExternalNosBrowser.cs => Local/NosSmooth.LocalBinding/ExternalNosBrowser.cs +7 -7
@@ 19,8 19,8 @@ namespace NosSmooth.LocalBinding;
/// </summary>
public class ExternalNosBrowser
{
    private readonly CharacterBindingOptions _characterOptions;
    private readonly SceneManagerBindingOptions _sceneManagerOptions;
    private readonly PlayerManagerOptions _playerManagerOptions;
    private readonly SceneManagerOptions _sceneManagerOptions;
    private PlayerManager? _playerManager;
    private SceneManager? _sceneManager;



@@ 28,16 28,16 @@ public class ExternalNosBrowser
    /// Initializes a new instance of the <see cref="ExternalNosBrowser"/> class.
    /// </summary>
    /// <param name="process">The process to browse.</param>
    /// <param name="characterOptions">The options for obtaining player manager.</param>
    /// <param name="playerManagerOptions">The options for obtaining player manager.</param>
    /// <param name="sceneManagerOptions">The scene manager options.</param>
    public ExternalNosBrowser
    (
        Process process,
        CharacterBindingOptions characterOptions,
        SceneManagerBindingOptions sceneManagerOptions
        PlayerManagerOptions playerManagerOptions,
        SceneManagerOptions sceneManagerOptions
    )
    {
        _characterOptions = characterOptions;
        _playerManagerOptions = playerManagerOptions;
        _sceneManagerOptions = sceneManagerOptions;
        Process = process;
        Memory = new ExternalMemory(process);


@@ 67,7 67,7 @@ public class ExternalNosBrowser
    {
        if (_playerManager is null)
        {
            var playerManagerResult = PlayerManager.Create(this, _characterOptions);
            var playerManagerResult = PlayerManager.Create(this, _playerManagerOptions);
            if (!playerManagerResult.IsSuccess)
            {
                return playerManagerResult;

M Local/NosSmooth.LocalBinding/NosBindingManager.cs => Local/NosSmooth.LocalBinding/NosBindingManager.cs +45 -10
@@ 23,6 23,7 @@ public class NosBindingManager : IDisposable
{
    private readonly CharacterBindingOptions _characterBindingOptions;
    private readonly NetworkBindingOptions _networkBindingOptions;
    private readonly ExternalNosBrowser _nosBrowser;
    private SceneManagerBindingOptions _sceneManagerBindingOptions;

    private NetworkBinding? _networkBinding;


@@ 35,11 36,15 @@ public class NosBindingManager : IDisposable
    /// <param name="characterBindingOptions">The character binding options.</param>
    /// <param name="networkBindingOptions">The network binding options.</param>
    /// <param name="sceneManagerBindingOptions">The scene manager binding options.</param>
    /// <param name="playerManagerOptions">The player manager options.</param>
    /// <param name="sceneManagerOptions">The scene manager options.</param>
    public NosBindingManager
    (
        IOptions<CharacterBindingOptions> characterBindingOptions,
        IOptions<NetworkBindingOptions> networkBindingOptions,
        IOptions<SceneManagerBindingOptions> sceneManagerBindingOptions
        IOptions<SceneManagerBindingOptions> sceneManagerBindingOptions,
        IOptions<PlayerManagerOptions> playerManagerOptions,
        IOptions<SceneManagerOptions> sceneManagerOptions
    )
    {
        Hooks = new ReloadedHooks();


@@ 48,6 53,8 @@ public class NosBindingManager : IDisposable
        _characterBindingOptions = characterBindingOptions.Value;
        _networkBindingOptions = networkBindingOptions.Value;
        _sceneManagerBindingOptions = sceneManagerBindingOptions.Value;
        _nosBrowser = new ExternalNosBrowser
            (Process.GetCurrentProcess(), playerManagerOptions.Value, sceneManagerOptions.Value);
    }

    /// <summary>


@@ 76,7 83,9 @@ public class NosBindingManager : IDisposable
            if (_networkBinding is null)
            {
                throw new InvalidOperationException
                    ("Could not get network. The binding manager is not initialized. Did you forget to call NosBindingManager.Initialize?");
                (
                    "Could not get network. The binding manager is not initialized. Did you forget to call NosBindingManager.Initialize?"
                );
            }

            return _networkBinding;


@@ 94,7 103,9 @@ public class NosBindingManager : IDisposable
            if (_characterBinding is null)
            {
                throw new InvalidOperationException
                    ("Could not get character. The binding manager is not initialized. Did you forget to call NosBindingManager.Initialize?");
                (
                    "Could not get character. The binding manager is not initialized. Did you forget to call NosBindingManager.Initialize?"
                );
            }

            return _characterBinding;


@@ 112,7 123,9 @@ public class NosBindingManager : IDisposable
            if (_sceneManagerBinding is null)
            {
                throw new InvalidOperationException
                    ("Could not get scene manager. The binding manager is not initialized. Did you forget to call NosBindingManager.Initialize?");
                (
                    "Could not get scene manager. The binding manager is not initialized. Did you forget to call NosBindingManager.Initialize?"
                );
            }

            return _sceneManagerBinding;


@@ 132,19 145,41 @@ public class NosBindingManager : IDisposable
        }
        _networkBinding = network.Entity;

        var character = PlayerManagerBinding.Create(this, _characterBindingOptions);
        if (!character.IsSuccess)
        var playerManager = _nosBrowser.GetPlayerManager();
        if (!playerManager.IsSuccess)
        {
            return Result.FromError(character);
            return Result.FromError(playerManager);
        }
        _characterBinding = character.Entity;

        var sceneManager = SceneManagerBinding.Create(this, _sceneManagerBindingOptions);
        var sceneManager = _nosBrowser.GetSceneManager();
        if (!sceneManager.IsSuccess)
        {
            return Result.FromError(sceneManager);
        }
        _sceneManagerBinding = sceneManager.Entity;

        var playerManagerBinding = PlayerManagerBinding.Create
        (
            this,
            playerManager.Entity,
            _characterBindingOptions
        );
        if (!playerManagerBinding.IsSuccess)
        {
            return Result.FromError(playerManagerBinding);
        }
        _characterBinding = playerManagerBinding.Entity;

        var sceneManagerBinding = SceneManagerBinding.Create
        (
            this,
            sceneManager.Entity,
            _sceneManagerBindingOptions
        );
        if (!sceneManagerBinding.IsSuccess)
        {
            return Result.FromError(sceneManagerBinding);
        }
        _sceneManagerBinding = sceneManagerBinding.Entity;

        return Result.FromSuccess();
    }

M Local/NosSmooth.LocalBinding/Objects/PlayerManagerBinding.cs => Local/NosSmooth.LocalBinding/Objects/PlayerManagerBinding.cs +14 -23
@@ 19,7 19,7 @@ namespace NosSmooth.LocalBinding.Objects;
/// </summary>
public class PlayerManagerBinding
{
        [Function
    [Function
    (
        new[] { FunctionAttribute.Register.eax, FunctionAttribute.Register.edx, FunctionAttribute.Register.ecx },
        FunctionAttribute.Register.eax,


@@ 53,16 53,12 @@ public class PlayerManagerBinding
    /// Create the network binding with finding the network object and functions.
    /// </summary>
    /// <param name="bindingManager">The binding manager.</param>
    /// <param name="playerManager">The player manager.</param>
    /// <param name="options">The options for the binding.</param>
    /// <returns>A network binding or an error.</returns>
    public static Result<PlayerManagerBinding> Create(NosBindingManager bindingManager, CharacterBindingOptions options)
    public static Result<PlayerManagerBinding> Create(NosBindingManager bindingManager, PlayerManager playerManager, CharacterBindingOptions options)
    {
        var process = Process.GetCurrentProcess();
        var characterObjectAddress = bindingManager.Scanner.CompiledFindPattern(options.CharacterObjectPattern);
        if (!characterObjectAddress.Found)
        {
            return new BindingNotFoundError(options.CharacterObjectPattern, "CharacterBinding");
        }

        var walkFunctionAddress = bindingManager.Scanner.CompiledFindPattern(options.WalkFunctionPattern);
        if (!walkFunctionAddress.Found)


@@ 97,7 93,7 @@ public class PlayerManagerBinding
        var binding = new PlayerManagerBinding
        (
            bindingManager,
            (IntPtr)(characterObjectAddress.Offset + (int)process.MainModule!.BaseAddress + 0x06),
            playerManager,
            walkWrapper,
            followEntityWrapper,
            unfollowEntityWrapper


@@ 129,7 125,6 @@ public class PlayerManagerBinding
    }

    private readonly NosBindingManager _bindingManager;
    private readonly IntPtr _characterAddress;

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


@@ 142,20 137,25 @@ public class PlayerManagerBinding
    private PlayerManagerBinding
    (
        NosBindingManager bindingManager,
        IntPtr characterAddress,
        PlayerManager playerManager,
        WalkDelegate originalWalk,
        FollowEntityDelegate originalFollowEntity,
        UnfollowEntityDelegate originalUnfollowEntity
    )
    {
        PlayerManager = playerManager;
        _bindingManager = bindingManager;
        _characterAddress = characterAddress;
        _originalWalk = originalWalk;
        _originalFollowEntity = originalFollowEntity;
        _originalUnfollowEntity = originalUnfollowEntity;
    }

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

    /// <summary>
    /// Event that is called when walk was called by NosTale.
    /// </summary>
    /// <remarks>


@@ 181,15 181,6 @@ public class PlayerManagerBinding
        return Result.FromSuccess();
    }

    private IntPtr GetCharacterAddress()
    {
        IntPtr characterAddress = _characterAddress;
        _bindingManager.Memory.Read(characterAddress, out characterAddress);
        _bindingManager.Memory.Read(characterAddress, out characterAddress);

        return characterAddress;
    }

    /// <summary>
    /// Walk to the given position.
    /// </summary>


@@ 201,7 192,7 @@ public class PlayerManagerBinding
        int param = (y << 16) | x;
        try
        {
            return _originalWalk(GetCharacterAddress(), param);
            return _originalWalk(PlayerManager.Address, param);
        }
        catch (Exception e)
        {


@@ 237,7 228,7 @@ public class PlayerManagerBinding
    {
        try
        {
            _originalFollowEntity(GetCharacterAddress(), entityAddress);
            _originalFollowEntity(PlayerManager.Address, entityAddress);
        }
        catch (Exception e)
        {


@@ 255,7 246,7 @@ public class PlayerManagerBinding
    {
        try
        {
            _originalUnfollowEntity(GetCharacterAddress());
            _originalUnfollowEntity(PlayerManager.Address);
        }
        catch (Exception e)
        {

M Local/NosSmooth.LocalBinding/Objects/SceneManagerBinding.cs => Local/NosSmooth.LocalBinding/Objects/SceneManagerBinding.cs +6 -20
@@ 36,18 36,13 @@ public class SceneManagerBinding
    /// Create the scene manager binding.
    /// </summary>
    /// <param name="bindingManager">The binding manager.</param>
    /// <param name="sceneManager">The scene manager.</param>
    /// <param name="bindingOptions">The options for the binding.</param>
    /// <returns>A network binding or an error.</returns>
    public static Result<SceneManagerBinding> Create
        (NosBindingManager bindingManager, SceneManagerBindingOptions bindingOptions)
        (NosBindingManager bindingManager, SceneManager sceneManager, SceneManagerBindingOptions bindingOptions)
    {
        var process = Process.GetCurrentProcess();
        var sceneManagerObjectAddress = bindingManager.Scanner.CompiledFindPattern
            (bindingOptions.SceneManagerObjectPattern);
        if (!sceneManagerObjectAddress.Found)
        {
            return new BindingNotFoundError(bindingOptions.SceneManagerObjectPattern, "SceneManagerBinding");
        }

        var focusEntityAddress = bindingManager.Scanner.CompiledFindPattern(bindingOptions.FocusEntityPattern);
        if (!focusEntityAddress.Found)


@@ 62,7 57,7 @@ public class SceneManagerBinding
        var binding = new SceneManagerBinding
        (
            bindingManager,
            (IntPtr)(sceneManagerObjectAddress.Offset + (int)process.MainModule!.BaseAddress + 0x01),
            sceneManager,
            focusEntityWrapper
        );



@@ 77,7 72,6 @@ public class SceneManagerBinding
    }

    private readonly NosBindingManager _bindingManager;
    private readonly IntPtr _sceneManagerAddress;
    private FocusEntityDelegate _originalFocusEntity;

    private IHook<FocusEntityDelegate>? _focusHook;


@@ 85,13 79,13 @@ public class SceneManagerBinding
    private SceneManagerBinding
    (
        NosBindingManager bindingManager,
        IntPtr sceneManagerAddress,
        SceneManager sceneManager,
        FocusEntityDelegate originalFocusEntity
    )
    {
        _originalFocusEntity = originalFocusEntity;
        _bindingManager = bindingManager;
        _sceneManagerAddress = sceneManagerAddress;
        SceneManager = sceneManager;
    }

    /// <summary>


@@ 105,15 99,7 @@ public class SceneManagerBinding
    /// <summary>
    /// Gets the scene manager object.
    /// </summary>
    public SceneManager SceneManager => new SceneManager(_bindingManager.Memory, GetSceneManagerAddress());

    private IntPtr GetSceneManagerAddress()
    {
        IntPtr sceneManagerAddress = _sceneManagerAddress;
        _bindingManager.Memory.Read(sceneManagerAddress, out sceneManagerAddress);

        return sceneManagerAddress;
    }
    public SceneManager SceneManager { get; }

    /// <summary>
    /// Focus the entity with the given id.

M Local/NosSmooth.LocalBinding/Options/CharacterBindingOptions.cs => Local/NosSmooth.LocalBinding/Options/CharacterBindingOptions.cs +0 -9
@@ 19,15 19,6 @@ public class CharacterBindingOptions
    public bool HookWalk { get; set; } = true;

    /// <summary>
    /// Gets or sets the pattern to find the character object at.
    /// </summary>
    /// <remarks>
    /// The address of the object is "three pointers down" from address found on this pattern.
    /// </remarks>
    public string CharacterObjectPattern { get; set; }
        = "33 C9 8B 55 FC A1 ?? ?? ?? ?? E8 ?? ?? ?? ??";

    /// <summary>
    /// Gets or sets the pattern to find the walk function at.
    /// </summary>
    public string WalkFunctionPattern { get; set; } = "55 8B EC 83 C4 EC 53 56 57 66 89 4D FA";

A Local/NosSmooth.LocalBinding/Options/PlayerManagerOptions.cs => Local/NosSmooth.LocalBinding/Options/PlayerManagerOptions.cs +27 -0
@@ 0,0 1,27 @@
//
//  PlayerManagerOptions.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.Structs;

namespace NosSmooth.LocalBinding.Options;

/// <summary>
/// Options for <see cref="PlayerManager"/>.
/// </summary>
public class PlayerManagerOptions
{
    /// <summary>
    /// Gets or sets the pattern to find the character object at.
    /// </summary>
    public string PlayerManagerPattern { get; set; }
        = "33 C9 8B 55 FC A1 ?? ?? ?? ?? E8 ?? ?? ?? ??";

    /// <summary>
    /// Gets or sets the offsets to find the player manager at from the static address.
    /// </summary>
    public int[] PlayerManagerOffsets { get; set; }
        = { 6, 0, 0 };
}
\ No newline at end of file

M Local/NosSmooth.LocalBinding/Options/SceneManagerBindingOptions.cs => Local/NosSmooth.LocalBinding/Options/SceneManagerBindingOptions.cs +0 -9
@@ 14,15 14,6 @@ namespace NosSmooth.LocalBinding.Options;
public class SceneManagerBindingOptions
{
    /// <summary>
    /// Gets or sets the pattern to find the scene manager object at.
    /// </summary>
    /// <remarks>
    /// The address of the object is direct pointer to the scene manager.
    /// </remarks>
    public string SceneManagerObjectPattern { get; set; }
        = "FF ?? ?? ?? ?? ?? FF FF FF 00 00 00 00 00 00 00 00 00 00 00 00 FF FF FF FF";

    /// <summary>
    /// Gets or sets the pattern to find the focus entity method at.
    /// </summary>
    public string FocusEntityPattern { get; set; }

A Local/NosSmooth.LocalBinding/Options/SceneManagerOptions.cs => Local/NosSmooth.LocalBinding/Options/SceneManagerOptions.cs +30 -0
@@ 0,0 1,30 @@
//
//  SceneManagerOptions.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.Structs;

namespace NosSmooth.LocalBinding.Options;

/// <summary>
/// Options for <see cref="SceneManager"/>.
/// </summary>
public class SceneManagerOptions
{
    /// <summary>
    /// Gets or sets the pattern to find the scene manager object at.
    /// </summary>
    /// <remarks>
    /// The address of the object is direct pointer to the scene manager.
    /// </remarks>
    public string SceneManagerObjectPattern { get; set; }
        = "FF ?? ?? ?? ?? ?? FF FF FF 00 00 00 00 00 00 00 00 00 00 00 00 FF FF FF FF";

    /// <summary>
    /// Gets or sets the offsets to find the scene manager at from the static address.
    /// </summary>
    public int[] SceneManagerOffsets { get; set; }
        = { 1 };
}
\ No newline at end of file

A Local/NosSmooth.LocalBinding/Structs/ControlManager.cs => Local/NosSmooth.LocalBinding/Structs/ControlManager.cs +79 -0
@@ 0,0 1,79 @@
//
//  ControlManager.cs
//
//  Copyright (c) František Boháček. All rights reserved.
//  Licensed under the MIT license. See LICENSE file in the project root for full license information.

using Reloaded.Memory.Sources;

namespace NosSmooth.LocalBinding.Structs;

/// <summary>
/// Base for player and pet managers.
/// </summary>
public abstract class ControlManager
{
    private readonly IMemory _memory;

    /// <summary>
    /// Initializes a new instance of the <see cref="ControlManager"/> class.
    /// </summary>
    /// <param name="memory">The memory.</param>
    public ControlManager(IMemory memory)
    {
        _memory = memory;
    }

    /// <summary>
    /// Gets the address of the manager.
    /// </summary>
    public abstract IntPtr Address { get; }

    /// <summary>
    /// Gets the current player position x coordinate.
    /// </summary>
    public int X
    {
        get
        {
            _memory.SafeRead(Address + 0x4, out short x);
            return x;
        }
    }

    /// <summary>
    /// Gets the current player position x coordinate.
    /// </summary>
    public int Y
    {
        get
        {
            _memory.SafeRead(Address + 0x6, out short y);
            return y;
        }
    }

    /// <summary>
    /// Gets the target x coordinate the player is moving to.
    /// </summary>
    public int TargetX
    {
        get
        {
            _memory.SafeRead(Address + 0x8, out short targetX);
            return targetX;
        }
    }

    /// <summary>
    /// Gets the target y coordinate the player is moving to.
    /// </summary>
    public int TargetY
    {
        get
        {
            _memory.SafeRead(Address + 0xA, out short targetX);
            return targetX;
        }
    }
}
\ No newline at end of file

M Local/NosSmooth.LocalBinding/Structs/MapObjBaseList.cs => Local/NosSmooth.LocalBinding/Structs/MapObjBaseList.cs +4 -4
@@ 17,7 17,7 @@ public class MapObjBaseList : IEnumerable<MapBaseObj>
{
    private readonly IMemory _memory;
    private readonly IntPtr _objListPointer;
    private readonly ArrayPtr<IntPtr> _objList;
    private readonly ArrayPtr<int> _objList;

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


@@ 26,8 26,8 @@ public class MapObjBaseList : IEnumerable<MapBaseObj>
    /// <param name="objListPointer">The object list pointer.</param>
    public MapObjBaseList(IMemory memory, IntPtr objListPointer)
    {
        memory.Read(objListPointer + 0x04, out IntPtr arrayFirst);
        _objList = new ArrayPtr<IntPtr>((ulong)arrayFirst, source: memory);
        memory.Read(objListPointer + 0x04, out uint arrayFirst);
        _objList = new ArrayPtr<int>(arrayFirst, source: memory);
        _memory = memory;
        _objListPointer = objListPointer;
    }


@@ 46,7 46,7 @@ public class MapObjBaseList : IEnumerable<MapBaseObj>
                throw new IndexOutOfRangeException();
            }

            return new MapBaseObj(_memory, _objList[index]);
            return new MapBaseObj(_memory, (IntPtr)_objList[index]);
        }
    }


M Local/NosSmooth.LocalBinding/Structs/PlayerManager.cs => Local/NosSmooth.LocalBinding/Structs/PlayerManager.cs +17 -60
@@ 6,6 6,7 @@

using Microsoft.Extensions.Options;
using NosSmooth.LocalBinding.Errors;
using NosSmooth.LocalBinding.Extensions;
using NosSmooth.LocalBinding.Options;
using Reloaded.Memory.Sources;
using Remora.Results;


@@ 15,7 16,7 @@ namespace NosSmooth.LocalBinding.Structs;
/// <summary>
/// NosTale player manager.
/// </summary>
public class PlayerManager
public class PlayerManager : ControlManager
{
    /// <summary>
    /// Create <see cref="PlayerManager"/> instance.


@@ 23,12 24,12 @@ public class PlayerManager
    /// <param name="nosBrowser">The NosTale process browser.</param>
    /// <param name="options">The options.</param>
    /// <returns>The player manager or an error.</returns>
    public static Result<PlayerManager> Create(ExternalNosBrowser nosBrowser, CharacterBindingOptions options)
    public static Result<PlayerManager> Create(ExternalNosBrowser nosBrowser, PlayerManagerOptions options)
    {
        var characterObjectAddress = nosBrowser.Scanner.CompiledFindPattern(options.CharacterObjectPattern);
        var characterObjectAddress = nosBrowser.Scanner.CompiledFindPattern(options.PlayerManagerPattern);
        if (!characterObjectAddress.Found)
        {
            return new BindingNotFoundError(options.CharacterObjectPattern, "PlayerManager");
            return new BindingNotFoundError(options.PlayerManagerPattern, "PlayerManager");
        }

        if (nosBrowser.Process.MainModule is null)


@@ 36,77 37,33 @@ public class PlayerManager
            return new NotFoundError("Cannot find the main module of the target process.");
        }

        var ptrAddress = nosBrowser.Process.MainModule.BaseAddress + characterObjectAddress.Offset + 0x06;
        nosBrowser.Memory.SafeRead(ptrAddress, out int address);
        nosBrowser.Memory.SafeRead((IntPtr)address, out address);
        return new PlayerManager(nosBrowser.Memory, (IntPtr)address);
        var staticAddress = (int)nosBrowser.Process.MainModule.BaseAddress + characterObjectAddress.Offset;
        return new PlayerManager(nosBrowser.Memory, staticAddress, options.PlayerManagerOffsets);
    }

    private readonly IMemory _memory;
    private readonly int _staticPlayerManagerAddress;
    private readonly int[] _playerManagerOffsets;

    /// <summary>
    /// Initializes a new instance of the <see cref="PlayerManager"/> class.
    /// </summary>
    /// <param name="memory">The memory.</param>
    /// <param name="playerManager">The pointer to the beginning of the player manager structure.</param>
    public PlayerManager(IMemory memory, IntPtr playerManager)
    /// <param name="staticPlayerManagerAddress">The pointer to the beginning of the player manager structure.</param>
    /// <param name="playerManagerOffsets">The offsets to get the player manager address from the static one.</param>
    public PlayerManager(IMemory memory, int staticPlayerManagerAddress, int[] playerManagerOffsets)
        : base(memory)
    {
        _memory = memory;
        Address = playerManager;
        _staticPlayerManagerAddress = staticPlayerManagerAddress;
        _playerManagerOffsets = playerManagerOffsets;
    }

    /// <summary>
    /// Gets the address to the player manager.
    /// </summary>
    public IntPtr Address { get; }

    /// <summary>
    /// Gets the current player position x coordinate.
    /// </summary>
    public int X
    {
        get
        {
            _memory.SafeRead(Address + 0x4, out short x);
            return x;
        }
    }

    /// <summary>
    /// Gets the current player position x coordinate.
    /// </summary>
    public int Y
    {
        get
        {
            _memory.SafeRead(Address + 0x6, out short y);
            return y;
        }
    }

    /// <summary>
    /// Gets the target x coordinate the player is moving to.
    /// </summary>
    public int TargetX
    {
        get
        {
            _memory.SafeRead(Address + 0x8, out short targetX);
            return targetX;
        }
    }

    /// <summary>
    /// Gets the target y coordinate the player is moving to.
    /// </summary>
    public int TargetY
    {
        get
        {
            _memory.SafeRead(Address + 0xA, out short targetX);
            return targetX;
        }
    }
    public override IntPtr Address => _memory.FollowStaticAddressOffsets
        (_staticPlayerManagerAddress, _playerManagerOffsets);

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

M Local/NosSmooth.LocalBinding/Structs/SceneManager.cs => Local/NosSmooth.LocalBinding/Structs/SceneManager.cs +23 -15
@@ 5,6 5,7 @@
//  Licensed under the MIT license. See LICENSE file in the project root for full license information.

using NosSmooth.LocalBinding.Errors;
using NosSmooth.LocalBinding.Extensions;
using NosSmooth.LocalBinding.Options;
using Reloaded.Memory.Sources;
using Remora.Results;


@@ 22,7 23,7 @@ public class SceneManager
    /// <param name="nosBrowser">The NosTale process browser.</param>
    /// <param name="options">The options.</param>
    /// <returns>The player manager or an error.</returns>
    public static Result<SceneManager> Create(ExternalNosBrowser nosBrowser, SceneManagerBindingOptions options)
    public static Result<SceneManager> Create(ExternalNosBrowser nosBrowser, SceneManagerOptions options)
    {
        var characterObjectAddress = nosBrowser.Scanner.CompiledFindPattern(options.SceneManagerObjectPattern);
        if (!characterObjectAddress.Found)


@@ 35,44 36,51 @@ public class SceneManager
            return new NotFoundError("Cannot find the main module of the target process.");
        }

        var ptrAddress = nosBrowser.Process.MainModule.BaseAddress + characterObjectAddress.Offset;
        nosBrowser.Memory.SafeRead(ptrAddress, out ptrAddress);
        return new SceneManager(nosBrowser.Memory, ptrAddress);
        int staticManagerAddress = (int)nosBrowser.Process.MainModule.BaseAddress + characterObjectAddress.Offset + 1;
        return new SceneManager(nosBrowser.Memory, staticManagerAddress, options.SceneManagerOffsets);
    }

    private readonly int[] _sceneManagerOffsets;
    private readonly IMemory _memory;
    private readonly IntPtr _sceneManager;
    private readonly int _staticSceneManagerAddress;

    /// <summary>
    /// Initializes a new instance of the <see cref="SceneManager"/> class.
    /// </summary>
    /// <param name="memory">The memory.</param>
    /// <param name="sceneManager">The pointer to the scene manager.</param>
    public SceneManager(IMemory memory, IntPtr 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)
    {
        _memory = memory;
        _sceneManager = sceneManager;
        _staticSceneManagerAddress = staticSceneManagerAddress;
        _sceneManagerOffsets = sceneManagerOffsets;
    }

    /// <summary>
    /// Gets the address of the scene manager.
    /// </summary>
    public IntPtr Address => _memory.FollowStaticAddressOffsets(_staticSceneManagerAddress, _sceneManagerOffsets);

    /// <summary>
    /// Gets the player list.
    /// </summary>
    public MapObjBaseList PlayerList => new MapObjBaseList(_memory, ReadPtr(_sceneManager + 0xC));
    public MapObjBaseList PlayerList => new MapObjBaseList(_memory, ReadPtr(Address + 0xC));

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

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

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

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


@@ 81,7 89,7 @@ public class SceneManager
    {
        get
        {
            var ptr = ReadPtr(_sceneManager + 0x48);
            var ptr = ReadPtr(Address + 0x48);
            if (ptr == IntPtr.Zero)
            {
                return null;


@@ 93,7 101,7 @@ public class SceneManager

    private IntPtr ReadPtr(IntPtr ptr)
    {
        _memory.Read(ptr, out IntPtr read);
        return read;
        _memory.Read(ptr, out int read);
        return (IntPtr)read;
    }
}
\ No newline at end of file