mirror of
https://github.com/araxiaonline/WoW-Launcher.git
synced 2026-06-13 01:22:21 -04:00
Update launcher to work with latest clients:
- NO dev mode anymore. A valid certificate is ALWAYS required. - NO Arctium server connection. But do not rename things, thanks. - NO other features than connecting to servers with valid certificates attached. Means: NO mod loading or dev mode. For that use our full-featured launcher at https://arctium.io - NO binary releases. - Only modern versions (all branches) are supported. What you can still do: - Launch modern game clients and connect to servers with a valid certificate attached. - Specify custom version and cdns URLs to be able to launch older clients or data from your own CDN.
This commit is contained in:
8
Arctium Game Launcher.slnx
Normal file
8
Arctium Game Launcher.slnx
Normal file
@@ -0,0 +1,8 @@
|
||||
<Solution>
|
||||
<Configurations>
|
||||
<Platform Name="x64" />
|
||||
</Configurations>
|
||||
<Project Path="src/Arctium.Game.Launcher.csproj">
|
||||
<Platform Project="x64" />
|
||||
</Project>
|
||||
</Solution>
|
||||
@@ -1,34 +0,0 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.0.31521.260
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Arctium.WoW.Launcher", "src\Arctium.WoW.Launcher.csproj", "{661B173A-D445-4706-9FF2-C0408ED62FA2}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|x64 = Debug|x64
|
||||
Release|x64 = Release|x64
|
||||
ReleaseCustomFiles|x64 = ReleaseCustomFiles|x64
|
||||
ReleaseCustomFilesSilentMode|x64 = ReleaseCustomFilesSilentMode|x64
|
||||
ReleaseSilentMode|x64 = ReleaseSilentMode|x64
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{661B173A-D445-4706-9FF2-C0408ED62FA2}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{661B173A-D445-4706-9FF2-C0408ED62FA2}.Debug|x64.Build.0 = Debug|x64
|
||||
{661B173A-D445-4706-9FF2-C0408ED62FA2}.Release|x64.ActiveCfg = Release|x64
|
||||
{661B173A-D445-4706-9FF2-C0408ED62FA2}.Release|x64.Build.0 = Release|x64
|
||||
{661B173A-D445-4706-9FF2-C0408ED62FA2}.ReleaseCustomFiles|x64.ActiveCfg = ReleaseCustomFiles|x64
|
||||
{661B173A-D445-4706-9FF2-C0408ED62FA2}.ReleaseCustomFiles|x64.Build.0 = ReleaseCustomFiles|x64
|
||||
{661B173A-D445-4706-9FF2-C0408ED62FA2}.ReleaseCustomFilesSilentMode|x64.ActiveCfg = ReleaseCustomFilesSilentMode|x64
|
||||
{661B173A-D445-4706-9FF2-C0408ED62FA2}.ReleaseCustomFilesSilentMode|x64.Build.0 = ReleaseCustomFilesSilentMode|x64
|
||||
{661B173A-D445-4706-9FF2-C0408ED62FA2}.ReleaseSilentMode|x64.ActiveCfg = ReleaseSilentMode|x64
|
||||
{661B173A-D445-4706-9FF2-C0408ED62FA2}.ReleaseSilentMode|x64.Build.0 = ReleaseSilentMode|x64
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {3B82A525-E7C4-4B51-9C88-3AE74C1AEC60}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
2
LICENSE
2
LICENSE
@@ -1,6 +1,6 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2021-2023 Arctium
|
||||
Copyright (c) 2021-2026 Arctium
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
51
README.md
51
README.md
@@ -1,19 +1,46 @@
|
||||
# THE FUTURE OF THIS LAUNCHER
|
||||
# Arctium Game Launcher (Public version)
|
||||
A game launcher for World of Warcraft that allows you to connect to custom servers with a valid tls certificate attached.
|
||||
|
||||
Binary releases [HERE](https://arctium.io/wow/)
|
||||
**NOTE**: This is a publicly available, stripped down version of the launcher. The **full-featured** launcher is distributed under different terms and is available at https://arctium.io
|
||||
|
||||
As of today (2023-09-30) this project goes closed source and is not longer be MIT licensed.
|
||||
### License, Copyright & Contributions
|
||||
|
||||
This does not affect the license of already published sources.
|
||||
Please see our Open Source project [Documentation Repo](https://github.com/Arctium/Documentation)
|
||||
|
||||
### This launcher is going to be closed source in future.
|
||||
## Special Request <3
|
||||
Please do NOT remove the name `arctium` from the final binary.
|
||||
Blizzard filters their crash logs based on localhost and the string `arctium` in the binary name.
|
||||
|
||||
What does this mean?
|
||||
### Supported Clients
|
||||
* 1.14.4 or later
|
||||
* 3.4.2 or later
|
||||
* 10.1.5 or later
|
||||
|
||||
- **No new MIT licensed binary releases.**
|
||||
- **No new features, game support updates or OS updates on this repo.**
|
||||
- **Current source with the listed supported game versions and supported systems stays public as it is.**
|
||||
- **Future releases with updates for new game versions, operating systems, ... will be available at the same link (https://arctium.io/wow)**
|
||||
**Later = all future version in that branch.**
|
||||
|
||||
#### Old README
|
||||
Most recent README available here: [README](README_OLD.md)
|
||||
### NOTE FOR SERVER CONNECTIONS
|
||||
* A valid certificate matching your authentication/bnet server host name.
|
||||
That certificate needs to be loaded by the authentication/bnet server too
|
||||
|
||||
### Binary Releases
|
||||
There are no binary releases of this version. Our **full-featured** launcher has binary releases at https://arctium.io
|
||||
|
||||
## Building
|
||||
|
||||
### Build Prerequisites
|
||||
* [.NET Core SDK 10.0.0 or later](https://dotnet.microsoft.com/download/dotnet/10.0)
|
||||
* Optional for native builds: C++ workload through Visual Studio 2026 or latest C++ build tools
|
||||
|
||||
### Build Instructions Windows (native)
|
||||
* Execute `dotnet publish -r win-x64 -c Configuration -p:platform="x64" -p:PublishAot=true`
|
||||
* Native output is placed in the `build` folder.
|
||||
|
||||
## Usage
|
||||
|
||||
### Windows Usage
|
||||
1. Copy `Actium Game Launcher.exe` to your World of Warcraft folder.
|
||||
2. Edit the `WTF/Config.wtf` to set your portal or use a different config file with the `-config Config2.wtf` launch arg.
|
||||
3. Run the `Actium Game Launcher.exe`
|
||||
|
||||
### Launch Parameters
|
||||
Use `--help`
|
||||
|
||||
@@ -1,70 +0,0 @@
|
||||
# WoW-Launcher
|
||||
A game launcher for World of Warcraft that allows you to connect to custom servers.
|
||||
|
||||
### License, Copyright & Contributions
|
||||
|
||||
Please see our Open Source project [Documentation Repo](https://github.com/Arctium/Documentation)
|
||||
|
||||
## Special Request <3
|
||||
|
||||
Please do NOT remove the name `arctium` from the final binary.
|
||||
Blizzard filters their crash logs based on localhost and the string `arctium` in the binary name.
|
||||
|
||||
### NOTE FOR LOCAL DEVELOPMENT & SERVER CONNECTIONS
|
||||
#### Applies to: 1.14.4 or later, 3.4.2 or later, 10.1.5 or later
|
||||
* **Dev Mode is enabled by default for local game portals.**
|
||||
* LOCAL HOSTNAME & IP: `REQUIRES` the `--dev` command line parameter to force the dev mode to avoid issues with invalid certificate chains. The launcher auto detectes local addresses and enables it.
|
||||
* EXTERNAL HOSTNAME:
|
||||
* `DO NOT` use the `--dev` command line parameter. It's blocked by the launcher.
|
||||
* `USE` a valid certificate matching your authentication/bnet server host name.
|
||||
* That certificate needs to be loaded by the authentication/bnet server too.
|
||||
* EXTERNAL IP: `NOT SUPPORTED`
|
||||
|
||||
### Binary Releases
|
||||
You can find signed binary releases at [Releases](https://github.com/Arctium/WoW-Launcher/releases)
|
||||
|
||||
### Supported Game Versions (Windows x86 64 bit, Release)
|
||||
* Dragonflight: 10.x
|
||||
* Shadowlands: 9.x
|
||||
* Classic BC/WotLK: 2.5.x, 3.4.x (--version Classic)
|
||||
* Classic Era: 1.14.x (--version ClassicEra)
|
||||
|
||||
## Building
|
||||
|
||||
### Build Prerequisites
|
||||
* [.NET Core SDK 7.0.0 or later](https://dotnet.microsoft.com/download/dotnet/7.0)
|
||||
* Optional for native builds: C++ workload through Visual Studio 2022 or latest C++ build tools
|
||||
|
||||
### Build Instructions Windows (native)
|
||||
* Available runtime identifiers/platforms: win-x64/x64, win-arm64/ARM64
|
||||
* Available release configurations: Release, ReleaseSilentMode, ReleaseCustomFiles, ReleaseCustomFilesSilentMode
|
||||
* Execute `dotnet publish -r RuntimeIdentifier -c Configuration -p:platform="Platform"`
|
||||
* Native output is placed in `build\Configuration\bin\native`
|
||||
|
||||
## Usage
|
||||
|
||||
### Windows Usage
|
||||
1. Copy `Actium WoW Launcher.exe` to your World of Warcraft folder.
|
||||
2. Optional: Edit the `WTF/Config.wtf` to set your portal or use a different config file with the `-config Config2.wtf` launch arg.
|
||||
3. Run the `Actium WoW Launcher.exe`
|
||||
|
||||
### Static Auth Seed Usage
|
||||
* Use the --staticseed launch parameter
|
||||
* On server side add `179D3DC3235629D07113A9B3867F97A7` as auth seed in the database.
|
||||
|
||||
### Custom File Loading Usage
|
||||
1. Get or create your own file mapping (.txt) file(s) and place it in the `mappings` folder.
|
||||
File Format: `fileId;filePath`
|
||||
2. Place your custom files (mods) in the `files` folder. Be sure to follow the correct folder structure.
|
||||
|
||||
### File mapping sources
|
||||
* https://github.com/wowdev/wow-listfile
|
||||
|
||||
### Launch Parameters
|
||||
Use `--help`
|
||||
|
||||
## WARNING
|
||||
|
||||
DO NOT USE THIS AS BASE FOR ANY OFFICIAL SERVER TOOLS.
|
||||
IT WILL GET YOU BANNED THERE!!!
|
||||
|
||||
27
src/Arctium.Game.Launcher.csproj
Normal file
27
src/Arctium.Game.Launcher.csproj
Normal file
@@ -0,0 +1,27 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<OutputPath>../build/$(Configuration)/bin</OutputPath>
|
||||
<AssemblyName>Arctium Game Launcher</AssemblyName>
|
||||
<ApplicationIcon>logo.ico</ApplicationIcon>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<Nullable>disable</Nullable>
|
||||
<LangVersion>preview</LangVersion>
|
||||
<Copyright>Arctium</Copyright>
|
||||
<Platforms>x64</Platforms>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<DefineConstants>$(DefineConstants);$(Platform)</DefineConstants>
|
||||
<Configurations>Debug;Release</Configurations>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<ServerGarbageCollection>true</ServerGarbageCollection>
|
||||
<GarbageCollectionAdaptationMode>1</GarbageCollectionAdaptationMode>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="10.0.1" />
|
||||
<PackageReference Include="System.CommandLine" Version="2.0.0-beta4.22272.1" />
|
||||
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -1,70 +0,0 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<OutputPath>../build/$(Configuration)/bin</OutputPath>
|
||||
<AssemblyName>Arctium WoW Launcher</AssemblyName>
|
||||
<ApplicationIcon>logo.ico</ApplicationIcon>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<TreatWarningsAsErrors>True</TreatWarningsAsErrors>
|
||||
<AppendTargetFrameworkToOutputPath>False</AppendTargetFrameworkToOutputPath>
|
||||
<AppendRuntimeIdentifierToOutputPath>False</AppendRuntimeIdentifierToOutputPath>
|
||||
<Nullable>disable</Nullable>
|
||||
<LangVersion>preview</LangVersion>
|
||||
<AssemblyVersion>10.0.0.0</AssemblyVersion>
|
||||
<FileVersion>10.0.0.0</FileVersion>
|
||||
<Copyright>Arctium</Copyright>
|
||||
<Platforms>x64</Platforms>
|
||||
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<DefineConstants>$(DefineConstants);$(Platform)</DefineConstants>
|
||||
<Configurations>Debug;Release;ReleaseSilentMode;ReleaseCustomFiles;ReleaseCustomFilesSilentMode</Configurations>
|
||||
<Optimize>True</Optimize>
|
||||
|
||||
<!-- Native AOT settings -->
|
||||
<PublishAot>true</PublishAot>
|
||||
<RootAllApplicationAssemblies>false</RootAllApplicationAssemblies>
|
||||
<IlcGenerateCompleteTypeMetadata>false</IlcGenerateCompleteTypeMetadata>
|
||||
<IlcGenerateStackTraceData>false</IlcGenerateStackTraceData>
|
||||
<UseSystemResourceKeys>true</UseSystemResourceKeys>
|
||||
<EventSourceSupport>false</EventSourceSupport>
|
||||
<InvariantGlobalization>true</InvariantGlobalization>
|
||||
<IlcScanReflection>false</IlcScanReflection>
|
||||
<IlcFoldIdenticalMethodBodies>true</IlcFoldIdenticalMethodBodies>
|
||||
<IlcTrimMetadata>true</IlcTrimMetadata>
|
||||
<IlcOptimizationPreference>Speed</IlcOptimizationPreference>
|
||||
<GenerateRuntimeConfigurationFiles>false</GenerateRuntimeConfigurationFiles>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)'=='Debug'">
|
||||
<DefineConstants>$(DefineConstants);$(Platform);CUSTOM_FILES</DefineConstants>
|
||||
<Optimize>False</Optimize>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)'=='Release'">
|
||||
<DebugType>none</DebugType>
|
||||
<DebugSymbols>false</DebugSymbols>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)'=='ReleaseCustomFiles'">
|
||||
<DefineConstants>$(DefineConstants);$(Platform);CUSTOM_FILES</DefineConstants>
|
||||
<DebugType>none</DebugType>
|
||||
<DebugSymbols>false</DebugSymbols>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)'=='ReleaseSilentMode'">
|
||||
<OutputType>WinExe</OutputType>
|
||||
<DebugType>none</DebugType>
|
||||
<DebugSymbols>false</DebugSymbols>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)'=='ReleaseCustomFilesSilentMode'">
|
||||
<OutputType>WinExe</OutputType>
|
||||
<DefineConstants>$(DefineConstants);$(Platform);CUSTOM_FILES</DefineConstants>
|
||||
<DebugType>none</DebugType>
|
||||
<DebugSymbols>false</DebugSymbols>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="System.CommandLine" Version="2.0.0-*" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -1,11 +1,13 @@
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
// Copyright (c) Arctium.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
namespace Arctium.WoW.Launcher.Constants;
|
||||
namespace Arctium.Game.Launcher.Constants;
|
||||
|
||||
enum GameVersion
|
||||
{
|
||||
Retail,
|
||||
Classic,
|
||||
ClassicEra
|
||||
ClassicEra,
|
||||
ClassicTitan
|
||||
}
|
||||
|
||||
|
||||
@@ -1,19 +1,9 @@
|
||||
// Copyright (c) Arctium.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
namespace Arctium.WoW.Launcher.Constants;
|
||||
namespace Arctium.Game.Launcher.Constants;
|
||||
|
||||
enum MemProtection
|
||||
{
|
||||
NoAccess = 0x1,
|
||||
ReadOnly = 0x2,
|
||||
ReadWrite = 0x4,
|
||||
WriteCopy = 0x8,
|
||||
Execute = 0x10,
|
||||
ExecuteRead = 0x20,
|
||||
ExecuteReadWrite = 0x40,
|
||||
ExecuteWriteCopy = 0x80,
|
||||
Guard = 0x100,
|
||||
NoCache = 0x200,
|
||||
WriteCombine = 0x400,
|
||||
}
|
||||
|
||||
@@ -1,11 +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 MemState
|
||||
{
|
||||
Commit = 0x1000,
|
||||
Reserve = 0x2000,
|
||||
Free = 0x10000
|
||||
}
|
||||
@@ -1,11 +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 MemType : uint
|
||||
{
|
||||
Private = 0x20000,
|
||||
Mapped = 0x40000,
|
||||
Image = 0x1000000
|
||||
}
|
||||
@@ -1,346 +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 NtStatus : uint
|
||||
{
|
||||
// Success
|
||||
Success = 0x00000000,
|
||||
Wait0 = 0x00000000,
|
||||
Wait1 = 0x00000001,
|
||||
Wait2 = 0x00000002,
|
||||
Wait3 = 0x00000003,
|
||||
Wait63 = 0x0000003f,
|
||||
Abandoned = 0x00000080,
|
||||
AbandonedWait0 = 0x00000080,
|
||||
AbandonedWait1 = 0x00000081,
|
||||
AbandonedWait2 = 0x00000082,
|
||||
AbandonedWait3 = 0x00000083,
|
||||
AbandonedWait63 = 0x000000bf,
|
||||
UserApc = 0x000000c0,
|
||||
KernelApc = 0x00000100,
|
||||
Alerted = 0x00000101,
|
||||
Timeout = 0x00000102,
|
||||
Pending = 0x00000103,
|
||||
Reparse = 0x00000104,
|
||||
MoreEntries = 0x00000105,
|
||||
NotAllAssigned = 0x00000106,
|
||||
SomeNotMapped = 0x00000107,
|
||||
OpLockBreakInProgress = 0x00000108,
|
||||
VolumeMounted = 0x00000109,
|
||||
RxActCommitted = 0x0000010a,
|
||||
NotifyCleanup = 0x0000010b,
|
||||
NotifyEnumDir = 0x0000010c,
|
||||
NoQuotasForAccount = 0x0000010d,
|
||||
PrimaryTransportConnectFailed = 0x0000010e,
|
||||
PageFaultTransition = 0x00000110,
|
||||
PageFaultDemandZero = 0x00000111,
|
||||
PageFaultCopyOnWrite = 0x00000112,
|
||||
PageFaultGuardPage = 0x00000113,
|
||||
PageFaultPagingFile = 0x00000114,
|
||||
CrashDump = 0x00000116,
|
||||
ReparseObject = 0x00000118,
|
||||
NothingToTerminate = 0x00000122,
|
||||
ProcessNotInJob = 0x00000123,
|
||||
ProcessInJob = 0x00000124,
|
||||
ProcessCloned = 0x00000129,
|
||||
FileLockedWithOnlyReaders = 0x0000012a,
|
||||
FileLockedWithWriters = 0x0000012b,
|
||||
|
||||
// Informational
|
||||
Informational = 0x40000000,
|
||||
ObjectNameExists = 0x40000000,
|
||||
ThreadWasSuspended = 0x40000001,
|
||||
WorkingSetLimitRange = 0x40000002,
|
||||
ImageNotAtBase = 0x40000003,
|
||||
RegistryRecovered = 0x40000009,
|
||||
|
||||
// Warning
|
||||
Warning = 0x80000000,
|
||||
GuardPageViolation = 0x80000001,
|
||||
DatatypeMisalignment = 0x80000002,
|
||||
Breakpoint = 0x80000003,
|
||||
SingleStep = 0x80000004,
|
||||
BufferOverflow = 0x80000005,
|
||||
NoMoreFiles = 0x80000006,
|
||||
HandlesClosed = 0x8000000a,
|
||||
PartialCopy = 0x8000000d,
|
||||
DeviceBusy = 0x80000011,
|
||||
InvalidEaName = 0x80000013,
|
||||
EaListInconsistent = 0x80000014,
|
||||
NoMoreEntries = 0x8000001a,
|
||||
LongJump = 0x80000026,
|
||||
DllMightBeInsecure = 0x8000002b,
|
||||
|
||||
// Error
|
||||
Error = 0xc0000000,
|
||||
Unsuccessful = 0xc0000001,
|
||||
NotImplemented = 0xc0000002,
|
||||
InvalidInfoClass = 0xc0000003,
|
||||
InfoLengthMismatch = 0xc0000004,
|
||||
AccessViolation = 0xc0000005,
|
||||
InPageError = 0xc0000006,
|
||||
PagefileQuota = 0xc0000007,
|
||||
InvalidHandle = 0xc0000008,
|
||||
BadInitialStack = 0xc0000009,
|
||||
BadInitialPc = 0xc000000a,
|
||||
InvalidCid = 0xc000000b,
|
||||
TimerNotCanceled = 0xc000000c,
|
||||
InvalidParameter = 0xc000000d,
|
||||
NoSuchDevice = 0xc000000e,
|
||||
NoSuchFile = 0xc000000f,
|
||||
InvalidDeviceRequest = 0xc0000010,
|
||||
EndOfFile = 0xc0000011,
|
||||
WrongVolume = 0xc0000012,
|
||||
NoMediaInDevice = 0xc0000013,
|
||||
NoMemory = 0xc0000017,
|
||||
NotMappedView = 0xc0000019,
|
||||
UnableToFreeVm = 0xc000001a,
|
||||
UnableToDeleteSection = 0xc000001b,
|
||||
IllegalInstruction = 0xc000001d,
|
||||
AlreadyCommitted = 0xc0000021,
|
||||
AccessDenied = 0xc0000022,
|
||||
BufferTooSmall = 0xc0000023,
|
||||
ObjectTypeMismatch = 0xc0000024,
|
||||
NonContinuableException = 0xc0000025,
|
||||
BadStack = 0xc0000028,
|
||||
NotLocked = 0xc000002a,
|
||||
NotCommitted = 0xc000002d,
|
||||
InvalidParameterMix = 0xc0000030,
|
||||
ObjectNameInvalid = 0xc0000033,
|
||||
ObjectNameNotFound = 0xc0000034,
|
||||
ObjectNameCollision = 0xc0000035,
|
||||
ObjectPathInvalid = 0xc0000039,
|
||||
ObjectPathNotFound = 0xc000003a,
|
||||
ObjectPathSyntaxBad = 0xc000003b,
|
||||
DataOverrun = 0xc000003c,
|
||||
DataLate = 0xc000003d,
|
||||
DataError = 0xc000003e,
|
||||
CrcError = 0xc000003f,
|
||||
SectionTooBig = 0xc0000040,
|
||||
PortConnectionRefused = 0xc0000041,
|
||||
InvalidPortHandle = 0xc0000042,
|
||||
SharingViolation = 0xc0000043,
|
||||
QuotaExceeded = 0xc0000044,
|
||||
InvalidPageProtection = 0xc0000045,
|
||||
MutantNotOwned = 0xc0000046,
|
||||
SemaphoreLimitExceeded = 0xc0000047,
|
||||
PortAlreadySet = 0xc0000048,
|
||||
SectionNotImage = 0xc0000049,
|
||||
SuspendCountExceeded = 0xc000004a,
|
||||
ThreadIsTerminating = 0xc000004b,
|
||||
BadWorkingSetLimit = 0xc000004c,
|
||||
IncompatibleFileMap = 0xc000004d,
|
||||
SectionProtection = 0xc000004e,
|
||||
EasNotSupported = 0xc000004f,
|
||||
EaTooLarge = 0xc0000050,
|
||||
NonExistentEaEntry = 0xc0000051,
|
||||
NoEasOnFile = 0xc0000052,
|
||||
EaCorruptError = 0xc0000053,
|
||||
FileLockConflict = 0xc0000054,
|
||||
LockNotGranted = 0xc0000055,
|
||||
DeletePending = 0xc0000056,
|
||||
CtlFileNotSupported = 0xc0000057,
|
||||
UnknownRevision = 0xc0000058,
|
||||
RevisionMismatch = 0xc0000059,
|
||||
InvalidOwner = 0xc000005a,
|
||||
InvalidPrimaryGroup = 0xc000005b,
|
||||
NoImpersonationToken = 0xc000005c,
|
||||
CantDisableMandatory = 0xc000005d,
|
||||
NoLogonServers = 0xc000005e,
|
||||
NoSuchLogonSession = 0xc000005f,
|
||||
NoSuchPrivilege = 0xc0000060,
|
||||
PrivilegeNotHeld = 0xc0000061,
|
||||
InvalidAccountName = 0xc0000062,
|
||||
UserExists = 0xc0000063,
|
||||
NoSuchUser = 0xc0000064,
|
||||
GroupExists = 0xc0000065,
|
||||
NoSuchGroup = 0xc0000066,
|
||||
MemberInGroup = 0xc0000067,
|
||||
MemberNotInGroup = 0xc0000068,
|
||||
LastAdmin = 0xc0000069,
|
||||
WrongPassword = 0xc000006a,
|
||||
IllFormedPassword = 0xc000006b,
|
||||
PasswordRestriction = 0xc000006c,
|
||||
LogonFailure = 0xc000006d,
|
||||
AccountRestriction = 0xc000006e,
|
||||
InvalidLogonHours = 0xc000006f,
|
||||
InvalidWorkstation = 0xc0000070,
|
||||
PasswordExpired = 0xc0000071,
|
||||
AccountDisabled = 0xc0000072,
|
||||
NoneMapped = 0xc0000073,
|
||||
TooManyLuidsRequested = 0xc0000074,
|
||||
LuidsExhausted = 0xc0000075,
|
||||
InvalidSubAuthority = 0xc0000076,
|
||||
InvalidAcl = 0xc0000077,
|
||||
InvalidSid = 0xc0000078,
|
||||
InvalidSecurityDescr = 0xc0000079,
|
||||
ProcedureNotFound = 0xc000007a,
|
||||
InvalidImageFormat = 0xc000007b,
|
||||
NoToken = 0xc000007c,
|
||||
BadInheritanceAcl = 0xc000007d,
|
||||
RangeNotLocked = 0xc000007e,
|
||||
DiskFull = 0xc000007f,
|
||||
ServerDisabled = 0xc0000080,
|
||||
ServerNotDisabled = 0xc0000081,
|
||||
TooManyGuidsRequested = 0xc0000082,
|
||||
GuidsExhausted = 0xc0000083,
|
||||
InvalidIdAuthority = 0xc0000084,
|
||||
AgentsExhausted = 0xc0000085,
|
||||
InvalidVolumeLabel = 0xc0000086,
|
||||
SectionNotExtended = 0xc0000087,
|
||||
NotMappedData = 0xc0000088,
|
||||
ResourceDataNotFound = 0xc0000089,
|
||||
ResourceTypeNotFound = 0xc000008a,
|
||||
ResourceNameNotFound = 0xc000008b,
|
||||
ArrayBoundsExceeded = 0xc000008c,
|
||||
FloatDenormalOperand = 0xc000008d,
|
||||
FloatDivideByZero = 0xc000008e,
|
||||
FloatInexactResult = 0xc000008f,
|
||||
FloatInvalidOperation = 0xc0000090,
|
||||
FloatOverflow = 0xc0000091,
|
||||
FloatStackCheck = 0xc0000092,
|
||||
FloatUnderflow = 0xc0000093,
|
||||
IntegerDivideByZero = 0xc0000094,
|
||||
IntegerOverflow = 0xc0000095,
|
||||
PrivilegedInstruction = 0xc0000096,
|
||||
TooManyPagingFiles = 0xc0000097,
|
||||
FileInvalid = 0xc0000098,
|
||||
InstanceNotAvailable = 0xc00000ab,
|
||||
PipeNotAvailable = 0xc00000ac,
|
||||
InvalidPipeState = 0xc00000ad,
|
||||
PipeBusy = 0xc00000ae,
|
||||
IllegalFunction = 0xc00000af,
|
||||
PipeDisconnected = 0xc00000b0,
|
||||
PipeClosing = 0xc00000b1,
|
||||
PipeConnected = 0xc00000b2,
|
||||
PipeListening = 0xc00000b3,
|
||||
InvalidReadMode = 0xc00000b4,
|
||||
IoTimeout = 0xc00000b5,
|
||||
FileForcedClosed = 0xc00000b6,
|
||||
ProfilingNotStarted = 0xc00000b7,
|
||||
ProfilingNotStopped = 0xc00000b8,
|
||||
NotSameDevice = 0xc00000d4,
|
||||
FileRenamed = 0xc00000d5,
|
||||
CantWait = 0xc00000d8,
|
||||
PipeEmpty = 0xc00000d9,
|
||||
CantTerminateSelf = 0xc00000db,
|
||||
InternalError = 0xc00000e5,
|
||||
InvalidParameter1 = 0xc00000ef,
|
||||
InvalidParameter2 = 0xc00000f0,
|
||||
InvalidParameter3 = 0xc00000f1,
|
||||
InvalidParameter4 = 0xc00000f2,
|
||||
InvalidParameter5 = 0xc00000f3,
|
||||
InvalidParameter6 = 0xc00000f4,
|
||||
InvalidParameter7 = 0xc00000f5,
|
||||
InvalidParameter8 = 0xc00000f6,
|
||||
InvalidParameter9 = 0xc00000f7,
|
||||
InvalidParameter10 = 0xc00000f8,
|
||||
InvalidParameter11 = 0xc00000f9,
|
||||
InvalidParameter12 = 0xc00000fa,
|
||||
MappedFileSizeZero = 0xc000011e,
|
||||
TooManyOpenedFiles = 0xc000011f,
|
||||
Cancelled = 0xc0000120,
|
||||
CannotDelete = 0xc0000121,
|
||||
InvalidComputerName = 0xc0000122,
|
||||
FileDeleted = 0xc0000123,
|
||||
SpecialAccount = 0xc0000124,
|
||||
SpecialGroup = 0xc0000125,
|
||||
SpecialUser = 0xc0000126,
|
||||
MembersPrimaryGroup = 0xc0000127,
|
||||
FileClosed = 0xc0000128,
|
||||
TooManyThreads = 0xc0000129,
|
||||
ThreadNotInProcess = 0xc000012a,
|
||||
TokenAlreadyInUse = 0xc000012b,
|
||||
PagefileQuotaExceeded = 0xc000012c,
|
||||
CommitmentLimit = 0xc000012d,
|
||||
InvalidImageLeFormat = 0xc000012e,
|
||||
InvalidImageNotMz = 0xc000012f,
|
||||
InvalidImageProtect = 0xc0000130,
|
||||
InvalidImageWin16 = 0xc0000131,
|
||||
LogonServer = 0xc0000132,
|
||||
DifferenceAtDc = 0xc0000133,
|
||||
SynchronizationRequired = 0xc0000134,
|
||||
DllNotFound = 0xc0000135,
|
||||
IoPrivilegeFailed = 0xc0000137,
|
||||
OrdinalNotFound = 0xc0000138,
|
||||
EntryPointNotFound = 0xc0000139,
|
||||
ControlCExit = 0xc000013a,
|
||||
PortNotSet = 0xc0000353,
|
||||
DebuggerInactive = 0xc0000354,
|
||||
CallbackBypass = 0xc0000503,
|
||||
PortClosed = 0xc0000700,
|
||||
MessageLost = 0xc0000701,
|
||||
InvalidMessage = 0xc0000702,
|
||||
RequestCanceled = 0xc0000703,
|
||||
RecursiveDispatch = 0xc0000704,
|
||||
LpcReceiveBufferExpected = 0xc0000705,
|
||||
LpcInvalidConnectionUsage = 0xc0000706,
|
||||
LpcRequestsNotAllowed = 0xc0000707,
|
||||
ResourceInUse = 0xc0000708,
|
||||
ProcessIsProtected = 0xc0000712,
|
||||
VolumeDirty = 0xc0000806,
|
||||
FileCheckedOut = 0xc0000901,
|
||||
CheckOutRequired = 0xc0000902,
|
||||
BadFileType = 0xc0000903,
|
||||
FileTooLarge = 0xc0000904,
|
||||
FormsAuthRequired = 0xc0000905,
|
||||
VirusInfected = 0xc0000906,
|
||||
VirusDeleted = 0xc0000907,
|
||||
TransactionalConflict = 0xc0190001,
|
||||
InvalidTransaction = 0xc0190002,
|
||||
TransactionNotActive = 0xc0190003,
|
||||
TmInitializationFailed = 0xc0190004,
|
||||
RmNotActive = 0xc0190005,
|
||||
RmMetadataCorrupt = 0xc0190006,
|
||||
TransactionNotJoined = 0xc0190007,
|
||||
DirectoryNotRm = 0xc0190008,
|
||||
CouldNotResizeLog = 0xc0190009,
|
||||
TransactionsUnsupportedRemote = 0xc019000a,
|
||||
LogResizeInvalidSize = 0xc019000b,
|
||||
RemoteFileVersionMismatch = 0xc019000c,
|
||||
CrmProtocolAlreadyExists = 0xc019000f,
|
||||
TransactionPropagationFailed = 0xc0190010,
|
||||
CrmProtocolNotFound = 0xc0190011,
|
||||
TransactionSuperiorExists = 0xc0190012,
|
||||
TransactionRequestNotValid = 0xc0190013,
|
||||
TransactionNotRequested = 0xc0190014,
|
||||
TransactionAlreadyAborted = 0xc0190015,
|
||||
TransactionAlreadyCommitted = 0xc0190016,
|
||||
TransactionInvalidMarshallBuffer = 0xc0190017,
|
||||
CurrentTransactionNotValid = 0xc0190018,
|
||||
LogGrowthFailed = 0xc0190019,
|
||||
ObjectNoLongerExists = 0xc0190021,
|
||||
StreamMiniversionNotFound = 0xc0190022,
|
||||
StreamMiniversionNotValid = 0xc0190023,
|
||||
MiniversionInaccessibleFromSpecifiedTransaction = 0xc0190024,
|
||||
CantOpenMiniversionWithModifyIntent = 0xc0190025,
|
||||
CantCreateMoreStreamMiniversions = 0xc0190026,
|
||||
HandleNoLongerValid = 0xc0190028,
|
||||
NoTxfMetadata = 0xc0190029,
|
||||
LogCorruptionDetected = 0xc0190030,
|
||||
CantRecoverWithHandleOpen = 0xc0190031,
|
||||
RmDisconnected = 0xc0190032,
|
||||
EnlistmentNotSuperior = 0xc0190033,
|
||||
RecoveryNotNeeded = 0xc0190034,
|
||||
RmAlreadyStarted = 0xc0190035,
|
||||
FileIdentityNotPersistent = 0xc0190036,
|
||||
CantBreakTransactionalDependency = 0xc0190037,
|
||||
CantCrossRmBoundary = 0xc0190038,
|
||||
TxfDirNotEmpty = 0xc0190039,
|
||||
IndoubtTransactionsExist = 0xc019003a,
|
||||
TmVolatile = 0xc019003b,
|
||||
RollbackTimerExpired = 0xc019003c,
|
||||
TxfAttributeCorrupt = 0xc019003d,
|
||||
EfsNotAllowedInTransaction = 0xc019003e,
|
||||
TransactionalOpenNotAllowed = 0xc019003f,
|
||||
TransactedMappingUnsupportedRemote = 0xc0190040,
|
||||
TxfMetadataAlreadyPresent = 0xc0190041,
|
||||
TransactionScopeCallbacksNotSet = 0xc0190042,
|
||||
TransactionRequiredPromotion = 0xc0190043,
|
||||
CannotExecuteFileInTransaction = 0xc0190044,
|
||||
TransactionsNotFrozen = 0xc0190045,
|
||||
|
||||
MaximumNtStatus = 0xffffffff
|
||||
}
|
||||
@@ -1,23 +1,19 @@
|
||||
// Copyright (c) Arctium.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
using static Arctium.WoW.Launcher.Misc.NativeWindows;
|
||||
using static Arctium.WoW.Launcher.Misc.Helpers;
|
||||
using static Arctium.Game.Launcher.Misc.NativeWindows;
|
||||
using static Arctium.Game.Launcher.Misc.Helpers;
|
||||
|
||||
namespace Arctium.WoW.Launcher.IO;
|
||||
namespace Arctium.Game.Launcher.IO;
|
||||
|
||||
class WinMemory
|
||||
{
|
||||
public byte[] Data { get; set; }
|
||||
|
||||
public nint BaseAddress { get; }
|
||||
|
||||
ProcessBasicInformation _peb;
|
||||
|
||||
readonly nint _processHandle;
|
||||
readonly Dictionary<string, (long Address, byte[] Data)> _patchList;
|
||||
|
||||
public WinMemory(ProcessInformation processInformation, long binaryLength)
|
||||
public WinMemory(ProcessInformation processInformation)
|
||||
{
|
||||
_processHandle = processInformation.ProcessHandle;
|
||||
|
||||
@@ -28,22 +24,18 @@ class WinMemory
|
||||
|
||||
if (BaseAddress == 0)
|
||||
throw new InvalidOperationException("Error while reading PEB data.");
|
||||
|
||||
Data = Read(BaseAddress, (int)binaryLength);
|
||||
|
||||
_patchList = new Dictionary<string, (long Address, byte[] Data)>();
|
||||
}
|
||||
|
||||
public void RefreshMemoryData(int size)
|
||||
{
|
||||
// Reset previous memory data.
|
||||
Data = null;
|
||||
if (Data != null)
|
||||
Array.Clear(Data, 0, Data.Length);
|
||||
|
||||
while (Data == null)
|
||||
while (Data == null || Unsafe.ReadUnaligned<long>(ref Data[0]) == 0)
|
||||
{
|
||||
Console.WriteLine("Refreshing client data...");
|
||||
|
||||
Data = Read(BaseAddress, size);
|
||||
ReadToData(BaseAddress, size);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,7 +57,20 @@ class WinMemory
|
||||
return 0;
|
||||
}
|
||||
|
||||
public nint Read(long address) => Read((nint)address);
|
||||
public void ReadToData(nint address, int size)
|
||||
{
|
||||
try
|
||||
{
|
||||
Data ??= new byte[size];
|
||||
|
||||
ReadProcessMemory(_processHandle, address, Data, size, out var dummy);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine(ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public byte[] Read(nint address, int size)
|
||||
{
|
||||
@@ -85,29 +90,6 @@ class WinMemory
|
||||
return null;
|
||||
}
|
||||
|
||||
public byte[] Read(long address, int size) => Read((nint)address, size);
|
||||
|
||||
public int ReadDataLength(nint address, string separator)
|
||||
{
|
||||
var length = 0L;
|
||||
var seperatorBytes = Encoding.UTF8.GetBytes(separator).Select(b => (short)b).ToArray();
|
||||
var dataLength = 1000;
|
||||
|
||||
// Read in batches here.
|
||||
while (length == 0)
|
||||
{
|
||||
length = Read(address, dataLength)?.FindPattern(seperatorBytes) ?? 0;
|
||||
|
||||
dataLength += 1000;
|
||||
|
||||
// Not found!
|
||||
if (dataLength >= 100_000)
|
||||
return -1;
|
||||
}
|
||||
|
||||
return (int)length;
|
||||
}
|
||||
|
||||
public void Write(nint address, byte[] data, MemProtection newProtection = MemProtection.ReadWrite)
|
||||
{
|
||||
try
|
||||
@@ -126,9 +108,24 @@ class WinMemory
|
||||
}
|
||||
}
|
||||
|
||||
public void Write(long address, byte[] data, MemProtection newProtection = MemProtection.ReadWrite) => Write((nint)address, data, newProtection);
|
||||
public async Task TryPatchPatterns(byte[][] patches, string patchName, bool? printInfo = null, bool exitOnFail = true, bool patchAll = false, params short[][] patterns)
|
||||
{
|
||||
if (patches.Length == 0 || patterns.Length == 0)
|
||||
return;
|
||||
|
||||
public Task PatchMemory(short[] pattern, byte[] patch, string patchName, bool? printInfo = null)
|
||||
if (!patchAll && await PatchMemory(patterns[0], patches[0], $"{patchName}", printInfo, exitOnFail))
|
||||
return;
|
||||
|
||||
for (var i = 0; i < patterns.Length; i++)
|
||||
{
|
||||
Console.ResetColor();
|
||||
|
||||
if (await PatchMemory(patterns[i], patches[i], $"{patchName} {i + 1}", printInfo, exitOnFail: false) && !patchAll)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public Task<bool> PatchMemory(short[] pattern, byte[] patch, string patchName, bool? printInfo = null, bool exitOnFail = true)
|
||||
{
|
||||
printInfo ??= IsDebugBuild();
|
||||
|
||||
@@ -140,198 +137,54 @@ class WinMemory
|
||||
// No result for the given pattern.
|
||||
if (patchOffset == 0)
|
||||
{
|
||||
Console.ForegroundColor = ConsoleColor.Yellow;
|
||||
|
||||
Console.WriteLine($"[{patchName}] No result found.");
|
||||
Console.WriteLine("Press any key to exit...");
|
||||
Console.ReadKey();
|
||||
|
||||
Launcher.CancellationTokenSource.Cancel();
|
||||
}
|
||||
|
||||
while (Read(patchOffset, patch.Length)?.SequenceEqual(patch) == false)
|
||||
Write(patchOffset, patch);
|
||||
|
||||
if (printInfo.Value)
|
||||
{
|
||||
Console.Write($"[{patchName}]");
|
||||
Console.ForegroundColor = ConsoleColor.Green;
|
||||
Console.WriteLine(" Done.");
|
||||
Console.ResetColor();
|
||||
Console.WriteLine();
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task QueuePatch(long patchOffset, byte[] patch, string patchName, bool? printInfo = null)
|
||||
{
|
||||
Launcher.CancellationTokenSource.Token.ThrowIfCancellationRequested();
|
||||
|
||||
printInfo ??= IsDebugBuild();
|
||||
|
||||
if (printInfo.Value)
|
||||
{
|
||||
Console.WriteLine($"[{patchName}] Adding...");
|
||||
|
||||
_patchList[patchName] = (patchOffset, patch);
|
||||
|
||||
Console.Write($"[{patchName}]");
|
||||
Console.ForegroundColor = ConsoleColor.Green;
|
||||
Console.WriteLine(" Done.");
|
||||
Console.ResetColor();
|
||||
Console.WriteLine();
|
||||
}
|
||||
else
|
||||
_patchList[patchName] = (patchOffset, patch);
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task QueuePatch(short[] pattern, byte[] patch, string patchName, int offsetBase = 0, bool? printInfo = null)
|
||||
{
|
||||
long patchOffset = Data.FindPattern(pattern);
|
||||
|
||||
// No result for the given pattern.
|
||||
if (patchOffset == 0)
|
||||
if (exitOnFail)
|
||||
{
|
||||
Console.ForegroundColor = ConsoleColor.Red;
|
||||
Console.WriteLine($"[{patchName}] No result found.");
|
||||
Console.ResetColor();
|
||||
Console.WriteLine("Press any key to exit...");
|
||||
|
||||
// Only wait if a console is available.
|
||||
if (!Console.IsInputRedirected)
|
||||
Console.ReadKey();
|
||||
|
||||
Launcher.CancellationTokenSource.Cancel();
|
||||
|
||||
return Task.CompletedTask;
|
||||
return Task.FromResult(false);
|
||||
}
|
||||
|
||||
return QueuePatch(patchOffset + offsetBase, patch, patchName, printInfo);
|
||||
}
|
||||
|
||||
bool RemapAndPatch(nint viewAddress, int viewSize)
|
||||
if (printInfo.Value)
|
||||
{
|
||||
// Suspend before remapping to prevent crashes.
|
||||
NtSuspendProcess(_processHandle);
|
||||
Console.ForegroundColor = ConsoleColor.Yellow;
|
||||
Console.WriteLine($"[{patchName}] No result found. This is just a warning! KEEP GOING...");
|
||||
Console.ResetColor();
|
||||
}
|
||||
|
||||
Data = Read(viewAddress, viewSize);
|
||||
return Task.FromResult(false);
|
||||
}
|
||||
|
||||
if (Data != null)
|
||||
while (Read((nint)patchOffset, patch.Length)?.SequenceEqual(patch) == false)
|
||||
Write((nint)patchOffset, patch);
|
||||
|
||||
if (printInfo.Value)
|
||||
{
|
||||
nint newViewHandle = 0;
|
||||
var maxSize = new LargeInteger { Quad = viewSize };
|
||||
|
||||
try
|
||||
{
|
||||
if (NtCreateSection(ref newViewHandle, 0xF001F, 0, ref maxSize, 0x40u, 0x8000000 | 0x400000, 0) == 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);
|
||||
|
||||
if (result == NtStatus.Success)
|
||||
{
|
||||
// Apply our patches.
|
||||
ApplyPatches(true);
|
||||
|
||||
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);
|
||||
|
||||
if (result == NtStatus.Success)
|
||||
{
|
||||
// Write our patched data trough the writable view to the memory.
|
||||
if (WriteProcessMemory(_processHandle, viewBase2, Data, viewSize, out var dummy))
|
||||
{
|
||||
// Unmap them writeable view, it's not longer needed.
|
||||
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)
|
||||
{
|
||||
// Also check if we can change the page protection.
|
||||
if (!VirtualProtectEx(_processHandle, BaseAddress, 0x4000, (uint)MemProtection.ReadWrite, out var oldProtect))
|
||||
NtResumeProcess(_processHandle);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
Console.Write($"[{patchName}] at 0x{patchOffset:X}");
|
||||
Console.ForegroundColor = ConsoleColor.Green;
|
||||
Console.WriteLine(" Done.");
|
||||
Console.ResetColor();
|
||||
Console.WriteLine();
|
||||
}
|
||||
|
||||
Console.WriteLine("Error while mapping the view with the given protection.");
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
NtClose(newViewHandle);
|
||||
}
|
||||
}
|
||||
else
|
||||
Console.WriteLine("Error while creating the view backup.");
|
||||
|
||||
NtResumeProcess(_processHandle);
|
||||
|
||||
return false;
|
||||
return Task.FromResult(true);
|
||||
}
|
||||
|
||||
void ApplyPatches(bool remap)
|
||||
{
|
||||
foreach (var p in _patchList)
|
||||
{
|
||||
var address = p.Value.Address;
|
||||
|
||||
if (address == 0)
|
||||
continue;
|
||||
|
||||
var patch = p.Value.Data;
|
||||
|
||||
// We are in a different section here.
|
||||
if (address > Data.Length)
|
||||
{
|
||||
if (address < BaseAddress)
|
||||
address += BaseAddress;
|
||||
|
||||
Write(address, patch);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (remap)
|
||||
{
|
||||
for (var i = 0; i < patch.Length; i++)
|
||||
Data[address + i] = patch[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool RemapAndPatch(bool remap)
|
||||
{
|
||||
if (!remap)
|
||||
{
|
||||
ApplyPatches(false);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (VirtualQueryEx(_processHandle, BaseAddress, out var mbi, MemoryBasicInformation.Size) != 0)
|
||||
return RemapAndPatch(mbi.BaseAddress, (int)mbi.RegionSize);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// Private functions.
|
||||
nint ReadImageBaseFromPEB(nint processHandle)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (NtQueryInformationProcess(processHandle, 0, ref _peb, ProcessBasicInformation.Size, out _) == NtStatus.Success)
|
||||
return Read(_peb.PebBaseAddress + 0x10);
|
||||
ProcessBasicInformation peb = default;
|
||||
|
||||
if (NtQueryInformationProcess(processHandle, 0, ref peb, ProcessBasicInformation.Size, out _) == 0)
|
||||
return Read(peb.PebBaseAddress + 0x10);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -340,22 +193,4 @@ class WinMemory
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool IsShortJump(byte[] instructions, int startIndex = 0)
|
||||
{
|
||||
return instructions[startIndex] >= 0x70 && instructions[startIndex] < 0x7F;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool IsJump(byte[] instructions, int startIndex = 0)
|
||||
{
|
||||
return instructions[startIndex] == 0x0F && instructions[startIndex + 1] >= 0x80 && instructions[startIndex + 1] <= 0x8F;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool IsUnconditionalJump(byte[] instructions, int startIndex = 0)
|
||||
{
|
||||
return instructions[startIndex] == 0xE9 || instructions[startIndex] == 0xEB;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,21 +4,19 @@
|
||||
using System.CommandLine.Builder;
|
||||
using System.CommandLine.Parsing;
|
||||
|
||||
namespace Arctium.WoW.Launcher;
|
||||
namespace Arctium.Game.Launcher;
|
||||
|
||||
static class LaunchOptions
|
||||
{
|
||||
public static bool IsDevModeAllowed { get; set; }
|
||||
|
||||
public static Option<GameVersion> Version = new("--version", () => GameVersion.Retail);
|
||||
public static Option<string> GamePath = new("--path");
|
||||
public static Option<string> GameBinary = new("--binary");
|
||||
public static Option<bool> KeepCache = new("--keepcache", () => true);
|
||||
public static Option<bool> UseStaticAuthSeed = new("--staticseed");
|
||||
public static Option<bool> DevMode = new("--dev", () => true, "Required for local development without valid certificates.");
|
||||
public static Option<string> VersionUrl = new("--versionurl");
|
||||
public static Option<string> CdnsUrl = new("--cdnsurl");
|
||||
public static Option<string> ProductName = new("--product", () => "wow");
|
||||
public static Option<string> CdnRegion = new("--region", () => "EU");
|
||||
public static Option<bool> SkipConnectionPatching = new("--skip", () => false, "Allows connection to servers that come with already patched clients.");
|
||||
public static Option<string> BgsPortal = new("--portal");
|
||||
|
||||
// Game command line options.
|
||||
public static Option<string> GameConfig = new("-config", () => "Config.wtf");
|
||||
@@ -31,18 +29,18 @@ static class LaunchOptions
|
||||
.UseSuggestDirective()
|
||||
.Build();
|
||||
|
||||
public static RootCommand RootCommand = new("Arctium WoW Launcher")
|
||||
public static RootCommand RootCommand = new("Arctium Game Launcher")
|
||||
{
|
||||
Version,
|
||||
GamePath,
|
||||
GameBinary,
|
||||
KeepCache,
|
||||
UseStaticAuthSeed,
|
||||
DevMode,
|
||||
VersionUrl,
|
||||
CdnsUrl,
|
||||
ProductName,
|
||||
CdnRegion,
|
||||
BgsPortal,
|
||||
GameConfig,
|
||||
SkipConnectionPatching
|
||||
};
|
||||
|
||||
static Command ConfigureCommandLine(Command rootCommand)
|
||||
|
||||
489
src/Launcher.cs
489
src/Launcher.cs
@@ -5,30 +5,42 @@ using System.CommandLine.Parsing;
|
||||
using System.Net.Security;
|
||||
using System.Net.Sockets;
|
||||
using System.Security.Authentication;
|
||||
using static Arctium.Game.Launcher.Misc.Helpers;
|
||||
|
||||
using static Arctium.WoW.Launcher.Misc.Helpers;
|
||||
|
||||
namespace Arctium.WoW.Launcher;
|
||||
namespace Arctium.Game.Launcher;
|
||||
|
||||
static class Launcher
|
||||
{
|
||||
public static nint GameProcessHandle { get; private set; }
|
||||
public static readonly CancellationTokenSource CancellationTokenSource = new();
|
||||
|
||||
public static async ValueTask<string> PrepareGameLaunch(ParseResult commandLineResult, IPFilter ipFilter)
|
||||
static List<GameVersion> _tryClients = [GameVersion.Retail, GameVersion.Classic, GameVersion.ClassicEra, GameVersion.ClassicTitan];
|
||||
static bool _useVersionV2;
|
||||
static byte[] _binaryData;
|
||||
|
||||
public static async ValueTask<string> PrepareGameLaunch(ParseResult commandLineResult, GameVersion? branchOverwrite)
|
||||
{
|
||||
Console.ResetColor();
|
||||
|
||||
var gameVersion = commandLineResult.GetValueForOption(LaunchOptions.Version);
|
||||
|
||||
if (branchOverwrite.HasValue)
|
||||
gameVersion = branchOverwrite.Value;
|
||||
|
||||
var (subFolder, binaryName, majorGameVersion, minGameBuild) = gameVersion switch
|
||||
{
|
||||
GameVersion.Retail => ("_retail_", "Wow.exe", new[] { 9, 10 }, 37862),
|
||||
GameVersion.Classic => ("_classic_", "WowClassic.exe", new[] { 2, 3 }, 39926),
|
||||
GameVersion.ClassicEra => ("_classic_era_", "WowClassic.exe", new[] { 1 }, 40347),
|
||||
GameVersion.Retail => ("_retail_", "Wow.exe", [10, 11, 12], 50401),
|
||||
GameVersion.Classic => ("_classic_", "WowClassic.exe", [2, 3, 4, 5], 50063),
|
||||
GameVersion.ClassicEra => ("_classic_era_", "WowClassic.exe", [1], 51001),
|
||||
GameVersion.ClassicTitan => ("_classic_titan_", "WowClassic.exe", new[] { 3 }, 64393),
|
||||
_ => throw new NotImplementedException("Invalid game version specified."),
|
||||
};
|
||||
|
||||
_tryClients.RemoveAll(c => c == gameVersion);
|
||||
|
||||
Console.ForegroundColor = ConsoleColor.Yellow;
|
||||
|
||||
Console.WriteLine($"Mode: Custom Server ({gameVersion})");
|
||||
Console.WriteLine();
|
||||
Console.WriteLine($"Mode: Public Custom Server ({gameVersion})");
|
||||
Console.ResetColor();
|
||||
|
||||
var currentFolder = AppDomain.CurrentDomain.BaseDirectory;
|
||||
@@ -51,10 +63,29 @@ static class Launcher
|
||||
gameBinaryPath = $"{gameFolder}/{binaryName}";
|
||||
}
|
||||
|
||||
gameFolder = gameFolder.Replace("\\/", "/").Replace("\\", "/");
|
||||
gameBinaryPath = gameBinaryPath.Replace("\\/", "/").Replace("\\", "/");
|
||||
|
||||
if (!File.Exists(gameBinaryPath) || !majorGameVersion.Contains(GetVersionValueFromClient(gameBinaryPath).Major))
|
||||
{
|
||||
Console.ForegroundColor = ConsoleColor.Red;
|
||||
Console.WriteLine($"[Error] No {gameVersion} client found.");
|
||||
Console.WriteLine($"No {gameVersion} client found at '{gameBinaryPath}'");
|
||||
|
||||
if (_tryClients.Count > 0)
|
||||
{
|
||||
var nextClient = _tryClients.First();
|
||||
|
||||
Console.ForegroundColor = ConsoleColor.Yellow;
|
||||
Console.WriteLine($"Trying a different branch...");
|
||||
Console.ResetColor();
|
||||
|
||||
return await PrepareGameLaunch(commandLineResult, nextClient);
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine();
|
||||
Console.WriteLine($"No supported client found.");
|
||||
}
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
@@ -85,37 +116,66 @@ static class Launcher
|
||||
}
|
||||
|
||||
var configPath = $"{gameFolder}/WTF/{commandLineResult.GetValueForOption(LaunchOptions.GameConfig)}";
|
||||
(string IPAddress, string HostName, int Port) portal = new();
|
||||
|
||||
if (!File.Exists(configPath))
|
||||
LaunchOptions.IsDevModeAllowed = false;
|
||||
else
|
||||
{
|
||||
var config = File.ReadAllText(configPath);
|
||||
|
||||
portal = ParsePortal(config);
|
||||
|
||||
LaunchOptions.IsDevModeAllowed = IsDevModeAllowed(ipFilter, portal.IPAddress);
|
||||
}
|
||||
|
||||
if (!LaunchOptions.IsDevModeAllowed)
|
||||
LaunchOptions.DevMode = new("--dev", () => false);
|
||||
|
||||
var devModeEnabled = commandLineResult.GetValueForOption(LaunchOptions.DevMode) && LaunchOptions.IsDevModeAllowed;
|
||||
|
||||
Console.ForegroundColor = ConsoleColor.Yellow;
|
||||
Console.WriteLine($"Developer mode: {(devModeEnabled ? "Enabled" : "Disabled")}");
|
||||
Console.WriteLine();
|
||||
Console.WriteLine($"Client Portal '{portal.HostName}'");
|
||||
Console.WriteLine($"Client Config: \"{configPath}\"");
|
||||
Console.ResetColor();
|
||||
|
||||
(string IPAddress, string HostName, int Port) portal = new();
|
||||
|
||||
|
||||
var config = File.ReadAllText(configPath);
|
||||
var bgsPortal = commandLineResult.GetValueForOption(LaunchOptions.BgsPortal);
|
||||
|
||||
portal = ParseOrSetPortal(ref config, bgsPortal);
|
||||
|
||||
if (!string.IsNullOrEmpty(bgsPortal))
|
||||
{
|
||||
Console.ForegroundColor = ConsoleColor.Yellow;
|
||||
Console.WriteLine($"Config Portal Overwrite:\"{bgsPortal}\"");
|
||||
Console.ResetColor();
|
||||
Console.WriteLine();
|
||||
|
||||
File.WriteAllText(configPath, config);
|
||||
}
|
||||
|
||||
_binaryData = File.ReadAllBytes(gameBinaryPath);
|
||||
_useVersionV2 = UsesVersionV2(_binaryData);
|
||||
|
||||
Console.ForegroundColor = ConsoleColor.Yellow;
|
||||
Console.WriteLine($"Client Path: '{gameBinaryPath}'");
|
||||
Console.WriteLine($"Client Portal: '{portal.HostName}:{portal.Port}'");
|
||||
Console.ForegroundColor = ConsoleColor.Gray;
|
||||
|
||||
// Check for valid certificate when dev mode is disabled.
|
||||
if (!devModeEnabled)
|
||||
|
||||
if (string.IsNullOrEmpty(portal.HostName) || string.IsNullOrWhiteSpace(portal.HostName))
|
||||
{
|
||||
Console.ForegroundColor = ConsoleColor.Red;
|
||||
Console.WriteLine();
|
||||
Console.WriteLine("Client Portal should not be empty.");
|
||||
Console.WriteLine("Be sure to have a valid portal in your Config.wtf file.");
|
||||
Console.ResetColor();
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
// Return if no valid ip address has been found.
|
||||
if (string.IsNullOrEmpty(portal.IPAddress) || string.IsNullOrWhiteSpace(portal.IPAddress))
|
||||
return string.Empty;
|
||||
|
||||
// Check for valid certificate.
|
||||
try
|
||||
{
|
||||
using var tcpClient = new TcpClient();
|
||||
using var tcpClientTimeout = new CancellationTokenSource(TimeSpan.FromSeconds(5));
|
||||
|
||||
// 3.5 seconds timeout.
|
||||
const int timeout = 3500;
|
||||
|
||||
tcpClient.ReceiveTimeout = timeout;
|
||||
tcpClient.SendTimeout = timeout;
|
||||
|
||||
using var tcpClientTimeout = new CancellationTokenSource(TimeSpan.FromMilliseconds(timeout));
|
||||
|
||||
await tcpClient.ConnectAsync(portal.HostName, portal.Port, tcpClientTimeout.Token);
|
||||
|
||||
@@ -138,10 +198,10 @@ static class Launcher
|
||||
|
||||
sslStream.AuthenticateAsClient(portal.HostName);
|
||||
}
|
||||
catch (Exception exception) when (exception is SocketException or OperationCanceledException)
|
||||
catch (IOException exception)
|
||||
{
|
||||
Console.ForegroundColor = ConsoleColor.Red;
|
||||
Console.WriteLine($"{portal.HostName}:{portal.Port} is offline.");
|
||||
Console.WriteLine($"Server {portal.HostName}:{portal.Port}: {exception.Message}");
|
||||
Console.ResetColor();
|
||||
|
||||
return string.Empty;
|
||||
@@ -149,13 +209,20 @@ static class Launcher
|
||||
catch (AuthenticationException)
|
||||
{
|
||||
Console.ForegroundColor = ConsoleColor.Yellow;
|
||||
Console.WriteLine($"Server with host name {portal.HostName} does not have a trusted certificate attached.");
|
||||
Console.WriteLine($"Server with host name {portal.HostName}:{portal.Port} does not have a trusted certificate attached.");
|
||||
Console.WriteLine("If you are the server owner be sure to generate one and replace the default bnet server certificate.");
|
||||
Console.WriteLine("One way to generate one is through Let's Encrypt.");
|
||||
Console.ResetColor();
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
catch (Exception exception) when (exception is SocketException or OperationCanceledException)
|
||||
{
|
||||
Console.ForegroundColor = ConsoleColor.Red;
|
||||
Console.WriteLine($"{portal.HostName}:{portal.Port} is offline or not reachable from the current network.");
|
||||
Console.ResetColor();
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
return gameBinaryPath;
|
||||
@@ -168,7 +235,61 @@ static class Launcher
|
||||
|
||||
Console.ForegroundColor = ConsoleColor.Yellow;
|
||||
Console.WriteLine($"Client Build {clientVersion}");
|
||||
Console.WriteLine($"Client Path '{appPath}'");
|
||||
Console.ResetColor();
|
||||
|
||||
// Assign the region and product dependent version url to check it's online status.
|
||||
string[] versionUrls = CreateVersionUrls(commandLineResult, clientVersion);
|
||||
|
||||
static string[] CreateVersionUrls(ParseResult commandLineResult, (int Major, int Minor, int Revision, int Build) clientVersion)
|
||||
{
|
||||
var versionUrl = commandLineResult.GetValueForOption(LaunchOptions.VersionUrl);
|
||||
|
||||
// Always return a forced version url parameter.
|
||||
if (!string.IsNullOrEmpty(versionUrl))
|
||||
return [versionUrl, versionUrl, versionUrl];
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
bool hasCustomVersionUrl = commandLineResult.HasOption(LaunchOptions.VersionUrl);
|
||||
bool hasCustomCdnUrl = commandLineResult.HasOption(LaunchOptions.CdnsUrl);
|
||||
|
||||
if (hasCustomCdnUrl || hasCustomVersionUrl)
|
||||
{
|
||||
Console.ForegroundColor = ConsoleColor.Yellow;
|
||||
Console.WriteLine("Warning: Custom version servers were specified.");
|
||||
Console.WriteLine("The game will connect to these servers.");
|
||||
Console.ResetColor();
|
||||
}
|
||||
|
||||
var cdnsUrl = commandLineResult.GetValueForOption(LaunchOptions.CdnsUrl);
|
||||
|
||||
if (hasCustomCdnUrl && (!CheckUrl(cdnsUrl, fallbackUrls: [Patterns.Common.CdnsUrl]).GetAwaiter().GetResult()))
|
||||
cdnsUrl = Patterns.Common.CdnsUrl;
|
||||
|
||||
Console.ForegroundColor = ConsoleColor.Cyan;
|
||||
|
||||
Console.WriteLine();
|
||||
Console.WriteLine("Game CDN connection info:");
|
||||
Console.WriteLine($"Version source(s):");
|
||||
|
||||
if (versionUrls.Length == 0)
|
||||
{
|
||||
foreach (var vUrl in new[] { Patterns.Common.VersionUrl, Patterns.Common.Version2Url, Patterns.Common.Version2ChinaUrl })
|
||||
Console.WriteLine($"- {vUrl}");
|
||||
}
|
||||
else
|
||||
{
|
||||
if (versionUrls.All(element => element.Equals(versionUrls[0])))
|
||||
Console.WriteLine($"- {versionUrls[0]}");
|
||||
else
|
||||
{
|
||||
foreach (var vUrl in versionUrls)
|
||||
Console.WriteLine($"- {vUrl}");
|
||||
}
|
||||
}
|
||||
|
||||
Console.WriteLine($"CDNs source: {cdnsUrl}");
|
||||
Console.WriteLine();
|
||||
Console.ResetColor();
|
||||
|
||||
@@ -178,159 +299,81 @@ static class Launcher
|
||||
try
|
||||
{
|
||||
Console.ForegroundColor = ConsoleColor.White;
|
||||
Console.WriteLine("Starting WoW client...");
|
||||
Console.WriteLine("Starting game client...");
|
||||
|
||||
var createSuccess = NativeWindows.CreateProcess(null, $"{appPath} {gameCommandLine}", 0, 0, false, 4, 0, new FileInfo(appPath).DirectoryName,
|
||||
var createSuccess = NativeWindows.CreateProcess(null, $"\"{appPath}\" {gameCommandLine}", 0, 0, false, 4, 0, new FileInfo(appPath).DirectoryName,
|
||||
ref startupInfo, out processInfo);
|
||||
|
||||
// On some systems we have to launch the game with the application name used.
|
||||
if (!createSuccess)
|
||||
createSuccess = NativeWindows.CreateProcess(appPath, $" {gameCommandLine}", 0, 0, false, 4, 0, null, ref startupInfo, out processInfo);
|
||||
createSuccess = NativeWindows.CreateProcess(appPath, $" {gameCommandLine}", 0, 0, false, 4, 0, new FileInfo(appPath).DirectoryName, ref startupInfo, out processInfo);
|
||||
|
||||
// Start process with suspend flags.
|
||||
if (createSuccess)
|
||||
{
|
||||
using var gameAppData = new MemoryStream(File.ReadAllBytes(appPath));
|
||||
GameProcessHandle = processInfo.ProcessHandle;
|
||||
|
||||
var memory = new WinMemory(processInfo, gameAppData.Length);
|
||||
using var gameAppData = File.OpenRead(appPath);
|
||||
|
||||
// Resume the process to initialize it.
|
||||
NativeWindows.NtResumeProcess(processInfo.ProcessHandle);
|
||||
var appDataLength = gameAppData.Length;
|
||||
var memory = new WinMemory(processInfo);
|
||||
|
||||
MemoryBasicInformation mbi;
|
||||
|
||||
// Wait for the memory region to be initialized.
|
||||
while (NativeWindows.VirtualQueryEx(processInfo.ProcessHandle, memory.BaseAddress, out mbi, MemoryBasicInformation.Size) == 0 ||
|
||||
mbi.RegionSize <= 0x1000)
|
||||
{ }
|
||||
|
||||
if (mbi.BaseAddress != 0)
|
||||
if (memory.BaseAddress != 0)
|
||||
{
|
||||
NativeWindows.NtSuspendProcess(processInfo.ProcessHandle);
|
||||
|
||||
byte[] certBundleData = Convert.FromBase64String(Patches.Common.CertBundleData);
|
||||
Process gameProcess = Process.GetProcessById((int)processInfo.ProcessId);
|
||||
|
||||
// Refresh the client data before patching.
|
||||
memory.RefreshMemoryData((int)gameAppData.Length);
|
||||
memory.RefreshMemoryData((int)appDataLength);
|
||||
|
||||
// We need to cache this here since we are using our RSA modulus as auth seed.
|
||||
var modulusOffset = memory.Data.FindPattern(Patterns.Common.CryptoRsaModulus);
|
||||
var legacyCertMode = clientVersion is (1, >= 14, <= 3, _) or (3, 4, <= 1, _) or (9, _, _, _) or (10, <= 1, < 5, _);
|
||||
// Custom CDN related patches.
|
||||
if ((hasCustomVersionUrl && hasCustomCdnUrl))
|
||||
{
|
||||
var versionUrlPatches = new byte[versionUrls.Length][];
|
||||
|
||||
if (!commandLineResult.GetValueForOption(LaunchOptions.SkipConnectionPatching))
|
||||
for (var i = 0; i < versionUrls.Length; i++)
|
||||
versionUrlPatches[i] = Encoding.UTF8.GetBytes(versionUrls[i] + '\0');
|
||||
|
||||
// 11.1.7 added new v2 links.
|
||||
if (_useVersionV2)
|
||||
{
|
||||
if (legacyCertMode)
|
||||
Task.WaitAll(
|
||||
[
|
||||
memory.TryPatchPatterns(versionUrlPatches, "Version URL", true, false, true,
|
||||
Patterns.Common.Version2UrlNew.ToPattern(), Patterns.Common.Version2ChinaUrlNew.ToPattern()),
|
||||
], CancellationTokenSource.Token);
|
||||
}
|
||||
else
|
||||
{
|
||||
Task.WaitAll(new[]
|
||||
{
|
||||
memory.PatchMemory(Patterns.Common.CertBundle, certBundleData, "Certificate Bundle"),
|
||||
memory.PatchMemory(Patterns.Common.SignatureModulus, Patches.Common.SignatureModulus, "Certificate Signature RsaModulus")
|
||||
}, CancellationTokenSource.Token);
|
||||
Task.WaitAll(
|
||||
[
|
||||
memory.TryPatchPatterns(versionUrlPatches, "Version URL", true, false, true,
|
||||
Patterns.Common.VersionUrl.ToPattern(), Patterns.Common.Version2Url.ToPattern(), Patterns.Common.Version2ChinaUrl.ToPattern()),
|
||||
memory.PatchMemory(Patterns.Common.CdnsUrl.ToPattern(), Encoding.UTF8.GetBytes(cdnsUrl), "CDNs URL", exitOnFail: false),
|
||||
], CancellationTokenSource.Token);
|
||||
}
|
||||
}
|
||||
|
||||
// Wait for all direct memory patch tasks to complete,
|
||||
Task.WaitAll(new[]
|
||||
{
|
||||
// Wait for all direct memory patch tasks to complete.
|
||||
Task.WaitAll(
|
||||
[
|
||||
memory.PatchMemory(Patterns.Common.ConnectToModulus, Patches.Common.RsaModulus, "ConnectTo RsaModulus"),
|
||||
|
||||
// Recent clients have a different signing algorithm in EnterEncryptedMode.
|
||||
clientVersion is (9, 2, 7, _) or (3, _, _, _) or (10, _, _, _) or (1, >= 14, >= 4, _)
|
||||
? memory.PatchMemory(Patterns.Common.CryptoEdPublicKey, Patches.Common.CryptoEdPublicKey, "GameCrypto Ed25519 PublicKey")
|
||||
: memory.PatchMemory(Patterns.Common.CryptoRsaModulus, Patches.Common.RsaModulus, "GameCrypto RsaModulus"),
|
||||
|
||||
memory.PatchMemory(Patterns.Common.CryptoEdPublicKey, Patches.Common.CryptoEdPublicKey, "GameCrypto Ed25519 PublicKey"),
|
||||
memory.PatchMemory(Patterns.Common.Portal, Patches.Common.Portal, "Login Portal"),
|
||||
memory.PatchMemory(Patterns.Windows.LauncherLogin, Patches.Windows.LauncherLogin, "Launcher Login Registry")
|
||||
}, CancellationTokenSource.Token);
|
||||
}
|
||||
], CancellationTokenSource.Token);
|
||||
|
||||
NativeWindows.NtResumeProcess(processInfo.ProcessHandle);
|
||||
|
||||
// Enable anti crash in dev mode, custom file mode or static auth seed mode.
|
||||
#if CUSTOM_FILES
|
||||
var antiCrash = true;
|
||||
#else
|
||||
var antiCrash = legacyCertMode || commandLineResult.HasOption(LaunchOptions.UseStaticAuthSeed) ||
|
||||
commandLineResult.GetValueForOption(LaunchOptions.DevMode) && LaunchOptions.IsDevModeAllowed;
|
||||
#endif
|
||||
|
||||
WaitForUnpack(ref processInfo, memory, ref mbi, gameAppData, antiCrash);
|
||||
|
||||
if (!commandLineResult.GetValueForOption(LaunchOptions.SkipConnectionPatching))
|
||||
{
|
||||
if (legacyCertMode)
|
||||
{
|
||||
Task.WaitAll(new[]
|
||||
{
|
||||
memory.QueuePatch(Patterns.Windows.CertBundle, Patches.Windows.CertBundle, "CertBundle"),
|
||||
memory.QueuePatch(Patterns.Windows.CertCommonName, Patches.Windows.CertCommonName, "CertCommonName", 5)
|
||||
}, CancellationTokenSource.Token);
|
||||
}
|
||||
else if (LaunchOptions.IsDevModeAllowed && commandLineResult.GetValueForOption(LaunchOptions.DevMode))
|
||||
{
|
||||
Task.WaitAll(new[]
|
||||
{
|
||||
memory.QueuePatch(Patterns.Windows.CertChain, Patches.Windows.CertChain, "CertChain"),
|
||||
memory.QueuePatch(Patterns.Windows.CertCommonName, Patches.Windows.CertCommonName, "CertCommonName", 5)
|
||||
}, CancellationTokenSource.Token);
|
||||
}
|
||||
}
|
||||
|
||||
if (commandLineResult.HasOption(LaunchOptions.UseStaticAuthSeed))
|
||||
{
|
||||
Console.ForegroundColor = ConsoleColor.Cyan;
|
||||
Console.WriteLine("Static auth seed used. Be sure that the server you are connecting to supports it.");
|
||||
Console.ResetColor();
|
||||
|
||||
// Generates a patch for the auth seed so we don't have to update them on each build.
|
||||
var authSeedFunctionOffset = GenerateAuthSeedFunctionPatch(memory, modulusOffset);
|
||||
|
||||
Task.WaitAll(new[]
|
||||
{
|
||||
memory.QueuePatch(authSeedFunctionOffset, Patches.Windows.AuthSeed, "CustomAuthSeedFunction")
|
||||
}, CancellationTokenSource.Token);
|
||||
}
|
||||
#if CUSTOM_FILES
|
||||
Task.WaitAll(new[]
|
||||
{
|
||||
(clientVersion is (10, _, _, _))
|
||||
? memory.QueuePatch(Patterns.Windows.LoadByFileIdAlternate, Patches.Windows.NoJump, "LoadByFileId", 3)
|
||||
: memory.QueuePatch(Patterns.Windows.LoadByFileId, Patches.Windows.NoJump, "LoadByFileId", 6),
|
||||
|
||||
(clientVersion is (10, _, _, _))
|
||||
? memory.QueuePatch(Patterns.Windows.LoadByFilePathAlternate, Patches.Windows.NoJump, "LoadByFilePath", 3)
|
||||
: memory.QueuePatch(Patterns.Windows.LoadByFilePath, Patches.Windows.NoJump, "LoadByFilePath", 3)
|
||||
}, CancellationTokenSource.Token);
|
||||
|
||||
var (idAlloc, stringAlloc) = ModLoader.LoadFileMappings(processInfo.ProcessHandle);
|
||||
|
||||
if (idAlloc != 0 && stringAlloc != 0)
|
||||
{
|
||||
if (!ModLoader.HookClient(memory, processInfo.ProcessHandle, idAlloc, stringAlloc))
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
NativeWindows.NtResumeProcess(processInfo.ProcessHandle);
|
||||
|
||||
if (memory.RemapAndPatch(antiCrash))
|
||||
{
|
||||
Console.WriteLine("Done :) ");
|
||||
|
||||
Console.ForegroundColor = ConsoleColor.Green;
|
||||
Console.WriteLine("You can login now.");
|
||||
|
||||
Console.WriteLine("Closing in 3 seconds...");
|
||||
Thread.Sleep(3000);
|
||||
Console.ResetColor();
|
||||
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.ForegroundColor = ConsoleColor.Red;
|
||||
Console.WriteLine("Error while launching the client.");
|
||||
|
||||
NativeWindows.TerminateProcess(processInfo.ProcessHandle, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Only exit and do not print any exception messages to the console.
|
||||
@@ -354,142 +397,4 @@ static class Launcher
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool IsDevModeAllowed(IPFilter ipFilter, string portalIP) => ipFilter.IsInRange(portalIP);
|
||||
|
||||
static long GenerateAuthSeedFunctionPatch(WinMemory memory, long modulusOffset)
|
||||
{
|
||||
var authSeedLoadOffset = memory.Data.FindPattern(Patterns.Windows.AuthSeed);
|
||||
|
||||
if (authSeedLoadOffset == 0)
|
||||
throw new InvalidDataException("authSeedLoadOffset");
|
||||
|
||||
var leaStartOffset = authSeedLoadOffset + 9;
|
||||
var leaValue = Unsafe.ReadUnaligned<int>(ref memory.Data[leaStartOffset + 3]);
|
||||
var authSeedWrapperOffset = leaStartOffset + leaValue + 7;
|
||||
var jmpValue = Unsafe.ReadUnaligned<uint>(ref memory.Data[authSeedWrapperOffset + 6]);
|
||||
var authSeedFunctionOffset = authSeedWrapperOffset + 5 + jmpValue + 5;
|
||||
|
||||
// Write the modulus offset to our custom get seed functions.
|
||||
// Resulting static auth seed is: 179D3DC3235629D07113A9B3867F97A7
|
||||
Unsafe.WriteUnaligned(ref Patches.Windows.AuthSeed[3], (uint)(modulusOffset - authSeedFunctionOffset - 7));
|
||||
|
||||
return authSeedFunctionOffset;
|
||||
}
|
||||
|
||||
static void WaitForUnpack(ref ProcessInformation processInfo, WinMemory memory, ref MemoryBasicInformation mbi, Stream gameAppData, bool antiCrash)
|
||||
{
|
||||
// Wait for client initialization.
|
||||
var initOffset = memory.Read(mbi.BaseAddress, (int)mbi.RegionSize)?.FindPattern(Patterns.Windows.Init) ?? 0;
|
||||
|
||||
while (initOffset == 0)
|
||||
{
|
||||
initOffset = memory.Read(mbi.BaseAddress, (int)mbi.RegionSize)?.FindPattern(Patterns.Windows.Init) ?? 0;
|
||||
|
||||
Console.WriteLine("Waiting for client initialization...");
|
||||
}
|
||||
|
||||
initOffset += BitConverter.ToUInt32(memory.Read(initOffset + memory.BaseAddress + 2, 4), 0) + 10;
|
||||
|
||||
while (memory.Read(initOffset + memory.BaseAddress, 1)?[0] == null ||
|
||||
memory.Read(initOffset + memory.BaseAddress, 1)?[0] == 0)
|
||||
memory.Data = memory.Read(mbi.BaseAddress, (int)mbi.RegionSize);
|
||||
|
||||
if (antiCrash)
|
||||
PrepareAntiCrash(memory, ref mbi, ref processInfo);
|
||||
|
||||
memory.RefreshMemoryData((int)mbi.RegionSize);
|
||||
}
|
||||
|
||||
static void PrepareAntiCrash(WinMemory memory, ref MemoryBasicInformation mbi, ref ProcessInformation processInfo)
|
||||
{
|
||||
memory.RefreshMemoryData((int)mbi.RegionSize);
|
||||
|
||||
// Suspend the process and handle the patches.
|
||||
NativeWindows.NtSuspendProcess(processInfo.ProcessHandle);
|
||||
|
||||
// Get Integrity check locations
|
||||
var integrityOffsets = memory.Data.FindPattern(Patterns.Windows.Integrity, int.MaxValue, (int)mbi.RegionSize).ToArray();
|
||||
|
||||
// Encrypt integrity offsets and patches and add them to the patch list.
|
||||
for (var i = 0; i < integrityOffsets.Length; i++)
|
||||
memory.QueuePatch(integrityOffsets[i], Patches.Windows.Integrity, $"Integrity{i}");
|
||||
|
||||
// Get Integrity check locations
|
||||
var integrityOffsets2 = memory.Data.FindPattern(Patterns.Windows.Integrity2, int.MaxValue, (int)mbi.RegionSize).ToArray();
|
||||
|
||||
// Encrypt integrity offsets and patches and add them to the patch list.
|
||||
for (var i = 0; i < integrityOffsets2.Length; i++)
|
||||
memory.QueuePatch(integrityOffsets2[i], Patches.Windows.Integrity, $"Integrity{integrityOffsets.Length + i}");
|
||||
|
||||
// Get Remap check locations.
|
||||
var remapOffsets = memory.Data.FindPattern(Patterns.Windows.Remap, int.MaxValue, (int)mbi.RegionSize);
|
||||
var lastAddress = 0;
|
||||
|
||||
foreach (var a in remapOffsets)
|
||||
{
|
||||
var instructionStart = (int)a + 4;
|
||||
var instructions = new byte[6];
|
||||
|
||||
Buffer.BlockCopy(memory.Data, instructionStart, instructions, 0, 6);
|
||||
|
||||
// Skip unconditional jumps.
|
||||
if (WinMemory.IsUnconditionalJump(instructions))
|
||||
continue;
|
||||
|
||||
int operandValue;
|
||||
|
||||
if (WinMemory.IsShortJump(instructions))
|
||||
operandValue = instructions[1] + 2;
|
||||
else if (WinMemory.IsJump(instructions))
|
||||
operandValue = BitConverter.ToInt32(instructions, 2) + 6;
|
||||
else
|
||||
throw new InvalidDataException("Invalid operand value.");
|
||||
|
||||
var jumpToValue = a + operandValue + 4;
|
||||
var tempPatches = new ConcurrentDictionary<string, (long, byte[])>();
|
||||
|
||||
// Find all references of real code parts inside the remap check functions.
|
||||
Parallel.For(lastAddress, memory.Data.Length, i =>
|
||||
{
|
||||
if (WinMemory.IsJump(memory.Data, i))
|
||||
{
|
||||
var jumpOperand = BitConverter.ToInt32(memory.Data, i + 2);
|
||||
var jumpSize = (int)jumpToValue - i - 6;
|
||||
|
||||
if (jumpOperand == jumpSize)
|
||||
{
|
||||
// Add 1 because we patch the instruction start.
|
||||
// This results in a shorter overall instruction length.
|
||||
var jumpBytes = new byte[] { 0xE9 }.Concat(BitConverter.GetBytes(jumpSize + 1)).ToArray();
|
||||
|
||||
tempPatches.TryAdd($"Jump{i}", (i, jumpBytes));
|
||||
}
|
||||
}
|
||||
else if (WinMemory.IsShortJump(memory.Data, i))
|
||||
{
|
||||
var jumpOperand = memory.Data[i + 1];
|
||||
var jumpSize = (int)jumpToValue - i - 2;
|
||||
|
||||
if (jumpOperand == jumpSize)
|
||||
{
|
||||
// Check for 0x48 here. This is an indicator for the test instructions.
|
||||
// Might need some better checks or future updates.
|
||||
if (memory.Data[i - 3] == 0x48)
|
||||
{
|
||||
var jumpBytes = new byte[] { 0xEB };
|
||||
|
||||
tempPatches.TryAdd($"ShortJump{i}", (i, jumpBytes));
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Add the remap crash patches to the patch list.
|
||||
foreach (var p in tempPatches)
|
||||
memory.QueuePatch(p.Value.Item1, p.Value.Item2, p.Key);
|
||||
|
||||
lastAddress = (int)a;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,30 +1,11 @@
|
||||
// Copyright (c) Arctium.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
namespace Arctium.WoW.Launcher.Misc;
|
||||
namespace Arctium.Game.Launcher.Misc;
|
||||
|
||||
static class Extensions
|
||||
{
|
||||
public static nint ToNint(this byte[] buffer) => (nint)BitConverter.ToInt64(buffer, 0);
|
||||
public static nint ToNint(this long value) => (nint)value;
|
||||
|
||||
public static byte[] GetCopy(this byte[] data)
|
||||
{
|
||||
var copy = new byte[data.Length];
|
||||
|
||||
Array.Copy(data, copy, data.Length);
|
||||
|
||||
return copy;
|
||||
}
|
||||
|
||||
public static short[] GetCopy(this short[] data)
|
||||
{
|
||||
var copy = new short[data.Length];
|
||||
|
||||
Array.Copy(data, copy, data.Length);
|
||||
|
||||
return copy;
|
||||
}
|
||||
|
||||
public static long FindPattern(this byte[] data, short[] pattern, long start, long baseOffset = 0)
|
||||
{
|
||||
@@ -49,28 +30,5 @@ static class Extensions
|
||||
}
|
||||
|
||||
public static long FindPattern(this byte[] data, short[] pattern, long baseOffset = 0) => FindPattern(data, pattern, 0L, baseOffset);
|
||||
|
||||
public static HashSet<long> FindPattern(this byte[] data, short[] pattern, int maxMatches, long maxOffset)
|
||||
{
|
||||
var matchList = new HashSet<long>();
|
||||
|
||||
long match = 0;
|
||||
|
||||
do
|
||||
{
|
||||
match = data.FindPattern(pattern, match, 0);
|
||||
|
||||
if (match == 0)
|
||||
continue;
|
||||
|
||||
matchList.Add(match);
|
||||
|
||||
match += pattern.Length;
|
||||
|
||||
} while ((matchList.Count < maxMatches || match < maxOffset) && match != 0);
|
||||
|
||||
return matchList;
|
||||
}
|
||||
|
||||
public static short[] ToPattern(this string data) => Encoding.UTF8.GetBytes(data).Select(b => (short)b).ToArray();
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
|
||||
namespace Arctium.WoW.Launcher.Misc;
|
||||
namespace Arctium.Game.Launcher.Misc;
|
||||
|
||||
static class Helpers
|
||||
{
|
||||
@@ -17,6 +17,13 @@ static class Helpers
|
||||
#endif
|
||||
}
|
||||
|
||||
public static void PublisherCheck()
|
||||
{
|
||||
// "Arctium" should not be removed from the final binary name.
|
||||
if (!Process.GetCurrentProcess().ProcessName.Contains("arctium", StringComparison.InvariantCultureIgnoreCase))
|
||||
Process.GetCurrentProcess().Kill();
|
||||
}
|
||||
|
||||
public static (int Major, int Minor, int Revision, int Build) GetVersionValueFromClient(string fileName)
|
||||
{
|
||||
var fileVersionInfo = FileVersionInfo.GetVersionInfo(fileName);
|
||||
@@ -25,43 +32,43 @@ static class Helpers
|
||||
fileVersionInfo.FileBuildPart, fileVersionInfo.FilePrivatePart);
|
||||
}
|
||||
|
||||
public static void PrintHeader(string serverName)
|
||||
public static void PrintHeader()
|
||||
{
|
||||
Console.ForegroundColor = ConsoleColor.Green;
|
||||
Console.WriteLine("Arctium Game Launcher");
|
||||
Console.ResetColor();
|
||||
Console.Write("Game: ");
|
||||
Console.ForegroundColor = ConsoleColor.Cyan;
|
||||
|
||||
Console.WriteLine(@"_____________World of Warcraft___________");
|
||||
Console.WriteLine(@" _ _ ");
|
||||
Console.WriteLine(@" /\ | | (_) ");
|
||||
Console.WriteLine(@" / \ _ __ ___| |_ _ _ _ _ __ ___ ");
|
||||
Console.WriteLine(@" / /\ \ | '__/ __| __| | | | | '_ ` _ \ ");
|
||||
Console.WriteLine(@" / ____ \| | | (__| |_| | |_| | | | | | |");
|
||||
Console.WriteLine(@"/_/ \_\_| \___|\__|_|\__,_|_| |_| |_|");
|
||||
Console.WriteLine("");
|
||||
|
||||
var sb = new StringBuilder();
|
||||
|
||||
sb.Append("_________________________________________");
|
||||
|
||||
var nameStart = (42 - serverName.Length) / 2;
|
||||
|
||||
sb.Insert(nameStart, serverName);
|
||||
sb.Remove(nameStart + serverName.Length, serverName.Length);
|
||||
|
||||
Console.WriteLine(sb);
|
||||
Console.WriteLine("{0,30}", "https://arctium.io");
|
||||
|
||||
Console.WriteLine("World of Warcraft");
|
||||
Console.ResetColor();
|
||||
Console.Write("Support: ");
|
||||
Console.ForegroundColor = ConsoleColor.Cyan;
|
||||
Console.WriteLine("https://arctium.io");
|
||||
Console.ResetColor();
|
||||
Console.WriteLine();
|
||||
Console.WriteLine($"Operating System: {RuntimeInformation.OSDescription}");
|
||||
}
|
||||
|
||||
public static (string IPAddress, string HostName, int Port) ParsePortal(string config)
|
||||
public static (string IPAddress, string HostName, int Port) ParseOrSetPortal(ref string config, string customPortal = null)
|
||||
{
|
||||
const string portalKey = "SET portal";
|
||||
|
||||
var portalIndex = config.IndexOf(portalKey, StringComparison.Ordinal);
|
||||
var portalIndex = config.IndexOf(portalKey, StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
if (portalIndex == -1)
|
||||
throw new ArgumentException("Config file does not contain the portal variable.");
|
||||
{
|
||||
if (string.IsNullOrEmpty(customPortal))
|
||||
throw new ArgumentException("Config file does not contain a valid portal variable.");
|
||||
|
||||
// Append a new portal variable to the config.
|
||||
config += $"{Environment.NewLine}{portalKey} \"{customPortal}\"";
|
||||
}
|
||||
|
||||
// Re-evaluate the portal variable.
|
||||
portalIndex = config.IndexOf(portalKey, StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
if (portalIndex == -1)
|
||||
throw new ArgumentException("Config file does not contain a valid portal variable.");
|
||||
|
||||
var startQuoteIndex = config.IndexOf('"', portalIndex);
|
||||
|
||||
@@ -78,8 +85,32 @@ static class Helpers
|
||||
var colonIndex = portalSpan.IndexOf(':');
|
||||
var ipSpan = colonIndex != -1 ? portalSpan[..colonIndex] : portalSpan;
|
||||
var port = colonIndex != -1 ? int.Parse(portalSpan[(colonIndex + 1)..]) : 1119;
|
||||
|
||||
// Check if we have more than one portal line.
|
||||
var lastPortalIndex = config.IndexOf(portalKey, portalIndex + portalLength, StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
if (lastPortalIndex != -1 && lastPortalIndex != portalIndex)
|
||||
{
|
||||
Console.ForegroundColor = ConsoleColor.Red;
|
||||
Console.WriteLine();
|
||||
Console.WriteLine("Client portal is set multiple times. Please check your config file!");
|
||||
Console.ResetColor();
|
||||
|
||||
return (string.Empty, string.Empty, port);
|
||||
}
|
||||
|
||||
var portalString = ipSpan.ToString().Trim();
|
||||
|
||||
// Override portal variable if a custom one is provided.
|
||||
if (!string.IsNullOrEmpty(customPortal))
|
||||
{
|
||||
portalString = customPortal;
|
||||
config = string.Concat(config.AsSpan(0, startQuoteIndex + 1), portalString, config.AsSpan(endQuoteIndex));
|
||||
|
||||
// Let's re-parse the new portal for verification purposes.
|
||||
return ParseOrSetPortal(ref config);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (IPAddress.TryParse(portalString, out var ipAddress))
|
||||
@@ -88,19 +119,36 @@ static class Helpers
|
||||
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.");
|
||||
{
|
||||
var ipv6Address = Dns.GetHostAddresses(portalString).FirstOrDefault(a => a.AddressFamily == AddressFamily.InterNetworkV6);
|
||||
|
||||
if (ipv6Address == null)
|
||||
throw new Exception("No IPv4/IPv6 address found for the provided host name.");
|
||||
|
||||
return (ipv6Address.ToString(), portalString, port);
|
||||
}
|
||||
|
||||
return (ipv4Address.ToString(), portalString, port);
|
||||
}
|
||||
catch (SocketException socketException) when (socketException.SocketErrorCode == SocketError.HostNotFound)
|
||||
{
|
||||
Console.ForegroundColor = ConsoleColor.Red;
|
||||
Console.WriteLine($"DNS resolution failed for portal: {portalString}");
|
||||
Console.ForegroundColor = ConsoleColor.Gray;
|
||||
|
||||
return (string.Empty, portalString, port);
|
||||
}
|
||||
catch (SocketException)
|
||||
{
|
||||
Console.WriteLine("No valid portal found. Dev (Local) mode disabled.");
|
||||
Console.ForegroundColor = ConsoleColor.Red;
|
||||
Console.WriteLine("No valid portal found.");
|
||||
Console.ForegroundColor = ConsoleColor.Gray;
|
||||
|
||||
return (string.Empty, string.Empty, port);
|
||||
return (string.Empty, portalString, port);
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task<bool> CheckUrl(string url, string fallbackUrl)
|
||||
public static async Task<bool> CheckUrl(string url, string[] fallbackUrls)
|
||||
{
|
||||
using var httpClient = new HttpClient();
|
||||
|
||||
@@ -111,15 +159,31 @@ static class Helpers
|
||||
var result = await httpClient.GetAsync(url);
|
||||
|
||||
if (!result.IsSuccessStatusCode)
|
||||
Console.WriteLine($"{url} not reachable. Falling back to {fallbackUrl}");
|
||||
{
|
||||
Console.WriteLine();
|
||||
Console.WriteLine($"{url} not reachable. Falling back to: ");
|
||||
|
||||
foreach (var furl in fallbackUrls)
|
||||
Console.WriteLine($"- {furl}");
|
||||
}
|
||||
|
||||
return result.IsSuccessStatusCode;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
Console.WriteLine($"{url} not reachable. Falling back to {fallbackUrl}");
|
||||
Console.WriteLine();
|
||||
Console.WriteLine($"{url} not reachable. Falling back to: ");
|
||||
|
||||
foreach (var furl in fallbackUrls)
|
||||
Console.WriteLine($"- {furl}");
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool UsesVersionV2(byte[] appPath)
|
||||
{
|
||||
return appPath.FindPattern(Patterns.Common.Version2UrlNew.ToPattern()) > 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,76 +0,0 @@
|
||||
// Copyright (c) Arctium.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
using System.Net;
|
||||
|
||||
namespace Arctium.WoW.Launcher.Misc;
|
||||
|
||||
public class IPFilter
|
||||
{
|
||||
readonly List<(IPAddress, IPAddress)> _ipRanges = new();
|
||||
|
||||
public void AddCidrRange(ReadOnlySpan<char> cidr)
|
||||
{
|
||||
var separatorIndex = cidr.IndexOf('/');
|
||||
|
||||
if (separatorIndex < 0)
|
||||
return;
|
||||
|
||||
var subnetMaskLengthSpan = cidr[(separatorIndex + 1)..];
|
||||
|
||||
if (!int.TryParse(subnetMaskLengthSpan, out var subnetMaskLength))
|
||||
return;
|
||||
|
||||
Span<byte> subnetMaskBytes = stackalloc byte[4];
|
||||
|
||||
for (var i = 0; i < subnetMaskLength; i++)
|
||||
subnetMaskBytes[i / 8] |= (byte)(1 << (7 - i % 8));
|
||||
|
||||
if (!IPAddress.TryParse(cidr[..separatorIndex], out var ip))
|
||||
return;
|
||||
|
||||
ReadOnlySpan<byte> ipBytes = ip.GetAddressBytes();
|
||||
|
||||
Span<byte> networkAddressBytes = stackalloc byte[4];
|
||||
Span<byte> broadcastAddressBytes = stackalloc byte[4];
|
||||
|
||||
for (var i = 0; i < 4; i++)
|
||||
{
|
||||
networkAddressBytes[i] = (byte)(ipBytes[i] & subnetMaskBytes[i]);
|
||||
broadcastAddressBytes[i] = (byte)(networkAddressBytes[i] | ~subnetMaskBytes[i]);
|
||||
}
|
||||
|
||||
_ipRanges.Add((new(networkAddressBytes), new(broadcastAddressBytes)));
|
||||
}
|
||||
|
||||
public bool IsInRange(ReadOnlySpan<char> targetIP)
|
||||
{
|
||||
if (!IPAddress.TryParse(targetIP, out var ip))
|
||||
return false;
|
||||
|
||||
ReadOnlySpan<byte> targetBytes = ip.GetAddressBytes();
|
||||
|
||||
foreach (var range in _ipRanges)
|
||||
{
|
||||
ReadOnlySpan<byte> networkAddressBytes = range.Item1.GetAddressBytes();
|
||||
ReadOnlySpan<byte> broadcastAddressBytes = range.Item2.GetAddressBytes();
|
||||
|
||||
var isInRange = true;
|
||||
|
||||
for (var i = 0; i < 4; i++)
|
||||
{
|
||||
if ((targetBytes[i] & networkAddressBytes[i]) != networkAddressBytes[i] ||
|
||||
(targetBytes[i] & broadcastAddressBytes[i]) != targetBytes[i])
|
||||
{
|
||||
isInRange = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (isInRange)
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,10 @@
|
||||
// Copyright (c) Arctium.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
namespace Arctium.WoW.Launcher.Misc;
|
||||
namespace Arctium.Game.Launcher.Misc;
|
||||
|
||||
static class NativeWindows
|
||||
{
|
||||
/// kernel32.dll
|
||||
// Process
|
||||
[DllImport("kernel32.dll", EntryPoint = "CreateProcessA", SetLastError = true)]
|
||||
public static extern bool CreateProcess(string lpApplicationName, string lpCommandLine, nint lpProcessAttributes, nint lpThreadAttributes, bool bInheritHandles, uint dwCreationFlags, nint lpEnvironment, string lpCurrentDirectory, ref StartupInfo lpStartupInfo, out ProcessInformation lpProcessInformation);
|
||||
|
||||
@@ -16,13 +14,6 @@ static class NativeWindows
|
||||
[DllImport("kernel32.dll", EntryPoint = "CloseHandle")]
|
||||
public static extern void CloseHandle(nint handle);
|
||||
|
||||
// Memory
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
public static extern int VirtualQueryEx(nint hProcess, nint lpBaseAddress, out MemoryBasicInformation mbi, int dwSize);
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
public static extern nint VirtualAllocEx(nint hProcess, nint lpAddress, uint dwSize, uint flAllocationType, uint flProtect);
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
public static extern bool VirtualProtectEx(nint hProcess, nint lpAddress, uint dwSize, uint flNewProtect, out uint lpflOldProtect);
|
||||
|
||||
@@ -35,27 +26,12 @@ static class NativeWindows
|
||||
[DllImport("kernel32.dll", EntryPoint = "FlushInstructionCache", SetLastError = true)]
|
||||
public static extern bool FlushInstructionCache(nint hProcess, nint lpBaseAddress, uint dwSize);
|
||||
|
||||
/// ntdll.dll
|
||||
// Process
|
||||
[DllImport("ntdll.dll", SetLastError = true)]
|
||||
public static extern NtStatus NtQueryInformationProcess(nint hProcess, int pic, ref ProcessBasicInformation pbi, int cb, out int pSize);
|
||||
|
||||
// Page/View
|
||||
[DllImport("ntdll.dll", SetLastError = true)]
|
||||
public static extern NtStatus NtCreateSection(ref nint sectionHandle, uint accessMask, nint zero, ref LargeInteger maximumSize, uint protection, uint allocationAttributes, nint zero2);
|
||||
|
||||
[DllImport("ntdll.dll", SetLastError = true)]
|
||||
public static extern NtStatus NtMapViewOfSection(nint sectionHandle, nint proccessHandle, ref nint baseAddress, nint zero, ulong regionSize, out LargeInteger sectionOffset, out uint viewSize, uint viewSection, nint zero2, int protection);
|
||||
|
||||
[DllImport("ntdll.dll", SetLastError = true)]
|
||||
public static extern NtStatus NtUnmapViewOfSection(nint processHandle, nint baseAddress);
|
||||
public static extern int NtQueryInformationProcess(nint hProcess, int pic, ref ProcessBasicInformation pbi, int cb, out int pSize);
|
||||
|
||||
[DllImport("ntdll.dll", SetLastError = true)]
|
||||
public static extern nint NtResumeProcess(nint processHandle);
|
||||
|
||||
[DllImport("ntdll.dll", SetLastError = true)]
|
||||
public static extern nint NtSuspendProcess(nint processHandle);
|
||||
|
||||
[DllImport("ntdll.dll", SetLastError = true)]
|
||||
public static extern nint NtClose(nint handle);
|
||||
}
|
||||
|
||||
189
src/ModLoader.cs
189
src/ModLoader.cs
@@ -1,189 +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;
|
||||
|
||||
class ModLoader
|
||||
{
|
||||
static readonly HashSet<uint> _loadedFileIds = new();
|
||||
|
||||
public static bool HookClient(WinMemory memory, nint processHandle, nint idAlloc, nint stringAlloc)
|
||||
{
|
||||
var asm = new byte[]
|
||||
{
|
||||
0x85, 0xD2, 0x0F, 0x88, 0x72, 0xAF, 0xFF, 0xFF, 0x33, 0xC0,
|
||||
0x49, 0xB9, 0x22, 0x22, 0x22, 0x22, 0x11, 0x11, 0x11, 0x11,
|
||||
0x8D, 0x0C, 0x40, 0xC1, 0xE1, 0x02, 0x42, 0x39, 0x14, 0x09,
|
||||
0x74, 0x0C, 0xFF, 0xC0, 0x3D, 0xEF, 0xBE, 0xAD, 0xDE, 0x72,
|
||||
0xEB, 0x33, 0xC0, 0xC3, 0x4A, 0x8B, 0x44, 0x09, 0x04, 0xC3
|
||||
};
|
||||
|
||||
var trampolineInjectAddress = NativeWindows.VirtualAllocEx(processHandle, IntPtr.Zero, (uint)asm.Length + 32, 0x00001000, (uint)MemProtection.ExecuteRead);
|
||||
var hookInstructions = new byte[] { 0x48, 0xB8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xE0, 0x90 };
|
||||
|
||||
// Copy the asm code address.
|
||||
Buffer.BlockCopy(BitConverter.GetBytes(trampolineInjectAddress), 0, hookInstructions, 2, 8);
|
||||
|
||||
// Inside the file load func!!!
|
||||
var hookAddress = memory.Data.FindPattern(Patterns.Windows.CustomFileIdHook);
|
||||
|
||||
if (hookAddress == 0)
|
||||
throw new InvalidDataException("CustomFileIdHook");
|
||||
|
||||
// Read original data from the hook function.
|
||||
var originalBytes = memory.Read(memory.BaseAddress + hookAddress, 13);
|
||||
|
||||
// Apply the hook.
|
||||
memory.QueuePatch(hookAddress, hookInstructions, "CustomFileIdHook");
|
||||
|
||||
// Copy count bytes.
|
||||
Buffer.BlockCopy(BitConverter.GetBytes(_loadedFileIds.Count), 0, asm, 35, 4);
|
||||
|
||||
// Copy mapping ptr bytes.
|
||||
Buffer.BlockCopy(BitConverter.GetBytes(idAlloc), 0, asm, 12, 8);
|
||||
|
||||
// Calculate the jump address bytes.
|
||||
var trampolineJmpAddress = (uint)(trampolineInjectAddress + asm.Length - (trampolineInjectAddress + 2) - 6);
|
||||
|
||||
// Copy trampoline bytes.
|
||||
Buffer.BlockCopy(BitConverter.GetBytes(trampolineJmpAddress), 0, asm, 4, 4);
|
||||
|
||||
memory.Write(trampolineInjectAddress, asm);
|
||||
|
||||
var jmpInstruction = new byte[] { 0x48, 0xB8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xE0, 0x90 };
|
||||
|
||||
// Copy the address where we continue to the jump instruction set.
|
||||
Buffer.BlockCopy(BitConverter.GetBytes((ulong)(hookAddress + memory.BaseAddress + 13)), 0, jmpInstruction, 2, 8);
|
||||
|
||||
memory.Write(trampolineInjectAddress + asm.Length, originalBytes.Concat(jmpInstruction).ToArray());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static (nint idAlloc, nint stringAlloc) LoadFileMappings(nint processHandle)
|
||||
{
|
||||
var loadedMappings = new List<(byte[] fileId, byte[] path, uint StringPos)>();
|
||||
var count = 0u;
|
||||
var localStringLength = 0u;
|
||||
|
||||
Console.WriteLine();
|
||||
Console.WriteLine($"Loading file mappings from '{AppDomain.CurrentDomain.BaseDirectory}files':");
|
||||
|
||||
Directory.CreateDirectory($"{AppDomain.CurrentDomain.BaseDirectory}mappings");
|
||||
|
||||
// Add all file mappings
|
||||
foreach (var f in Directory.EnumerateFiles($"{AppDomain.CurrentDomain.BaseDirectory}mappings", "*.txt", SearchOption.TopDirectoryOnly))
|
||||
{
|
||||
Console.ForegroundColor = ConsoleColor.DarkYellow;
|
||||
Console.ForegroundColor = ConsoleColor.Gray;
|
||||
Console.Write($"- {new FileInfo(f).Name}");
|
||||
|
||||
try
|
||||
{
|
||||
GetFileMappingData(loadedMappings, out var localCount, ref localStringLength, f);
|
||||
|
||||
count += localCount;
|
||||
|
||||
Console.ForegroundColor = ConsoleColor.Green;
|
||||
Console.WriteLine(" (Done)");
|
||||
Console.ForegroundColor = ConsoleColor.Gray;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.ForegroundColor = ConsoleColor.Red;
|
||||
Console.WriteLine(" (Failed)");
|
||||
Console.ForegroundColor = ConsoleColor.Gray;
|
||||
|
||||
Console.WriteLine(ex);
|
||||
Console.WriteLine(ex.StackTrace);
|
||||
}
|
||||
}
|
||||
|
||||
Console.WriteLine();
|
||||
|
||||
// Just startup without doing anything.
|
||||
if (loadedMappings.Count == 0)
|
||||
{
|
||||
Console.WriteLine("No custom files.");
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.ForegroundColor = ConsoleColor.DarkGreen;
|
||||
Console.WriteLine($"Loaded {loadedMappings.Count} file mappings.");
|
||||
Console.ForegroundColor = ConsoleColor.Gray;
|
||||
Console.WriteLine();
|
||||
|
||||
// Allocate file mapping data.
|
||||
return AllocateFileMapping(processHandle, loadedMappings, count, localStringLength);
|
||||
}
|
||||
|
||||
return default;
|
||||
}
|
||||
|
||||
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))
|
||||
.ToLookup(k => k[1], k => k[0]);
|
||||
|
||||
var filesDir = $@"{AppDomain.CurrentDomain.BaseDirectory}files";
|
||||
|
||||
// Create if it doesn't exist.
|
||||
Directory.CreateDirectory(filesDir);
|
||||
|
||||
Console.WriteLine();
|
||||
|
||||
foreach (var f in Directory.EnumerateFiles(filesDir, "*", SearchOption.AllDirectories))
|
||||
{
|
||||
// Get the sub path inside the files folder.
|
||||
var filesDirLength = Encoding.UTF8.GetByteCount(filesDir);
|
||||
var pathBytes = Encoding.UTF8.GetBytes(f);
|
||||
var path = Encoding.UTF8.GetString(pathBytes.Skip(filesDirLength + 1).ToArray()).Replace("\\", "/").ToLowerInvariant();
|
||||
|
||||
foreach (var fileId in mappings[path])
|
||||
{
|
||||
var id = uint.Parse(fileId);
|
||||
|
||||
if (_loadedFileIds.Contains(id))
|
||||
{
|
||||
Console.WriteLine($"Skipping overlapping file '{id} - {f}'.");
|
||||
continue;
|
||||
}
|
||||
|
||||
_loadedFileIds.Add(id);
|
||||
|
||||
loadedMappings.Add((BitConverter.GetBytes(id), pathBytes, stringLength));
|
||||
|
||||
stringLength += ((uint)pathBytes.Length + 1);
|
||||
}
|
||||
}
|
||||
|
||||
count = (uint)loadedMappings.Count;
|
||||
}
|
||||
|
||||
static (nint, nint) AllocateFileMapping(nint handle, List<(byte[] fileId, byte[] path, uint StringPos)> loadedMappings, uint count, uint stringLength)
|
||||
{
|
||||
var idAlloc = NativeWindows.VirtualAllocEx(handle, IntPtr.Zero, (count * (4 + 8)), 0x00001000, 0x04);
|
||||
var stringAlloc = NativeWindows.VirtualAllocEx(handle, IntPtr.Zero, stringLength, 0x00001000, 0x04);
|
||||
var idAllocData = new byte[(count * (4 + 8))];
|
||||
var stringAllocData = new byte[stringLength];
|
||||
|
||||
Parallel.For(0, loadedMappings.Count, 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);
|
||||
});
|
||||
|
||||
NativeWindows.WriteProcessMemory(handle, idAlloc, idAllocData, idAllocData.Length, out var _);
|
||||
NativeWindows.WriteProcessMemory(handle, stringAlloc, stringAllocData, stringAllocData.Length, out var _);
|
||||
|
||||
// READ ONLY
|
||||
NativeWindows.VirtualProtectEx(handle, idAlloc, (count * (4 + 8)), 0x02, out var _);
|
||||
NativeWindows.VirtualProtectEx(handle, stringAlloc, stringLength, 0x02, out var _);
|
||||
|
||||
return (idAlloc, stringAlloc);
|
||||
}
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
@@ -1,18 +1,10 @@
|
||||
// Copyright (c) Arctium.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
namespace Arctium.WoW.Launcher.Patches;
|
||||
namespace Arctium.Game.Launcher.Patches;
|
||||
|
||||
static class Windows
|
||||
{
|
||||
public static byte[] Integrity = { 0xC2, 0x00, 0x00 };
|
||||
public static byte[] CertBundle = { 0x90, 0x90 };
|
||||
public static byte[] CertCommonName = { 0xB0, 0x01 };
|
||||
public static byte[] CertChain = { 0xB3, 0x01 };
|
||||
public static byte[] ShortJump = { 0xEB };
|
||||
public static byte[] NoJump = { 0x00, 0x00, 0x00, 0x00 };
|
||||
public static byte[] AuthSeed = { 0x0F, 0x28, 0x05, 0xEF, 0xBE, 0xAD, 0xDE, 0x0F, 0x11, 0x02, 0xC3 };
|
||||
|
||||
// Registry entry used for -launcherlogin.
|
||||
public static byte[] LauncherLogin = Encoding.UTF8.GetBytes(@"Software\Custom Game Server Dev\Battle.net\Launch Options\");
|
||||
}
|
||||
|
||||
@@ -1,15 +1,19 @@
|
||||
// Copyright (c) Arctium.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
namespace Arctium.WoW.Launcher.Patterns;
|
||||
namespace Arctium.Game.Launcher.Patterns;
|
||||
|
||||
static class Common
|
||||
{
|
||||
public static short[] ConnectToModulus = { 0x91, 0xD5, 0x9B, 0xB7, 0xD4, 0xE1, 0x83, 0xA5 };
|
||||
public static short[] SignatureModulus = { 0x35, 0xFF, 0x17, 0xE7, 0x33, 0xC4, 0xD3, 0xD4 };
|
||||
public static short[] CryptoRsaModulus = { 0x71, 0xFD, 0xFA, 0x60, 0x14, 0x0D, 0xF2, 0x05 };
|
||||
public static short[] CryptoEdPublicKey = { 0x15, 0xD6, 0x18, 0xBD, 0x7D, 0xB5, 0x77, 0xBD };
|
||||
public static short[] CryptoEdPublicKey = { 0x15, 0xD6, 0x18, 0xBD, 0x7D, 0xB5, 0x77, 0xBD, 0x9A, 0x8D, 0x45, 0x76, 0x9C, 0x59, 0xE4, 0xFC, 0x63 };
|
||||
|
||||
public static short[] CertBundle = "{\"Created\":".ToPattern();
|
||||
public static string VersionUrl = "http://%s.patch.battle.net:1119/%s/versions";
|
||||
public static string Version2Url = "https://%s.version.battle.net/v2/products/%s/versions";
|
||||
public static string Version2UrlNew = "https://%s.version.battle.net/v2/products/%s/%s";
|
||||
public static string Version2ChinaUrl = "https://cn.version.battlenet.com.cn/v2/products/%s/versions";
|
||||
public static string Version2ChinaUrlNew = "https://cn.version.battlenet.com.cn/v2/products/%s/%s";
|
||||
public static string CdnsUrl = "http://%s.patch.battle.net:1119/%s/cdns";
|
||||
public static short[] Portal = ".actual.battle.net\0".ToPattern();
|
||||
}
|
||||
|
||||
@@ -1,34 +1,10 @@
|
||||
// Copyright (c) Arctium.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
namespace Arctium.WoW.Launcher.Patterns;
|
||||
namespace Arctium.Game.Launcher.Patterns;
|
||||
|
||||
static class Windows
|
||||
{
|
||||
public static short[] Init = { 0xC7, 0x05, -1, -1, -1, -1, 0x01, 0x00, 0x00, 0x00, 0x48, 0x8D, -1, -1, -1, -1, -1, 0x48, 0x8D, -1, -1, -1, -1, -1, 0xE8, -1, -1, -1, -1, 0x85 };
|
||||
|
||||
// Anti Crash.
|
||||
public static short[] Integrity = { 0x44, 0x89, -1, 0x24, -1, 0x44, 0x89, -1, 0x24, -1, 0x89, -1, 0x24, -1, 0x48, 0x89, -1, 0x24, -1, 0x53, 0x56, 0x57 };
|
||||
public static short[] Integrity2 = { 0x44, 0x89, -1, 0x24, -1, 0x44, 0x89, -1, 0x24, -1, 0x89, -1, 0x24, -1, 0x48, 0x89, -1, 0x24, -1, 0x53, 0x57 };
|
||||
public static short[] Remap = { 0x48, -1, -1, 0x1E, 0x0F, 0x83 };
|
||||
|
||||
// Certificate bundle loading.
|
||||
public static short[] CertBundle = { 0x75, 0x06, 0x48, -1, -1, 0x60, 0x5F, 0xC3 };
|
||||
public static short[] CertCommonName = { 0x80, -1, 0x2A, 0x75, -1, 0x32, 0xC0, 0x48 };
|
||||
|
||||
// Developer mode.
|
||||
public static short[] CertChain = { 0x32, 0xDB, 0xEB, 0x02, 0xB3, 0x01, 0x48, 0x83, -1, -1, 0x00, 0x00, 0x00, 0x00 };
|
||||
|
||||
// Auth seed function.
|
||||
public static short[] AuthSeed = { 0x57, 0x6F, 0x57, 0x00, 0xE8, -1, -1, -1, -1, 0x48, 0x8D };
|
||||
|
||||
// Custom files (mods).
|
||||
public static short[] LoadByFileId = { 0x41, -1, -1, 0x01, 0x0F, 0x84, -1, 0x00, 0x00, 0x00, 0x48, 0x8B, -1, -1, -1, -1, -1, 0x8B };
|
||||
public static short[] LoadByFileIdAlternate = { 0x01, 0x0F, 0x84, -1, -1, -1, -1, 0x48, 0x8B, -1, -1, -1, -1, -1, 0x8B, -1, 0xE8 };
|
||||
public static short[] LoadByFilePath = { 0x01, 0x0F, 0x84, -1, -1, -1, -1, 0x48, 0x8B, -1, -1, -1, -1, -1, 0x44, 0x89, -1, -1, -1, 0x48, 0x85, 0xC9 };
|
||||
public static short[] LoadByFilePathAlternate = { 0x01, 0x0F, 0x84, -1, -1, -1, -1, 0x48, 0x8B, -1, -1, -1, -1, -1, 0x44, 0x89, -1, -1, -1, 0x00, 0x00, 0x00, 0x48, 0x85, 0xC9 };
|
||||
public static short[] CustomFileIdHook = { 0x48, 0x89, 0x5C, 0x24, 0x08, 0x57, 0x48, 0x83, 0xEC, 0x20, 0x48, 0x8B, -1, 0x48, -1, -1, 0x48, -1, -1, -1, -1, -1, -1, 0xE8, -1, -1, -1, -1, 0x48, -1, -1, -1, -1, -1, -1, 0x48, -1, -1, 0x74, -1, 0x48, -1, 0xCD };
|
||||
|
||||
// Registry entry used for -launcherlogin.
|
||||
public static short[] LauncherLogin = @"Software\Blizzard Entertainment\Battle.net\Launch Options\".ToPattern();
|
||||
}
|
||||
|
||||
@@ -2,30 +2,25 @@
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
using System.CommandLine.Parsing;
|
||||
using Arctium.WoW.Launcher;
|
||||
using Arctium.Game.Launcher;
|
||||
|
||||
using static Arctium.WoW.Launcher.Misc.Helpers;
|
||||
using static Arctium.Game.Launcher.Misc.Helpers;
|
||||
|
||||
// "Arctium" should not be removed from the final binary name.
|
||||
if (!Process.GetCurrentProcess().ProcessName.Contains("arctium", StringComparison.InvariantCultureIgnoreCase))
|
||||
WaitAndExit();
|
||||
PublisherCheck();
|
||||
PrintHeader();
|
||||
|
||||
Console.ForegroundColor = ConsoleColor.Red;
|
||||
Console.WriteLine("THIS LAUNCHER IS DISCONTINUED!!! Stop asking for support.");
|
||||
Console.ForegroundColor = ConsoleColor.Yellow;
|
||||
Console.WriteLine("You can find the new supported launcher on https://arctium.io");
|
||||
var versionInfo = FileVersionInfo.GetVersionInfo(Environment.ProcessPath);
|
||||
Console.WriteLine($"Using {(int)(Environment.ProcessorCount * 0.75)} of {Environment.ProcessorCount} processors.");
|
||||
Console.WriteLine();
|
||||
Console.ResetColor();
|
||||
WaitAndExit(5000);
|
||||
|
||||
PrintHeader("WoW Client Launcher");
|
||||
PublisherCheck();
|
||||
|
||||
LaunchOptions.RootCommand.SetHandler(async context =>
|
||||
{
|
||||
CreateDevIPFilter(out var ipFilter);
|
||||
|
||||
try
|
||||
{
|
||||
// Prefer / instead of \ for the client path.
|
||||
var appPath = (await Launcher.PrepareGameLaunch(context.ParseResult, ipFilter)).Replace("\\", "/");
|
||||
var appPath = (await Launcher.PrepareGameLaunch(context.ParseResult, default)).Replace("\\", "/");
|
||||
var gameCommandLine = string.Join(" ", context.ParseResult.UnmatchedTokens);
|
||||
|
||||
// Add config parameter to the game command line.
|
||||
@@ -33,39 +28,19 @@ LaunchOptions.RootCommand.SetHandler(async context =>
|
||||
|
||||
if (string.IsNullOrEmpty(appPath) || !Launcher.LaunchGame(appPath, gameCommandLine, context.ParseResult))
|
||||
WaitAndExit(5000);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (Launcher.GameProcessHandle != nint.Zero)
|
||||
NativeWindows.TerminateProcess(Launcher.GameProcessHandle, 0);
|
||||
|
||||
Console.ForegroundColor = ConsoleColor.Red;
|
||||
Console.WriteLine(ex.Message);
|
||||
Console.ResetColor();
|
||||
}
|
||||
});
|
||||
|
||||
await LaunchOptions.Instance.InvokeAsync(args);
|
||||
return;
|
||||
|
||||
void CreateDevIPFilter(out IPFilter ipFilter)
|
||||
{
|
||||
ipFilter = new IPFilter();
|
||||
|
||||
ipFilter.AddCidrRange("0.0.0.0/8");
|
||||
ipFilter.AddCidrRange("10.0.0.0/8");
|
||||
ipFilter.AddCidrRange("100.64.0.0/10");
|
||||
ipFilter.AddCidrRange("127.0.0.0/8");
|
||||
ipFilter.AddCidrRange("169.254.0.0/16");
|
||||
ipFilter.AddCidrRange("172.16.0.0/12");
|
||||
ipFilter.AddCidrRange("192.0.0.0/24");
|
||||
ipFilter.AddCidrRange("192.0.0.0/29");
|
||||
ipFilter.AddCidrRange("192.0.0.8/32");
|
||||
ipFilter.AddCidrRange("192.0.0.9/32");
|
||||
ipFilter.AddCidrRange("192.0.0.170/32");
|
||||
ipFilter.AddCidrRange("192.0.0.171/32");
|
||||
ipFilter.AddCidrRange("192.0.2.0/24");
|
||||
ipFilter.AddCidrRange("192.31.196.0/24");
|
||||
ipFilter.AddCidrRange("192.52.193.0/24");
|
||||
ipFilter.AddCidrRange("192.88.99.0/24");
|
||||
ipFilter.AddCidrRange("192.168.0.0/16");
|
||||
ipFilter.AddCidrRange("192.175.48.0/24");
|
||||
ipFilter.AddCidrRange("198.18.0.0/15");
|
||||
ipFilter.AddCidrRange("198.51.100.0/24");
|
||||
ipFilter.AddCidrRange("203.0.113.0/24");
|
||||
ipFilter.AddCidrRange("240.0.0.0/4");
|
||||
ipFilter.AddCidrRange("255.255.255.255/32");
|
||||
}
|
||||
|
||||
static void WaitAndExit(int ms = 2000)
|
||||
{
|
||||
|
||||
@@ -1,17 +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.Structures;
|
||||
|
||||
[StructLayout(LayoutKind.Explicit, Size = 8)]
|
||||
struct LargeInteger
|
||||
{
|
||||
[FieldOffset(0)]
|
||||
public long Quad;
|
||||
[FieldOffset(0)]
|
||||
public uint Low;
|
||||
[FieldOffset(4)]
|
||||
public int High;
|
||||
|
||||
public static int Size => Marshal.SizeOf<LargeInteger>();
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
// Copyright (c) Arctium.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
namespace Arctium.WoW.Launcher.Structures;
|
||||
namespace Arctium.Game.Launcher.Structures;
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
struct MemoryBasicInformation
|
||||
@@ -10,9 +10,9 @@ struct MemoryBasicInformation
|
||||
public nint AllocationBase;
|
||||
public MemProtection AllocationProtect;
|
||||
public nint RegionSize;
|
||||
public MemState State;
|
||||
public int State;
|
||||
public MemProtection Protect;
|
||||
public MemType Type;
|
||||
public int Type;
|
||||
|
||||
public static int Size => Marshal.SizeOf<MemoryBasicInformation>();
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Copyright (c) Arctium.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
namespace Arctium.WoW.Launcher.Structures;
|
||||
namespace Arctium.Game.Launcher.Structures;
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||
struct ProcessBasicInformation
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Copyright (c) Arctium.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
namespace Arctium.WoW.Launcher.Structures;
|
||||
namespace Arctium.Game.Launcher.Structures;
|
||||
|
||||
struct ProcessInformation
|
||||
{
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Copyright (c) Arctium.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
namespace Arctium.WoW.Launcher.Structures;
|
||||
namespace Arctium.Game.Launcher.Structures;
|
||||
|
||||
struct StartupInfo
|
||||
{
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
// Copyright (c) Arctium.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
global using System.Collections.Concurrent;
|
||||
global using System.CommandLine;
|
||||
global using System.Diagnostics;
|
||||
global using System.Runtime.CompilerServices;
|
||||
global using System.Runtime.InteropServices;
|
||||
global using System.Text;
|
||||
|
||||
global using Arctium.WoW.Launcher.Constants;
|
||||
global using Arctium.WoW.Launcher.IO;
|
||||
global using Arctium.WoW.Launcher.Misc;
|
||||
global using Arctium.WoW.Launcher.Structures;
|
||||
global using Arctium.Game.Launcher.Constants;
|
||||
global using Arctium.Game.Launcher.IO;
|
||||
global using Arctium.Game.Launcher.Misc;
|
||||
global using Arctium.Game.Launcher.Structures;
|
||||
|
||||
Reference in New Issue
Block a user