Skip to content

Commit 24114f0

Browse files
committed
#8a: yeaah no clue why i did that earlier. anyways, now there's a steamstub patcher built in lol.
1 parent fa34913 commit 24114f0

12 files changed

Lines changed: 356 additions & 7 deletions

README.md

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,16 @@ __**If using self built .dlls:**__
2020

2121
## Configuration
2222

23-
Create `union-crax.ini` next to the game executable to change your AppId as needed. If this file is missing, AppId defaults to `480` and plugins are not loaded. `PluginsFolder` is relative to the game executable or wherever it's set in the .ini. Or should be. I haven't tested it yet. Check the `steam_appid.txt` file that gets created upon running the game to check if your set AppId was accepted.
23+
Create `union-crax.ini` next to the game executable to change your AppId as needed. If this file is missing, AppId defaults to `480` and plugins are not loaded. `PluginsFolder` is relative to the game executable or wherever it's set in the .ini. Or should be. I haven't tested it yet. Check the `steam_appid.txt` file that gets created upon running the game to check if your set AppId was accepted. For games that have `480` patched in
24+
the game's code, try setting it to something else free that's multiplayer, like `440`
25+
(Team Fortress 2). Shapes of Dreams did not work using `480`, but worked fine with `440`.
26+
((THANK YOU to deityofsukana for helping figure that out for certain!!!))
2427

2528
```ini
2629
[Settings]
2730
AppId=480
2831
PluginsFolder=plugins
32+
GetStubbedLol=false
2933
```
3034

