* new stub dll GameOverlayRenderer for the experiemntal steamclient setup

* for windows build script: prevent permissive language extensions via the compiler flag `/permissive-`
This commit is contained in:
otavepto 2024-03-17 01:15:48 +02:00
parent 166e7db122
commit 5b55d448f7
8 changed files with 296 additions and 16 deletions

View File

@ -148,7 +148,7 @@ jobs:
- name: Build release mode
shell: cmd
working-directory: ${{ github.workspace }}
run: build_win.bat -verbose +exclient-extra-32 +exclient-extra-64 release
run: build_win.bat -verbose +exclient-extra-32 +exclient-extra-64 +lib-gameoverlay-32 +lib-gameoverlay-64 release
### package (release mode)
- name: Package build (release)
@ -170,7 +170,7 @@ jobs:
- name: Build debug mode
shell: cmd
working-directory: ${{ github.workspace }}
run: build_win.bat -verbose +exclient-extra-32 +exclient-extra-64 debug
run: build_win.bat -verbose +exclient-extra-32 +exclient-extra-64 +lib-gameoverlay-32 +lib-gameoverlay-64 debug
### package (debug mode)
- name: Package build (debug)

View File

@ -6,8 +6,12 @@
the script will attempt to detect and use the built-in tool `sudo` if it was available
* for windows: updated stub drm patterns and added a workaround for older variants,
this increases the compatibility, but makes it easier to be detected
* added missing example file `disable_lobby_creation.txt` in `steam_settings` folder + updated release `README`
* new stub dll `GameOverlayRenderer` for the experiemntal steamclient setup,
some apps verify the existence of this dll, either on disk, or inside their memory space.
not recommended to ignore it
* allow overlay invitations to obscure game input to be able to accept/reject the request
* added missing example file `disable_lobby_creation.txt` in `steam_settings` folder + updated release `README`
* for windows build script: prevent permissive language extensions via the compiler flag `/permissive-`
---

View File

