#include #include "loader/component_loader.hpp" #include "scheduler.hpp" #include #include #include #include "game/game.hpp" #include "steam/interface.hpp" #include "steam/steam.hpp" namespace steam_proxy { namespace { enum class ownership_state { success, unowned, nosteam, error, }; ownership_state state_; utils::binary_resource runner_file(RUNNER, "runner.exe"); } class component final : public component_interface { public: void post_load() override { if (game::environment::is_dedi()) { return; } this->load_client(); this->clean_up_on_error(); #ifndef DEV_BUILD try { const std::string game_type = game::environment::is_sp() ? "singleplayer" : "multiplayer"; state_ = this->start_mod("\xF0\x9F\x92\x8E" " iw6-mod: " + (game_type), game::environment::is_sp() ? 209160 : 209170); } catch (const std::exception& ex) { state_ = ownership_state::error; printf("Steam: %s\n", ex.what()); } #endif (void)state_; } void pre_destroy() override { if (this->steam_client_module_) { if (this->steam_pipe_) { if (this->global_user_) { this->steam_client_module_.invoke("Steam_ReleaseUser", this->steam_pipe_, this->global_user_); } this->steam_client_module_.invoke("Steam_BReleaseSteamPipe", this->steam_pipe_); } } } #ifdef LOAD_STEAM_OVERLAY [[nodiscard]] const utils::nt::library& get_overlay_module() const { return steam_overlay_module_; } #endif private: utils::nt::library steam_client_module_{}; #ifdef LOAD_STEAM_OVERLAY utils::nt::library steam_overlay_module_{}; #endif steam::interface client_engine_{}; steam::interface client_user_{}; steam::interface client_utils_{}; void* steam_pipe_ = nullptr; void* global_user_ = nullptr; [[nodiscard]] void* load_client_engine() const { if (!this->steam_client_module_) return nullptr; for (auto i = 1; i < 1000; ++i) { const auto* name = utils::string::va("CLIENTENGINE_INTERFACE_VERSION%03i", i); auto* const client_engine = this->steam_client_module_.invoke("CreateInterface", name, 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"); #ifdef LOAD_STEAM_OVERLAY this->steam_overlay_module_ = utils::nt::library::load(steam_path / "gameoverlayrenderer64.dll"); #endif 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("Steam_CreateSteamPipe"); this->global_user_ = this->steam_client_module_.invoke("Steam_ConnectToGlobalUser", this->steam_pipe_); this->client_user_ = this->client_engine_.invoke(8, this->steam_pipe_, this->global_user_); // GetIClientUser this->client_utils_ = this->client_engine_.invoke(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("BIsSubscribedApp", app_id)) { #ifdef _DEBUG app_id = 480; // Spacewar #else return ownership_state::unowned; #endif } this->client_utils_.invoke("SetAppIDForCurrentPipe", app_id, false); char our_directory[MAX_PATH]{}; GetCurrentDirectoryA(sizeof(our_directory), our_directory); const auto path = runner_file.get_extracted_file(); const auto* 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 = "iw6-mod"; game_id.raw.mod_id = *reinterpret_cast(mod_id) | 0x80000000; this->client_user_.invoke("SpawnProcess", path.data(), cmdline, 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("Steam_BConnected", this->global_user_, this->steam_pipe_) && this->steam_client_module_.invoke("Steam_BLoggedOn", this->global_user_, this->steam_pipe_) ) { return scheduler::cond_continue; } this->do_cleanup(); return scheduler::cond_end; }); } }; } REGISTER_COMPONENT(steam_proxy::component)