Add configuration file and debugging features to launcher.

This commit is contained in:
Ray Koopa 2019-01-01 22:04:07 +01:00
parent 35946c8e84
commit e5352cadf2
8 changed files with 389 additions and 17 deletions

View File

@ -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);
}
}

View File

@ -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
{
/// <summary>
/// Represents a Win32 process providing specific functionality not available in the .NET <see cref="Process"/>
/// class.
/// </summary>
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
}
}

View File

@ -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)
}

View File

@ -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<Config>();
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);
}
}
}

View File

@ -6,14 +6,22 @@
<AssemblyTitle>Online Worms Launcher</AssemblyTitle>
<Authors>Syroot</Authors>
<Copyright>(c) Syroot, licensed under MIT</Copyright>
<DebugType Condition="'$(Configuration)'=='Release'">None</DebugType>
<Description>Game launcher for Online Worms</Description>
<OutputType>WinExe</OutputType>
<ProductName>Online Worms Launcher</ProductName>
<TargetFrameworks>net461</TargetFrameworks>
<Version>0.1.0-alpha1</Version>
<ApplicationManifest>app.manifest</ApplicationManifest>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Costura.Fody" Version="3.2.1" />
<ProjectReference Include="..\Syroot.Worms\Syroot.Worms.csproj" />
<PackageReference Include="Costura.Fody" Version="3.2.1" />
<PackageReference Include="Microsoft.Extensions.Configuration" Version="2.2.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="2.2.0" />
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="2.2.0" />
<ProjectReference Include="..\Syroot.Worms\Syroot.Worms.csproj" />
</ItemGroup>
<ItemGroup>
<Reference Include="System.Windows.Forms" />
</ItemGroup>
</Project>

View File

@ -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
}
}
}

View File

@ -0,0 +1,24 @@
using System;
namespace Syroot.Worms.OnlineWorms.Launcher.WinApi
{
/// <summary>
/// Represents WinAPI definitions from WinBase.h.
/// </summary>
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
}
}
}

View File

@ -0,0 +1,72 @@
<?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">
<!-- UAC Manifest Options
If you want to change the Windows User Account Control level replace the
requestedExecutionLevel node with one of the following.
<requestedExecutionLevel level="asInvoker" uiAccess="false" />
<requestedExecutionLevel level="requireAdministrator" uiAccess="false" />
<requestedExecutionLevel level="highestAvailable" uiAccess="false" />
Specifying requestedExecutionLevel element will disable file and registry virtualization.
Remove this element if your application requires this virtualization for backwards
compatibility.
-->
<requestedExecutionLevel level="asInvoker" uiAccess="false" />
</requestedPrivileges>
</security>
</trustInfo>
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
<application>
<!-- A list of the Windows versions that this application has been tested on
and is designed to work with. Uncomment the appropriate elements
and Windows will automatically select the most compatible environment. -->
<!-- Windows Vista -->
<!--<supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}" />-->
<!-- Windows 7 -->
<!--<supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}" />-->
<!-- Windows 8 -->
<!--<supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}" />-->
<!-- Windows 8.1 -->
<!--<supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}" />-->
<!-- Windows 10 -->
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
</application>
</compatibility>
<!-- Indicates that the application is DPI-aware and will not be automatically scaled by Windows at higher
DPIs. Windows Presentation Foundation (WPF) applications are automatically DPI-aware and do not need
to opt in. Windows Forms applications targeting .NET Framework 4.6 that opt into this setting, should
also set the 'EnableWindowsFormsHighDpiAutoResizing' setting to 'true' in their app.config. -->
<application xmlns="urn:schemas-microsoft-com:asm.v3">
<windowsSettings>
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true</dpiAware>
</windowsSettings>
</application>
<!-- Enable themes for Windows common controls and dialogs (Windows XP and later) -->
<dependency>
<dependentAssembly>
<assemblyIdentity
type="win32"
name="Microsoft.Windows.Common-Controls"
version="6.0.0.0"
processorArchitecture="*"
publicKeyToken="6595b64144ccf1df"
language="*"
/>
</dependentAssembly>
</dependency>
</assembly>