added persistent modes to cold client loader + refactored ini sections

This commit is contained in:
otavepto 2024-02-05 22:18:00 +02:00
parent 7c1f7dc4ab
commit 3482c6fae3
7 changed files with 394 additions and 158 deletions

View File

@ -156,7 +156,7 @@ jobs:
run: sudo chmod 77 package_linux.sh && sudo ./package_linux.sh release
### upload artifact/package to github Actions (release mode)
- name: Upload build pacakge (release)
- name: Upload build package (release)
uses: actions/upload-artifact@v4
with:
name: "build-linux-release-${{ github.sha }}"
@ -178,7 +178,7 @@ jobs:
run: sudo chmod 77 package_linux.sh && sudo ./package_linux.sh debug
### upload artifact/package to github Actions (debug mode)
- name: Upload build pacakge (debug)
- name: Upload build package (debug)
uses: actions/upload-artifact@v4
with:
name: "build-linux-debug-${{ github.sha }}"

View File

@ -157,7 +157,7 @@ jobs:
run: package_win.bat release
### upload artifact/package to github Actions (release mode)
- name: Upload build pacakge (release)
- name: Upload build package (release)
uses: actions/upload-artifact@v4
with:
name: "build-win-release-${{ github.sha }}"
@ -179,7 +179,7 @@ jobs:
run: package_win.bat debug
### upload artifact/package to github Actions (debug mode)
- name: Upload build pacakge (debug)
- name: Upload build package (debug)
uses: actions/upload-artifact@v4
with:
name: "build-win-debug-${{ github.sha }}"

View File

@ -1,3 +1,13 @@
## 2024/2/7
* new persistent modes for cold client loader, mode 2 is a more accurate simulation and allows launching apps from their .exe
---
* **[Breaking]** changed the ini sections of the cold client loader
---
## 2024/1/26
* **[Detanup01]** added a new command line option for the tool `generate_emu_config` to disable the generation of `disable_xxx.txt` files,

View File

