Add steam proxy
This commit is contained in:
parent
038621c438
commit
bef4ec67b6
16
premake5.lua
16
premake5.lua
@ -311,5 +311,21 @@ project "client"
|
||||
|
||||
dependencies.imports()
|
||||
|
||||
|
||||
project "runner"
|
||||
kind "WindowedApp"
|
||||
language "C++"
|
||||
|
||||
files {"./src/runner/**.rc", "./src/runner/**.hpp", "./src/runner/**.cpp", "./src/runner/resources/**.*"}
|
||||
|
||||
includedirs {"./src/runner", "./src/common", "%{prj.location}/src"}
|
||||
|
||||
links {"common"}
|
||||
|
||||
resincludedirs {"$(ProjectDir)src"}
|
||||
|
||||
dependencies.imports()
|
||||
|
||||
|
||||
group "Dependencies"
|
||||
dependencies.projects()
|
||||
|
233
src/client/component/steam_proxy.cpp
Normal file
233
src/client/component/steam_proxy.cpp
Normal file
@ -0,0 +1,233 @@
|
||||
#include <std_include.hpp>
|
||||
#include "loader/component_loader.hpp"
|
||||
#include "steam_proxy.hpp"
|
||||
#include "scheduler.hpp"
|
||||
|
||||
#include <utils/nt.hpp>
|
||||
#include <utils/flags.hpp>
|
||||
#include <utils/string.hpp>
|
||||
#include <utils/binary_resource.hpp>
|
||||
|
||||
#include "resource.hpp"
|
||||
|
||||
#include "steam/interface.hpp"
|
||||
#include "steam/steam.hpp"
|
||||
|
||||
namespace steam_proxy
|
||||
{
|
||||
namespace
|
||||
{
|
||||
utils::binary_resource runner_file(RUNNER, "boiii-runner.exe");
|
||||
|
||||
enum class ownership_state
|
||||
{
|
||||
success,
|
||||
unowned,
|
||||
nosteam,
|
||||
error,
|
||||
};
|
||||
|
||||
bool is_disabled()
|
||||
{
|
||||
static const auto disabled = utils::flags::has_flag("nosteam");
|
||||
return disabled;
|
||||
}
|
||||
}
|
||||
|
||||
class component final : public component_interface
|
||||
{
|
||||
public:
|
||||
void pre_start() override
|
||||
{
|
||||
/*if (is_disabled())
|
||||
{
|
||||
return;
|
||||
}*/
|
||||
|
||||
this->load_client();
|
||||
this->clean_up_on_error();
|
||||
|
||||
#ifndef DEV_BUILD
|
||||
try
|
||||
{
|
||||
const auto res = this->start_mod("\xE2\x98\x84\xEF\xB8\x8F" " BOIII"s, steam::SteamUtils()->GetAppID());
|
||||
|
||||
switch (res)
|
||||
{
|
||||
case ownership_state::nosteam:
|
||||
throw std::runtime_error("Steam must be running to play this game!");
|
||||
case ownership_state::unowned:
|
||||
throw std::runtime_error("You must own the game on steam to play this mod!");
|
||||
case ownership_state::error:
|
||||
throw std::runtime_error("Failed to verify ownership of the game!");
|
||||
case ownership_state::success:
|
||||
break;
|
||||
}
|
||||
}
|
||||
catch (std::exception& e)
|
||||
{
|
||||
printf("Steam: %s\n", e.what());
|
||||
MessageBoxA(GetForegroundWindow(), e.what(), "Error", MB_ICONERROR);
|
||||
TerminateProcess(GetCurrentProcess(), 1234);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void pre_destroy() override
|
||||
{
|
||||
if (this->steam_client_module_)
|
||||
{
|
||||
if (this->steam_pipe_)
|
||||
{
|
||||
if (this->global_user_)
|
||||
{
|
||||
this->steam_client_module_.invoke<void>("Steam_ReleaseUser", this->steam_pipe_,
|
||||
this->global_user_);
|
||||
}
|
||||
|
||||
this->steam_client_module_.invoke<bool>("Steam_BReleaseSteamPipe", this->steam_pipe_);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const utils::nt::library& get_overlay_module() const
|
||||
{
|
||||
return steam_overlay_module_;
|
||||
}
|
||||
|
||||
private:
|
||||
utils::nt::library steam_client_module_{};
|
||||
utils::nt::library steam_overlay_module_{};
|
||||
|
||||
steam::interface client_engine_{};
|
||||
steam::interface client_user_{};
|
||||
steam::interface client_utils_{};
|
||||
|
||||
void* steam_pipe_ = nullptr;
|
||||
void* global_user_ = nullptr;
|
||||
|
||||
void* load_client_engine() const
|
||||
{
|
||||
if (!this->steam_client_module_) return nullptr;
|
||||
|
||||
for (auto i = 1; i > 0; ++i)
|
||||
{
|
||||
std::string name = utils::string::va("CLIENTENGINE_INTERFACE_VERSION%03i", i);
|
||||
auto* const client_engine = this->steam_client_module_
|
||||
.invoke<void*>("CreateInterface", name.data(), nullptr);
|
||||
if (client_engine) return client_engine;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void load_client()
|
||||
{
|
||||
const std::filesystem::path steam_path = steam::SteamAPI_GetSteamInstallPath();
|
||||
if (steam_path.empty()) return;
|
||||
|
||||
utils::nt::library::load(steam_path / "tier0_s64.dll");
|
||||
utils::nt::library::load(steam_path / "vstdlib_s64.dll");
|
||||
this->steam_overlay_module_ = utils::nt::library::load(steam_path / "gameoverlayrenderer64.dll");
|
||||
this->steam_client_module_ = utils::nt::library::load(steam_path / "steamclient64.dll");
|
||||
if (!this->steam_client_module_) return;
|
||||
|
||||
this->client_engine_ = load_client_engine();
|
||||
if (!this->client_engine_) return;
|
||||
|
||||
this->steam_pipe_ = this->steam_client_module_.invoke<void*>("Steam_CreateSteamPipe");
|
||||
this->global_user_ = this->steam_client_module_.invoke<void*>(
|
||||
"Steam_ConnectToGlobalUser", this->steam_pipe_);
|
||||
this->client_user_ = this->client_engine_.invoke<void*>(8, this->steam_pipe_, this->global_user_);
|
||||
// GetIClientUser
|
||||
this->client_utils_ = this->client_engine_.invoke<void*>(14, this->steam_pipe_); // GetIClientUtils
|
||||
}
|
||||
|
||||
ownership_state start_mod(const std::string& title, const size_t app_id)
|
||||
{
|
||||
__try
|
||||
{
|
||||
return this->start_mod_unsafe(title, app_id);
|
||||
}
|
||||
__except (EXCEPTION_EXECUTE_HANDLER)
|
||||
{
|
||||
this->do_cleanup();
|
||||
return ownership_state::error;
|
||||
}
|
||||
}
|
||||
|
||||
ownership_state start_mod_unsafe(const std::string& title, size_t app_id)
|
||||
{
|
||||
if (!this->client_utils_ || !this->client_user_)
|
||||
{
|
||||
return ownership_state::nosteam;
|
||||
}
|
||||
|
||||
if (!this->client_user_.invoke<bool>("BIsSubscribedApp", app_id))
|
||||
{
|
||||
//app_id = 480; // Spacewar
|
||||
return ownership_state::unowned;
|
||||
}
|
||||
|
||||
this->client_utils_.invoke<void>("SetAppIDForCurrentPipe", app_id, false);
|
||||
|
||||
char our_directory[MAX_PATH] = {0};
|
||||
GetCurrentDirectoryA(sizeof(our_directory), our_directory);
|
||||
|
||||
const auto path = runner_file.get_extracted_file();
|
||||
const std::string cmdline = utils::string::va("\"%s\" -proc %d", path.data(), GetCurrentProcessId());
|
||||
|
||||
steam::game_id game_id;
|
||||
game_id.raw.type = 1; // k_EGameIDTypeGameMod
|
||||
game_id.raw.app_id = app_id & 0xFFFFFF;
|
||||
|
||||
const auto* mod_id = "bo3";
|
||||
game_id.raw.mod_id = *reinterpret_cast<const unsigned int*>(mod_id) | 0x80000000;
|
||||
|
||||
this->client_user_.invoke<bool>("SpawnProcess", path.data(), cmdline.data(), our_directory,
|
||||
&game_id.bits, title.data(), 0, 0, 0);
|
||||
|
||||
return ownership_state::success;
|
||||
}
|
||||
|
||||
void do_cleanup()
|
||||
{
|
||||
this->client_engine_ = nullptr;
|
||||
this->client_user_ = nullptr;
|
||||
this->client_utils_ = nullptr;
|
||||
|
||||
this->steam_pipe_ = nullptr;
|
||||
this->global_user_ = nullptr;
|
||||
|
||||
this->steam_client_module_ = utils::nt::library{nullptr};
|
||||
}
|
||||
|
||||
void clean_up_on_error()
|
||||
{
|
||||
scheduler::schedule([this]()
|
||||
{
|
||||
if (this->steam_client_module_
|
||||
&& this->steam_pipe_
|
||||
&& this->global_user_
|
||||
&& this->steam_client_module_.invoke<bool>("Steam_BConnected", this->global_user_,
|
||||
this->steam_pipe_)
|
||||
&& this->steam_client_module_.invoke<bool>("Steam_BLoggedOn", this->global_user_, this->steam_pipe_)
|
||||
)
|
||||
{
|
||||
return scheduler::cond_continue;
|
||||
}
|
||||
|
||||
this->do_cleanup();
|
||||
return scheduler::cond_end;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const utils::nt::library& get_overlay_module()
|
||||
{
|
||||
// TODO: Find a better way to do this
|
||||
return component_loader::get<component>()->get_overlay_module();
|
||||
}
|
||||
}
|
||||
|
||||
REGISTER_COMPONENT(steam_proxy::component)
|
7
src/client/component/steam_proxy.hpp
Normal file
7
src/client/component/steam_proxy.hpp
Normal file
@ -0,0 +1,7 @@
|
||||
#pragma once
|
||||
#include <utils/nt.hpp>
|
||||
|
||||
namespace steam_proxy
|
||||
{
|
||||
const utils::nt::library& get_overlay_module();
|
||||
}
|
@ -11,3 +11,5 @@
|
||||
#define DW_FASTFILE 305
|
||||
#define DW_KEYS 306
|
||||
#define DW_QOSCONFIG 307
|
||||
|
||||
#define RUNNER 308
|
||||
|
@ -102,6 +102,12 @@ DW_FASTFILE RCDATA "resources/dw/core_ffotd_tu32_593.ff"
|
||||
DW_KEYS RCDATA "resources/dw/keys.txt"
|
||||
DW_QOSCONFIG RCDATA "resources/dw/qosconfig4.csv"
|
||||
|
||||
#ifdef _DEBUG
|
||||
RUNNER RCDATA "../../build/bin/x64/Debug/runner.exe"
|
||||
#else
|
||||
RUNNER RCDATA "../../build/bin/x64/Release/runner.exe"
|
||||
#endif
|
||||
|
||||
#endif // English (United States) resources
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
@ -78,6 +78,7 @@
|
||||
#include <variant>
|
||||
#include <cassert>
|
||||
|
||||
#include <udis86.h>
|
||||
#include <MinHook.h>
|
||||
#include <asmjit/core/jitruntime.h>
|
||||
#include <asmjit/x86/x86assembler.h>
|
||||
|
98
src/client/steam/interface.cpp
Normal file
98
src/client/steam/interface.cpp
Normal file
@ -0,0 +1,98 @@
|
||||
#include <std_include.hpp>
|
||||
#include "interface.hpp"
|
||||
|
||||
#include <utils/memory.hpp>
|
||||
#include <utils/nt.hpp>
|
||||
|
||||
namespace steam
|
||||
{
|
||||
interface::interface() : interface(nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
interface::interface(void* interface_ptr) : interface_ptr_(static_cast<void***>(interface_ptr))
|
||||
{
|
||||
}
|
||||
|
||||
interface::operator bool() const
|
||||
{
|
||||
return this->interface_ptr_ != nullptr;
|
||||
}
|
||||
|
||||
void* interface::find_method(const std::string& name)
|
||||
{
|
||||
const auto method_entry = this->methods_.find(name);
|
||||
if (method_entry != this->methods_.end())
|
||||
{
|
||||
return method_entry->second;
|
||||
}
|
||||
|
||||
return this->search_method(name);
|
||||
}
|
||||
|
||||
void* interface::search_method(const std::string& name)
|
||||
{
|
||||
if (!utils::memory::is_bad_read_ptr(this->interface_ptr_))
|
||||
{
|
||||
auto vftbl = *this->interface_ptr_;
|
||||
|
||||
while (!utils::memory::is_bad_read_ptr(vftbl) && !utils::memory::is_bad_code_ptr(*vftbl))
|
||||
{
|
||||
const auto ptr = *vftbl;
|
||||
const auto result = this->analyze_method(ptr);
|
||||
if (!result.empty())
|
||||
{
|
||||
this->methods_[result] = ptr;
|
||||
|
||||
if (result == name)
|
||||
{
|
||||
return ptr;
|
||||
}
|
||||
}
|
||||
|
||||
++vftbl;
|
||||
}
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
std::string interface::analyze_method(const void* method_ptr)
|
||||
{
|
||||
if (utils::memory::is_bad_code_ptr(method_ptr)) return {};
|
||||
|
||||
ud_t ud;
|
||||
ud_init(&ud);
|
||||
ud_set_mode(&ud, 64);
|
||||
ud_set_pc(&ud, uint64_t(method_ptr));
|
||||
ud_set_input_buffer(&ud, static_cast<const uint8_t*>(method_ptr), INT32_MAX);
|
||||
|
||||
while (true)
|
||||
{
|
||||
ud_disassemble(&ud);
|
||||
|
||||
if (ud_insn_mnemonic(&ud) == UD_Iret)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if (ud_insn_mnemonic(&ud) == UD_Ilea)
|
||||
{
|
||||
const auto* operand = ud_insn_opr(&ud, 1);
|
||||
if (operand && operand->type == UD_OP_MEM && operand->base == UD_R_RIP)
|
||||
{
|
||||
auto* operand_ptr = reinterpret_cast<char*>(ud_insn_len(&ud) + ud_insn_off(&ud) + operand->lval.
|
||||
sdword);
|
||||
if (!utils::memory::is_bad_read_ptr(operand_ptr) && utils::memory::is_rdata_ptr(operand_ptr))
|
||||
{
|
||||
return operand_ptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (*reinterpret_cast<unsigned char*>(ud.pc) == 0xCC) break; // int 3
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
}
|
85
src/client/steam/interface.hpp
Normal file
85
src/client/steam/interface.hpp
Normal file
@ -0,0 +1,85 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef interface
|
||||
#undef interface
|
||||
#endif
|
||||
|
||||
namespace steam
|
||||
{
|
||||
struct raw_steam_id final
|
||||
{
|
||||
unsigned int account_id : 32;
|
||||
unsigned int account_instance : 20;
|
||||
unsigned int account_type : 4;
|
||||
int universe : 8;
|
||||
};
|
||||
|
||||
typedef union
|
||||
{
|
||||
raw_steam_id raw;
|
||||
unsigned long long bits;
|
||||
} steam_id;
|
||||
|
||||
#pragma pack( push, 1 )
|
||||
struct raw_game_id final
|
||||
{
|
||||
unsigned int app_id : 24;
|
||||
unsigned int type : 8;
|
||||
unsigned int mod_id : 32;
|
||||
};
|
||||
|
||||
typedef union
|
||||
{
|
||||
raw_game_id raw;
|
||||
unsigned long long bits;
|
||||
} game_id;
|
||||
#pragma pack( pop )
|
||||
|
||||
class interface final
|
||||
{
|
||||
public:
|
||||
|
||||
interface();
|
||||
interface(void* interface_ptr);
|
||||
|
||||
operator bool() const;
|
||||
|
||||
template <typename T, typename... Args>
|
||||
T invoke(const std::string& method_name, Args ... args)
|
||||
{
|
||||
if (!this->interface_ptr_)
|
||||
{
|
||||
throw std::runtime_error("Invalid interface pointer");
|
||||
}
|
||||
|
||||
const auto method = this->find_method(method_name);
|
||||
if (!method)
|
||||
{
|
||||
throw std::runtime_error("Unable to find method: " + method_name);
|
||||
}
|
||||
|
||||
return static_cast<T(__thiscall*)(void*, Args ...)>(method)(this->interface_ptr_, args...);
|
||||
}
|
||||
|
||||
template <typename T, typename... Args>
|
||||
T invoke(const size_t table_entry, Args ... args)
|
||||
{
|
||||
if (!this->interface_ptr_)
|
||||
{
|
||||
throw std::runtime_error("Invalid interface pointer");
|
||||
}
|
||||
|
||||
return static_cast<T(__thiscall*)(void*, Args ...)>((*this->interface_ptr_)[table_entry])(
|
||||
this->interface_ptr_, args...);
|
||||
}
|
||||
|
||||
private:
|
||||
void*** interface_ptr_;
|
||||
std::unordered_map<std::string, void*> methods_;
|
||||
|
||||
void* find_method(const std::string& name);
|
||||
void* search_method(const std::string& name);
|
||||
|
||||
std::string analyze_method(const void* method_ptr);
|
||||
};
|
||||
}
|
100
src/runner/resource.rc
Normal file
100
src/runner/resource.rc
Normal file
@ -0,0 +1,100 @@
|
||||
// Microsoft Visual C++ generated resource script.
|
||||
//
|
||||
#pragma code_page(65001)
|
||||
|
||||
#define APSTUDIO_READONLY_SYMBOLS
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Generated from the TEXTINCLUDE 2 resource.
|
||||
//
|
||||
#include "windows.h"
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
#undef APSTUDIO_READONLY_SYMBOLS
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
// English (United States) resources
|
||||
|
||||
#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)
|
||||
LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
|
||||
|
||||
#ifdef APSTUDIO_INVOKED
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// TEXTINCLUDE
|
||||
//
|
||||
|
||||
1 TEXTINCLUDE
|
||||
BEGIN
|
||||
"#include ""windows.h""\r\n"
|
||||
"\0"
|
||||
END
|
||||
|
||||
2 TEXTINCLUDE
|
||||
BEGIN
|
||||
"\r\n"
|
||||
"\0"
|
||||
END
|
||||
|
||||
#endif // APSTUDIO_INVOKED
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Version
|
||||
//
|
||||
|
||||
VS_VERSION_INFO VERSIONINFO
|
||||
FILEVERSION 1,0,0,0
|
||||
PRODUCTVERSION 1,0,0,0
|
||||
FILEFLAGSMASK 0x3fL
|
||||
#ifdef _DEBUG
|
||||
FILEFLAGS 0x1L
|
||||
#else
|
||||
FILEFLAGS 0x0L
|
||||
#endif
|
||||
FILEOS 0x40004L
|
||||
FILETYPE VFT_DLL
|
||||
FILESUBTYPE 0x0L
|
||||
BEGIN
|
||||
BLOCK "StringFileInfo"
|
||||
BEGIN
|
||||
BLOCK "040904b0"
|
||||
BEGIN
|
||||
VALUE "CompanyName", "X Labs"
|
||||
VALUE "FileDescription", "Steam mod runner"
|
||||
VALUE "FileVersion", "1.0.0.0"
|
||||
VALUE "InternalName", "Runner"
|
||||
VALUE "LegalCopyright", "All rights reserved."
|
||||
VALUE "OriginalFilename", "runner.exe"
|
||||
VALUE "ProductName", "runner"
|
||||
VALUE "ProductVersion", "1.0.0.0"
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
BEGIN
|
||||
VALUE "Translation", 0x409, 1200
|
||||
END
|
||||
END
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Binary Data
|
||||
//
|
||||
|
||||
102 ICON "../client/resources/icon.ico"
|
||||
|
||||
#endif // English (United States) resources
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
|
||||
#ifndef APSTUDIO_INVOKED
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Generated from the TEXTINCLUDE 3 resource.
|
||||
//
|
||||
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
#endif // not APSTUDIO_INVOKED
|
||||
|
23
src/runner/runner.cpp
Normal file
23
src/runner/runner.cpp
Normal file
@ -0,0 +1,23 @@
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <Windows.h>
|
||||
#include <cstdlib>
|
||||
|
||||
int __stdcall WinMain(HINSTANCE, HINSTANCE, PSTR, int)
|
||||
{
|
||||
const auto* const command = "-proc ";
|
||||
const char* parent_proc = strstr(GetCommandLineA(), command);
|
||||
|
||||
if (parent_proc)
|
||||
{
|
||||
const auto pid = DWORD(atoi(parent_proc + strlen(command)));
|
||||
auto* const process_handle = OpenProcess(SYNCHRONIZE, FALSE, pid);
|
||||
if (process_handle)
|
||||
{
|
||||
WaitForSingleObject(process_handle, INFINITE);
|
||||
CloseHandle(process_handle);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
Loading…
Reference in New Issue
Block a user