This commit is contained in:
Fabian
2023-07-25 03:48:20 +02:00
parent a2262238f4
commit 1396634079
9 changed files with 89 additions and 108 deletions

View File

@@ -1,13 +0,0 @@
// Copyright (c) Arctium.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace Arctium.WoW.Launcher.Constants;
enum BinaryTypes : uint
{
None = 0000000000,
Pe32 = 0x0000014C,
Pe64 = 0x00008664,
Mach32 = 0xFEEDFACE,
Mach64 = 0xFEEDFACF
}

View File

@@ -10,16 +10,16 @@ class WinMemory
{
public byte[] Data { get; set; }
public nint ProcessHandle { get; }
public nint BaseAddress { get; }
ProcessBasicInformation peb;
readonly Dictionary<string, (long Address, byte[] Data)> patchList;
ProcessBasicInformation _peb;
readonly nint _processHandle;
readonly Dictionary<string, (long Address, byte[] Data)> _patchList;
public WinMemory(ProcessInformation processInformation, long binaryLength)
{
ProcessHandle = processInformation.ProcessHandle;
_processHandle = processInformation.ProcessHandle;
if (processInformation.ProcessHandle == 0)
throw new InvalidOperationException("No valid process found.");
@@ -31,7 +31,7 @@ class WinMemory
Data = Read(BaseAddress, (int)binaryLength);
patchList = new();
_patchList = new Dictionary<string, (long Address, byte[] Data)>();
}
public void RefreshMemoryData(int size)
@@ -53,7 +53,7 @@ class WinMemory
{
var buffer = new byte[8];
if (ReadProcessMemory(ProcessHandle, address, buffer, buffer.Length, out var dummy))
if (ReadProcessMemory(_processHandle, address, buffer, buffer.Length, out var dummy))
return buffer.ToNint();
}
@@ -73,7 +73,7 @@ class WinMemory
{
var buffer = new byte[size];
if (ReadProcessMemory(ProcessHandle, address, buffer, size, out var dummy))
if (ReadProcessMemory(_processHandle, address, buffer, size, out var dummy))
return buffer;
}
@@ -87,10 +87,10 @@ class WinMemory
public byte[] Read(long address, int size) => Read((nint)address, size);
public int ReadDataLength(nint address, string seperator)
public int ReadDataLength(nint address, string separator)
{
var length = 0L;
var seperatorBytes = Encoding.UTF8.GetBytes(seperator).Select(b => (short)b).ToArray();
var seperatorBytes = Encoding.UTF8.GetBytes(separator).Select(b => (short)b).ToArray();
var dataLength = 1000;
// Read in batches here.
@@ -112,12 +112,12 @@ class WinMemory
{
try
{
VirtualProtectEx(ProcessHandle, address, (uint)data.Length, (uint)newProtection, out var oldProtect);
VirtualProtectEx(_processHandle, address, (uint)data.Length, (uint)newProtection, out var oldProtect);
WriteProcessMemory(ProcessHandle, address, data, data.Length, out var written);
WriteProcessMemory(_processHandle, address, data, data.Length, out _);
FlushInstructionCache(ProcessHandle, address, (uint)data.Length);
VirtualProtectEx(ProcessHandle, address, (uint)data.Length, oldProtect, out oldProtect);
FlushInstructionCache(_processHandle, address, (uint)data.Length);
VirtualProtectEx(_processHandle, address, (uint)data.Length, oldProtect, out oldProtect);
}
catch (Exception ex)
@@ -174,7 +174,7 @@ class WinMemory
{
Console.WriteLine($"[{patchName}] Adding...");
patchList[patchName] = (patchOffset, patch);
_patchList[patchName] = (patchOffset, patch);
Console.Write($"[{patchName}]");
Console.ForegroundColor = ConsoleColor.Green;
@@ -183,7 +183,7 @@ class WinMemory
Console.WriteLine();
}
else
patchList[patchName] = (patchOffset, patch);
_patchList[patchName] = (patchOffset, patch);
return Task.CompletedTask;
}
@@ -209,10 +209,10 @@ class WinMemory
return QueuePatch(patchOffset + offsetBase, patch, patchName, printInfo);
}
public bool RemapAndPatch(nint viewAddress, int viewSize)
bool RemapAndPatch(nint viewAddress, int viewSize)
{
// Suspend before remapping to prevent crashes.
NtSuspendProcess(ProcessHandle);
NtSuspendProcess(_processHandle);
Data = Read(viewAddress, viewSize);
@@ -224,13 +224,13 @@ class WinMemory
try
{
if (NtCreateSection(ref newViewHandle, 0xF001F, 0, ref maxSize, 0x40u, 0x8000000 | 0x400000, 0) == NtStatus.Success &&
NtUnmapViewOfSection(ProcessHandle, viewAddress) == NtStatus.Success)
NtUnmapViewOfSection(_processHandle, viewAddress) == NtStatus.Success)
{
var viewBase = viewAddress;
// Map the view with original protections.
var result = NtMapViewOfSection(newViewHandle, ProcessHandle, ref viewBase, 0, (ulong)viewSize, out var viewOffset,
out var newViewSize, 2, 0, (int)MemProtection.ExecuteRead);
var result = NtMapViewOfSection(newViewHandle, _processHandle, ref viewBase, 0, (ulong)viewSize, out _,
out _, 2, 0, (int)MemProtection.ExecuteRead);
if (result == NtStatus.Success)
{
@@ -240,23 +240,24 @@ class WinMemory
nint viewBase2 = 0;
// Create a writable view to write our patches through to preserve the original protections.
result = NtMapViewOfSection(newViewHandle, ProcessHandle, ref viewBase2, 0, (uint)viewSize, out var viewOffset2,
out var newViewSize2, 2, 0, (int)MemProtection.ReadWrite);
result = NtMapViewOfSection(newViewHandle, _processHandle, ref viewBase2, 0, (uint)viewSize, out _,
out _, 2, 0, (int)MemProtection.ReadWrite);
if (result == NtStatus.Success)
{
// Write our patched data trough the writable view to the memory.
if (WriteProcessMemory(ProcessHandle, viewBase2, Data, viewSize, out var dummy))
if (WriteProcessMemory(_processHandle, viewBase2, Data, viewSize, out var dummy))
{
// Unmap them writeable view, it's not longer needed.
NtUnmapViewOfSection(ProcessHandle, viewBase2);
NtUnmapViewOfSection(_processHandle, viewBase2);
// Check if the allocation protections is the right one.
if (VirtualQueryEx(ProcessHandle, BaseAddress, out MemoryBasicInformation mbi, MemoryBasicInformation.Size) != 0 && mbi.AllocationProtect == MemProtection.ExecuteRead)
if (VirtualQueryEx(_processHandle, BaseAddress, out MemoryBasicInformation mbi, MemoryBasicInformation.Size) != 0
&& mbi.AllocationProtect == MemProtection.ExecuteRead)
{
// Also check if we can change the page protection.
if (!VirtualProtectEx(ProcessHandle, BaseAddress, 0x4000, (uint)MemProtection.ReadWrite, out var oldProtect))
NtResumeProcess(ProcessHandle);
if (!VirtualProtectEx(_processHandle, BaseAddress, 0x4000, (uint)MemProtection.ReadWrite, out _))
NtResumeProcess(_processHandle);
return true;
}
@@ -275,14 +276,14 @@ class WinMemory
else
Console.WriteLine("Error while creating the view backup.");
NtResumeProcess(ProcessHandle);
NtResumeProcess(_processHandle);
return false;
}
void ApplyPatches(bool remap)
{
foreach (var p in patchList)
foreach (var p in _patchList)
{
var address = p.Value.Address;
@@ -297,7 +298,7 @@ class WinMemory
if (address < BaseAddress)
address += BaseAddress;
Write(address, patch, MemProtection.ReadWrite);
Write(address, patch);
continue;
}
@@ -314,11 +315,11 @@ class WinMemory
{
if (!remap)
{
ApplyPatches(remap);
ApplyPatches(false);
return true;
}
if (VirtualQueryEx(ProcessHandle, BaseAddress, out MemoryBasicInformation mbi, MemoryBasicInformation.Size) != 0)
if (VirtualQueryEx(_processHandle, BaseAddress, out var mbi, MemoryBasicInformation.Size) != 0)
return RemapAndPatch(mbi.BaseAddress, (int)mbi.RegionSize);
return false;
@@ -329,8 +330,8 @@ class WinMemory
{
try
{
if (NtQueryInformationProcess(processHandle, 0, ref peb, ProcessBasicInformation.Size, out int sizeInfoReturned) == NtStatus.Success)
return Read(peb.PebBaseAddress + 0x10);
if (NtQueryInformationProcess(processHandle, 0, ref _peb, ProcessBasicInformation.Size, out _) == NtStatus.Success)
return Read(_peb.PebBaseAddress + 0x10);
}
catch (Exception ex)
{

View File

@@ -5,7 +5,7 @@ using System.CommandLine.Parsing;
using System.Net.Security;
using System.Net.Sockets;
using System.Security.Authentication;
using System.Security.Cryptography.X509Certificates;
using static Arctium.WoW.Launcher.Misc.Helpers;
namespace Arctium.WoW.Launcher;
@@ -17,7 +17,7 @@ static class Launcher
public static string PrepareGameLaunch(ParseResult commandLineResult, IPFilter ipFilter)
{
var gameVersion = commandLineResult.GetValueForOption(LaunchOptions.Version);
var (SubFolder, BinaryName, MajorGameVersion, MinGameBuild) = gameVersion switch
var (subFolder, binaryName, majorGameVersion, minGameBuild) = gameVersion switch
{
#if x64
GameVersion.Retail => ("_retail_", "Wow.exe", new[] { 9, 10 }, 37862),
@@ -38,26 +38,26 @@ static class Launcher
Console.ResetColor();
var currentFolder = AppDomain.CurrentDomain.BaseDirectory;
var gameFolder = $"{currentFolder}/{SubFolder}";
var gameFolder = $"{currentFolder}/{subFolder}";
if (commandLineResult.HasOption(LaunchOptions.GameBinary))
BinaryName = commandLineResult.GetValueForOption(LaunchOptions.GameBinary);
binaryName = commandLineResult.GetValueForOption(LaunchOptions.GameBinary);
var gameBinaryPath = $"{gameFolder}/{BinaryName}";
var gameBinaryPath = $"{gameFolder}/{binaryName}";
if (commandLineResult.HasOption(LaunchOptions.GamePath))
{
gameFolder = commandLineResult.GetValueForOption(LaunchOptions.GamePath);
gameBinaryPath = $"{gameFolder}/{BinaryName}";
gameBinaryPath = $"{gameFolder}/{binaryName}";
}
else if (!File.Exists(gameBinaryPath))
{
// Also support game installations without branch sub folders.
gameFolder = currentFolder;
gameBinaryPath = $"{gameFolder}/{BinaryName}";
gameBinaryPath = $"{gameFolder}/{binaryName}";
}
if (!File.Exists(gameBinaryPath) || !MajorGameVersion.Contains(GetVersionValueFromClient(gameBinaryPath).Major))
if (!File.Exists(gameBinaryPath) || !majorGameVersion.Contains(GetVersionValueFromClient(gameBinaryPath).Major))
{
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine($"[Error] No {gameVersion} client found.");
@@ -67,11 +67,11 @@ static class Launcher
var gameClientBuild = GetVersionValueFromClient(gameBinaryPath).Build;
if (gameClientBuild < MinGameBuild && gameClientBuild != 0)
if (gameClientBuild < minGameBuild && gameClientBuild != 0)
{
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine($"Your found client version {gameClientBuild} is not supported.");
Console.WriteLine($"The minimum required build is {MinGameBuild}");
Console.WriteLine($"The minimum required build is {minGameBuild}");
return string.Empty;
}
@@ -119,22 +119,25 @@ static class Launcher
{
try
{
var tcpClient = new TcpClient(portal.HostName, portal.Port);
var sslStream = new SslStream(tcpClient.GetStream(), false,
(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors) =>
using var tcpClient = new TcpClient(portal.HostName, portal.Port);
// Cancel after 5 seconds.
tcpClient.ReceiveTimeout = 5000;
tcpClient.SendTimeout = 5000;
using var sslStream = new SslStream(tcpClient.GetStream(), false,
(_, _, _, sslPolicyErrors) =>
{
if (sslPolicyErrors == SslPolicyErrors.None)
{
Console.ForegroundColor = ConsoleColor.Green;
Console.WriteLine($"Certificate for server '{portal.HostName}' successfully validated.");
Console.WriteLine();
Console.ResetColor();
return true;
}
// Redirect to the trusted cert warning.
throw new AuthenticationException();
if (sslPolicyErrors != SslPolicyErrors.None)
throw new AuthenticationException();
Console.ForegroundColor = ConsoleColor.Green;
Console.WriteLine($"Certificate for server '{portal.HostName}' successfully validated.");
Console.WriteLine();
Console.ResetColor();
return true;
},
null
);
@@ -272,13 +275,11 @@ static class Launcher
NativeWindows.NtResumeProcess(processInfo.ProcessHandle);
var antiCrash = false;
// Enable anti crash in dev mode, custom file mode or static auth seed mode.
#if CUSTOM_FILES
antiCrash = true;
var antiCrash = true;
#else
antiCrash = legacyCertMode || commandLineResult.HasOption(LaunchOptions.UseStaticAuthSeed) ||
var antiCrash = legacyCertMode || commandLineResult.HasOption(LaunchOptions.UseStaticAuthSeed) ||
commandLineResult.GetValueForOption(LaunchOptions.DevMode) && LaunchOptions.IsDevModeAllowed;
#endif

View File

@@ -60,17 +60,16 @@ static class Extensions
{
match = data.FindPattern(pattern, match, 0);
if (match != 0)
{
if (!matchList.Contains(match))
matchList.Add(match);
match += pattern.Length;
}
if (match == 0)
continue;
matchList.Add(match);
match += pattern.Length;
} while ((matchList.Count < maxMatches || match < maxOffset) && match != 0);
return matchList;
return matchList;
}
public static short[] ToPattern(this string data) => Encoding.UTF8.GetBytes(data).Select(b => (short)b).ToArray();

View File

@@ -85,7 +85,7 @@ static class Helpers
if (IPAddress.TryParse(portalString, out var ipAddress))
return (ipAddress.ToString(), portalString, port);
var ipv4Address = Dns.GetHostAddresses(portalString).FirstOrDefault(a => a.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork);
var ipv4Address = Dns.GetHostAddresses(portalString).FirstOrDefault(a => a.AddressFamily == AddressFamily.InterNetwork);
if (ipv4Address == null)
throw new Exception("No IPv4 address found for the provided hostname.");
@@ -102,7 +102,9 @@ static class Helpers
public static async Task<bool> CheckUrl(string url, string fallbackUrl)
{
using var httpClient = new HttpClient { Timeout = TimeSpan.FromSeconds(5) };
using var httpClient = new HttpClient();
httpClient.Timeout = TimeSpan.FromSeconds(5);
try
{

View File

@@ -7,12 +7,7 @@ namespace Arctium.WoW.Launcher.Misc;
public class IPFilter
{
readonly ICollection<(IPAddress, IPAddress)> _ipRanges;
public IPFilter()
{
_ipRanges = new List<(IPAddress, IPAddress)>();
}
readonly List<(IPAddress, IPAddress)> _ipRanges = new();
public void AddCidrRange(ReadOnlySpan<char> cidr)
{

View File

@@ -51,10 +51,10 @@ static class NativeWindows
public static extern NtStatus NtUnmapViewOfSection(nint processHandle, nint baseAddress);
[DllImport("ntdll.dll", SetLastError = true)]
public static extern nint NtResumeProcess(nint ProcessHandle);
public static extern nint NtResumeProcess(nint processHandle);
[DllImport("ntdll.dll", SetLastError = true)]
public static extern nint NtSuspendProcess(nint ProcessHandle);
public static extern nint NtSuspendProcess(nint processHandle);
[DllImport("ntdll.dll", SetLastError = true)]
public static extern nint NtClose(nint handle);

View File

@@ -6,7 +6,7 @@ namespace Arctium.WoW.Launcher;
#if x64
class ModLoader
{
static readonly HashSet<uint> loadedFileIds = new();
static readonly HashSet<uint> _loadedFileIds = new();
public static bool HookClient(WinMemory memory, nint processHandle, nint idAlloc, nint stringAlloc)
{
@@ -38,7 +38,7 @@ class ModLoader
memory.QueuePatch(hookAddress, hookInstructions, "CustomFileIdHook");
// Copy count bytes.
Buffer.BlockCopy(BitConverter.GetBytes(loadedFileIds.Count), 0, asm, 35, 4);
Buffer.BlockCopy(BitConverter.GetBytes(_loadedFileIds.Count), 0, asm, 35, 4);
// Copy mapping ptr bytes.
Buffer.BlockCopy(BitConverter.GetBytes(idAlloc), 0, asm, 12, 8);
@@ -121,7 +121,7 @@ class ModLoader
return default;
}
static bool GetFileMappingData(List<(byte[] fileId, byte[] path, uint StringPos)> loadedMappings, out uint count, ref uint stringLength, string mappingFile)
static void GetFileMappingData(List<(byte[] fileId, byte[] path, uint StringPos)> loadedMappings, out uint count, ref uint stringLength, string mappingFile)
{
var mappings = File.ReadAllLines(mappingFile).Where(s => s.Trim() != "")
.Select(s => s.ToLowerInvariant().Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries))
@@ -145,13 +145,13 @@ class ModLoader
{
var id = uint.Parse(fileId);
if (loadedFileIds.Contains(id))
if (_loadedFileIds.Contains(id))
{
Console.WriteLine($"Skipping overlapping file '{id} - {f}'.");
continue;
}
loadedFileIds.Add(id);
_loadedFileIds.Add(id);
loadedMappings.Add((BitConverter.GetBytes(id), pathBytes, stringLength));
@@ -160,11 +160,6 @@ class ModLoader
}
count = (uint)loadedMappings.Count;
if (loadedMappings.Count == 0)
return false;
return true;
}
static (nint, nint) AllocateFileMapping(nint handle, List<(byte[] fileId, byte[] path, uint StringPos)> loadedMappings, uint count, uint stringLength)
@@ -176,11 +171,11 @@ class ModLoader
Parallel.For(0, loadedMappings.Count, m =>
{
var (fileId, path, StringPos) = loadedMappings[m];
var (fileId, path, stringPos) = loadedMappings[m];
Buffer.BlockCopy(fileId, 0, idAllocData, m * 12, fileId.Length);
Buffer.BlockCopy(BitConverter.GetBytes(stringAlloc + StringPos), 0, idAllocData, (m * 12) + 4, 8);
Buffer.BlockCopy(path, 0, stringAllocData, (int)StringPos, path.Length);
Buffer.BlockCopy(BitConverter.GetBytes(stringAlloc + stringPos), 0, idAllocData, (m * 12) + 4, 8);
Buffer.BlockCopy(path, 0, stringAllocData, (int)stringPos, path.Length);
});
NativeWindows.WriteProcessMemory(handle, idAlloc, idAllocData, idAllocData.Length, out var _);

View File

@@ -7,7 +7,7 @@ using Arctium.WoW.Launcher;
using static Arctium.WoW.Launcher.Misc.Helpers;
// "Arctium" should not be removed from the final binary name.
if (!Process.GetCurrentProcess().ProcessName.ToLowerInvariant().Contains("arctium"))
if (!Process.GetCurrentProcess().ProcessName.Contains("arctium", StringComparison.InvariantCultureIgnoreCase))
WaitAndExit();
PrintHeader("WoW Client Launcher");
@@ -28,6 +28,7 @@ LaunchOptions.RootCommand.SetHandler(context =>
});
await LaunchOptions.Instance.InvokeAsync(args);
return;
void CreateDevIPFilter(out IPFilter ipFilter)
{