~ruther/NosSmooth

73dd9e7c06fb751344b3e3e1a76b559112191627 — Rutherther 3 years ago a7292e3
chore: remove projects for local injectable client, move them to NosSmooth.Local
97 files changed, 0 insertions(+), 6553 deletions(-)

D Local/NosSmooth.ChatCommands/ChatCommandInterceptor.cs
D Local/NosSmooth.ChatCommands/ChatCommandsOptions.cs
D Local/NosSmooth.ChatCommands/FeedbackService.cs
D Local/NosSmooth.ChatCommands/NosSmooth.ChatCommands.csproj
D Local/NosSmooth.ChatCommands/ServiceCollectionExtensions.cs
D Local/NosSmooth.Inject/NosSmooth.Inject.vcxproj
D Local/NosSmooth.Inject/NosSmooth.Inject.vcxproj.filters
D Local/NosSmooth.Inject/coreclr_delegates.h
D Local/NosSmooth.Inject/dllmain.cpp
D Local/NosSmooth.Inject/framework.h
D Local/NosSmooth.Inject/hostfxr.h
D Local/NosSmooth.Inject/nethost.h
D Local/NosSmooth.Inject/nossmooth.cpp
D Local/NosSmooth.Inject/nossmooth.h
D Local/NosSmooth.Inject/pch.cpp
D Local/NosSmooth.Inject/pch.h
D Local/NosSmooth.Injector.CLI/Commands/InjectCommand.cs
D Local/NosSmooth.Injector.CLI/Commands/ListProcessesCommand.cs
D Local/NosSmooth.Injector.CLI/NosSmooth.Injector.CLI.csproj
D Local/NosSmooth.Injector.CLI/Program.cs
D Local/NosSmooth.Injector.CLI/app.manifest
D Local/NosSmooth.Injector/Errors/InjectionFailedError.cs
D Local/NosSmooth.Injector/Errors/InsufficientPermissionsError.cs
D Local/NosSmooth.Injector/Errors/ProcessNotFoundError.cs
D Local/NosSmooth.Injector/LoadParams.cs
D Local/NosSmooth.Injector/ManagedMemoryAllocation.cs
D Local/NosSmooth.Injector/NosInjector.cs
D Local/NosSmooth.Injector/NosInjectorOptions.cs
D Local/NosSmooth.Injector/NosSmooth.Injector.csproj
D Local/NosSmooth.LocalBinding/Errors/BindingNotFoundError.cs
D Local/NosSmooth.LocalBinding/Errors/CouldNotInitializeModuleError.cs
D Local/NosSmooth.LocalBinding/Errors/NotNostaleProcessError.cs
D Local/NosSmooth.LocalBinding/Extensions/MemoryExtensions.cs
D Local/NosSmooth.LocalBinding/Extensions/ServiceCollectionExtensions.cs
D Local/NosSmooth.LocalBinding/IsExternalInit.cs
D Local/NosSmooth.LocalBinding/NosBindingManager.cs
D Local/NosSmooth.LocalBinding/NosBrowserManager.cs
D Local/NosSmooth.LocalBinding/NosSmooth.LocalBinding.csproj
D Local/NosSmooth.LocalBinding/Objects/NetworkBinding.cs
D Local/NosSmooth.LocalBinding/Objects/NostaleStringA.cs
D Local/NosSmooth.LocalBinding/Objects/PetManagerBinding.cs
D Local/NosSmooth.LocalBinding/Objects/PlayerManagerBinding.cs
D Local/NosSmooth.LocalBinding/Objects/UnitManagerBinding.cs
D Local/NosSmooth.LocalBinding/Options/CharacterBindingOptions.cs
D Local/NosSmooth.LocalBinding/Options/NetworkBindingOptions.cs
D Local/NosSmooth.LocalBinding/Options/PetManagerBindingOptions.cs
D Local/NosSmooth.LocalBinding/Options/PetManagerOptions.cs
D Local/NosSmooth.LocalBinding/Options/PlayerManagerOptions.cs
D Local/NosSmooth.LocalBinding/Options/SceneManagerOptions.cs
D Local/NosSmooth.LocalBinding/Options/UnitManagerBindingOptions.cs
D Local/NosSmooth.LocalBinding/Structs/ControlManager.cs
D Local/NosSmooth.LocalBinding/Structs/MapBaseObj.cs
D Local/NosSmooth.LocalBinding/Structs/MapNpcObj.cs
D Local/NosSmooth.LocalBinding/Structs/MapPlayerObj.cs
D Local/NosSmooth.LocalBinding/Structs/NostaleList.cs
D Local/NosSmooth.LocalBinding/Structs/NostaleObject.cs
D Local/NosSmooth.LocalBinding/Structs/PetManager.cs
D Local/NosSmooth.LocalBinding/Structs/PetManagerList.cs
D Local/NosSmooth.LocalBinding/Structs/PlayerManager.cs
D Local/NosSmooth.LocalBinding/Structs/SceneManager.cs
D Local/NosSmooth.LocalClient/CommandHandlers/Walk/ControlCommandWalkHandler.cs
D Local/NosSmooth.LocalClient/CommandHandlers/Walk/Errors/WalkNotFinishedError.cs
D Local/NosSmooth.LocalClient/CommandHandlers/Walk/PetWalkCommandHandler.cs
D Local/NosSmooth.LocalClient/CommandHandlers/Walk/PlayerWalkCommandHandler.cs
D Local/NosSmooth.LocalClient/CommandHandlers/Walk/WalkCommandHandlerOptions.cs
D Local/NosSmooth.LocalClient/CommandHandlers/Walk/WalkUnfinishedReason.cs
D Local/NosSmooth.LocalClient/Extensions/ServiceCollectionExtensions.cs
D Local/NosSmooth.LocalClient/IPacketInterceptor.cs
D Local/NosSmooth.LocalClient/IsExternalInit.cs
D Local/NosSmooth.LocalClient/LocalClientOptions.cs
D Local/NosSmooth.LocalClient/NosSmooth.LocalClient.csproj
D Local/NosSmooth.LocalClient/NostaleLocalClient.cs
D Local/NosSmooth.LocalClient/NostaleWindow.cs
D Local/NosSmooth.LocalClient/Utils/User32.cs
D NosSmooth.Unix.sln
D NosSmooth.Unix.sln.DotSettings
M NosSmooth.sln
D Samples/ExternalBrowser/ExternalBrowser.csproj
D Samples/ExternalBrowser/Program.cs
D Samples/InterceptNameChanger/DllMain.cs
D Samples/InterceptNameChanger/FodyWeavers.xml
D Samples/InterceptNameChanger/InterceptNameChanger.csproj
D Samples/InterceptNameChanger/NameChangeInterceptor.cs
D Samples/InterceptNameChanger/NameChanger.cs
D Samples/InterceptNameChanger/Properties/AssemblyInfo.cs
D Samples/SimpleChat/DllMain.cs
D Samples/SimpleChat/FodyWeavers.xml
D Samples/SimpleChat/SayResponder.cs
D Samples/SimpleChat/SimpleChat.cs
D Samples/SimpleChat/SimpleChat.csproj
D Samples/WalkCommands/Commands/CombatCommands.cs
D Samples/WalkCommands/Commands/DetachCommand.cs
D Samples/WalkCommands/Commands/WalkCommands.cs
D Samples/WalkCommands/DllMain.cs
D Samples/WalkCommands/FodyWeavers.xml
D Samples/WalkCommands/Startup.cs
D Samples/WalkCommands/WalkCommands.csproj
D Local/NosSmooth.ChatCommands/ChatCommandInterceptor.cs => Local/NosSmooth.ChatCommands/ChatCommandInterceptor.cs +0 -92
@@ 1,92 0,0 @@
//
//  ChatCommandInterceptor.cs
//
//  Copyright (c) František Boháček. All rights reserved.
//  Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System.Reflection.Emit;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using NosSmooth.Core.Extensions;
using NosSmooth.LocalClient;
using NosSmooth.Packets.Enums;
using NosSmooth.Packets.Enums.Chat;
using NosSmooth.Packets.Server.Chat;
using Remora.Commands.Services;

namespace NosSmooth.ChatCommands;

/// <summary>
/// Handles commands in the chat.
/// </summary>
public class ChatCommandInterceptor : IPacketInterceptor
{
    private readonly CommandService _commandService;
    private readonly IServiceProvider _serviceProvider;
    private readonly FeedbackService _feedbackService;
    private readonly ILogger<ChatCommandInterceptor> _logger;
    private readonly ChatCommandsOptions _options;

    /// <summary>
    /// Initializes a new instance of the <see cref="ChatCommandInterceptor"/> class.
    /// </summary>
    /// <param name="options">The options.</param>
    /// <param name="commandService">The command service.</param>
    /// <param name="serviceProvider">The services.</param>
    /// <param name="feedbackService">The feedback service.</param>
    /// <param name="logger">The logger.</param>
    public ChatCommandInterceptor
    (
        IOptions<ChatCommandsOptions> options,
        CommandService commandService,
        IServiceProvider serviceProvider,
        FeedbackService feedbackService,
        ILogger<ChatCommandInterceptor> logger
    )
    {
        _commandService = commandService;
        _serviceProvider = serviceProvider;
        _feedbackService = feedbackService;
        _logger = logger;
        _options = options.Value;
    }

    /// <inheritdoc />
    public bool InterceptSend(ref string packet)
    {
        ReadOnlySpan<char> span = packet;
        if (span.StartsWith("say ") && span.Slice(4).StartsWith(_options.Prefix))
        {
            var command = span.Slice(4 + _options.Prefix.Length).ToString();
            Task.Run(async () => await ExecuteCommand(command));
            return false;
        }

        return true;
    }

    /// <inheritdoc />
    public bool InterceptReceive(ref string packet)
    {
        return true;
    }

    private async Task ExecuteCommand(string command)
    {
        var preparedResult = await _commandService.TryPrepareCommandAsync(command, _serviceProvider);
        if (!preparedResult.IsSuccess)
        {
            _logger.LogError($"Could not prepare \"{command}\"");
            _logger.LogResultError(preparedResult);
            await _feedbackService.SendErrorMessageAsync($"Could not prepare the given command. {preparedResult.Error.Message}");
        }

        var executeResult = await _commandService.TryExecuteAsync(preparedResult.Entity, _serviceProvider);
        if (!executeResult.IsSuccess)
        {
            _logger.LogError($"Could not execute \"{command}\"");
            _logger.LogResultError(executeResult);
            await _feedbackService.SendErrorMessageAsync($"Could not execute the given command. {executeResult.Error.Message}");
        }
    }
}
\ No newline at end of file

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

namespace NosSmooth.ChatCommands;

/// <summary>
/// Options for <see cref="ChatCommandInterceptor"/>.
/// </summary>
public class ChatCommandsOptions
{
    /// <summary>
    /// Gets or sets the command prefix.
    /// </summary>
    public string Prefix { get; set; } = "#";
}
\ No newline at end of file

D Local/NosSmooth.ChatCommands/FeedbackService.cs => Local/NosSmooth.ChatCommands/FeedbackService.cs +0 -68
@@ 1,68 0,0 @@
//
//  FeedbackService.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.Core.Client;
using NosSmooth.Packets.Enums;
using NosSmooth.Packets.Enums.Chat;
using NosSmooth.Packets.Server.Chat;
using Remora.Results;

namespace NosSmooth.ChatCommands;

/// <summary>
/// Feedback for chat commands.
/// </summary>
public class FeedbackService
{
    private readonly INostaleClient _client;

    /// <summary>
    /// Initializes a new instance of the <see cref="FeedbackService"/> class.
    /// </summary>
    /// <param name="client">The nostale client.</param>
    public FeedbackService(INostaleClient client)
    {
        _client = client;

    }

    /// <summary>
    /// Send message error.
    /// </summary>
    /// <param name="message">The message to send.</param>
    /// <param name="ct">The cancellation token for cancelling the operation.</param>
    /// <returns>A result that may or may not have succeeded.</returns>
    public Task<Result> SendErrorMessageAsync(string message, CancellationToken ct = default)
        => SendMessageAsync(message, SayColor.Red, ct);

    /// <summary>
    /// Send message success.
    /// </summary>
    /// <param name="message">The message to send.</param>
    /// <param name="ct">The cancellation token for cancelling the operation.</param>
    /// <returns>A result that may or may not have succeeded.</returns>
    public Task<Result> SendSuccessMessageAsync(string message, CancellationToken ct = default)
        => SendMessageAsync(message, SayColor.Green, ct);

    /// <summary>
    /// Send message info.
    /// </summary>
    /// <param name="message">The message to send.</param>
    /// <param name="ct">The cancellation token for cancelling the operation.</param>
    /// <returns>A result that may or may not have succeeded.</returns>
    public Task<Result> SendInfoMessageAsync(string message, CancellationToken ct = default)
        => SendMessageAsync(message, SayColor.Default, ct);

    /// <summary>
    /// Send message with the given color.
    /// </summary>
    /// <param name="message">The message to send.</param>
    /// <param name="color">The color.</param>
    /// <param name="ct">The cancellation token for cancelling the operation.</param>
    /// <returns>A result that may or may not have succeeded.</returns>
    public Task<Result> SendMessageAsync(string message, SayColor color, CancellationToken ct = default)
        => _client.ReceivePacketAsync(new SayPacket(EntityType.Map, 0, color, message), ct);
}
\ No newline at end of file

D Local/NosSmooth.ChatCommands/NosSmooth.ChatCommands.csproj => Local/NosSmooth.ChatCommands/NosSmooth.ChatCommands.csproj +0 -18
@@ 1,18 0,0 @@
<Project Sdk="Microsoft.NET.Sdk">

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

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

    <ItemGroup>
      <PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="6.0.0" />
      <PackageReference Include="Remora.Commands" Version="9.0.0" />
    </ItemGroup>

</Project>

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

using Microsoft.Extensions.DependencyInjection;
using NosSmooth.LocalClient;
using NosSmooth.LocalClient.Extensions;
using Remora.Commands.Extensions;

namespace NosSmooth.ChatCommands;

/// <summary>
/// Extension methods for <see cref="IServiceCollection"/>.
/// </summary>
public static class ServiceCollectionExtensions
{
    /// <summary>
    /// Adds NosTale commands and the interceptor to execute commands with.
    /// </summary>
    /// <param name="serviceCollection">The service collection.</param>
    /// <param name="prefix">The prefix for the commands.</param>
    /// <returns>The collection.</returns>
    public static IServiceCollection AddNostaleChatCommands(this IServiceCollection serviceCollection, string prefix = "#")
    {
        serviceCollection
            .Configure<ChatCommandsOptions>((o) => o.Prefix = prefix);

        return serviceCollection
            .AddCommands()
            .Configure<LocalClientOptions>(o => o.AllowIntercept = true)
            .AddSingleton<FeedbackService>()
            .AddPacketInterceptor<ChatCommandInterceptor>();
    }
}
\ No newline at end of file

D Local/NosSmooth.Inject/NosSmooth.Inject.vcxproj => Local/NosSmooth.Inject/NosSmooth.Inject.vcxproj +0 -174
@@ 1,174 0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <ItemGroup Label="ProjectConfigurations">
    <ProjectConfiguration Include="Debug|Win32">
      <Configuration>Debug</Configuration>
      <Platform>Win32</Platform>
    </ProjectConfiguration>
    <ProjectConfiguration Include="Release|Win32">
      <Configuration>Release</Configuration>
      <Platform>Win32</Platform>
    </ProjectConfiguration>
    <ProjectConfiguration Include="Debug|x64">
      <Configuration>Debug</Configuration>
      <Platform>x64</Platform>
    </ProjectConfiguration>
    <ProjectConfiguration Include="Release|x64">
      <Configuration>Release</Configuration>
      <Platform>x64</Platform>
    </ProjectConfiguration>
  </ItemGroup>
  <PropertyGroup Label="Globals">
    <VCProjectVersion>16.0</VCProjectVersion>
    <Keyword>Win32Proj</Keyword>
    <ProjectGuid>{ca2873d8-bd0b-4583-818d-b94a3c2abba3}</ProjectGuid>
    <RootNamespace>NosSmoothInject</RootNamespace>
    <WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
  </PropertyGroup>
  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
    <ConfigurationType>DynamicLibrary</ConfigurationType>
    <UseDebugLibraries>true</UseDebugLibraries>
    <PlatformToolset>v143</PlatformToolset>
    <CharacterSet>Unicode</CharacterSet>
  </PropertyGroup>
  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
    <ConfigurationType>DynamicLibrary</ConfigurationType>
    <UseDebugLibraries>false</UseDebugLibraries>
    <PlatformToolset>v143</PlatformToolset>
    <WholeProgramOptimization>true</WholeProgramOptimization>
    <CharacterSet>Unicode</CharacterSet>
  </PropertyGroup>
  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
    <ConfigurationType>DynamicLibrary</ConfigurationType>
    <UseDebugLibraries>true</UseDebugLibraries>
    <PlatformToolset>v143</PlatformToolset>
    <CharacterSet>Unicode</CharacterSet>
  </PropertyGroup>
  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
    <ConfigurationType>DynamicLibrary</ConfigurationType>
    <UseDebugLibraries>false</UseDebugLibraries>
    <PlatformToolset>v143</PlatformToolset>
    <WholeProgramOptimization>true</WholeProgramOptimization>
    <CharacterSet>Unicode</CharacterSet>
  </PropertyGroup>
  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
  <ImportGroup Label="ExtensionSettings">
  </ImportGroup>
  <ImportGroup Label="Shared">
  </ImportGroup>
  <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
  </ImportGroup>
  <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
  </ImportGroup>
  <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
  </ImportGroup>
  <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
  </ImportGroup>
  <PropertyGroup Label="UserMacros" />
  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
    <LinkIncremental>true</LinkIncremental>
  </PropertyGroup>
  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
    <LinkIncremental>false</LinkIncremental>
  </PropertyGroup>
  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
    <LinkIncremental>true</LinkIncremental>
  </PropertyGroup>
  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
    <LinkIncremental>false</LinkIncremental>
  </PropertyGroup>
  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
    <ClCompile>
      <WarningLevel>Level3</WarningLevel>
      <SDLCheck>true</SDLCheck>
      <PreprocessorDefinitions>WIN32;_DEBUG;NOSSMOOTHINJECT_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
      <ConformanceMode>true</ConformanceMode>
      <PrecompiledHeader>NotUsing</PrecompiledHeader>
      <PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
    </ClCompile>
    <Link>
      <SubSystem>Windows</SubSystem>
      <GenerateDebugInformation>true</GenerateDebugInformation>
      <EnableUAC>false</EnableUAC>
      <AdditionalLibraryDirectories>%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
      <AdditionalDependencies>nethost.lib;%(AdditionalDependencies)</AdditionalDependencies>
    </Link>
  </ItemDefinitionGroup>
  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
    <ClCompile>
      <WarningLevel>Level3</WarningLevel>
      <FunctionLevelLinking>true</FunctionLevelLinking>
      <IntrinsicFunctions>true</IntrinsicFunctions>
      <SDLCheck>true</SDLCheck>
      <PreprocessorDefinitions>WIN32;NDEBUG;NOSSMOOTHINJECT_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
      <ConformanceMode>true</ConformanceMode>
      <PrecompiledHeader>NotUsing</PrecompiledHeader>
      <PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
    </ClCompile>
    <Link>
      <SubSystem>Windows</SubSystem>
      <EnableCOMDATFolding>true</EnableCOMDATFolding>
      <OptimizeReferences>true</OptimizeReferences>
      <GenerateDebugInformation>true</GenerateDebugInformation>
      <EnableUAC>false</EnableUAC>
      <AdditionalLibraryDirectories>%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
      <AdditionalDependencies>nethost.lib;%(AdditionalDependencies)</AdditionalDependencies>
    </Link>
  </ItemDefinitionGroup>
  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
    <ClCompile>
      <WarningLevel>Level3</WarningLevel>
      <SDLCheck>true</SDLCheck>
      <PreprocessorDefinitions>_DEBUG;NOSSMOOTHINJECT_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
      <ConformanceMode>true</ConformanceMode>
      <PrecompiledHeader>NotUsing</PrecompiledHeader>
      <PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
    </ClCompile>
    <Link>
      <SubSystem>Windows</SubSystem>
      <GenerateDebugInformation>true</GenerateDebugInformation>
      <EnableUAC>false</EnableUAC>
      <AdditionalLibraryDirectories>%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
      <AdditionalDependencies>nethost.lib;%(AdditionalDependencies)</AdditionalDependencies>
    </Link>
  </ItemDefinitionGroup>
  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
    <ClCompile>
      <WarningLevel>Level3</WarningLevel>
      <FunctionLevelLinking>true</FunctionLevelLinking>
      <IntrinsicFunctions>true</IntrinsicFunctions>
      <SDLCheck>true</SDLCheck>
      <PreprocessorDefinitions>NDEBUG;NOSSMOOTHINJECT_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
      <ConformanceMode>true</ConformanceMode>
      <PrecompiledHeader>NotUsing</PrecompiledHeader>
      <PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
    </ClCompile>
    <Link>
      <SubSystem>Windows</SubSystem>
      <EnableCOMDATFolding>true</EnableCOMDATFolding>
      <OptimizeReferences>true</OptimizeReferences>
      <GenerateDebugInformation>true</GenerateDebugInformation>
      <EnableUAC>false</EnableUAC>
      <AdditionalLibraryDirectories>%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
      <AdditionalDependencies>nethost.lib;%(AdditionalDependencies)</AdditionalDependencies>
    </Link>
  </ItemDefinitionGroup>
  <ItemGroup>
    <ClCompile Include="dllmain.cpp" />
    <ClCompile Include="nossmooth.cpp" />
  </ItemGroup>
  <ItemGroup>
    <ClInclude Include="coreclr_delegates.h" />
    <ClInclude Include="nethost.h" />
    <ClInclude Include="hostfxr.h" />
    <ClInclude Include="nossmooth.h" />
  </ItemGroup>
  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
  <ImportGroup Label="ExtensionTargets">
  </ImportGroup>
</Project>
\ No newline at end of file

D Local/NosSmooth.Inject/NosSmooth.Inject.vcxproj.filters => Local/NosSmooth.Inject/NosSmooth.Inject.vcxproj.filters +0 -39
@@ 1,39 0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <ItemGroup>
    <Filter Include="Source Files">
      <UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
      <Extensions>cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
    </Filter>
    <Filter Include="Header Files">
      <UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
      <Extensions>h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd</Extensions>
    </Filter>
    <Filter Include="Resource Files">
      <UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
      <Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
    </Filter>
  </ItemGroup>
  <ItemGroup>
    <ClCompile Include="dllmain.cpp">
      <Filter>Source Files</Filter>
    </ClCompile>
    <ClCompile Include="nossmooth.cpp">
      <Filter>Source Files</Filter>
    </ClCompile>
  </ItemGroup>
  <ItemGroup>
    <ClInclude Include="nossmooth.h">
      <Filter>Header Files</Filter>
    </ClInclude>
    <ClInclude Include="hostfxr.h">
      <Filter>Header Files</Filter>
    </ClInclude>
    <ClInclude Include="nethost.h">
      <Filter>Header Files</Filter>
    </ClInclude>
    <ClInclude Include="coreclr_delegates.h">
      <Filter>Header Files</Filter>
    </ClInclude>
  </ItemGroup>
</Project>
\ No newline at end of file

D Local/NosSmooth.Inject/coreclr_delegates.h => Local/NosSmooth.Inject/coreclr_delegates.h +0 -47
@@ 1,47 0,0 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

#ifndef __CORECLR_DELEGATES_H__
#define __CORECLR_DELEGATES_H__

#include <stdint.h>

#if defined(_WIN32)
#define CORECLR_DELEGATE_CALLTYPE __stdcall
#ifdef _WCHAR_T_DEFINED
typedef wchar_t char_t;
#else
typedef unsigned short char_t;
#endif
#else
#define CORECLR_DELEGATE_CALLTYPE
typedef char char_t;
#endif

#define UNMANAGEDCALLERSONLY_METHOD ((const char_t*)-1)

// Signature of delegate returned by coreclr_delegate_type::load_assembly_and_get_function_pointer
typedef int (CORECLR_DELEGATE_CALLTYPE* load_assembly_and_get_function_pointer_fn)(
    const char_t* assembly_path      /* Fully qualified path to assembly */,
    const char_t* type_name          /* Assembly qualified type name */,
    const char_t* method_name        /* Public static method name compatible with delegateType */,
    const char_t* delegate_type_name /* Assembly qualified delegate type name or null
                                        or UNMANAGEDCALLERSONLY_METHOD if the method is marked with
                                        the UnmanagedCallersOnlyAttribute. */,
    void* reserved           /* Extensibility parameter (currently unused and must be 0) */,
    /*out*/ void** delegate          /* Pointer where to store the function pointer result */);

// Signature of delegate returned by load_assembly_and_get_function_pointer_fn when delegate_type_name == null (default)
typedef int (CORECLR_DELEGATE_CALLTYPE* component_entry_point_fn)(void* arg, int32_t arg_size_in_bytes);

typedef int (CORECLR_DELEGATE_CALLTYPE* get_function_pointer_fn)(
    const char_t* type_name          /* Assembly qualified type name */,
    const char_t* method_name        /* Public static method name compatible with delegateType */,
    const char_t* delegate_type_name /* Assembly qualified delegate type name or null,
                                        or UNMANAGEDCALLERSONLY_METHOD if the method is marked with
                                        the UnmanagedCallersOnlyAttribute. */,
    void* load_context       /* Extensibility parameter (currently unused and must be 0) */,
    void* reserved           /* Extensibility parameter (currently unused and must be 0) */,
    /*out*/ void** delegate          /* Pointer where to store the function pointer result */);

#endif // __CORECLR_DELEGATES_H__
\ No newline at end of file

D Local/NosSmooth.Inject/dllmain.cpp => Local/NosSmooth.Inject/dllmain.cpp +0 -19
@@ 1,19 0,0 @@
// dllmain.cpp : Defines the entry point for the DLL application.
#include <windows.h>

BOOL APIENTRY DllMain( HMODULE hModule,
                       DWORD  ul_reason_for_call,
                       LPVOID lpReserved
                     )
{
    switch (ul_reason_for_call)
    {
    case DLL_PROCESS_ATTACH:
    case DLL_THREAD_ATTACH:
    case DLL_THREAD_DETACH:
    case DLL_PROCESS_DETACH:
        break;
    }
    return TRUE;
}


D Local/NosSmooth.Inject/framework.h => Local/NosSmooth.Inject/framework.h +0 -5
@@ 1,5 0,0 @@
#pragma once

#define WIN32_LEAN_AND_MEAN             // Exclude rarely-used stuff from Windows headers
// Windows Header Files
#include <windows.h>

D Local/NosSmooth.Inject/hostfxr.h => Local/NosSmooth.Inject/hostfxr.h +0 -323
@@ 1,323 0,0 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

#ifndef __HOSTFXR_H__
#define __HOSTFXR_H__

#include <stddef.h>
#include <stdint.h>

