From 6c7a610350b3590d99556c10e558d5a612a8d3ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Franti=C5=A1ek=20Boh=C3=A1=C4=8Dek?= Date: Sat, 4 Feb 2023 18:37:03 +0100 Subject: [PATCH] feat: add possibility to open or close a console in NosTale process --- src/Local/NosSmooth.Comms.Inject/DllMain.cs | 30 ++-- .../MessageResponders/ConsoleResponder.cs | 30 ++++ .../Messages/ConsoleMessage.cs | 9 ++ .../NosSmooth.Comms.Inject/WinConsole.cs | 152 ++++++++++++++++++ .../NosSmooth.Comms.Local/CommsInjector.cs | 43 ++++- 5 files changed, 248 insertions(+), 16 deletions(-) create mode 100644 src/Local/NosSmooth.Comms.Inject/MessageResponders/ConsoleResponder.cs create mode 100644 src/Local/NosSmooth.Comms.Inject/Messages/ConsoleMessage.cs create mode 100644 src/Local/NosSmooth.Comms.Inject/WinConsole.cs diff --git a/src/Local/NosSmooth.Comms.Inject/DllMain.cs b/src/Local/NosSmooth.Comms.Inject/DllMain.cs index 2159180..e8c3411 100644 --- a/src/Local/NosSmooth.Comms.Inject/DllMain.cs +++ b/src/Local/NosSmooth.Comms.Inject/DllMain.cs @@ -26,14 +26,10 @@ namespace NosSmooth.Comms.Inject; /// public class DllMain { - private static IHost? _host; + private const uint StdOutputHandle = 0xFFFFFFF5; - /// - /// Allocate console. - /// - /// Whether the operation was successful. - [DllImport("kernel32")] - public static extern bool AllocConsole(); + private static bool _consoleAllocated; + private static IHost? _host; /// /// Enable named pipes server. @@ -46,14 +42,30 @@ public class DllMain host => { var manager = host.Services.GetRequiredService(); - return manager.RunManagerAsync(host.Services.GetRequiredService().ApplicationStopping); + return manager.RunManagerAsync + (host.Services.GetRequiredService().ApplicationStopping); } ); } + /// + /// Open a console. + /// + public static void OpenConsole() + { + WinConsole.Initialize(false); + } + + /// + /// Close a console. + /// + public static void CloseConsole() + { + WinConsole.Close(); + } + private static void Main(Func> host) { - AllocConsole(); new Thread ( () => diff --git a/src/Local/NosSmooth.Comms.Inject/MessageResponders/ConsoleResponder.cs b/src/Local/NosSmooth.Comms.Inject/MessageResponders/ConsoleResponder.cs new file mode 100644 index 0000000..8d36673 --- /dev/null +++ b/src/Local/NosSmooth.Comms.Inject/MessageResponders/ConsoleResponder.cs @@ -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; + +/// +public class ConsoleResponder : IMessageResponder +{ + /// + public Task 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 diff --git a/src/Local/NosSmooth.Comms.Inject/Messages/ConsoleMessage.cs b/src/Local/NosSmooth.Comms.Inject/Messages/ConsoleMessage.cs new file mode 100644 index 0000000..e3683c4 --- /dev/null +++ b/src/Local/NosSmooth.Comms.Inject/Messages/ConsoleMessage.cs @@ -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 diff --git a/src/Local/NosSmooth.Comms.Inject/WinConsole.cs b/src/Local/NosSmooth.Comms.Inject/WinConsole.cs new file mode 100644 index 0000000..1287d59 --- /dev/null +++ b/src/Local/NosSmooth.Comms.Inject/WinConsole.cs @@ -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; + +/// +/// A class for managing Windows console. +/// +internal static class WinConsole +{ + /// + /// Initialize a console. + /// + /// Whether to create (true) or attach (false) to a console. + 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 + } + + /// + /// Close a console. + /// + 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 diff --git a/src/Local/NosSmooth.Comms.Local/CommsInjector.cs b/src/Local/NosSmooth.Comms.Local/CommsInjector.cs index a9e1a16..8dcf648 100644 --- a/src/Local/NosSmooth.Comms.Local/CommsInjector.cs +++ b/src/Local/NosSmooth.Comms.Local/CommsInjector.cs @@ -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> 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.FromError(injectResult); @@ -151,4 +146,38 @@ public class CommsInjector var nostaleClient = _resolver.Resolve(handler); return new Comms(process, handler, nostaleClient); } + + /// + /// Open a console in the target process. + /// + /// + /// Log of inject will be printed to the console. + /// + /// The process. + /// A result that may or may not have succeeded. + public Result OpenConsole(Process process) + { + return Inject(process, nameof(DllMain.OpenConsole)); + } + + /// + /// Close a console in the target process. + /// + /// The process. + /// A result that may or may not have succeeded. + 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 -- 2.48.1