@ -39,6 +39,9 @@ set /a BUILD_TOOL_LOBBY=1
set /a BUILD_LIB_NET_SOCKETS_32=0
set /a BUILD_LIB_NET_SOCKETS_64=0
set /a BUILD_LIB_GAMEOVERLAY_32=0
set /a BUILD_LIB_GAMEOVERLAY_64=0
:: < 0: deduce, > 1: force
set /a PARALLEL_THREADS_OVERRIDE=-1
@ -85,6 +88,10 @@ set /a VERBOSE=0
set /a BUILD_LIB_NET_SOCKETS_32=1
) else if "%~1"=="+lib-netsockets-64" (
set /a BUILD_LIB_NET_SOCKETS_64=1
) else if "%~1"=="+lib-gameoverlay-32" (
set /a BUILD_LIB_GAMEOVERLAY_32=1
) else if "%~1"=="+lib-gameoverlay-64" (
set /a BUILD_LIB_GAMEOVERLAY_64=1
) else if "%~1"=="-j" (
call :get_parallel_threads_count %~2 || (
call :err_msg "Invalid arg after -j, expected a number"
@ -175,7 +182,7 @@ set "dos_stub_exe_64=%win_resources_src_dir%\file_dos_stub\file_dos_stub_64.exe"
set "protoc_exe_32=%deps_dir%\protobuf\install32\bin\protoc.exe"
set "protoc_exe_64=%deps_dir%\protobuf\install64\bin\protoc.exe"
set "common_compiler_args=/std:c++17 /MP%build_threads% /DYNAMICBASE /errorReport:none /nologo /utf-8 /Zc:char8_t- /EHsc /GF /GL- /GS"
set "common_compiler_args=/std:c++17 /permissive- /MP%build_threads% /DYNAMICBASE /errorReport:none /nologo /utf-8 /Zc:char8_t- /EHsc /GF /GL- /GS"
set "common_compiler_args_32=%common_compiler_args%"
set "common_compiler_args_64=%common_compiler_args%"
@ -391,6 +398,14 @@ if %BUILD_LIB_NET_SOCKETS_32% equ 1 (
echo: & echo:
)
:: gameoverlayrenderer lib (x32)
if %BUILD_LIB_GAMEOVERLAY_32% equ 1 (
call :compile_gameoverlay_lib_32 || (
set /a last_code+=1
)
echo: & echo:
)
endlocal & set /a last_code=%last_code%
@ -494,6 +509,14 @@ if %BUILD_LIB_NET_SOCKETS_64% equ 1 (
echo: & echo:
)
:: gameoverlayrenderer lib (x64)
if %BUILD_LIB_GAMEOVERLAY_64% equ 1 (
call :compile_gameoverlay_lib_64 || (
set /a last_code+=1
)
echo: & echo:
)
endlocal & set /a last_code=%last_code%
@ -577,7 +600,7 @@ endlocal & exit /b %_exit%
:compile_experimentalclient_32
setlocal
echo // building lib steamclient.dll - 32
echo // building experimental lib steamclient.dll - 32
set src_files="%win_resources_out_dir%\rsrc-client-32.res" %release_src% "%libs_dir%\detours\*.cpp" "controller\gamepad.c" "overlay_experimental\*.cpp"
set extra_inc_dirs=%overlay_inc32%
set extra_libs=%overlay_lib32%
@ -591,7 +614,7 @@ endlocal & exit /b %_exit%
:compile_experimentalclient_ldr_32
setlocal
echo // building executable steamclient_loader_32.exe - 32
echo // building experimental executable steamclient_loader_32.exe - 32
set src_files="%win_resources_out_dir%\rsrc-launcher-32.res" "%tools_src_dir%\steamclient_loader\win\*.cpp" "helpers\pe_helpers.cpp" "helpers\common_helpers.cpp" "helpers\dbg_log.cpp"
set extra_inc_dirs=/I"%tools_src_dir%\steamclient_loader\win\extra_protection" /I"pe_helpers"
set extra_libs="user32.lib"
@ -605,7 +628,7 @@ endlocal & exit /b %_exit%
:compile_experimentalclient_extra_32
setlocal
echo // building library steamclient_extra.dll - 32
echo // building experimental library steamclient_extra.dll - 32
set src_files="%win_resources_out_dir%\rsrc-client-32.res" "%tools_src_dir%\steamclient_loader\win\extra_protection\*.cpp" "helpers\pe_helpers.cpp" "helpers\common_helpers.cpp" "%libs_dir%\detours\*.cpp"
set extra_inc_dirs=/I"%tools_src_dir%\steamclient_loader\win\extra_protection" /I"pe_helpers"
call :build_for 1 0 "%steamclient_dir%\extra_dlls\steamclient_extra.dll" src_files extra_inc_dirs
@ -652,6 +675,19 @@ endlocal & exit /b %_exit%
)
endlocal & exit /b %_exit%
:compile_gameoverlay_lib_32
setlocal
echo // building experimental library GameOverlayRenderer.dll - 32
set src_files="game_overlay_renderer_lib\game_overlay_renderer_lib.cpp"
set extra_inc_dirs=/I"game_overlay_renderer_lib"
call :build_for 1 0 "%steamclient_dir%\GameOverlayRenderer.dll" src_files extra_inc_dirs
set /a _exit=%errorlevel%
if %_exit% equ 0 (
call :change_dos_stub 1 "%steamclient_dir%\GameOverlayRenderer.dll"
call "%signer_tool%" "%steamclient_dir%\GameOverlayRenderer.dll"
)
endlocal & exit /b %_exit%
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: x64
@ -695,7 +731,7 @@ endlocal & exit /b %_exit%
:compile_experimentalclient_64
setlocal
echo // building lib steamclient64.dll - 64
echo // building experimental lib steamclient64.dll - 64
set src_files="%win_resources_out_dir%\rsrc-client-64.res" %release_src% "%libs_dir%\detours\*.cpp" "controller\gamepad.c" "overlay_experimental\*.cpp"
set extra_inc_dirs=%overlay_inc64%
set extra_libs=%overlay_lib64%
@ -709,7 +745,7 @@ endlocal & exit /b %_exit%
:compile_experimentalclient_ldr_64
setlocal
echo // building executable steamclient_loader_64.exe - 64
echo // building experimental executable steamclient_loader_64.exe - 64
set src_files="%win_resources_out_dir%\rsrc-launcher-64.res" "%tools_src_dir%\steamclient_loader\win\*.cpp" "helpers\pe_helpers.cpp" "helpers\common_helpers.cpp" "helpers\dbg_log.cpp"
set extra_inc_dirs=/I"%tools_src_dir%\steamclient_loader\win\extra_protection" /I"pe_helpers"
set extra_libs="user32.lib"
@ -723,7 +759,7 @@ endlocal & exit /b %_exit%
:compile_experimentalclient_extra_64
setlocal
echo // building library steamclient_extra64.dll - 64
echo // building experimental library steamclient_extra64.dll - 64
set src_files="%win_resources_out_dir%\rsrc-client-64.res" "%tools_src_dir%\steamclient_loader\win\extra_protection\*.cpp" "helpers\pe_helpers.cpp" "helpers\common_helpers.cpp" "%libs_dir%\detours\*.cpp"
set extra_inc_dirs=/I"%tools_src_dir%\steamclient_loader\win\extra_protection" /I"pe_helpers"
call :build_for 0 0 "%steamclient_dir%\extra_dlls\steamclient_extra64.dll" src_files extra_inc_dirs
@ -746,6 +782,19 @@ endlocal & exit /b %_exit%
)
endlocal & exit /b %_exit%
:compile_gameoverlay_lib_64
setlocal
echo // building experimental library GameOverlayRenderer64.dll - 64
set src_files="game_overlay_renderer_lib\game_overlay_renderer_lib.cpp"
set extra_inc_dirs=/I"game_overlay_renderer_lib"
call :build_for 0 0 "%steamclient_dir%\GameOverlayRenderer64.dll" src_files extra_inc_dirs
set /a _exit=%errorlevel%
if %_exit% equ 0 (
call :change_dos_stub 0 "%steamclient_dir%\GameOverlayRenderer64.dll"
call "%signer_tool%" "%steamclient_dir%\GameOverlayRenderer64.dll"
)
endlocal & exit /b %_exit%
:err_msg