#if defined(_WIN32)
#define HOSTFXR_CALLTYPE __cdecl
#ifdef _WCHAR_T_DEFINED
typedef wchar_t char_t;
#else
typedef unsigned short char_t;
#endif
#else
#define HOSTFXR_CALLTYPE
typedef char char_t;
#endif

enum hostfxr_delegate_type
{
    hdt_com_activation,
    hdt_load_in_memory_assembly,
    hdt_winrt_activation,
    hdt_com_register,
    hdt_com_unregister,
    hdt_load_assembly_and_get_function_pointer,
    hdt_get_function_pointer,
};

typedef int32_t(HOSTFXR_CALLTYPE* hostfxr_main_fn)(const int argc, const char_t** argv);
typedef int32_t(HOSTFXR_CALLTYPE* hostfxr_main_startupinfo_fn)(
    const int argc,
    const char_t** argv,
    const char_t* host_path,
    const char_t* dotnet_root,
    const char_t* app_path);
typedef int32_t(HOSTFXR_CALLTYPE* hostfxr_main_bundle_startupinfo_fn)(
    const int argc,
    const char_t** argv,
    const char_t* host_path,
    const char_t* dotnet_root,
    const char_t* app_path,
    int64_t bundle_header_offset);

typedef void(HOSTFXR_CALLTYPE* hostfxr_error_writer_fn)(const char_t* message);

//
// Sets a callback which is to be used to write errors to.
//
// Parameters:
//     error_writer
//         A callback function which will be invoked every time an error is to be reported.
//         Or nullptr to unregister previously registered callback and return to the default behavior.
// Return value:
//     The previously registered callback (which is now unregistered), or nullptr if no previous callback
//     was registered
//
// The error writer is registered per-thread, so the registration is thread-local. On each thread
// only one callback can be registered. Subsequent registrations overwrite the previous ones.
//
// By default no callback is registered in which case the errors are written to stderr.
//
// Each call to the error writer is sort of like writing a single line (the EOL character is omitted).
// Multiple calls to the error writer may occure for one failure.
//
// If the hostfxr invokes functions in hostpolicy as part of its operation, the error writer
// will be propagated to hostpolicy for the duration of the call. This means that errors from
// both hostfxr and hostpolicy will be reporter through the same error writer.
//
typedef hostfxr_error_writer_fn(HOSTFXR_CALLTYPE* hostfxr_set_error_writer_fn)(hostfxr_error_writer_fn error_writer);

typedef void* hostfxr_handle;
struct hostfxr_initialize_parameters
{
    size_t size;
    const char_t* host_path;
    const char_t* dotnet_root;
};

//
// Initializes the hosting components for a dotnet command line running an application
//
// Parameters:
//    argc
//      Number of argv arguments
//    argv
//      Command-line arguments for running an application (as if through the dotnet executable).
//      Only command-line arguments which are accepted by runtime installation are supported, SDK/CLI commands are not supported.
//      For example 'app.dll app_argument_1 app_argument_2`.
//    parameters
//      Optional. Additional parameters for initialization
//    host_context_handle
//      On success, this will be populated with an opaque value representing the initialized host context
//
// Return value:
//    Success          - Hosting components were successfully initialized
//    HostInvalidState - Hosting components are already initialized
//
// This function parses the specified command-line arguments to determine the application to run. It will
// then find the corresponding .runtimeconfig.json and .deps.json with which to resolve frameworks and
// dependencies and prepare everything needed to load the runtime.
//
// This function only supports arguments for running an application. It does not support SDK commands.
//
// This function does not load the runtime.
//
typedef int32_t(HOSTFXR_CALLTYPE* hostfxr_initialize_for_dotnet_command_line_fn)(
    int argc,
    const char_t** argv,
    const struct hostfxr_initialize_parameters* parameters,
    /*out*/ hostfxr_handle* host_context_handle);

//
// Initializes the hosting components using a .runtimeconfig.json file
//
// Parameters:
//    runtime_config_path
//      Path to the .runtimeconfig.json file
//    parameters
//      Optional. Additional parameters for initialization
//    host_context_handle
//      On success, this will be populated with an opaque value representing the initialized host context
//
// Return value:
//    Success                            - Hosting components were successfully initialized
//    Success_HostAlreadyInitialized     - Config is compatible with already initialized hosting components
//    Success_DifferentRuntimeProperties - Config has runtime properties that differ from already initialized hosting components
//    CoreHostIncompatibleConfig         - Config is incompatible with already initialized hosting components
//
// This function will process the .runtimeconfig.json to resolve frameworks and prepare everything needed
// to load the runtime. It will only process the .deps.json from frameworks (not any app/component that
// may be next to the .runtimeconfig.json).
//
// This function does not load the runtime.
//
// If called when the runtime has already been loaded, this function will check if the specified runtime
// config is compatible with the existing runtime.
//
// Both Success_HostAlreadyInitialized and Success_DifferentRuntimeProperties codes are considered successful
// initializations. In the case of Success_DifferentRuntimeProperties, it is left to the consumer to verify that
// the difference in properties is acceptable.
//
typedef int32_t(HOSTFXR_CALLTYPE* hostfxr_initialize_for_runtime_config_fn)(
    const char_t* runtime_config_path,
    const struct hostfxr_initialize_parameters* parameters,
    /*out*/ hostfxr_handle* host_context_handle);

//
// Gets the runtime property value for an initialized host context
//
// Parameters:
//     host_context_handle
//       Handle to the initialized host context
//     name
//       Runtime property name
//     value
//       Out parameter. Pointer to a buffer with the property value.
//
// Return value:
//     The error code result.
//
// The buffer pointed to by value is owned by the host context. The lifetime of the buffer is only
// guaranteed until any of the below occur:
//   - a 'run' method is called for the host context
//   - properties are changed via hostfxr_set_runtime_property_value
//   - the host context is closed via 'hostfxr_close'
//
// If host_context_handle is nullptr and an active host context exists, this function will get the
// property value for the active host context.
//
typedef int32_t(HOSTFXR_CALLTYPE* hostfxr_get_runtime_property_value_fn)(
    const hostfxr_handle host_context_handle,
    const char_t* name,
    /*out*/ const char_t** value);

//
// Sets the value of a runtime property for an initialized host context
//
// Parameters:
//     host_context_handle
//       Handle to the initialized host context
//     name
//       Runtime property name
//     value
//       Value to set
//
// Return value:
//     The error code result.
//
// Setting properties is only supported for the first host context, before the runtime has been loaded.
//
// If the property already exists in the host context, it will be overwritten. If value is nullptr, the
// property will be removed.
//
typedef int32_t(HOSTFXR_CALLTYPE* hostfxr_set_runtime_property_value_fn)(
    const hostfxr_handle host_context_handle,
    const char_t* name,
    const char_t* value);

//
// Gets all the runtime properties for an initialized host context
//
// Parameters:
//     host_context_handle
//       Handle to the initialized host context
//     count
//       [in] Size of the keys and values buffers
//       [out] Number of properties returned (size of keys/values buffers used). If the input value is too
//             small or keys/values is nullptr, this is populated with the number of available properties
//     keys
//       Array of pointers to buffers with runtime property keys
//     values
//       Array of pointers to buffers with runtime property values
//
// Return value:
//     The error code result.
//
// The buffers pointed to by keys and values are owned by the host context. The lifetime of the buffers is only
// guaranteed until any of the below occur:
//   - a 'run' method is called for the host context
//   - properties are changed via hostfxr_set_runtime_property_value
//   - the host context is closed via 'hostfxr_close'
//
// If host_context_handle is nullptr and an active host context exists, this function will get the
// properties for the active host context.
//
typedef int32_t(HOSTFXR_CALLTYPE* hostfxr_get_runtime_properties_fn)(
    const hostfxr_handle host_context_handle,
    /*inout*/ size_t* count,
    /*out*/ const char_t** keys,
    /*out*/ const char_t** values);

//
// Load CoreCLR and run the application for an initialized host context
//
// Parameters:
//     host_context_handle
//       Handle to the initialized host context
//
// Return value:
//     If the app was successfully run, the exit code of the application. Otherwise, the error code result.
//
// The host_context_handle must have been initialized using hostfxr_initialize_for_dotnet_command_line.
//
// This function will not return until the managed application exits.
//
typedef int32_t(HOSTFXR_CALLTYPE* hostfxr_run_app_fn)(const hostfxr_handle host_context_handle);

//
// Gets a typed delegate from the currently loaded CoreCLR or from a newly created one.
//
// Parameters:
//     host_context_handle
//       Handle to the initialized host context
//     type
//       Type of runtime delegate requested
//     delegate
//       An out parameter that will be assigned the delegate.
//
// Return value:
//     The error code result.
//
// If the host_context_handle was initialized using hostfxr_initialize_for_runtime_config,
// then all delegate types are supported.
// If the host_context_handle was initialized using hostfxr_initialize_for_dotnet_command_line,
// then only the following delegate types are currently supported:
//     hdt_load_assembly_and_get_function_pointer
//     hdt_get_function_pointer
//
typedef int32_t(HOSTFXR_CALLTYPE* hostfxr_get_runtime_delegate_fn)(
    const hostfxr_handle host_context_handle,
    enum hostfxr_delegate_type type,
    /*out*/ void** delegate);

//
// Closes an initialized host context
//
// Parameters:
//     host_context_handle
//       Handle to the initialized host context
//
// Return value:
//     The error code result.
//
typedef int32_t(HOSTFXR_CALLTYPE* hostfxr_close_fn)(const hostfxr_handle host_context_handle);

struct hostfxr_dotnet_environment_sdk_info
{
    size_t size;
    const char_t* version;
    const char_t* path;
};

typedef void(HOSTFXR_CALLTYPE* hostfxr_get_dotnet_environment_info_result_fn)(
    const struct hostfxr_dotnet_environment_info* info,
    void* result_context);

struct hostfxr_dotnet_environment_framework_info
{
    size_t size;
    const char_t* name;
    const char_t* version;
    const char_t* path;
};

struct hostfxr_dotnet_environment_info
{
    size_t size;

    const char_t* hostfxr_version;
    const char_t* hostfxr_commit_hash;

    size_t sdk_count;
    const hostfxr_dotnet_environment_sdk_info* sdks;

    size_t framework_count;
    const hostfxr_dotnet_environment_framework_info* frameworks;
};

#endif //__HOSTFXR_H__
\ No newline at end of file

D Local/NosSmooth.Inject/nethost.h => Local/NosSmooth.Inject/nethost.h +0 -94
@@ 1,94 0,0 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

#ifndef __NETHOST_H__
#define __NETHOST_H__

#include <stddef.h>

#if defined(_WIN32)
#ifdef NETHOST_EXPORT
#define NETHOST_API __declspec(dllexport)
#else
#define NETHOST_API __declspec(dllimport)
#endif

#define NETHOST_CALLTYPE __stdcall
#ifdef _WCHAR_T_DEFINED
typedef wchar_t char_t;
#else
typedef unsigned short char_t;
#endif
#else
#ifdef NETHOST_EXPORT
#define NETHOST_API __attribute__((__visibility__("default")))
#else
#define NETHOST_API
#endif

#define NETHOST_CALLTYPE
typedef char char_t;
#endif

#ifdef __cplusplus
extern "C" {
#endif

    // Parameters for get_hostfxr_path
    //
    // Fields:
    //   size
    //     Size of the struct. This is used for versioning.
    //
    //   assembly_path
    //     Path to the compenent's assembly.
    //     If specified, hostfxr is located as if the assembly_path is the apphost
    //
    //   dotnet_root
    //     Path to directory containing the dotnet executable.
    //     If specified, hostfxr is located as if an application is started using
    //     'dotnet app.dll', which means it will be searched for under the dotnet_root
    //     path and the assembly_path is ignored.
    //
    struct get_hostfxr_parameters {
        size_t size;
        const char_t* assembly_path;
        const char_t* dotnet_root;
    };

    //
    // Get the path to the hostfxr library
    //
    // Parameters:
    //   buffer
    //     Buffer that will be populated with the hostfxr path, including a null terminator.
    //
    //   buffer_size
    //     [in] Size of buffer in char_t units.
    //     [out] Size of buffer used in char_t units. If the input value is too small
    //           or buffer is nullptr, this is populated with the minimum required size
    //           in char_t units for a buffer to hold the hostfxr path
    //
    //   get_hostfxr_parameters
    //     Optional. Parameters that modify the behaviour for locating the hostfxr library.
    //     If nullptr, hostfxr is located using the enviroment variable or global registration
    //
    // Return value:
    //   0 on success, otherwise failure
    //   0x80008098 - buffer is too small (HostApiBufferTooSmall)
    //
    // Remarks:
    //   The full search for the hostfxr library is done on every call. To minimize the need
    //   to call this function multiple times, pass a large buffer (e.g. PATH_MAX).
    //
    NETHOST_API int NETHOST_CALLTYPE get_hostfxr_path(
        char_t* buffer,
        size_t* buffer_size,
        const struct get_hostfxr_parameters* parameters);

#ifdef __cplusplus
} // extern "C"
#endif

#endif // __NETHOST_H__

D Local/NosSmooth.Inject/nossmooth.cpp => Local/NosSmooth.Inject/nossmooth.cpp +0 -125
@@ 1,125 0,0 @@
#include "nossmooth.h"

// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

// Standard headers
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <iostream>
#include "nethost.h"
#include "coreclr_delegates.h"
#include "hostfxr.h"

#include <cassert>

#define STR(s) L ## s
#define CH(c) L ## c
#define DIR_SEPARATOR L'\\'

using string_t = std::basic_string<char_t>;

// Globals to hold hostfxr exports
hostfxr_initialize_for_runtime_config_fn init_fptr;
hostfxr_get_runtime_delegate_fn get_delegate_fptr;
hostfxr_close_fn close_fptr;

// Forward declarations
bool load_hostfxr();
load_assembly_and_get_function_pointer_fn get_dotnet_load_assembly(const char_t* assembly);

/********************************************************************************************
 * Function used to load and activate .NET Core
 ********************************************************************************************/

 // Forward declarations
void* load_library(const char_t*);
void* get_export(void*, const char*);

void* load_library(const char_t* path)
{
    HMODULE h = ::LoadLibraryW(path);
    assert(h != nullptr);
    return (void*)h;
}
void* get_export(void* h, const char* name)
{
    void* f = ::GetProcAddress((HMODULE)h, name);
    assert(f != nullptr);
    return f;
}

// Using the nethost library, discover the location of hostfxr and get exports
bool load_hostfxr()
{
    // Pre-allocate a large buffer for the path to hostfxr
    char_t buffer[MAX_PATH];
    size_t buffer_size = sizeof(buffer) / sizeof(char_t);
    int rc = get_hostfxr_path(buffer, &buffer_size, nullptr);
    if (rc != 0)
        return false;

    // Load hostfxr and get desired exports
    void* lib = load_library(buffer);
    init_fptr = (hostfxr_initialize_for_runtime_config_fn)get_export(lib, "hostfxr_initialize_for_runtime_config");
    get_delegate_fptr = (hostfxr_get_runtime_delegate_fn)get_export(lib, "hostfxr_get_runtime_delegate");
    close_fptr = (hostfxr_close_fn)get_export(lib, "hostfxr_close");

    return (init_fptr && get_delegate_fptr && close_fptr);
}

// Load and initialize .NET Core and get desired function pointer for scenario
load_assembly_and_get_function_pointer_fn get_dotnet_load_assembly(const char_t* config_path)
{
    // Load .NET Core
    void* load_assembly_and_get_function_pointer = nullptr;
    hostfxr_handle cxt = nullptr;
    int rc = init_fptr(config_path, nullptr, &cxt);
    if (rc != 0 || cxt == nullptr)
    {
        std::cerr << "Init failed: " << std::hex << std::showbase << rc << std::endl;
        close_fptr(cxt);
        return nullptr;
    }

    // Get the load assembly function pointer
    rc = get_delegate_fptr(
        cxt,
        hdt_load_assembly_and_get_function_pointer,
        &load_assembly_and_get_function_pointer);
    if (rc != 0 || load_assembly_and_get_function_pointer == nullptr)
        std::cerr << "Get delegate failed: " << std::hex << std::showbase << rc << std::endl;

    close_fptr(cxt);
    return (load_assembly_and_get_function_pointer_fn)load_assembly_and_get_function_pointer;
}

bool LoadAndCallMethod(LoadParams* params)
{
    if (!load_hostfxr())
    {
        assert(false && "Failure: load_hostfxr()");
        return false;
    }

    load_assembly_and_get_function_pointer_fn load_assembly_and_get_function_pointer = nullptr;
    load_assembly_and_get_function_pointer = get_dotnet_load_assembly(params->runtimeConfigPath);
    assert(load_assembly_and_get_function_pointer != nullptr && "Failure: get_dotnet_load_assembly()");

    typedef void (CORECLR_DELEGATE_CALLTYPE* main_entry_point_fn)();
    main_entry_point_fn main = nullptr;
    int rc = load_assembly_and_get_function_pointer(
        params->libraryPath,
        params->typePath,
        params->methodName,
        UNMANAGEDCALLERSONLY_METHOD,
        nullptr,
        (void**)&main);
    assert(rc == 0 && main != nullptr && "Failure: load_assembly_and_get_function_pointer()");
    main();
    return true;
}
\ No newline at end of file

D Local/NosSmooth.Inject/nossmooth.h => Local/NosSmooth.Inject/nossmooth.h +0 -15
@@ 1,15 0,0 @@
#pragma once
#include <Windows.h>

#pragma pack(push, 1)
struct LoadParams
{
    wchar_t *libraryPath;
    wchar_t *runtimeConfigPath;
    wchar_t *typePath;
    wchar_t *methodName;
};
#pragma pack(pop)
#define DllExport extern "C" __declspec( dllexport )

DllExport bool LoadAndCallMethod(LoadParams* params);
\ No newline at end of file

D Local/NosSmooth.Inject/pch.cpp => Local/NosSmooth.Inject/pch.cpp +0 -5
@@ 1,5 0,0 @@
// pch.cpp: source file corresponding to the pre-compiled header

#include "pch.h"

// When you are using pre-compiled headers, this source file is necessary for compilation to succeed.

D Local/NosSmooth.Inject/pch.h => Local/NosSmooth.Inject/pch.h +0 -13
@@ 1,13 0,0 @@
// pch.h: This is a precompiled header file.
// Files listed below are compiled only once, improving build performance for future builds.
// This also affects IntelliSense performance, including code completion and many code browsing features.
// However, files listed here are ALL re-compiled if any one of them is updated between builds.
// Do not add files here that you will be updating frequently as this negates the performance advantage.

#ifndef PCH_H
#define PCH_H

// add headers that you want to pre-compile here
#include "framework.h"

#endif //PCH_H

D Local/NosSmooth.Injector.CLI/Commands/InjectCommand.cs => Local/NosSmooth.Injector.CLI/Commands/InjectCommand.cs +0 -68
@@ 1,68 0,0 @@
//
//  InjectCommand.cs
//
//  Copyright (c) František Boháček. All rights reserved.
//  Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System.ComponentModel;
using System.Diagnostics;
using Remora.Commands.Attributes;
using Remora.Commands.Groups;
using Remora.Results;

namespace NosSmooth.Injector.CLI.Commands
{
    /// <summary>
    /// Injection command for injecting .NET 5+ libraries with UnmanagedCallersOnly method.
    /// </summary>
    internal class InjectCommand : CommandGroup
    {
        private readonly NosInjector _injector;

        /// <summary>
        /// Initializes a new instance of the <see cref="InjectCommand"/> class.
        /// </summary>
        /// <param name="injector">The nos smooth injector.</param>
        public InjectCommand(NosInjector injector)
        {
            _injector = injector;
        }

        /// <summary>
        /// The command to inject.
        /// </summary>
        /// <param name="process">The id of the process or part of its name.</param>
        /// <param name="dllPath">The path to the dll to inject.</param>
        /// <param name="typeName">The full type specifier. Default is LibraryName.DllMain, LibraryName.</param>
        /// <param name="methodName">The name of the UnmanagedCallersOnly method. Default is Main.</param>
        /// <returns>A result that may or may not have succeeded.</returns>
        [Command("inject")]
        public Task<Result> Inject
        (
            [Description("The id of the process to inject into.")]
            string process,
            [Description("The path to the dll to inject.")]
            string dllPath,
            [Option('t', "type"), Description("The full type specifier. Default is LibraryName.DllMain, LibraryName")]
            string? typeName = null,
            [Option('m', "method"), Description("The name of the UnmanagedCallersOnly method. Default is Main")]
            string? methodName = null
        )
        {
            if (!int.TryParse(process, out var processId))
            {
                var foundProcess = Process.GetProcesses().FirstOrDefault(x => x.ProcessName.Contains(process, StringComparison.OrdinalIgnoreCase));
                if (foundProcess is null)
                {
                    return Task.FromResult(Result.FromError(new NotFoundError("Could not find the given process.")));
                }

                processId = foundProcess.Id;
            }

            var dllName = Path.GetFileNameWithoutExtension(dllPath);
            return Task.FromResult
                (_injector.Inject(processId, dllPath, $"{dllName}.DllMain, {dllName}", methodName ?? "Main"));
        }
    }
}
\ No newline at end of file

D Local/NosSmooth.Injector.CLI/Commands/ListProcessesCommand.cs => Local/NosSmooth.Injector.CLI/Commands/ListProcessesCommand.cs +0 -41
@@ 1,41 0,0 @@
//
//  ListProcessesCommand.cs
//
//  Copyright (c) František Boháček. All rights reserved.
//  Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System.Diagnostics;
using Remora.Commands.Attributes;
using Remora.Commands.Groups;
using Remora.Results;

namespace NosSmooth.Injector.CLI.Commands
{
    /// <summary>
    /// Command for listing processes to find id of NosTale process.
    /// </summary>
    internal class ListProcessesCommand : CommandGroup
    {
        /// <summary>
        /// Lists processes by the given criteria.
        /// </summary>
        /// <param name="nameContains">What should the name of the process contain.</param>
        /// <returns>A result that may or may not have succeeded.</returns>
        [Command("list")]
        public Task<Result> List(string nameContains = "Nostale")
        {
            var processes = Process.GetProcesses();
            foreach (var process in processes.Where(x => x.ProcessName.Contains(nameContains, StringComparison.OrdinalIgnoreCase)))
            {
                Console.WriteLine(ProcessToString(process));
            }

            return Task.FromResult(Result.FromSuccess());
        }

        private string ProcessToString(Process process)
        {
            return $"{process.ProcessName} - {process.Id}";
        }
    }
}

D Local/NosSmooth.Injector.CLI/NosSmooth.Injector.CLI.csproj => Local/NosSmooth.Injector.CLI/NosSmooth.Injector.CLI.csproj +0 -28
@@ 1,28 0,0 @@
<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net6.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
	<Nullable>enable</Nullable>
	<ApplicationManifest>app.manifest</ApplicationManifest>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="6.0.0" />
    <PackageReference Include="Remora.Commands" Version="8.0.0" />
  </ItemGroup>

  <ItemGroup>
    <NativeLibs Remove="Program.cs" />
  </ItemGroup>

  <ItemGroup>
    <NativeLibs Remove="app.manifest" />
  </ItemGroup>

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

</Project>

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

using Microsoft.Extensions.DependencyInjection;
using NosSmooth.Injector.CLI.Commands;
using Remora.Commands.Extensions;
using Remora.Commands.Services;
using Remora.Results;

namespace NosSmooth.Injector.CLI
{
    /// <summary>
    /// The entrypoint class.
    /// </summary>
    public static class Program
    {
        /// <summary>
        /// The entrypoint method.
        /// </summary>
        /// <param name="argv">The command line arguments.</param>
        /// <returns>A task that may or may not have succeeded.</returns>
        public static async Task Main(string[] argv)
        {
            var services = CreateServices();
            var commandService = services.GetRequiredService<CommandService>();
            var preparedCommandResult = await commandService.TryPrepareCommandAsync(string.Join(' ', argv), services);
            if (!preparedCommandResult.IsSuccess)
            {
                Console.Error.WriteLine($"There was an error, {preparedCommandResult.Error.Message}");
                return;
            }

            if (preparedCommandResult.Entity is null)
            {
                Console.Error.WriteLine("You must enter a command such ast list or inject.");
                return;
            }

            var executionResult = await commandService.TryExecuteAsync(preparedCommandResult.Entity, services);
            if (!executionResult.Entity.IsSuccess)
            {
                switch (executionResult.Entity.Error)
                {
                    case ExceptionError exc:
                        Console.Error.WriteLine($"There was an exception, {exc.Exception.Message}");
                        break;
                    default:
                        Console.Error.WriteLine($"There was an error, {executionResult.Entity.Error!.Message}");
                        break;
                }

                return;
            }
        }

        private static IServiceProvider CreateServices()
        {
            var collection = new ServiceCollection();
            collection
                .AddSingleton<NosInjector>()
                .AddOptions<NosInjectorOptions>();

            collection
                .AddCommands()
                .AddCommandTree()
                    .WithCommandGroup<InjectCommand>()
                    .WithCommandGroup<ListProcessesCommand>();

            return collection.BuildServiceProvider();
        }
    }
}

D Local/NosSmooth.Injector.CLI/app.manifest => Local/NosSmooth.Injector.CLI/app.manifest +0 -11
@@ 1,11 0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
  <assemblyIdentity version="1.0.0.0" name="MyApplication.app"/>
  <trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
    <security>
      <requestedPrivileges xmlns="urn:schemas-microsoft-com:asm.v3">
        <requestedExecutionLevel level="requireAdministrator" uiAccess="false" />
      </requestedPrivileges>
    </security>
  </trustInfo>
</assembly>
\ No newline at end of file

D Local/NosSmooth.Injector/Errors/InjectionFailedError.cs => Local/NosSmooth.Injector/Errors/InjectionFailedError.cs +0 -15
@@ 1,15 0,0 @@
//
//  InjectionFailedError.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.Injector.Errors;

/// <summary>
/// The injection could not be finished successfully.
/// </summary>
/// <param name="DllPath">The path to the dll.</param>
public record InjectionFailedError(string DllPath) : ResultError($"Could not inject {DllPath} dll into the process.");
\ No newline at end of file

D Local/NosSmooth.Injector/Errors/InsufficientPermissionsError.cs => Local/NosSmooth.Injector/Errors/InsufficientPermissionsError.cs +0 -20
@@ 1,20 0,0 @@
//
//  InsufficientPermissionsError.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.Injector.Errors;

/// <summary>
/// The current user has insufficient permissions to inject into the given process.
/// </summary>
/// <param name="ProcessId">The id of the process.</param>
/// <param name="ProcessName">The name of the process.</param>
public record InsufficientPermissionsError(long ProcessId, string ProcessName)
    : ResultError
    (
        $"Insufficient permissions to open process {ProcessId} ({ProcessName}). Try running the injector as administrator."
    );
\ No newline at end of file

D Local/NosSmooth.Injector/Errors/ProcessNotFoundError.cs => Local/NosSmooth.Injector/Errors/ProcessNotFoundError.cs +0 -16
@@ 1,16 0,0 @@
//
//  ProcessNotFoundError.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.Injector.Errors;

