#include #include "loader/component_loader.hpp" #include #include #include #include #include #include "steam/interface.hpp" #include "steam/steam.hpp" #include "steam_proxy.hpp" #include "scheduler.hpp" namespace steam_proxy { namespace { utils::nt::library steam_client_module{}; utils::nt::library steam_overlay_module{}; void* steam_pipe = nullptr; void* global_user = nullptr; steam::interface client_engine{}; steam::interface client_user{}; steam::interface client_utils{}; steam::interface client_friends{}; steam::interface client_ugc{}; utils::concurrency::container subscribed_items; enum class ownership_state { success, unowned, nosteam, error, }; bool is_disabled() { static const auto disabled = utils::flags::has_flag("nosteam"); return disabled; } void* load_client_engine() { if (!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 temp_client_engine = steam_client_module .invoke("CreateInterface", name.data(), nullptr); if (temp_client_engine) return temp_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"); steam_overlay_module = utils::nt::library::load(steam_path / "gameoverlayrenderer64.dll"); steam_client_module = utils::nt::library::load(steam_path / "steamclient64.dll"); if (!steam_client_module) return; client_engine = load_client_engine(); if (!client_engine) return; steam_pipe = steam_client_module.invoke("Steam_CreateSteamPipe"); global_user = steam_client_module.invoke( "Steam_ConnectToGlobalUser", steam_pipe); client_user = client_engine.invoke(8, global_user, steam_pipe); client_utils = client_engine.invoke(14, steam_pipe); client_friends = client_engine.invoke(13, global_user, steam_pipe); client_ugc = client_engine.invoke(62, global_user, steam_pipe); } void do_cleanup() { client_engine = nullptr; client_user = nullptr; client_utils = nullptr; client_friends = nullptr; client_ugc = nullptr; steam_pipe = nullptr; global_user = nullptr; steam_client_module = utils::nt::library{nullptr}; } bool perform_cleanup_if_needed() { if (steam_client_module && steam_pipe && global_user && steam_client_module.invoke("Steam_BConnected", global_user, steam_pipe) && steam_client_module.invoke("Steam_BLoggedOn", global_user, steam_pipe) ) { return false; } do_cleanup(); return true; } void clean_up_on_error() { scheduler::schedule([] { if (perform_cleanup_if_needed()) { return scheduler::cond_end; } return scheduler::cond_continue; }, scheduler::main); } ownership_state start_mod_unsafe(const std::string& title, size_t app_id) { if (!client_utils || !client_user) { return ownership_state::nosteam; } if (!client_user.invoke("BIsSubscribedApp", app_id)) { //app_id = 480; // Spacewar return ownership_state::unowned; } if (is_disabled()) { return ownership_state::success; } client_utils.invoke("SetAppIDForCurrentPipe", app_id, false); char our_directory[MAX_PATH] = {0}; GetCurrentDirectoryA(sizeof(our_directory), our_directory); const auto self = utils::nt::library::get_by_address(start_mod_unsafe); const auto path = self.get_path(); const auto* cmdline = utils::string::va("\"%s\" -proc %d", path.generic_string().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(mod_id) | 0x80000000; client_user.invoke("SpawnProcess", path.generic_string().data(), cmdline, our_directory, &game_id.bits, title.data(), 0, 0, 0); return ownership_state::success; } ownership_state start_mod(const std::string& title, const size_t app_id) { __try { return start_mod_unsafe(title, app_id); } __except (EXCEPTION_EXECUTE_HANDLER) { do_cleanup(); return ownership_state::error; } } } class component final : public component_interface { public: void post_load() override { load_client(); perform_cleanup_if_needed(); } void post_unpack() override { try { const auto res = 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(nullptr, e.what(), "BOIII Error", MB_ICONERROR | MB_SETFOREGROUND | MB_TOPMOST); TerminateProcess(GetCurrentProcess(), 1234); } clean_up_on_error(); } void pre_destroy() override { if (steam_client_module && steam_pipe) { if (global_user) { steam_client_module.invoke("Steam_ReleaseUser", steam_pipe, global_user); } steam_client_module.invoke("Steam_BReleaseSteamPipe", steam_pipe); } } component_priority priority() const override { return component_priority::steam_proxy; } }; const utils::nt::library& get_overlay_module() { return steam_overlay_module; } const char* get_player_name() { if (client_friends) { return client_friends.invoke("GetPersonaName"); } return "boiii"; } void update_subscribed_items() { subscribed_item_map map{}; const auto _ = utils::finally([&] { subscribed_items.access([&](subscribed_item_map& items) { items = std::move(map); }); }); if (!client_ugc) { return; } try { const auto app_id = steam::SteamUtils()->GetAppID(); const auto num_items = client_ugc.invoke("GetNumSubscribedItems", app_id); if (!num_items) { return; } std::vector ids; ids.resize(num_items); auto result = client_ugc.invoke("GetSubscribedItems", app_id, ids.data(), num_items); result = std::min(num_items, result); for (uint32_t i = 0; i < result; ++i) { char buffer[0x1000] = {0}; subscribed_item item{}; item.state = client_ugc.invoke("GetItemState", app_id, ids[i]); item.available = client_ugc.invoke("GetItemInstallInfo", app_id, ids[i], &item.size_on_disk, buffer, sizeof(buffer), &item.time_stamp); item.path = buffer; map[ids[i]] = std::move(item); } } catch (...) { client_ugc = {}; } } void access_subscribed_items( const std::function& callback) { subscribed_items.access(callback); } } REGISTER_COMPONENT(steam_proxy::component)