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

View File

@ -157,7 +157,7 @@ jobs:
run: package_win.bat release run: package_win.bat release
### upload artifact/package to github Actions (release mode) ### upload artifact/package to github Actions (release mode)
- name: Upload build pacakge (release) - name: Upload build package (release)
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
with: with:
name: "build-win-release-${{ github.sha }}" name: "build-win-release-${{ github.sha }}"
@ -179,7 +179,7 @@ jobs:
run: package_win.bat debug run: package_win.bat debug
### upload artifact/package to github Actions (debug mode) ### upload artifact/package to github Actions (debug mode)
- name: Upload build pacakge (debug) - name: Upload build package (debug)
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
with: with:
name: "build-win-debug-${{ github.sha }}" 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 ## 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, * **[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: Additional arguments you can pass to this script:
* `-j <n>`: build with `<n>` parallel jobs, by default 70% of the available threads * `-j <n>`: build with `<n>` parallel jobs, by default 70% of the available threads
* `-verbose`: output compiler/linker commands used by `CMAKE` * `-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. * `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. 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 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`. **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 <stdio.h>
#include <string> #include <string>
#include <set> #include <set>
#include <thread>
static const std::wstring IniFile = pe_helpers::get_current_exe_path_w() + L"ColdClientLoader.ini"; 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"; 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_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) 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) int APIENTRY wWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPWSTR lpCmdLine, _In_ int nCmdShow)
{ {
dbg_log::init(dbg_file.c_str()); dbg_log::init(dbg_file.c_str());
@ -155,13 +342,12 @@ int APIENTRY wWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance
return 1; return 1;
} }
std::wstring Client64Path = common_helpers::to_absolute( ClientPath = common_helpers::to_absolute(
get_ini_value(L"SteamClient", L"SteamClient64Dll"), get_ini_value(L"SteamClient", L"SteamClientDll"),
pe_helpers::get_current_exe_path_w() pe_helpers::get_current_exe_path_w()
); );
Client64Path = common_helpers::to_absolute(
std::wstring ClientPath = common_helpers::to_absolute( get_ini_value(L"SteamClient", L"SteamClient64Dll"),
get_ini_value(L"SteamClient", L"SteamClientDll"),
pe_helpers::get_current_exe_path_w() pe_helpers::get_current_exe_path_w()
); );
std::wstring ExeFile = common_helpers::to_absolute( 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 ExeCommandLine = get_ini_value(L"SteamClient", L"ExeCommandLine");
std::wstring AppId = get_ini_value(L"SteamClient", L"AppId"); 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 // dlls to inject
std::wstring ForceInjectSteamClient = get_ini_value(L"Injection", L"ForceInjectSteamClient");
std::wstring DllsToInjectFolder = common_helpers::to_absolute( 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() pe_helpers::get_current_exe_path_w()
); );
std::wstring IgnoreInjectionError = get_ini_value(L"Extra", L"IgnoreInjectionError", L"1"); std::wstring IgnoreInjectionError = get_ini_value(L"Injection", L"IgnoreInjectionError", L"1");
std::wstring IgnoreLoaderArchDifference = get_ini_value(L"Extra", L"IgnoreLoaderArchDifference", L"0"); 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(ForceInjectSteamClient);
to_bool_ini_val(IgnoreInjectionError); to_bool_ini_val(IgnoreInjectionError);
to_bool_ini_val(IgnoreLoaderArchDifference); 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 // log everything
dbg_log::write(L"SteamClient::Exe: " + ExeFile); 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::AppId: " + AppId);
dbg_log::write(L"SteamClient::SteamClient: " + ClientPath); dbg_log::write(L"SteamClient::SteamClient: " + ClientPath);
dbg_log::write(L"SteamClient::SteamClient64Dll: " + Client64Path); 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"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"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]) { if (!common_helpers::file_exist(Client64Path)) {
SetEnvironmentVariableW(L"SteamAppId", AppId.c_str()); dbg_log::write("Couldn't find the requested SteamClient64Dll");
SetEnvironmentVariableW(L"SteamGameId", AppId.c_str()); MessageBoxA(NULL, "Couldn't find the requested SteamClient64Dll.", "ColdClientLoader", MB_ICONERROR);
SetEnvironmentVariableW(L"SteamOverlayGameId", AppId.c_str()); dbg_log::close();
} else { return 1;
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(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; return 1;
} }
@ -231,30 +426,19 @@ int APIENTRY wWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance
dbg_log::close(); dbg_log::close();
return 1; return 1;
} }
if (!common_helpers::file_exist(Client64Path)) { bool isDllsToInjectFolderFound = false;
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;
}
if (DllsToInjectFolder.size()) { 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"); 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); MessageBoxA(NULL, "Couldn't find the requested folder of dlls to inject.", "ColdClientLoader", MB_ICONERROR);
dbg_log::close(); // it was requested to make this a non intrusive error, don't shutdown the whole thing because the folder is missing
return 1; // dbg_log::close();
// return 1;
} }
} }
auto exe_header = get_pe_header(ExeFile); auto exe_header = get_pe_header(ExeFile);
if (exe_header.empty()) { if (exe_header.empty()) {
dbg_log::write("Couldn't read the exe header"); dbg_log::write("Couldn't read the exe header");
@ -277,8 +461,7 @@ int APIENTRY wWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance
dbg_log::write("Detected exe arch: x64"); dbg_log::write("Detected exe arch: x64");
} }
bool loader_is_32 = pe_helpers::is_module_32(GetModuleHandleW(NULL)); if (loader_is_32) {
if (pe_helpers::is_module_32(GetModuleHandleW(NULL))) {
dbg_log::write("Detected loader arch: x32"); dbg_log::write("Detected loader arch: x32");
} else { } else {
dbg_log::write("Detected loader arch: x64"); 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 (isDllsToInjectFolderFound) {
if (DllsToInjectFolder.size()) {
std::wstring failed_dlls{}; std::wstring failed_dlls{};
dlls_to_inject = collect_dlls_to_inject(DllsToInjectFolder, is_exe_32, failed_dlls); dlls_to_inject = collect_dlls_to_inject(DllsToInjectFolder, is_exe_32, failed_dlls);
if (failed_dlls.size() && IgnoreInjectionError.empty()) { 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 (ForceInjectSteamClient.size()) {
if (is_exe_32) { if (is_exe_32) {
dlls_to_inject.insert(dlls_to_inject.begin(), ClientPath); dlls_to_inject.insert(dlls_to_inject.begin(), ClientPath);
@ -371,57 +496,143 @@ int APIENTRY wWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance
dlls_to_inject.insert(dlls_to_inject.begin(), Client64Path); dlls_to_inject.insert(dlls_to_inject.begin(), Client64Path);
} }
} }
for (const auto &dll : dlls_to_inject) {
dbg_log::write(L"Injecting dll: '" + dll + L"' ..."); if (PersistentMode != L"2" ) {
const char *err_inject = nullptr; if (AppId.size() && AppId[0]) {
DWORD code = pe_helpers::loadlib_remote(processInfo.hProcess, dll, &err_inject); set_steam_env_vars(AppId);
if (code != ERROR_SUCCESS) { } else {
std::wstring err_full = dbg_log::write("You forgot to set the AppId");
L"Failed to inject the dll: " + dll + L"\n" + MessageBoxA(NULL, "You forgot to set the AppId.", "ColdClientLoader", MB_ICONERROR);
common_helpers::str_to_w(err_inject) + L"\n" + return 1;
common_helpers::str_to_w(pe_helpers::get_err_string(code)) + L"\n" + }
L"Error code = " + std::to_wstring(code) + L"\n"; } else { // steam://run/
dbg_log::write(err_full); constexpr const static wchar_t STEAM_LAUNCH_CMD_1[] = L"steam://run/";
if (IgnoreInjectionError.empty()) { constexpr const static wchar_t STEAM_LAUNCH_CMD_2[] = L"run/";
TerminateProcess(processInfo.hProcess, 1); AppId.clear(); // we don't care about the app id in the ini
CloseHandle(processInfo.hProcess); std::wstring my_cmd = lpCmdLine && lpCmdLine[0] ? std::wstring(lpCmdLine) : std::wstring();
CloseHandle(processInfo.hThread); dbg_log::write(L"persistent mode 2 detecting steam launch cmd from: " + my_cmd);
MessageBoxW(NULL, err_full.c_str(), L"ColdClientLoader", MB_ICONERROR); if (my_cmd.find(STEAM_LAUNCH_CMD_1) == 0) {
dbg_log::close(); AppId = my_cmd.substr(sizeof(STEAM_LAUNCH_CMD_1) / sizeof(wchar_t), my_cmd.find_first_of(L" \t"));
return 1; 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) {
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" +
L"Error code = " + std::to_wstring(code) + L"\n";
dbg_log::write(err_full);
if (IgnoreInjectionError.empty()) {
TerminateProcess(processInfo.hProcess, 1);
CloseHandle(processInfo.hProcess);
CloseHandle(processInfo.hThread);
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 {
dbg_log::write("Injected!");
} }
} else {
dbg_log::write("Injected!");
} }
// 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";
dbg_log::write(msg);
MessageBoxA(NULL, msg.c_str(), "ColdClientLoader", MB_OK);
}
// wait
WaitForSingleObject(processInfo.hThread, INFINITE);
CloseHandle(processInfo.hProcess);
CloseHandle(processInfo.hThread);
} }
// run if (PersistentMode == L"1" || (PersistentMode == L"2" && AppId.empty()) ) {
if (ResumeByDebugger.empty()) { MessageBoxA(NULL, "Press OK when you have finished playing to close the loader", "Cold Client Loader (waiting)", MB_OK);
ResumeThread(processInfo.hThread);
} else {
std::string msg = "Attach a debugger now to PID " + std::to_string(processInfo.dwProcessId) + " and resume its main thread";
dbg_log::write(msg);
MessageBoxA(NULL, msg.c_str(), "ColdClientLoader", MB_OK);
} }
// wait if (PersistentMode != L"2" || AppId.empty()) { // persistent mode 0 or 1, or mode 2 without app id
WaitForSingleObject(processInfo.hThread, INFINITE); cleanup_registry_hkcu();
cleanup_registry_hklm();
CloseHandle(processInfo.hProcess); cleanup_registry_hkcs();
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()));
}
} }
dbg_log::close(); dbg_log::close();

View File

@ -6,7 +6,7 @@ Exe=game.exe
ExeRunDir= ExeRunDir=
# any additional args to pass, ex: -dx11, also any args passed to the loader will be passed to the app # any additional args to pass, ex: -dx11, also any args passed to the loader will be passed to the app
ExeCommandLine= ExeCommandLine=
# IMPORTANT # IMPORTANT, unless [Persistence] Mode=2
AppId= AppId=
# path to the steamclient dlls, both must be set, # path to the steamclient dlls, both must be set,
@ -14,14 +14,10 @@ AppId=
SteamClientDll=steamclient.dll SteamClientDll=steamclient.dll
SteamClient64Dll=steamclient64.dll SteamClient64Dll=steamclient64.dll
[Injection]
# force inject steamclient dll instead of waiting for the app to load it # force inject steamclient dll instead of waiting for the app to load it
ForceInjectSteamClient=0 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 # path to a folder containing some dlls to inject into the app upon start
# this folder will be traversed recursively # this folder will be traversed recursively
# additionally, inside this folder you can create a file called `load_order.txt` and # additionally, inside this folder you can create a file called `load_order.txt` and
@ -30,9 +26,22 @@ ResumeByDebugger=0
# example: # example:
#DllsToInjectFolder=extra_dlls #DllsToInjectFolder=extra_dlls
DllsToInjectFolder= DllsToInjectFolder=
# don't display an error message when a dll injection fails # don't display an error message when a dll injection fails
IgnoreInjectionError=1 IgnoreInjectionError=1
# don't display an error message if the architecture of the loader is different from the app # 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 # 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 # both the loader and the app must have the same arch for the injection to work
IgnoreLoaderArchDifference=0 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