Skip to content

Commit 7c36283

Browse files
committed
handle steam drm encrypting bl1 exe
1 parent 793b9ef commit 7c36283

File tree

8 files changed

+198
-7
lines changed

8 files changed

+198
-7
lines changed

src/unrealsdk/game/bl1/antidebug.cpp

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,3 @@
1-
2-
// - NOTE -
3-
// Copied from bl2/antidebug.cpp
4-
//
5-
61
#include "unrealsdk/pch.h"
72

83
#include "unrealsdk/game/bl1/bl1.h"

src/unrealsdk/game/bl1/bl1.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ using namespace unrealsdk::unreal;
1414
namespace unrealsdk::game {
1515

1616
void BL1Hook::hook(void) {
17+
wait_for_steam_drm();
18+
1719
hook_antidebug();
1820

1921
hook_process_event();

src/unrealsdk/game/bl1/bl1.h

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,11 @@ namespace unrealsdk::game {
1212

1313
class BL1Hook : public AbstractHook {
1414
protected:
15+
/**
16+
* @brief Blocking waits for the steam drm to finish unpacking the executable.
17+
*/
18+
static void wait_for_steam_drm(void);
19+
1520
/**
1621
* @brief Finds `FName::Init`, and sets up such that `fname_init` may be called.
1722
*/
@@ -126,10 +131,11 @@ class BL1Hook : public AbstractHook {
126131

127132
template <>
128133
struct GameTraits<BL1Hook> {
129-
static constexpr auto NAME = "Borderlands";
134+
static constexpr auto NAME = "Borderlands 1";
130135

131136
static bool matches_executable(std::string_view executable) {
132-
return executable == "Borderlands.exe" || executable == "borderlands.exe";
137+
return executable.starts_with("Borderlands.exe")
138+
|| executable.starts_with("borderlands.exe");
133139
}
134140
};
135141

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
#include "unrealsdk/pch.h"
2+
#include "unrealsdk/game/bl1/bl1.h"
3+
#include "unrealsdk/memory.h"
4+
#include "unrealsdk/utils.h"
5+
6+
#if UNREALSDK_FLAVOUR == UNREALSDK_FLAVOUR_WILLOW && !defined(UNREALSDK_IMPORTING)
7+
8+
using namespace unrealsdk::utils;
9+
using namespace unrealsdk::memory;
10+
11+
namespace unrealsdk::game {
12+
13+
namespace {
14+
15+
// This is ___tmainCRTStartup, so expect it's very stable
16+
const constinit Pattern<14> UNPACKED_ENTRY_SIG{"6A 58 68 ?? ?? ?? ?? E8 ?? ?? ?? ?? 33 DB"};
17+
18+
std::atomic<bool> ready = false;
19+
std::mutex ready_mutex;
20+
std::condition_variable ready_cv;
21+
22+
// This is probably orders of magnitude bigger than we need, but best be safe
23+
const constexpr std::chrono::seconds FALLBACK_DELAY{5};
24+
25+
// NOLINTBEGIN(readability-identifier-naming)
26+
27+
// NOLINTNEXTLINE(modernize-use-using) - need a typedef for calling conventions in msvc
28+
typedef void(WINAPI* GetStatupInfoA_func)(LPSTARTUPINFOA);
29+
GetStatupInfoA_func GetStatupInfoA_ptr;
30+
31+
void GetStartupInfoA_hook(LPSTARTUPINFOA lpStartupInfo) {
32+
GetStatupInfoA_ptr(lpStartupInfo);
33+
34+
static_assert(decltype(ready)::is_always_lock_free, "need to lock on checking ready flag too");
35+
if (ready.load()) {
36+
return;
37+
}
38+
39+
const std::lock_guard<std::mutex> lock{ready_mutex};
40+
ready.store(true);
41+
ready_cv.notify_all();
42+
}
43+
44+
// NOLINTEND(readability-identifier-naming)
45+
46+
} // namespace
47+
48+
void BL1Hook::wait_for_steam_drm(void) {
49+
{
50+
// Immediately suspend the other threads
51+
const ThreadSuspender suspend{};
52+
53+
if (UNPACKED_ENTRY_SIG.sigscan_nullable() != 0) {
54+
// If we found a match, we're already unpacked
55+
return;
56+
}
57+
58+
LOG(MISC, "Waiting for steam drm unpack");
59+
60+
// Set up a hook for GetStartupInfoA, which is the one of the first things the unpacked
61+
// entry function calls, which we'll use to tell once it's been unpacked
62+
MH_STATUS status = MH_OK;
63+
64+
status = MH_CreateHook(reinterpret_cast<LPVOID>(&GetStartupInfoA),
65+
reinterpret_cast<LPVOID>(&GetStartupInfoA_hook),
66+
reinterpret_cast<LPVOID*>(&GetStatupInfoA_ptr));
67+
if (status != MH_OK) {
68+
LOG(ERROR, "Failed to create GetStartupInfoA hook: {:x}",
69+
static_cast<uint32_t>(status));
70+
71+
LOG(ERROR, "Falling back to a static delay");
72+
std::this_thread::sleep_for(FALLBACK_DELAY);
73+
return;
74+
}
75+
76+
status = MH_EnableHook(reinterpret_cast<LPVOID>(&GetStartupInfoA));
77+
if (status != MH_OK) {
78+
LOG(ERROR, "Failed to enable GetStartupInfoA hook: {:x}",
79+
static_cast<uint32_t>(status));
80+
81+
LOG(ERROR, "Falling back to a static delay");
82+
std::this_thread::sleep_for(FALLBACK_DELAY);
83+
return;
84+
}
85+
86+
// Drop out of this scope and unsuspend the other threads, let the unpacker run
87+
}
88+
89+
std::unique_lock lock(ready_mutex);
90+
ready_cv.wait(lock, [] { return ready.load(); });
91+
92+
MH_STATUS status = MH_OK;
93+
status = MH_DisableHook(reinterpret_cast<LPVOID>(&GetStartupInfoA));
94+
if (status != MH_OK) {
95+
LOG(ERROR, "Failed to disable GetStartupInfoA hook: {:x}", static_cast<uint32_t>(status));
96+
97+
// If it fails, there isn't really any harm in leaving it active, just return
98+
return;
99+
}
100+
101+
status = MH_RemoveHook(reinterpret_cast<LPVOID>(&GetStartupInfoA));
102+
if (status != MH_OK) {
103+
LOG(ERROR, "Failed to remove GetStartupInfoA hook: {:x}", static_cast<uint32_t>(status));
104+
return;
105+
}
106+
}
107+
108+
} // namespace unrealsdk::game
109+
110+
#endif

src/unrealsdk/memory.cpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,15 @@ std::pair<uintptr_t, size_t> get_exe_range(void) {
2828
auto module_length = nt_header->OptionalHeader.SizeOfImage;
2929

3030
range = {reinterpret_cast<uintptr_t>(allocation_base), module_length};
31+
32+
if constexpr (sizeof(uintptr_t) == 4) {
33+
LOG(MISC, "Executable memory range: {:08x}-{:08x}", range->first,
34+
range->first + range->second);
35+
} else {
36+
LOG(MISC, "Executable memory range: {:012x}-{:012x}", range->first,
37+
range->first + range->second);
38+
}
39+
3140
return *range;
3241
}
3342

src/unrealsdk/pch.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@
2929
#define SetThreadDescription(x, y)
3030
#endif
3131

32+
#include <tlhelp32.h>
33+
3234
#include <MinHook.h>
3335

3436
#ifdef __cplusplus

src/unrealsdk/utils.cpp

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,4 +90,57 @@ std::filesystem::path get_executable(void) {
9090
return path;
9191
}
9292

93+
namespace {
94+
95+
/**
96+
* @brief Suspends or resumes all other threads in the process.
97+
*
98+
* @param resume True if to resume, false if to suspend them.
99+
*/
100+
void adjust_thread_running_status(bool resume) {
101+
HANDLE thread_snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);
102+
if (thread_snapshot == nullptr) {
103+
CloseHandle(thread_snapshot);
104+
return;
105+
}
106+
107+
THREADENTRY32 te32{};
108+
te32.dwSize = sizeof(THREADENTRY32);
109+
110+
if (Thread32First(thread_snapshot, &te32) == 0) {
111+
CloseHandle(thread_snapshot);
112+
return;
113+
}
114+
115+
do {
116+
if (te32.th32OwnerProcessID != GetCurrentProcessId()
117+
|| te32.th32ThreadID == GetCurrentThreadId()) {
118+
continue;
119+
}
120+
121+
HANDLE thread = OpenThread(THREAD_GET_CONTEXT | THREAD_SET_CONTEXT | THREAD_SUSPEND_RESUME,
122+
0, te32.th32ThreadID);
123+
if (thread != nullptr) {
124+
if (resume) {
125+
ResumeThread(thread);
126+
} else {
127+
SuspendThread(thread);
128+
}
129+
130+
CloseHandle(thread);
131+
}
132+
} while (Thread32Next(thread_snapshot, &te32) != 0);
133+
134+
CloseHandle(thread_snapshot);
135+
}
136+
137+
} // namespace
138+
139+
ThreadSuspender::ThreadSuspender(void) {
140+
adjust_thread_running_status(false);
141+
}
142+
ThreadSuspender::~ThreadSuspender() {
143+
adjust_thread_running_status(true);
144+
}
145+
93146
} // namespace unrealsdk::utils

src/unrealsdk/utils.h

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,20 @@ struct DLLSafeCallback {
131131
R operator()(As... args) { return this->vftable->call(this, args...); }
132132
};
133133

134+
/**
135+
* @brief RAII class which suspends all other threads for it's lifespan.
136+
*/
137+
class ThreadSuspender {
138+
public:
139+
ThreadSuspender(void);
140+
~ThreadSuspender();
141+
142+
ThreadSuspender(const ThreadSuspender&) = delete;
143+
ThreadSuspender(ThreadSuspender&&) = delete;
144+
ThreadSuspender& operator=(const ThreadSuspender&) = delete;
145+
ThreadSuspender& operator=(ThreadSuspender&&) = delete;
146+
};
147+
134148
namespace {
135149

136150
template <typename T>

0 commit comments

Comments
 (0)