2022-09-26 12:39:01 -04:00
|
|
|
#include <std_include.hpp>
|
|
|
|
#include "loader/component_loader.hpp"
|
|
|
|
|
|
|
|
#include <utils/nt.hpp>
|
|
|
|
#include <utils/flags.hpp>
|
|
|
|
#include <utils/string.hpp>
|
2022-09-30 13:19:58 -04:00
|
|
|
#include <utils/finally.hpp>
|
|
|
|
#include <utils/concurrency.hpp>
|
2022-09-26 12:39:01 -04:00
|
|
|
|
|
|
|
#include "steam/interface.hpp"
|
|
|
|
#include "steam/steam.hpp"
|
|
|
|
|
2022-11-21 13:08:27 -05:00
|
|
|
#include "steam_proxy.hpp"
|
|
|
|
#include "scheduler.hpp"
|
|
|
|
|
2022-09-26 12:39:01 -04:00
|
|
|
namespace steam_proxy
|
|
|
|
{
|
|
|
|
namespace
|
|
|
|
{
|
2022-10-01 05:08:42 -04:00
|
|
|
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_item_map> subscribed_items;
|
|
|
|
|
2022-09-26 12:39:01 -04:00
|
|
|
enum class ownership_state
|
|
|
|
{
|
|
|
|
success,
|
|
|
|
unowned,
|
|
|
|
nosteam,
|
|
|
|
error,
|
|
|
|
};
|
|
|
|
|
|
|
|
bool is_disabled()
|
|
|
|
{
|
|
|
|
static const auto disabled = utils::flags::has_flag("nosteam");
|
|
|
|
return disabled;
|
|
|
|
}
|
2022-09-30 13:19:58 -04:00
|
|
|
|
2022-10-01 05:08:42 -04:00
|
|
|
void* load_client_engine()
|
2022-09-30 13:19:58 -04:00
|
|
|
{
|
2022-10-01 05:08:42 -04:00
|
|
|
if (!steam_client_module) return nullptr;
|
2022-09-26 12:39:01 -04:00
|
|
|
|
2022-12-04 05:09:08 -05:00
|
|
|
for (auto i = 1; i <= 999; ++i)
|
2022-09-26 12:39:01 -04:00
|
|
|
{
|
|
|
|
std::string name = utils::string::va("CLIENTENGINE_INTERFACE_VERSION%03i", i);
|
2022-10-01 05:08:42 -04:00
|
|
|
auto* const temp_client_engine = steam_client_module
|
2023-01-01 15:46:36 -05:00
|
|
|
.invoke<void*>("CreateInterface", name.data(), nullptr);
|
2022-10-01 05:08:42 -04:00
|
|
|
if (temp_client_engine) return temp_client_engine;
|
2022-09-26 12:39:01 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
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");
|
2022-10-01 05:08:42 -04:00
|
|
|
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<void*>("Steam_CreateSteamPipe");
|
|
|
|
global_user = steam_client_module.invoke<void*>(
|
2023-01-01 15:46:36 -05:00
|
|
|
"Steam_ConnectToGlobalUser", steam_pipe);
|
2022-10-01 05:08:42 -04:00
|
|
|
|
|
|
|
client_user = client_engine.invoke<void*>(8, global_user, steam_pipe);
|
|
|
|
client_utils = client_engine.invoke<void*>(14, steam_pipe);
|
|
|
|
client_friends = client_engine.invoke<void*>(13, global_user, steam_pipe);
|
2022-11-20 10:41:03 -05:00
|
|
|
client_ugc = client_engine.invoke<void*>(62, global_user, steam_pipe);
|
2022-09-26 12:39:01 -04:00
|
|
|
}
|
|
|
|
|
2022-10-01 05:08:42 -04:00
|
|
|
void do_cleanup()
|
2022-09-26 12:39:01 -04:00
|
|
|
{
|
2022-10-01 05:08:42 -04:00
|
|
|
client_engine = nullptr;
|
|
|
|
client_user = nullptr;
|
|
|
|
client_utils = nullptr;
|
2022-10-01 12:52:15 -04:00
|
|
|
client_friends = nullptr;
|
|
|
|
client_ugc = nullptr;
|
2022-10-01 05:08:42 -04:00
|
|
|
|
|
|
|
steam_pipe = nullptr;
|
|
|
|
global_user = nullptr;
|
|
|
|
|
|
|
|
steam_client_module = utils::nt::library{nullptr};
|
|
|
|
}
|
|
|
|
|
2022-10-01 12:52:15 -04:00
|
|
|
bool perform_cleanup_if_needed()
|
|
|
|
{
|
|
|
|
if (steam_client_module
|
|
|
|
&& steam_pipe
|
|
|
|
&& global_user
|
|
|
|
&& steam_client_module.invoke<bool>("Steam_BConnected", global_user,
|
|
|
|
steam_pipe)
|
|
|
|
&& steam_client_module.invoke<bool>("Steam_BLoggedOn", global_user, steam_pipe)
|
|
|
|
)
|
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
do_cleanup();
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2022-10-01 05:08:42 -04:00
|
|
|
void clean_up_on_error()
|
|
|
|
{
|
|
|
|
scheduler::schedule([]
|
2022-09-26 12:39:01 -04:00
|
|
|
{
|
2022-10-01 12:52:15 -04:00
|
|
|
if (perform_cleanup_if_needed())
|
2022-10-01 05:08:42 -04:00
|
|
|
{
|
2022-10-01 12:52:15 -04:00
|
|
|
return scheduler::cond_end;
|
2022-10-01 05:08:42 -04:00
|
|
|
}
|
|
|
|
|
2022-10-01 12:52:15 -04:00
|
|
|
return scheduler::cond_continue;
|
|
|
|
}, scheduler::main);
|
2022-09-26 12:39:01 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
ownership_state start_mod_unsafe(const std::string& title, size_t app_id)
|
|
|
|
{
|
2022-10-01 05:08:42 -04:00
|
|
|
if (!client_utils || !client_user)
|
2022-09-26 12:39:01 -04:00
|
|
|
{
|
|
|
|
return ownership_state::nosteam;
|
|
|
|
}
|
|
|
|
|
2022-10-01 05:08:42 -04:00
|
|
|
if (!client_user.invoke<bool>("BIsSubscribedApp", app_id))
|
2022-09-26 12:39:01 -04:00
|
|
|
{
|
2022-12-04 05:09:08 -05:00
|
|
|
#ifdef DEV_BUILD
|
|
|
|
app_id = 480; // Spacewar
|
|
|
|
#else
|
2022-09-26 12:39:01 -04:00
|
|
|
return ownership_state::unowned;
|
2022-12-04 05:09:08 -05:00
|
|
|
#endif
|
2022-09-26 12:39:01 -04:00
|
|
|
}
|
|
|
|
|
2022-09-26 13:00:22 -04:00
|
|
|
if (is_disabled())
|
|
|
|
{
|
|
|
|
return ownership_state::success;
|
|
|
|
}
|
|
|
|
|
2022-10-01 05:08:42 -04:00
|
|
|
client_utils.invoke<void>("SetAppIDForCurrentPipe", app_id, false);
|
2022-09-26 12:39:01 -04:00
|
|
|
|
|
|
|
char our_directory[MAX_PATH] = {0};
|
|
|
|
GetCurrentDirectoryA(sizeof(our_directory), our_directory);
|
|
|
|
|
2022-11-11 10:21:24 -05:00
|
|
|
const auto self = utils::nt::library::get_by_address(start_mod_unsafe);
|
|
|
|
const auto path = self.get_path();
|
2022-12-04 05:09:08 -05:00
|
|
|
const auto* cmdline = utils::string::va("\"%s\" -proc %d", path.generic_string().data(),
|
|
|
|
GetCurrentProcessId());
|
2022-09-26 12:39:01 -04:00
|
|
|
|
|
|
|
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;
|
|
|
|
|
2022-11-21 13:35:15 -05:00
|
|
|
client_user.invoke<bool>("SpawnProcess", path.generic_string().data(), cmdline, our_directory,
|
2022-10-01 05:08:42 -04:00
|
|
|
&game_id.bits, title.data(), 0, 0, 0);
|
2022-09-26 12:39:01 -04:00
|
|
|
|
|
|
|
return ownership_state::success;
|
|
|
|
}
|
|
|
|
|
2022-10-01 05:08:42 -04:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
2022-12-04 05:09:08 -05:00
|
|
|
|
|
|
|
void evaluate_ownership_state(const ownership_state state)
|
|
|
|
{
|
|
|
|
#ifdef DEV_BUILD
|
|
|
|
(void)state;
|
|
|
|
#else
|
|
|
|
switch (state)
|
|
|
|
{
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
}
|
2022-10-01 05:08:42 -04:00
|
|
|
}
|
|
|
|
|
2023-02-05 13:53:48 -05:00
|
|
|
struct component final : client_component
|
2022-10-01 05:08:42 -04:00
|
|
|
{
|
2022-11-09 14:19:08 -05:00
|
|
|
void post_load() override
|
2022-09-26 12:39:01 -04:00
|
|
|
{
|
2022-10-01 05:08:42 -04:00
|
|
|
load_client();
|
2022-10-01 14:19:32 -04:00
|
|
|
perform_cleanup_if_needed();
|
2022-10-01 05:08:42 -04:00
|
|
|
}
|
2022-09-26 12:39:01 -04:00
|
|
|
|
2022-10-01 05:08:42 -04:00
|
|
|
void post_unpack() override
|
|
|
|
{
|
2022-12-04 05:09:08 -05:00
|
|
|
const auto res = start_mod("\xE2\x98\x84\xEF\xB8\x8F" " BOIII"s, steam::SteamUtils()->GetAppID());
|
|
|
|
evaluate_ownership_state(res);
|
2022-10-01 14:19:32 -04:00
|
|
|
clean_up_on_error();
|
2022-09-26 12:39:01 -04:00
|
|
|
}
|
|
|
|
|
2022-10-01 05:08:42 -04:00
|
|
|
void pre_destroy() override
|
2022-09-26 12:39:01 -04:00
|
|
|
{
|
2022-10-01 05:08:42 -04:00
|
|
|
if (steam_client_module && steam_pipe)
|
2022-09-26 12:39:01 -04:00
|
|
|
{
|
2022-10-01 05:08:42 -04:00
|
|
|
if (global_user)
|
2022-09-26 12:39:01 -04:00
|
|
|
{
|
2022-10-01 05:08:42 -04:00
|
|
|
steam_client_module.invoke<void>("Steam_ReleaseUser", steam_pipe,
|
|
|
|
global_user);
|
2022-09-26 12:39:01 -04:00
|
|
|
}
|
|
|
|
|
2022-10-01 05:08:42 -04:00
|
|
|
steam_client_module.invoke<bool>("Steam_BReleaseSteamPipe", steam_pipe);
|
|
|
|
}
|
2022-09-26 12:39:01 -04:00
|
|
|
}
|
2022-10-01 14:42:40 -04:00
|
|
|
|
2022-11-11 11:00:34 -05:00
|
|
|
component_priority priority() const override
|
2022-10-01 14:42:40 -04:00
|
|
|
{
|
2022-11-11 11:00:34 -05:00
|
|
|
return component_priority::steam_proxy;
|
2022-10-01 14:42:40 -04:00
|
|
|
}
|
2022-09-26 12:39:01 -04:00
|
|
|
};
|
|
|
|
|
|
|
|
const utils::nt::library& get_overlay_module()
|
|
|
|
{
|
2022-10-01 05:08:42 -04:00
|
|
|
return steam_overlay_module;
|
2022-09-26 12:39:01 -04:00
|
|
|
}
|
2022-09-29 12:15:40 -04:00
|
|
|
|
|
|
|
const char* get_player_name()
|
|
|
|
{
|
2022-10-01 05:08:42 -04:00
|
|
|
if (client_friends)
|
|
|
|
{
|
|
|
|
return client_friends.invoke<const char*>("GetPersonaName");
|
|
|
|
}
|
|
|
|
|
|
|
|
return "boiii";
|
2022-09-29 12:15:40 -04:00
|
|
|
}
|
2022-09-30 13:19:58 -04:00
|
|
|
|
|
|
|
void update_subscribed_items()
|
|
|
|
{
|
2022-10-01 05:08:42 -04:00
|
|
|
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<uint32_t>("GetNumSubscribedItems", app_id);
|
|
|
|
|
|
|
|
if (!num_items)
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
std::vector<uint64_t> ids;
|
|
|
|
ids.resize(num_items);
|
|
|
|
|
|
|
|
auto result = client_ugc.invoke<uint32_t>("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<uint32_t>("GetItemState", app_id, ids[i]);
|
|
|
|
item.available = client_ugc.invoke<bool>("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 = {};
|
|
|
|
}
|
2022-09-30 13:19:58 -04:00
|
|
|
}
|
|
|
|
|
2022-10-01 05:08:42 -04:00
|
|
|
void access_subscribed_items(
|
2023-01-01 15:46:36 -05:00
|
|
|
const std::function<void(const subscribed_item_map&)>& callback)
|
2022-09-30 13:19:58 -04:00
|
|
|
{
|
2022-10-01 05:08:42 -04:00
|
|
|
subscribed_items.access(callback);
|
2022-09-30 13:19:58 -04:00
|
|
|
}
|
2022-09-26 12:39:01 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
REGISTER_COMPONENT(steam_proxy::component)
|