/// <summary>
/// The given process was not found.
/// </summary>
/// <param name="ProcessId">The id of the process.</param>
public record ProcessNotFoundError(string ProcessId)
    : NotFoundError($"Could not find process with the given id {ProcessId}.");
\ No newline at end of file

D Local/NosSmooth.Injector/LoadParams.cs => Local/NosSmooth.Injector/LoadParams.cs +0 -44
@@ 1,44 0,0 @@
//
//  LoadParams.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;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;

namespace NosSmooth.Injector
{
    /// <summary>
    /// The parameters passed to the inject module.
    /// </summary>
    internal struct LoadParams
    {
        /// <summary>
        /// The full path of the library.
        /// </summary>
        public int LibraryPath;

        /// <summary>
        /// The full path of the library.
        /// </summary>
        public int RuntimeConfigPath;

        /// <summary>
        /// The full path to the type with the method marked as UnsafeCallersOnly.
        /// </summary>
        /// <remarks>
        /// Can be for example "LibraryNamespace.Type, LibraryNamespace".
        /// </remarks>
        public int TypePath;

        /// <summary>
        /// The name of the method to execute.
        /// </summary>
        public int MethodName;
    }
}

D Local/NosSmooth.Injector/ManagedMemoryAllocation.cs => Local/NosSmooth.Injector/ManagedMemoryAllocation.cs +0 -47
@@ 1,47 0,0 @@
//
//  ManagedMemoryAllocation.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.Net.NetworkInformation;
using Reloaded.Memory.Sources;

namespace NosSmooth.Injector;

/// <summary>
/// Represents freeable memory allocation.
/// </summary>
internal class ManagedMemoryAllocation : IDisposable
{
    private readonly IMemory _memory;

    /// <summary>
    /// Initializes a new instance of the <see cref="ManagedMemoryAllocation"/> class.
    /// </summary>
    /// <param name="memory">The memory with allocation.</param>
    /// <param name="pointer">The pointer to allocated memory.</param>
    public ManagedMemoryAllocation(IMemory memory, IntPtr pointer)
    {
        Pointer = pointer;
        _memory = memory;

    }

    /// <summary>
    /// The allocated pointer number.
    /// </summary>
    public IntPtr Pointer { get; private set; }

    /// <summary>
    /// Whether the memory is currently allocated.
    /// </summary>
    public bool Allocated => Pointer != IntPtr.Zero;

    /// <inheritdoc />
    public void Dispose()
    {
        _memory.Free(Pointer);
        Pointer = IntPtr.Zero;
    }
}
\ No newline at end of file

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

using System.Diagnostics;
using System.Security;
using System.Text;
using Microsoft.Extensions.Options;
using NosSmooth.Injector.Errors;
using Reloaded.Memory.Sources;
using Remora.Results;

namespace NosSmooth.Injector;

/// <summary>
/// Nos smooth injector for .NET 5+ projects.
/// </summary>
/// <remarks>
/// If you want to inject your project into NosTale that
/// uses NosSmooth.LocalClient, use this injector.
/// You must expose static UnmanagedCallersOnly method.
/// </remarks>
public class NosInjector
{
    private readonly NosInjectorOptions _options;

    /// <summary>
    /// Initializes a new instance of the <see cref="NosInjector"/> class.
    /// </summary>
    /// <param name="options">The injector options.</param>
    public NosInjector(IOptions<NosInjectorOptions> options)
    {
        _options = options.Value;
    }

    /// <summary>
    /// Inject the given .NET 5+ dll into NosTale process.
    /// </summary>
    /// <remarks>
    /// The dll must also have .runtimeconfig.json present next to the dll.
    /// </remarks>
    /// <param name="processId">The id of the process to inject to.</param>
    /// <param name="dllPath">The absolute path to the dll to inject.</param>
    /// <param name="classPath">The full path to the class. Such as "MyLibrary.DllMain, MyLibrary".</param>
    /// <param name="methodName">The name of the method to execute. The method should return void and have no parameters.</param>
    /// <returns>A result that may or may not have succeeded.</returns>
    public Result Inject
    (
        int processId,
        string dllPath,
        string classPath,
        string methodName = "Main"
    )
    {
        using var process = Process.GetProcessById(processId);
        return Inject(process, dllPath, classPath, methodName);
    }

    /// <summary>
    /// Inject the given .NET 5+ dll into NosTale process.
    /// </summary>
    /// <remarks>
    /// The dll must also have .runtimeconfig.json present next to the dll.
    /// </remarks>
    /// <param name="process">The process to inject to.</param>
    /// <param name="dllPath">The absolute path to the dll to inject.</param>
    /// <param name="classPath">The full path to the class. Such as "MyLibrary.DllMain, MyLibrary".</param>
    /// <param name="methodName">The name of the method to execute. The method should return void and have no parameters.</param>
    /// <returns>A result that may or may not have succeeded.</returns>
    public Result Inject
    (
        Process process,
        string dllPath,
        string classPath,
        string methodName = "Main"
    )
    {
        try
        {
            using var injector = new Reloaded.Injector.Injector(process);
            var memory = new ExternalMemory(process);

            var directoryName = Path.GetDirectoryName(dllPath);
            if (directoryName is null)
            {
                return new GenericError("There was an error obtaining directory name of the dll path.");
            }

            var runtimePath = Path.Combine
                (directoryName, Path.GetFileNameWithoutExtension(dllPath)) + ".runtimeconfig.json";

            using var dllPathMemory = AllocateString(memory, dllPath);
            using var classPathMemory = AllocateString(memory, classPath);
            using var methodNameMemory = AllocateString(memory, methodName);
            using var runtimePathMemory = AllocateString(memory, runtimePath);

            if (!dllPathMemory.Allocated || !classPathMemory.Allocated || !methodNameMemory.Allocated
                || !runtimePathMemory.Allocated)
            {
                return new GenericError("Could not allocate memory in the external process.");
            }

            var loadParams = new LoadParams()
            {
                LibraryPath = (int)dllPathMemory.Pointer,
                MethodName = (int)methodNameMemory.Pointer,
                RuntimeConfigPath = (int)runtimePathMemory.Pointer,
                TypePath = (int)classPathMemory.Pointer
            };

            var nosSmoothInjectPath = Path.GetFullPath(_options.NosSmoothInjectPath);
            var injected = injector.Inject(nosSmoothInjectPath);
            if (injected == 0)
            {
                return new InjectionFailedError(nosSmoothInjectPath);
            }

            var functionResult = injector.CallFunction(nosSmoothInjectPath, "LoadAndCallMethod", loadParams);
            if (functionResult != 0)
            {
                return new InjectionFailedError(dllPath);
            }

            return Result.FromSuccess();
        }
        catch (UnauthorizedAccessException)
        {
            return new InsufficientPermissionsError(process.Id, process.ProcessName);
        }
        catch (SecurityException)
        {
            return new InsufficientPermissionsError(process.Id, process.ProcessName);
        }
        catch (Exception e)
        {
            return e;
        }
    }

    private ManagedMemoryAllocation AllocateString(IMemory memory, string str)
    {
        var bytes = Encoding.Unicode.GetBytes(str);
        var allocated = memory.Allocate(bytes.Length + 1);
        if (allocated == IntPtr.Zero)
        {
            return new ManagedMemoryAllocation(memory, allocated);
        }

        memory.SafeWriteRaw(allocated + bytes.Length, new byte[] { 0 });
        memory.SafeWriteRaw(allocated, bytes);
        return new ManagedMemoryAllocation(memory, allocated);
    }
}
\ No newline at end of file

D Local/NosSmooth.Injector/NosInjectorOptions.cs => Local/NosSmooth.Injector/NosInjectorOptions.cs +0 -32
@@ 1,32 0,0 @@
//
//  NosInjectorOptions.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;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Extensions.Options;

namespace NosSmooth.Injector
{
    /// <summary>
    /// Options for NosInjector.
    /// </summary>
    public class NosInjectorOptions : IOptions<NosInjectorOptions>
    {
        /// <summary>
        /// Gets or sets the path to the nos smooth inject dll.
        /// </summary>
        /// <remarks>
        /// If not absolute path, then relative path from the current executing process is assumed.
        /// </remarks>
        public string NosSmoothInjectPath { get; set; } = "NosSmooth.Inject.dll";

        /// <inheritdoc/>
        public NosInjectorOptions Value => this;
    }
}

D Local/NosSmooth.Injector/NosSmooth.Injector.csproj => Local/NosSmooth.Injector/NosSmooth.Injector.csproj +0 -15
@@ 1,15 0,0 @@
<Project Sdk="Microsoft.NET.Sdk">

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

  <ItemGroup>
    <PackageReference Include="Microsoft.Extensions.Options" Version="6.0.0" />
    <PackageReference Include="Reloaded.Injector" Version="1.2.4" />
    <PackageReference Include="Remora.Results" Version="7.1.0" />
  </ItemGroup>

</Project>

D Local/NosSmooth.LocalBinding/Errors/BindingNotFoundError.cs => Local/NosSmooth.LocalBinding/Errors/BindingNotFoundError.cs +0 -17
@@ 1,17 0,0 @@
//
//  BindingNotFoundError.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>
/// The memory pattern was not found in the memory.
/// </summary>
/// <param name="Pattern">The pattern that could not be found.</param>
/// <param name="Path">The entity the pattern should represent.</param>
public record BindingNotFoundError(string Pattern, string Path)
    : ResultError($"Could not find pattern ({Pattern}) in the memory while searching for {Path}.");

D Local/NosSmooth.LocalBinding/Errors/CouldNotInitializeModuleError.cs => Local/NosSmooth.LocalBinding/Errors/CouldNotInitializeModuleError.cs +0 -17
@@ 1,17 0,0 @@
//
//  CouldNotInitializeModuleError.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>
/// Could not initialize the given NosTale module.
/// </summary>
/// <param name="Module">The module type that could not be initialized.</param>
/// <param name="UnderlyingError">The error why the module could not be initialized.</param>
public record CouldNotInitializeModuleError
    (Type Module, IResultError UnderlyingError) : ResultError($"Could initialize a nostale module {Module.FullName}.");
\ No newline at end of file

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

using System.Diagnostics;
using Remora.Results;

namespace NosSmooth.LocalBinding.Errors;

/// <summary>
/// The process you tried to browse is not a nostale process.
/// </summary>
/// <param name="process"></param>
public record NotNostaleProcessError(Process process) : ResultError
    ($"The process {process.ProcessName} ({process.Id} is not a NosTale process.");
\ No newline at end of file

D Local/NosSmooth.LocalBinding/Extensions/MemoryExtensions.cs => Local/NosSmooth.LocalBinding/Extensions/MemoryExtensions.cs +0 -33
@@ 1,33 0,0 @@
//
//  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

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

using Microsoft.Extensions.DependencyInjection;
using NosSmooth.LocalBinding.Objects;
using NosSmooth.LocalBinding.Structs;

namespace NosSmooth.LocalBinding.Extensions;

/// <summary>
/// Contains extension methods for <see cref="IServiceCollection"/>.
/// </summary>
public static class ServiceCollectionExtensions
{
    /// <summary>
    /// Adds bindings to Nostale objects along with <see cref="NosBindingManager"/> to initialize those.
    /// </summary>
    /// <remarks>
    /// Adds <see cref="PlayerManagerBinding"/> and <see cref="NetworkBinding"/>.
    /// You have to initialize these using <see cref="NosBindingManager"/>
    /// prior to requesting them from the provider, otherwise an exception
    /// will be thrown.
    /// </remarks>
    /// <param name="serviceCollection">The service collection.</param>
    /// <returns>The collection.</returns>
    public static IServiceCollection AddNostaleBindings(this IServiceCollection serviceCollection)
    {
        return serviceCollection
            .AddSingleton<NosBindingManager>()
            .AddSingleton<NosBrowserManager>()
            .AddSingleton(p => p.GetRequiredService<NosBrowserManager>().PlayerManager)
            .AddSingleton(p => p.GetRequiredService<NosBrowserManager>().SceneManager)
            .AddSingleton(p => p.GetRequiredService<NosBrowserManager>().PetManagerList)
            .AddSingleton(p => p.GetRequiredService<NosBrowserManager>().SceneManager)
            .AddSingleton(p => p.GetRequiredService<NosBrowserManager>().PetManagerList)
            .AddSingleton(p => p.GetRequiredService<NosBindingManager>().PlayerManager)
            .AddSingleton(p => p.GetRequiredService<NosBindingManager>().PetManager)
            .AddSingleton(p => p.GetRequiredService<NosBindingManager>().UnitManager)
            .AddSingleton(p => p.GetRequiredService<NosBindingManager>().Network);
    }
}
\ No newline at end of file

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

namespace System.Runtime.CompilerServices
{
    /// <summary>
    /// Dummy.
    /// </summary>
    public class IsExternalInit
    {
    }
}
\ No newline at end of file

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

using System.Diagnostics;
using Microsoft.Extensions.Options;
using NosSmooth.LocalBinding.Errors;
using NosSmooth.LocalBinding.Objects;
using NosSmooth.LocalBinding.Options;
using Reloaded.Hooks;
using Reloaded.Hooks.Definitions;
using Reloaded.Memory.Sigscan;
using Reloaded.Memory.Sources;
using Remora.Results;

namespace NosSmooth.LocalBinding;

/// <summary>
/// Nostale entity binding manager.
/// </summary>
public class NosBindingManager : IDisposable
{
    private readonly NosBrowserManager _browserManager;
    private readonly PetManagerBindingOptions _petManagerBindingOptions;
    private readonly CharacterBindingOptions _characterBindingOptions;
    private readonly NetworkBindingOptions _networkBindingOptions;
    private UnitManagerBindingOptions _unitManagerBindingOptions;

    private NetworkBinding? _networkBinding;
    private PlayerManagerBinding? _characterBinding;
    private UnitManagerBinding? _unitManagerBinding;
    private PetManagerBinding? _petManagerBinding;

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

    /// <summary>
    /// Gets the memory scanner.
    /// </summary>
    internal Scanner Scanner { get; }

    /// <summary>
    /// Gets the reloaded hooks.
    /// </summary>
    internal IReloadedHooks Hooks { get; }

    /// <summary>
    /// Gets the current process memory.
    /// </summary>
    internal IMemory Memory { get; }

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

            return _networkBinding;
        }
    }

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

            return _characterBinding;
        }
    }

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

            return _unitManagerBinding;
        }
    }

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

            return _petManagerBinding;
        }
    }

    /// <summary>
    /// Initialize the existing bindings and hook NosTale functions.
    /// </summary>
    /// <returns>A result that may or may not have succeeded.</returns>
    public IResult Initialize()
    {
        List<IResult> errorResults = new List<IResult>();
        var browserInitializationResult = _browserManager.Initialize();
        if (!browserInitializationResult.IsSuccess)
        {
            if (browserInitializationResult.Error is NotNostaleProcessError)
            {
                return browserInitializationResult;
            }

            errorResults.Add(browserInitializationResult);
        }

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

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

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

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

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

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

    /// <summary>
    /// Disable the currently enabled nostale hooks.
    /// </summary>
    /// <returns>A result that may or may not have succeeded.</returns>
    public Result DisableHooks()
    {
        if (_networkBinding is not null)
        {
            var result = _networkBinding.DisableHooks();
            if (!result.IsSuccess)
            {
                return result;
            }
        }

        return Result.FromSuccess();
    }

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

    /// <summary>
    /// Create a hook object for the given pattern.
    /// </summary>
    /// <param name="name">The name of the binding.</param>
    /// <param name="callbackFunction">The callback function to call instead of the original one.</param>
    /// <param name="pattern">The pattern.</param>
    /// <param name="offset">The offset from the pattern.</param>
    /// <param name="hook">Whether to activate the hook.</param>
    /// <typeparam name="TFunction">The type of the function.</typeparam>
    /// <returns>The hook object or an error.</returns>
    internal Result<IHook<TFunction>> CreateHookFromPattern<TFunction>
    (
        string name,
        TFunction callbackFunction,
        string pattern,
        int offset = 0,
        bool hook = true
    )
    {
        var walkFunctionAddress = Scanner.CompiledFindPattern(pattern);
        if (!walkFunctionAddress.Found)
        {
            return new BindingNotFoundError(pattern, "PetManagerBinding.PetWalk");
        }

        try
        {
            return Result<IHook<TFunction>>.FromSuccess
            (
                Hooks.CreateHook
                (
                    callbackFunction,
                    walkFunctionAddress.Offset + (int)_browserManager.Process.MainModule!.BaseAddress
                )
            );
        }
        catch (Exception e)
        {
            return e;
        }
    }
}
\ No newline at end of file

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

using System.Diagnostics;
using Microsoft.Extensions.Options;
using NosSmooth.LocalBinding.Errors;
using NosSmooth.LocalBinding.Options;
using NosSmooth.LocalBinding.Structs;
using Reloaded.Memory.Sigscan;
using Reloaded.Memory.Sources;
using Remora.Results;

namespace NosSmooth.LocalBinding;

/// <summary>
/// Used for browsing a nostale process data.
/// </summary>
public class NosBrowserManager
{
    /// <summary>
    /// Checks whether the given process is a NosTale client process.
    /// </summary>
    /// <remarks>
    /// This is just a guess based on presence of "NostaleData" directory.
    /// </remarks>
    /// <param name="process">The process to check.</param>
    /// <returns>Whether the process is a NosTale client.</returns>
    public static bool IsProcessNostaleProcess(Process process)
    {
        if (process.MainModule is null)
        {
            return false;
        }

        var processDirectory = Path.GetDirectoryName(process.MainModule.FileName);
        if (processDirectory is null)
        {
            return false;
        }

        return Directory.Exists(Path.Combine(processDirectory, "NostaleData"));
    }

    /// <summary>
    /// Get all running nostale processes.
    /// </summary>
    /// <returns>The nostale processes.</returns>
    public static IEnumerable<Process> GetAllNostaleProcesses()
        => Process
            .GetProcesses()
            .Where(IsProcessNostaleProcess);

    private readonly PlayerManagerOptions _playerManagerOptions;
    private readonly SceneManagerOptions _sceneManagerOptions;
    private readonly PetManagerOptions _petManagerOptions;
    private PlayerManager? _playerManager;
    private SceneManager? _sceneManager;
    private PetManagerList? _petManagerList;

    /// <summary>
    /// Initializes a new instance of the <see cref="NosBrowserManager"/> class.
    /// </summary>
    /// <param name="playerManagerOptions">The options for obtaining player manager.</param>
    /// <param name="sceneManagerOptions">The scene manager options.</param>
    /// <param name="petManagerOptions">The pet manager options.</param>
    public NosBrowserManager
    (
        IOptions<PlayerManagerOptions> playerManagerOptions,
        IOptions<SceneManagerOptions> sceneManagerOptions,
        IOptions<PetManagerOptions> petManagerOptions
    )
        : this
        (
            Process.GetCurrentProcess(),
            playerManagerOptions.Value,
            sceneManagerOptions.Value,
            petManagerOptions.Value
        )
    {
    }

    /// <summary>
    /// Initializes a new instance of the <see cref="NosBrowserManager"/> class.
    /// </summary>
    /// <param name="process">The process to browse.</param>
    /// <param name="playerManagerOptions">The options for obtaining player manager.</param>
    /// <param name="sceneManagerOptions">The scene manager options.</param>
    /// <param name="petManagerOptions">The pet manager options.</param>
    public NosBrowserManager
    (
        Process process,
        PlayerManagerOptions playerManagerOptions,
        SceneManagerOptions sceneManagerOptions,
        PetManagerOptions petManagerOptions
    )
    {
        _playerManagerOptions = playerManagerOptions;
        _sceneManagerOptions = sceneManagerOptions;
        _petManagerOptions = petManagerOptions;
        Process = process;
        Memory = new ExternalMemory(process);
        Scanner = new Scanner(process, process.MainModule);
    }

    /// <summary>
    /// The NosTale process.
    /// </summary>
    public Process Process { get; }

    /// <summary>
    /// Gets the memory scanner.
    /// </summary>
    internal Scanner Scanner { get; }

    /// <summary>
    /// Gets the current process memory.
    /// </summary>
    internal IMemory Memory { get; }

    /// <summary>
    /// Gets whether this is a NosTale process or not.
    /// </summary>
    public bool IsNostaleProcess => NosBrowserManager.IsProcessNostaleProcess(Process);

    /// <summary>
    /// Gets whether the player is currently in game.
    /// </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 != IntPtr.Zero;
        }
    }

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

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

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

    /// <summary>
    /// Initialize the nos browser modules.
    /// </summary>
    /// <remarks>
    /// Needed to use all of the classes from NosTale.
    /// </remarks>
    /// <returns>A result that may or may not have succeeded.</returns>
    public IResult Initialize()
    {
        if (!IsNostaleProcess)
        {
            return (Result)new NotNostaleProcessError(Process);
        }

        List<IResult> errorResults = new List<IResult>();
        if (_playerManager is null)
        {
            var playerManagerResult = PlayerManager.Create(this, _playerManagerOptions);
            if (!playerManagerResult.IsSuccess)
            {
                errorResults.Add
                (
                    Result.FromError
                    (
                        new CouldNotInitializeModuleError(typeof(PlayerManager), playerManagerResult.Error),
                        playerManagerResult
                    )
                );
            }

            _playerManager = playerManagerResult.Entity;
        }

        if (_sceneManager is null)
        {
            var sceneManagerResult = SceneManager.Create(this, _sceneManagerOptions);
            if (!sceneManagerResult.IsSuccess)
            {
                errorResults.Add
                (
                    Result.FromError
                    (
                        new CouldNotInitializeModuleError(typeof(SceneManager), sceneManagerResult.Error),
                        sceneManagerResult
                    )
                );
            }

            _sceneManager = sceneManagerResult.Entity;
        }

        if (_petManagerList is null)
        {
            var petManagerResult = PetManagerList.Create(this, _petManagerOptions);
            if (!petManagerResult.IsSuccess)
            {
                errorResults.Add
                (
                    Result.FromError
                    (
                        new CouldNotInitializeModuleError(typeof(PetManagerList), petManagerResult.Error),
                        petManagerResult
                    )
                );
            }

            _petManagerList = petManagerResult.Entity;
        }

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

D Local/NosSmooth.LocalBinding/NosSmooth.LocalBinding.csproj => Local/NosSmooth.LocalBinding/NosSmooth.LocalBinding.csproj +0 -18
@@ 1,18 0,0 @@
<Project Sdk="Microsoft.NET.Sdk">

    <PropertyGroup>
        <TargetFramework>netstandard2.0</TargetFramework>
        <ImplicitUsings>enable</ImplicitUsings>
        <Nullable>enable</Nullable>
        <LangVersion>10</LangVersion>
    </PropertyGroup>

    <ItemGroup>
      <PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="6.0.0" />
      <PackageReference Include="Microsoft.Extensions.Options" Version="6.0.0" />
      <PackageReference Include="Reloaded.Hooks" Version="3.5.0" />
      <PackageReference Include="Reloaded.Memory.Sigscan" Version="1.2.1" />
      <PackageReference Include="Remora.Results" Version="7.1.0" />
    </ItemGroup>

</Project>

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

using System.Diagnostics;
using System.Runtime.InteropServices;
using NosSmooth.LocalBinding.Errors;
using NosSmooth.LocalBinding.Options;
using Reloaded.Hooks.Definitions;
using Reloaded.Hooks.Definitions.X86;
using Remora.Results;

namespace NosSmooth.LocalBinding.Objects;

/// <summary>
/// The binding to nostale network object.
/// </summary>
public class NetworkBinding
{
    [Function
    (
        new[] { FunctionAttribute.Register.eax, FunctionAttribute.Register.edx },
        FunctionAttribute.Register.eax,
        FunctionAttribute.StackCleanup.Callee
    )]
    private delegate void PacketSendDelegate(IntPtr packetObject, IntPtr packetString);

    [Function
    (
        new[] { FunctionAttribute.Register.eax, FunctionAttribute.Register.edx },
        FunctionAttribute.Register.eax,
        FunctionAttribute.StackCleanup.Callee
    )]
    private delegate void PacketReceiveDelegate(IntPtr packetObject, IntPtr packetString);

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

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

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

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

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

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

        if (options.HookSend)
        {
            binding._sendHook = sendFunction
                .Hook(binding.SendPacketDetour);
            binding._originalSend = binding._sendHook.OriginalFunction;
        }

        if (options.HookReceive)
        {
            binding._receiveHook = receiveFunction
                .Hook(binding.ReceivePacketDetour);
            binding._originalReceive = binding._receiveHook.OriginalFunction;
        }

        binding._sendHook?.Activate();
        binding._receiveHook?.Activate();
        return binding;
    }

    private readonly NosBindingManager _bindingManager;
    private readonly IntPtr _networkManagerAddress;
    private IHook<PacketSendDelegate>? _sendHook;
    private IHook<PacketReceiveDelegate>? _receiveHook;
    private PacketSendDelegate _originalSend;
    private PacketReceiveDelegate _originalReceive;

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

    /// <summary>
    /// Event that is called when packet send was called by NosTale.
    /// </summary>
    /// <remarks>
    /// The send must be hooked for this event to be called.
    /// </remarks>
    public event Func<string, bool>? PacketSend;

    /// <summary>
    /// Event that is called when packet receive was called by NosTale.
    /// </summary>
    /// <remarks>
    /// The receive must be hooked for this event to be called.
    /// </remarks>
    public event Func<string, bool>? PacketReceive;

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

        return Result.FromSuccess();
    }

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

        return Result.FromSuccess();
    }

    /// <summary>
    /// Disable all the hooks that are currently enabled.
    /// </summary>
    /// <returns>A result that may or may not have succeeded.</returns>
    public Result DisableHooks()
    {
        _receiveHook?.Disable();
        _sendHook?.Disable();
        return Result.FromSuccess();
    }

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

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

        return networkManager;
    }

    private void SendPacketDetour(IntPtr packetObject, IntPtr packetString)
    {
        var packet = Marshal.PtrToStringAnsi(packetString);
        if (packet is null)
        { // ?
            _originalSend(packetObject, packetString);
        }
        else
        {
            var result = PacketSend?.Invoke(packet);
            if (result ?? true)
            {
                _originalSend(packetObject, packetString);
            }
        }
    }

    private void ReceivePacketDetour(IntPtr packetObject, IntPtr packetString)
    {
        var packet = Marshal.PtrToStringAnsi(packetString);
        if (packet is null)
        { // ?
            _originalReceive(packetObject, packetString);
        }
        else
        {
            var result = PacketReceive?.Invoke(packet);
            if (result ?? true)
            {
                _originalReceive(packetObject, packetString);
            }
        }
    }
}
\ No newline at end of file

D Local/NosSmooth.LocalBinding/Objects/NostaleStringA.cs => Local/NosSmooth.LocalBinding/Objects/NostaleStringA.cs +0 -84
@@ 1,84 0,0 @@
//
//  NostaleStringA.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.Text;
using Reloaded.Memory.Sources;

namespace NosSmooth.LocalBinding.Objects;

/// <summary>
/// Represents nostale string object.
/// </summary>
public class NostaleStringA : IDisposable
{
    private readonly IMemory _memory;
    private IntPtr _pointer;