@ -107,7 +107,7 @@ This will:
Additional arguments you can pass to this script:
* `-j <n>`: build with `<n>` parallel jobs, by default 70% of the available threads
* `-verbose`: output compiler/linker commands used by `CMAKE`
* `-packages_only`: install the required Linux pacakges via `apt isntall` and exit (don't rebuild)
* `-packages_only`: install the required Linux packages via `apt isntall` and exit (don't rebuild)
---

View File

@ -34,6 +34,12 @@ You do not need to create a `steam_interfaces.txt` file for the `steamclient` ve
* `IgnoreLoaderArchDifference`: don't display an error message if the architecture of the loader is different from the app.
this will result in a silent failure if a dll injection didn't succeed.
both the loader and the app must have the same arch for the injection to work
* `Mode` (in `[Persistence]` section):
- 0 = turned off
- 1 = loader will spawn the exe and keep hanging in the background until you press "OK"
- 2 = loader will NOT spawn exe, it will just setup the required environemnt and keep hanging
in the background until you run the exe manually, and press "OK" when you've finished playing.
This should help in scenarios where an external app has to launch the game, it is also recommended to run the loader with admin rights in this mode
**Note** that any arguments passed to `steamclient_loader.exe` via command line will be passed to the target `.exe`.

View File

@ -13,11 +13,29 @@
#include <stdio.h>
#include <string>
#include <set>
#include <thread>
static const std::wstring IniFile = pe_helpers::get_current_exe_path_w() + L"ColdClientLoader.ini";
static const std::wstring dbg_file = pe_helpers::get_current_exe_path_w() + L"COLD_LDR_LOG.txt";
constexpr static const char STEAM_UNIVERSE[] = "Public";
constexpr static const char STEAM_URL_PROTOCOL[] = "URL:steam protocol";
const bool loader_is_32 = pe_helpers::is_module_32(GetModuleHandleW(NULL));
const std::wstring hklm_path(loader_is_32 ? L"SOFTWARE\\Valve\\Steam" : L"SOFTWARE\\WOW6432Node\\Valve\\Steam");
// Declare some variables to be used for Steam registry.
const DWORD UserId = 0x03100004771F810D & 0xffffffff;
const DWORD ProcessID = GetCurrentProcessId();
std::wstring ClientPath{};
std::wstring Client64Path{};
std::vector<std::wstring> dlls_to_inject{};
std::wstring get_ini_value(LPCWSTR section, LPCWSTR key, LPCWSTR default_val = NULL)
{
@ -144,6 +162,175 @@ static void to_bool_ini_val(std::wstring &val)
}
}
bool orig_steam_hkcu = false;
WCHAR OrgSteamCDir_hkcu[8192] = { 0 };
DWORD Size1_hkcu = _countof(OrgSteamCDir_hkcu);
WCHAR OrgSteamCDir64_hkcu[8192] = { 0 };
DWORD Size2_hkcu = _countof(OrgSteamCDir64_hkcu);
bool patch_registry_hkcu()
{
HKEY Registrykey = { 0 };
if (RegOpenKeyExW(HKEY_CURRENT_USER, L"Software\\Valve\\Steam\\ActiveProcess", 0, KEY_ALL_ACCESS, &Registrykey) == ERROR_SUCCESS) {
orig_steam_hkcu = true;
// Get original values to restore later.
DWORD keyType = REG_SZ;
RegQueryValueExW(Registrykey, L"SteamClientDll", 0, &keyType, (LPBYTE)&OrgSteamCDir_hkcu, &Size1_hkcu);
RegQueryValueExW(Registrykey, L"SteamClientDll64", 0, &keyType, (LPBYTE)&OrgSteamCDir64_hkcu, &Size2_hkcu);
dbg_log::write("Found previous registry entry (HKCU) for Steam");
} else if (RegCreateKeyExW(HKEY_CURRENT_USER, L"Software\\Valve\\Steam\\ActiveProcess", 0, 0, REG_OPTION_NON_VOLATILE,
KEY_ALL_ACCESS, NULL, &Registrykey, NULL) == ERROR_SUCCESS) {
dbg_log::write("Created new registry entry (HKCU) for Steam");
} else {
dbg_log::write("Unable to patch Registry (HKCU), error = " + std::to_string(GetLastError()));
return false;
}
RegSetValueExA(Registrykey, "ActiveUser", NULL, REG_DWORD, (const BYTE *)&UserId, sizeof(DWORD));
RegSetValueExA(Registrykey, "pid", NULL, REG_DWORD, (const BYTE *)&ProcessID, sizeof(DWORD));
RegSetValueExW(Registrykey, L"SteamClientDll", NULL, REG_SZ, (const BYTE *)ClientPath.c_str(), (ClientPath.size() + 1) * sizeof(ClientPath[0]));
RegSetValueExW(Registrykey, L"SteamClientDll64", NULL, REG_SZ, (const BYTE *)Client64Path.c_str(), (Client64Path.size() + 1) * sizeof(Client64Path[0]));
RegSetValueExA(Registrykey, "Universe", NULL, REG_SZ, (const BYTE *)STEAM_UNIVERSE, (DWORD)sizeof(STEAM_UNIVERSE));
RegCloseKey(Registrykey);
return true;
}
void cleanup_registry_hkcu()
{
if (!orig_steam_hkcu) return;
dbg_log::write("restoring registry entries (HKCU)");
HKEY Registrykey = { 0 };
if (RegOpenKeyExW(HKEY_CURRENT_USER, L"Software\\Valve\\Steam\\ActiveProcess", 0, KEY_ALL_ACCESS, &Registrykey) == ERROR_SUCCESS) {
// Restore the values.
RegSetValueExW(Registrykey, L"SteamClientDll", NULL, REG_SZ, (LPBYTE)OrgSteamCDir_hkcu, Size1_hkcu);
RegSetValueExW(Registrykey, L"SteamClientDll64", NULL, REG_SZ, (LPBYTE)OrgSteamCDir64_hkcu, Size2_hkcu);
// Close the HKEY Handle.
RegCloseKey(Registrykey);
} else {
dbg_log::write("Unable to restore the original registry entry (HKCU), error = " + std::to_string(GetLastError()));
}
}
bool orig_steam_hklm = false;
WCHAR OrgInstallPath_hklm[8192] = { 0 };
DWORD Size1_hklm = _countof(OrgInstallPath_hklm);
bool patch_registry_hklm()
{
HKEY Registrykey = { 0 };
if (RegOpenKeyExW(HKEY_LOCAL_MACHINE, hklm_path.c_str(), 0, KEY_ALL_ACCESS, &Registrykey) == ERROR_SUCCESS) {
orig_steam_hklm = true;
// Get original values to restore later.
DWORD keyType = REG_SZ;
RegQueryValueExW(Registrykey, L"InstallPath", 0, &keyType, (LPBYTE)&OrgInstallPath_hklm, &Size1_hklm);
dbg_log::write("Found previous registry entry (HKLM) for Steam");
} else if (RegCreateKeyExW(HKEY_LOCAL_MACHINE, hklm_path.c_str(), 0, 0, REG_OPTION_NON_VOLATILE,
KEY_ALL_ACCESS, NULL, &Registrykey, NULL) == ERROR_SUCCESS) {
dbg_log::write("Created new registry entry (HKLM) for Steam");
} else {
dbg_log::write("Unable to patch Registry (HKLM), error = " + std::to_string(GetLastError()));
return false;
}
const auto my_path = pe_helpers::get_current_exe_path_w();
RegSetValueExW(Registrykey, L"InstallPath", NULL, REG_SZ, (const BYTE *)my_path.c_str(), (my_path.size() + 1) * sizeof(my_path[0]));
RegSetValueExA(Registrykey, "SteamPID", NULL, REG_DWORD, (const BYTE *)&ProcessID, sizeof(DWORD));
RegSetValueExA(Registrykey, "Universe", NULL, REG_SZ, (const BYTE *)STEAM_UNIVERSE, (DWORD)sizeof(STEAM_UNIVERSE));
RegCloseKey(Registrykey);
return true;
}
void cleanup_registry_hklm()
{
if (!orig_steam_hklm) return;
dbg_log::write("restoring registry entries (HKLM)");
HKEY Registrykey = { 0 };
if (RegOpenKeyExW(HKEY_LOCAL_MACHINE, hklm_path.c_str(), 0, KEY_ALL_ACCESS, &Registrykey) == ERROR_SUCCESS) {
RegSetValueExW(Registrykey, L"InstallPath", NULL, REG_SZ, (const BYTE *)OrgInstallPath_hklm, Size1_hklm);
RegCloseKey(Registrykey);
} else {
dbg_log::write("Unable to restore the original registry entry (HKLM), error = " + std::to_string(GetLastError()));
}
}
bool orig_steam_hkcs_1 = false;
bool orig_steam_hkcs_2 = false;
WCHAR OrgCommand_hkcs[8192] = { 0 };
DWORD Size1_hkcs = _countof(OrgCommand_hkcs);
bool patch_registry_hkcs()
{
HKEY Registrykey = { 0 };
if (RegOpenKeyExW(HKEY_CLASSES_ROOT, L"steam", 0, KEY_ALL_ACCESS, &Registrykey) == ERROR_SUCCESS) {
orig_steam_hkcs_1 = true;
dbg_log::write("Found previous registry entry (HKCS) #1 for Steam");
} else if (RegCreateKeyExW(HKEY_CLASSES_ROOT, L"steam", 0, 0, REG_OPTION_NON_VOLATILE,
KEY_ALL_ACCESS, NULL, &Registrykey, NULL) == ERROR_SUCCESS) {
dbg_log::write("Created new registry entry (HKCS) #1 for Steam");
} else {
dbg_log::write("Unable to patch Registry (HKCS) #1, error = " + std::to_string(GetLastError()));
return false;
}
HKEY Registrykey_2 = { 0 };
if (RegOpenKeyExW(HKEY_CLASSES_ROOT, L"steam\\Shell\\Open\\Command", 0, KEY_ALL_ACCESS, &Registrykey_2) == ERROR_SUCCESS) {
orig_steam_hkcs_2 = true;
// Get original values to restore later.
DWORD keyType = REG_SZ;
RegQueryValueExW(Registrykey_2, L"", 0, &keyType, (LPBYTE)&OrgCommand_hkcs, &Size1_hkcs);
dbg_log::write("Found previous registry entry (HKCS) #2 for Steam");
} else if (RegCreateKeyExW(HKEY_CLASSES_ROOT, L"steam\\Shell\\Open\\Command", 0, 0, REG_OPTION_NON_VOLATILE,
KEY_ALL_ACCESS, NULL, &Registrykey_2, NULL) == ERROR_SUCCESS) {
dbg_log::write("Created new registry entry (HKCS) #2 for Steam");
} else {
dbg_log::write("Unable to patch Registry (HKCS) #2, error = " + std::to_string(GetLastError()));
RegCloseKey(Registrykey);
return false;
}
RegSetValueExA(Registrykey, "", NULL, REG_SZ, (const BYTE *)STEAM_URL_PROTOCOL, (DWORD)sizeof(STEAM_URL_PROTOCOL));
RegSetValueExA(Registrykey, "URL Protocol", NULL, REG_SZ, (const BYTE *)"", (DWORD)sizeof(""));
RegCloseKey(Registrykey);
const auto cmd = pe_helpers::get_current_exe_path_w() + L"steam.exe -- \"%1\"";
RegSetValueExW(Registrykey_2, L"", NULL, REG_SZ, (const BYTE *)cmd.c_str(), (cmd.size() + 1) * sizeof(cmd[0]));
RegCloseKey(Registrykey_2);
return true;
}
void cleanup_registry_hkcs()
{
if (orig_steam_hkcs_2) {
dbg_log::write("restoring registry entries (HKCU) #1");
HKEY Registrykey = { 0 };
if (RegOpenKeyExW(HKEY_CLASSES_ROOT, L"steam\\Shell\\Open\\Command", 0, KEY_ALL_ACCESS, &Registrykey) == ERROR_SUCCESS) {
RegSetValueExW(Registrykey, L"", NULL, REG_SZ, (const BYTE *)OrgCommand_hkcs, Size1_hkcs);
RegCloseKey(Registrykey);
} else {
dbg_log::write("Unable to restore the original registry entry (HKCS) #2, error = " + std::to_string(GetLastError()));
}
}
if (!orig_steam_hkcs_1) {
dbg_log::write("removing registry entries (HKCU) #2 (added by loader)");
HKEY Registrykey = { 0 };
RegDeleteKeyA(HKEY_CLASSES_ROOT, "steam");
}
}
void set_steam_env_vars(const std::wstring &AppId)
{
SetEnvironmentVariableW(L"SteamAppId", AppId.c_str());
SetEnvironmentVariableW(L"SteamGameId", AppId.c_str());
SetEnvironmentVariableW(L"SteamOverlayGameId", AppId.c_str());
}
int APIENTRY wWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPWSTR lpCmdLine, _In_ int nCmdShow)
{
dbg_log::init(dbg_file.c_str());
@ -155,13 +342,12 @@ int APIENTRY wWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance
return 1;
}
std::wstring Client64Path = common_helpers::to_absolute(
get_ini_value(L"SteamClient", L"SteamClient64Dll"),
ClientPath = common_helpers::to_absolute(
get_ini_value(L"SteamClient", L"SteamClientDll"),
pe_helpers::get_current_exe_path_w()
);
std::wstring ClientPath = common_helpers::to_absolute(
get_ini_value(L"SteamClient", L"SteamClientDll"),
Client64Path = common_helpers::to_absolute(
get_ini_value(L"SteamClient", L"SteamClient64Dll"),
pe_helpers::get_current_exe_path_w()
);
std::wstring ExeFile = common_helpers::to_absolute(
@ -174,22 +360,26 @@ int APIENTRY wWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance
);
std::wstring ExeCommandLine = get_ini_value(L"SteamClient", L"ExeCommandLine");
std::wstring AppId = get_ini_value(L"SteamClient", L"AppId");
std::wstring ForceInjectSteamClient = get_ini_value(L"SteamClient", L"ForceInjectSteamClient");
std::wstring ResumeByDebugger = get_ini_value(L"Debug", L"ResumeByDebugger");
// dlls to inject
std::wstring ForceInjectSteamClient = get_ini_value(L"Injection", L"ForceInjectSteamClient");
std::wstring DllsToInjectFolder = common_helpers::to_absolute(
get_ini_value(L"Extra", L"DllsToInjectFolder"),
get_ini_value(L"Injection", L"DllsToInjectFolder"),
pe_helpers::get_current_exe_path_w()
);
std::wstring IgnoreInjectionError = get_ini_value(L"Extra", L"IgnoreInjectionError", L"1");
std::wstring IgnoreLoaderArchDifference = get_ini_value(L"Extra", L"IgnoreLoaderArchDifference", L"0");
std::wstring IgnoreInjectionError = get_ini_value(L"Injection", L"IgnoreInjectionError", L"1");
std::wstring IgnoreLoaderArchDifference = get_ini_value(L"Injection", L"IgnoreLoaderArchDifference", L"0");
std::wstring PersistentMode = get_ini_value(L"Persistence", L"Mode", L"0");
// debug
std::wstring ResumeByDebugger = get_ini_value(L"Debug", L"ResumeByDebugger");
to_bool_ini_val(ResumeByDebugger);
to_bool_ini_val(ForceInjectSteamClient);
to_bool_ini_val(IgnoreInjectionError);
to_bool_ini_val(IgnoreLoaderArchDifference);
to_bool_ini_val(ResumeByDebugger);
if (PersistentMode != L"0" && PersistentMode != L"1" && PersistentMode != L"2") PersistentMode = L"0";
// log everything
dbg_log::write(L"SteamClient::Exe: " + ExeFile);
@ -198,19 +388,24 @@ int APIENTRY wWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance
dbg_log::write(L"SteamClient::AppId: " + AppId);
dbg_log::write(L"SteamClient::SteamClient: " + ClientPath);
dbg_log::write(L"SteamClient::SteamClient64Dll: " + Client64Path);
dbg_log::write(L"SteamClient::PersistentMode: " + PersistentMode);
dbg_log::write(L"SteamClient::ForceInjectSteamClient: " + ForceInjectSteamClient);
dbg_log::write(L"Injection::DllsToInjectFolder: " + DllsToInjectFolder);
dbg_log::write(L"Injection::IgnoreInjectionError: " + IgnoreInjectionError);
dbg_log::write(L"Injection::IgnoreLoaderArchDifference: " + IgnoreLoaderArchDifference);
dbg_log::write(L"Debug::ResumeByDebugger: " + ResumeByDebugger);
dbg_log::write(L"Extra::DllsToInjectFolder: " + DllsToInjectFolder);
dbg_log::write(L"Extra::IgnoreInjectionError: " + IgnoreInjectionError);
dbg_log::write(L"Extra::IgnoreLoaderArchDifference: " + IgnoreLoaderArchDifference);
if (AppId.size() && AppId[0]) {
SetEnvironmentVariableW(L"SteamAppId", AppId.c_str());
SetEnvironmentVariableW(L"SteamGameId", AppId.c_str());
SetEnvironmentVariableW(L"SteamOverlayGameId", AppId.c_str());
} else {
dbg_log::write("You forgot to set the AppId");
MessageBoxA(NULL, "You forgot to set the AppId.", "ColdClientLoader", MB_ICONERROR);
if (!common_helpers::file_exist(Client64Path)) {
dbg_log::write("Couldn't find the requested SteamClient64Dll");
MessageBoxA(NULL, "Couldn't find the requested SteamClient64Dll.", "ColdClientLoader", MB_ICONERROR);
dbg_log::close();
return 1;
}
if (!common_helpers::file_exist(ClientPath)) {
dbg_log::write("Couldn't find the requested SteamClientDll");
MessageBoxA(NULL, "Couldn't find the requested SteamClientDll.", "ColdClientLoader", MB_ICONERROR);
dbg_log::close();
return 1;
}
@ -232,26 +427,15 @@ int APIENTRY wWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance
return 1;
}
if (!common_helpers::file_exist(Client64Path)) {
dbg_log::write("Couldn't find the requested SteamClient64Dll");
MessageBoxA(NULL, "Couldn't find the requested SteamClient64Dll.", "ColdClientLoader", MB_ICONERROR);
dbg_log::close();
return 1;
}
if (!common_helpers::file_exist(ClientPath)) {
dbg_log::write("Couldn't find the requested SteamClientDll");
MessageBoxA(NULL, "Couldn't find the requested SteamClientDll.", "ColdClientLoader", MB_ICONERROR);
dbg_log::close();
return 1;
}
bool isDllsToInjectFolderFound = false;
if (DllsToInjectFolder.size()) {
if (!common_helpers::dir_exist(DllsToInjectFolder)) {
isDllsToInjectFolderFound = common_helpers::dir_exist(DllsToInjectFolder);
if (!isDllsToInjectFolderFound) {
dbg_log::write("Couldn't find the requested folder of dlls to inject");
MessageBoxA(NULL, "Couldn't find the requested folder of dlls to inject.", "ColdClientLoader", MB_ICONERROR);
dbg_log::close();
return 1;
// it was requested to make this a non intrusive error, don't shutdown the whole thing because the folder is missing
// dbg_log::close();
// return 1;
}
}
@ -277,8 +461,7 @@ int APIENTRY wWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance
dbg_log::write("Detected exe arch: x64");
}
bool loader_is_32 = pe_helpers::is_module_32(GetModuleHandleW(NULL));
if (pe_helpers::is_module_32(GetModuleHandleW(NULL))) {
if (loader_is_32) {
dbg_log::write("Detected loader arch: x32");
} else {
dbg_log::write("Detected loader arch: x64");
@ -291,8 +474,7 @@ int APIENTRY wWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance
}
}
std::vector<std::wstring> dlls_to_inject{};
if (DllsToInjectFolder.size()) {
if (isDllsToInjectFolderFound) {
std::wstring failed_dlls{};
dlls_to_inject = collect_dlls_to_inject(DllsToInjectFolder, is_exe_32, failed_dlls);
if (failed_dlls.size() && IgnoreInjectionError.empty()) {
@ -307,63 +489,6 @@ int APIENTRY wWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance
}
}
}
HKEY Registrykey = { 0 };
// Declare some variables to be used for Steam registry.
DWORD UserId = 0x03100004771F810D & 0xffffffff;
DWORD ProcessID = GetCurrentProcessId();
bool orig_steam = false;
DWORD keyType = REG_SZ;
WCHAR OrgSteamCDir[8192] = { 0 };
WCHAR OrgSteamCDir64[8192] = { 0 };
DWORD Size1 = _countof(OrgSteamCDir);
DWORD Size2 = _countof(OrgSteamCDir64);
if (RegOpenKeyExW(HKEY_CURRENT_USER, L"Software\\Valve\\Steam\\ActiveProcess", 0, KEY_ALL_ACCESS, &Registrykey) == ERROR_SUCCESS)
{
orig_steam = true;
// Get original values to restore later.
RegQueryValueExW(Registrykey, L"SteamClientDll", 0, &keyType, (LPBYTE)& OrgSteamCDir, &Size1);
RegQueryValueExW(Registrykey, L"SteamClientDll64", 0, &keyType, (LPBYTE)& OrgSteamCDir64, &Size2);
} else {
if (RegCreateKeyExW(HKEY_CURRENT_USER, L"Software\\Valve\\Steam\\ActiveProcess", 0, 0, REG_OPTION_NON_VOLATILE,
KEY_ALL_ACCESS, NULL, &Registrykey, NULL) != ERROR_SUCCESS)
{
dbg_log::write("Unable to patch Steam process informations on the Windows registry (ActiveProcess), error = " + std::to_string(GetLastError()));
MessageBoxA(NULL, "Unable to patch Steam process informations on the Windows registry.", "ColdClientLoader", MB_ICONERROR);
dbg_log::close();
return 1;
}
}
// Set values to Windows registry.
RegSetValueExA(Registrykey, "ActiveUser", NULL, REG_DWORD, (const BYTE *)&UserId, sizeof(DWORD));
RegSetValueExA(Registrykey, "pid", NULL, REG_DWORD, (const BYTE *)&ProcessID, sizeof(DWORD));
RegSetValueExW(Registrykey, L"SteamClientDll", NULL, REG_SZ, (const BYTE *)ClientPath.c_str(), (ClientPath.size() + 1) * sizeof(ClientPath[0]));
RegSetValueExW(Registrykey, L"SteamClientDll64", NULL, REG_SZ, (const BYTE *)Client64Path.c_str(), (Client64Path.size() + 1) * sizeof(Client64Path[0]));
RegSetValueExA(Registrykey, "Universe", NULL, REG_SZ, (const BYTE *)STEAM_UNIVERSE, (DWORD)sizeof(STEAM_UNIVERSE));
// Close the HKEY Handle.
RegCloseKey(Registrykey);
// spawn the exe
STARTUPINFOW info = { 0 };
SecureZeroMemory(&info, sizeof(info));
info.cb = sizeof(info);
PROCESS_INFORMATION processInfo = { 0 };
SecureZeroMemory(&processInfo, sizeof(processInfo));
WCHAR CommandLine[16384] = { 0 };
_snwprintf(CommandLine, _countof(CommandLine), L"\"%ls\" %ls %ls", ExeFile.c_str(), ExeCommandLine.c_str(), lpCmdLine);
if (!CreateProcessW(ExeFile.c_str(), CommandLine, NULL, NULL, TRUE, CREATE_SUSPENDED, NULL, ExeRunDir.c_str(), &info, &processInfo))
{
dbg_log::write("Unable to load the requested EXE file");
MessageBoxA(NULL, "Unable to load the requested EXE file.", "ColdClientLoader", MB_ICONERROR);
dbg_log::close();
return 1;
}
if (ForceInjectSteamClient.size()) {
if (is_exe_32) {
dlls_to_inject.insert(dlls_to_inject.begin(), ClientPath);
@ -371,12 +496,92 @@ int APIENTRY wWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance
dlls_to_inject.insert(dlls_to_inject.begin(), Client64Path);
}
}
if (PersistentMode != L"2" ) {
if (AppId.size() && AppId[0]) {
set_steam_env_vars(AppId);
} else {
dbg_log::write("You forgot to set the AppId");
MessageBoxA(NULL, "You forgot to set the AppId.", "ColdClientLoader", MB_ICONERROR);
return 1;
}
} else { // steam://run/
constexpr const static wchar_t STEAM_LAUNCH_CMD_1[] = L"steam://run/";
constexpr const static wchar_t STEAM_LAUNCH_CMD_2[] = L"run/";
AppId.clear(); // we don't care about the app id in the ini
std::wstring my_cmd = lpCmdLine && lpCmdLine[0] ? std::wstring(lpCmdLine) : std::wstring();
dbg_log::write(L"persistent mode 2 detecting steam launch cmd from: " + my_cmd);
if (my_cmd.find(STEAM_LAUNCH_CMD_1) == 0) {
AppId = my_cmd.substr(sizeof(STEAM_LAUNCH_CMD_1) / sizeof(wchar_t), my_cmd.find_first_of(L" \t"));
dbg_log::write("persistent mode 2 got steam launch cmd #1");
} else if (my_cmd.find(STEAM_LAUNCH_CMD_2) == 0) {
AppId = my_cmd.substr(sizeof(STEAM_LAUNCH_CMD_2) / sizeof(wchar_t), my_cmd.find_first_of(L" \t"));
dbg_log::write("persistent mode 2 got steam launch cmd #2");
} else {
dbg_log::write("persistent mode 2 didn't detect a valid steam launch cmd");
}
if (AppId.size()) {
set_steam_env_vars(AppId);
dbg_log::write(L"persistent mode 2 will use app id = " + AppId);
} else {
dbg_log::write("persistent mode 2 didn't find app id");
}
}
if (!patch_registry_hkcu()) {
dbg_log::close();
MessageBoxA(NULL, "Unable to patch Registry (HKCU).", "ColdClientLoader", MB_ICONERROR);
return 1;
}
if (!patch_registry_hklm()) {
cleanup_registry_hkcu();
dbg_log::close();
MessageBoxA(NULL, "Unable to patch Registry (HKLM).", "ColdClientLoader", MB_ICONERROR);
return 1;
}
// if (!patch_registry_hkcs()) { // this fails due to admin rights, not a big deal
// cleanup_registry_hkcu();
// cleanup_registry_hklm();
// dbg_log::close();
// MessageBoxA(NULL, "Unable to patch Registry (HKCS).", "ColdClientLoader", MB_ICONERROR);
// return 1;
// }
if (PersistentMode != L"2" || AppId.size()) { // persistent mode 0 or 1, or mode 2 with defined app id
STARTUPINFOW info = { 0 };
SecureZeroMemory(&info, sizeof(info));
info.cb = sizeof(info);
PROCESS_INFORMATION processInfo = { 0 };
SecureZeroMemory(&processInfo, sizeof(processInfo));
dbg_log::write("spawning the requested EXE file");
WCHAR CommandLine[16384] = { 0 };
_snwprintf(CommandLine, _countof(CommandLine), L"\"%ls\" %ls %ls", ExeFile.c_str(), ExeCommandLine.c_str(), lpCmdLine);
if (!CreateProcessW(ExeFile.c_str(), CommandLine, NULL, NULL, TRUE, CREATE_SUSPENDED, NULL, ExeRunDir.c_str(), &info, &processInfo)) {
dbg_log::write("Unable to load the requested EXE file");
cleanup_registry_hkcu();
cleanup_registry_hklm();
cleanup_registry_hkcs();
dbg_log::close();
MessageBoxA(NULL, "Unable to load the requested EXE file.", "ColdClientLoader", MB_ICONERROR);
return 1;
}
dbg_log::write("injecting " + std::to_string(dlls_to_inject.size()) + " dlls");
for (const auto &dll : dlls_to_inject) {
dbg_log::write(L"Injecting dll: '" + dll + L"' ...");
const char *err_inject = nullptr;
DWORD code = pe_helpers::loadlib_remote(processInfo.hProcess, dll, &err_inject);
if (code != ERROR_SUCCESS) {
std::wstring err_full =
auto err_full =
L"Failed to inject the dll: " + dll + L"\n" +
common_helpers::str_to_w(err_inject) + L"\n" +
common_helpers::str_to_w(pe_helpers::get_err_string(code)) + L"\n" +
@ -386,8 +591,13 @@ int APIENTRY wWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance
TerminateProcess(processInfo.hProcess, 1);
CloseHandle(processInfo.hProcess);
CloseHandle(processInfo.hThread);
MessageBoxW(NULL, err_full.c_str(), L"ColdClientLoader", MB_ICONERROR);
cleanup_registry_hkcu();
cleanup_registry_hklm();
cleanup_registry_hkcs();
dbg_log::close();
MessageBoxW(NULL, err_full.c_str(), L"ColdClientLoader", MB_ICONERROR);
return 1;
}
} else {
@ -397,6 +607,10 @@ int APIENTRY wWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance
// run
if (ResumeByDebugger.empty()) {
dbg_log::write("resuming the main thread of the exe");
// MessageBoxA(nullptr, "Wait then press ok", "Wait some seconds", MB_OK);
// games like house flipper 2 won't launch until the previous exe has exited, which takes some seconds
if (PersistentMode == L"2") std::this_thread::sleep_for(std::chrono::seconds(5));
ResumeThread(processInfo.hThread);
} else {
std::string msg = "Attach a debugger now to PID " + std::to_string(processInfo.dwProcessId) + " and resume its main thread";
@ -409,19 +623,16 @@ int APIENTRY wWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance
CloseHandle(processInfo.hProcess);
CloseHandle(processInfo.hThread);
if (orig_steam) {
if (RegOpenKeyExW(HKEY_CURRENT_USER, L"Software\\Valve\\Steam\\ActiveProcess", 0, KEY_ALL_ACCESS, &Registrykey) == ERROR_SUCCESS)
{
// Restore the values.
RegSetValueExW(Registrykey, L"SteamClientDll", NULL, REG_SZ, (LPBYTE)OrgSteamCDir, Size1);
RegSetValueExW(Registrykey, L"SteamClientDll64", NULL, REG_SZ, (LPBYTE)OrgSteamCDir64, Size2);
// Close the HKEY Handle.
RegCloseKey(Registrykey);
} else {
dbg_log::write("Unable to restore the original Steam process informations in the Windows registry, error = " + std::to_string(GetLastError()));
}
if (PersistentMode == L"1" || (PersistentMode == L"2" && AppId.empty()) ) {
MessageBoxA(NULL, "Press OK when you have finished playing to close the loader", "Cold Client Loader (waiting)", MB_OK);
}
if (PersistentMode != L"2" || AppId.empty()) { // persistent mode 0 or 1, or mode 2 without app id
cleanup_registry_hkcu();
cleanup_registry_hklm();
cleanup_registry_hkcs();
}
dbg_log::close();

View File

@ -6,7 +6,7 @@ Exe=game.exe
ExeRunDir=
# any additional args to pass, ex: -dx11, also any args passed to the loader will be passed to the app
ExeCommandLine=
# IMPORTANT
# IMPORTANT, unless [Persistence] Mode=2
AppId=
# path to the steamclient dlls, both must be set,
@ -14,14 +14,10 @@ AppId=
SteamClientDll=steamclient.dll
SteamClient64Dll=steamclient64.dll
[Injection]
# force inject steamclient dll instead of waiting for the app to load it
ForceInjectSteamClient=0
[Debug]
# don't call `ResumeThread()` on the main thread after spawning the .exe
ResumeByDebugger=0
[Extra]
# path to a folder containing some dlls to inject into the app upon start
# this folder will be traversed recursively
# additionally, inside this folder you can create a file called `load_order.txt` and
@ -30,9 +26,22 @@ ResumeByDebugger=0
# example:
#DllsToInjectFolder=extra_dlls
DllsToInjectFolder=
# don't display an error message when a dll injection fails
IgnoreInjectionError=1
# don't display an error message if the architecture of the loader is different from the app
# this will result in a silent failure if a dll injection didn't succeed
# both the loader and the app must have the same arch for the injection to work
IgnoreLoaderArchDifference=0
[Persistence]
# 0 = turned off
# 1 = loader will spawn the exe and keep hanging in the background until you press "OK"
# 2 = loader will NOT spawn exe, it will just setup the required environemnt and keep hanging in the background
# you have to run the Exe manually, and finally press "OK" when you've finished playing
# it is advised to run the loader as admin in this mode
Mode=0
[Debug]
# don't call `ResumeThread()` on the main thread after spawning the .exe
ResumeByDebugger=0