From e5352cadf25075766a0887675d5817098bae05b9 Mon Sep 17 00:00:00 2001 From: Ray Koopa Date: Tue, 1 Jan 2019 22:04:07 +0100 Subject: [PATCH] Add configuration file and debugging features to launcher. --- .../Config.cs | 23 ++++ .../NativeProcess.cs | 98 ++++++++++++++++ .../OWLauncherConfig.json | 16 +++ .../Program.cs | 56 +++++++--- .../Syroot.Worms.OnlineWorms.Launcher.csproj | 12 +- .../WinApi/Kernel32.cs | 105 ++++++++++++++++++ .../WinApi/WinBase.cs | 24 ++++ .../app.manifest | 72 ++++++++++++ 8 files changed, 389 insertions(+), 17 deletions(-) create mode 100644 src/Syroot.Worms.OnlineWorms.Launcher/Config.cs create mode 100644 src/Syroot.Worms.OnlineWorms.Launcher/NativeProcess.cs create mode 100644 src/Syroot.Worms.OnlineWorms.Launcher/OWLauncherConfig.json create mode 100644 src/Syroot.Worms.OnlineWorms.Launcher/WinApi/Kernel32.cs create mode 100644 src/Syroot.Worms.OnlineWorms.Launcher/WinApi/WinBase.cs create mode 100644 src/Syroot.Worms.OnlineWorms.Launcher/app.manifest diff --git a/src/Syroot.Worms.OnlineWorms.Launcher/Config.cs b/src/Syroot.Worms.OnlineWorms.Launcher/Config.cs new file mode 100644 index 0000000..e65053f --- /dev/null +++ b/src/Syroot.Worms.OnlineWorms.Launcher/Config.cs @@ -0,0 +1,23 @@ +using System.Net; + +namespace Syroot.Worms.OnlineWorms.Launcher +{ + internal class Config + { + // ---- PROPERTIES --------------------------------------------------------------------------------------------- + + public string ServerIP { get; set; } + public ushort ServerPort { get; set; } + + public string UserName { get; set; } + public string Password { get; set; } + public int PasswordKey { get; set; } + + public string ExecutablePath { get; set; } = "DWait.exe"; + public string ExecutableArgs { get; set; } + public string MappingName { get; set; } = "KG1234567890"; + public bool StartSuspended { get; set; } + + internal IPEndPoint ServerEndPoint => new IPEndPoint(IPAddress.Parse(ServerIP), ServerPort); + } +} diff --git a/src/Syroot.Worms.OnlineWorms.Launcher/NativeProcess.cs b/src/Syroot.Worms.OnlineWorms.Launcher/NativeProcess.cs new file mode 100644 index 0000000..b286a34 --- /dev/null +++ b/src/Syroot.Worms.OnlineWorms.Launcher/NativeProcess.cs @@ -0,0 +1,98 @@ +using System; +using System.ComponentModel; +using static Syroot.Worms.OnlineWorms.Launcher.WinApi.Kernel32; +using static Syroot.Worms.OnlineWorms.Launcher.WinApi.WinBase; +using static Syroot.Worms.OnlineWorms.Launcher.WinApi.WinBase.WaitResult; + +namespace Syroot.Worms.OnlineWorms.Launcher +{ + /// + /// Represents a Win32 process providing specific functionality not available in the .NET + /// class. + /// + internal class NativeProcess : IDisposable + { + // ---- FIELDS ------------------------------------------------------------------------------------------------- + + private readonly IntPtr _processHandle; + private readonly IntPtr _mainThreadHandle; + private bool _disposed; + + // ---- CONSTRUCTORS & DESTRUCTOR ------------------------------------------------------------------------------ + + internal NativeProcess(string executablePath, string commandLine, string currentDirectory = null, + NativeProcessFlags flags = NativeProcessFlags.None) + { + commandLine = String.Join(" ", executablePath, commandLine).TrimEnd(); + + ProcessCreationFlags creationFlags = ProcessCreationFlags.None; + if (flags.HasFlag(NativeProcessFlags.StartSuspended)) + creationFlags |= ProcessCreationFlags.CREATE_SUSPENDED; + + STARTUPINFO startupInfo = new STARTUPINFO(); + + if (!CreateProcess(null, commandLine, IntPtr.Zero, IntPtr.Zero, + flags.HasFlag(NativeProcessFlags.InheritHandles), creationFlags, IntPtr.Zero, currentDirectory, + ref startupInfo, out PROCESS_INFORMATION processInfo)) + throw new InvalidOperationException("Could not create process.", new Win32Exception()); + + _processHandle = processInfo.hProcess; + _mainThreadHandle = processInfo.hThread; + } + + ~NativeProcess() + { + // Do not change this code. Put cleanup code in Dispose(bool disposing). + Dispose(false); + } + + // ---- METHODS (PUBLIC) --------------------------------------------------------------------------------------- + + // This code added to correctly implement the disposable pattern. + public void Dispose() + { + // Do not change this code. Put cleanup code in Dispose(bool disposing). + Dispose(true); + GC.SuppressFinalize(this); + } + + // ---- METHODS (INTERNAL) ------------------------------------------------------------------------------------- + + internal void Resume() + { + if (ResumeThread(_mainThreadHandle) == UInt32.MaxValue) + throw new InvalidOperationException("Could not resume process.", new Win32Exception()); + } + + internal void Terminate() + { + if (!TerminateProcess(_processHandle, 0)) + throw new InvalidOperationException("Could not terminate process.", new Win32Exception()); + } + + internal void WaitForExit() + { + if (WaitForSingleObject(_processHandle, INFINITE) == WAIT_FAILED) + throw new InvalidOperationException("Could not wait for process.", new Win32Exception()); + } + + // ---- METHODS (PROTECTED) ------------------------------------------------------------------------------------ + + protected virtual void Dispose(bool disposing) + { + if (!_disposed) + { + CloseHandle(_processHandle); + _disposed = true; + } + } + } + + [Flags] + internal enum NativeProcessFlags + { + None = 0, + InheritHandles = 1 << 0, + StartSuspended = 1 << 1 + } +} diff --git a/src/Syroot.Worms.OnlineWorms.Launcher/OWLauncherConfig.json b/src/Syroot.Worms.OnlineWorms.Launcher/OWLauncherConfig.json new file mode 100644 index 0000000..0a3ebab --- /dev/null +++ b/src/Syroot.Worms.OnlineWorms.Launcher/OWLauncherConfig.json @@ -0,0 +1,16 @@ +{ + // Server connection + "ServerIP": "127.0.0.1", + "ServerPort": "17022", + + // Autologin credentials (optional) + "UserName": "UserName", + "Password": "Password", + + // Debugging settings (optional) + "PasswordKey": "0", // base value to encrypt and decrypt password in launch file mapping + "ExecutablePath": "DWait.exe", // relative or absolute path to game executable + "ExecutableArgs": "", // additional arguments appended to command line + "MappingName": "KG1234567890", // launch file mapping identifier used and passed as first argument to game + "StartSuspended": "false" // true to create, but wait for confirmation to run the game process (to attach debuggers) +} \ No newline at end of file diff --git a/src/Syroot.Worms.OnlineWorms.Launcher/Program.cs b/src/Syroot.Worms.OnlineWorms.Launcher/Program.cs index 7f8f823..0049f14 100644 --- a/src/Syroot.Worms.OnlineWorms.Launcher/Program.cs +++ b/src/Syroot.Worms.OnlineWorms.Launcher/Program.cs @@ -1,38 +1,64 @@ using System; -using System.Diagnostics; using System.IO; -using System.Net; +using System.Windows.Forms; +using Microsoft.Extensions.Configuration; namespace Syroot.Worms.OnlineWorms.Launcher { internal class Program { - // ---- CONSTANTS ---------------------------------------------------------------------------------------------- - - private const string _executableName = "DWait.exe"; - private const string _configMapName = "KG1234567890"; // must start with "KG" and have 12 characters. - // ---- METHODS (PRIVATE) -------------------------------------------------------------------------------------- private static void Main(string[] args) { try { - if (!File.Exists(_executableName)) - throw new FileNotFoundException($"Could not find game executable \"{Path.GetFullPath(_executableName)}\"."); + // Create and read the configuration. + Config config = new ConfigurationBuilder() + .AddJsonFile("OWLauncherConfig.json", true) + .Build() + .Get(); - using (new LaunchConfig + // Create the launch configuration. + LaunchConfig launchConfig = new LaunchConfig { - ServerEndPoint = new IPEndPoint(IPAddress.Loopback, 17022), - UserName = "Ray" - }.CreateMappedFile(_configMapName)) + ServerEndPoint = config.ServerEndPoint, + UserName = config.UserName + }; + launchConfig.SetPassword(config.Password, config.PasswordKey); + + // Map the launch configuration to pass it to the process. + using (launchConfig.CreateMappedFile(config.MappingName)) { - Process.Start(_executableName, _configMapName).WaitForExit(); + // Create the process. + string commandLine = String.Join(" ", config.MappingName, config.ExecutableArgs).TrimEnd(); + string directory = Path.GetDirectoryName(config.ExecutablePath); + if (String.IsNullOrEmpty(directory)) + directory = null; + NativeProcessFlags flags = NativeProcessFlags.InheritHandles; + if (config.StartSuspended) + flags |= NativeProcessFlags.StartSuspended; + NativeProcess process = new NativeProcess(config.ExecutablePath, commandLine, directory, flags); + if (config.StartSuspended) + { + if (MessageBox.Show("Game process is paused. Click OK to resume or Cancel to kill it.", + Application.ProductName, MessageBoxButtons.OKCancel, MessageBoxIcon.Information) + == DialogResult.OK) + { + process.Resume(); + } + else + { + process.Terminate(); + } + } + + process.WaitForExit(); } } catch (Exception ex) { - Console.WriteLine(ex.Message); + Console.WriteLine(ex); } } } diff --git a/src/Syroot.Worms.OnlineWorms.Launcher/Syroot.Worms.OnlineWorms.Launcher.csproj b/src/Syroot.Worms.OnlineWorms.Launcher/Syroot.Worms.OnlineWorms.Launcher.csproj index 285d533..21f9e03 100644 --- a/src/Syroot.Worms.OnlineWorms.Launcher/Syroot.Worms.OnlineWorms.Launcher.csproj +++ b/src/Syroot.Worms.OnlineWorms.Launcher/Syroot.Worms.OnlineWorms.Launcher.csproj @@ -6,14 +6,22 @@ Online Worms Launcher Syroot (c) Syroot, licensed under MIT + None Game launcher for Online Worms WinExe Online Worms Launcher net461 0.1.0-alpha1 + app.manifest - - + + + + + + + + diff --git a/src/Syroot.Worms.OnlineWorms.Launcher/WinApi/Kernel32.cs b/src/Syroot.Worms.OnlineWorms.Launcher/WinApi/Kernel32.cs new file mode 100644 index 0000000..7d27dd8 --- /dev/null +++ b/src/Syroot.Worms.OnlineWorms.Launcher/WinApi/Kernel32.cs @@ -0,0 +1,105 @@ +using System; +using System.Runtime.InteropServices; +using static Syroot.Worms.OnlineWorms.Launcher.WinApi.WinBase; + +namespace Syroot.Worms.OnlineWorms.Launcher.WinApi +{ + internal static class Kernel32 + { + // ---- METHODS (INTERNAL) ------------------------------------------------------------------------------------- + + [DllImport(nameof(Kernel32), SetLastError = true)] + internal static extern bool CloseHandle(IntPtr hObject); + + [DllImport(nameof(Kernel32), SetLastError = true)] + internal static extern bool CreateProcess(string lpApplicationName, string lpCommandLine, + IntPtr lpProcessAttributes, IntPtr lpThreadAttributes, bool bInheritHandles, + ProcessCreationFlags dwCreationFlags, IntPtr lpEnvironment, string lpCurrentDirectory, + ref STARTUPINFO lpStartupInfo, out PROCESS_INFORMATION lpProcessInformation); + + [DllImport(nameof(Kernel32), SetLastError = true)] + internal static extern uint ResumeThread(IntPtr hThread); + + [DllImport(nameof(Kernel32), SetLastError = true)] + internal static extern bool TerminateProcess(IntPtr hProcess, uint uExitCode); + + [DllImport(nameof(Kernel32), SetLastError = true)] + internal static extern WaitResult WaitForSingleObject(IntPtr hHandle, uint dwMilliseconds); + + // ---- CLASSES, STRUCTS & ENUMS ------------------------------------------------------------------------------- + + internal struct PROCESS_INFORMATION + { + internal IntPtr hProcess; + internal IntPtr hThread; + internal uint dwProcessId; + internal uint dwThreadId; + }; + + internal struct STARTUPINFO + { + internal uint cb; + internal string lpReserved; + internal string lpDesktop; + internal string lpTitle; + internal uint dwX; + internal uint dwY; + internal uint dwXSize; + internal uint dwYSize; + internal uint dwXCountChars; + internal uint dwYCountChars; + internal uint dwFillAttribute; + internal uint dwFlags; + internal ushort wShowWindow; + internal ushort cbReserved2; + internal IntPtr lpReserved2; + internal IntPtr hStdInput; + internal IntPtr hStdOutput; + internal IntPtr hStdError; + }; + + internal enum ProcessCreationFlags : uint + { + None, + + DEBUG_PROCESS = 1 << 0, + DEBUG_ONLY_THIS_PROCESS = 1 << 1, + CREATE_SUSPENDED = 1 << 2, + DETACHED_PROCESS = 1 << 3, + + CREATE_NEW_CONSOLE = 1 << 4, + NORMAL_PRIORITY_CLASS = 1 << 5, + IDLE_PRIORITY_CLASS = 1 << 6, + HIGH_PRIORITY_CLASS = 1 << 7, + + REALTIME_PRIORITY_CLASS = 1 << 8, + CREATE_NEW_PROCESS_GROUP = 1 << 9, + CREATE_UNICODE_ENVIRONMENT = 1 << 10, + CREATE_SEPARATE_WOW_VDM = 1 << 11, + + CREATE_SHARED_WOW_VDM = 1 << 12, + CREATE_FORCEDOS = 1 << 13, + BELOW_NORMAL_PRIORITY_CLASS = 1 << 14, + ABOVE_NORMAL_PRIORITY_CLASS = 1 << 15, + + INHERIT_PARENT_AFFINITY = 1 << 16, + [Obsolete] INHERIT_CALLER_PRIORITY = 1 << 17, + CREATE_PROTECTED_PROCESS = 1 << 18, + EXTENDED_STARTUPINFO_PRESENT = 1 << 19, + + PROCESS_MODE_BACKGROUND_BEGIN = 1 << 20, + PROCESS_MODE_BACKGROUND_END = 1 << 21, + CREATE_SECURE_PROCESS = 1 << 22, + + CREATE_BREAKAWAY_FROM_JOB = 1 << 24, + CREATE_PRESERVE_CODE_AUTHZ_LEVEL = 1 << 25, + CREATE_DEFAULT_ERROR_MODE = 1 << 26, + CREATE_NO_WINDOW = 1 << 27, + + PROFILE_USER = 1 << 28, + PROFILE_KERNEL = 1 << 29, + PROFILE_SERVER = 1 << 30, + CREATE_IGNORE_SYSTEM_DEFAULT = 1u << 31 + } + } +} diff --git a/src/Syroot.Worms.OnlineWorms.Launcher/WinApi/WinBase.cs b/src/Syroot.Worms.OnlineWorms.Launcher/WinApi/WinBase.cs new file mode 100644 index 0000000..e8e7916 --- /dev/null +++ b/src/Syroot.Worms.OnlineWorms.Launcher/WinApi/WinBase.cs @@ -0,0 +1,24 @@ +using System; + +namespace Syroot.Worms.OnlineWorms.Launcher.WinApi +{ + /// + /// Represents WinAPI definitions from WinBase.h. + /// + internal static class WinBase + { + // ---- CONSTANTS ---------------------------------------------------------------------------------------------- + + internal const uint INFINITE = UInt32.MaxValue; + + // ---- CLASSES, STRUCTS & ENUMS ------------------------------------------------------------------------------- + + internal enum WaitResult : uint + { + WAIT_OBJECT_0 = 0, + WAIT_ABANDONED = 1 << 7, + WAIT_TIMEOUT = 258, + WAIT_FAILED = UInt32.MaxValue + } + } +} diff --git a/src/Syroot.Worms.OnlineWorms.Launcher/app.manifest b/src/Syroot.Worms.OnlineWorms.Launcher/app.manifest new file mode 100644 index 0000000..577179c --- /dev/null +++ b/src/Syroot.Worms.OnlineWorms.Launcher/app.manifest @@ -0,0 +1,72 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + true + + + + + + + + + + +