    /// <summary>
    /// Create an instance of <see cref="NostaleStringA"/>.
    /// </summary>
    /// <param name="memory">The memory to allocate the string on.</param>
    /// <param name="data">The string contents.</param>
    /// <returns>A nostale string.</returns>
    public static NostaleStringA Create(IMemory memory, string data)
    {
        var bytes = Encoding.ASCII.GetBytes(data);
        var allocated = memory.Allocate(bytes.Length + 1 + 8);
        memory.SafeWrite(allocated, 1);
        memory.SafeWrite(allocated + 4, data.Length);
        memory.SafeWriteRaw(allocated + 8, bytes);
        memory.SafeWrite(allocated + 8 + data.Length, 0);

        return new NostaleStringA(memory, allocated);
    }

    private NostaleStringA(IMemory memory, IntPtr pointer)
    {
        _memory = memory;
        _pointer = pointer;

    }

    /// <summary>
    /// Finalizes an instance of the <see cref="NostaleStringA"/> class.
    /// </summary>
    ~NostaleStringA()
    {
        Free();
    }

    /// <summary>
    /// Gets whether the string is still allocated.
    /// </summary>
    public bool Allocated => _pointer != IntPtr.Zero;

    /// <summary>
    /// Get the pointer to the string.
    /// </summary>
    /// <returns>A pointer to the string to pass to NosTale.</returns>
    public IntPtr Get()
    {
        return _pointer + 0x08;
    }

    /// <summary>
    /// Free the memory allocated by the string.
    /// </summary>
    public void Free()
    {
        if (Allocated)
        {
            _memory.Free(_pointer);
            _pointer = IntPtr.Zero;
        }
    }

    /// <inheritdoc />
    public void Dispose()
    {
        Free();
    }
}
\ No newline at end of file

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

using NosSmooth.LocalBinding.Options;
using NosSmooth.LocalBinding.Structs;
using Reloaded.Hooks.Definitions;
using Reloaded.Hooks.Definitions.X86;
using Remora.Results;

namespace NosSmooth.LocalBinding.Objects;

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

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

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

    [Function
    (
        new[] { FunctionAttribute.Register.eax, FunctionAttribute.Register.edx, FunctionAttribute.Register.ecx },
        FunctionAttribute.Register.eax,
        FunctionAttribute.StackCleanup.Callee
    )]
    private delegate bool PetWalkDelegate
    (
        IntPtr petManagerPtr,
        uint position,
        short unknown0 = 0,
        int unknown1 = 1,
        int unknown2 = 1
    );

    private IHook<PetWalkDelegate> _petWalkHook = null!;

    private PetManagerBinding(PetManagerList petManagerList)
    {
        PetManagerList = petManagerList;
    }

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

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

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

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

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

    private bool PetWalkDetour
    (
        IntPtr petManagerPtr,
        uint position,
        short unknown0 = 0,
        int unknown1 = 1,
        int unknown2 = 1
    )
    {
        return _petWalkHook.OriginalFunction
        (
            petManagerPtr,
            position,
            unknown0,
            unknown1,
            unknown2
        );
    }
}
\ No newline at end of file

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

using System.Diagnostics;
using NosSmooth.LocalBinding.Errors;
using NosSmooth.LocalBinding.Options;
using NosSmooth.LocalBinding.Structs;
using Reloaded.Hooks.Definitions;
using Reloaded.Hooks.Definitions.X86;
using Remora.Results;

namespace NosSmooth.LocalBinding.Objects;

/// <summary>
/// The nostale binding of a character.
/// </summary>
public class PlayerManagerBinding
{
    [Function
    (
        new[] { FunctionAttribute.Register.eax, FunctionAttribute.Register.edx, FunctionAttribute.Register.ecx },
        FunctionAttribute.Register.eax,
        FunctionAttribute.StackCleanup.Callee
    )]
    private delegate bool WalkDelegate(IntPtr playerManagerPtr, int position, short unknown0 = 0, int unknown1 = 1);

    [Function
    (
        new[] { FunctionAttribute.Register.eax, FunctionAttribute.Register.edx, FunctionAttribute.Register.ecx },
        FunctionAttribute.Register.eax,
        FunctionAttribute.StackCleanup.Callee
    )]
    private delegate bool FollowEntityDelegate
    (
        IntPtr playerManagerPtr,
        IntPtr entityPtr,
        int unknown1 = 0,
        int unknown2 = 1
    );

    [Function
    (
        new[] { FunctionAttribute.Register.eax, FunctionAttribute.Register.edx },
        FunctionAttribute.Register.eax,
        FunctionAttribute.StackCleanup.Callee
    )]
    private delegate void UnfollowEntityDelegate(IntPtr playerManagerPtr, int unknown = 0);

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

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

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

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

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

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

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

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

        if (options.HookWalk)
        {
            binding._walkHook = walkFunction
                .Hook(binding.WalkDetour);
            binding._originalWalk = binding._walkHook.OriginalFunction;
        }

        if (options.HookFollowEntity)
        {
            binding._followHook = followEntityFunction.Hook(binding.FollowEntityDetour);
            binding._originalFollowEntity = binding._followHook.OriginalFunction;
        }

        if (options.HookUnfollowEntity)
        {
            binding._unfollowHook = unfollowEntityFunction.Hook(binding.UnfollowEntityDetour);
            binding._originalUnfollowEntity = binding._unfollowHook.OriginalFunction;
        }

        binding._walkHook?.Activate();
        binding._followHook?.Activate();
        binding._unfollowHook?.Activate();
        return binding;
    }

    private readonly NosBindingManager _bindingManager;

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

    private FollowEntityDelegate _originalFollowEntity;
    private UnfollowEntityDelegate _originalUnfollowEntity;
    private WalkDelegate _originalWalk;

    private PlayerManagerBinding
    (
        NosBindingManager bindingManager,
        PlayerManager playerManager,
        WalkDelegate originalWalk,
        FollowEntityDelegate originalFollowEntity,
        UnfollowEntityDelegate originalUnfollowEntity
    )
    {
        PlayerManager = playerManager;
        _bindingManager = bindingManager;
        _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>
    /// The walk must be hooked for this event to be called.
    /// </remarks>
    public event Func<ushort, ushort, bool>? WalkCall;

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

    /// <summary>
    /// Disable all the hooks that are currently enabled.
    /// </summary>
    /// <returns>A result that may or may not have succeeded.</returns>
    public Result DisableHooks()
    {
        _walkHook?.Disable();
        return Result.FromSuccess();
    }

    /// <summary>
    /// Walk to the given position.
    /// </summary>
    /// <param name="x">The x coordinate.</param>
    /// <param name="y">The y coordinate.</param>
    /// <returns>A result that may or may not have succeeded.</returns>
    public Result<bool> Walk(ushort x, ushort y)
    {
        int param = (y << 16) | x;
        try
        {
            return _originalWalk(PlayerManager.Address, param);
        }
        catch (Exception e)
        {
            return e;
        }
    }

    private bool WalkDetour(IntPtr characterObject, int position, short unknown0, int unknown1)
    {
        var result = WalkCall?.Invoke((ushort)(position & 0xFFFF), (ushort)((position >> 16) & 0xFFFF));
        if (result ?? true)
        {
            return _originalWalk(characterObject, position, unknown0, unknown1);
        }

        return false;
    }

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

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

        return Result.FromSuccess();
    }

    /// <summary>
    /// Stop following entity.
    /// </summary>
    /// <returns>A result that may or may not have succeeded.</returns>
    public Result UnfollowEntity()
    {
        try
        {
            _originalUnfollowEntity(PlayerManager.Address);
        }
        catch (Exception e)
        {
            return e;
        }

        return Result.FromSuccess();
    }

    private bool FollowEntityDetour
    (
        IntPtr playerManagerPtr,
        IntPtr entityPtr,
        int unknown1,
        int unknown2
    )
    {
        var result = FollowEntityCall?.Invoke(new MapBaseObj(_bindingManager.Memory, entityPtr));
        if (result ?? true)
        {
            return _originalFollowEntity(playerManagerPtr, entityPtr, unknown1, unknown2);
        }

        return false;
    }

    private void UnfollowEntityDetour(IntPtr playerManagerPtr, int unknown)
    {
        var result = FollowEntityCall?.Invoke(null);
        if (result ?? true)
        {
            _originalUnfollowEntity(playerManagerPtr, unknown);
        }
    }
}
\ No newline at end of file

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

using System.Diagnostics;
using NosSmooth.LocalBinding.Errors;
using NosSmooth.LocalBinding.Extensions;
using NosSmooth.LocalBinding.Options;
using NosSmooth.LocalBinding.Structs;
using Reloaded.Hooks;
using Reloaded.Hooks.Definitions;
using Reloaded.Hooks.Definitions.X86;
using Reloaded.Memory.Buffers.Internal.Kernel32;
using Remora.Results;
using SharpDisasm.Udis86;

namespace NosSmooth.LocalBinding.Objects;

/// <summary>
/// The nostale binding of a scene manager.
/// </summary>
/// <remarks>
/// The scene manager holds addresses to entities, mouse position, ....
/// </remarks>
public class UnitManagerBinding
{
    [Function
    (
        new[] { FunctionAttribute.Register.eax, FunctionAttribute.Register.edx },
        FunctionAttribute.Register.eax,
        FunctionAttribute.StackCleanup.Callee
    )]
    private delegate int FocusEntityDelegate(IntPtr unitManagerPtr, IntPtr entityPtr);

