~ruther/NosSmooth.Comms

6c7a610350b3590d99556c10e558d5a612a8d3ea — František Boháček 2 years ago c5b50a9
feat: add possibility to open or close a console in NosTale process
M src/Local/NosSmooth.Comms.Inject/DllMain.cs => src/Local/NosSmooth.Comms.Inject/DllMain.cs +21 -9
@@ 26,14 26,10 @@ namespace NosSmooth.Comms.Inject;
/// </summary>
public class DllMain
{
    private static IHost? _host;
    private const uint StdOutputHandle = 0xFFFFFFF5;

    /// <summary>
    /// Allocate console.
    /// </summary>
    /// <returns>Whether the operation was successful.</returns>
    [DllImport("kernel32")]
    public static extern bool AllocConsole();
    private static bool _consoleAllocated;
    private static IHost? _host;

    /// <summary>
    /// Enable named pipes server.


@@ 46,14 42,30 @@ public class DllMain
            host =>
            {
                var manager = host.Services.GetRequiredService<ServerManager>();
                return manager.RunManagerAsync(host.Services.GetRequiredService<IHostApplicationLifetime>().ApplicationStopping);
                return manager.RunManagerAsync
                    (host.Services.GetRequiredService<IHostApplicationLifetime>().ApplicationStopping);
            }
        );
    }

    /// <summary>
    /// Open a console.
    /// </summary>
    public static void OpenConsole()
    {
        WinConsole.Initialize(false);
    }

    /// <summary>
    /// Close a console.
    /// </summary>
    public static void CloseConsole()
    {
        WinConsole.Close();
    }

    private static void Main(Func<IHost, Task<Result>> host)
    {
        AllocConsole();
        new Thread
        (
            () =>

A src/Local/NosSmooth.Comms.Inject/MessageResponders/ConsoleResponder.cs => src/Local/NosSmooth.Comms.Inject/MessageResponders/ConsoleResponder.cs +30 -0
@@ 0,0 1,30 @@
//
//  ConsoleResponder.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.Comms.Data.Responders;
using NosSmooth.Comms.Inject.Messages;
using Remora.Results;

namespace NosSmooth.Comms.Inject.MessageResponders;

/// <inheritdoc />
public class ConsoleResponder : IMessageResponder<ConsoleMessage>
{
    /// <inheritdoc />
    public Task<Result> Respond(ConsoleMessage message, CancellationToken ct = default)
    {
        if (message.Open)
        {
            WinConsole.Initialize(false);
        }
        else
        {
            WinConsole.Close();
        }

        return Task.FromResult(Result.FromSuccess());
    }
}
\ No newline at end of file

A src/Local/NosSmooth.Comms.Inject/Messages/ConsoleMessage.cs => src/Local/NosSmooth.Comms.Inject/Messages/ConsoleMessage.cs +9 -0
@@ 0,0 1,9 @@
//
//  ConsoleMessage.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.Comms.Inject.Messages;

public record ConsoleMessage(bool Open);
\ No newline at end of file

A src/Local/NosSmooth.Comms.Inject/WinConsole.cs => src/Local/NosSmooth.Comms.Inject/WinConsole.cs +152 -0
@@ 0,0 1,152 @@
//
//  WinConsole.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 Microsoft.Win32.SafeHandles;

namespace NosSmooth.Comms.Inject;

/// <summary>
/// A class for managing Windows console.
/// </summary>
internal static class WinConsole
{
    /// <summary>
    /// Initialize a console.
    /// </summary>
    /// <param name="alwaysCreateNewConsole">Whether to create (true) or attach (false) to a console.</param>
    public static void Initialize(bool alwaysCreateNewConsole = true)
    {
#if WINDOWS
        bool consoleAttached = true;
        if (alwaysCreateNewConsole
            || (AttachConsole(ATTACH_PARRENT) == 0
                && Marshal.GetLastWin32Error() != ERROR_ACCESS_DENIED))
        {
            consoleAttached = AllocConsole() != 0;
        }

        if (consoleAttached)
        {
            InitializeOutStream();
            InitializeInStream();
        }
#endif
    }

    /// <summary>
    /// Close a console.
    /// </summary>
    public static void Close()
    {
#if WINDOWS
        FreeConsole();
#endif
    }
#if WINDOWS
    private static void InitializeOutStream()
    {
        var fs = CreateFileStream("CONOUT$", GENERIC_WRITE, FILE_SHARE_WRITE, FileAccess.Write);
        if (fs != null)
        {
            var writer = new StreamWriter(fs) { AutoFlush = true };
            Console.SetOut(writer);
            Console.SetError(writer);
        }
    }

    private static void InitializeInStream()
    {
        var fs = CreateFileStream("CONIN$", GENERIC_READ, FILE_SHARE_READ, FileAccess.Read);
        if (fs != null)
        {
            Console.SetIn(new StreamReader(fs));
        }
    }

    private static FileStream? CreateFileStream
    (
        string name,
        uint win32DesiredAccess,
        uint win32ShareMode,
        FileAccess dotNetFileAccess
    )
    {
        var file = new SafeFileHandle
        (
            CreateFileW
            (
                name,
                win32DesiredAccess,
                win32ShareMode,
                IntPtr.Zero,
                OPEN_EXISTING,
                FILE_ATTRIBUTE_NORMAL,
                IntPtr.Zero
            ),
            true
        );
        if (!file.IsInvalid)
        {
            var fs = new FileStream(file, dotNetFileAccess);
            return fs;
        }
        return null;
    }

    [DllImport
    (
        "kernel32.dll",
        EntryPoint = "AllocConsole",
        SetLastError = true,
        CharSet = CharSet.Auto,
        CallingConvention = CallingConvention.StdCall
    )]
    private static extern int AllocConsole();

    [DllImport
    (
        "kernel32.dll",
        EntryPoint = "AttachConsole",
        SetLastError = true,
        CharSet = CharSet.Auto,
        CallingConvention = CallingConvention.StdCall
    )]
    private static extern uint AttachConsole(uint dwProcessId);

    [DllImport
    (
        "kernel32.dll",
        EntryPoint = "CreateFileW",
        SetLastError = true,
        CharSet = CharSet.Auto,
        CallingConvention = CallingConvention.StdCall
    )]
    private static extern IntPtr CreateFileW
    (
        string lpFileName,
        uint dwDesiredAccess,
        uint dwShareMode,
        IntPtr lpSecurityAttributes,
        uint dwCreationDisposition,
        uint dwFlagsAndAttributes,
        IntPtr hTemplateFile
    );

    [DllImport("kernel32")]
    private static extern bool FreeConsole();
#endif

    private const uint GENERIC_WRITE = 0x40000000;
    private const uint GENERIC_READ = 0x80000000;
    private const uint FILE_SHARE_READ = 0x00000001;
    private const uint FILE_SHARE_WRITE = 0x00000002;
    private const uint OPEN_EXISTING = 0x00000003;
    private const uint FILE_ATTRIBUTE_NORMAL = 0x80;
    private const uint ERROR_ACCESS_DENIED = 5;

    private const uint ATTACH_PARRENT = 0xFFFFFFFF;
}
\ No newline at end of file

M src/Local/NosSmooth.Comms.Local/CommsInjector.cs => src/Local/NosSmooth.Comms.Local/CommsInjector.cs +36 -7
@@ 9,6 9,7 @@ using Microsoft.Extensions.DependencyInjection;
using NosSmooth.Comms.Core;
using NosSmooth.Comms.Core.NamedPipes;
using NosSmooth.Comms.Data;
using NosSmooth.Comms.Inject;
using NosSmooth.Injector;
using NosSmooth.LocalBinding;
using NosSmooth.LocalBinding.Options;


@@ 124,13 125,7 @@ public class CommsInjector
    public async Task<Result<Comms>> EstablishNamedPipesConnectionAsync
        (Process process, CancellationToken stopToken, CancellationToken ct)
    {
        var injectResult = _injector.Inject
        (
            process,
            Path.GetFullPath("NosSmooth.Comms.Inject.dll"),
            "NosSmooth.Comms.Inject.DllMain, NosSmooth.Comms.Inject",
            "EnableNamedPipes"
        );
        var injectResult = Inject(process, nameof(DllMain.EnableNamedPipes));
        if (!injectResult.IsSuccess)
        {
            return Result<Comms>.FromError(injectResult);


@@ 151,4 146,38 @@ public class CommsInjector
        var nostaleClient = _resolver.Resolve(handler);
        return new Comms(process, handler, nostaleClient);
    }

    /// <summary>
    /// Open a console in the target process.
    /// </summary>
    /// <remarks>
    /// Log of inject will be printed to the console.
    /// </remarks>
    /// <param name="process">The process.</param>
    /// <returns>A result that may or may not have succeeded.</returns>
    public Result OpenConsole(Process process)
    {
        return Inject(process, nameof(DllMain.OpenConsole));
    }

    /// <summary>
    /// Close a console in the target process.
    /// </summary>
    /// <param name="process">The process.</param>
    /// <returns>A result that may or may not have succeeded.</returns>
    public Result CloseConsole(Process process)
    {
        return Inject(process, nameof(DllMain.CloseConsole));
    }

    private Result Inject(Process process, string method)
    {
        return _injector.Inject
        (
            process,
            Path.GetFullPath("NosSmooth.Comms.Inject.dll"),
            "NosSmooth.Comms.Inject.DllMain, NosSmooth.Comms.Inject",
            method
        );
    }
}
\ No newline at end of file

Do not follow this link