Discord in game join requests

This commit is contained in:
Federico Cecchetto
2022-04-11 11:05:21 +02:00
parent 691df3f607
commit f65a942897
13 changed files with 514 additions and 62 deletions

View File

@ -7,12 +7,25 @@
#include "command.hpp"
#include "network.hpp"
#include "party.hpp"
#include "materials.hpp"
#include "ui_scripting.hpp"
#include "game/ui_scripting/execution.hpp"
#include <utils/string.hpp>
#include <utils/cryptography.hpp>
#include <utils/http.hpp>
#include <discord_rpc.h>
#define DEFAULT_AVATAR "discord_default_avatar"
#define AVATAR "discord_avatar_%s"
#define DEFAULT_AVATAR_URL "https://cdn.discordapp.com/embed/avatars/0.png"
#define AVATAR_URL "https://cdn.discordapp.com/avatars/%s/%s.png?size=128"
#include "discord.hpp"
namespace discord
{
namespace
@ -21,8 +34,6 @@ namespace discord
void update_discord()
{
Discord_RunCallbacks();
if (!game::CL_IsCgameInitialized() || game::VirtualLobby_Loaded())
{
discord_presence.details = game::environment::is_sp() ? "Singleplayer" : "Multiplayer";
@ -105,6 +116,51 @@ namespace discord
Discord_UpdatePresence(&discord_presence);
}
void download_user_avatar(const std::string& id, const std::string& avatar)
{
const auto data = utils::http::get_data(
utils::string::va(AVATAR_URL, id.data(), avatar.data()));
if (data.has_value())
{
materials::add(utils::string::va(AVATAR, id.data()), data.value());
}
}
bool has_default_avatar = false;
void download_default_avatar()
{
const auto data = utils::http::get_data(DEFAULT_AVATAR_URL);
if (data.has_value())
{
has_default_avatar = true;
materials::add(DEFAULT_AVATAR, data.value());
}
}
}
std::string get_avatar_material(const std::string& id)
{
const auto avatar_name = utils::string::va(AVATAR, id.data());
if (materials::exists(avatar_name))
{
return avatar_name;
}
if (has_default_avatar)
{
return DEFAULT_AVATAR;
}
return "black";
}
void respond(const std::string& id, int reply)
{
scheduler::once([=]()
{
Discord_Respond(id.data(), reply);
}, scheduler::pipeline::async);
}
class component final : public component_interface
@ -122,16 +178,19 @@ namespace discord
handlers.ready = ready;
handlers.errored = errored;
handlers.disconnected = errored;
handlers.joinGame = joinGame;
handlers.joinGame = join_game;
handlers.spectateGame = nullptr;
handlers.joinRequest = joinRequest;
handlers.joinRequest = join_request;
Discord_Initialize("947125042930667530", &handlers, 1, nullptr);
scheduler::once(download_default_avatar, scheduler::pipeline::async);
scheduler::once([]()
{
scheduler::once(update_discord, scheduler::pipeline::async);
scheduler::loop(update_discord, scheduler::pipeline::async, 5s);
scheduler::loop(Discord_RunCallbacks, scheduler::pipeline::async, 1s);
}, scheduler::pipeline::main);
initialized_ = true;
@ -153,11 +212,8 @@ namespace discord
static void ready(const DiscordUser* request)
{
ZeroMemory(&discord_presence, sizeof(discord_presence));
discord_presence.instance = 1;
console::info("Discord: Ready on %s (%s)\n", request->username, request->userId);
Discord_UpdatePresence(&discord_presence);
}
@ -166,25 +222,55 @@ namespace discord
console::error("Discord: Error (%i): %s\n", error_code, message);
}
static void joinGame(const char* joinSecret)
static void join_game(const char* join_secret)
{
console::info("Discord: Join game called with join secret: %s\n", joinSecret);
console::info("Discord: Join game called with join secret: %s\n", join_secret);
scheduler::once([joinSecret]()
std::string secret = join_secret;
scheduler::once([=]()
{
game::netadr_s target{};
if (game::NET_StringToAdr(joinSecret, &target))
if (game::NET_StringToAdr(secret.data(), &target))
{
console::info("Discord: Connecting to server: %s\n", joinSecret);
console::info("Discord: Connecting to server: %s\n", secret.data());
party::connect(target);
}
}, scheduler::pipeline::main);
}
static void joinRequest(const DiscordUser* request)
static void join_request(const DiscordUser* request)
{
console::info("Discord: joinRequest from %s (%s)\n", request->username, request->userId);
// Discord_Respond(request->userId, DISCORD_REPLY_YES);
console::info("Discord: join_request from %s (%s)\n", request->username, request->userId);
if (game::Com_InFrontend() || !ui_scripting::lui_running())
{
Discord_Respond(request->userId, DISCORD_REPLY_IGNORE);
return;
}
std::string user_id = request->userId;
std::string avatar = request->avatar;
std::string discriminator = request->discriminator;
std::string username = request->username;
scheduler::once([=]()
{
const ui_scripting::table request_table{};
request_table.set("avatar", avatar);
request_table.set("discriminator", discriminator);
request_table.set("userid", user_id);
request_table.set("username", username);
ui_scripting::notify("discord_join_request",
{
{"request", request_table}
});
}, scheduler::pipeline::lui);
if (!materials::exists(utils::string::va(AVATAR, user_id.data())))
{
download_user_avatar(user_id, avatar);
}
}
};
}

