2022-02-27 20:54:27 -08:00
|
|
|
#include <std_include.hpp>
|
|
|
|
#include "loader/component_loader.hpp"
|
|
|
|
|
|
|
|
#include "party.hpp"
|
|
|
|
#include "console.hpp"
|
|
|
|
#include "command.hpp"
|
|
|
|
#include "network.hpp"
|
|
|
|
#include "scheduler.hpp"
|
|
|
|
#include "server_list.hpp"
|
2022-09-11 08:07:19 -05:00
|
|
|
#include "download.hpp"
|
2022-10-11 05:11:54 +02:00
|
|
|
#include "fastfiles.hpp"
|
2022-12-22 11:33:44 -06:00
|
|
|
#include "mods.hpp"
|
2022-09-11 08:07:19 -05:00
|
|
|
|
|
|
|
#include "game/game.hpp"
|
2022-10-27 21:48:00 -05:00
|
|
|
#include "game/ui_scripting/execution.hpp"
|
2022-02-27 20:54:27 -08:00
|
|
|
|
|
|
|
#include "steam/steam.hpp"
|
|
|
|
|
2022-12-21 15:39:59 -06:00
|
|
|
#include <utils/properties.hpp>
|
2022-02-27 20:54:27 -08:00
|
|
|
#include <utils/string.hpp>
|
|
|
|
#include <utils/info_string.hpp>
|
|
|
|
#include <utils/cryptography.hpp>
|
|
|
|
#include <utils/hook.hpp>
|
2022-09-11 08:07:19 -05:00
|
|
|
#include <utils/io.hpp>
|
2022-02-27 20:54:27 -08:00
|
|
|
|
|
|
|
namespace party
|
|
|
|
{
|
|
|
|
namespace
|
|
|
|
{
|
|
|
|
struct
|
|
|
|
{
|
|
|
|
game::netadr_s host{};
|
|
|
|
std::string challenge{};
|
|
|
|
bool hostDefined{false};
|
|
|
|
} connect_state;
|
|
|
|
|
|
|
|
std::string sv_motd;
|
|
|
|
int sv_maxclients;
|
|
|
|
|
2022-11-11 15:54:32 +01:00
|
|
|
struct usermap_file
|
|
|
|
{
|
|
|
|
std::string extension;
|
|
|
|
std::string name;
|
|
|
|
bool optional;
|
|
|
|
};
|
|
|
|
|
|
|
|
std::vector<usermap_file> usermap_files =
|
|
|
|
{
|
|
|
|
{".ff", "usermaphash", false},
|
|
|
|
{"_load.ff", "usermaploadhash", true},
|
|
|
|
{".arena", "usermaparenahash", true},
|
|
|
|
};
|
|
|
|
|
2022-12-21 14:24:36 -06:00
|
|
|
struct
|
|
|
|
{
|
|
|
|
game::netadr_s host{};
|
|
|
|
utils::info_string info_string{};
|
|
|
|
} saved_info_response;
|
2022-10-27 21:48:00 -05:00
|
|
|
|
2022-02-27 20:54:27 -08:00
|
|
|
void perform_game_initialization()
|
|
|
|
{
|
|
|
|
command::execute("onlinegame 1", true);
|
|
|
|
command::execute("xstartprivateparty", true);
|
|
|
|
command::execute("xblive_privatematch 1", true);
|
|
|
|
command::execute("startentitlements", true);
|
|
|
|
}
|
|
|
|
|
|
|
|
void connect_to_party(const game::netadr_s& target, const std::string& mapname, const std::string& gametype)
|
|
|
|
{
|
|
|
|
if (game::environment::is_sp())
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (game::Live_SyncOnlineDataFlags(0) != 0)
|
|
|
|
{
|
|
|
|
// initialize the game after onlinedataflags is 32 (workaround)
|
|
|
|
if (game::Live_SyncOnlineDataFlags(0) == 32)
|
|
|
|
{
|
|
|
|
scheduler::once([=]()
|
|
|
|
{
|
|
|
|
command::execute("xstartprivateparty", true);
|
|
|
|
command::execute("disconnect", true); // 32 -> 0
|
|
|
|
|
|
|
|
connect_to_party(target, mapname, gametype);
|
|
|
|
}, scheduler::pipeline::main, 1s);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
scheduler::once([=]()
|
|
|
|
{
|
|
|
|
connect_to_party(target, mapname, gametype);
|
|
|
|
}, scheduler::pipeline::main, 1s);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
perform_game_initialization();
|
|
|
|
|
2022-10-11 00:10:24 +02:00
|
|
|
if (game::VirtualLobby_Loaded())
|
|
|
|
{
|
|
|
|
// exit from virtuallobby
|
|
|
|
utils::hook::invoke<void>(0x13C9C0_b, 1);
|
|
|
|
}
|
2022-02-27 20:54:27 -08:00
|
|
|
|
2022-10-11 05:11:54 +02:00
|
|
|
if (!fastfiles::is_stock_map(mapname))
|
|
|
|
{
|
|
|
|
fastfiles::set_usermap(mapname);
|
|
|
|
}
|
|
|
|
|
2022-02-27 20:54:27 -08:00
|
|
|
// CL_ConnectFromParty
|
|
|
|
char session_info[0x100] = {};
|
2022-05-19 00:36:32 +02:00
|
|
|
utils::hook::invoke<void>(0x12DFF0_b, 0, session_info, &target, mapname.data(), gametype.data());
|
2022-02-27 20:54:27 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
std::string get_dvar_string(const std::string& dvar)
|
|
|
|
{
|
|
|
|
auto* dvar_value = game::Dvar_FindVar(dvar.data());
|
|
|
|
if (dvar_value && dvar_value->current.string)
|
|
|
|
{
|
|
|
|
return dvar_value->current.string;
|
|
|
|
}
|
|
|
|
|
|
|
|
return {};
|
|
|
|
}
|
|
|
|
|
|
|
|
int get_dvar_int(const std::string& dvar)
|
|
|
|
{
|
|
|
|
auto* dvar_value = game::Dvar_FindVar(dvar.data());
|
|
|
|
if (dvar_value && dvar_value->current.integer)
|
|
|
|
{
|
|
|
|
return dvar_value->current.integer;
|
|
|
|
}
|
|
|
|
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool get_dvar_bool(const std::string& dvar)
|
|
|
|
{
|
|
|
|
auto* dvar_value = game::Dvar_FindVar(dvar.data());
|
|
|
|
if (dvar_value && dvar_value->current.enabled)
|
|
|
|
{
|
|
|
|
return dvar_value->current.enabled;
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2022-05-28 12:51:26 -05:00
|
|
|
const char* get_didyouknow_stub(void* table, int row, int column)
|
2022-02-27 20:54:27 -08:00
|
|
|
{
|
2022-05-28 15:51:38 +02:00
|
|
|
if (party::sv_motd.empty())
|
2022-02-27 20:54:27 -08:00
|
|
|
{
|
2022-05-28 12:51:26 -05:00
|
|
|
return utils::hook::invoke<const char*>(0x5A0AC0_b, table, row, column);
|
2022-02-27 20:54:27 -08:00
|
|
|
}
|
2022-05-28 12:51:26 -05:00
|
|
|
return utils::string::va("%s", party::sv_motd.data());
|
2022-02-27 20:54:27 -08:00
|
|
|
}
|
|
|
|
|
2022-05-21 12:26:30 +02:00
|
|
|
void disconnect()
|
2022-02-27 20:54:27 -08:00
|
|
|
{
|
|
|
|
if (!game::VirtualLobby_Loaded())
|
|
|
|
{
|
|
|
|
if (game::CL_IsCgameInitialized())
|
|
|
|
{
|
2022-05-21 12:26:30 +02:00
|
|
|
// CL_AddReliableCommand
|
|
|
|
utils::hook::invoke<void>(0x12B810_b, 0, "disconnect");
|
2022-02-27 20:54:27 -08:00
|
|
|
// CL_WritePacket
|
2022-05-21 12:26:30 +02:00
|
|
|
utils::hook::invoke<void>(0x13D490_b, 0);
|
2022-02-27 20:54:27 -08:00
|
|
|
}
|
|
|
|
// CL_Disconnect
|
2022-05-21 12:26:30 +02:00
|
|
|
utils::hook::invoke<void>(0x12F080_b, 0);
|
2022-02-27 20:54:27 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-05-21 12:26:30 +02:00
|
|
|
utils::hook::detour cl_disconnect_hook;
|
2022-02-27 20:54:27 -08:00
|
|
|
|
2022-10-12 23:40:17 +02:00
|
|
|
void cl_disconnect_stub(int show_main_menu) // possibly bool
|
2022-02-27 20:54:27 -08:00
|
|
|
{
|
2022-03-11 22:28:08 +01:00
|
|
|
party::clear_sv_motd();
|
2022-10-12 23:40:17 +02:00
|
|
|
if (!game::VirtualLobby_Loaded())
|
|
|
|
{
|
|
|
|
fastfiles::clear_usermap();
|
|
|
|
}
|
|
|
|
cl_disconnect_hook.invoke<void>(show_main_menu);
|
2022-02-27 20:54:27 -08:00
|
|
|
}
|
|
|
|
|
2022-12-22 13:21:30 +01:00
|
|
|
std::unordered_map<std::string, std::string> hash_cache;
|
|
|
|
|
2022-11-11 15:54:32 +01:00
|
|
|
std::string get_file_hash(const std::string& file)
|
2022-10-11 05:11:54 +02:00
|
|
|
{
|
|
|
|
if (!utils::io::file_exists(file))
|
|
|
|
{
|
|
|
|
return {};
|
|
|
|
}
|
|
|
|
|
2022-12-22 13:21:30 +01:00
|
|
|
const auto iter = hash_cache.find(file);
|
|
|
|
if (iter != hash_cache.end())
|
|
|
|
{
|
|
|
|
return iter->second;
|
|
|
|
}
|
|
|
|
|
2022-10-11 05:11:54 +02:00
|
|
|
const auto data = utils::io::read_file(file);
|
|
|
|
const auto sha = utils::cryptography::sha1::compute(data, true);
|
2022-12-22 13:21:30 +01:00
|
|
|
hash_cache[file] = sha;
|
2022-11-11 15:54:32 +01:00
|
|
|
return sha;
|
|
|
|
}
|
|
|
|
|
|
|
|
std::string get_usermap_file_path(const std::string& mapname, const std::string& extension)
|
|
|
|
{
|
|
|
|
return utils::string::va("usermaps\\%s\\%s%s", mapname.data(), mapname.data(), extension.data());
|
2022-10-11 05:11:54 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
void check_download_map(const utils::info_string& info, std::vector<download::file_t>& files)
|
|
|
|
{
|
|
|
|
const auto mapname = info.get("mapname");
|
2022-10-11 18:29:53 +02:00
|
|
|
if (fastfiles::is_stock_map(mapname))
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
2022-10-11 05:11:54 +02:00
|
|
|
|
2022-10-11 18:29:53 +02:00
|
|
|
if (mapname.contains('.') || mapname.contains("::"))
|
2022-10-11 05:11:54 +02:00
|
|
|
{
|
2022-10-11 18:29:53 +02:00
|
|
|
throw std::runtime_error(utils::string::va("Invalid server mapname value %s\n", mapname.data()));
|
|
|
|
}
|
2022-10-11 05:11:54 +02:00
|
|
|
|
2022-11-11 15:54:32 +01:00
|
|
|
const auto check_file = [&](const std::string& ext, const std::string& name, bool optional)
|
2022-10-11 18:29:53 +02:00
|
|
|
{
|
2022-11-11 15:54:32 +01:00
|
|
|
const std::string filename = utils::string::va("usermaps/%s/%s%s", mapname.data(), mapname.data(), ext.data());
|
|
|
|
const auto source_hash = info.get(name);
|
2022-10-11 05:11:54 +02:00
|
|
|
if (source_hash.empty())
|
|
|
|
{
|
2022-10-11 18:29:53 +02:00
|
|
|
if (!optional)
|
|
|
|
{
|
2022-11-11 15:54:32 +01:00
|
|
|
throw std::runtime_error(utils::string::va("Server %s is empty", name.data()));
|
2022-10-11 18:29:53 +02:00
|
|
|
}
|
|
|
|
|
2022-10-11 05:11:54 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
const auto hash = get_file_hash(filename);
|
2022-11-11 15:54:32 +01:00
|
|
|
if (hash != source_hash)
|
2022-10-11 05:11:54 +02:00
|
|
|
{
|
|
|
|
files.emplace_back(filename, source_hash);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2022-11-11 15:54:32 +01:00
|
|
|
for (const auto& [ext, name, opt] : usermap_files)
|
|
|
|
{
|
|
|
|
check_file(ext, name, opt);
|
|
|
|
}
|
2022-10-11 05:11:54 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
bool check_download_mod(const utils::info_string& info, std::vector<download::file_t>& files)
|
2022-02-28 18:11:09 +01:00
|
|
|
{
|
2022-11-11 15:54:32 +01:00
|
|
|
static const auto fs_game = game::Dvar_FindVar("fs_game");
|
|
|
|
const auto client_fs_game = utils::string::to_lower(fs_game->current.string);
|
2022-09-11 08:07:19 -05:00
|
|
|
const auto server_fs_game = utils::string::to_lower(info.get("fs_game"));
|
2022-11-11 15:54:32 +01:00
|
|
|
|
|
|
|
if (server_fs_game.empty() && client_fs_game.empty())
|
2022-09-11 20:50:12 +02:00
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2022-11-11 15:54:32 +01:00
|
|
|
if (server_fs_game.empty() && !client_fs_game.empty())
|
|
|
|
{
|
2022-12-29 12:08:46 -06:00
|
|
|
mods::set_mod("");
|
2022-11-11 15:54:32 +01:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2022-10-11 05:11:54 +02:00
|
|
|
if (!server_fs_game.starts_with("mods/") || server_fs_game.contains('.') || server_fs_game.contains("::"))
|
2022-09-11 08:07:19 -05:00
|
|
|
{
|
2022-10-11 05:11:54 +02:00
|
|
|
throw std::runtime_error(utils::string::va("Invalid server fs_game value %s\n", server_fs_game.data()));
|
2022-09-11 08:07:19 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
const auto source_hash = info.get("modHash");
|
|
|
|
if (source_hash.empty())
|
|
|
|
{
|
2022-10-11 05:11:54 +02:00
|
|
|
throw std::runtime_error("Connection failed: Server mod hash is empty.");
|
2022-09-11 08:07:19 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
const auto mod_path = server_fs_game + "/mod.ff";
|
|
|
|
auto has_to_download = !utils::io::file_exists(mod_path);
|
2022-10-11 05:11:54 +02:00
|
|
|
|
2022-09-11 08:07:19 -05:00
|
|
|
if (!has_to_download)
|
|
|
|
{
|
|
|
|
const auto data = utils::io::read_file(mod_path);
|
|
|
|
const auto hash = utils::cryptography::sha1::compute(data, true);
|
|
|
|
|
|
|
|
has_to_download = source_hash != hash;
|
|
|
|
}
|
2022-10-11 05:11:54 +02:00
|
|
|
|
|
|
|
if (has_to_download)
|
2022-09-11 08:07:19 -05:00
|
|
|
{
|
2022-10-11 05:11:54 +02:00
|
|
|
files.emplace_back(mod_path, source_hash);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
else if (client_fs_game != server_fs_game)
|
|
|
|
{
|
2022-12-22 11:33:44 -06:00
|
|
|
mods::set_mod(server_fs_game);
|
2022-10-11 05:11:54 +02:00
|
|
|
return true;
|
2022-09-11 08:07:19 -05:00
|
|
|
}
|
|
|
|
|
2022-10-11 05:11:54 +02:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2022-10-27 21:48:00 -05:00
|
|
|
void close_joining_popups()
|
|
|
|
{
|
|
|
|
if (game::Menu_IsMenuOpenAndVisible(0, "popup_acceptinginvite"))
|
|
|
|
{
|
|
|
|
command::execute("lui_close popup_acceptinginvite", false);
|
|
|
|
}
|
|
|
|
if (game::Menu_IsMenuOpenAndVisible(0, "generic_waiting_popup_"))
|
|
|
|
{
|
|
|
|
command::execute("lui_close generic_waiting_popup_", false);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-12-21 15:39:59 -06:00
|
|
|
std::string get_whitelist_json_path()
|
|
|
|
{
|
|
|
|
return (utils::properties::get_appdata_path() / "whitelist.json").generic_string();
|
|
|
|
}
|
|
|
|
|
2022-12-21 15:46:02 -06:00
|
|
|
nlohmann::json get_whitelist_json_object()
|
2022-12-21 15:39:59 -06:00
|
|
|
{
|
|
|
|
std::string data;
|
|
|
|
if (!utils::io::read_file(get_whitelist_json_path(), &data))
|
|
|
|
{
|
2022-12-21 15:46:02 -06:00
|
|
|
return nullptr;
|
2022-12-21 15:39:59 -06:00
|
|
|
}
|
|
|
|
|
2022-12-21 15:46:02 -06:00
|
|
|
nlohmann::json obj;
|
2022-12-21 15:39:59 -06:00
|
|
|
try
|
|
|
|
{
|
2022-12-21 15:46:02 -06:00
|
|
|
obj = nlohmann::json::parse(data.data());
|
2022-12-21 15:39:59 -06:00
|
|
|
}
|
|
|
|
catch (const nlohmann::json::parse_error& ex)
|
|
|
|
{
|
|
|
|
menu_error(utils::string::va("%s\n", ex.what()));
|
2022-12-21 15:46:02 -06:00
|
|
|
return nullptr;
|
2022-12-21 15:39:59 -06:00
|
|
|
}
|
|
|
|
|
2022-12-21 15:46:02 -06:00
|
|
|
return obj;
|
2022-12-21 15:39:59 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
std::string target_ip_to_string(const game::netadr_s& target)
|
|
|
|
{
|
|
|
|
return utils::string::va("%i.%i.%i.%i",
|
|
|
|
static_cast<int>(saved_info_response.host.ip[0]),
|
|
|
|
static_cast<int>(saved_info_response.host.ip[1]),
|
|
|
|
static_cast<int>(saved_info_response.host.ip[2]),
|
|
|
|
static_cast<int>(saved_info_response.host.ip[3]));
|
|
|
|
}
|
|
|
|
|
2022-12-21 15:41:45 -06:00
|
|
|
bool download_files(const game::netadr_s& target, const utils::info_string& info, bool allow_download);
|
|
|
|
|
2022-12-22 08:22:52 -06:00
|
|
|
bool should_user_confirm(const game::netadr_s& target)
|
2022-10-27 21:48:00 -05:00
|
|
|
{
|
2022-12-21 15:46:02 -06:00
|
|
|
nlohmann::json obj = get_whitelist_json_object();
|
2022-12-22 07:27:28 -06:00
|
|
|
if (obj != nullptr)
|
2022-12-21 15:39:59 -06:00
|
|
|
{
|
2022-12-22 07:27:28 -06:00
|
|
|
const auto target_ip = target_ip_to_string(target);
|
|
|
|
for (const auto& [key, value] : obj.items())
|
2022-12-21 15:39:59 -06:00
|
|
|
{
|
2022-12-22 07:27:28 -06:00
|
|
|
if (value.is_string() && value.get<std::string>() == target_ip)
|
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
2022-12-21 15:39:59 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-12-21 14:24:36 -06:00
|
|
|
close_joining_popups();
|
2022-12-22 08:22:52 -06:00
|
|
|
command::execute("lui_open_popup popup_confirmdownload", false);
|
2022-12-21 15:39:59 -06:00
|
|
|
|
|
|
|
return true;
|
2022-10-27 21:48:00 -05:00
|
|
|
}
|
|
|
|
|
2022-10-11 05:11:54 +02:00
|
|
|
bool needs_vid_restart = false;
|
|
|
|
|
2022-12-21 14:24:36 -06:00
|
|
|
bool download_files(const game::netadr_s& target, const utils::info_string& info, bool allow_download)
|
2022-10-11 05:11:54 +02:00
|
|
|
{
|
|
|
|
try
|
2022-09-11 08:07:19 -05:00
|
|
|
{
|
2022-10-11 05:11:54 +02:00
|
|
|
std::vector<download::file_t> files{};
|
2022-09-11 08:07:19 -05:00
|
|
|
|
2022-10-11 05:11:54 +02:00
|
|
|
const auto needs_restart = check_download_mod(info, files);
|
|
|
|
needs_vid_restart = needs_vid_restart || needs_restart;
|
|
|
|
check_download_map(info, files);
|
2022-09-11 08:07:19 -05:00
|
|
|
|
2022-10-11 05:11:54 +02:00
|
|
|
if (files.size() > 0)
|
|
|
|
{
|
2022-12-22 08:22:52 -06:00
|
|
|
if (!allow_download && should_user_confirm(target))
|
2022-12-21 14:34:58 -06:00
|
|
|
{
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2022-12-21 14:24:36 -06:00
|
|
|
download::stop_download();
|
|
|
|
download::start_download(target, info, files);
|
2022-10-11 05:11:54 +02:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
else if (needs_restart || needs_vid_restart)
|
|
|
|
{
|
|
|
|
command::execute("vid_restart");
|
|
|
|
needs_vid_restart = false;
|
|
|
|
scheduler::once([=]()
|
|
|
|
{
|
|
|
|
connect(target);
|
|
|
|
}, scheduler::pipeline::main);
|
|
|
|
return true;
|
|
|
|
}
|
2022-09-11 08:07:19 -05:00
|
|
|
}
|
2022-10-11 05:11:54 +02:00
|
|
|
catch (const std::exception& e)
|
2022-09-11 08:07:19 -05:00
|
|
|
{
|
2022-10-11 05:11:54 +02:00
|
|
|
menu_error(e.what());
|
2022-10-12 23:40:17 +02:00
|
|
|
return true;
|
2022-10-11 05:11:54 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
2022-09-11 08:07:19 -05:00
|
|
|
|
2022-10-11 05:11:54 +02:00
|
|
|
void set_new_map(const char* mapname, const char* gametype, game::msg_t* msg)
|
|
|
|
{
|
2022-11-11 15:54:32 +01:00
|
|
|
if (game::SV_Loaded() || fastfiles::is_stock_map(mapname))
|
|
|
|
{
|
|
|
|
utils::hook::invoke<void>(0x13AAD0_b, mapname, gametype);
|
|
|
|
return;
|
|
|
|
}
|
2022-10-11 05:11:54 +02:00
|
|
|
|
2022-12-21 23:33:16 +01:00
|
|
|
fastfiles::set_usermap(mapname);
|
|
|
|
|
2022-11-11 15:54:32 +01:00
|
|
|
for (const auto& [ext, key, opt] : usermap_files)
|
2022-10-11 05:11:54 +02:00
|
|
|
{
|
2022-11-11 15:54:32 +01:00
|
|
|
char buffer[0x100] = {0};
|
|
|
|
const std::string source_hash = game::MSG_ReadStringLine(msg,
|
|
|
|
buffer, static_cast<unsigned int>(sizeof(buffer)));
|
|
|
|
|
|
|
|
const auto path = get_usermap_file_path(mapname, ext);
|
|
|
|
const auto hash = get_file_hash(path);
|
|
|
|
|
|
|
|
if ((!source_hash.empty() && hash != source_hash) || (source_hash.empty() && !opt))
|
2022-09-11 08:07:19 -05:00
|
|
|
{
|
2022-10-11 05:11:54 +02:00
|
|
|
command::execute("disconnect");
|
|
|
|
scheduler::once([]
|
|
|
|
{
|
|
|
|
connect(connect_state.host);
|
|
|
|
}, scheduler::pipeline::main);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
2022-09-11 08:07:19 -05:00
|
|
|
|
2022-10-11 05:11:54 +02:00
|
|
|
utils::hook::invoke<void>(0x13AAD0_b, mapname, gametype);
|
|
|
|
}
|
|
|
|
|
|
|
|
void loading_new_map_cl_stub(utils::hook::assembler& a)
|
|
|
|
{
|
|
|
|
a.pushad64();
|
|
|
|
a.mov(r8, rdi);
|
|
|
|
a.call_aligned(set_new_map);
|
|
|
|
a.popad64();
|
|
|
|
|
|
|
|
a.mov(al, 1);
|
|
|
|
a.jmp(0x12FCAA_b);
|
|
|
|
}
|
|
|
|
|
|
|
|
std::string current_sv_mapname;
|
|
|
|
|
|
|
|
void sv_spawn_server_stub(const char* map, void* a2, void* a3, void* a4, void* a5)
|
|
|
|
{
|
|
|
|
if (!fastfiles::is_stock_map(map))
|
|
|
|
{
|
|
|
|
fastfiles::set_usermap(map);
|
2022-09-11 08:07:19 -05:00
|
|
|
}
|
|
|
|
|
2022-12-22 13:21:30 +01:00
|
|
|
hash_cache.clear();
|
2022-10-11 05:11:54 +02:00
|
|
|
current_sv_mapname = map;
|
|
|
|
utils::hook::invoke<void>(0x54BBB0_b, map, a2, a3, a4, a5);
|
|
|
|
}
|
|
|
|
|
|
|
|
utils::hook::detour net_out_of_band_print_hook;
|
|
|
|
void net_out_of_band_print_stub(game::netsrc_t sock, game::netadr_s* addr, const char* data)
|
|
|
|
{
|
2022-10-11 18:29:53 +02:00
|
|
|
if (!std::strstr(data, "loadingnewmap"))
|
2022-10-11 05:11:54 +02:00
|
|
|
{
|
|
|
|
return net_out_of_band_print_hook.invoke<void>(sock, addr, data);
|
|
|
|
}
|
2022-11-11 15:54:32 +01:00
|
|
|
|
|
|
|
std::string buffer{};
|
|
|
|
const auto line = [&](const std::string& data_)
|
|
|
|
{
|
|
|
|
buffer.append(data_);
|
|
|
|
buffer.append("\n");
|
|
|
|
};
|
|
|
|
|
|
|
|
const auto* sv_gametype = game::Dvar_FindVar("g_gametype");
|
|
|
|
line("loadingnewmap");
|
|
|
|
line(current_sv_mapname);
|
|
|
|
line(sv_gametype->current.string);
|
|
|
|
|
|
|
|
const auto add_hash = [&](const std::string extension)
|
|
|
|
{
|
|
|
|
const auto filename = get_usermap_file_path(current_sv_mapname, extension);
|
|
|
|
const auto hash = get_file_hash(filename);
|
|
|
|
line(hash);
|
|
|
|
};
|
2022-10-11 05:11:54 +02:00
|
|
|
|
|
|
|
const auto is_usermap = fastfiles::usermap_exists(current_sv_mapname);
|
2022-11-11 15:54:32 +01:00
|
|
|
for (const auto& [ext, key, opt] : usermap_files)
|
2022-10-11 05:11:54 +02:00
|
|
|
{
|
2022-11-11 15:54:32 +01:00
|
|
|
if (is_usermap)
|
|
|
|
{
|
|
|
|
add_hash(ext);
|
|
|
|
}
|
|
|
|
else
|
2022-10-11 05:11:54 +02:00
|
|
|
{
|
2022-11-11 15:54:32 +01:00
|
|
|
line("");
|
2022-10-11 05:11:54 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-11-11 15:54:32 +01:00
|
|
|
net_out_of_band_print_hook.invoke<void>(sock, addr, buffer.data());
|
2022-02-28 18:11:09 +01:00
|
|
|
}
|
2022-02-27 20:54:27 -08:00
|
|
|
}
|
|
|
|
|
2022-12-22 08:22:52 -06:00
|
|
|
std::string get_www_url()
|
|
|
|
{
|
|
|
|
return saved_info_response.info_string.get("sv_wwwBaseUrl");
|
|
|
|
}
|
|
|
|
|
|
|
|
void user_download_response(bool response)
|
|
|
|
{
|
|
|
|
if (!response)
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
nlohmann::json obj = get_whitelist_json_object();
|
|
|
|
if (obj == nullptr)
|
|
|
|
{
|
|
|
|
obj = {};
|
|
|
|
}
|
|
|
|
|
|
|
|
obj.push_back(target_ip_to_string(saved_info_response.host));
|
|
|
|
|
|
|
|
utils::io::write_file(get_whitelist_json_path(), obj.dump(4));
|
|
|
|
|
|
|
|
download_files(saved_info_response.host, saved_info_response.info_string, true);
|
|
|
|
}
|
|
|
|
|
2022-09-11 08:07:19 -05:00
|
|
|
void menu_error(const std::string& error)
|
|
|
|
{
|
|
|
|
console::error("%s\n", error.data());
|
|
|
|
|
2022-10-27 21:48:00 -05:00
|
|
|
close_joining_popups();
|
2022-10-11 05:11:54 +02:00
|
|
|
|
2022-09-11 16:26:11 +02:00
|
|
|
utils::hook::invoke<void>(0x17D770_b, error.data(), "MENU_NOTICE"); // Com_SetLocalizedErrorMessage
|
|
|
|
*reinterpret_cast<int*>(0x2ED2F78_b) = 1;
|
2022-09-11 08:07:19 -05:00
|
|
|
}
|
|
|
|
|
2022-03-11 22:28:08 +01:00
|
|
|
void clear_sv_motd()
|
|
|
|
{
|
|
|
|
party::sv_motd.clear();
|
|
|
|
}
|
|
|
|
|
2022-02-27 20:54:27 -08:00
|
|
|
int get_client_num_by_name(const std::string& name)
|
|
|
|
{
|
|
|
|
for (auto i = 0; !name.empty() && i < *game::mp::svs_numclients; ++i)
|
|
|
|
{
|
|
|
|
if (game::mp::g_entities[i].client)
|
|
|
|
{
|
|
|
|
char client_name[16] = {0};
|
|
|
|
strncpy_s(client_name, game::mp::g_entities[i].client->name, sizeof(client_name));
|
|
|
|
game::I_CleanStr(client_name);
|
|
|
|
|
|
|
|
if (client_name == name)
|
|
|
|
{
|
|
|
|
return i;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
void reset_connect_state()
|
|
|
|
{
|
|
|
|
connect_state = {};
|
|
|
|
}
|
|
|
|
|
|
|
|
int get_client_count()
|
|
|
|
{
|
|
|
|
auto count = 0;
|
2022-05-21 12:26:30 +02:00
|
|
|
const auto* svs_clients = *game::mp::svs_clients;
|
|
|
|
if (svs_clients == nullptr)
|
|
|
|
{
|
|
|
|
return count;
|
|
|
|
}
|
|
|
|
|
2022-02-27 20:54:27 -08:00
|
|
|
for (auto i = 0; i < *game::mp::svs_numclients; ++i)
|
|
|
|
{
|
2022-05-21 12:26:30 +02:00
|
|
|
if (svs_clients[i].header.state >= 1)
|
2022-02-27 20:54:27 -08:00
|
|
|
{
|
|
|
|
++count;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return count;
|
|
|
|
}
|
|
|
|
|
|
|
|
int get_bot_count()
|
|
|
|
{
|
|
|
|
auto count = 0;
|
2022-05-21 12:26:30 +02:00
|
|
|
const auto* svs_clients = *game::mp::svs_clients;
|
|
|
|
if (svs_clients == nullptr)
|
|
|
|
{
|
|
|
|
return count;
|
|
|
|
}
|
|
|
|
|
2022-02-27 20:54:27 -08:00
|
|
|
for (auto i = 0; i < *game::mp::svs_numclients; ++i)
|
|
|
|
{
|
2022-05-21 12:26:30 +02:00
|
|
|
if (svs_clients[i].header.state >= 1 &&
|
2022-02-27 20:54:27 -08:00
|
|
|
game::SV_BotIsBot(i))
|
|
|
|
{
|
|
|
|
++count;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return count;
|
|
|
|
}
|
|
|
|
|
|
|
|
void connect(const game::netadr_s& target)
|
|
|
|
{
|
|
|
|
if (game::environment::is_sp())
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
command::execute("lui_open_popup popup_acceptinginvite", false);
|
|
|
|
|
|
|
|
connect_state.host = target;
|
|
|
|
connect_state.challenge = utils::cryptography::random::get_challenge();
|
|
|
|
connect_state.hostDefined = true;
|
|
|
|
|
|
|
|
network::send(target, "getInfo", connect_state.challenge);
|
|
|
|
}
|
|
|
|
|
2022-03-15 18:59:03 -05:00
|
|
|
game::netadr_s get_state_host()
|
|
|
|
{
|
|
|
|
return connect_state.host;
|
|
|
|
}
|
|
|
|
|
2022-08-16 15:23:36 -07:00
|
|
|
void start_map(const std::string& mapname, bool dev)
|
2022-02-27 20:54:27 -08:00
|
|
|
{
|
|
|
|
if (game::Live_SyncOnlineDataFlags(0) > 32)
|
|
|
|
{
|
|
|
|
scheduler::once([=]()
|
|
|
|
{
|
2022-08-16 15:23:36 -07:00
|
|
|
start_map(mapname, dev);
|
2022-02-27 20:54:27 -08:00
|
|
|
}, scheduler::pipeline::main, 1s);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
if (!game::SV_MapExists(mapname.data()))
|
|
|
|
{
|
|
|
|
console::info("Map '%s' doesn't exist.\n", mapname.data());
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
auto* current_mapname = game::Dvar_FindVar("mapname");
|
|
|
|
if (current_mapname && utils::string::to_lower(current_mapname->current.string) ==
|
|
|
|
utils::string::to_lower(mapname) && (game::SV_Loaded() && !game::VirtualLobby_Loaded()))
|
|
|
|
{
|
|
|
|
console::info("Restarting map: %s\n", mapname.data());
|
|
|
|
command::execute("map_restart", false);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!game::environment::is_dedi())
|
|
|
|
{
|
|
|
|
if (game::SV_Loaded())
|
|
|
|
{
|
|
|
|
const auto* args = "Leave";
|
|
|
|
game::UI_RunMenuScript(0, &args);
|
|
|
|
}
|
|
|
|
|
|
|
|
perform_game_initialization();
|
|
|
|
}
|
|
|
|
|
|
|
|
console::info("Starting map: %s\n", mapname.data());
|
|
|
|
auto* gametype = game::Dvar_FindVar("g_gametype");
|
|
|
|
if (gametype && gametype->current.string)
|
|
|
|
{
|
|
|
|
command::execute(utils::string::va("ui_gametype %s", gametype->current.string), true);
|
|
|
|
}
|
|
|
|
command::execute(utils::string::va("ui_mapname %s", mapname.data()), true);
|
|
|
|
|
|
|
|
/*auto* maxclients = game::Dvar_FindVar("sv_maxclients");
|
|
|
|
if (maxclients)
|
|
|
|
{
|
|
|
|
command::execute(utils::string::va("ui_maxclients %i", maxclients->current.integer), true);
|
|
|
|
command::execute(utils::string::va("party_maxplayers %i", maxclients->current.integer), true);
|
|
|
|
}*/
|
|
|
|
|
2022-08-16 15:23:36 -07:00
|
|
|
command::execute((dev ? "sv_cheats 1" : "sv_cheats 0"), true);
|
|
|
|
|
2022-02-27 20:54:27 -08:00
|
|
|
const auto* args = "StartServer";
|
|
|
|
game::UI_RunMenuScript(0, &args);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
int server_client_count()
|
|
|
|
{
|
|
|
|
return party::sv_maxclients;
|
|
|
|
}
|
|
|
|
|
|
|
|
class component final : public component_interface
|
|
|
|
{
|
|
|
|
public:
|
|
|
|
void post_unpack() override
|
|
|
|
{
|
|
|
|
if (game::environment::is_sp())
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// detour CL_Disconnect to clear motd
|
2022-05-21 12:26:30 +02:00
|
|
|
cl_disconnect_hook.create(0x12F080_b, cl_disconnect_stub);
|
2022-02-27 20:54:27 -08:00
|
|
|
|
|
|
|
if (game::environment::is_mp())
|
|
|
|
{
|
|
|
|
// show custom drop reason
|
2022-05-28 15:51:38 +02:00
|
|
|
utils::hook::nop(0x12EF4E_b, 13);
|
|
|
|
utils::hook::jump(0x12EF4E_b, utils::hook::assemble([](utils::hook::assembler& a)
|
|
|
|
{
|
|
|
|
a.mov(rdx, rsi);
|
|
|
|
a.mov(ecx, 2);
|
|
|
|
a.jmp(0x12EF27_b);
|
|
|
|
}), true);
|
2022-05-21 12:26:30 +02:00
|
|
|
|
|
|
|
command::add("disconnect", disconnect);
|
2022-02-27 20:54:27 -08:00
|
|
|
}
|
2022-02-28 15:22:04 +01:00
|
|
|
|
2022-02-27 20:54:27 -08:00
|
|
|
// enable custom kick reason in GScr_KickPlayer
|
2022-05-21 12:26:30 +02:00
|
|
|
utils::hook::set<uint8_t>(0xE423D_b, 0xEB);
|
2022-02-27 20:54:27 -08:00
|
|
|
|
2022-05-28 15:51:38 +02:00
|
|
|
// allow custom didyouknow based on sv_motd
|
|
|
|
utils::hook::call(0x1A8A3A_b, get_didyouknow_stub);
|
|
|
|
|
2022-10-11 05:11:54 +02:00
|
|
|
// add usermaphash to loadingnewmap command
|
|
|
|
utils::hook::jump(0x12FA68_b, utils::hook::assemble(loading_new_map_cl_stub), true);
|
|
|
|
utils::hook::call(0x54CC98_b, sv_spawn_server_stub);
|
|
|
|
net_out_of_band_print_hook.create(game::NET_OutOfBandPrint, net_out_of_band_print_stub);
|
|
|
|
|
2022-02-27 20:54:27 -08:00
|
|
|
command::add("map", [](const command::params& argument)
|
|
|
|
{
|
|
|
|
if (argument.size() != 2)
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2022-08-16 15:23:36 -07:00
|
|
|
start_map(argument[1], false);
|
|
|
|
});
|
|
|
|
|
|
|
|
command::add("devmap", [](const command::params& argument)
|
|
|
|
{
|
|
|
|
if (argument.size() != 2)
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
party::start_map(argument[1], true);
|
2022-02-27 20:54:27 -08:00
|
|
|
});
|
|
|
|
|
|
|
|
command::add("map_restart", []()
|
|
|
|
{
|
|
|
|
if (!game::SV_Loaded() || game::VirtualLobby_Loaded())
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
2022-03-03 07:07:37 -06:00
|
|
|
|
2022-05-19 17:01:34 +02:00
|
|
|
*reinterpret_cast<int*>(0xB7B8E60_b) = 1; // sv_map_restart
|
|
|
|
*reinterpret_cast<int*>(0xB7B8E64_b) = 1; // sv_loadScripts
|
|
|
|
*reinterpret_cast<int*>(0xB7B8E68_b) = 0; // sv_migrate
|
|
|
|
|
|
|
|
utils::hook::invoke<void>(0x54BD50_b); // SV_CheckLoadGame
|
2022-02-27 20:54:27 -08:00
|
|
|
});
|
|
|
|
|
|
|
|
command::add("fast_restart", []()
|
|
|
|
{
|
|
|
|
if (game::SV_Loaded() && !game::VirtualLobby_Loaded())
|
|
|
|
{
|
|
|
|
game::SV_FastRestart(0);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
command::add("reconnect", [](const command::params& argument)
|
|
|
|
{
|
|
|
|
if (!connect_state.hostDefined)
|
|
|
|
{
|
|
|
|
console::info("Cannot connect to server.\n");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (game::CL_IsCgameInitialized())
|
|
|
|
{
|
|
|
|
command::execute("disconnect");
|
|
|
|
command::execute("reconnect");
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
connect(connect_state.host);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
command::add("connect", [](const command::params& argument)
|
|
|
|
{
|
|
|
|
if (argument.size() != 2)
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
game::netadr_s target{};
|
|
|
|
if (game::NET_StringToAdr(argument[1], &target))
|
|
|
|
{
|
|
|
|
connect(target);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
command::add("kickClient", [](const command::params& params)
|
|
|
|
{
|
|
|
|
if (params.size() < 2)
|
|
|
|
{
|
|
|
|
console::info("usage: kickClient <num>, <reason>(optional)\n");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!game::SV_Loaded() || game::VirtualLobby_Loaded())
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
std::string reason;
|
|
|
|
if (params.size() > 2)
|
|
|
|
{
|
|
|
|
reason = params.join(2);
|
|
|
|
}
|
|
|
|
if (reason.empty())
|
|
|
|
{
|
|
|
|
reason = "EXE_PLAYERKICKED";
|
|
|
|
}
|
|
|
|
|
|
|
|
const auto client_num = atoi(params.get(1));
|
|
|
|
if (client_num < 0 || client_num >= *game::mp::svs_numclients)
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
scheduler::once([client_num, reason]()
|
|
|
|
{
|
|
|
|
game::SV_KickClientNum(client_num, reason.data());
|
|
|
|
}, scheduler::pipeline::server);
|
|
|
|
});
|
|
|
|
|
|
|
|
command::add("kick", [](const command::params& params)
|
|
|
|
{
|
|
|
|
if (params.size() < 2)
|
|
|
|
{
|
|
|
|
console::info("usage: kick <name>, <reason>(optional)\n");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!game::SV_Loaded() || game::VirtualLobby_Loaded())
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
std::string reason;
|
|
|
|
if (params.size() > 2)
|
|
|
|
{
|
|
|
|
reason = params.join(2);
|
|
|
|
}
|
|
|
|
if (reason.empty())
|
|
|
|
{
|
|
|
|
reason = "EXE_PLAYERKICKED";
|
|
|
|
}
|
|
|
|
|
|
|
|
const std::string name = params.get(1);
|
|
|
|
if (name == "all"s)
|
|
|
|
{
|
|
|
|
for (auto i = 0; i < *game::mp::svs_numclients; ++i)
|
|
|
|
{
|
|
|
|
scheduler::once([i, reason]()
|
|
|
|
{
|
|
|
|
game::SV_KickClientNum(i, reason.data());
|
|
|
|
}, scheduler::pipeline::server);
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
const auto client_num = get_client_num_by_name(name);
|
|
|
|
if (client_num < 0 || client_num >= *game::mp::svs_numclients)
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
scheduler::once([client_num, reason]()
|
|
|
|
{
|
|
|
|
game::SV_KickClientNum(client_num, reason.data());
|
|
|
|
}, scheduler::pipeline::server);
|
|
|
|
});
|
|
|
|
|
|
|
|
scheduler::once([]()
|
|
|
|
{
|
|
|
|
const auto hash = game::generateHashValue("sv_sayName");
|
|
|
|
game::Dvar_RegisterString(hash, "sv_sayName", "console", game::DvarFlags::DVAR_FLAG_NONE);
|
|
|
|
}, scheduler::pipeline::main);
|
|
|
|
|
|
|
|
command::add("tell", [](const command::params& params)
|
|
|
|
{
|
|
|
|
if (params.size() < 3)
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
const auto client_num = atoi(params.get(1));
|
|
|
|
const auto message = params.join(2);
|
|
|
|
const auto* const name = game::Dvar_FindVar("sv_sayName")->current.string;
|
|
|
|
|
|
|
|
game::SV_GameSendServerCommand(client_num, game::SV_CMD_CAN_IGNORE,
|
|
|
|
utils::string::va("%c \"%s: %s\"", 84, name, message.data()));
|
2022-11-01 00:09:02 -05:00
|
|
|
console::info("%s -> %i: %s\n", name, client_num, message.data());
|
2022-02-27 20:54:27 -08:00
|
|
|
});
|
|
|
|
|
|
|
|
command::add("tellraw", [](const command::params& params)
|
|
|
|
{
|
|
|
|
if (params.size() < 3)
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
const auto client_num = atoi(params.get(1));
|
|
|
|
const auto message = params.join(2);
|
|
|
|
|
|
|
|
game::SV_GameSendServerCommand(client_num, game::SV_CMD_CAN_IGNORE,
|
|
|
|
utils::string::va("%c \"%s\"", 84, message.data()));
|
2022-11-01 00:09:02 -05:00
|
|
|
console::info("%i: %s\n", client_num, message.data());
|
2022-02-27 20:54:27 -08:00
|
|
|
});
|
|
|
|
|
|
|
|
command::add("say", [](const command::params& params)
|
|
|
|
{
|
|
|
|
if (params.size() < 2)
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
const auto message = params.join(1);
|
|
|
|
const auto* const name = game::Dvar_FindVar("sv_sayName")->current.string;
|
|
|
|
|
|
|
|
game::SV_GameSendServerCommand(
|
|
|
|
-1, game::SV_CMD_CAN_IGNORE, utils::string::va("%c \"%s: %s\"", 84, name, message.data()));
|
2022-11-01 00:09:02 -05:00
|
|
|
console::info("%s: %s\n", name, message.data());
|
2022-02-27 20:54:27 -08:00
|
|
|
});
|
|
|
|
|
|
|
|
command::add("sayraw", [](const command::params& params)
|
|
|
|
{
|
|
|
|
if (params.size() < 2)
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
const auto message = params.join(1);
|
|
|
|
|
|
|
|
game::SV_GameSendServerCommand(-1, game::SV_CMD_CAN_IGNORE,
|
|
|
|
utils::string::va("%c \"%s\"", 84, message.data()));
|
2022-11-01 00:09:02 -05:00
|
|
|
console::info("%s\n", message.data());
|
2022-02-27 20:54:27 -08:00
|
|
|
});
|
|
|
|
|
2022-11-01 00:09:02 -05:00
|
|
|
network::on("getInfo", [](const game::netadr_s& target, const std::string& data)
|
2022-02-27 20:54:27 -08:00
|
|
|
{
|
2022-10-11 05:11:54 +02:00
|
|
|
const auto mapname = get_dvar_string("mapname");
|
|
|
|
|
2022-11-01 00:09:02 -05:00
|
|
|
utils::info_string info;
|
|
|
|
info.set("challenge", data);
|
2022-02-28 15:22:04 +01:00
|
|
|
info.set("gamename", "H1");
|
2022-02-27 20:54:27 -08:00
|
|
|
info.set("hostname", get_dvar_string("sv_hostname"));
|
|
|
|
info.set("gametype", get_dvar_string("g_gametype"));
|
|
|
|
info.set("sv_motd", get_dvar_string("sv_motd"));
|
|
|
|
info.set("xuid", utils::string::va("%llX", steam::SteamUser()->GetSteamID().bits));
|
2022-10-11 05:11:54 +02:00
|
|
|
info.set("mapname", mapname);
|
2022-02-27 20:54:27 -08:00
|
|
|
info.set("isPrivate", get_dvar_string("g_password").empty() ? "0" : "1");
|
|
|
|
info.set("clients", utils::string::va("%i", get_client_count()));
|
|
|
|
info.set("bots", utils::string::va("%i", get_bot_count()));
|
|
|
|
info.set("sv_maxclients", utils::string::va("%i", *game::mp::svs_numclients));
|
|
|
|
info.set("protocol", utils::string::va("%i", PROTOCOL));
|
|
|
|
info.set("playmode", utils::string::va("%i", game::Com_GetCurrentCoDPlayMode()));
|
2022-03-01 03:16:31 +01:00
|
|
|
info.set("sv_running", utils::string::va("%i", get_dvar_bool("sv_running") && !game::VirtualLobby_Loaded()));
|
2022-02-27 20:54:27 -08:00
|
|
|
info.set("dedicated", utils::string::va("%i", get_dvar_bool("dedicated")));
|
2022-09-11 08:07:19 -05:00
|
|
|
info.set("sv_wwwBaseUrl", get_dvar_string("sv_wwwBaseUrl"));
|
|
|
|
|
2022-10-11 05:11:54 +02:00
|
|
|
if (!fastfiles::is_stock_map(mapname))
|
|
|
|
{
|
2022-11-11 15:54:32 +01:00
|
|
|
const auto add_hash = [&](const std::string& extension, const std::string& name)
|
2022-10-11 05:11:54 +02:00
|
|
|
{
|
2022-11-11 15:54:32 +01:00
|
|
|
const auto path = get_usermap_file_path(mapname, extension);
|
|
|
|
const auto hash = get_file_hash(path);
|
|
|
|
info.set(name, hash);
|
|
|
|
};
|
2022-10-11 05:11:54 +02:00
|
|
|
|
2022-11-11 15:54:32 +01:00
|
|
|
for (const auto& [ext, name, opt] : usermap_files)
|
2022-10-11 05:11:54 +02:00
|
|
|
{
|
2022-11-11 15:54:32 +01:00
|
|
|
add_hash(ext, name);
|
2022-10-11 05:11:54 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-09-11 08:07:19 -05:00
|
|
|
const auto fs_game = get_dvar_string("fs_game");
|
|
|
|
info.set("fs_game", fs_game);
|
|
|
|
|
2022-10-11 05:11:54 +02:00
|
|
|
if (!fs_game.empty())
|
2022-09-11 08:07:19 -05:00
|
|
|
{
|
2022-10-11 05:11:54 +02:00
|
|
|
const auto hash = get_file_hash(utils::string::va("%s/mod.ff", fs_game.data()));
|
2022-11-11 15:54:32 +01:00
|
|
|
info.set("modHash", hash);
|
2022-09-11 08:07:19 -05:00
|
|
|
}
|
2022-02-27 20:54:27 -08:00
|
|
|
|
|
|
|
network::send(target, "infoResponse", info.build(), '\n');
|
|
|
|
});
|
|
|
|
|
2022-11-01 00:09:02 -05:00
|
|
|
network::on("infoResponse", [](const game::netadr_s& target, const std::string& data)
|
2022-02-27 20:54:27 -08:00
|
|
|
{
|
2022-11-01 00:09:02 -05:00
|
|
|
const utils::info_string info(data);
|
2022-05-20 09:51:17 +02:00
|
|
|
server_list::handle_info_response(target, info);
|
2022-02-27 20:54:27 -08:00
|
|
|
|
|
|
|
if (connect_state.host != target)
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2022-12-21 14:24:36 -06:00
|
|
|
saved_info_response = {};
|
|
|
|
saved_info_response.host = target;
|
|
|
|
saved_info_response.info_string = info;
|
2022-10-27 21:48:00 -05:00
|
|
|
|
2022-02-27 20:54:27 -08:00
|
|
|
if (info.get("challenge") != connect_state.challenge)
|
|
|
|
{
|
2022-09-11 08:07:19 -05:00
|
|
|
menu_error("Connection failed: Invalid challenge.");
|
2022-02-27 20:54:27 -08:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
const auto gamename = info.get("gamename");
|
2022-02-28 15:22:04 +01:00
|
|
|
if (gamename != "H1"s)
|
2022-02-27 20:54:27 -08:00
|
|
|
{
|
2022-09-11 08:07:19 -05:00
|
|
|
menu_error("Connection failed: Invalid gamename.");
|
2022-02-27 20:54:27 -08:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
const auto playmode = info.get("playmode");
|
|
|
|
if (game::CodPlayMode(std::atoi(playmode.data())) != game::Com_GetCurrentCoDPlayMode())
|
|
|
|
{
|
2022-09-11 08:07:19 -05:00
|
|
|
menu_error("Connection failed: Invalid playmode.");
|
2022-02-27 20:54:27 -08:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
const auto sv_running = info.get("sv_running");
|
|
|
|
if (!std::atoi(sv_running.data()))
|
|
|
|
{
|
2022-09-11 08:07:19 -05:00
|
|
|
menu_error("Connection failed: Server not running.");
|
2022-02-27 20:54:27 -08:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
const auto mapname = info.get("mapname");
|
|
|
|
if (mapname.empty())
|
|
|
|
{
|
2022-09-11 08:07:19 -05:00
|
|
|
menu_error("Connection failed: Invalid map.");
|
2022-02-27 20:54:27 -08:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
const auto gametype = info.get("gametype");
|
|
|
|
if (gametype.empty())
|
|
|
|
{
|
2022-09-11 08:07:19 -05:00
|
|
|
menu_error("Connection failed: Invalid gametype.");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2022-10-27 21:48:00 -05:00
|
|
|
if (download_files(target, info, false))
|
2022-09-11 08:07:19 -05:00
|
|
|
{
|
2022-02-27 20:54:27 -08:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
party::sv_motd = info.get("sv_motd");
|
|
|
|
party::sv_maxclients = std::stoi(info.get("sv_maxclients"));
|
|
|
|
|
|
|
|
connect_to_party(target, mapname, gametype);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2022-05-19 00:36:32 +02:00
|
|
|
REGISTER_COMPONENT(party::component)
|