3135
## Plugin Loader / Injector
@@ -39,6 +43,15 @@ plugins/
3943
03_another_one_(dj_khaled!!).dll
4044
```
4145

46+
## SteamStubbed
47+
48+
If `GetStubbedLol` is enabled in the .ini file, it will attempt to patch SteamStub on the fly. This is meant for games that Steamless cannot unpack, such as Dave the Diver. However, it can be used to keep from modifying the game files at all, or as little as possible. I'm not responsible for the code, it was used from DenuvoSanctuary's original Rust code, [which can be found here](https://github.com/denuvosanctuary/steamstubbed). I
49+
rewrote it in C++ so I could try integrating it into this project and not need to inject it. I did not ask for permission to use it in any way, so if there are any issues with that, please contact me and I'll remove it or work something out. It's not much of a change anyways, and they're easy to find too.
50+
51+
If the function is disabled, or was never written in the first place, then it simply
52+
will just ignore the function entirely and continue as it wassn't implemented in the
53+
first place.
54+
4255
## Building
4356

4457
__**Quick way (true Chad way - quick, simple, and easy):**__

dllmain.cpp

Lines changed: 89 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -295,6 +295,12 @@ BOOL WINAPI DllMain(HMODULE hModule, DWORD dwReason, LPVOID lpReserved)
295295
s_PluginLoader.LoadPlugins();
296296

297297
UCOLOG("[UCOnline2] %zu plugin(s) loaded", s_PluginLoader.LoadedCount());
298+
299+
g_bSteamStubEnabled = s_PluginLoader.GetSteamStubEnabled();
300+
if (g_bSteamStubEnabled)
301+
{
302+
SteamStub_Init();
303+
}
298304
}
299305

300306
return TRUE;
@@ -772,5 +778,87 @@ void CDumpHandler::WriteDump(DWORD exceptionCode, _EXCEPTION_POINTERS* pExceptio
772778

773779
ReleaseSRWLockExclusive(&m_Lock);
774780
}
775-
776781
#endif
782+
783+
/**
784+
* SteamStubbed, credits to DenuvoSanctuary for the original code for this.
785+
* Originally written in Rust, rewritten here in C++ to integrate it.
786+
*/
787+
#include <intrin.h>
788+
#include "include/MinHook.h"
789+
#include <atomic>
790+
791+
static bool g_bSteamStubEnabled = false;
792+
793+
static std::atomic<uint32_t> g_SteamStubCount{ 0 };
794+
static constexpr uint32_t STEAM_STUB_MAX_COUNT = 1;
795+
static constexpr uint8_t STEAM_STUB_SIGNATURE[] = { 0x44, 0x0F, 0xB6, 0xF8, 0x3C, 0x30, 0x0F, 0x84 };
796+
797+
typedef DWORD(WINAPI* GetTickCount_t)(void);
798+
static GetTickCount_t g_OrigGetTickCount = nullptr;
799+
800+
static uint8_t* SteamStub_FindSignature(uint8_t* start, uint8_t* end, const uint8_t* sig, size_t sigLen)
801+
{
802+
for (uint8_t* p = start; p < end - sigLen; ++p)
803+
{
804+
bool match = true;
805+
for (size_t i = 0; i < sigLen; ++i)
806+
{
807+
if (p[i] != sig[i])
808+
{
809+
match = false;
810+
break;
811+
}
812+
}
813+
if (match)
814+
return p;
815+
}
816+
return nullptr;
817+
}
818+
819+
static DWORD WINAPI SteamStub_HookGetTickCount(void)
820+
{
821+
uint8_t* returnAddr = reinterpret_cast<uint8_t*>(_ReturnAddress());
822+
823+
uint8_t* start = returnAddr;
824+
uint8_t* end = start + 128;
825+
826+
DWORD oldProtect = 0;
827+
if (!VirtualProtect(start, static_cast<SIZE_T>(end - start), PAGE_EXECUTE_READWRITE, &oldProtect))
828+
{
829+
return g_OrigGetTickCount();
830+
}
831+
832+
uint8_t* found = SteamStub_FindSignature(start, end, STEAM_STUB_SIGNATURE, sizeof(STEAM_STUB_SIGNATURE));
833+
if (found)
834+
{
835+
found[6] = 0x90;
836+
found[7] = 0xE9;
837+
838+
uint32_t count = g_SteamStubCount.fetch_add(1, std::memory_order_seq_cst) + 1;
839+
if (count >= STEAM_STUB_MAX_COUNT)
840+
{
841+
MH_DisableHook(reinterpret_cast<LPVOID*>(GetTickCount));
842+
}
843+
}
844+
845+
VirtualProtect(start, static_cast<SIZE_T>(end - start), oldProtect, &oldProtect);
846+
847+
return g_OrigGetTickCount();
848+
}
849+
850+
static void SteamStub_Init()
851+
{
852+
if (MH_Initialize() != MH_OK)
853+
return;
854+
855+
void* pTarget = reinterpret_cast<void*>(GetTickCount);
856+
857+
if (MH_CreateHook(pTarget, SteamStub_HookGetTickCount, reinterpret_cast<LPVOID*>(&g_OrigGetTickCount)) != MH_OK)
858+
return;
859+
860+
if (MH_EnableHook(pTarget) != MH_OK)
861+
return;
862+
863+
UCOLOG("[UCOnline2] SteamStub hook initialized");
864+
}

include/MinHook.h

Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
/*
2+
* MinHook - The Minimalistic API Hooking Library for x64/x86
3+
* Copyright (C) 2009-2017 Tsuda Kageyu.
4+
* All rights reserved.
5+
*
6+
* Redistribution and use in source and binary forms, with or without
7+
* modification, are permitted provided that the following conditions
8+
* are met:
9+
*
10+
* 1. Redistributions of source code must retain the above copyright
11+
* notice, this list of conditions and the following disclaimer.
12+
* 2. Redistributions in binary form must reproduce the above copyright
13+
* notice, this list of conditions and the following disclaimer in the
14+
* documentation and/or other materials provided with the distribution.
15+
*
16+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17+
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
18+
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
19+
* PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER
20+
* OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
21+
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
22+
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
23+
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
24+
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
25+
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
26+
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27+
*/
28+
29+
#pragma once
30+
31+
#if !(defined _M_IX86) && !(defined _M_X64) && !(defined __i386__) && !(defined __x86_64__)
32+
#error MinHook supports only x86 and x64 systems.
33+
#endif
34+
35+
#include <windows.h>
36+
37+
// MinHook Error Codes.
38+
typedef enum MH_STATUS
39+
{
40+
// Unknown error. Should not be returned.
41+
MH_UNKNOWN = -1,
42+
43+
// Successful.
44+
MH_OK = 0,
45+
46+
// MinHook is already initialized.
47+
MH_ERROR_ALREADY_INITIALIZED,
48+
49+
// MinHook is not initialized yet, or already uninitialized.
50+
MH_ERROR_NOT_INITIALIZED,
51+
52+
// The hook for the specified target function is already created.
53+
MH_ERROR_ALREADY_CREATED,
54+
55+
// The hook for the specified target function is not created yet.
56+
MH_ERROR_NOT_CREATED,
57+
58+
// The hook for the specified target function is already enabled.
59+
MH_ERROR_ENABLED,
60+
61+
// The hook for the specified target function is not enabled yet, or already
62+
// disabled.
63+
MH_ERROR_DISABLED,
64+
65+
// The specified pointer is invalid. It points the address of non-allocated
66+
// and/or non-executable region.
67+
MH_ERROR_NOT_EXECUTABLE,
68+
69+
// The specified target function cannot be hooked.
70+
MH_ERROR_UNSUPPORTED_FUNCTION,
71+
72+
// Failed to allocate memory.
73+
MH_ERROR_MEMORY_ALLOC,
74+
75+
// Failed to change the memory protection.
76+
MH_ERROR_MEMORY_PROTECT,
77+
78+
// The specified module is not loaded.
79+
MH_ERROR_MODULE_NOT_FOUND,
80+
81+
// The specified function is not found.
82+
MH_ERROR_FUNCTION_NOT_FOUND
83+
}
84+
MH_STATUS;
85+
86+
// Can be passed as a parameter to MH_EnableHook, MH_DisableHook,
87+
// MH_QueueEnableHook or MH_QueueDisableHook.
88+
#define MH_ALL_HOOKS NULL
89+
90+
#ifdef __cplusplus
91+
extern "C" {
92+
#endif
93+
94+
// Initialize the MinHook library. You must call this function EXACTLY ONCE
95+
// at the beginning of your program.
96+
MH_STATUS WINAPI MH_Initialize(VOID);
97+
98+
// Uninitialize the MinHook library. You must call this function EXACTLY
99+
// ONCE at the end of your program.
100+
MH_STATUS WINAPI MH_Uninitialize(VOID);
101+
102+
// Creates a hook for the specified target function, in disabled state.
103+
// Parameters:
104+
// pTarget [in] A pointer to the target function, which will be
105+
// overridden by the detour function.
106+
// pDetour [in] A pointer to the detour function, which will override
107+
// the target function.
108+
// ppOriginal [out] A pointer to the trampoline function, which will be
109+
// used to call the original target function.
110+
// This parameter can be NULL.
111+
MH_STATUS WINAPI MH_CreateHook(LPVOID pTarget, LPVOID pDetour, LPVOID *ppOriginal);
112+
113+
// Creates a hook for the specified API function, in disabled state.
114+
// Parameters:
115+
// pszModule [in] A pointer to the loaded module name which contains the
116+
// target function.
117+
// pszProcName [in] A pointer to the target function name, which will be
118+
// overridden by the detour function.
119+
// pDetour [in] A pointer to the detour function, which will override
120+
// the target function.
121+
// ppOriginal [out] A pointer to the trampoline function, which will be
122+
// used to call the original target function.
123+
// This parameter can be NULL.
124+
MH_STATUS WINAPI MH_CreateHookApi(
125+
LPCWSTR pszModule, LPCSTR pszProcName, LPVOID pDetour, LPVOID *ppOriginal);
126+
127+
// Creates a hook for the specified API function, in disabled state.
128+
// Parameters:
129+
// pszModule [in] A pointer to the loaded module name which contains the
130+
// target function.
131+
// pszProcName [in] A pointer to the target function name, which will be
132+
// overridden by the detour function.
133+
// pDetour [in] A pointer to the detour function, which will override
134+
// the target function.
135+
// ppOriginal [out] A pointer to the trampoline function, which will be
136+
// used to call the original target function.
137+
// This parameter can be NULL.
138+
// ppTarget [out] A pointer to the target function, which will be used
139+
// with other functions.
140+
// This parameter can be NULL.
141+
MH_STATUS WINAPI MH_CreateHookApiEx(
142+
LPCWSTR pszModule, LPCSTR pszProcName, LPVOID pDetour, LPVOID *ppOriginal, LPVOID *ppTarget);
143+
144+
// Removes an already created hook.
145+
// Parameters:
146+
// pTarget [in] A pointer to the target function.
147+
MH_STATUS WINAPI MH_RemoveHook(LPVOID pTarget);
148+
149+
// Enables an already created hook.
150+
// Parameters:
151+
// pTarget [in] A pointer to the target function.
152+
// If this parameter is MH_ALL_HOOKS, all created hooks are
153+
// enabled in one go.
154+
MH_STATUS WINAPI MH_EnableHook(LPVOID pTarget);
155+
156+
// Disables an already created hook.
157+
// Parameters:
158+
// pTarget [in] A pointer to the target function.
159+
// If this parameter is MH_ALL_HOOKS, all created hooks are
160+
// disabled in one go.
161+
MH_STATUS WINAPI MH_DisableHook(LPVOID pTarget);
162+
163+
// Queues to enable an already created hook.
164+
// Parameters:
165+
// pTarget [in] A pointer to the target function.
166+
// If this parameter is MH_ALL_HOOKS, all created hooks are
167+
// queued to be enabled.
168+
MH_STATUS WINAPI MH_QueueEnableHook(LPVOID pTarget);
169+
170+
// Queues to disable an already created hook.
171+
// Parameters:
172+
// pTarget [in] A pointer to the target function.
173+
// If this parameter is MH_ALL_HOOKS, all created hooks are
174+
// queued to be disabled.
175+
MH_STATUS WINAPI MH_QueueDisableHook(LPVOID pTarget);
176+
177+
// Applies all queued changes in one go.
178+
MH_STATUS WINAPI MH_ApplyQueued(VOID);
179+
180+
// Translates the MH_STATUS to its name as a string.
181+
const char * WINAPI MH_StatusToString(MH_STATUS status);
182+
183+
#ifdef __cplusplus
184+
}
185+
#endif

include/callback_dispatcher.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,12 @@
1+
/**
2+
* The heavy lifter of this and how multiplayer games work on Steam in general.
3+
* This is the class that handles all of the callbacks, and also the call results.
4+
* Matchmaking, lobby creation & handling, server list retrieval, all of that is
5+
* done through here. Without the dispatcher, none of these things would work.
6+
*
7+
* ~veeλnti<3 2026
8+
*/
9+
110
#pragma once
211

312
#include <map>

include/dll_loader.h

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,15 @@
1+
/**
2+
* I know it's named dll_loader.h, but in reality it does more than just that.
3+
* I added the other features after I had made it and established it would be
4+
* just a dll injector / loader and didn't realize my fuckup. I'm gonna try and
5+
* change it at some point and hope it doesn't break anything later on down the
6+
* road. Right now, this handles the injection loader, the ini configuration,
7+
* and the appid customization stuff. And now, it handles the steam stub loading
8+
* and live patching as well. The actual code can be found at the end of dllmain.cpp.
9+
*
10+
* ~veeλnti<3 2026
11+
*/
12+
113
#pragma once
214

315
#include <Windows.h>
@@ -45,6 +57,17 @@ class CDLLLoader
4557
return (id == 0) ? 480 : id;
4658
}
4759

60+
bool GetSteamStubEnabled()
61+
{
62+
if (m_IniPath[0] == '\0')
63+
return false;
64+
65+
char buf[8] = { 0 };
66+
GetPrivateProfileStringA("Settings", "GetStubbedLol", "false", buf, sizeof(buf), m_IniPath);
67+
68+
return (_stricmp(buf, "true") == 0 || _stricmp(buf, "1") == 0 || _stricmp(buf, "yes") == 0);
69+
}
70+
4871
void LoadPlugins()
4972
{
5073
if (m_IniPath[0] == '\0')

include/dump_handler.h

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
1-
/* This should only be a debug build feature, as it is not needed by myself really.
2-
This also only dumps crashes that may occur when using this, I cannot guarantee stability.
3-
So do expect potential issues. Thank you for your patience. */
1+
/**
2+
* This should only be a debug build feature, as it is not needed by myself really.
3+
* This also only dumps crashes that may occur when using this, I cannot guarantee stability.
4+
* So do expect potential issues. Thank you for your patience.
5+
*
6+
* ~veeλnti<3 2026
7+
*/
48

59
#pragma once
610

include/globals.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,12 @@
1+
/**
2+
* All of the interfaces, exports found in the real dlls, namespaces,
3+
* functions, you name it. This is kinda necessary, and also really
4+
* touchy. So try to be careful here, if you even look at it sideways
5+
* it'll take that as a sign to just throw the whole build process lol.
6+
*
7+
* ~veeλnti<3 2026
8+
*/
9+
110
#pragma once
211

312
#include <Windows.h>

0 commit comments

Comments
 (0)