View File

@ -0,0 +1,109 @@
#include "game_overlay_renderer_lib.h"
static bool overlay_enabled = false;
static bool screenshots_hooked = false;
static float scale_factor_x = 0.0f;
static float scale_factor_y = 0.0f;
static int32 notification_inset_x = 0;
static int32 notification_inset_y = 0;
static ENotificationPosition notification_position = ENotificationPosition::k_EPositionInvalid;
S_API_EXPORT steam_bool S_CALLTYPE BOverlayNeedsPresent()
{
return false;
}
S_API_EXPORT steam_bool S_CALLTYPE IsOverlayEnabled()
{
return overlay_enabled;
}
S_API_EXPORT void S_CALLTYPE OverlayHookD3D3(void *param_1, void *param_2)
{
}
S_API_EXPORT void S_CALLTYPE SetNotificationInset(int32 param_1, int32 param_2)
{
if (param_1 >= 0 && param_2 >=0) {
notification_inset_x = param_1;
notification_inset_y = param_2;
}
}
S_API_EXPORT void S_CALLTYPE SetNotificationPosition(ENotificationPosition param_1)
{
switch (param_1)
{
case ENotificationPosition::k_EPositionTopLeft:
case ENotificationPosition::k_EPositionTopRight:
case ENotificationPosition::k_EPositionBottomLeft:
case ENotificationPosition::k_EPositionBottomRight:
notification_position = param_1;
break;
default: break;
}
}
S_API_EXPORT steam_bool S_CALLTYPE SteamOverlayIsUsingGamepad()
{
return false;
}
S_API_EXPORT steam_bool S_CALLTYPE SteamOverlayIsUsingKeyboard()
{
return false;
}
S_API_EXPORT void S_CALLTYPE ValveHookScreenshots(bool param_1)
{
screenshots_hooked = param_1;
}
S_API_EXPORT steam_bool S_CALLTYPE ValveIsScreenshotsHooked()
{
return screenshots_hooked;
}
#if defined(__WINDOWS_32__)
void __stdcall VirtualFreeWrapper(
void *param_1, void *param_2,
void *stack_cleanup_1, void *stack_cleanup_2, void *stack_cleanup_3, void *stack_cleanup_4, void *stack_cleanup_5,
void *stack_cleanup_6, void *stack_cleanup_7, void *stack_cleanup_8, void *stack_cleanup_9
)
{
// https://stackoverflow.com/a/2805560
#pragma comment(linker, "/EXPORT:" __FUNCTION__ "=" __FUNCDNAME__)
}
#endif // __WINDOWS_32__
S_API_EXPORT void S_CALLTYPE VulkanSteamOverlayGetScaleFactors(float *param_1, float *param_2)
{
// the original function doesn't check for nullptr
if (param_1) *param_1 = 0.0f;
if (param_2) *param_2 = 0.0f;
}
S_API_EXPORT void S_CALLTYPE VulkanSteamOverlayPresent(
void *param_1, int32 param_2, int32 param_3, void *param_4, void * param_5,
void *param_6, void *param_7, void *param_8, void *param_9, void *param_10
)
{
}
S_API_EXPORT void S_CALLTYPE VulkanSteamOverlayProcessCapturedFrame(
bool param_1, int32 param_2, int32 param_3, int32 param_4, void *param_5, void *param_6, int32 param_7,
int32 param_8, int32 param_9, int32 param_10, int32 param_11, int16 param_12, int16 param_13, int16 param_14
)
{
}

