2024-01-13 01:18:52 +02:00
// a Modified version of ColdClientLoader originally written by Rat431
2023-12-24 06:42:36 +02:00
// https://github.com/Rat431/ColdAPI_Steam/tree/master/src/ColdClientLoader
2019-12-03 12:52:12 -05:00
2024-01-13 01:18:52 +02:00
# include "common_helpers/common_helpers.hpp"
# include "pe_helpers/pe_helpers.hpp"
# include "dbg_log/dbg_log.hpp"
2019-12-03 12:52:12 -05:00
// C RunTime Header Files
# include <stdlib.h>
# include <malloc.h>
# include <memory.h>
# include <tchar.h>
2020-07-06 11:27:05 -04:00
# include <stdio.h>
2024-01-03 01:56:26 +02:00
# include <string>
2024-05-08 00:30:23 +03:00
# include <filesystem>
2024-01-19 03:47:58 +02:00
# include <set>
2024-02-05 22:18:00 +02:00
# include <thread>
2024-05-24 06:22:14 +03:00
# include <algorithm>
2019-12-03 12:52:12 -05:00
2024-01-12 15:50:08 +02:00
2024-05-08 00:30:23 +03:00
static std : : wstring IniFile { } ;
static const std : : wstring dbg_file = pe_helpers : : get_current_exe_path_w ( ) + pe_helpers : : get_current_exe_name_w ( ) + L " .log " ;
2024-01-13 01:18:52 +02:00
constexpr static const char STEAM_UNIVERSE [ ] = " Public " ;
2024-02-05 22:18:00 +02:00
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 { } ;
2024-01-13 01:18:52 +02:00
std : : wstring get_ini_value ( LPCWSTR section , LPCWSTR key , LPCWSTR default_val = NULL )
2019-12-03 12:52:12 -05:00
{
2024-01-13 01:18:52 +02:00
std : : vector < wchar_t > buff ( INT16_MAX ) ;
DWORD read_chars = GetPrivateProfileStringW ( section , key , default_val , & buff [ 0 ] , ( DWORD ) buff . size ( ) , IniFile . c_str ( ) ) ;
if ( ! read_chars ) {
std : : wstring ( ) ;
}
// "If neither lpAppName nor lpKeyName is NULL and the supplied destination buffer is too small to hold the requested string, the return value is equal to nSize minus one"
int trials = 3 ;
while ( ( read_chars = = ( buff . size ( ) - 1 ) ) & & trials > 0 ) {
buff . resize ( buff . size ( ) * 2 ) ;
read_chars = GetPrivateProfileStringW ( section , key , default_val , & buff [ 0 ] , ( DWORD ) buff . size ( ) , IniFile . c_str ( ) ) ;
- - trials ;
}
return std : : wstring ( & buff [ 0 ] , read_chars ) ;
2019-12-03 12:52:12 -05:00
}
2024-01-14 02:29:02 +02:00
static std : : vector < uint8_t > get_pe_header ( const std : : wstring & filepath )
{
2024-02-13 02:08:14 +02:00
try {
2024-05-24 06:22:14 +03:00
std : : ifstream file ( std : : filesystem : : path ( filepath ) , std : : ios : : binary ) ;
2024-01-14 02:29:02 +02:00
if ( ! file . is_open ( ) ) throw ;
file . seekg ( 0 , std : : ios : : beg ) ;
// 2MB is enough
std : : vector < uint8_t > data ( 2 * 1024 * 1024 , 0 ) ;
file . read ( ( char * ) & data [ 0 ] , data . size ( ) ) ;
file . close ( ) ;
return data ;
2024-02-13 02:08:14 +02:00
} catch ( const std : : exception & e ) {
dbg_log : : write ( std : : string ( " Error reading PE header: " ) + e . what ( ) ) ;
2024-01-14 02:29:02 +02:00
return std : : vector < uint8_t > ( ) ;
}
}
2024-04-20 00:52:29 +02:00
static std : : vector < std : : wstring > collect_dlls_to_inject ( const std : : wstring & extra_dlls_folder , bool is_exe_32 , std : : wstring & failed_dlls )
2024-01-19 03:47:58 +02:00
{
const auto load_order_file = std : : filesystem : : path ( extra_dlls_folder ) / " load_order.txt " ;
std : : vector < std : : wstring > dlls_to_inject { } ;
for ( auto const & dir_entry :
std : : filesystem : : recursive_directory_iterator ( extra_dlls_folder , std : : filesystem : : directory_options : : follow_directory_symlink ) ) {
if ( std : : filesystem : : is_directory ( dir_entry . path ( ) ) ) continue ;
auto dll_path = dir_entry . path ( ) . wstring ( ) ;
// ignore this file if it is the load order file
2024-06-21 07:48:30 +03:00
if ( common_helpers : : str_cmp_insensitive ( dll_path , load_order_file . wstring ( ) ) ) continue ;
2024-01-19 03:47:58 +02:00
auto dll_header = get_pe_header ( dll_path ) ;
if ( dll_header . empty ( ) ) {
2024-06-21 07:48:30 +03:00
dbg_log : : write ( L " Failed to get PE header of dll: ' " + dll_path + L " ' " ) ;
2024-01-19 03:47:58 +02:00
failed_dlls + = dll_path + L " \n " ;
continue ;
}
bool is_dll_32 = pe_helpers : : is_module_32 ( ( HMODULE ) & dll_header [ 0 ] ) ;
bool is_dll_64 = pe_helpers : : is_module_64 ( ( HMODULE ) & dll_header [ 0 ] ) ;
if ( ( ! is_dll_32 & & ! is_dll_64 ) | | ( is_dll_32 & & is_dll_64 ) ) { // ARM, or just a regular file
2024-06-21 07:48:30 +03:00
dbg_log : : write ( L " Dll ' " + dll_path + L " ' is neither 32 nor 64 bit and will be ignored " ) ;
2024-01-19 03:47:58 +02:00
failed_dlls + = dll_path + L " \n " ;
continue ;
}
if ( is_dll_32 = = is_exe_32 ) { // same arch
dlls_to_inject . push_back ( dll_path ) ;
2024-06-21 07:48:30 +03:00
dbg_log : : write ( L " Dll ' " + dll_path + L " ' is valid " ) ;
2024-01-19 03:47:58 +02:00
} else {
2024-06-21 07:48:30 +03:00
dbg_log : : write ( L " Dll ' " + dll_path + L " ' has a different arch than the exe and will be ignored " ) ;
2024-01-19 03:47:58 +02:00
failed_dlls + = dll_path + L " \n " ;
}
}
2024-06-21 07:48:30 +03:00
bool only_specified = false ;
2024-01-19 03:47:58 +02:00
std : : vector < std : : wstring > ordered_dlls_to_inject { } ;
{
2024-06-21 07:48:30 +03:00
dbg_log : : write ( L " Searching for load order file: ' " + load_order_file . wstring ( ) + L " ' " ) ;
2024-01-19 03:47:58 +02:00
auto f_order = std : : wifstream ( load_order_file , std : : ios : : in ) ;
if ( f_order . is_open ( ) ) {
2024-06-21 07:48:30 +03:00
only_specified = true ;
dbg_log : : write ( L " Reading load order file... " ) ;
2024-01-19 03:47:58 +02:00
std : : wstring line { } ;
while ( std : : getline ( f_order , line ) ) {
auto abs = common_helpers : : to_absolute ( line , extra_dlls_folder ) ;
2024-06-21 07:48:30 +03:00
dbg_log : : write ( L " Load order line: ' " + abs + L " ' " ) ;
auto it = std : : find_if ( dlls_to_inject . begin ( ) , dlls_to_inject . end ( ) , [ & abs ] ( const std : : wstring & dll_to_inject ) {
return common_helpers : : str_cmp_insensitive ( dll_to_inject , abs ) ;
2024-01-19 03:47:58 +02:00
} ) ;
if ( it ! = dlls_to_inject . end ( ) ) {
dbg_log : : write ( " Found the dll specified by the load order line " ) ;
ordered_dlls_to_inject . push_back ( * it ) ;
}
}
f_order . close ( ) ;
}
}
2024-06-21 07:48:30 +03:00
if ( only_specified ) return ordered_dlls_to_inject ;
2024-01-19 03:47:58 +02:00
2024-06-21 07:48:30 +03:00
return dlls_to_inject ;
2024-01-19 03:47:58 +02:00
}
static void to_bool_ini_val ( std : : wstring & val )
{
for ( auto & c : val ) {
c = ( wchar_t ) std : : tolower ( ( int ) c ) ;
}
if ( val ! = L " 1 " & & val ! = L " y " & & val ! = L " yes " & & val ! = L " true " ) {
val . clear ( ) ;
}
}
2024-02-05 22:18:00 +02:00
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 ) {
2024-02-07 03:39:22 +02:00
dbg_log : : write ( " restoring registry entries (HKCS) #1 " ) ;
2024-02-05 22:18:00 +02:00
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 ) {
2024-02-07 03:39:22 +02:00
dbg_log : : write ( " removing registry entries (HKCS) #2 (added by loader) " ) ;
2024-02-05 22:18:00 +02:00
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 ( ) ) ;
2024-04-20 01:13:10 +02:00
// this env var conflicts with Steam Input
// SetEnvironmentVariableW(L"SteamOverlayGameId", AppId.c_str());
2024-04-13 17:35:10 +02:00
// these 2 wil be overridden by the emu
SetEnvironmentVariableW ( L " SteamAppUser " , L " cold_player " ) ;
SetEnvironmentVariableW ( L " SteamUser " , L " cold_player " ) ;
SetEnvironmentVariableW ( L " SteamClientLaunch " , L " 1 " ) ;
SetEnvironmentVariableW ( L " SteamEnv " , L " 1 " ) ;
SetEnvironmentVariableW ( L " SteamPath " , pe_helpers : : get_current_exe_path_w ( ) . c_str ( ) ) ;
2024-02-05 22:18:00 +02:00
}
2019-12-03 12:52:12 -05:00
int APIENTRY wWinMain ( _In_ HINSTANCE hInstance , _In_opt_ HINSTANCE hPrevInstance , _In_ LPWSTR lpCmdLine , _In_ int nCmdShow )
{
2024-01-13 01:18:52 +02:00
dbg_log : : init ( dbg_file . c_str ( ) ) ;
2024-05-08 00:30:23 +03:00
IniFile = pe_helpers : : get_current_exe_path_w ( ) + std : : filesystem : : path ( pe_helpers : : get_current_exe_name_w ( ) ) . stem ( ) . wstring ( ) + L " .ini " ;
dbg_log : : write ( L " Searching for configuration file: " + IniFile ) ;
2024-01-13 01:18:52 +02:00
if ( ! common_helpers : : file_exist ( IniFile ) ) {
2024-05-08 00:30:23 +03:00
IniFile = pe_helpers : : get_current_exe_path_w ( ) + L " ColdClientLoader.ini " ;
dbg_log : : write ( L " Searching for configuration file: " + IniFile ) ;
}
if ( ! common_helpers : : file_exist ( IniFile ) ) {
dbg_log : : write ( L " Couldn't find the configuration file " ) ;
MessageBoxA ( NULL , " Couldn't find the configuration file. " , " ColdClientLoader " , MB_ICONERROR ) ;
2024-01-13 01:18:52 +02:00
dbg_log : : close ( ) ;
return 1 ;
}
2024-02-05 22:18:00 +02:00
ClientPath = common_helpers : : to_absolute (
get_ini_value ( L " SteamClient " , L " SteamClientDll " ) ,
2024-01-13 01:18:52 +02:00
pe_helpers : : get_current_exe_path_w ( )
) ;
2024-02-05 22:18:00 +02:00
Client64Path = common_helpers : : to_absolute (
get_ini_value ( L " SteamClient " , L " SteamClient64Dll " ) ,
2024-01-13 01:18:52 +02:00
pe_helpers : : get_current_exe_path_w ( )
) ;
std : : wstring ExeFile = common_helpers : : to_absolute (
get_ini_value ( L " SteamClient " , L " Exe " ) ,
pe_helpers : : get_current_exe_path_w ( )
) ;
std : : wstring ExeRunDir = common_helpers : : to_absolute (
get_ini_value ( L " SteamClient " , L " ExeRunDir " ) ,
pe_helpers : : get_current_exe_path_w ( )
) ;
std : : wstring ExeCommandLine = get_ini_value ( L " SteamClient " , L " ExeCommandLine " ) ;
std : : wstring AppId = get_ini_value ( L " SteamClient " , L " AppId " ) ;
2024-01-14 02:29:02 +02:00
// dlls to inject
2024-02-05 22:18:00 +02:00
std : : wstring ForceInjectSteamClient = get_ini_value ( L " Injection " , L " ForceInjectSteamClient " ) ;
2024-03-17 01:15:48 +02:00
std : : wstring ForceInjectGameOverlayRenderer = get_ini_value ( L " Injection " , L " ForceInjectGameOverlayRenderer " ) ;
2024-01-19 03:47:58 +02:00
std : : wstring DllsToInjectFolder = common_helpers : : to_absolute (
2024-02-05 22:18:00 +02:00
get_ini_value ( L " Injection " , L " DllsToInjectFolder " ) ,
2024-01-14 02:29:02 +02:00
pe_helpers : : get_current_exe_path_w ( )
) ;
2024-02-05 22:18:00 +02:00
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 " ) ;
2024-01-19 03:47:58 +02:00
to_bool_ini_val ( ForceInjectSteamClient ) ;
2024-03-17 01:15:48 +02:00
to_bool_ini_val ( ForceInjectGameOverlayRenderer ) ;
2024-01-19 03:47:58 +02:00
to_bool_ini_val ( IgnoreInjectionError ) ;
to_bool_ini_val ( IgnoreLoaderArchDifference ) ;
2024-02-05 22:18:00 +02:00
to_bool_ini_val ( ResumeByDebugger ) ;
if ( PersistentMode ! = L " 0 " & & PersistentMode ! = L " 1 " & & PersistentMode ! = L " 2 " ) PersistentMode = L " 0 " ;
2024-01-14 02:29:02 +02:00
2024-01-13 01:18:52 +02:00
// log everything
2024-01-14 02:29:02 +02:00
dbg_log : : write ( L " SteamClient::Exe: " + ExeFile ) ;
dbg_log : : write ( L " SteamClient::ExeRunDir: " + ExeRunDir ) ;
dbg_log : : write ( L " SteamClient::ExeCommandLine: " + ExeCommandLine ) ;
dbg_log : : write ( L " SteamClient::AppId: " + AppId ) ;
dbg_log : : write ( L " SteamClient::SteamClient: " + ClientPath ) ;
dbg_log : : write ( L " SteamClient::SteamClient64Dll: " + Client64Path ) ;
2024-02-05 22:18:00 +02:00
dbg_log : : write ( L " SteamClient::PersistentMode: " + PersistentMode ) ;
2024-01-19 03:47:58 +02:00
dbg_log : : write ( L " SteamClient::ForceInjectSteamClient: " + ForceInjectSteamClient ) ;
2024-03-17 01:15:48 +02:00
dbg_log : : write ( L " SteamClient::ForceInjectGameOverlayRenderer: " + ForceInjectGameOverlayRenderer ) ;
2024-02-05 22:18:00 +02:00
dbg_log : : write ( L " Injection::DllsToInjectFolder: " + DllsToInjectFolder ) ;
dbg_log : : write ( L " Injection::IgnoreInjectionError: " + IgnoreInjectionError ) ;
dbg_log : : write ( L " Injection::IgnoreLoaderArchDifference: " + IgnoreLoaderArchDifference ) ;
2024-01-19 03:47:58 +02:00
dbg_log : : write ( L " Debug::ResumeByDebugger: " + ResumeByDebugger ) ;
2024-02-05 22:18:00 +02:00
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 ( ) ;
2024-01-13 01:18:52 +02:00
return 1 ;
}
2024-01-14 02:29:02 +02:00
if ( ! common_helpers : : file_exist ( ExeFile ) ) {
dbg_log : : write ( " Couldn't find the requested Exe file " ) ;
MessageBoxA ( NULL , " Couldn't find the requested Exe file. " , " ColdClientLoader " , MB_ICONERROR ) ;
dbg_log : : close ( ) ;
return 1 ;
}
if ( ExeRunDir . empty ( ) ) {
ExeRunDir = std : : filesystem : : path ( ExeFile ) . parent_path ( ) . wstring ( ) ;
2024-01-19 03:47:58 +02:00
dbg_log : : write ( L " Setting ExeRunDir to: " + ExeRunDir ) ;
2024-01-14 02:29:02 +02:00
}
if ( ! common_helpers : : dir_exist ( ExeRunDir ) ) {
dbg_log : : write ( " Couldn't find the requested Exe run dir " ) ;
MessageBoxA ( NULL , " Couldn't find the requested Exe run dir. " , " ColdClientLoader " , MB_ICONERROR ) ;
dbg_log : : close ( ) ;
return 1 ;
}
2024-02-05 22:18:00 +02:00
bool isDllsToInjectFolderFound = false ;
2024-01-19 03:47:58 +02:00
if ( DllsToInjectFolder . size ( ) ) {
2024-02-05 22:18:00 +02:00
isDllsToInjectFolderFound = common_helpers : : dir_exist ( DllsToInjectFolder ) ;
if ( ! isDllsToInjectFolderFound ) {
2024-01-14 02:29:02 +02:00
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 ) ;
2024-02-05 22:18:00 +02:00
// 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;
2024-01-14 02:29:02 +02:00
}
}
2024-02-05 22:18:00 +02:00
2024-01-14 02:29:02 +02:00
auto exe_header = get_pe_header ( ExeFile ) ;
if ( exe_header . empty ( ) ) {
dbg_log : : write ( " Couldn't read the exe header " ) ;
MessageBoxA ( NULL , " Couldn't read the exe header. " , " ColdClientLoader " , MB_ICONERROR ) ;
2024-01-13 01:18:52 +02:00
dbg_log : : close ( ) ;
return 1 ;
}
2024-01-14 02:29:02 +02:00
bool is_exe_32 = pe_helpers : : is_module_32 ( ( HMODULE ) & exe_header [ 0 ] ) ;
bool is_exe_64 = pe_helpers : : is_module_64 ( ( HMODULE ) & exe_header [ 0 ] ) ;
if ( ( ! is_exe_32 & & ! is_exe_64 ) | | ( is_exe_32 & & is_exe_64 ) ) { // ARM, or just a regular file
dbg_log : : write ( " The requested exe is invalid (neither 32 nor 64 bit) " ) ;
MessageBoxA ( NULL , " The requested exe is invalid (neither 32 nor 64 bit) " , " ColdClientLoader " , MB_ICONERROR ) ;
2024-01-13 01:18:52 +02:00
dbg_log : : close ( ) ;
return 1 ;
}
2024-01-14 02:29:02 +02:00
if ( is_exe_32 ) {
dbg_log : : write ( " Detected exe arch: x32 " ) ;
} else {
dbg_log : : write ( " Detected exe arch: x64 " ) ;
}
2024-02-05 22:18:00 +02:00
if ( loader_is_32 ) {
2024-01-14 02:29:02 +02:00
dbg_log : : write ( " Detected loader arch: x32 " ) ;
} else {
dbg_log : : write ( " Detected loader arch: x64 " ) ;
}
if ( loader_is_32 ! = is_exe_32 ) {
dbg_log : : write ( " Arch of loader and requested exe are different, it is advised to use the appropriate one " ) ;
2024-01-19 03:47:58 +02:00
if ( IgnoreLoaderArchDifference . empty ( ) ) {
MessageBoxA ( NULL , " Arch of loader and requested exe are different, \n it is advised to use the appropriate one. " , " ColdClientLoader " , MB_OK ) ;
}
2024-01-14 02:29:02 +02:00
}
2024-02-05 22:18:00 +02:00
if ( isDllsToInjectFolderFound ) {
2024-01-19 03:47:58 +02:00
std : : wstring failed_dlls { } ;
dlls_to_inject = collect_dlls_to_inject ( DllsToInjectFolder , is_exe_32 , failed_dlls ) ;
2024-01-14 02:29:02 +02:00
if ( failed_dlls . size ( ) & & IgnoreInjectionError . empty ( ) ) {
int choice = MessageBoxW (
NULL ,
( L " The following dlls cannot be injected: \n " + failed_dlls + L " \n Continue ? " ) . c_str ( ) ,
L " ColdClientLoader " ,
MB_YESNO ) ;
if ( choice ! = IDYES ) {
dbg_log : : close ( ) ;
return 1 ;
}
}
}
2024-03-17 01:15:48 +02:00
if ( ForceInjectGameOverlayRenderer . size ( ) ) {
if ( is_exe_32 ) {
std : : wstring GameOverlayPath = common_helpers : : to_absolute (
L " GameOverlayRenderer.dll " ,
std : : filesystem : : path ( ClientPath ) . parent_path ( ) . wstring ( )
) ;
if ( ! common_helpers : : file_exist ( GameOverlayPath ) ) {
dbg_log : : write ( " Couldn't find GameOverlayRenderer.dll " ) ;
MessageBoxA ( NULL , " Couldn't find GameOverlayRenderer.dll. " , " ColdClientLoader " , MB_ICONERROR ) ;
} else {
dlls_to_inject . insert ( dlls_to_inject . begin ( ) , GameOverlayPath ) ;
}
} else { // 64
std : : wstring GameOverlay64Path = common_helpers : : to_absolute (
L " GameOverlayRenderer64.dll " ,
std : : filesystem : : path ( Client64Path ) . parent_path ( ) . wstring ( )
) ;
if ( ! common_helpers : : file_exist ( GameOverlay64Path ) ) {
dbg_log : : write ( " Couldn't find GameOverlayRenderer64.dll " ) ;
MessageBoxA ( NULL , " Couldn't find GameOverlayRenderer64.dll. " , " ColdClientLoader " , MB_ICONERROR ) ;
} else {
dlls_to_inject . insert ( dlls_to_inject . begin ( ) , GameOverlay64Path ) ;
}
}
}
2024-01-19 03:47:58 +02:00
if ( ForceInjectSteamClient . size ( ) ) {
2024-01-14 02:29:02 +02:00
if ( is_exe_32 ) {
dlls_to_inject . insert ( dlls_to_inject . begin ( ) , ClientPath ) ;
} else {
dlls_to_inject . insert ( dlls_to_inject . begin ( ) , Client64Path ) ;
}
}
2024-02-05 22:18:00 +02:00
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/ " ;
2024-02-07 03:39:22 +02:00
constexpr const static wchar_t STEAM_LAUNCH_CMD_2 [ ] = L " -- \" steam://rungameid/ " ;
2024-02-05 22:18:00 +02:00
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 ( ) ;
2024-02-07 03:39:22 +02:00
dbg_log : : write ( L " persistent mode 2 detecting steam launch cmd from: ' " + my_cmd + L " ' " ) ;
2024-02-05 22:18:00 +02:00
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 " ) ;
2024-01-14 02:29:02 +02:00
} else {
2024-02-05 22:18:00 +02:00
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 " ) ;
2024-01-13 01:18:52 +02:00
}
}
2024-02-05 22:18:00 +02:00
if ( ! patch_registry_hkcu ( ) ) {
dbg_log : : close ( ) ;
MessageBoxA ( NULL , " Unable to patch Registry (HKCU). " , " ColdClientLoader " , MB_ICONERROR ) ;
return 1 ;
2024-01-13 01:18:52 +02:00
}
2024-01-03 01:56:26 +02:00
2024-02-05 22:18:00 +02:00
if ( ! patch_registry_hklm ( ) ) {
cleanup_registry_hkcu ( ) ;
dbg_log : : close ( ) ;
MessageBoxA ( NULL , " Unable to patch Registry (HKLM). " , " ColdClientLoader " , MB_ICONERROR ) ;
return 1 ;
}
2024-02-07 03:39:22 +02:00
patch_registry_hkcs ( ) ;
2024-02-05 22:18:00 +02:00
// 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 } ;
2024-01-13 01:18:52 +02:00
2024-02-05 22:18:00 +02:00
SecureZeroMemory ( & info , sizeof ( info ) ) ;
info . cb = sizeof ( info ) ;
2024-01-13 01:18:52 +02:00
2024-02-05 22:18:00 +02:00
PROCESS_INFORMATION processInfo = { 0 } ;
SecureZeroMemory ( & processInfo , sizeof ( processInfo ) ) ;
2024-01-13 01:18:52 +02:00
2024-02-05 22:18:00 +02:00
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! " ) ;
}
}
// 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 ) ;
2024-01-13 01:18:52 +02:00
} else {
2024-02-05 22:18:00 +02:00
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 ) ;
2024-01-13 01:18:52 +02:00
}
2024-02-05 22:18:00 +02:00
// wait
WaitForSingleObject ( processInfo . hThread , INFINITE ) ;
CloseHandle ( processInfo . hProcess ) ;
CloseHandle ( processInfo . hThread ) ;
}
2024-02-07 03:39:22 +02:00
if ( PersistentMode = = L " 1 " ) {
2024-02-05 22:18:00 +02:00
MessageBoxA ( NULL , " Press OK when you have finished playing to close the loader " , " Cold Client Loader (waiting) " , MB_OK ) ;
2024-02-07 03:39:22 +02:00
} else if ( PersistentMode = = L " 2 " & & AppId . empty ( ) ) {
MessageBoxA ( NULL , " Start the game, then press OK when you have finished playing to close the loader " , " Cold Client Loader (waiting) " , MB_OK ) ;
2024-02-05 22:18:00 +02:00
}
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 ( ) ;
2024-01-13 01:18:52 +02:00
}
dbg_log : : close ( ) ;
return 0 ;
2019-12-03 12:52:12 -05:00
}