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
|
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
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
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.**
|
**Later = all future version in that branch.**
|
||||||
- **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)**
|
|
||||||
|
|
||||||
#### Old README
|
### NOTE FOR SERVER CONNECTIONS
|
||||||
Most recent README available here: [README](README_OLD.md)
|
* 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
|
enum GameVersion
|
||||||
{
|
{
|
||||||
Retail,
|
Retail,
|
||||||
Classic,
|
Classic,
|
||||||
ClassicEra
|
ClassicEra,
|
||||||
|
ClassicTitan
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,19 +1,9 @@
|
|||||||
// Copyright (c) Arctium.
|
// Copyright (c) Arctium.
|
||||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
// 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
|
enum MemProtection
|
||||||
{
|
{
|
||||||
NoAccess = 0x1,
|
ReadWrite = 0x4,
|
||||||
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.
|
// Copyright (c) Arctium.
|
||||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
// 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.Game.Launcher.Misc.NativeWindows;
|
||||||
using static Arctium.WoW.Launcher.Misc.Helpers;
|
using static Arctium.Game.Launcher.Misc.Helpers;
|
||||||
|
|
||||||
namespace Arctium.WoW.Launcher.IO;
|
namespace Arctium.Game.Launcher.IO;
|
||||||
|
|
||||||
class WinMemory
|
class WinMemory
|
||||||
{
|
{
|
||||||
public byte[] Data { get; set; }
|
public byte[] Data { get; set; }
|
||||||
|
|
||||||
public nint BaseAddress { get; }
|
public nint BaseAddress { get; }
|
||||||
|
|
||||||
ProcessBasicInformation _peb;
|
|
||||||
|
|
||||||
readonly nint _processHandle;
|
readonly nint _processHandle;
|
||||||
readonly Dictionary<string, (long Address, byte[] Data)> _patchList;
|
|
||||||
|
|
||||||
public WinMemory(ProcessInformation processInformation, long binaryLength)
|
public WinMemory(ProcessInformation processInformation)
|
||||||
{
|
{
|
||||||
_processHandle = processInformation.ProcessHandle;
|
_processHandle = processInformation.ProcessHandle;
|
||||||
|
|
||||||
@@ -28,22 +24,18 @@ class WinMemory
|
|||||||
|
|
||||||
if (BaseAddress == 0)
|
if (BaseAddress == 0)
|
||||||
throw new InvalidOperationException("Error while reading PEB data.");
|
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)
|
public void RefreshMemoryData(int size)
|
||||||
{
|
{
|
||||||
// Reset previous memory data.
|
// 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...");
|
Console.WriteLine("Refreshing client data...");
|
||||||
|
ReadToData(BaseAddress, size);
|
||||||
Data = Read(BaseAddress, size);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -65,7 +57,20 @@ class WinMemory
|
|||||||
return 0;
|
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)
|
public byte[] Read(nint address, int size)
|
||||||
{
|
{
|
||||||
@@ -85,29 +90,6 @@ class WinMemory
|
|||||||
return null;
|
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)
|
public void Write(nint address, byte[] data, MemProtection newProtection = MemProtection.ReadWrite)
|
||||||
{
|
{
|
||||||
try
|
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();
|
printInfo ??= IsDebugBuild();
|
||||||
|
|
||||||
@@ -140,198 +137,54 @@ class WinMemory
|
|||||||
// No result for the given pattern.
|
// No result for the given pattern.
|
||||||
if (patchOffset == 0)
|
if (patchOffset == 0)
|
||||||
{
|
{
|
||||||
Console.ForegroundColor = ConsoleColor.Yellow;
|
if (exitOnFail)
|
||||||
|
{
|
||||||
|
Console.ForegroundColor = ConsoleColor.Red;
|
||||||
|
Console.WriteLine($"[{patchName}] No result found.");
|
||||||
|
Console.WriteLine("Press any key to exit...");
|
||||||
|
|
||||||
Console.WriteLine($"[{patchName}] No result found.");
|
// Only wait if a console is available.
|
||||||
Console.WriteLine("Press any key to exit...");
|
if (!Console.IsInputRedirected)
|
||||||
Console.ReadKey();
|
Console.ReadKey();
|
||||||
|
|
||||||
Launcher.CancellationTokenSource.Cancel();
|
Launcher.CancellationTokenSource.Cancel();
|
||||||
|
|
||||||
|
return Task.FromResult(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (printInfo.Value)
|
||||||
|
{
|
||||||
|
Console.ForegroundColor = ConsoleColor.Yellow;
|
||||||
|
Console.WriteLine($"[{patchName}] No result found. This is just a warning! KEEP GOING...");
|
||||||
|
Console.ResetColor();
|
||||||
|
}
|
||||||
|
|
||||||
|
return Task.FromResult(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
while (Read(patchOffset, patch.Length)?.SequenceEqual(patch) == false)
|
while (Read((nint)patchOffset, patch.Length)?.SequenceEqual(patch) == false)
|
||||||
Write(patchOffset, patch);
|
Write((nint)patchOffset, patch);
|
||||||
|
|
||||||
if (printInfo.Value)
|
if (printInfo.Value)
|
||||||
{
|
{
|
||||||
Console.Write($"[{patchName}]");
|
Console.Write($"[{patchName}] at 0x{patchOffset:X}");
|
||||||
Console.ForegroundColor = ConsoleColor.Green;
|
Console.ForegroundColor = ConsoleColor.Green;
|
||||||
Console.WriteLine(" Done.");
|
Console.WriteLine(" Done.");
|
||||||
Console.ResetColor();
|
Console.ResetColor();
|
||||||
Console.WriteLine();
|
Console.WriteLine();
|
||||||
}
|
}
|
||||||
|
|
||||||
return Task.CompletedTask;
|
return Task.FromResult(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
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)
|
|
||||||
{
|
|
||||||
Console.ForegroundColor = ConsoleColor.Red;
|
|
||||||
Console.WriteLine($"[{patchName}] No result found.");
|
|
||||||
Console.ResetColor();
|
|
||||||
Console.WriteLine("Press any key to exit...");
|
|
||||||
Console.ReadKey();
|
|
||||||
|
|
||||||
Launcher.CancellationTokenSource.Cancel();
|
|
||||||
|
|
||||||
return Task.CompletedTask;
|
|
||||||
}
|
|
||||||
|
|
||||||
return QueuePatch(patchOffset + offsetBase, patch, patchName, printInfo);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool RemapAndPatch(nint viewAddress, int viewSize)
|
|
||||||
{
|
|
||||||
// Suspend before remapping to prevent crashes.
|
|
||||||
NtSuspendProcess(_processHandle);
|
|
||||||
|
|
||||||
Data = Read(viewAddress, viewSize);
|
|
||||||
|
|
||||||
if (Data != null)
|
|
||||||
{
|
|
||||||
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.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;
|
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
nint ReadImageBaseFromPEB(nint processHandle)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (NtQueryInformationProcess(processHandle, 0, ref _peb, ProcessBasicInformation.Size, out _) == NtStatus.Success)
|
ProcessBasicInformation peb = default;
|
||||||
return Read(_peb.PebBaseAddress + 0x10);
|
|
||||||
|
if (NtQueryInformationProcess(processHandle, 0, ref peb, ProcessBasicInformation.Size, out _) == 0)
|
||||||
|
return Read(peb.PebBaseAddress + 0x10);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -340,22 +193,4 @@ class WinMemory
|
|||||||
|
|
||||||
return 0;
|
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,24 +1,22 @@
|
|||||||
// Copyright (c) Arctium.
|
// Copyright (c) Arctium.
|
||||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
|
||||||
using System.CommandLine.Builder;
|
using System.CommandLine.Builder;
|
||||||
using System.CommandLine.Parsing;
|
using System.CommandLine.Parsing;
|
||||||
|
|
||||||
namespace Arctium.WoW.Launcher;
|
namespace Arctium.Game.Launcher;
|
||||||
|
|
||||||
static class LaunchOptions
|
static class LaunchOptions
|
||||||
{
|
{
|
||||||
public static bool IsDevModeAllowed { get; set; }
|
|
||||||
|
|
||||||
public static Option<GameVersion> Version = new("--version", () => GameVersion.Retail);
|
public static Option<GameVersion> Version = new("--version", () => GameVersion.Retail);
|
||||||
public static Option<string> GamePath = new("--path");
|
public static Option<string> GamePath = new("--path");
|
||||||
public static Option<string> GameBinary = new("--binary");
|
public static Option<string> GameBinary = new("--binary");
|
||||||
public static Option<bool> KeepCache = new("--keepcache", () => true);
|
public static Option<bool> KeepCache = new("--keepcache", () => true);
|
||||||
public static Option<bool> UseStaticAuthSeed = new("--staticseed");
|
public static Option<string> VersionUrl = new("--versionurl");
|
||||||
public static Option<bool> DevMode = new("--dev", () => true, "Required for local development without valid certificates.");
|
public static Option<string> CdnsUrl = new("--cdnsurl");
|
||||||
public static Option<string> ProductName = new("--product", () => "wow");
|
public static Option<string> ProductName = new("--product", () => "wow");
|
||||||
public static Option<string> CdnRegion = new("--region", () => "EU");
|
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.
|
// Game command line options.
|
||||||
public static Option<string> GameConfig = new("-config", () => "Config.wtf");
|
public static Option<string> GameConfig = new("-config", () => "Config.wtf");
|
||||||
@@ -31,18 +29,18 @@ static class LaunchOptions
|
|||||||
.UseSuggestDirective()
|
.UseSuggestDirective()
|
||||||
.Build();
|
.Build();
|
||||||
|
|
||||||
public static RootCommand RootCommand = new("Arctium WoW Launcher")
|
public static RootCommand RootCommand = new("Arctium Game Launcher")
|
||||||
{
|
{
|
||||||
Version,
|
Version,
|
||||||
GamePath,
|
GamePath,
|
||||||
GameBinary,
|
GameBinary,
|
||||||
KeepCache,
|
KeepCache,
|
||||||
UseStaticAuthSeed,
|
VersionUrl,
|
||||||
DevMode,
|
CdnsUrl,
|
||||||
ProductName,
|
ProductName,
|
||||||
CdnRegion,
|
CdnRegion,
|
||||||
|
BgsPortal,
|
||||||
GameConfig,
|
GameConfig,
|
||||||
SkipConnectionPatching
|
|
||||||
};
|
};
|
||||||
|
|
||||||
static Command ConfigureCommandLine(Command rootCommand)
|
static Command ConfigureCommandLine(Command rootCommand)
|
||||||
|
|||||||
573
src/Launcher.cs
573
src/Launcher.cs
@@ -1,34 +1,46 @@
|
|||||||
// Copyright (c) Arctium.
|
// Copyright (c) Arctium.
|
||||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
|
||||||
using System.CommandLine.Parsing;
|
using System.CommandLine.Parsing;
|
||||||
using System.Net.Security;
|
using System.Net.Security;
|
||||||
using System.Net.Sockets;
|
using System.Net.Sockets;
|
||||||
using System.Security.Authentication;
|
using System.Security.Authentication;
|
||||||
|
using static Arctium.Game.Launcher.Misc.Helpers;
|
||||||
|
|
||||||
using static Arctium.WoW.Launcher.Misc.Helpers;
|
namespace Arctium.Game.Launcher;
|
||||||
|
|
||||||
namespace Arctium.WoW.Launcher;
|
|
||||||
|
|
||||||
static class Launcher
|
static class Launcher
|
||||||
{
|
{
|
||||||
|
public static nint GameProcessHandle { get; private set; }
|
||||||
public static readonly CancellationTokenSource CancellationTokenSource = new();
|
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);
|
var gameVersion = commandLineResult.GetValueForOption(LaunchOptions.Version);
|
||||||
|
|
||||||
|
if (branchOverwrite.HasValue)
|
||||||
|
gameVersion = branchOverwrite.Value;
|
||||||
|
|
||||||
var (subFolder, binaryName, majorGameVersion, minGameBuild) = gameVersion switch
|
var (subFolder, binaryName, majorGameVersion, minGameBuild) = gameVersion switch
|
||||||
{
|
{
|
||||||
GameVersion.Retail => ("_retail_", "Wow.exe", new[] { 9, 10 }, 37862),
|
GameVersion.Retail => ("_retail_", "Wow.exe", [10, 11, 12], 50401),
|
||||||
GameVersion.Classic => ("_classic_", "WowClassic.exe", new[] { 2, 3 }, 39926),
|
GameVersion.Classic => ("_classic_", "WowClassic.exe", [2, 3, 4, 5], 50063),
|
||||||
GameVersion.ClassicEra => ("_classic_era_", "WowClassic.exe", new[] { 1 }, 40347),
|
GameVersion.ClassicEra => ("_classic_era_", "WowClassic.exe", [1], 51001),
|
||||||
|
GameVersion.ClassicTitan => ("_classic_titan_", "WowClassic.exe", new[] { 3 }, 64393),
|
||||||
_ => throw new NotImplementedException("Invalid game version specified."),
|
_ => throw new NotImplementedException("Invalid game version specified."),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
_tryClients.RemoveAll(c => c == gameVersion);
|
||||||
|
|
||||||
Console.ForegroundColor = ConsoleColor.Yellow;
|
Console.ForegroundColor = ConsoleColor.Yellow;
|
||||||
|
|
||||||
Console.WriteLine($"Mode: Custom Server ({gameVersion})");
|
Console.WriteLine($"Mode: Public Custom Server ({gameVersion})");
|
||||||
Console.WriteLine();
|
|
||||||
Console.ResetColor();
|
Console.ResetColor();
|
||||||
|
|
||||||
var currentFolder = AppDomain.CurrentDomain.BaseDirectory;
|
var currentFolder = AppDomain.CurrentDomain.BaseDirectory;
|
||||||
@@ -51,10 +63,29 @@ static class Launcher
|
|||||||
gameBinaryPath = $"{gameFolder}/{binaryName}";
|
gameBinaryPath = $"{gameFolder}/{binaryName}";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
gameFolder = gameFolder.Replace("\\/", "/").Replace("\\", "/");
|
||||||
|
gameBinaryPath = gameBinaryPath.Replace("\\/", "/").Replace("\\", "/");
|
||||||
|
|
||||||
if (!File.Exists(gameBinaryPath) || !majorGameVersion.Contains(GetVersionValueFromClient(gameBinaryPath).Major))
|
if (!File.Exists(gameBinaryPath) || !majorGameVersion.Contains(GetVersionValueFromClient(gameBinaryPath).Major))
|
||||||
{
|
{
|
||||||
Console.ForegroundColor = ConsoleColor.Red;
|
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;
|
return string.Empty;
|
||||||
}
|
}
|
||||||
@@ -85,77 +116,113 @@ static class Launcher
|
|||||||
}
|
}
|
||||||
|
|
||||||
var configPath = $"{gameFolder}/WTF/{commandLineResult.GetValueForOption(LaunchOptions.GameConfig)}";
|
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.ForegroundColor = ConsoleColor.Yellow;
|
||||||
Console.WriteLine($"Developer mode: {(devModeEnabled ? "Enabled" : "Disabled")}");
|
|
||||||
Console.WriteLine();
|
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;
|
Console.ForegroundColor = ConsoleColor.Gray;
|
||||||
|
|
||||||
// Check for valid certificate when dev mode is disabled.
|
|
||||||
if (!devModeEnabled)
|
if (string.IsNullOrEmpty(portal.HostName) || string.IsNullOrWhiteSpace(portal.HostName))
|
||||||
{
|
{
|
||||||
try
|
Console.ForegroundColor = ConsoleColor.Red;
|
||||||
{
|
Console.WriteLine();
|
||||||
using var tcpClient = new TcpClient();
|
Console.WriteLine("Client Portal should not be empty.");
|
||||||
using var tcpClientTimeout = new CancellationTokenSource(TimeSpan.FromSeconds(5));
|
Console.WriteLine("Be sure to have a valid portal in your Config.wtf file.");
|
||||||
|
Console.ResetColor();
|
||||||
|
|
||||||
await tcpClient.ConnectAsync(portal.HostName, portal.Port, tcpClientTimeout.Token);
|
return string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
using var sslStream = new SslStream(tcpClient.GetStream(), false,
|
// Return if no valid ip address has been found.
|
||||||
(_, _, _, sslPolicyErrors) =>
|
if (string.IsNullOrEmpty(portal.IPAddress) || string.IsNullOrWhiteSpace(portal.IPAddress))
|
||||||
{
|
return string.Empty;
|
||||||
// Redirect to the trusted cert warning.
|
|
||||||
if (sslPolicyErrors != SslPolicyErrors.None)
|
|
||||||
throw new AuthenticationException();
|
|
||||||
|
|
||||||
Console.ForegroundColor = ConsoleColor.Green;
|
// Check for valid certificate.
|
||||||
Console.WriteLine($"Certificate for server '{portal.HostName}' successfully validated.");
|
try
|
||||||
Console.WriteLine();
|
{
|
||||||
Console.ResetColor();
|
using var tcpClient = new TcpClient();
|
||||||
|
|
||||||
return true;
|
// 3.5 seconds timeout.
|
||||||
},
|
const int timeout = 3500;
|
||||||
null
|
|
||||||
);
|
|
||||||
|
|
||||||
sslStream.AuthenticateAsClient(portal.HostName);
|
tcpClient.ReceiveTimeout = timeout;
|
||||||
}
|
tcpClient.SendTimeout = timeout;
|
||||||
catch (Exception exception) when (exception is SocketException or OperationCanceledException)
|
|
||||||
{
|
|
||||||
Console.ForegroundColor = ConsoleColor.Red;
|
|
||||||
Console.WriteLine($"{portal.HostName}:{portal.Port} is offline.");
|
|
||||||
Console.ResetColor();
|
|
||||||
|
|
||||||
return string.Empty;
|
using var tcpClientTimeout = new CancellationTokenSource(TimeSpan.FromMilliseconds(timeout));
|
||||||
}
|
|
||||||
catch (AuthenticationException)
|
|
||||||
{
|
|
||||||
Console.ForegroundColor = ConsoleColor.Yellow;
|
|
||||||
Console.WriteLine($"Server with host name {portal.HostName} 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;
|
await tcpClient.ConnectAsync(portal.HostName, portal.Port, tcpClientTimeout.Token);
|
||||||
}
|
|
||||||
|
using var sslStream = new SslStream(tcpClient.GetStream(), false,
|
||||||
|
(_, _, _, sslPolicyErrors) =>
|
||||||
|
{
|
||||||
|
// Redirect to the trusted cert warning.
|
||||||
|
if (sslPolicyErrors != SslPolicyErrors.None)
|
||||||
|
throw new AuthenticationException();
|
||||||
|
|
||||||
|
Console.ForegroundColor = ConsoleColor.Green;
|
||||||
|
Console.WriteLine($"Certificate for server '{portal.HostName}' successfully validated.");
|
||||||
|
Console.WriteLine();
|
||||||
|
Console.ResetColor();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
null
|
||||||
|
);
|
||||||
|
|
||||||
|
sslStream.AuthenticateAsClient(portal.HostName);
|
||||||
|
}
|
||||||
|
catch (IOException exception)
|
||||||
|
{
|
||||||
|
Console.ForegroundColor = ConsoleColor.Red;
|
||||||
|
Console.WriteLine($"Server {portal.HostName}:{portal.Port}: {exception.Message}");
|
||||||
|
Console.ResetColor();
|
||||||
|
|
||||||
|
return string.Empty;
|
||||||
|
}
|
||||||
|
catch (AuthenticationException)
|
||||||
|
{
|
||||||
|
Console.ForegroundColor = ConsoleColor.Yellow;
|
||||||
|
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;
|
return gameBinaryPath;
|
||||||
@@ -168,7 +235,61 @@ static class Launcher
|
|||||||
|
|
||||||
Console.ForegroundColor = ConsoleColor.Yellow;
|
Console.ForegroundColor = ConsoleColor.Yellow;
|
||||||
Console.WriteLine($"Client Build {clientVersion}");
|
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.WriteLine();
|
||||||
Console.ResetColor();
|
Console.ResetColor();
|
||||||
|
|
||||||
@@ -178,158 +299,80 @@ static class Launcher
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
Console.ForegroundColor = ConsoleColor.White;
|
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);
|
ref startupInfo, out processInfo);
|
||||||
|
|
||||||
// On some systems we have to launch the game with the application name used.
|
// On some systems we have to launch the game with the application name used.
|
||||||
if (!createSuccess)
|
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.
|
// Start process with suspend flags.
|
||||||
if (createSuccess)
|
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.
|
var appDataLength = gameAppData.Length;
|
||||||
NativeWindows.NtResumeProcess(processInfo.ProcessHandle);
|
var memory = new WinMemory(processInfo);
|
||||||
|
|
||||||
MemoryBasicInformation mbi;
|
if (memory.BaseAddress != 0)
|
||||||
|
|
||||||
// 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)
|
|
||||||
{
|
{
|
||||||
NativeWindows.NtSuspendProcess(processInfo.ProcessHandle);
|
Process gameProcess = Process.GetProcessById((int)processInfo.ProcessId);
|
||||||
|
|
||||||
byte[] certBundleData = Convert.FromBase64String(Patches.Common.CertBundleData);
|
|
||||||
|
|
||||||
// Refresh the client data before patching.
|
// 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.
|
// Custom CDN related patches.
|
||||||
var modulusOffset = memory.Data.FindPattern(Patterns.Common.CryptoRsaModulus);
|
if ((hasCustomVersionUrl && hasCustomCdnUrl))
|
||||||
var legacyCertMode = clientVersion is (1, >= 14, <= 3, _) or (3, 4, <= 1, _) or (9, _, _, _) or (10, <= 1, < 5, _);
|
|
||||||
|
|
||||||
if (!commandLineResult.GetValueForOption(LaunchOptions.SkipConnectionPatching))
|
|
||||||
{
|
{
|
||||||
if (legacyCertMode)
|
var versionUrlPatches = new byte[versionUrls.Length][];
|
||||||
|
|
||||||
|
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)
|
||||||
{
|
{
|
||||||
Task.WaitAll(new[]
|
Task.WaitAll(
|
||||||
{
|
[
|
||||||
memory.PatchMemory(Patterns.Common.CertBundle, certBundleData, "Certificate Bundle"),
|
memory.TryPatchPatterns(versionUrlPatches, "Version URL", true, false, true,
|
||||||
memory.PatchMemory(Patterns.Common.SignatureModulus, Patches.Common.SignatureModulus, "Certificate Signature RsaModulus")
|
Patterns.Common.Version2UrlNew.ToPattern(), Patterns.Common.Version2ChinaUrlNew.ToPattern()),
|
||||||
}, CancellationTokenSource.Token);
|
], CancellationTokenSource.Token);
|
||||||
}
|
}
|
||||||
|
else
|
||||||
// Wait for all direct memory patch tasks to complete,
|
|
||||||
Task.WaitAll(new[]
|
|
||||||
{
|
{
|
||||||
memory.PatchMemory(Patterns.Common.ConnectToModulus, Patches.Common.RsaModulus, "ConnectTo RsaModulus"),
|
Task.WaitAll(
|
||||||
|
[
|
||||||
// Recent clients have a different signing algorithm in EnterEncryptedMode.
|
memory.TryPatchPatterns(versionUrlPatches, "Version URL", true, false, true,
|
||||||
clientVersion is (9, 2, 7, _) or (3, _, _, _) or (10, _, _, _) or (1, >= 14, >= 4, _)
|
Patterns.Common.VersionUrl.ToPattern(), Patterns.Common.Version2Url.ToPattern(), Patterns.Common.Version2ChinaUrl.ToPattern()),
|
||||||
? memory.PatchMemory(Patterns.Common.CryptoEdPublicKey, Patches.Common.CryptoEdPublicKey, "GameCrypto Ed25519 PublicKey")
|
memory.PatchMemory(Patterns.Common.CdnsUrl.ToPattern(), Encoding.UTF8.GetBytes(cdnsUrl), "CDNs URL", exitOnFail: false),
|
||||||
: memory.PatchMemory(Patterns.Common.CryptoRsaModulus, Patches.Common.RsaModulus, "GameCrypto RsaModulus"),
|
], CancellationTokenSource.Token);
|
||||||
|
}
|
||||||
memory.PatchMemory(Patterns.Common.Portal, Patches.Common.Portal, "Login Portal"),
|
|
||||||
memory.PatchMemory(Patterns.Windows.LauncherLogin, Patches.Windows.LauncherLogin, "Launcher Login Registry")
|
|
||||||
}, CancellationTokenSource.Token);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Wait for all direct memory patch tasks to complete.
|
||||||
|
Task.WaitAll(
|
||||||
|
[
|
||||||
|
memory.PatchMemory(Patterns.Common.ConnectToModulus, Patches.Common.RsaModulus, "ConnectTo 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);
|
||||||
|
|
||||||
NativeWindows.NtResumeProcess(processInfo.ProcessHandle);
|
NativeWindows.NtResumeProcess(processInfo.ProcessHandle);
|
||||||
|
|
||||||
// Enable anti crash in dev mode, custom file mode or static auth seed mode.
|
Console.WriteLine("Done :) ");
|
||||||
#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);
|
Console.ForegroundColor = ConsoleColor.Green;
|
||||||
|
Console.WriteLine("You can login now.");
|
||||||
|
Console.WriteLine("Closing in 3 seconds...");
|
||||||
|
Thread.Sleep(3000);
|
||||||
|
Console.ResetColor();
|
||||||
|
|
||||||
if (!commandLineResult.GetValueForOption(LaunchOptions.SkipConnectionPatching))
|
return true;
|
||||||
{
|
|
||||||
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.ResetColor();
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Console.ForegroundColor = ConsoleColor.Red;
|
|
||||||
Console.WriteLine("Error while launching the client.");
|
|
||||||
|
|
||||||
NativeWindows.TerminateProcess(processInfo.ProcessHandle, 0);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -354,142 +397,4 @@ static class Launcher
|
|||||||
|
|
||||||
return false;
|
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.
|
// Copyright (c) Arctium.
|
||||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
// 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
|
static class Extensions
|
||||||
{
|
{
|
||||||
public static nint ToNint(this byte[] buffer) => (nint)BitConverter.ToInt64(buffer, 0);
|
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)
|
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 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();
|
public static short[] ToPattern(this string data) => Encoding.UTF8.GetBytes(data).Select(b => (short)b).ToArray();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
// Copyright (c) Arctium.
|
// Copyright (c) Arctium.
|
||||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Net.Sockets;
|
using System.Net.Sockets;
|
||||||
|
|
||||||
namespace Arctium.WoW.Launcher.Misc;
|
namespace Arctium.Game.Launcher.Misc;
|
||||||
|
|
||||||
static class Helpers
|
static class Helpers
|
||||||
{
|
{
|
||||||
@@ -17,6 +17,13 @@ static class Helpers
|
|||||||
#endif
|
#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)
|
public static (int Major, int Minor, int Revision, int Build) GetVersionValueFromClient(string fileName)
|
||||||
{
|
{
|
||||||
var fileVersionInfo = FileVersionInfo.GetVersionInfo(fileName);
|
var fileVersionInfo = FileVersionInfo.GetVersionInfo(fileName);
|
||||||
@@ -25,43 +32,43 @@ static class Helpers
|
|||||||
fileVersionInfo.FileBuildPart, fileVersionInfo.FilePrivatePart);
|
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.ForegroundColor = ConsoleColor.Cyan;
|
||||||
|
Console.WriteLine("World of Warcraft");
|
||||||
Console.WriteLine(@"_____________World of Warcraft___________");
|
Console.ResetColor();
|
||||||
Console.WriteLine(@" _ _ ");
|
Console.Write("Support: ");
|
||||||
Console.WriteLine(@" /\ | | (_) ");
|
Console.ForegroundColor = ConsoleColor.Cyan;
|
||||||
Console.WriteLine(@" / \ _ __ ___| |_ _ _ _ _ __ ___ ");
|
Console.WriteLine("https://arctium.io");
|
||||||
Console.WriteLine(@" / /\ \ | '__/ __| __| | | | | '_ ` _ \ ");
|
Console.ResetColor();
|
||||||
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();
|
Console.WriteLine();
|
||||||
Console.WriteLine($"Operating System: {RuntimeInformation.OSDescription}");
|
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";
|
const string portalKey = "SET portal";
|
||||||
|
|
||||||
var portalIndex = config.IndexOf(portalKey, StringComparison.Ordinal);
|
var portalIndex = config.IndexOf(portalKey, StringComparison.OrdinalIgnoreCase);
|
||||||
|
|
||||||
if (portalIndex == -1)
|
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);
|
var startQuoteIndex = config.IndexOf('"', portalIndex);
|
||||||
|
|
||||||
@@ -78,8 +85,32 @@ static class Helpers
|
|||||||
var colonIndex = portalSpan.IndexOf(':');
|
var colonIndex = portalSpan.IndexOf(':');
|
||||||
var ipSpan = colonIndex != -1 ? portalSpan[..colonIndex] : portalSpan;
|
var ipSpan = colonIndex != -1 ? portalSpan[..colonIndex] : portalSpan;
|
||||||
var port = colonIndex != -1 ? int.Parse(portalSpan[(colonIndex + 1)..]) : 1119;
|
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();
|
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
|
try
|
||||||
{
|
{
|
||||||
if (IPAddress.TryParse(portalString, out var ipAddress))
|
if (IPAddress.TryParse(portalString, out var ipAddress))
|
||||||
@@ -88,22 +119,39 @@ static class Helpers
|
|||||||
var ipv4Address = Dns.GetHostAddresses(portalString).FirstOrDefault(a => a.AddressFamily == AddressFamily.InterNetwork);
|
var ipv4Address = Dns.GetHostAddresses(portalString).FirstOrDefault(a => a.AddressFamily == AddressFamily.InterNetwork);
|
||||||
|
|
||||||
if (ipv4Address == null)
|
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);
|
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)
|
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();
|
using var httpClient = new HttpClient();
|
||||||
|
|
||||||
httpClient.Timeout = TimeSpan.FromSeconds(5);
|
httpClient.Timeout = TimeSpan.FromSeconds(5);
|
||||||
|
|
||||||
try
|
try
|
||||||
@@ -111,15 +159,31 @@ static class Helpers
|
|||||||
var result = await httpClient.GetAsync(url);
|
var result = await httpClient.GetAsync(url);
|
||||||
|
|
||||||
if (!result.IsSuccessStatusCode)
|
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;
|
return result.IsSuccessStatusCode;
|
||||||
}
|
}
|
||||||
catch (Exception)
|
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;
|
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.
|
// Copyright (c) Arctium.
|
||||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
// 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
|
static class NativeWindows
|
||||||
{
|
{
|
||||||
/// kernel32.dll
|
|
||||||
// Process
|
|
||||||
[DllImport("kernel32.dll", EntryPoint = "CreateProcessA", SetLastError = true)]
|
[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);
|
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")]
|
[DllImport("kernel32.dll", EntryPoint = "CloseHandle")]
|
||||||
public static extern void CloseHandle(nint handle);
|
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)]
|
[DllImport("kernel32.dll", SetLastError = true)]
|
||||||
public static extern bool VirtualProtectEx(nint hProcess, nint lpAddress, uint dwSize, uint flNewProtect, out uint lpflOldProtect);
|
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)]
|
[DllImport("kernel32.dll", EntryPoint = "FlushInstructionCache", SetLastError = true)]
|
||||||
public static extern bool FlushInstructionCache(nint hProcess, nint lpBaseAddress, uint dwSize);
|
public static extern bool FlushInstructionCache(nint hProcess, nint lpBaseAddress, uint dwSize);
|
||||||
|
|
||||||
/// ntdll.dll
|
|
||||||
// Process
|
|
||||||
[DllImport("ntdll.dll", SetLastError = true)]
|
[DllImport("ntdll.dll", SetLastError = true)]
|
||||||
public static extern NtStatus NtQueryInformationProcess(nint hProcess, int pic, ref ProcessBasicInformation pbi, int cb, out int pSize);
|
public static extern int 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);
|
|
||||||
|
|
||||||
[DllImport("ntdll.dll", SetLastError = true)]
|
[DllImport("ntdll.dll", SetLastError = true)]
|
||||||
public static extern nint NtResumeProcess(nint processHandle);
|
public static extern nint NtResumeProcess(nint processHandle);
|
||||||
|
|
||||||
[DllImport("ntdll.dll", SetLastError = true)]
|
[DllImport("ntdll.dll", SetLastError = true)]
|
||||||
public static extern nint NtSuspendProcess(nint processHandle);
|
public static extern nint NtSuspendProcess(nint processHandle);
|
||||||
|
|
||||||
[DllImport("ntdll.dll", SetLastError = true)]
|
|
||||||
public static extern nint NtClose(nint handle);
|
|
||||||
}
|
}
|
||||||
|
|||||||
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.
|
// Copyright (c) Arctium.
|
||||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
// 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
|
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.
|
// Registry entry used for -launcherlogin.
|
||||||
public static byte[] LauncherLogin = Encoding.UTF8.GetBytes(@"Software\Custom Game Server Dev\Battle.net\Launch Options\");
|
public static byte[] LauncherLogin = Encoding.UTF8.GetBytes(@"Software\Custom Game Server Dev\Battle.net\Launch Options\");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,15 +1,19 @@
|
|||||||
// Copyright (c) Arctium.
|
// Copyright (c) Arctium.
|
||||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
// 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
|
static class Common
|
||||||
{
|
{
|
||||||
public static short[] ConnectToModulus = { 0x91, 0xD5, 0x9B, 0xB7, 0xD4, 0xE1, 0x83, 0xA5 };
|
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[] 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();
|
public static short[] Portal = ".actual.battle.net\0".ToPattern();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,34 +1,10 @@
|
|||||||
// Copyright (c) Arctium.
|
// Copyright (c) Arctium.
|
||||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
// 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
|
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.
|
// Registry entry used for -launcherlogin.
|
||||||
public static short[] LauncherLogin = @"Software\Blizzard Entertainment\Battle.net\Launch Options\".ToPattern();
|
public static short[] LauncherLogin = @"Software\Blizzard Entertainment\Battle.net\Launch Options\".ToPattern();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,71 +1,46 @@
|
|||||||
// Copyright (c) Arctium.
|
// Copyright (c) Arctium.
|
||||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
|
||||||
using System.CommandLine.Parsing;
|
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.
|
PublisherCheck();
|
||||||
if (!Process.GetCurrentProcess().ProcessName.Contains("arctium", StringComparison.InvariantCultureIgnoreCase))
|
PrintHeader();
|
||||||
WaitAndExit();
|
|
||||||
|
|
||||||
Console.ForegroundColor = ConsoleColor.Red;
|
var versionInfo = FileVersionInfo.GetVersionInfo(Environment.ProcessPath);
|
||||||
Console.WriteLine("THIS LAUNCHER IS DISCONTINUED!!! Stop asking for support.");
|
Console.WriteLine($"Using {(int)(Environment.ProcessorCount * 0.75)} of {Environment.ProcessorCount} processors.");
|
||||||
Console.ForegroundColor = ConsoleColor.Yellow;
|
|
||||||
Console.WriteLine("You can find the new supported launcher on https://arctium.io");
|
|
||||||
Console.WriteLine();
|
Console.WriteLine();
|
||||||
Console.ResetColor();
|
|
||||||
WaitAndExit(5000);
|
|
||||||
|
|
||||||
PrintHeader("WoW Client Launcher");
|
PublisherCheck();
|
||||||
|
|
||||||
LaunchOptions.RootCommand.SetHandler(async context =>
|
LaunchOptions.RootCommand.SetHandler(async context =>
|
||||||
{
|
{
|
||||||
CreateDevIPFilter(out var ipFilter);
|
try
|
||||||
|
{
|
||||||
|
// Prefer / instead of \ for the client path.
|
||||||
|
var appPath = (await Launcher.PrepareGameLaunch(context.ParseResult, default)).Replace("\\", "/");
|
||||||
|
var gameCommandLine = string.Join(" ", context.ParseResult.UnmatchedTokens);
|
||||||
|
|
||||||
// Prefer / instead of \ for the client path.
|
// Add config parameter to the game command line.
|
||||||
var appPath = (await Launcher.PrepareGameLaunch(context.ParseResult, ipFilter)).Replace("\\", "/");
|
gameCommandLine += $" -config {context.ParseResult.GetValueForOption(LaunchOptions.GameConfig)}";
|
||||||
var gameCommandLine = string.Join(" ", context.ParseResult.UnmatchedTokens);
|
|
||||||
|
|
||||||
// Add config parameter to the game command line.
|
if (string.IsNullOrEmpty(appPath) || !Launcher.LaunchGame(appPath, gameCommandLine, context.ParseResult))
|
||||||
gameCommandLine += $" -config {context.ParseResult.GetValueForOption(LaunchOptions.GameConfig)}";
|
WaitAndExit(5000);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
if (Launcher.GameProcessHandle != nint.Zero)
|
||||||
|
NativeWindows.TerminateProcess(Launcher.GameProcessHandle, 0);
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(appPath) || !Launcher.LaunchGame(appPath, gameCommandLine, context.ParseResult))
|
Console.ForegroundColor = ConsoleColor.Red;
|
||||||
WaitAndExit(5000);
|
Console.WriteLine(ex.Message);
|
||||||
|
Console.ResetColor();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
await LaunchOptions.Instance.InvokeAsync(args);
|
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)
|
static void WaitAndExit(int ms = 2000)
|
||||||
{
|
{
|
||||||
@@ -75,4 +50,4 @@ static void WaitAndExit(int ms = 2000)
|
|||||||
Thread.Sleep(ms);
|
Thread.Sleep(ms);
|
||||||
|
|
||||||
Environment.Exit(0);
|
Environment.Exit(0);
|
||||||
}
|
}
|
||||||
@@ -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.
|
// Copyright (c) Arctium.
|
||||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
// 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)]
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
struct MemoryBasicInformation
|
struct MemoryBasicInformation
|
||||||
@@ -10,9 +10,9 @@ struct MemoryBasicInformation
|
|||||||
public nint AllocationBase;
|
public nint AllocationBase;
|
||||||
public MemProtection AllocationProtect;
|
public MemProtection AllocationProtect;
|
||||||
public nint RegionSize;
|
public nint RegionSize;
|
||||||
public MemState State;
|
public int State;
|
||||||
public MemProtection Protect;
|
public MemProtection Protect;
|
||||||
public MemType Type;
|
public int Type;
|
||||||
|
|
||||||
public static int Size => Marshal.SizeOf<MemoryBasicInformation>();
|
public static int Size => Marshal.SizeOf<MemoryBasicInformation>();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
// Copyright (c) Arctium.
|
// Copyright (c) Arctium.
|
||||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
// 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)]
|
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||||
struct ProcessBasicInformation
|
struct ProcessBasicInformation
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
// Copyright (c) Arctium.
|
// Copyright (c) Arctium.
|
||||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
// 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
|
struct ProcessInformation
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
// Copyright (c) Arctium.
|
// Copyright (c) Arctium.
|
||||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
// 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
|
struct StartupInfo
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,14 +1,13 @@
|
|||||||
// Copyright (c) Arctium.
|
// Copyright (c) Arctium.
|
||||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
// 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.CommandLine;
|
||||||
global using System.Diagnostics;
|
global using System.Diagnostics;
|
||||||
global using System.Runtime.CompilerServices;
|
global using System.Runtime.CompilerServices;
|
||||||
global using System.Runtime.InteropServices;
|
global using System.Runtime.InteropServices;
|
||||||
global using System.Text;
|
global using System.Text;
|
||||||
|
|
||||||
global using Arctium.WoW.Launcher.Constants;
|
global using Arctium.Game.Launcher.Constants;
|
||||||
global using Arctium.WoW.Launcher.IO;
|
global using Arctium.Game.Launcher.IO;
|
||||||
global using Arctium.WoW.Launcher.Misc;
|
global using Arctium.Game.Launcher.Misc;
|
||||||
global using Arctium.WoW.Launcher.Structures;
|
global using Arctium.Game.Launcher.Structures;
|
||||||
|
|||||||
Reference in New Issue
Block a user