View File

@ -0,0 +1,70 @@
// this library is sometimes needed because some apps (like appid 410900) will check
// for its existence inside their memory space during runtime,
// otherwise they'll trigger custom nagging stuff
// notice how functions return a boolean result as uint32 not bool
// the return value is placed inside the entire 'eax' register, not just 'al'
// you have to disassem both 32-bit and 64-bit libraries to see this nonsense
// ex: 32-bit ValveIsScreenshotsHooked(): mov al, byte ptr ds:[0x7AF62DA1]
// 64-bit ValveIsScreenshotsHooked(): movzx eax, byte ptr ds:[0x00007FF8938F235D]
#ifndef __GAME_OVERLAY_RENDERER_LIB_H__
#define __GAME_OVERLAY_RENDERER_LIB_H__
#include "dll/common_includes.h"
S_API_EXPORT steam_bool S_CALLTYPE BOverlayNeedsPresent();
S_API_EXPORT steam_bool S_CALLTYPE IsOverlayEnabled();
S_API_EXPORT void S_CALLTYPE OverlayHookD3D3(void *param_1, void *param_2);
S_API_EXPORT void S_CALLTYPE SetNotificationInset(int32 param_1, int32 param_2);
S_API_EXPORT void S_CALLTYPE SetNotificationPosition(ENotificationPosition param_1);
S_API_EXPORT steam_bool S_CALLTYPE SteamOverlayIsUsingGamepad();
S_API_EXPORT steam_bool S_CALLTYPE SteamOverlayIsUsingKeyboard();
S_API_EXPORT void S_CALLTYPE ValveHookScreenshots(bool param_1);
S_API_EXPORT steam_bool S_CALLTYPE ValveIsScreenshotsHooked();
// only available for 32-bit lib on Windows
// the function takes 2 arguments, but they're expected to be at [esp] & [esp+4]
// notice how the top of the stack is not the return address, but param 1
// this function might be designed to be invoked via a jmp instruction,
// at the end it does this:
// popad // pop all 8 general purpose registers
// popfd // pop flags register
// ret // regular return
// notice how it eventually deallocates 8+1 registers (4 bytes * 9),
// meaning that (4 bytes * 2 input params) + (4 bytes * 9) are deallocated from the stack
// note: this is not marked S_API_EXPORT, because later we'll use a linker pragma
// to both export this function, and prevent __stdcall name mangling
#if defined(__WINDOWS_32__)
void __stdcall VirtualFreeWrapper(
void *param_1, void *param_2,
void *stack_cleanup_1, void *stack_cleanup_2, void *stack_cleanup_3, void *stack_cleanup_4, void *stack_cleanup_5,
void *stack_cleanup_6, void *stack_cleanup_7, void *stack_cleanup_8, void *stack_cleanup_9
);
#endif // __WINDOWS_32__
S_API_EXPORT void S_CALLTYPE VulkanSteamOverlayGetScaleFactors(float *param_1, float *param_2);
S_API_EXPORT void S_CALLTYPE VulkanSteamOverlayPresent(
void *param_1, int32 param_2, int32 param_3, void *param_4, void * param_5,
void *param_6, void *param_7, void *param_8, void *param_9, void *param_10
);
S_API_EXPORT void S_CALLTYPE VulkanSteamOverlayProcessCapturedFrame(
bool param_1, int32 param_2, int32 param_3, int32 param_4, void *param_5, void *param_6, int32 param_7,
int32 param_8, int32 param_9, int32 param_10, int32 param_11, int16 param_12, int16 param_13, int16 param_14
);
#endif // __GAME_OVERLAY_RENDERER_LIB_H__

