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
+
+
+
+
+
+
+
+
+
+
+