View File

@ -0,0 +1,7 @@
#pragma once
namespace discord
{
std::string get_avatar_material(const std::string& id);
void respond(const std::string& id, int reply);
}

View File

@ -4,6 +4,7 @@
#include "game/game.hpp"
#include "game_console.hpp"
#include "ui_scripting.hpp"
#include "game/ui_scripting/execution.hpp"
#include <utils/hook.hpp>
@ -15,14 +16,10 @@ namespace input
utils::hook::detour cl_char_event_hook;
utils::hook::detour cl_key_event_hook;
bool lui_running()
{
return *game::hks::lua_state != nullptr;
}
void cl_char_event_stub(const int local_client_num, const int key)
{
if (lui_running())
if (ui_scripting::lui_running())
{
ui_scripting::notify("keypress",
{
@ -41,7 +38,7 @@ namespace input
void cl_key_event_stub(const int local_client_num, const int key, const int down)
{
if (lui_running())
if (ui_scripting::lui_running())
{
ui_scripting::notify(down ? "keydown" : "keyup",
{

View File

@ -166,6 +166,14 @@ namespace materials
});
}
bool exists(const std::string& name)
{
return material_data.access<bool>([&](material_data_t& data_)
{
return data_.images.find(name) != data_.images.end();
});
}
void clear()
{
material_data.access([&](material_data_t& data_)

View File

@ -3,5 +3,6 @@
namespace materials
{
void add(const std::string& name, const std::string& data);
bool exists(const std::string& name);
void clear();
}

View File

@ -88,6 +88,7 @@ namespace scheduler
utils::hook::detour r_end_frame_hook;
utils::hook::detour g_run_frame_hook;
utils::hook::detour main_frame_hook;
utils::hook::detour hks_frame_hook;
void execute(const pipeline type)
{
@ -112,6 +113,15 @@ namespace scheduler
main_frame_hook.invoke<void>();
execute(pipeline::main);
}
void hks_frame_stub()
{
const auto state = *game::hks::lua_state;
if (state)
{
execute(pipeline::lui);
}
}
}
void schedule(const std::function<bool()>& callback, const pipeline type,
@ -183,6 +193,7 @@ namespace scheduler
r_end_frame_hook.create(SELECT_VALUE(0x1404F7310, 0x1405FE470), scheduler::r_end_frame_stub);
g_run_frame_hook.create(SELECT_VALUE(0x1402772D0, 0x14033A640), scheduler::server_frame_stub);
main_frame_hook.create(SELECT_VALUE(0x1401CE8D0, 0x1400D8310), scheduler::main_frame_stub);
hks_frame_hook.create(SELECT_VALUE(0x1400E37F0, 0x1401755B0), scheduler::hks_frame_stub);
}
void pre_destroy() override

View File

@ -16,6 +16,9 @@ namespace scheduler
// The game's main thread
main,
// LUI context
lui,
count,
};

View File

@ -93,6 +93,7 @@ namespace ui_scripting
void hks_shutdown_stub()
{
converted_functions.clear();
ui_scripting::lua::engine::stop();
hks_shutdown_hook.invoke<void*>();
}
@ -107,15 +108,6 @@ namespace ui_scripting
return hks_allocator_hook.invoke<void*>(userData, oldMemory, oldSize, newSize);
}
void hks_frame_stub()
{
const auto state = *game::hks::lua_state;
if (state)
{
ui_scripting::lua::engine::run_frame();
}
}
}
int main_function_handler(game::hks::lua_State* state)
@ -132,7 +124,7 @@ namespace ui_scripting
return 0;
}
const auto function = converted_functions[closure];
const auto& function = converted_functions[closure];
const auto count = static_cast<int>(state->m_apistack.top - state->m_apistack.base);
const auto arguments = get_return_values(count);
const auto s = function.lua_state();
@ -175,6 +167,11 @@ namespace ui_scripting
error_hook_enabled = false;
}
bool lui_running()
{
return *game::hks::lua_state != nullptr;
}
class component final : public component_interface
{
public:
@ -186,11 +183,12 @@ namespace ui_scripting
return;
}
scheduler::loop(ui_scripting::lua::engine::run_frame, scheduler::pipeline::lui);
hks_start_hook.create(SELECT_VALUE(0x1400E4B40, 0x140176A40), hks_start_stub);
hks_shutdown_hook.create(SELECT_VALUE(0x1400DD3D0, 0x14016CA80), hks_shutdown_stub);
hksi_lual_error_hook.create(SELECT_VALUE(0x1400A5EA0, 0x14012F300), hksi_lual_error_stub);
hks_allocator_hook.create(SELECT_VALUE(0x14009B570, 0x14012BAC0), hks_allocator_stub);
hks_frame_hook.create(SELECT_VALUE(0x1400E37F0, 0x1401755B0), hks_frame_stub);
lui_error_hook.create(SELECT_VALUE(0x14007D7D0, 0x14010C9E0), lui_error_stub);
hksi_hks_error_hook.create(SELECT_VALUE(0x14009DD80, 0x14012E390), hksi_hks_error_stub);

View File

@ -9,4 +9,6 @@ namespace ui_scripting
void enable_error_hook();
void disable_error_hook();
bool lui_running();
}