View File

@ -17,17 +17,26 @@ You do not need to create a `steam_interfaces.txt` file for the `steamclient` ve
* `steamclient64.dll`
* `ColdClientLoader.ini`
* `steamclient_loader.exe`
2. Edit `ColdClientLoader.ini` and specify:
2. While it is not mandatory, it is highly recommended to copy the relevant `GameOverlayRenderer` dll(s)
This is recommended because some apps check for the existence of this dll, either on disk, on inside their memory space, otherwise they'll trigger custom protection.
When in doubt, just copy both dlls.
* `GameOverlayRenderer.dll`: for 32-bit apps
* `GameOverlayRenderer64.dll`: for 64-bit apps
3. Edit `ColdClientLoader.ini` and specify:
* `AppId`: the app ID
* `Exe`: the path to the game's executable/launcher, either full path or relative to this loader
* `ExeRunDir` *(optional)*: generally this must be set to the folder containing the game's exe, if left empty then it will be automatically set to the folder containing the game's exe.
* `ExeCommandLine` *(optional)*: additional args to pass to the exe, example: `-dx11 -windowed`
Optionally you can specify a different location for `steamclient(64).dll`:
Optionally you can specify a different location for `steamclient(64).dll`:
* `SteamClientDll`: path to `steamclient.dll`, either full path or relative to this loader
* `SteamClient64Dll`: path to `steamclient(64).dll`, either full path or relative to this loader
* `SteamClient64Dll`: path to `steamclient64.dll`, either full path or relative to this loader
* `ForceInjectSteamClient`: force inject `steamclient(64).dll` instead of letting the app load it automatically
* `ForceInjectGameOverlayRenderer`: force inject `GameOverlayRenderer(64).dll` instead of letting the app load it automatically.
These dlls are expected to be in the same folder of `steamclient(64).dll`
* `ResumeByDebugger`: setting this to `1` or `y` or `true` will prevent the loader from calling `ResumeThread()` on the main thread after spawning the .exe, and it will display a mesage with the process ID (PID) so you attach your debugger on it.
Note that you have to resume the main thread from the debugger after attaching, also the entry breakpoint may not be set automatically, but you can do that manually.
Note that you have to resume the main thread from the debugger after attaching, also the entry breakpoint may not be set automatically, but you can do that manually.
* `DllsToInjectFolder` *(optional)*: path to a folder containing dlls to force inject into the app upon start,
the loader will attempt to detect the dll architecture (32 or 64 bit), if it didn't match the architecture of the exe, then it will ignored
* `IgnoreInjectionError`: setting this to `1` or `y` or `true` will prevent the loader from displaying an error message when a dll injection fails
@ -59,4 +68,12 @@ Any .dll file not mentioned in this file will be loaded later, but in random ord
---
## `extra_dlls`
This folder contains an experimental dll which, when injected, will attempt to patch the Stub drm in meory, only for v3.1
This folder contains an experimental dll which, when injected, will attempt to patch the Stub drm in memory, mainly for newer variants but it also works on some of the older ones.
This isn't a complete solution, just a different method.
---
## `GameOverlayRenderer`
Some apps verify the existence of this dll, either on disk, or inside their memory space, that's why this dll exists.
It is **NOT** recommended to ignore this dll.