    /// <summary>
    /// Create the scene manager binding.
    /// </summary>
    /// <param name="bindingManager">The binding manager.</param>
    /// <param name="bindingOptions">The options for the binding.</param>
    /// <returns>A network binding or an error.</returns>
    public static Result<UnitManagerBinding> Create
        (NosBindingManager bindingManager, UnitManagerBindingOptions bindingOptions)
    {
        var process = Process.GetCurrentProcess();

        var unitManagerStaticAddress = bindingManager.Scanner.CompiledFindPattern(bindingOptions.UnitManagerPattern);
        if (!unitManagerStaticAddress.Found)
        {
            return new BindingNotFoundError(bindingOptions.UnitManagerPattern, "UnitManagerBinding.UnitManager");
        }

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

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

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

        if (bindingOptions.HookFocusEntity)
        {
            binding._focusHook = focusEntityFunction.Hook(binding.FocusEntityDetour);
            binding._originalFocusEntity = binding._focusHook.OriginalFunction;
        }

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

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

    private readonly NosBindingManager _bindingManager;
    private FocusEntityDelegate _originalFocusEntity;

    private IHook<FocusEntityDelegate>? _focusHook;

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

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

    /// <summary>
    /// Event that is called when entity focus was called by NosTale.
    /// </summary>
    /// <remarks>
    /// The focus entity must be hooked for this event to be called.
    /// </remarks>
    public event Func<MapBaseObj?, bool>? EntityFocus;

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

    /// <summary>
    /// Focus the entity.
    /// </summary>
    /// <param name="entityAddress">The entity address.</param>
    /// <returns>A result that may or may not have succeeded.</returns>
    public Result FocusEntity(IntPtr entityAddress)
    {
        try
        {
            var res = _originalFocusEntity(Address, entityAddress);
            Console.WriteLine(res);
        }
        catch (Exception e)
        {
            return e;
        }

        return Result.FromSuccess();
    }

    private int FocusEntityDetour(IntPtr unitManagerPtr, IntPtr entityId)
    {
        MapBaseObj? obj = null;
        if (entityId != IntPtr.Zero)
        {
            obj = new MapBaseObj(_bindingManager.Memory, entityId);
        }

        var result = EntityFocus?.Invoke(obj);
        if (result ?? true)
        {
            var res = _originalFocusEntity(unitManagerPtr, entityId);
            Console.WriteLine(res);
            return res;
        }

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

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

using NosSmooth.LocalBinding.Objects;

namespace NosSmooth.LocalBinding.Options;

/// <summary>
/// Options for <see cref="PlayerManagerBinding"/>.
/// </summary>
public class CharacterBindingOptions
{
    /// <summary>
    /// Gets or sets whether to hook the walk function.
    /// </summary>
    public bool HookWalk { get; set; } = true;

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

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

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

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

    /// <summary>
    /// Gets or sets whether to hook the unfollow entity function.
    /// </summary>
    public bool HookUnfollowEntity { get; set; } = true;
}
\ No newline at end of file

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

using NosSmooth.LocalBinding.Objects;

namespace NosSmooth.LocalBinding.Options;

/// <summary>
/// Options for <see cref="NetworkBinding"/>.
/// </summary>
public class NetworkBindingOptions
{
    /// <summary>
    /// Gets or sets whether to hook the send packet function.
    /// </summary>
    public bool HookSend { get; set; } = true;

    /// <summary>
    /// Gets or sets whether to hook the receive packet function.
    /// </summary>
    public bool HookReceive { get; set; } = true;

    /// <summary>
    /// Gets or sets the pattern to find the network object at.
    /// </summary>
    /// <remarks>
    /// The address of the object is "three pointers down" from address found on this pattern.
    /// </remarks>
    public string NetworkObjectPattern { get; set; }
        = "A1 ?? ?? ?? ?? 8B 00 BA ?? ?? ?? ?? E8 ?? ?? ?? ?? E9 ?? ?? ?? ?? A1 ?? ?? ?? ?? 8B 00 8B 40 40";

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

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

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

using NosSmooth.LocalBinding.Objects;

namespace NosSmooth.LocalBinding.Options;

/// <summary>
/// Options for <see cref="PetManagerBinding"/>.
/// </summary>
public class PetManagerBindingOptions
{
    /// <summary>
    /// Gets or sets the pattern of a pet walk function.
    /// </summary>
    public string PetWalkPattern { get; set; }
        = "55 8b ec 83 c4 e4 53 56 57 8b f9 89 55 fc 8b d8 c6 45 fb 00";

    /// <summary>
    /// Gets or sets whether to hook the pet walk function.
    /// </summary>
    public bool HookPetWalk { get; set; } = true;
}
\ No newline at end of file

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

using NosSmooth.LocalBinding.Objects;
using NosSmooth.LocalBinding.Structs;

namespace NosSmooth.LocalBinding.Options;

/// <summary>
/// Options for <see cref="PetManagerList"/>.
/// </summary>
public class PetManagerOptions
{
    /// <summary>
    /// Gets or sets the pattern to find static pet manager list address at.
    /// </summary>
    public string PetManagerListPattern { get; set; }
        = "8B F8 8B D3 A1 ?? ?? ?? ?? E8 ?? ?? ?? ?? 8B D0";

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

D Local/NosSmooth.LocalBinding/Options/PlayerManagerOptions.cs => Local/NosSmooth.LocalBinding/Options/PlayerManagerOptions.cs +0 -27
@@ 1,27 0,0 @@
//
//  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 };
}
\ No newline at end of file

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

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

using NosSmooth.LocalBinding.Objects;

namespace NosSmooth.LocalBinding.Options;

/// <summary>
/// Options for <see cref="UnitManagerBinding"/>.
/// </summary>
public class UnitManagerBindingOptions
{
    /// <summary>
    /// Gets or sets the pattern to static address of unit manager.
    /// </summary>
    public string UnitManagerPattern { get; set; }
        = "TODO";

    /// <summary>
    /// Gets or sets the pointer offsets from the unit manager static address.
    /// </summary>
    public int[] UnitManagerOffsets { get; set; }
        = { 15 };

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

    /// <summary>
    /// Gets or sets whether to hook the Focus entity function.
    /// </summary>
    public bool HookFocusEntity { get; set; } = true;
}
\ No newline at end of file

D Local/NosSmooth.LocalBinding/Structs/ControlManager.cs => Local/NosSmooth.LocalBinding/Structs/ControlManager.cs +0 -78
@@ 1,78 0,0 @@
//
//  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 : NostaleObject
{
    /// <summary>
    /// Initializes a new instance of the <see cref="ControlManager"/> class.
    /// </summary>
    /// <param name="memory">The memory.</param>
    /// <param name="address">The address of the manager.</param>
    protected ControlManager(IMemory memory, IntPtr address)
        : base(memory, address)
    {
    }

    /// <summary>
    /// Gets the entity this control manager is for.
    /// </summary>
    public abstract MapBaseObj Entity { 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

D Local/NosSmooth.LocalBinding/Structs/MapBaseObj.cs => Local/NosSmooth.LocalBinding/Structs/MapBaseObj.cs +0 -68
@@ 1,68 0,0 @@
//
//  MapBaseObj.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 map object. Common for players, monsters, npcs.
/// </summary>
public class MapBaseObj : NostaleObject
{
    /// <summary>
    /// Initializes a new instance of the <see cref="MapBaseObj"/> class.
    /// </summary>
    public MapBaseObj()
    {
    }

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

    /// <summary>
    /// Gets the id of the entity.
    /// </summary>
    public long Id
    {
        get
        {
            Memory.SafeRead(Address + 0x08, out int id);
            return id;
        }
    }

    /// <summary>
    /// Gets the x coordinate of the entity.
    /// </summary>
    public ushort X
    {
        get
        {
            Memory.SafeRead(Address + 0x0C, out ushort x);
            return x;
        }
    }

    /// <summary>
    /// Gets the y coordinate of the entity.
    /// </summary>
    public ushort Y
    {
        get
        {
            Memory.SafeRead(Address + 0x0E, out ushort y);
            return y;
        }
    }
}
\ No newline at end of file

D Local/NosSmooth.LocalBinding/Structs/MapNpcObj.cs => Local/NosSmooth.LocalBinding/Structs/MapNpcObj.cs +0 -25
@@ 1,25 0,0 @@
//
//  MapNpcObj.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>
/// Npc NosTale object.
/// </summary>
public class MapNpcObj : MapBaseObj
{
    /// <summary>
    /// Initializes a new instance of the <see cref="MapNpcObj"/> class.
    /// </summary>
    /// <param name="memory">The memory.</param>
    /// <param name="mapObjPointer">The pointer to the object.</param>
    public MapNpcObj(IMemory memory, IntPtr mapObjPointer)
        : base(memory, mapObjPointer)
    {
    }
}
\ No newline at end of file

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

using System.Runtime.InteropServices;
using System.Text;
using Reloaded.Memory.Sources;

namespace NosSmooth.LocalBinding.Structs;

/// <summary>
/// NosTale Player object.
/// </summary>
public class MapPlayerObj : MapBaseObj
{
    private readonly IMemory _memory;

    /// <summary>
    /// Initializes a new instance of the <see cref="MapPlayerObj"/> class.
    /// </summary>
    /// <param name="memory">The memory.</param>
    /// <param name="mapObjPointer">The player object pointer.</param>
    public MapPlayerObj(IMemory memory, IntPtr mapObjPointer)
        : base(memory, mapObjPointer)
    {
        _memory = memory;
    }

    /// <summary>
    /// Gets the name of the player.
    /// </summary>
    public string? Name
    {
        get
        {
            _memory.SafeRead(Address + 0x1EC, out int nameAddress);
            _memory.SafeRead((IntPtr)nameAddress - 4, out int nameLength);
            byte[] data = new byte[nameLength];
            _memory.SafeReadRaw((IntPtr)nameAddress, out data, nameLength);
            return Encoding.ASCII.GetString(data);
        }
    }
}
\ No newline at end of file

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

using System.Collections;
using Reloaded.Memory.Pointers;
using Reloaded.Memory.Sources;

namespace NosSmooth.LocalBinding.Structs;

/// <summary>
/// A class representing a list from nostale.
/// </summary>
/// <typeparam name="T">The type.</typeparam>
public class NostaleList<T> : IEnumerable<T>
    where T : NostaleObject, new()
{
    private readonly IMemory _memory;

    /// <summary>
    /// Initializes a new instance of the <see cref="NostaleList{T}"/> class.
    /// </summary>
    /// <param name="memory">The memory.</param>
    /// <param name="objListPointer">The object list pointer.</param>
    public NostaleList(IMemory memory, IntPtr objListPointer)
    {
        _memory = memory;
        Address = objListPointer;
    }

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

    /// <summary>
    /// Gets the element at the given index.
    /// </summary>
    /// <param name="index">The index of the element.</param>
    /// <exception cref="IndexOutOfRangeException">Thrown if the index is not in the bounds of the array.</exception>
    public T this[int index]
    {
        get
        {
            if (index >= Length || index < 0)
            {
                throw new IndexOutOfRangeException();
            }

            _memory.SafeRead(Address + 0x04, out int arrayAddress);
            _memory.SafeRead((IntPtr)arrayAddress + (0x04 * index), out int objectAddress);

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

    /// <summary>
    /// Gets the length of the array.
    /// </summary>
    public int Length
    {
        get
        {
            _memory.SafeRead(Address + 0x08, out int length);
            return length;
        }
    }

    /// <inheritdoc/>
    public IEnumerator<T> GetEnumerator()
    {
        return new NostaleListEnumerator(this);
    }

    /// <inheritdoc/>
    IEnumerator IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }

    private class NostaleListEnumerator : IEnumerator<T>
    {
        private readonly NostaleList<T> _list;
        private int _index;

        public NostaleListEnumerator(NostaleList<T> list)
        {
            _index = -1;
            _list = list;
        }

        public bool MoveNext()
        {
            if (_list.Length > _index + 1)
            {
                _index++;
                return true;
            }

            return false;
        }

        public void Reset()
        {
            _index = -1;
        }

        public T Current => _list[_index];

        object IEnumerator.Current => Current;

        public void Dispose()
        {
        }
    }
}
\ No newline at end of file

D Local/NosSmooth.LocalBinding/Structs/NostaleObject.cs => Local/NosSmooth.LocalBinding/Structs/NostaleObject.cs +0 -43
@@ 1,43 0,0 @@
//
//  NostaleObject.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>
/// A NosTale object base.
/// </summary>
public abstract class NostaleObject
{
    /// <summary>
    /// Initializes a new instance of the <see cref="NostaleObject"/> class.
    /// </summary>
    internal NostaleObject()
    {
    }

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

    /// <summary>
    /// Gets the memory the object is stored in.
    /// </summary>
    internal virtual IMemory Memory { get; set; } = null!;

    /// <summary>
    /// Gets the address of the object.
    /// </summary>
    public virtual IntPtr Address { get; internal set; } = IntPtr.Zero;
}
\ No newline at end of file

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

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

namespace NosSmooth.LocalBinding.Structs;

/// <summary>
/// NosTale pet manager.
/// </summary>
public class PetManager : ControlManager
{
    /// <summary>
    /// Initializes a new instance of the <see cref="PetManager"/> class.
    /// </summary>
    public PetManager()
        : base(null!, IntPtr.Zero)
    {
    }

    /// <summary>
    /// Initializes a new instance of the <see cref="PetManager"/> class.
    /// </summary>
    /// <param name="memory">The memory.</param>
    /// <param name="petManagerAddress">The pet manager address.</param>
    public PetManager(IMemory memory, IntPtr petManagerAddress)
        : base(memory, petManagerAddress)
    {
    }

    /// <summary>
    /// Gets the player object.
    /// </summary>
    public MapNpcObj Pet
    {
        get
        {
            Memory.SafeRead(Address + 0x7C, out int playerAddress);
            return new MapNpcObj(Memory, (IntPtr)playerAddress);
        }
    }

    /// <inheritdoc />
    public override MapBaseObj Entity => Pet;
}
\ No newline at end of file

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

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

namespace NosSmooth.LocalBinding.Structs;

/// <summary>
/// NosTale list of <see cref="PetManager"/>.
/// </summary>
public class PetManagerList : NostaleList<PetManager>
{
    /// <summary>
    /// Create <see cref="PlayerManager"/> instance.
    /// </summary>
    /// <param name="nosBrowserManager">The NosTale process browser.</param>
    /// <param name="options">The options.</param>
    /// <returns>The player manager or an error.</returns>
    public static Result<PetManagerList> Create(NosBrowserManager nosBrowserManager, PetManagerOptions options)
    {
        var characterObjectAddress = nosBrowserManager.Scanner.CompiledFindPattern(options.PetManagerListPattern);
        if (!characterObjectAddress.Found)
        {
            return new BindingNotFoundError(options.PetManagerListPattern, "PetManagerList");
        }

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

        int staticManagerAddress = (int)nosBrowserManager.Process.MainModule.BaseAddress + characterObjectAddress.Offset;
        return new PetManagerList(nosBrowserManager.Memory, staticManagerAddress, options.PetManagerListOffsets);
    }

    /// <summary>
    /// Initializes a new instance of the <see cref="PetManagerList"/> class.
    /// </summary>
    /// <param name="memory">The memory.</param>
    /// <param name="staticPetManagerListAddress">The static pet manager address.</param>
    /// <param name="staticPetManagerOffsets">The offsets to follow to the pet manager list address.</param>
    public PetManagerList(IMemory memory, int staticPetManagerListAddress, int[] staticPetManagerOffsets)
        : base(memory, memory.FollowStaticAddressOffsets(staticPetManagerListAddress, staticPetManagerOffsets))
    {
    }
}
\ No newline at end of file

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

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

namespace NosSmooth.LocalBinding.Structs;

/// <summary>
/// NosTale player manager.
/// </summary>
public class PlayerManager : ControlManager
{
    /// <summary>
    /// Create <see cref="PlayerManager"/> instance.
    /// </summary>
    /// <param name="nosBrowserManager">The NosTale process browser.</param>
    /// <param name="options">The options.</param>
    /// <returns>The player manager or an error.</returns>
    public static Result<PlayerManager> Create(NosBrowserManager nosBrowserManager, PlayerManagerOptions options)
    {
        var characterObjectAddress = nosBrowserManager.Scanner.CompiledFindPattern(options.PlayerManagerPattern);
        if (!characterObjectAddress.Found)
        {
            return new BindingNotFoundError(options.PlayerManagerPattern, "PlayerManager");
        }

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

        var staticAddress = (int)nosBrowserManager.Process.MainModule.BaseAddress + characterObjectAddress.Offset;
        return new PlayerManager(nosBrowserManager.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="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, IntPtr.Zero)
    {
        _memory = memory;
        _staticPlayerManagerAddress = staticPlayerManagerAddress;
        _playerManagerOffsets = playerManagerOffsets;
    }

    /// <summary>
    /// Gets the address to the player manager.
    /// </summary>
    public override IntPtr Address => _memory.FollowStaticAddressOffsets
        (_staticPlayerManagerAddress, _playerManagerOffsets);

    /// <summary>
    /// Gets the player object.
    /// </summary>
    public MapPlayerObj Player
    {
        get
        {
            _memory.SafeRead(Address + 0x20, out int playerAddress);
            return new MapPlayerObj(_memory, (IntPtr)playerAddress);
        }
    }

    /// <summary>
    /// Gets the player id.
    /// </summary>
    public int PlayerId
    {
        get
        {
            _memory.SafeRead(Address + 0x24, out int playerId);
            return playerId;
        }
    }

    /// <inheritdoc />
    public override MapBaseObj Entity => Player;
}
\ No newline at end of file

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

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

namespace NosSmooth.LocalBinding.Structs;

/// <summary>
/// Represents nostale scene manager struct.
/// </summary>
public class SceneManager
{
    /// <summary>
    /// Create <see cref="PlayerManager"/> instance.
    /// </summary>
    /// <param name="nosBrowserManager">The NosTale process browser.</param>
    /// <param name="options">The options.</param>
    /// <returns>The player manager or an error.</returns>
    public static Result<SceneManager> Create(NosBrowserManager nosBrowserManager, SceneManagerOptions options)
    {
        var characterObjectAddress = nosBrowserManager.Scanner.CompiledFindPattern(options.SceneManagerObjectPattern);
        if (!characterObjectAddress.Found)
        {
            return new BindingNotFoundError(options.SceneManagerObjectPattern, "SceneManager");
        }

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

        int staticManagerAddress = (int)nosBrowserManager.Process.MainModule.BaseAddress + characterObjectAddress.Offset;
        return new SceneManager(nosBrowserManager.Memory, staticManagerAddress, options.SceneManagerOffsets);
    }

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

    /// <summary>
    /// Initializes a new instance of the <see cref="SceneManager"/> class.
    /// </summary>
    /// <param name="memory">The memory.</param>
    /// <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;
        _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 NostaleList<MapBaseObj> PlayerList => new NostaleList<MapBaseObj>(_memory, ReadPtr(Address + 0xC));

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

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

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

    /// <summary>
    /// Gets the entity that is currently being followed by the player.
    /// </summary>
    public MapBaseObj? FollowEntity
    {
        get
        {
            var ptr = ReadPtr(Address + 0x48);
            if (ptr == IntPtr.Zero)
            {
                return null;
            }

            return new MapBaseObj(_memory, ptr);
        }
    }

    /// <summary>
    /// Gets the lock on target marked address.
    /// </summary>
    public IntPtr LockOnTargetMarkedAddress
    {
        get
        {
            var ptr = ReadPtr(Address + 0x1C);
            ptr = ReadPtr(ptr + 0x04);
            ptr = ReadPtr(ptr + 0x00);
            return ptr;
        }
    }

    private IntPtr ReadPtr(IntPtr ptr)
    {
        _memory.Read(ptr, out int read);
        return (IntPtr)read;
    }

    /// <summary>
    /// Find the given entity address.
    /// </summary>
    /// <param name="id">The id of the entity.</param>
    /// <returns>The pointer to the entity or an error.</returns>
    public Result<MapBaseObj?> FindEntity(int id)
    {
        if (id == 0)
        {
            return Result<MapBaseObj?>.FromSuccess(null);
        }

        var item = ItemList.FirstOrDefault(x => x.Id == id);
        if (item is not null)
        {
            return item;
        }

        var monster = MonsterList.FirstOrDefault(x => x.Id == id);
        if (monster is not null)
        {
            return monster;
        }

        var npc = NpcList.FirstOrDefault(x => x.Id == id);
        if (npc is not null)
        {
            return npc;
        }

        var player = PlayerList.FirstOrDefault(x => x.Id == id);
        if (player is not null)
        {
            return player;
        }

        return new NotFoundError($"Could not find entity with id {id}");
    }
}
\ No newline at end of file

D Local/NosSmooth.LocalClient/CommandHandlers/Walk/ControlCommandWalkHandler.cs => Local/NosSmooth.LocalClient/CommandHandlers/Walk/ControlCommandWalkHandler.cs +0 -145
@@ 1,145 0,0 @@
//
//  ControlCommandWalkHandler.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.Core.Client;
using NosSmooth.Core.Commands.Control;
using NosSmooth.Core.Commands.Walking;
using NosSmooth.Core.Extensions;
using NosSmooth.LocalBinding.Structs;
using NosSmooth.LocalClient.CommandHandlers.Walk.Errors;
using Remora.Results;

namespace NosSmooth.LocalClient.CommandHandlers.Walk;

/// <summary>
/// Handler for control manager walk command.
/// </summary>
internal class ControlCommandWalkHandler
{
    private readonly INostaleClient _nostaleClient;
    private readonly Func<ushort, ushort, Result<bool>> _walkFunction;
    private readonly ControlManager _controlManager;
    private readonly WalkCommandHandlerOptions _options;

    private ushort _x;
    private ushort _y;

    /// <summary>
    /// Initializes a new instance of the <see cref="ControlCommandWalkHandler"/> class.
    /// </summary>
    /// <param name="nostaleClient">The nostale client.</param>
    /// <param name="walkFunction">The walk function.</param>
    /// <param name="controlManager">The control manager.</param>
    /// <param name="options">The options.</param>
    public ControlCommandWalkHandler
    (
        INostaleClient nostaleClient,
        Func<ushort, ushort, Result<bool>> walkFunction,
        ControlManager controlManager,
        WalkCommandHandlerOptions options
    )
    {
        _nostaleClient = nostaleClient;
        _walkFunction = walkFunction;
        _controlManager = controlManager;
        _options = options;
    }

    /// <summary>
    /// Handle walk take control command.
    /// </summary>
    /// <param name="x">The x coordinate.</param>
    /// <param name="y">The y coordinate.</param>
    /// <param name="command">The take control command.</param>
    /// <param name="groupName">The name of the take control group.</param>
    /// <param name="ct">The cancellation token for cancelling the operation.</param>
    /// <returns>A <see cref="Task{TResult}"/> representing the result of the asynchronous operation.</returns>
    public async Task<Result> HandleCommand(ushort x, ushort y, ITakeControlCommand command, string groupName, CancellationToken ct = default)
    {
        _x = x;
        _y = y;

        using CancellationTokenSource linked = CancellationTokenSource.CreateLinkedTokenSource(ct);
        WalkUnfinishedReason? reason = null;
        var takeControlCommand = command.CreateTakeControl
        (
            groupName,
            WalkGrantedCallback,
            (r) =>
            {
                reason = r switch
                {
                    ControlCancelReason.AnotherTask => WalkUnfinishedReason.AnotherTask,
                    ControlCancelReason.UserAction => WalkUnfinishedReason.UserAction,
                    _ => WalkUnfinishedReason.Unknown
                };
                return Task.FromResult(Result.FromSuccess());
            }
        );

        var commandResult = await _nostaleClient.SendCommandAsync(takeControlCommand, ct);
        if (!commandResult.IsSuccess)
        {
            return commandResult;
        }

        if (reason is null && !IsAt(x, y))
        {
            reason = WalkUnfinishedReason.PathNotFound;
        }

        if (reason is null)
        {
            return Result.FromSuccess();
        }

        return new WalkNotFinishedError
        (
            _controlManager.X,
            _controlManager.Y,
            (WalkUnfinishedReason)reason
        );
    }

    private bool IsAtTarget()
    {
        return _controlManager.TargetX == _controlManager.Entity.X
            && _controlManager.TargetY == _controlManager.Entity.Y;
    }

    private bool IsAt(ushort x, ushort y)
    {
        return _controlManager.Entity.X == x && _controlManager.Entity.Y == y;
    }

    private async Task<Result> WalkGrantedCallback(CancellationToken ct)
    {
        var result = _walkFunction(_x, _y);
        if (!result.IsSuccess)
        {
            return Result.FromError(result);
        }

        while (!ct.IsCancellationRequested)
        {
            try
            {
                await Task.Delay(_options.CheckDelay, ct);
            }
            catch
            {
                // ignored
            }

            if (IsAtTarget() || IsAt(_x, _y))
            {
                return Result.FromSuccess();
            }
        }

        return Result.FromSuccess(); // cancellation is handled in cancellation callback.
    }
}
\ No newline at end of file

D Local/NosSmooth.LocalClient/CommandHandlers/Walk/Errors/WalkNotFinishedError.cs => Local/NosSmooth.LocalClient/CommandHandlers/Walk/Errors/WalkNotFinishedError.cs +0 -18
@@ 1,18 0,0 @@
//
//  WalkNotFinishedError.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.LocalClient.CommandHandlers.Walk.Errors;

/// <summary>
/// Represents an error that can be returned from walk command handler.
/// </summary>
/// <param name="X">The x coordinate where the player is. (if known)</param>
/// <param name="Y">The y coordinate where the player is. (if known)</param>
/// <param name="Reason"></param>
public record WalkNotFinishedError(int? X, int? Y, WalkUnfinishedReason Reason)
    : ResultError($"Could not finish the walk to {X} {Y}, because {Reason}");
\ No newline at end of file

D Local/NosSmooth.LocalClient/CommandHandlers/Walk/PetWalkCommandHandler.cs => Local/NosSmooth.LocalClient/CommandHandlers/Walk/PetWalkCommandHandler.cs +0 -75
@@ 1,75 0,0 @@
//
//  PetWalkCommandHandler.cs
//
//  Copyright (c) František Boháček. All rights reserved.
//  Licensed under the MIT license. See LICENSE file in the project root for full license information.

using Microsoft.Extensions.Options;
using NosSmooth.Core.Client;
using NosSmooth.Core.Commands;
using NosSmooth.Core.Commands.Control;
using NosSmooth.Core.Commands.Walking;
using NosSmooth.LocalBinding.Objects;
using Remora.Results;

namespace NosSmooth.LocalClient.CommandHandlers.Walk;

/// <summary>
/// Handles <see cref="PetWalkCommand"/>.
/// </summary>
public class PetWalkCommandHandler : ICommandHandler<PetWalkCommand>
{
    /// <summary>
    /// Group that is used for <see cref="TakeControlCommand"/>.
    /// </summary>
    public const string PetWalkControlGroup = "PetWalk";

    private readonly PetManagerBinding _petManagerBinding;
    private readonly INostaleClient _nostaleClient;
    private readonly WalkCommandHandlerOptions _options;

    /// <summary>
    /// Initializes a new instance of the <see cref="PetWalkCommandHandler"/> class.
    /// </summary>
    /// <param name="petManagerBinding">The character object binding.</param>
    /// <param name="nostaleClient">The nostale client.</param>
    /// <param name="options">The options.</param>
    public PetWalkCommandHandler
    (
        PetManagerBinding petManagerBinding,
        INostaleClient nostaleClient,
        IOptions<WalkCommandHandlerOptions> options
    )
    {
        _options = options.Value;
        _petManagerBinding = petManagerBinding;
        _nostaleClient = nostaleClient;
    }

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

        var handler = new ControlCommandWalkHandler
        (
            _nostaleClient,
            (x, y) => _petManagerBinding.PetWalk(command.PetSelector, x, y),
            petManager,
            _options
        );

        return await handler.HandleCommand
        (
            command.TargetX,
            command.TargetY,
            command,
            PetWalkControlGroup + "_" + command.PetSelector,
            ct
        );
    }
}
\ No newline at end of file

D Local/NosSmooth.LocalClient/CommandHandlers/Walk/PlayerWalkCommandHandler.cs => Local/NosSmooth.LocalClient/CommandHandlers/Walk/PlayerWalkCommandHandler.cs +0 -71
@@ 1,71 0,0 @@
//
//  PlayerWalkCommandHandler.cs
//
//  Copyright (c) František Boháček. All rights reserved.
//  Licensed under the MIT license. See LICENSE file in the project root for full license information.

using Microsoft.Extensions.Options;
using NosSmooth.Core.Client;
using NosSmooth.Core.Commands;
using NosSmooth.Core.Commands.Control;
using NosSmooth.Core.Commands.Walking;
using NosSmooth.Core.Extensions;
using NosSmooth.LocalBinding.Objects;
using NosSmooth.LocalClient.CommandHandlers.Walk.Errors;
using Remora.Results;

namespace NosSmooth.LocalClient.CommandHandlers.Walk;

/// <summary>
/// Handles <see cref="PlayerWalkCommand"/>.
/// </summary>
public class PlayerWalkCommandHandler : ICommandHandler<PlayerWalkCommand>
{
    /// <summary>
    /// Group that is used for <see cref="TakeControlCommand"/>.
    /// </summary>
    public const string PlayerWalkControlGroup = "PlayerWalk";

    private readonly PlayerManagerBinding _playerManagerBinding;
    private readonly INostaleClient _nostaleClient;
    private readonly WalkCommandHandlerOptions _options;

    /// <summary>
    /// Initializes a new instance of the <see cref="PlayerWalkCommandHandler"/> class.
    /// </summary>
    /// <param name="playerManagerBinding">The character object binding.</param>
    /// <param name="nostaleClient">The nostale client.</param>
    /// <param name="options">The options.</param>
    public PlayerWalkCommandHandler
    (
        PlayerManagerBinding playerManagerBinding,
        INostaleClient nostaleClient,
        IOptions<WalkCommandHandlerOptions> options
    )
    {
        _options = options.Value;
        _playerManagerBinding = playerManagerBinding;
        _nostaleClient = nostaleClient;
    }

    /// <inheritdoc/>
    public async Task<Result> HandleCommand(PlayerWalkCommand command, CancellationToken ct = default)
    {
        var handler = new ControlCommandWalkHandler
        (
            _nostaleClient,
            (x, y) => _playerManagerBinding.Walk(x, y),
            _playerManagerBinding.PlayerManager,
            _options
        );

        return await handler.HandleCommand
        (
            command.TargetX,
            command.TargetY,
            command,
            PlayerWalkControlGroup,
            ct
        );
    }
}
\ No newline at end of file

D Local/NosSmooth.LocalClient/CommandHandlers/Walk/WalkCommandHandlerOptions.cs => Local/NosSmooth.LocalClient/CommandHandlers/Walk/WalkCommandHandlerOptions.cs +0 -23
@@ 1,23 0,0 @@
//
//  WalkCommandHandlerOptions.cs
//
//  Copyright (c) František Boháček. All rights reserved.
//  Licensed under the MIT license. See LICENSE file in the project root for full license information.

namespace NosSmooth.LocalClient.CommandHandlers.Walk
{
    /// <summary>
    /// Options for <see cref="PlayerWalkCommandHandler"/>.
    /// </summary>
    public class WalkCommandHandlerOptions
    {
        /// <summary>
        /// The command handler sleeps for this duration, then checks for new info. Unit is milliseconds.
        /// </summary>
        /// <remarks>
        /// The operation is done with a cancellation token, if there is an outer event, then it can be faster.
        /// Walk function is called again as well after this delay.
        /// </remarks>
        public int CheckDelay { get; set; } = 50;
    }
}

D Local/NosSmooth.LocalClient/CommandHandlers/Walk/WalkUnfinishedReason.cs => Local/NosSmooth.LocalClient/CommandHandlers/Walk/WalkUnfinishedReason.cs +0 -36
@@ 1,36 0,0 @@
//
//  WalkUnfinishedReason.cs
//
//  Copyright (c) František Boháček. All rights reserved.
//  Licensed under the MIT license. See LICENSE file in the project root for full license information.

namespace NosSmooth.LocalClient.CommandHandlers.Walk;

/// <summary>
/// Reason for not finishing a walk.
/// </summary>
public enum WalkUnfinishedReason
{
    /// <summary>
    /// There was an unknown unfinished reason.
    /// </summary>
    Unknown,

    /// <summary>
    /// The client could not find path to the given location.
    /// </summary>
    /// <remarks>
    /// The user walked just some part of the path.
    /// </remarks>
    PathNotFound,

    /// <summary>
    /// The user has took an action that has cancelled the walk.
    /// </summary>
    UserAction,

    /// <summary>
    /// There was another walk action that cancelled this one.
    /// </summary>
    AnotherTask
}
\ No newline at end of file

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

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using NosSmooth.Core.Client;
using NosSmooth.Core.Extensions;
using NosSmooth.LocalBinding.Extensions;
using NosSmooth.LocalClient.CommandHandlers.Walk;

namespace NosSmooth.LocalClient.Extensions;

/// <summary>
/// Contains extension methods for <see cref="IServiceCollection"/>.
/// </summary>
public static class ServiceCollectionExtensions
{
    /// <summary>
    /// Adds <see cref="NostaleLocalClient"/> along with all core dependencies.
    /// </summary>
    /// <param name="serviceCollection">The service collection.</param>
    /// <returns>The collection.</returns>
    public static IServiceCollection AddLocalClient(this IServiceCollection serviceCollection)
    {
        serviceCollection.AddNostaleCore();
        serviceCollection.AddNostaleBindings();
        serviceCollection
            .AddTakeControlCommand()
            .AddCommandHandler<PlayerWalkCommandHandler>()
            .AddCommandHandler<PetWalkCommandHandler>();
        serviceCollection.TryAddSingleton<NostaleLocalClient>();
        serviceCollection.TryAddSingleton<INostaleClient>(p => p.GetRequiredService<NostaleLocalClient>());

        return serviceCollection;
    }

    /// <summary>
    /// Adds packet interceptor.
    /// </summary>
    /// <param name="serviceCollection">The service collection.</param>
    /// <typeparam name="TInterceptor">The type of the interceptor.</typeparam>
    /// <returns>The collection.</returns>
    public static IServiceCollection AddPacketInterceptor<TInterceptor>(this IServiceCollection serviceCollection)
        where TInterceptor : class, IPacketInterceptor
    {
        return serviceCollection.AddSingleton<IPacketInterceptor, TInterceptor>();
    }
}
\ No newline at end of file

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

namespace NosSmooth.LocalClient;

/// <summary>
/// Used for intercepting packet communication,
/// changing the contents of the packets and/or cancelling them altogether.
/// </summary>
public interface IPacketInterceptor
{
    /// <summary>
    /// Intercept the given packet.
    /// </summary>
    /// <param name="packet">The packet itself, if it is changed, the modified packet will be sent.</param>
    /// <returns>Whether to send the packet to the server.</returns>
    public bool InterceptSend(ref string packet);

    /// <summary>
    /// Intercept the given packet.
    /// </summary>
    /// <param name="packet">The packet itself, if it is changed, the modified packet will be received.</param>
    /// <returns>Whether to receive the packet by the client.</returns>
    public bool InterceptReceive(ref string packet);
}
\ No newline at end of file

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

namespace System.Runtime.CompilerServices
{
    /// <summary>
    /// Dummy.
    /// </summary>
    public class IsExternalInit
    {
    }
}
\ No newline at end of file

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

namespace NosSmooth.LocalClient;

/// <summary>
/// Options for <see cref="NostaleLocalClient"/>.
/// </summary>
public class LocalClientOptions
{
    /// <summary>
    /// Gets or sets whether the interception of packets should be allowed.
    /// </summary>
    public bool AllowIntercept { get; set; }
}
\ No newline at end of file

D Local/NosSmooth.LocalClient/NosSmooth.LocalClient.csproj => Local/NosSmooth.LocalClient/NosSmooth.LocalClient.csproj +0 -21
@@ 1,21 0,0 @@
<Project Sdk="Microsoft.NET.Sdk">

    <PropertyGroup>
        <ImplicitUsings>enable</ImplicitUsings>
        <Nullable>enable</Nullable>
        <LangVersion>10</LangVersion>
        <TargetFrameworks>net6.0;netstandard2.1</TargetFrameworks>
    </PropertyGroup>

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

    <ItemGroup>
      <PackageReference Include="Microsoft.Extensions.Logging" Version="6.0.0" />
      <PackageReference Include="Reloaded.Hooks" Version="3.5.0" />
      <PackageReference Include="Reloaded.Memory.Sigscan" Version="1.2.1" />
    </ItemGroup>

</Project>

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

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using NosSmooth.Core.Client;
using NosSmooth.Core.Commands;
using NosSmooth.Core.Commands.Control;
using NosSmooth.Core.Extensions;
using NosSmooth.Core.Packets;
using NosSmooth.LocalBinding.Objects;
using NosSmooth.LocalBinding.Structs;
using NosSmooth.Packets;
using NosSmooth.Packets.Errors;
using NosSmooth.PacketSerializer.Abstractions.Attributes;
using Remora.Results;

namespace NosSmooth.LocalClient;

/// <summary>
/// The local nostale client.
/// </summary>
/// <remarks>
/// Client used for living in the same process as NostaleClientX.exe.
/// It hooks the send and receive packet methods.
/// </remarks>
public class NostaleLocalClient : BaseNostaleClient
{
    private readonly NetworkBinding _networkBinding;
    private readonly PlayerManagerBinding _playerManagerBinding;
    private readonly ControlCommands _controlCommands;
    private readonly IPacketSerializer _packetSerializer;
    private readonly IPacketHandler _packetHandler;
    private readonly ILogger _logger;
    private readonly IServiceProvider _provider;
    private readonly LocalClientOptions _options;
    private CancellationToken? _stopRequested;
    private IPacketInterceptor? _interceptor;

    /// <summary>
    /// Initializes a new instance of the <see cref="NostaleLocalClient"/> class.
    /// </summary>
    /// <param name="networkBinding">The network binding.</param>
    /// <param name="playerManagerBinding">The player manager binding.</param>
    /// <param name="controlCommands">The control commands.</param>
    /// <param name="commandProcessor">The command processor.</param>
    /// <param name="packetSerializer">The packet serializer.</param>
    /// <param name="packetHandler">The packet handler.</param>
    /// <param name="logger">The logger.</param>
    /// <param name="options">The options for the client.</param>
    /// <param name="provider">The dependency injection provider.</param>
    public NostaleLocalClient
    (
        NetworkBinding networkBinding,
        PlayerManagerBinding playerManagerBinding,
        ControlCommands controlCommands,
        CommandProcessor commandProcessor,
        IPacketSerializer packetSerializer,
        IPacketHandler packetHandler,
        ILogger<NostaleLocalClient> logger,
        IOptions<LocalClientOptions> options,
        IServiceProvider provider
    )
        : base(commandProcessor, packetSerializer)
    {
        _options = options.Value;
        _networkBinding = networkBinding;
        _playerManagerBinding = playerManagerBinding;
        _controlCommands = controlCommands;
        _packetSerializer = packetSerializer;
        _packetHandler = packetHandler;
        _logger = logger;
        _provider = provider;
    }

    /// <inheritdoc />
    public override async Task<Result> RunAsync(CancellationToken stopRequested = default)
    {
        _stopRequested = stopRequested;
        _logger.LogInformation("Starting local client");
        _networkBinding.PacketSend += SendCallback;
        _networkBinding.PacketReceive += ReceiveCallback;

        _playerManagerBinding.FollowEntityCall += FollowEntity;
        _playerManagerBinding.WalkCall += Walk;

        try
        {
            await Task.Delay(-1, stopRequested);
        }
        catch
        {
            // ignored
        }

        _networkBinding.PacketSend -= SendCallback;
        _networkBinding.PacketReceive -= ReceiveCallback;
        _playerManagerBinding.FollowEntityCall -= FollowEntity;
        _playerManagerBinding.WalkCall -= Walk;

        return Result.FromSuccess();
    }

    /// <inheritdoc />
    public override Task<Result> ReceivePacketAsync(string packetString, CancellationToken ct = default)
    {
        ReceivePacket(packetString);
        return Task.FromResult(Result.FromSuccess());
    }

    /// <inheritdoc />
    public override Task<Result> SendPacketAsync(string packetString, CancellationToken ct = default)
    {
        SendPacket(packetString);
        return Task.FromResult(Result.FromSuccess());
    }

    private bool ReceiveCallback(string packet)
    {
        bool accepted = true;
        if (_options.AllowIntercept)
        {
            if (_interceptor is null)
            {
                _interceptor = _provider.GetRequiredService<IPacketInterceptor>();
            }

            accepted = _interceptor.InterceptReceive(ref packet);
        }

        Task.Run(async () => await ProcessPacketAsync(PacketSource.Server, packet));

        return accepted;
    }

    private bool SendCallback(string packet)
    {
        bool accepted = true;
        if (_options.AllowIntercept)
        {
            if (_interceptor is null)
            {
                _interceptor = _provider.GetRequiredService<IPacketInterceptor>();
            }

            accepted = _interceptor.InterceptSend(ref packet);
        }

        Task.Run(async () => await ProcessPacketAsync(PacketSource.Client, packet));

        return accepted;
    }

    private void SendPacket(string packetString)
    {
        _networkBinding.SendPacket(packetString);
        _logger.LogDebug($"Sending client packet {packetString}");
    }

    private void ReceivePacket(string packetString)
    {
        _networkBinding.ReceivePacket(packetString);
        _logger.LogDebug($"Receiving client packet {packetString}");
    }

    private async Task ProcessPacketAsync(PacketSource type, string packetString)
    {
        var packet = _packetSerializer.Deserialize(packetString, type);
        if (!packet.IsSuccess)
        {
            if (packet.Error is not PacketConverterNotFoundError)
            {
                _logger.LogWarning("Could not parse {Packet}. Reason:", packetString);
                _logger.LogResultError(packet);
            }

            packet = new ParsingFailedPacket(packet, packetString);
        }

        Result result;
        if (type == PacketSource.Server)
        {
            result = await _packetHandler.HandleReceivedPacketAsync
                (packet.Entity, packetString, _stopRequested ?? default);
        }
        else
        {
            result = await _packetHandler.HandleSentPacketAsync(packet.Entity, packetString, _stopRequested ?? default);
        }

        if (!result.IsSuccess)
        {
            _logger.LogError("There was an error whilst handling packet");
            _logger.LogResultError(result);
        }
    }

    private bool FollowEntity(MapBaseObj? obj)
    {
        Task.Run
        (
            async () => await _controlCommands.CancelAsync
                (ControlCommandsFilter.UserCancellable, false, (CancellationToken)_stopRequested!)
        );
        return true;
    }

    private bool Walk(ushort x, ushort y)
    {
        return _controlCommands.AllowUserActions;
    }
}
\ No newline at end of file

D Local/NosSmooth.LocalClient/NostaleWindow.cs => Local/NosSmooth.LocalClient/NostaleWindow.cs +0 -58
@@ 1,58 0,0 @@
//
//  NostaleWindow.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.LocalClient.Utils;

namespace NosSmooth.LocalClient;

/// <summary>
/// Represents window of nostale client.
/// </summary>
public class NostaleWindow
{
    private const int WM_KEYDOWN = 0x0100;
    private const int WM_KEYUP = 0x0101;
    private const int WM_CHAR = 0x0102;

    /// <summary>
    /// Initializes a new instance of the <see cref="NostaleWindow"/> class.
    /// </summary>
    /// <param name="handle">The handle of the window.</param>
    public NostaleWindow(IntPtr handle) => Handle = handle;

    /// <summary>
    /// Gets the window handle.
    /// </summary>
    public IntPtr Handle { get; }

    /// <summary>
    /// Changes the title of the window.
    /// </summary>
    /// <param name="name">The new name of the window.</param>
    public void Rename(string name)
    {
        User32.SetWindowText(Handle, name);
    }

    /// <summary>
    /// Bring the window to front.
    /// </summary>
    public void BringToFront()
    {
        User32.SetForegroundWindow(Handle);
    }

    /// <summary>
    /// Send the given key to the window.
    /// </summary>
    /// <param name="key">The id of the key.</param>
    public void SendKey(uint key)
    {
        User32.PostMessage(Handle, WM_KEYDOWN, key, 0);
        User32.PostMessage(Handle, WM_CHAR, key, 0);
        User32.PostMessage(Handle, WM_KEYUP, key, 0);
    }
}
\ No newline at end of file

D Local/NosSmooth.LocalClient/Utils/User32.cs => Local/NosSmooth.LocalClient/Utils/User32.cs +0 -88
@@ 1,88 0,0 @@
//
//  User32.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 System.Runtime.InteropServices;
using System.Text;
#pragma warning disable CS1591

namespace NosSmooth.LocalClient.Utils;

/// <summary>
/// Represents class with extern calls to user32.dll.
/// </summary>
[SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600", MessageId = "Elements should be documented", Justification = "user32.dll methods do not need documentation, it can be found on msdn.")]
public class User32
{
    public delegate bool EnumWindowsProc(IntPtr hWnd, IntPtr lParam);

    [DllImport("user32.dll")]
    public static extern int PostMessage(IntPtr hWnd, int uMsg, uint wParam, uint lParam);

    [DllImport("user32.dll")]
    public static extern IntPtr FindWindow(string lpClassName, string lpWindowName);

    [DllImport("user32.dll")]
    public static extern bool SetForegroundWindow(IntPtr hWnd);

    [DllImport("user32.dll")]
    public static extern bool SetWindowText(IntPtr hWnd, string text);

    [DllImport("user32.dll")]
    public static extern int EnumWindows(EnumWindowsProc callback, IntPtr lParam);

    [DllImport("user32.dll")]
    public static extern int GetWindowText(IntPtr hWnd, StringBuilder text, int count);

    [DllImport("user32.dll", CharSet = CharSet.Unicode)]
    public static extern int GetWindowTextLength(IntPtr hWnd);

    [DllImport("user32.dll")]
    public static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint processId);

    /// <summary>
    /// Finds all windows with the given title matching.
    /// </summary>
    /// <param name="title">The title to match.</param>
    /// <returns>The matched windows.</returns>
    public static IEnumerable<IntPtr> FindWindowsWithTitle(string title)
    {
        var windows = new List<IntPtr>();
        EnumWindows(
            (hWnd, lParam) =>
            {
                string windowTitle = GetWindowTitle(hWnd);
                if (windowTitle.Equals(title))
                {
                    windows.Add(hWnd);
                }

                return true;
            },
            IntPtr.Zero
        );

        return windows;
    }

    /// <summary>
    /// Returns the title of a window.
    /// </summary>
    /// <param name="hWnd">The handle of the window.</param>
    /// <returns>The title of the window.</returns>
    public static string GetWindowTitle(IntPtr hWnd)
    {
        int size = GetWindowTextLength(hWnd);
        if (size == 0)
        {
            return string.Empty;
        }

        var sb = new StringBuilder(size + 1);
        GetWindowText(hWnd, sb, sb.Capacity);
        return sb.ToString();
    }
}
\ No newline at end of file

D NosSmooth.Unix.sln => NosSmooth.Unix.sln +0 -191
@@ 1,191 0,0 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.0.32014.148
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NosSmooth.Core", "Core\NosSmooth.Core\NosSmooth.Core.csproj", "{8CCDB0CD-DFB2-492E-B1A2-8DD030F923D3}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NosSmooth.Game", "Core\NosSmooth.Game\NosSmooth.Game.csproj", "{19666500-4636-4400-8855-496317F4A7F7}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Samples", "Samples", "{F20FE754-FDEA-4F3A-93D4-0750CB9EBB33}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Remote", "Remote", "{F9EFA63C-0A88-45EB-B36F-C4A9D63BDFD1}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NosSmooth.Cryptography", "Remote\NosSmooth.Cryptography\NosSmooth.Cryptography.csproj", "{05E3039D-EDF6-4CDC-B062-CB67760ACB5F}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NosSmooth.Language", "Core\NosSmooth.Language\NosSmooth.Language.csproj", "{06F03B1F-F68A-4C5B-B7FE-128A7CE1A1D7}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NosSmooth.Extensions", "Core\NosSmooth.Extensions\NosSmooth.Extensions.csproj", "{B6C23EA5-55FE-436A-96EA-FE2974C5881D}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Core", "Core", "{01B5E872-271F-4D30-A1AA-AD48D81840C5}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".metadata", ".metadata", "{FA63BCED-9D81-4FF7-BA75-A6F3BA31ECDE}"
	ProjectSection(SolutionItems) = preProject
		Directory.Build.props = Directory.Build.props
		stylecop.ruleset = stylecop.ruleset
		stylecop.json = stylecop.json
	EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{C6A8760D-92CB-4307-88A7-36CCAEBA4AD1}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NosSmooth.Packets.Tests", "Tests\NosSmooth.Packets.Tests\NosSmooth.Packets.Tests.csproj", "{CE7EE14A-BCC5-4A8B-8D9F-403BF09BD99A}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Packets", "Packets", "{1EBECD64-77C3-4D5B-BAD1-C2803E2FBD5E}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NosSmooth.Packets", "Packets\NosSmooth.Packets\NosSmooth.Packets.csproj", "{583D3651-E13F-481F-A9D9-A093DFCDB10F}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NosSmooth.PacketSerializer", "Packets\NosSmooth.PacketSerializer\NosSmooth.PacketSerializer.csproj", "{89509BB0-3618-4F8A-8D51-4B251BD13CD4}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NosSmooth.PacketSerializer.Abstractions", "Packets\NosSmooth.PacketSerializer.Abstractions\NosSmooth.PacketSerializer.Abstractions.csproj", "{77A62552-25AB-43FD-BFBA-5B5FD6F066A7}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NosSmooth.PacketSerializersGenerator", "Packets\NosSmooth.PacketSerializersGenerator\NosSmooth.PacketSerializersGenerator.csproj", "{CE4EB82B-7AAD-4689-BB13-557B7EFF4290}"
EndProject
Global
	GlobalSection(SolutionConfigurationPlatforms) = preSolution
		Debug|Any CPU = Debug|Any CPU
		Debug|x64 = Debug|x64
		Debug|x86 = Debug|x86
		Release|Any CPU = Release|Any CPU
		Release|x64 = Release|x64
		Release|x86 = Release|x86
	EndGlobalSection
	GlobalSection(ProjectConfigurationPlatforms) = postSolution
		{8CCDB0CD-DFB2-492E-B1A2-8DD030F923D3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
		{8CCDB0CD-DFB2-492E-B1A2-8DD030F923D3}.Debug|Any CPU.Build.0 = Debug|Any CPU
		{8CCDB0CD-DFB2-492E-B1A2-8DD030F923D3}.Debug|x64.ActiveCfg = Debug|Any CPU
		{8CCDB0CD-DFB2-492E-B1A2-8DD030F923D3}.Debug|x64.Build.0 = Debug|Any CPU
		{8CCDB0CD-DFB2-492E-B1A2-8DD030F923D3}.Debug|x86.ActiveCfg = Debug|Any CPU
		{8CCDB0CD-DFB2-492E-B1A2-8DD030F923D3}.Debug|x86.Build.0 = Debug|Any CPU
		{8CCDB0CD-DFB2-492E-B1A2-8DD030F923D3}.Release|Any CPU.ActiveCfg = Release|Any CPU
		{8CCDB0CD-DFB2-492E-B1A2-8DD030F923D3}.Release|Any CPU.Build.0 = Release|Any CPU
		{8CCDB0CD-DFB2-492E-B1A2-8DD030F923D3}.Release|x64.ActiveCfg = Release|Any CPU
		{8CCDB0CD-DFB2-492E-B1A2-8DD030F923D3}.Release|x64.Build.0 = Release|Any CPU
		{8CCDB0CD-DFB2-492E-B1A2-8DD030F923D3}.Release|x86.ActiveCfg = Release|Any CPU
		{8CCDB0CD-DFB2-492E-B1A2-8DD030F923D3}.Release|x86.Build.0 = Release|Any CPU
		{19666500-4636-4400-8855-496317F4A7F7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
		{19666500-4636-4400-8855-496317F4A7F7}.Debug|Any CPU.Build.0 = Debug|Any CPU
		{19666500-4636-4400-8855-496317F4A7F7}.Debug|x64.ActiveCfg = Debug|Any CPU
		{19666500-4636-4400-8855-496317F4A7F7}.Debug|x64.Build.0 = Debug|Any CPU
		{19666500-4636-4400-8855-496317F4A7F7}.Debug|x86.ActiveCfg = Debug|Any CPU
		{19666500-4636-4400-8855-496317F4A7F7}.Debug|x86.Build.0 = Debug|Any CPU
		{19666500-4636-4400-8855-496317F4A7F7}.Release|Any CPU.ActiveCfg = Release|Any CPU
		{19666500-4636-4400-8855-496317F4A7F7}.Release|Any CPU.Build.0 = Release|Any CPU
		{19666500-4636-4400-8855-496317F4A7F7}.Release|x64.ActiveCfg = Release|Any CPU
		{19666500-4636-4400-8855-496317F4A7F7}.Release|x64.Build.0 = Release|Any CPU
		{19666500-4636-4400-8855-496317F4A7F7}.Release|x86.ActiveCfg = Release|Any CPU
		{19666500-4636-4400-8855-496317F4A7F7}.Release|x86.Build.0 = Release|Any CPU
		{05E3039D-EDF6-4CDC-B062-CB67760ACB5F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
		{05E3039D-EDF6-4CDC-B062-CB67760ACB5F}.Debug|Any CPU.Build.0 = Debug|Any CPU
		{05E3039D-EDF6-4CDC-B062-CB67760ACB5F}.Debug|x64.ActiveCfg = Debug|Any CPU
		{05E3039D-EDF6-4CDC-B062-CB67760ACB5F}.Debug|x64.Build.0 = Debug|Any CPU
		{05E3039D-EDF6-4CDC-B062-CB67760ACB5F}.Debug|x86.ActiveCfg = Debug|Any CPU
		{05E3039D-EDF6-4CDC-B062-CB67760ACB5F}.Debug|x86.Build.0 = Debug|Any CPU
		{05E3039D-EDF6-4CDC-B062-CB67760ACB5F}.Release|Any CPU.ActiveCfg = Release|Any CPU
		{05E3039D-EDF6-4CDC-B062-CB67760ACB5F}.Release|Any CPU.Build.0 = Release|Any CPU
		{05E3039D-EDF6-4CDC-B062-CB67760ACB5F}.Release|x64.ActiveCfg = Release|Any CPU
		{05E3039D-EDF6-4CDC-B062-CB67760ACB5F}.Release|x64.Build.0 = Release|Any CPU
		{05E3039D-EDF6-4CDC-B062-CB67760ACB5F}.Release|x86.ActiveCfg = Release|Any CPU
		{05E3039D-EDF6-4CDC-B062-CB67760ACB5F}.Release|x86.Build.0 = Release|Any CPU
		{06F03B1F-F68A-4C5B-B7FE-128A7CE1A1D7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
		{06F03B1F-F68A-4C5B-B7FE-128A7CE1A1D7}.Debug|Any CPU.Build.0 = Debug|Any CPU
		{06F03B1F-F68A-4C5B-B7FE-128A7CE1A1D7}.Debug|x64.ActiveCfg = Debug|Any CPU
		{06F03B1F-F68A-4C5B-B7FE-128A7CE1A1D7}.Debug|x64.Build.0 = Debug|Any CPU
		{06F03B1F-F68A-4C5B-B7FE-128A7CE1A1D7}.Debug|x86.ActiveCfg = Debug|Any CPU
		{06F03B1F-F68A-4C5B-B7FE-128A7CE1A1D7}.Debug|x86.Build.0 = Debug|Any CPU
		{06F03B1F-F68A-4C5B-B7FE-128A7CE1A1D7}.Release|Any CPU.ActiveCfg = Release|Any CPU
		{06F03B1F-F68A-4C5B-B7FE-128A7CE1A1D7}.Release|Any CPU.Build.0 = Release|Any CPU
		{06F03B1F-F68A-4C5B-B7FE-128A7CE1A1D7}.Release|x64.ActiveCfg = Release|Any CPU
		{06F03B1F-F68A-4C5B-B7FE-128A7CE1A1D7}.Release|x64.Build.0 = Release|Any CPU
		{06F03B1F-F68A-4C5B-B7FE-128A7CE1A1D7}.Release|x86.ActiveCfg = Release|Any CPU
		{06F03B1F-F68A-4C5B-B7FE-128A7CE1A1D7}.Release|x86.Build.0 = Release|Any CPU
		{B6C23EA5-55FE-436A-96EA-FE2974C5881D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
		{B6C23EA5-55FE-436A-96EA-FE2974C5881D}.Debug|Any CPU.Build.0 = Debug|Any CPU
		{B6C23EA5-55FE-436A-96EA-FE2974C5881D}.Debug|x64.ActiveCfg = Debug|Any CPU
		{B6C23EA5-55FE-436A-96EA-FE2974C5881D}.Debug|x64.Build.0 = Debug|Any CPU
		{B6C23EA5-55FE-436A-96EA-FE2974C5881D}.Debug|x86.ActiveCfg = Debug|Any CPU
		{B6C23EA5-55FE-436A-96EA-FE2974C5881D}.Debug|x86.Build.0 = Debug|Any CPU
		{B6C23EA5-55FE-436A-96EA-FE2974C5881D}.Release|Any CPU.ActiveCfg = Release|Any CPU
		{B6C23EA5-55FE-436A-96EA-FE2974C5881D}.Release|Any CPU.Build.0 = Release|Any CPU
		{B6C23EA5-55FE-436A-96EA-FE2974C5881D}.Release|x64.ActiveCfg = Release|Any CPU
		{B6C23EA5-55FE-436A-96EA-FE2974C5881D}.Release|x64.Build.0 = Release|Any CPU
		{B6C23EA5-55FE-436A-96EA-FE2974C5881D}.Release|x86.ActiveCfg = Release|Any CPU
		{B6C23EA5-55FE-436A-96EA-FE2974C5881D}.Release|x86.Build.0 = Release|Any CPU
		{CE7EE14A-BCC5-4A8B-8D9F-403BF09BD99A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
		{CE7EE14A-BCC5-4A8B-8D9F-403BF09BD99A}.Debug|Any CPU.Build.0 = Debug|Any CPU
		{CE7EE14A-BCC5-4A8B-8D9F-403BF09BD99A}.Debug|x64.ActiveCfg = Debug|Any CPU
		{CE7EE14A-BCC5-4A8B-8D9F-403BF09BD99A}.Debug|x64.Build.0 = Debug|Any CPU
		{CE7EE14A-BCC5-4A8B-8D9F-403BF09BD99A}.Debug|x86.ActiveCfg = Debug|Any CPU
		{CE7EE14A-BCC5-4A8B-8D9F-403BF09BD99A}.Debug|x86.Build.0 = Debug|Any CPU
		{CE7EE14A-BCC5-4A8B-8D9F-403BF09BD99A}.Release|Any CPU.ActiveCfg = Release|Any CPU
		{CE7EE14A-BCC5-4A8B-8D9F-403BF09BD99A}.Release|Any CPU.Build.0 = Release|Any CPU
		{CE7EE14A-BCC5-4A8B-8D9F-403BF09BD99A}.Release|x64.ActiveCfg = Release|Any CPU
		{CE7EE14A-BCC5-4A8B-8D9F-403BF09BD99A}.Release|x64.Build.0 = Release|Any CPU
		{CE7EE14A-BCC5-4A8B-8D9F-403BF09BD99A}.Release|x86.ActiveCfg = Release|Any CPU
		{CE7EE14A-BCC5-4A8B-8D9F-403BF09BD99A}.Release|x86.Build.0 = Release|Any CPU
		{583D3651-E13F-481F-A9D9-A093DFCDB10F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
		{583D3651-E13F-481F-A9D9-A093DFCDB10F}.Debug|Any CPU.Build.0 = Debug|Any CPU
		{583D3651-E13F-481F-A9D9-A093DFCDB10F}.Debug|x64.ActiveCfg = Debug|Any CPU
		{583D3651-E13F-481F-A9D9-A093DFCDB10F}.Debug|x64.Build.0 = Debug|Any CPU
		{583D3651-E13F-481F-A9D9-A093DFCDB10F}.Debug|x86.ActiveCfg = Debug|Any CPU
		{583D3651-E13F-481F-A9D9-A093DFCDB10F}.Debug|x86.Build.0 = Debug|Any CPU
		{583D3651-E13F-481F-A9D9-A093DFCDB10F}.Release|Any CPU.ActiveCfg = Release|Any CPU
		{583D3651-E13F-481F-A9D9-A093DFCDB10F}.Release|Any CPU.Build.0 = Release|Any CPU
		{583D3651-E13F-481F-A9D9-A093DFCDB10F}.Release|x64.ActiveCfg = Release|Any CPU
		{583D3651-E13F-481F-A9D9-A093DFCDB10F}.Release|x64.Build.0 = Release|Any CPU
		{583D3651-E13F-481F-A9D9-A093DFCDB10F}.Release|x86.ActiveCfg = Release|Any CPU
		{583D3651-E13F-481F-A9D9-A093DFCDB10F}.Release|x86.Build.0 = Release|Any CPU
		{89509BB0-3618-4F8A-8D51-4B251BD13CD4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
		{89509BB0-3618-4F8A-8D51-4B251BD13CD4}.Debug|Any CPU.Build.0 = Debug|Any CPU
		{89509BB0-3618-4F8A-8D51-4B251BD13CD4}.Debug|x64.ActiveCfg = Debug|Any CPU
		{89509BB0-3618-4F8A-8D51-4B251BD13CD4}.Debug|x64.Build.0 = Debug|Any CPU
		{89509BB0-3618-4F8A-8D51-4B251BD13CD4}.Debug|x86.ActiveCfg = Debug|Any CPU
		{89509BB0-3618-4F8A-8D51-4B251BD13CD4}.Debug|x86.Build.0 = Debug|Any CPU
		{89509BB0-3618-4F8A-8D51-4B251BD13CD4}.Release|Any CPU.ActiveCfg = Release|Any CPU
		{89509BB0-3618-4F8A-8D51-4B251BD13CD4}.Release|Any CPU.Build.0 = Release|Any CPU
		{89509BB0-3618-4F8A-8D51-4B251BD13CD4}.Release|x64.ActiveCfg = Release|Any CPU
		{89509BB0-3618-4F8A-8D51-4B251BD13CD4}.Release|x64.Build.0 = Release|Any CPU
		{89509BB0-3618-4F8A-8D51-4B251BD13CD4}.Release|x86.ActiveCfg = Release|Any CPU
		{89509BB0-3618-4F8A-8D51-4B251BD13CD4}.Release|x86.Build.0 = Release|Any CPU
		{77A62552-25AB-43FD-BFBA-5B5FD6F066A7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
		{77A62552-25AB-43FD-BFBA-5B5FD6F066A7}.Debug|Any CPU.Build.0 = Debug|Any CPU
		{77A62552-25AB-43FD-BFBA-5B5FD6F066A7}.Debug|x64.ActiveCfg = Debug|Any CPU
		{77A62552-25AB-43FD-BFBA-5B5FD6F066A7}.Debug|x64.Build.0 = Debug|Any CPU
		{77A62552-25AB-43FD-BFBA-5B5FD6F066A7}.Debug|x86.ActiveCfg = Debug|Any CPU
		{77A62552-25AB-43FD-BFBA-5B5FD6F066A7}.Debug|x86.Build.0 = Debug|Any CPU
		{77A62552-25AB-43FD-BFBA-5B5FD6F066A7}.Release|Any CPU.ActiveCfg = Release|Any CPU
		{77A62552-25AB-43FD-BFBA-5B5FD6F066A7}.Release|Any CPU.Build.0 = Release|Any CPU
		{77A62552-25AB-43FD-BFBA-5B5FD6F066A7}.Release|x64.ActiveCfg = Release|Any CPU
		{77A62552-25AB-43FD-BFBA-5B5FD6F066A7}.Release|x64.Build.0 = Release|Any CPU
		{77A62552-25AB-43FD-BFBA-5B5FD6F066A7}.Release|x86.ActiveCfg = Release|Any CPU
		{77A62552-25AB-43FD-BFBA-5B5FD6F066A7}.Release|x86.Build.0 = Release|Any CPU
		{CE4EB82B-7AAD-4689-BB13-557B7EFF4290}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
		{CE4EB82B-7AAD-4689-BB13-557B7EFF4290}.Debug|Any CPU.Build.0 = Debug|Any CPU
		{CE4EB82B-7AAD-4689-BB13-557B7EFF4290}.Debug|x64.ActiveCfg = Debug|Any CPU
		{CE4EB82B-7AAD-4689-BB13-557B7EFF4290}.Debug|x64.Build.0 = Debug|Any CPU
		{CE4EB82B-7AAD-4689-BB13-557B7EFF4290}.Debug|x86.ActiveCfg = Debug|Any CPU
		{CE4EB82B-7AAD-4689-BB13-557B7EFF4290}.Debug|x86.Build.0 = Debug|Any CPU
		{CE4EB82B-7AAD-4689-BB13-557B7EFF4290}.Release|Any CPU.ActiveCfg = Release|Any CPU
		{CE4EB82B-7AAD-4689-BB13-557B7EFF4290}.Release|Any CPU.Build.0 = Release|Any CPU
		{CE4EB82B-7AAD-4689-BB13-557B7EFF4290}.Release|x64.ActiveCfg = Release|Any CPU
		{CE4EB82B-7AAD-4689-BB13-557B7EFF4290}.Release|x64.Build.0 = Release|Any CPU
		{CE4EB82B-7AAD-4689-BB13-557B7EFF4290}.Release|x86.ActiveCfg = Release|Any CPU
		{CE4EB82B-7AAD-4689-BB13-557B7EFF4290}.Release|x86.Build.0 = Release|Any CPU
	EndGlobalSection
	GlobalSection(SolutionProperties) = preSolution
		HideSolutionNode = FALSE
	EndGlobalSection
	GlobalSection(NestedProjects) = preSolution
		{8CCDB0CD-DFB2-492E-B1A2-8DD030F923D3} = {01B5E872-271F-4D30-A1AA-AD48D81840C5}
		{19666500-4636-4400-8855-496317F4A7F7} = {01B5E872-271F-4D30-A1AA-AD48D81840C5}
		{05E3039D-EDF6-4CDC-B062-CB67760ACB5F} = {F9EFA63C-0A88-45EB-B36F-C4A9D63BDFD1}
		{06F03B1F-F68A-4C5B-B7FE-128A7CE1A1D7} = {01B5E872-271F-4D30-A1AA-AD48D81840C5}
		{CE7EE14A-BCC5-4A8B-8D9F-403BF09BD99A} = {C6A8760D-92CB-4307-88A7-36CCAEBA4AD1}
		{583D3651-E13F-481F-A9D9-A093DFCDB10F} = {1EBECD64-77C3-4D5B-BAD1-C2803E2FBD5E}
		{89509BB0-3618-4F8A-8D51-4B251BD13CD4} = {1EBECD64-77C3-4D5B-BAD1-C2803E2FBD5E}
		{77A62552-25AB-43FD-BFBA-5B5FD6F066A7} = {1EBECD64-77C3-4D5B-BAD1-C2803E2FBD5E}
		{CE4EB82B-7AAD-4689-BB13-557B7EFF4290} = {1EBECD64-77C3-4D5B-BAD1-C2803E2FBD5E}
	EndGlobalSection
	GlobalSection(ExtensibilityGlobals) = postSolution
		SolutionGuid = {C5F46653-4DEC-429B-8580-4ED18ED9B4CA}
	EndGlobalSection
EndGlobal

D NosSmooth.Unix.sln.DotSettings => NosSmooth.Unix.sln.DotSettings +0 -6
@@ 1,6 0,0 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
	<s:String x:Key="/Default/CodeStyle/FileHeader/FileHeaderText/@EntryValue">
 $FILENAME$

 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.</s:String></wpf:ResourceDictionary>
\ No newline at end of file

M NosSmooth.sln => NosSmooth.sln +0 -187
@@ 5,16 5,6 @@ VisualStudioVersion = 17.0.32014.148
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NosSmooth.Core", "Core\NosSmooth.Core\NosSmooth.Core.csproj", "{8CCDB0CD-DFB2-492E-B1A2-8DD030F923D3}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NosSmooth.Game", "Core\NosSmooth.Game\NosSmooth.Game.csproj", "{19666500-4636-4400-8855-496317F4A7F7}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Samples", "Samples", "{F20FE754-FDEA-4F3A-93D4-0750CB9EBB33}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Local", "Local", "{6078AE6E-7CD0-48E4-84E0-EB164D8881DA}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NosSmooth.LocalClient", "Local\NosSmooth.LocalClient\NosSmooth.LocalClient.csproj", "{46D0A205-CA30-41CC-9A80-10B543FBD344}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NosSmooth.Language", "Core\NosSmooth.Language\NosSmooth.Language.csproj", "{06F03B1F-F68A-4C5B-B7FE-128A7CE1A1D7}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Core", "Core", "{01B5E872-271F-4D30-A1AA-AD48D81840C5}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".metadata", ".metadata", "{FA63BCED-9D81-4FF7-BA75-A6F3BA31ECDE}"


@@ 22,16 12,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".metadata", ".metadata", "{
		Directory.build.props = Directory.build.props
	EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SimpleChat", "Samples\SimpleChat\SimpleChat.csproj", "{4017A4F4-5E59-48AA-A7D0-A8518148933A}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{C6A8760D-92CB-4307-88A7-36CCAEBA4AD1}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "InterceptNameChanger", "Samples\InterceptNameChanger\InterceptNameChanger.csproj", "{F96F3AA0-131E-4B6B-AB21-BBE2DEBCEF3A}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "LowLevel", "LowLevel", "{9025731C-084E-4E82-8CD4-0F52D3AA1F54}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WalkCommands", "Samples\WalkCommands\WalkCommands.csproj", "{18A62EF6-ADDA-4224-90AB-2D5DCFC95D3E}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NosSmooth.Core.Tests", "Tests\NosSmooth.Core.Tests\NosSmooth.Core.Tests.csproj", "{1A10C624-48E5-425D-938E-31A4CC7AC687}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NosSmooth.PacketSerializer", "Packets\NosSmooth.PacketSerializer\NosSmooth.PacketSerializer.csproj", "{C4DAFD83-C6DC-4597-AA1F-BA2F3ABB612C}"


@@ 40,24 22,12 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NosSmooth.PacketSerializers
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NosSmooth.Packets.Tests", "Tests\NosSmooth.Packets.Tests\NosSmooth.Packets.Tests.csproj", "{9BC56B40-64E3-4A8F-AD49-C52857A35026}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "NosSmooth.Inject", "Local\NosSmooth.Inject\NosSmooth.Inject.vcxproj", "{CA2873D8-BD0B-4583-818D-B94A3C2ABBA3}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NosSmooth.Injector.CLI", "Local\NosSmooth.Injector.CLI\NosSmooth.Injector.CLI.csproj", "{5D351C91-C631-40F6-82D2-F8D68468B076}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NosSmooth.Injector", "Local\NosSmooth.Injector\NosSmooth.Injector.csproj", "{7B68DE7D-159B-42A7-8CF8-69F156CD04DE}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NosSmooth.LocalBinding", "Local\NosSmooth.LocalBinding\NosSmooth.LocalBinding.csproj", "{BC0DCD32-E917-4187-8CF3-970D17301ED3}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Packets", "Packets", "{54A49AC2-55B3-4156-8023-41C56719EBB5}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NosSmooth.Packets", "Packets\NosSmooth.Packets\NosSmooth.Packets.csproj", "{86B4ED0C-CD28-4C6C-B58E-B4B1F7AAD683}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NosSmooth.PacketSerializer.Abstractions", "Packets\NosSmooth.PacketSerializer.Abstractions\NosSmooth.PacketSerializer.Abstractions.csproj", "{CF03BCEA-EB5B-427F-8576-7DA7EB869BDC}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NosSmooth.ChatCommands", "Local\NosSmooth.ChatCommands\NosSmooth.ChatCommands.csproj", "{7DC7FC22-EFA6-4EB0-8E75-99F746E50F6E}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ExternalBrowser", "Samples\ExternalBrowser\ExternalBrowser.csproj", "{A3BCC882-6DCD-4326-8BC2-A5C78AD10361}"
EndProject
Global
	GlobalSection(SolutionConfigurationPlatforms) = preSolution
		Debug|Any CPU = Debug|Any CPU


@@ 80,78 50,6 @@ Global
		{8CCDB0CD-DFB2-492E-B1A2-8DD030F923D3}.Release|x64.Build.0 = Release|Any CPU
		{8CCDB0CD-DFB2-492E-B1A2-8DD030F923D3}.Release|x86.ActiveCfg = Release|Any CPU
		{8CCDB0CD-DFB2-492E-B1A2-8DD030F923D3}.Release|x86.Build.0 = Release|Any CPU
		{19666500-4636-4400-8855-496317F4A7F7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
		{19666500-4636-4400-8855-496317F4A7F7}.Debug|Any CPU.Build.0 = Debug|Any CPU
		{19666500-4636-4400-8855-496317F4A7F7}.Debug|x64.ActiveCfg = Debug|Any CPU
		{19666500-4636-4400-8855-496317F4A7F7}.Debug|x64.Build.0 = Debug|Any CPU
		{19666500-4636-4400-8855-496317F4A7F7}.Debug|x86.ActiveCfg = Debug|Any CPU
		{19666500-4636-4400-8855-496317F4A7F7}.Debug|x86.Build.0 = Debug|Any CPU
		{19666500-4636-4400-8855-496317F4A7F7}.Release|Any CPU.ActiveCfg = Release|Any CPU
		{19666500-4636-4400-8855-496317F4A7F7}.Release|Any CPU.Build.0 = Release|Any CPU
		{19666500-4636-4400-8855-496317F4A7F7}.Release|x64.ActiveCfg = Release|Any CPU
		{19666500-4636-4400-8855-496317F4A7F7}.Release|x64.Build.0 = Release|Any CPU
		{19666500-4636-4400-8855-496317F4A7F7}.Release|x86.ActiveCfg = Release|Any CPU
		{19666500-4636-4400-8855-496317F4A7F7}.Release|x86.Build.0 = Release|Any CPU
		{46D0A205-CA30-41CC-9A80-10B543FBD344}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
		{46D0A205-CA30-41CC-9A80-10B543FBD344}.Debug|Any CPU.Build.0 = Debug|Any CPU
		{46D0A205-CA30-41CC-9A80-10B543FBD344}.Debug|x64.ActiveCfg = Debug|Any CPU
		{46D0A205-CA30-41CC-9A80-10B543FBD344}.Debug|x64.Build.0 = Debug|Any CPU
		{46D0A205-CA30-41CC-9A80-10B543FBD344}.Debug|x86.ActiveCfg = Debug|Any CPU
		{46D0A205-CA30-41CC-9A80-10B543FBD344}.Debug|x86.Build.0 = Debug|Any CPU
		{46D0A205-CA30-41CC-9A80-10B543FBD344}.Release|Any CPU.ActiveCfg = Release|Any CPU
		{46D0A205-CA30-41CC-9A80-10B543FBD344}.Release|Any CPU.Build.0 = Release|Any CPU
		{46D0A205-CA30-41CC-9A80-10B543FBD344}.Release|x64.ActiveCfg = Release|Any CPU
		{46D0A205-CA30-41CC-9A80-10B543FBD344}.Release|x64.Build.0 = Release|Any CPU
		{46D0A205-CA30-41CC-9A80-10B543FBD344}.Release|x86.ActiveCfg = Release|Any CPU
		{46D0A205-CA30-41CC-9A80-10B543FBD344}.Release|x86.Build.0 = Release|Any CPU
		{06F03B1F-F68A-4C5B-B7FE-128A7CE1A1D7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
		{06F03B1F-F68A-4C5B-B7FE-128A7CE1A1D7}.Debug|Any CPU.Build.0 = Debug|Any CPU
		{06F03B1F-F68A-4C5B-B7FE-128A7CE1A1D7}.Debug|x64.ActiveCfg = Debug|Any CPU
		{06F03B1F-F68A-4C5B-B7FE-128A7CE1A1D7}.Debug|x64.Build.0 = Debug|Any CPU
		{06F03B1F-F68A-4C5B-B7FE-128A7CE1A1D7}.Debug|x86.ActiveCfg = Debug|Any CPU
		{06F03B1F-F68A-4C5B-B7FE-128A7CE1A1D7}.Debug|x86.Build.0 = Debug|Any CPU
		{06F03B1F-F68A-4C5B-B7FE-128A7CE1A1D7}.Release|Any CPU.ActiveCfg = Release|Any CPU
		{06F03B1F-F68A-4C5B-B7FE-128A7CE1A1D7}.Release|Any CPU.Build.0 = Release|Any CPU
		{06F03B1F-F68A-4C5B-B7FE-128A7CE1A1D7}.Release|x64.ActiveCfg = Release|Any CPU
		{06F03B1F-F68A-4C5B-B7FE-128A7CE1A1D7}.Release|x64.Build.0 = Release|Any CPU
		{06F03B1F-F68A-4C5B-B7FE-128A7CE1A1D7}.Release|x86.ActiveCfg = Release|Any CPU
		{06F03B1F-F68A-4C5B-B7FE-128A7CE1A1D7}.Release|x86.Build.0 = Release|Any CPU
		{4017A4F4-5E59-48AA-A7D0-A8518148933A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
		{4017A4F4-5E59-48AA-A7D0-A8518148933A}.Debug|Any CPU.Build.0 = Debug|Any CPU
		{4017A4F4-5E59-48AA-A7D0-A8518148933A}.Debug|x64.ActiveCfg = Debug|Any CPU
		{4017A4F4-5E59-48AA-A7D0-A8518148933A}.Debug|x64.Build.0 = Debug|Any CPU
		{4017A4F4-5E59-48AA-A7D0-A8518148933A}.Debug|x86.ActiveCfg = Debug|Any CPU
		{4017A4F4-5E59-48AA-A7D0-A8518148933A}.Debug|x86.Build.0 = Debug|Any CPU
		{4017A4F4-5E59-48AA-A7D0-A8518148933A}.Release|Any CPU.ActiveCfg = Release|Any CPU
		{4017A4F4-5E59-48AA-A7D0-A8518148933A}.Release|Any CPU.Build.0 = Release|Any CPU
		{4017A4F4-5E59-48AA-A7D0-A8518148933A}.Release|x64.ActiveCfg = Release|Any CPU
		{4017A4F4-5E59-48AA-A7D0-A8518148933A}.Release|x64.Build.0 = Release|Any CPU
		{4017A4F4-5E59-48AA-A7D0-A8518148933A}.Release|x86.ActiveCfg = Release|Any CPU
		{4017A4F4-5E59-48AA-A7D0-A8518148933A}.Release|x86.Build.0 = Release|Any CPU
		{F96F3AA0-131E-4B6B-AB21-BBE2DEBCEF3A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
		{F96F3AA0-131E-4B6B-AB21-BBE2DEBCEF3A}.Debug|Any CPU.Build.0 = Debug|Any CPU
		{F96F3AA0-131E-4B6B-AB21-BBE2DEBCEF3A}.Debug|x64.ActiveCfg = Debug|Any CPU
		{F96F3AA0-131E-4B6B-AB21-BBE2DEBCEF3A}.Debug|x64.Build.0 = Debug|Any CPU
		{F96F3AA0-131E-4B6B-AB21-BBE2DEBCEF3A}.Debug|x86.ActiveCfg = Debug|Any CPU
		{F96F3AA0-131E-4B6B-AB21-BBE2DEBCEF3A}.Debug|x86.Build.0 = Debug|Any CPU
		{F96F3AA0-131E-4B6B-AB21-BBE2DEBCEF3A}.Release|Any CPU.ActiveCfg = Release|Any CPU
		{F96F3AA0-131E-4B6B-AB21-BBE2DEBCEF3A}.Release|Any CPU.Build.0 = Release|Any CPU
		{F96F3AA0-131E-4B6B-AB21-BBE2DEBCEF3A}.Release|x64.ActiveCfg = Release|Any CPU
		{F96F3AA0-131E-4B6B-AB21-BBE2DEBCEF3A}.Release|x64.Build.0 = Release|Any CPU
		{F96F3AA0-131E-4B6B-AB21-BBE2DEBCEF3A}.Release|x86.ActiveCfg = Release|Any CPU
		{F96F3AA0-131E-4B6B-AB21-BBE2DEBCEF3A}.Release|x86.Build.0 = Release|Any CPU
		{18A62EF6-ADDA-4224-90AB-2D5DCFC95D3E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
		{18A62EF6-ADDA-4224-90AB-2D5DCFC95D3E}.Debug|Any CPU.Build.0 = Debug|Any CPU
		{18A62EF6-ADDA-4224-90AB-2D5DCFC95D3E}.Debug|x64.ActiveCfg = Debug|Any CPU
		{18A62EF6-ADDA-4224-90AB-2D5DCFC95D3E}.Debug|x64.Build.0 = Debug|Any CPU
		{18A62EF6-ADDA-4224-90AB-2D5DCFC95D3E}.Debug|x86.ActiveCfg = Debug|Any CPU
		{18A62EF6-ADDA-4224-90AB-2D5DCFC95D3E}.Debug|x86.Build.0 = Debug|Any CPU
		{18A62EF6-ADDA-4224-90AB-2D5DCFC95D3E}.Release|Any CPU.ActiveCfg = Release|Any CPU
		{18A62EF6-ADDA-4224-90AB-2D5DCFC95D3E}.Release|Any CPU.Build.0 = Release|Any CPU
		{18A62EF6-ADDA-4224-90AB-2D5DCFC95D3E}.Release|x64.ActiveCfg = Release|Any CPU
		{18A62EF6-ADDA-4224-90AB-2D5DCFC95D3E}.Release|x64.Build.0 = Release|Any CPU
		{18A62EF6-ADDA-4224-90AB-2D5DCFC95D3E}.Release|x86.ActiveCfg = Release|Any CPU
		{18A62EF6-ADDA-4224-90AB-2D5DCFC95D3E}.Release|x86.Build.0 = Release|Any CPU
		{1A10C624-48E5-425D-938E-31A4CC7AC687}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
		{1A10C624-48E5-425D-938E-31A4CC7AC687}.Debug|Any CPU.Build.0 = Debug|Any CPU
		{1A10C624-48E5-425D-938E-31A4CC7AC687}.Debug|x64.ActiveCfg = Debug|Any CPU


@@ 200,54 98,6 @@ Global
		{9BC56B40-64E3-4A8F-AD49-C52857A35026}.Release|x64.Build.0 = Release|Any CPU
		{9BC56B40-64E3-4A8F-AD49-C52857A35026}.Release|x86.ActiveCfg = Release|Any CPU
		{9BC56B40-64E3-4A8F-AD49-C52857A35026}.Release|x86.Build.0 = Release|Any CPU
		{CA2873D8-BD0B-4583-818D-B94A3C2ABBA3}.Debug|Any CPU.ActiveCfg = Debug|x64
		{CA2873D8-BD0B-4583-818D-B94A3C2ABBA3}.Debug|Any CPU.Build.0 = Debug|x64
		{CA2873D8-BD0B-4583-818D-B94A3C2ABBA3}.Debug|x64.ActiveCfg = Debug|x64
		{CA2873D8-BD0B-4583-818D-B94A3C2ABBA3}.Debug|x64.Build.0 = Debug|x64
		{CA2873D8-BD0B-4583-818D-B94A3C2ABBA3}.Debug|x86.ActiveCfg = Debug|Win32
		{CA2873D8-BD0B-4583-818D-B94A3C2ABBA3}.Debug|x86.Build.0 = Debug|Win32
		{CA2873D8-BD0B-4583-818D-B94A3C2ABBA3}.Release|Any CPU.ActiveCfg = Release|x64
		{CA2873D8-BD0B-4583-818D-B94A3C2ABBA3}.Release|Any CPU.Build.0 = Release|x64
		{CA2873D8-BD0B-4583-818D-B94A3C2ABBA3}.Release|x64.ActiveCfg = Release|x64
		{CA2873D8-BD0B-4583-818D-B94A3C2ABBA3}.Release|x64.Build.0 = Release|x64
		{CA2873D8-BD0B-4583-818D-B94A3C2ABBA3}.Release|x86.ActiveCfg = Release|Win32
		{CA2873D8-BD0B-4583-818D-B94A3C2ABBA3}.Release|x86.Build.0 = Release|Win32
		{5D351C91-C631-40F6-82D2-F8D68468B076}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
		{5D351C91-C631-40F6-82D2-F8D68468B076}.Debug|Any CPU.Build.0 = Debug|Any CPU
		{5D351C91-C631-40F6-82D2-F8D68468B076}.Debug|x64.ActiveCfg = Debug|Any CPU
		{5D351C91-C631-40F6-82D2-F8D68468B076}.Debug|x64.Build.0 = Debug|Any CPU
		{5D351C91-C631-40F6-82D2-F8D68468B076}.Debug|x86.ActiveCfg = Debug|Any CPU
		{5D351C91-C631-40F6-82D2-F8D68468B076}.Debug|x86.Build.0 = Debug|Any CPU
		{5D351C91-C631-40F6-82D2-F8D68468B076}.Release|Any CPU.ActiveCfg = Release|Any CPU
		{5D351C91-C631-40F6-82D2-F8D68468B076}.Release|Any CPU.Build.0 = Release|Any CPU
		{5D351C91-C631-40F6-82D2-F8D68468B076}.Release|x64.ActiveCfg = Release|Any CPU
		{5D351C91-C631-40F6-82D2-F8D68468B076}.Release|x64.Build.0 = Release|Any CPU
		{5D351C91-C631-40F6-82D2-F8D68468B076}.Release|x86.ActiveCfg = Release|Any CPU
		{5D351C91-C631-40F6-82D2-F8D68468B076}.Release|x86.Build.0 = Release|Any CPU
		{7B68DE7D-159B-42A7-8CF8-69F156CD04DE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
		{7B68DE7D-159B-42A7-8CF8-69F156CD04DE}.Debug|Any CPU.Build.0 = Debug|Any CPU
		{7B68DE7D-159B-42A7-8CF8-69F156CD04DE}.Debug|x64.ActiveCfg = Debug|Any CPU
		{7B68DE7D-159B-42A7-8CF8-69F156CD04DE}.Debug|x64.Build.0 = Debug|Any CPU
		{7B68DE7D-159B-42A7-8CF8-69F156CD04DE}.Debug|x86.ActiveCfg = Debug|Any CPU
		{7B68DE7D-159B-42A7-8CF8-69F156CD04DE}.Debug|x86.Build.0 = Debug|Any CPU
		{7B68DE7D-159B-42A7-8CF8-69F156CD04DE}.Release|Any CPU.ActiveCfg = Release|Any CPU
		{7B68DE7D-159B-42A7-8CF8-69F156CD04DE}.Release|Any CPU.Build.0 = Release|Any CPU
		{7B68DE7D-159B-42A7-8CF8-69F156CD04DE}.Release|x64.ActiveCfg = Release|Any CPU
		{7B68DE7D-159B-42A7-8CF8-69F156CD04DE}.Release|x64.Build.0 = Release|Any CPU
		{7B68DE7D-159B-42A7-8CF8-69F156CD04DE}.Release|x86.ActiveCfg = Release|Any CPU
		{7B68DE7D-159B-42A7-8CF8-69F156CD04DE}.Release|x86.Build.0 = Release|Any CPU
		{BC0DCD32-E917-4187-8CF3-970D17301ED3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
		{BC0DCD32-E917-4187-8CF3-970D17301ED3}.Debug|Any CPU.Build.0 = Debug|Any CPU
		{BC0DCD32-E917-4187-8CF3-970D17301ED3}.Debug|x64.ActiveCfg = Debug|Any CPU
		{BC0DCD32-E917-4187-8CF3-970D17301ED3}.Debug|x64.Build.0 = Debug|Any CPU
		{BC0DCD32-E917-4187-8CF3-970D17301ED3}.Debug|x86.ActiveCfg = Debug|Any CPU
		{BC0DCD32-E917-4187-8CF3-970D17301ED3}.Debug|x86.Build.0 = Debug|Any CPU
		{BC0DCD32-E917-4187-8CF3-970D17301ED3}.Release|Any CPU.ActiveCfg = Release|Any CPU
		{BC0DCD32-E917-4187-8CF3-970D17301ED3}.Release|Any CPU.Build.0 = Release|Any CPU
		{BC0DCD32-E917-4187-8CF3-970D17301ED3}.Release|x64.ActiveCfg = Release|Any CPU
		{BC0DCD32-E917-4187-8CF3-970D17301ED3}.Release|x64.Build.0 = Release|Any CPU
		{BC0DCD32-E917-4187-8CF3-970D17301ED3}.Release|x86.ActiveCfg = Release|Any CPU
		{BC0DCD32-E917-4187-8CF3-970D17301ED3}.Release|x86.Build.0 = Release|Any CPU
		{86B4ED0C-CD28-4C6C-B58E-B4B1F7AAD683}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
		{86B4ED0C-CD28-4C6C-B58E-B4B1F7AAD683}.Debug|Any CPU.Build.0 = Debug|Any CPU
		{86B4ED0C-CD28-4C6C-B58E-B4B1F7AAD683}.Debug|x64.ActiveCfg = Debug|Any CPU


@@ 272,55 122,18 @@ Global
		{CF03BCEA-EB5B-427F-8576-7DA7EB869BDC}.Release|x64.Build.0 = Release|Any CPU
		{CF03BCEA-EB5B-427F-8576-7DA7EB869BDC}.Release|x86.ActiveCfg = Release|Any CPU
		{CF03BCEA-EB5B-427F-8576-7DA7EB869BDC}.Release|x86.Build.0 = Release|Any CPU
		{7DC7FC22-EFA6-4EB0-8E75-99F746E50F6E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
		{7DC7FC22-EFA6-4EB0-8E75-99F746E50F6E}.Debug|Any CPU.Build.0 = Debug|Any CPU
		{7DC7FC22-EFA6-4EB0-8E75-99F746E50F6E}.Debug|x64.ActiveCfg = Debug|Any CPU
		{7DC7FC22-EFA6-4EB0-8E75-99F746E50F6E}.Debug|x64.Build.0 = Debug|Any CPU
		{7DC7FC22-EFA6-4EB0-8E75-99F746E50F6E}.Debug|x86.ActiveCfg = Debug|Any CPU
		{7DC7FC22-EFA6-4EB0-8E75-99F746E50F6E}.Debug|x86.Build.0 = Debug|Any CPU
		{7DC7FC22-EFA6-4EB0-8E75-99F746E50F6E}.Release|Any CPU.ActiveCfg = Release|Any CPU
		{7DC7FC22-EFA6-4EB0-8E75-99F746E50F6E}.Release|Any CPU.Build.0 = Release|Any CPU
		{7DC7FC22-EFA6-4EB0-8E75-99F746E50F6E}.Release|x64.ActiveCfg = Release|Any CPU
		{7DC7FC22-EFA6-4EB0-8E75-99F746E50F6E}.Release|x64.Build.0 = Release|Any CPU
		{7DC7FC22-EFA6-4EB0-8E75-99F746E50F6E}.Release|x86.ActiveCfg = Release|Any CPU
		{7DC7FC22-EFA6-4EB0-8E75-99F746E50F6E}.Release|x86.Build.0 = Release|Any CPU
		{A3BCC882-6DCD-4326-8BC2-A5C78AD10361}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
		{A3BCC882-6DCD-4326-8BC2-A5C78AD10361}.Debug|Any CPU.Build.0 = Debug|Any CPU
		{A3BCC882-6DCD-4326-8BC2-A5C78AD10361}.Debug|x64.ActiveCfg = Debug|Any CPU
		{A3BCC882-6DCD-4326-8BC2-A5C78AD10361}.Debug|x64.Build.0 = Debug|Any CPU
		{A3BCC882-6DCD-4326-8BC2-A5C78AD10361}.Debug|x86.ActiveCfg = Debug|Any CPU
		{A3BCC882-6DCD-4326-8BC2-A5C78AD10361}.Debug|x86.Build.0 = Debug|Any CPU
		{A3BCC882-6DCD-4326-8BC2-A5C78AD10361}.Release|Any CPU.ActiveCfg = Release|Any CPU
		{A3BCC882-6DCD-4326-8BC2-A5C78AD10361}.Release|Any CPU.Build.0 = Release|Any CPU
		{A3BCC882-6DCD-4326-8BC2-A5C78AD10361}.Release|x64.ActiveCfg = Release|Any CPU
		{A3BCC882-6DCD-4326-8BC2-A5C78AD10361}.Release|x64.Build.0 = Release|Any CPU
		{A3BCC882-6DCD-4326-8BC2-A5C78AD10361}.Release|x86.ActiveCfg = Release|Any CPU
		{A3BCC882-6DCD-4326-8BC2-A5C78AD10361}.Release|x86.Build.0 = Release|Any CPU
	EndGlobalSection
	GlobalSection(SolutionProperties) = preSolution
		HideSolutionNode = FALSE
	EndGlobalSection
	GlobalSection(NestedProjects) = preSolution
		{8CCDB0CD-DFB2-492E-B1A2-8DD030F923D3} = {01B5E872-271F-4D30-A1AA-AD48D81840C5}
		{19666500-4636-4400-8855-496317F4A7F7} = {01B5E872-271F-4D30-A1AA-AD48D81840C5}
		{46D0A205-CA30-41CC-9A80-10B543FBD344} = {6078AE6E-7CD0-48E4-84E0-EB164D8881DA}
		{06F03B1F-F68A-4C5B-B7FE-128A7CE1A1D7} = {01B5E872-271F-4D30-A1AA-AD48D81840C5}
		{4017A4F4-5E59-48AA-A7D0-A8518148933A} = {9025731C-084E-4E82-8CD4-0F52D3AA1F54}
		{F96F3AA0-131E-4B6B-AB21-BBE2DEBCEF3A} = {9025731C-084E-4E82-8CD4-0F52D3AA1F54}
		{9025731C-084E-4E82-8CD4-0F52D3AA1F54} = {F20FE754-FDEA-4F3A-93D4-0750CB9EBB33}
		{1A10C624-48E5-425D-938E-31A4CC7AC687} = {C6A8760D-92CB-4307-88A7-36CCAEBA4AD1}
		{9BC56B40-64E3-4A8F-AD49-C52857A35026} = {C6A8760D-92CB-4307-88A7-36CCAEBA4AD1}
		{CA2873D8-BD0B-4583-818D-B94A3C2ABBA3} = {6078AE6E-7CD0-48E4-84E0-EB164D8881DA}
		{5D351C91-C631-40F6-82D2-F8D68468B076} = {6078AE6E-7CD0-48E4-84E0-EB164D8881DA}
		{7B68DE7D-159B-42A7-8CF8-69F156CD04DE} = {6078AE6E-7CD0-48E4-84E0-EB164D8881DA}
		{18A62EF6-ADDA-4224-90AB-2D5DCFC95D3E} = {F20FE754-FDEA-4F3A-93D4-0750CB9EBB33}
		{BC0DCD32-E917-4187-8CF3-970D17301ED3} = {6078AE6E-7CD0-48E4-84E0-EB164D8881DA}
		{C61EBDB6-053C-48C3-B896-58642639C93F} = {54A49AC2-55B3-4156-8023-41C56719EBB5}
		{C4DAFD83-C6DC-4597-AA1F-BA2F3ABB612C} = {54A49AC2-55B3-4156-8023-41C56719EBB5}
		{86B4ED0C-CD28-4C6C-B58E-B4B1F7AAD683} = {54A49AC2-55B3-4156-8023-41C56719EBB5}
		{CF03BCEA-EB5B-427F-8576-7DA7EB869BDC} = {54A49AC2-55B3-4156-8023-41C56719EBB5}
		{7DC7FC22-EFA6-4EB0-8E75-99F746E50F6E} = {6078AE6E-7CD0-48E4-84E0-EB164D8881DA}
		{A3BCC882-6DCD-4326-8BC2-A5C78AD10361} = {F20FE754-FDEA-4F3A-93D4-0750CB9EBB33}
	EndGlobalSection
	GlobalSection(ExtensibilityGlobals) = postSolution
		SolutionGuid = {C5F46653-4DEC-429B-8580-4ED18ED9B4CA}

D Samples/ExternalBrowser/ExternalBrowser.csproj => Samples/ExternalBrowser/ExternalBrowser.csproj +0 -15
@@ 1,15 0,0 @@
<Project Sdk="Microsoft.NET.Sdk">

    <PropertyGroup>
        <OutputType>Exe</OutputType>
        <TargetFramework>net6.0</TargetFramework>
        <ImplicitUsings>enable</ImplicitUsings>
        <Nullable>enable</Nullable>
    </PropertyGroup>

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

</Project>

D Samples/ExternalBrowser/Program.cs => Samples/ExternalBrowser/Program.cs +0 -75
@@ 1,75 0,0 @@
//
//  Program.cs
//
//  Copyright (c) František Boháček. All rights reserved.
//  Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System.Diagnostics;
using NosSmooth.Core.Extensions;
using NosSmooth.LocalBinding;
using NosSmooth.LocalBinding.Options;

namespace ExternalBrowser;

/// <summary>
/// The entrypoint class for ExternalBrowser.
/// </summary>
public class Program
{
    /// <summary>
    /// The entrypoint method for ExternalBrowser.
    /// </summary>
    /// <param name="arguments">The arguments.</param>
    public static void Main(string[] arguments)
    {
        var playerManagerOptions = new PlayerManagerOptions();
        var sceneManagerOptions = new SceneManagerOptions();
        var petManagerOptions = new PetManagerOptions();

        foreach (var argument in arguments)
        {
            Process[] processes;

            if (int.TryParse(argument, out var processId))
            {
                processes = new[] { Process.GetProcessById(processId) };
            }
            else
            {
                processes = Process
                    .GetProcesses()
                    .Where(x => x.ProcessName.Contains(argument))
                    .ToArray();
            }

            foreach (var process in processes)
            {
                var externalBrowser = new NosBrowserManager
                    (process, playerManagerOptions, sceneManagerOptions, petManagerOptions);

                if (!externalBrowser.IsNostaleProcess)
                {
                    Console.Error.WriteLine($"Process {process.Id} is not a nostale process.");
                    continue;
                }

                var initializationResult = externalBrowser.Initialize();
                if (!initializationResult.IsSuccess)
                {
                    Console.Error.WriteLine(initializationResult.ToFullString());
                }

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

                if (!externalBrowser.IsInGame)
                {
                    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}");
            }
        }
    }
}
\ No newline at end of file

D Samples/InterceptNameChanger/DllMain.cs => Samples/InterceptNameChanger/DllMain.cs +0 -45
@@ 1,45 0,0 @@
//
//  DllMain.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;
using System.Runtime.InteropServices;
using System.Threading;

namespace InterceptNameChanger
{
    /// <summary>
    /// The main entrypoint class of the dll.
    /// </summary>
    public class DllMain
    {
        [DllImport("kernel32")]
#pragma warning disable SA1600
        public static extern bool AllocConsole();
#pragma warning restore SA1600

        /// <summary>
        /// The main entrypoint method of the dll.
        /// </summary>
        [UnmanagedCallersOnly(EntryPoint = "Main")]
        public static void Main()
        {
            AllocConsole();
            Console.WriteLine("Hello from InterceptNameChanger DllMain entry point.");

            new Thread(() =>
            {
                try
                {
                    new NameChanger().RunAsync().GetAwaiter().GetResult();
                }
                catch (Exception e)
                {
                    Console.WriteLine(e.ToString());
                }
            }).Start();
        }
    }
}
\ No newline at end of file

D Samples/InterceptNameChanger/FodyWeavers.xml => Samples/InterceptNameChanger/FodyWeavers.xml +0 -3
@@ 1,3 0,0 @@
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd">
  <Costura />
</Weavers>
\ No newline at end of file

D Samples/InterceptNameChanger/InterceptNameChanger.csproj => Samples/InterceptNameChanger/InterceptNameChanger.csproj +0 -25
@@ 1,25 0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>net6.0</TargetFramework>
    <Nullable>enable</Nullable>
    <LangVersion>10</LangVersion>
    <GenerateRuntimeConfigurationFiles>true</GenerateRuntimeConfigurationFiles>
  </PropertyGroup>
  <ItemGroup>
    <PackageReference Include="Costura.Fody" Version="5.7.0">
      <PrivateAssets>all</PrivateAssets>
      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
    </PackageReference>
    <PackageReference Include="Fody" Version="6.6.0">
      <PrivateAssets>all</PrivateAssets>
      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
    </PackageReference>
    <PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="6.0.0" />
    <PackageReference Include="Microsoft.Extensions.Logging" Version="6.0.0" />
    <PackageReference Include="Microsoft.Extensions.Logging.Console" Version="6.0.0" />
  </ItemGroup>
  <ItemGroup>
    <ProjectReference Include="..\..\Core\NosSmooth.Core\NosSmooth.Core.csproj" />
    <ProjectReference Include="..\..\Local\NosSmooth.LocalClient\NosSmooth.LocalClient.csproj" />
  </ItemGroup>
</Project>
\ No newline at end of file

D Samples/InterceptNameChanger/NameChangeInterceptor.cs => Samples/InterceptNameChanger/NameChangeInterceptor.cs +0 -81
@@ 1,81 0,0 @@
//
//  NameChangeInterceptor.cs
//
//  Copyright (c) František Boháček. All rights reserved.
//  Licensed under the MIT license. See LICENSE file in the project root for full license information.

using Microsoft.Extensions.Logging;
using NosSmooth.Core.Client;
using NosSmooth.LocalClient;
using NosSmooth.Packets.Enums;
using NosSmooth.Packets.Enums.Chat;
using NosSmooth.Packets.Server.Chat;

namespace InterceptNameChanger
{
    /// <summary>
    /// Intercepts the packets so name in c_info may be replaced.
    /// </summary>
    public class NameChangeInterceptor : IPacketInterceptor
    {
        private readonly INostaleClient _client;
        private readonly ILogger<NameChangeInterceptor> _logger;
        private string _name = "Intercept";

        /// <summary>
        /// Initializes a new instance of the <see cref="NameChangeInterceptor"/> class.
        /// </summary>
        /// <param name="client">The nostale client.</param>
        /// <param name="logger">The logger.</param>
        public NameChangeInterceptor(INostaleClient client, ILogger<NameChangeInterceptor> logger)
        {
            _client = client;
            _logger = logger;
        }

        /// <inheritdoc/>
        public bool InterceptSend(ref string packet)
        {
            if (packet.StartsWith("say #"))
            {
                _name = packet.Substring(5).Replace(" ", "⠀"); // Mind the symbols!
                _logger.LogInformation("Name changed to {Name}", _name);
                _client.ReceivePacketAsync
                    (
                        new SayPacket
                        (
                            EntityType.Map,
                            1,
                            SayColor.Red,
                            $"Name changed to {_name}, change map for it to take effect."
                        )
                    )
                    .GetAwaiter()
                    .GetResult();
                return false;
            }

            return true; // Accept the packet
        }

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

                if (!result.IsSuccess)
                {
                    _logger.LogError("Could not send the c_info packet: {Reason}", result.Error.Message);
                    return true; // Accept the packet so client is not confused
                }
                return false; // Reject the packet
            }

            return true; // Accept the packet
        }
    }
}
\ No newline at end of file

D Samples/InterceptNameChanger/NameChanger.cs => Samples/InterceptNameChanger/NameChanger.cs +0 -86
@@ 1,86 0,0 @@
//
//  NameChanger.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.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using NosSmooth.Core.Client;
using NosSmooth.Core.Extensions;
using NosSmooth.LocalBinding;
using NosSmooth.LocalClient;
using NosSmooth.LocalClient.Extensions;
using NosSmooth.Packets.Enums;
using NosSmooth.Packets.Enums.Chat;
using NosSmooth.Packets.Extensions;
using NosSmooth.Packets.Packets;
using NosSmooth.Packets.Server.Chat;

namespace InterceptNameChanger
{
    /// <summary>
    /// Main class of name changer.
    /// </summary>
    public class NameChanger
    {
        /// <summary>
        /// Run the name changer.
        /// </summary>
        /// <returns>A task that may or may not have succeeded.</returns>
        public async Task RunAsync()
        {
            var provider = new ServiceCollection()
                .AddLocalClient()

                // .AddPacketResponder<SayResponder>()
                .AddLogging
                (
                    b =>
                    {
                        b.ClearProviders();
                        b.AddConsole();
                        b.SetMinimumLevel(LogLevel.Debug);
                    }
                )
                .Configure<LocalClientOptions>(o => o.AllowIntercept = true)
                .AddSingleton<IPacketInterceptor, NameChangeInterceptor>()
                .BuildServiceProvider();

            var logger = provider.GetRequiredService<ILogger<NameChanger>>();
            logger.LogInformation("Hello world from NameChanger!");

            var bindingManager = provider.GetRequiredService<NosBindingManager>();
            var initializeResult = bindingManager.Initialize();
            if (!initializeResult.IsSuccess)
            {
                logger.LogError($"Could not initialize NosBindingManager.");
                logger.LogResultError(initializeResult);
            }

            var packetTypesRepository = provider.GetRequiredService<IPacketTypesRepository>();
            var packetAddResult = packetTypesRepository.AddDefaultPackets();
            if (!packetAddResult.IsSuccess)
            {
                logger.LogError("Could not initialize default packet serializers correctly");
                logger.LogResultError(packetAddResult);
            }

            var client = provider.GetRequiredService<INostaleClient>();

            var sayResult = await client.ReceivePacketAsync
            (
                new SayPacket
                    (EntityType.Map, 1, SayColor.Red, "The name may be changed by typing #{NewName} into the chat.")
            );

            if (!sayResult.IsSuccess)
            {
                logger.LogError("Could not send say packet");
            }

            await client.RunAsync();
        }
    }
}
\ No newline at end of file

D Samples/InterceptNameChanger/Properties/AssemblyInfo.cs => Samples/InterceptNameChanger/Properties/AssemblyInfo.cs +0 -41
@@ 1,41 0,0 @@
//
//  AssemblyInfo.cs
//
//  Copyright (c) František Boháček. All rights reserved.
//  Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System.Reflection;
using System.Runtime.InteropServices;

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

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

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

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

D Samples/SimpleChat/DllMain.cs => Samples/SimpleChat/DllMain.cs +0 -32
@@ 1,32 0,0 @@
//
//  DllMain.cs
//
//  Copyright (c) František Boháček. All rights reserved.
//  Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System.Runtime.InteropServices;

namespace SimpleChat;

/// <summary>
/// The main entrypoint class of the dll.
/// </summary>
public class DllMain
{
    [DllImport("kernel32")]
#pragma warning disable SA1600
    public static extern bool AllocConsole();
#pragma warning restore SA1600

    /// <summary>
    /// The main entrypoint method of the dll.
    /// </summary>
    [UnmanagedCallersOnly(EntryPoint = "Main")]
    public static void Main()
    {
        AllocConsole();
        Console.WriteLine("Hello from SimpleChat DllMain entry point.");

        new Thread(() => new SimpleChat().RunAsync().GetAwaiter().GetResult()).Start();
    }
}
\ No newline at end of file

D Samples/SimpleChat/FodyWeavers.xml => Samples/SimpleChat/FodyWeavers.xml +0 -4
@@ 1,4 0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd">
  <Costura />
</Weavers>
\ No newline at end of file

D Samples/SimpleChat/SayResponder.cs => Samples/SimpleChat/SayResponder.cs +0 -51
@@ 1,51 0,0 @@
//
//  SayResponder.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.Core.Client;
using NosSmooth.Core.Packets;
using NosSmooth.Packets.Enums;
using NosSmooth.Packets.Enums.Chat;
using NosSmooth.Packets.Server.Chat;
using Remora.Results;

namespace SimpleChat;

/// <summary>
/// Responds to <see cref="SayPacket"/>.
/// </summary>
public class SayResponder : IPacketResponder<SayPacket>, IPacketResponder<MsgPacket>
{
    private readonly INostaleClient _client;

    /// <summary>
    /// Initializes a new instance of the <see cref="SayResponder"/> class.
    /// </summary>
    /// <param name="client">The nostale client.</param>
    public SayResponder(INostaleClient client)
    {
        _client = client;
    }

    /// <inheritdoc />
    public Task<Result> Respond(PacketEventArgs<SayPacket> packet, CancellationToken ct = default)
    {
        return _client.ReceivePacketAsync
        (
            new SayPacket(EntityType.Map, 1, SayColor.Red, "Hello world from NosSmooth!"),
            ct
        );
    }

    /// <inheritdoc />
    public Task<Result> Respond(PacketEventArgs<MsgPacket> packet, CancellationToken ct = default)
    {
        return _client.ReceivePacketAsync
        (
            new SayPacket(EntityType.Map, 1, SayColor.Red, "Hello world from NosSmooth!"),
            ct
        );
    }
}
\ No newline at end of file

D Samples/SimpleChat/SimpleChat.cs => Samples/SimpleChat/SimpleChat.cs +0 -74
@@ 1,74 0,0 @@
//
//  SimpleChat.cs
//
//  Copyright (c) František Boháček. All rights reserved.
//  Licensed under the MIT license. See LICENSE file in the project root for full license information.

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using NosSmooth.Core.Client;
using NosSmooth.Core.Extensions;
using NosSmooth.LocalBinding;
using NosSmooth.LocalClient.Extensions;
using NosSmooth.Packets.Enums;
using NosSmooth.Packets.Enums.Chat;
using NosSmooth.Packets.Extensions;
using NosSmooth.Packets.Packets;
using NosSmooth.Packets.Server.Chat;

namespace SimpleChat;

/// <summary>
/// The main simple chat class.
/// </summary>
public class SimpleChat
{
    /// <summary>
    /// Run the client.
    /// </summary>
    /// <returns>The task that may or may not have succeeded.</returns>
    public async Task RunAsync()
    {
        var provider = new ServiceCollection()
            .AddLocalClient()
            .AddPacketResponder<SayResponder>()
            .AddLogging
            (
                b =>
                {
                    b.ClearProviders();
                    b.AddConsole();
                    b.SetMinimumLevel(LogLevel.Debug);
                }
            )
            .BuildServiceProvider();

        var logger = provider.GetRequiredService<ILogger<SimpleChat>>();
        logger.LogInformation("Hello world from SimpleChat!");

        var bindingManager = provider.GetRequiredService<NosBindingManager>();
        var initializeResult = bindingManager.Initialize();
        if (!initializeResult.IsSuccess)
        {
            logger.LogError($"Could not initialize NosBindingManager.");
            logger.LogResultError(initializeResult);
        }

        var packetTypesRepository = provider.GetRequiredService<IPacketTypesRepository>();
        var packetAddResult = packetTypesRepository.AddDefaultPackets();
        if (!packetAddResult.IsSuccess)
        {
            logger.LogError("Could not initialize default packet serializers correctly");
            logger.LogResultError(packetAddResult);
        }

        var client = provider.GetRequiredService<INostaleClient>();

        await client.ReceivePacketAsync
        (
            new SayPacket(EntityType.Map, 1, SayColor.Red, "Hello world from NosSmooth!")
        );

        await client.RunAsync();
    }
}
\ No newline at end of file

D Samples/SimpleChat/SimpleChat.csproj => Samples/SimpleChat/SimpleChat.csproj +0 -26
@@ 1,26 0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>net6.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
    <LangVersion>10</LangVersion>
    <GenerateRuntimeConfigurationFiles>true</GenerateRuntimeConfigurationFiles>
  </PropertyGroup>
  <ItemGroup>
    <PackageReference Include="Costura.Fody" Version="5.7.0">
      <PrivateAssets>all</PrivateAssets>
      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
    </PackageReference>
    <PackageReference Include="Fody" Version="6.6.0">
      <PrivateAssets>all</PrivateAssets>
      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
    </PackageReference>
    <PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="6.0.0" />
    <PackageReference Include="Microsoft.Extensions.Logging" Version="6.0.0" />
    <PackageReference Include="Microsoft.Extensions.Logging.Console" Version="6.0.0" />
  </ItemGroup>
  <ItemGroup>
    <ProjectReference Include="..\..\Core\NosSmooth.Core\NosSmooth.Core.csproj" />
    <ProjectReference Include="..\..\Local\NosSmooth.LocalClient\NosSmooth.LocalClient.csproj" />
  </ItemGroup>
</Project>
\ No newline at end of file

D Samples/WalkCommands/Commands/CombatCommands.cs => Samples/WalkCommands/Commands/CombatCommands.cs +0 -92
@@ 1,92 0,0 @@
//
//  CombatCommands.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.ChatCommands;
using NosSmooth.Core.Client;
using NosSmooth.LocalBinding;
using NosSmooth.LocalBinding.Objects;
using NosSmooth.LocalBinding.Structs;
using Remora.Commands.Attributes;
using Remora.Commands.Groups;
using Remora.Results;

namespace WalkCommands.Commands;

/// <summary>
/// Represents command group for combat commands.
/// </summary>
public class CombatCommands : CommandGroup
{
    private readonly UnitManagerBinding _unitManagerBinding;
    private readonly SceneManager _sceneManager;
    private readonly PlayerManagerBinding _playerManagerBinding;
    private readonly FeedbackService _feedbackService;

    /// <summary>
    /// Initializes a new instance of the <see cref="CombatCommands"/> class.
    /// </summary>
    /// <param name="unitManagerBinding">The scene manager binding.</param>
    /// <param name="sceneManager">The scene manager.</param>
    /// <param name="playerManagerBinding">The character binding.</param>
    /// <param name="feedbackService">The feedback service.</param>
    public CombatCommands
    (
        UnitManagerBinding unitManagerBinding,
        SceneManager sceneManager,
        PlayerManagerBinding playerManagerBinding,
        FeedbackService feedbackService
    )
    {
        _unitManagerBinding = unitManagerBinding;
        _sceneManager = sceneManager;
        _playerManagerBinding = playerManagerBinding;
        _feedbackService = feedbackService;
    }

    /// <summary>
    /// Focus the given entity.
    /// </summary>
    /// <param name="entityId">The entity id to focus.</param>
    /// <returns>A task that may or may not have succeeded.</returns>
    [Command("focus")]
    public Task<Result> HandleFocusAsync(int entityId)
    {
        var entityResult = _sceneManager.FindEntity(entityId);
        if (!entityResult.IsSuccess)
        {
            return Task.FromResult(Result.FromError(entityResult));
        }

        return Task.FromResult(_unitManagerBinding.FocusEntity(entityResult.Entity));
    }

    /// <summary>
    /// Follow the given entity.
    /// </summary>
    /// <param name="entityId">The entity id to follow.</param>
    /// <returns>A task that may or may not have succeeded.</returns>
    [Command("follow")]
    public Task<Result> HandleFollowAsync(int entityId)
    {
        var entityResult = _sceneManager.FindEntity(entityId);
        if (!entityResult.IsSuccess)
        {
            return Task.FromResult(Result.FromError(entityResult));
        }

        return Task.FromResult(_playerManagerBinding.FollowEntity(entityResult.Entity));
    }

    /// <summary>
    /// Stop following an entity.
    /// </summary>
    /// <returns>A task that may or may not have succeeded.</returns>
    [Command("unfollow")]
    public Task<Result> HandleUnfollowAsync()
    {
        return Task.FromResult(_playerManagerBinding.UnfollowEntity());
    }
}
\ No newline at end of file

D Samples/WalkCommands/Commands/DetachCommand.cs => Samples/WalkCommands/Commands/DetachCommand.cs +0 -52
@@ 1,52 0,0 @@
//
//  DetachCommand.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.ChatCommands;
using NosSmooth.Core.Client;
using NosSmooth.Packets.Enums;
using NosSmooth.Packets.Enums.Chat;
using NosSmooth.Packets.Server.Chat;
using Remora.Commands.Groups;
using Remora.Results;

namespace WalkCommands.Commands;

/// <summary>
/// Group for detaching command that detaches the dll.
/// </summary>
public class DetachCommand : CommandGroup
{
    private readonly CancellationTokenSource _dllStop;
    private readonly FeedbackService _feedbackService;

    /// <summary>
    /// Initializes a new instance of the <see cref="DetachCommand"/> class.
    /// </summary>
    /// <param name="dllStop">The cancellation token source to stop the client.</param>
    /// <param name="feedbackService">The feedback service.</param>
    public DetachCommand(CancellationTokenSource dllStop, FeedbackService feedbackService)
    {
        _dllStop = dllStop;
        _feedbackService = feedbackService;
    }

    /// <summary>
    /// Detach the dll.
    /// </summary>
    /// <returns>A result that may or may not have succeeded.</returns>
    public async Task<Result> HandleDetach()
    {
        var receiveResult = await _feedbackService.SendInfoMessageAsync("Going to detach!", CancellationToken);

        if (!receiveResult.IsSuccess)
        {
            return receiveResult;
        }

        _dllStop.Cancel();
        return Result.FromSuccess();
    }
}

D Samples/WalkCommands/Commands/WalkCommands.cs => Samples/WalkCommands/Commands/WalkCommands.cs +0 -87
@@ 1,87 0,0 @@
//
//  WalkCommands.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.ChatCommands;
using NosSmooth.Core.Client;
using NosSmooth.Core.Commands;
using NosSmooth.Core.Commands.Walking;
using NosSmooth.Core.Extensions;
using NosSmooth.Packets.Enums;
using NosSmooth.Packets.Enums.Chat;
using NosSmooth.Packets.Server.Chat;
using Remora.Commands.Attributes;
using Remora.Commands.Groups;
using Remora.Results;

namespace WalkCommands.Commands;

/// <summary>
/// Represents command group for walking.
/// </summary>
public class WalkCommands : CommandGroup
{
    private readonly INostaleClient _client;
    private readonly FeedbackService _feedbackService;

    /// <summary>
    /// Initializes a new instance of the <see cref="WalkCommands"/> class.
    /// </summary>
    /// <param name="client">The nostale client.</param>
    /// <param name="feedbackService">The feedback service.</param>
    public WalkCommands(INostaleClient client, FeedbackService feedbackService)
    {
        _client = client;
        _feedbackService = feedbackService;
    }

    /// <summary>
    /// Attempts to walk the character to the specified lcoation.
    /// </summary>
    /// <param name="x">The x coordinate.</param>
    /// <param name="y">The y coordinate.</param>
    /// <param name="isCancellable">Whether the user can cancel the operation.</param>
    /// <param name="petSelectors">The pet selectors indices.</param>
    /// <returns>A result that may or may not have succeeded.</returns>
    [Command("walk")]
    public async Task<Result> HandleWalkToAsync
    (
        ushort x,
        ushort y,
        bool isCancellable = true,
        [Option('p', "pet")] params int[] petSelectors
    )
    {
        var receiveResult = await _client.ReceivePacketAsync
        (
            new SayPacket(EntityType.Map, 1, SayColor.Red, $"Going to walk to {x} {y}."),
            CancellationToken
        );

        if (!receiveResult.IsSuccess)
        {
            return receiveResult;
        }

        var command = new WalkCommand(x, y, petSelectors, AllowUserCancel: isCancellable);
        var walkResult = await _client.SendCommandAsync(command, CancellationToken);
        if (!walkResult.IsSuccess)
        {
            await _feedbackService.SendErrorMessageAsync($"Could not finish walking. {walkResult.ToFullString()}", CancellationToken);
            await _client.ReceivePacketAsync
            (
                new SayPacket(EntityType.Map, 1, SayColor.Red, "Could not finish walking."),
                CancellationToken
            );
            return walkResult;
        }

        return await _client.ReceivePacketAsync
        (
            new SayPacket(EntityType.Map, 1, SayColor.Red, "Walk has finished successfully."),
            CancellationToken
        );
    }
}

D Samples/WalkCommands/DllMain.cs => Samples/WalkCommands/DllMain.cs +0 -40
@@ 1,40 0,0 @@
//
//  DllMain.cs
//
//  Copyright (c) František Boháček. All rights reserved.
//  Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System.Runtime.InteropServices;

namespace WalkCommands;

/// <summary>
/// Represents the dll entrypoint class.
/// </summary>
public class DllMain
{
    [DllImport("kernel32")]
#pragma warning disable SA1600
    public static extern bool AllocConsole();
#pragma warning restore SA1600

    /// <summary>
    /// Represents the dll entrypoint method.
    /// </summary>
    [UnmanagedCallersOnly(EntryPoint = "Main")]
    public static void Main()
    {
        AllocConsole();
        new Thread(() =>
        {
            try
            {
                new Startup().RunAsync().GetAwaiter().GetResult();
            }
            catch (Exception e)
            {
                Console.WriteLine(e.ToString());
            }
        }).Start();
    }
}
\ No newline at end of file

D Samples/WalkCommands/FodyWeavers.xml => Samples/WalkCommands/FodyWeavers.xml +0 -3
@@ 1,3 0,0 @@
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd">
  <Costura />
</Weavers>
\ No newline at end of file

D Samples/WalkCommands/Startup.cs => Samples/WalkCommands/Startup.cs +0 -82
@@ 1,82 0,0 @@
//
//  Startup.cs
//
//  Copyright (c) František Boháček. All rights reserved.
//  Licensed under the MIT license. See LICENSE file in the project root for full license information.

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using NosSmooth.ChatCommands;
using NosSmooth.Core.Client;
using NosSmooth.Core.Extensions;
using NosSmooth.LocalBinding;
using NosSmooth.LocalClient;
using NosSmooth.LocalClient.Extensions;
using NosSmooth.Packets.Extensions;
using NosSmooth.Packets.Packets;
using Remora.Commands.Extensions;
using WalkCommands.Commands;

namespace WalkCommands;

/// <summary>
/// Startup class of WalkCommands.
/// </summary>
public class Startup
{
    private IServiceProvider BuildServices()
    {
        var collection = new ServiceCollection()
            .AddLocalClient()
            .AddScoped<Commands.WalkCommands>()
            .AddScoped<DetachCommand>()
            .AddSingleton<CancellationTokenSource>()
            .Configure<LocalClientOptions>(o => o.AllowIntercept = true)
            .AddNostaleChatCommands()
            .AddLogging
            (
                b =>
                {
                    b.ClearProviders();
                    b.AddConsole();
                    b.SetMinimumLevel(LogLevel.Debug);
                }
            );

        collection.AddCommandTree()
            .WithCommandGroup<DetachCommand>()
            .WithCommandGroup<CombatCommands>()
            .WithCommandGroup<Commands.WalkCommands>();
        return collection.BuildServiceProvider();
    }

    /// <summary>
    /// Run the MoveToMiniland.
    /// </summary>
    /// <returns>A task that may or may not have succeeded.</returns>
    public async Task RunAsync()
    {
        var provider = BuildServices();
        var bindingManager = provider.GetRequiredService<NosBindingManager>();
        var logger = provider.GetRequiredService<ILogger<Startup>>();
        var initializeResult = bindingManager.Initialize();
        if (!initializeResult.IsSuccess)
        {
            logger.LogError($"Could not initialize NosBindingManager.");
            logger.LogResultError(initializeResult);
        }

        var packetTypesRepository = provider.GetRequiredService<IPacketTypesRepository>();
        var packetAddResult = packetTypesRepository.AddDefaultPackets();
        if (!packetAddResult.IsSuccess)
        {
            logger.LogError("Could not initialize default packet serializers correctly");
            logger.LogResultError(packetAddResult);
        }

        var mainCancellation = provider.GetRequiredService<CancellationTokenSource>();

        var client = provider.GetRequiredService<INostaleClient>();
        await client.RunAsync(mainCancellation.Token);
    }
}
\ No newline at end of file

D Samples/WalkCommands/WalkCommands.csproj => Samples/WalkCommands/WalkCommands.csproj +0 -42
@@ 1,42 0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>net6.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
    <AssemblyName>WalkCommands</AssemblyName>
    <RootNamespace>WalkCommands</RootNamespace>
    <LangVersion>10</LangVersion>
    <GenerateRuntimeConfigurationFiles>true</GenerateRuntimeConfigurationFiles>
    <EnableDynamicLoading>true</EnableDynamicLoading>
    <CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
    <RestoreProjectStyle>PackageReference</RestoreProjectStyle>
  </PropertyGroup>
  <ItemGroup>
    <PackageReference Include="Costura.Fody">
      <Version>5.7.0</Version>
      <PrivateAssets>all</PrivateAssets>
      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
    </PackageReference>
    <PackageReference Include="Fody">
      <Version>6.6.0</Version>
      <PrivateAssets>all</PrivateAssets>
      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
    </PackageReference>
    <PackageReference Include="Microsoft.Extensions.DependencyInjection">
      <Version>6.0.0</Version>
    </PackageReference>
    <PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions">
      <Version>6.0.0</Version>
    </PackageReference>
    <PackageReference Include="Microsoft.Extensions.Logging.Console">
      <Version>6.0.0</Version>
    </PackageReference>
    <PackageReference Include="Remora.Results">
      <Version>7.1.0</Version>
    </PackageReference>
  </ItemGroup>
  <ItemGroup>
    <ProjectReference Include="..\..\Local\NosSmooth.ChatCommands\NosSmooth.ChatCommands.csproj" />
    <ProjectReference Include="..\..\Local\NosSmooth.LocalClient\NosSmooth.LocalClient.csproj" />
  </ItemGroup>
</Project>
\ No newline at end of file

Do not follow this link