Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 0 additions & 5 deletions src/unrealsdk/game/bl1/antidebug.cpp
Original file line number Diff line number Diff line change
@@ -1,8 +1,3 @@

// - NOTE -
// Copied from bl2/antidebug.cpp
//

#include "unrealsdk/pch.h"

#include "unrealsdk/game/bl1/bl1.h"
Expand Down
17 changes: 14 additions & 3 deletions src/unrealsdk/game/bl1/bl1.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ using namespace unrealsdk::unreal;
namespace unrealsdk::game {

void BL1Hook::hook(void) {
wait_for_steam_drm();

hook_antidebug();

hook_process_event();
Expand Down Expand Up @@ -41,6 +43,11 @@ void BL1Hook::post_init(void) {

namespace {

#if defined(__MINGW32__)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wattributes" // thiscall on non-class
#endif

// FFrame::Step is inlined, so instead we manually re-implement it using GNatives.
const constinit Pattern<11> GNATIVES_SIG{
"8B 14 95 {????????}" // mov edx, [edx*4+01F942C0]
Expand All @@ -49,18 +56,22 @@ const constinit Pattern<11> GNATIVES_SIG{
};

// NOLINTNEXTLINE(modernize-use-using)
typedef void(__stdcall* fframe_step_func)(FFrame*, void*);
typedef void(__thiscall* fframe_step_func)(UObject*, FFrame*, void*);
fframe_step_func** fframe_step_gnatives;

#if defined(__MINGW32__)
#pragma GCC diagnostic pop
#endif

} // namespace

void BL1Hook::find_fframe_step(void) {
fframe_step_gnatives = GNATIVES_SIG.sigscan_nullable<fframe_step_func**>();
LOG(MISC, "GNatives: {:p}", reinterpret_cast<void*>(fframe_step_gnatives));
}

void BL1Hook::fframe_step(unreal::FFrame* frame, unreal::UObject* /*obj*/, void* param) const {
((*fframe_step_gnatives)[*frame->Code++])(frame, param);
void BL1Hook::fframe_step(FFrame* frame, UObject* obj, void* param) const {
((*fframe_step_gnatives)[*frame->Code++])(obj, frame, param);
}

#pragma region FName::Init
Expand Down
10 changes: 8 additions & 2 deletions src/unrealsdk/game/bl1/bl1.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@ namespace unrealsdk::game {

class BL1Hook : public AbstractHook {
protected:
/**
* @brief Blocking waits for the steam drm to finish unpacking the executable.
*/
static void wait_for_steam_drm(void);

/**
* @brief Finds `FName::Init`, and sets up such that `fname_init` may be called.
*/
Expand Down Expand Up @@ -126,10 +131,11 @@ class BL1Hook : public AbstractHook {

template <>
struct GameTraits<BL1Hook> {
static constexpr auto NAME = "Borderlands";
static constexpr auto NAME = "Borderlands 1";

static bool matches_executable(std::string_view executable) {
return executable == "Borderlands.exe" || executable == "borderlands.exe";
return executable.starts_with("Borderlands.exe")
|| executable.starts_with("borderlands.exe");
}
};

Expand Down
110 changes: 110 additions & 0 deletions src/unrealsdk/game/bl1/steamdrm.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
#include "unrealsdk/pch.h"
#include "unrealsdk/game/bl1/bl1.h"
#include "unrealsdk/memory.h"
#include "unrealsdk/utils.h"

#if UNREALSDK_FLAVOUR == UNREALSDK_FLAVOUR_WILLOW && !defined(UNREALSDK_IMPORTING)

using namespace unrealsdk::utils;
using namespace unrealsdk::memory;

namespace unrealsdk::game {

namespace {

// This is ___tmainCRTStartup, so expect it's very stable
const constinit Pattern<14> UNPACKED_ENTRY_SIG{"6A 58 68 ?? ?? ?? ?? E8 ?? ?? ?? ?? 33 DB"};

std::atomic<bool> ready = false;
std::mutex ready_mutex;
std::condition_variable ready_cv;

// This is probably orders of magnitude bigger than we need, but best be safe
const constexpr std::chrono::seconds FALLBACK_DELAY{5};

// NOLINTBEGIN(readability-identifier-naming)

// NOLINTNEXTLINE(modernize-use-using) - need a typedef for calling conventions in msvc
typedef void(WINAPI* GetStatupInfoA_func)(LPSTARTUPINFOA);
GetStatupInfoA_func GetStatupInfoA_ptr;

void GetStartupInfoA_hook(LPSTARTUPINFOA lpStartupInfo) {
GetStatupInfoA_ptr(lpStartupInfo);

static_assert(decltype(ready)::is_always_lock_free, "need to lock on checking ready flag too");
if (ready.load()) {
return;
}

const std::lock_guard<std::mutex> lock{ready_mutex};
ready.store(true);
ready_cv.notify_all();
}

// NOLINTEND(readability-identifier-naming)

} // namespace

void BL1Hook::wait_for_steam_drm(void) {
{
// Immediately suspend the other threads
const ThreadSuspender suspend{};

if (UNPACKED_ENTRY_SIG.sigscan_nullable() != 0) {
// If we found a match, we're already unpacked
return;
}

LOG(MISC, "Waiting for steam drm unpack");

// Set up a hook for GetStartupInfoA, which is the one of the first things the unpacked
// entry function calls, which we'll use to tell once it's been unpacked
MH_STATUS status = MH_OK;

status = MH_CreateHook(reinterpret_cast<LPVOID>(&GetStartupInfoA),
reinterpret_cast<LPVOID>(&GetStartupInfoA_hook),
reinterpret_cast<LPVOID*>(&GetStatupInfoA_ptr));
if (status != MH_OK) {
LOG(ERROR, "Failed to create GetStartupInfoA hook: {:x}",
static_cast<uint32_t>(status));

LOG(ERROR, "Falling back to a static delay");
std::this_thread::sleep_for(FALLBACK_DELAY);
return;
}

status = MH_EnableHook(reinterpret_cast<LPVOID>(&GetStartupInfoA));
if (status != MH_OK) {
LOG(ERROR, "Failed to enable GetStartupInfoA hook: {:x}",
static_cast<uint32_t>(status));

LOG(ERROR, "Falling back to a static delay");
std::this_thread::sleep_for(FALLBACK_DELAY);
return;
}

// Drop out of this scope and unsuspend the other threads, let the unpacker run
}

std::unique_lock lock(ready_mutex);
ready_cv.wait(lock, [] { return ready.load(); });

MH_STATUS status = MH_OK;
status = MH_DisableHook(reinterpret_cast<LPVOID>(&GetStartupInfoA));
if (status != MH_OK) {
LOG(ERROR, "Failed to disable GetStartupInfoA hook: {:x}", static_cast<uint32_t>(status));

// If it fails, there isn't really any harm in leaving it active, just return
return;
}

status = MH_RemoveHook(reinterpret_cast<LPVOID>(&GetStartupInfoA));
if (status != MH_OK) {
LOG(ERROR, "Failed to remove GetStartupInfoA hook: {:x}", static_cast<uint32_t>(status));
return;
}
Comment on lines +92 to +105
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any reason for the duplication?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

disable vs remove? those aren't the same thing, best to clean up properly

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, i'm blind just assumed they were both remove lol

}

} // namespace unrealsdk::game

#endif
2 changes: 1 addition & 1 deletion src/unrealsdk/game/tps/offsets.h
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ class UClass : public UStruct {
UObject* ClassDefaultObject;

private:
uint8_t UnknownData01[0x14];
uint8_t UnknownData01[0x10];

public:
unreal::TArray<unreal::FImplementedInterface> Interfaces;
Expand Down
9 changes: 9 additions & 0 deletions src/unrealsdk/memory.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,15 @@ std::pair<uintptr_t, size_t> get_exe_range(void) {
auto module_length = nt_header->OptionalHeader.SizeOfImage;

range = {reinterpret_cast<uintptr_t>(allocation_base), module_length};

if constexpr (sizeof(uintptr_t) == 4) {
LOG(MISC, "Executable memory range: {:08x}-{:08x}", range->first,
range->first + range->second);
} else {
LOG(MISC, "Executable memory range: {:012x}-{:012x}", range->first,
range->first + range->second);
}

return *range;
}

Expand Down
2 changes: 2 additions & 0 deletions src/unrealsdk/pch.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@
#define SetThreadDescription(x, y)
#endif

#include <tlhelp32.h>

#include <MinHook.h>

#ifdef __cplusplus
Expand Down
8 changes: 6 additions & 2 deletions src/unrealsdk/unreal/structs/fscriptdelegate.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -94,8 +94,12 @@ void FScriptDelegate::validate_signature(const std::optional<BoundFunction>& fun
reasonably simple to implement.
*/
{
auto func_props = func->func->properties();
auto sig_props = signature->properties();
auto func_props = std::ranges::filter_view(func->func->properties(), [](UProperty* prop) {
return (prop->PropertyFlags() & UProperty::PROP_FLAG_PARAM) != 0;
});
auto sig_props = std::ranges::filter_view(signature->properties(), [](UProperty* prop) {
return (prop->PropertyFlags() & UProperty::PROP_FLAG_PARAM) != 0;
});

auto [func_diff, sig_diff] = std::ranges::mismatch(
func_props, sig_props,
Expand Down
53 changes: 53 additions & 0 deletions src/unrealsdk/utils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -90,4 +90,57 @@ std::filesystem::path get_executable(void) {
return path;
}

namespace {

/**
* @brief Suspends or resumes all other threads in the process.
*
* @param resume True if to resume, false if to suspend them.
*/
void adjust_thread_running_status(bool resume) {
HANDLE thread_snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);
if (thread_snapshot == nullptr) {
CloseHandle(thread_snapshot);
return;
}

THREADENTRY32 te32{};
te32.dwSize = sizeof(THREADENTRY32);

if (Thread32First(thread_snapshot, &te32) == 0) {
CloseHandle(thread_snapshot);
return;
}

do {
if (te32.th32OwnerProcessID != GetCurrentProcessId()
|| te32.th32ThreadID == GetCurrentThreadId()) {
continue;
}

HANDLE thread = OpenThread(THREAD_GET_CONTEXT | THREAD_SET_CONTEXT | THREAD_SUSPEND_RESUME,
0, te32.th32ThreadID);
if (thread != nullptr) {
if (resume) {
ResumeThread(thread);
} else {
SuspendThread(thread);
}

CloseHandle(thread);
}
} while (Thread32Next(thread_snapshot, &te32) != 0);

CloseHandle(thread_snapshot);
}

} // namespace

ThreadSuspender::ThreadSuspender(void) {
adjust_thread_running_status(false);
}
ThreadSuspender::~ThreadSuspender() {
adjust_thread_running_status(true);
}

} // namespace unrealsdk::utils
14 changes: 14 additions & 0 deletions src/unrealsdk/utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,20 @@ struct DLLSafeCallback {
R operator()(As... args) { return this->vftable->call(this, args...); }
};

/**
* @brief RAII class which suspends all other threads for it's lifespan.
*/
class ThreadSuspender {
public:
ThreadSuspender(void);
~ThreadSuspender();

ThreadSuspender(const ThreadSuspender&) = delete;
ThreadSuspender(ThreadSuspender&&) = delete;
ThreadSuspender& operator=(const ThreadSuspender&) = delete;
ThreadSuspender& operator=(ThreadSuspender&&) = delete;
};

namespace {

template <typename T>
Expand Down