View File

@ -79,7 +79,7 @@ static std::vector<uint8_t> get_pe_header(const std::wstring &filepath)
static std::vector<std::wstring> collect_dlls_to_inject(
const std::wstring &extra_dlls_folder,
bool is_exe_32,
std::wstring &failed_dlls = std::wstring{})
std::wstring &failed_dlls)
{
const auto load_order_file = std::filesystem::path(extra_dlls_folder) / "load_order.txt";
std::vector<std::wstring> dlls_to_inject{};
@ -361,6 +361,7 @@ int APIENTRY wWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance
// dlls to inject
std::wstring ForceInjectSteamClient = get_ini_value(L"Injection", L"ForceInjectSteamClient");
std::wstring ForceInjectGameOverlayRenderer = get_ini_value(L"Injection", L"ForceInjectGameOverlayRenderer");
std::wstring DllsToInjectFolder = common_helpers::to_absolute(
get_ini_value(L"Injection", L"DllsToInjectFolder"),
pe_helpers::get_current_exe_path_w()
@ -374,6 +375,7 @@ int APIENTRY wWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance
std::wstring ResumeByDebugger = get_ini_value(L"Debug", L"ResumeByDebugger");
to_bool_ini_val(ForceInjectSteamClient);
to_bool_ini_val(ForceInjectGameOverlayRenderer);
to_bool_ini_val(IgnoreInjectionError);
to_bool_ini_val(IgnoreLoaderArchDifference);
to_bool_ini_val(ResumeByDebugger);
@ -388,6 +390,7 @@ int APIENTRY wWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance
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::ForceInjectGameOverlayRenderer: " + ForceInjectGameOverlayRenderer);
dbg_log::write(L"Injection::DllsToInjectFolder: " + DllsToInjectFolder);
dbg_log::write(L"Injection::IgnoreInjectionError: " + IgnoreInjectionError);
dbg_log::write(L"Injection::IgnoreLoaderArchDifference: " + IgnoreLoaderArchDifference);
@ -487,6 +490,31 @@ int APIENTRY wWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance
}
}
}
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);
}
}
}
if (ForceInjectSteamClient.size()) {
if (is_exe_32) {
dlls_to_inject.insert(dlls_to_inject.begin(), ClientPath);

View File

@ -18,6 +18,9 @@ SteamClient64Dll=steamclient64.dll
# force inject steamclient dll instead of waiting for the app to load it
ForceInjectSteamClient=0
# force inject GameOverlayRenderer dll instead of waiting for the app to load it
ForceInjectGameOverlayRenderer=0
# 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