Merge pull request #513 from Brentdevent/mods
Mods support for dedicated servers
This commit is contained in:
commit
6c51aeb8b6
@ -5,6 +5,7 @@
|
|||||||
#include "steam/steam.hpp"
|
#include "steam/steam.hpp"
|
||||||
|
|
||||||
#include "network.hpp"
|
#include "network.hpp"
|
||||||
|
#include "workshop.hpp"
|
||||||
|
|
||||||
#include <utils/hook.hpp>
|
#include <utils/hook.hpp>
|
||||||
#include <utils/string.hpp>
|
#include <utils/string.hpp>
|
||||||
@ -114,6 +115,8 @@ namespace getinfo
|
|||||||
info.set("sv_running", std::to_string(game::is_server_running()));
|
info.set("sv_running", std::to_string(game::is_server_running()));
|
||||||
info.set("dedicated", game::is_server() ? "1" : "0");
|
info.set("dedicated", game::is_server() ? "1" : "0");
|
||||||
info.set("hc", std::to_string(game::Com_GametypeSettings_GetUInt("hardcoremode", false)));
|
info.set("hc", std::to_string(game::Com_GametypeSettings_GetUInt("hardcoremode", false)));
|
||||||
|
info.set("modname", workshop::get_mod_name(game::get_dvar_string("fs_game")));
|
||||||
|
info.set("fs_game", game::get_dvar_string("fs_game"));
|
||||||
info.set("shortversion", SHORTVERSION);
|
info.set("shortversion", SHORTVERSION);
|
||||||
|
|
||||||
network::send(target, "infoResponse", info.build(), '\n');
|
network::send(target, "infoResponse", info.build(), '\n');
|
||||||
|
@ -37,12 +37,12 @@ namespace party
|
|||||||
}
|
}
|
||||||
|
|
||||||
void connect_to_lobby(const game::netadr_t& addr, const std::string& mapname, const std::string& gamemode,
|
void connect_to_lobby(const game::netadr_t& addr, const std::string& mapname, const std::string& gamemode,
|
||||||
const std::string& pub_id)
|
const std::string& usermap_id, const std::string& mod_id)
|
||||||
{
|
{
|
||||||
workshop::load_usermap_mod_if_needed(pub_id);
|
workshop::load_mod_if_needed(usermap_id, mod_id);
|
||||||
|
|
||||||
game::XSESSION_INFO info{};
|
game::XSESSION_INFO info{};
|
||||||
game::CL_ConnectFromLobby(0, &info, &addr, 1, 0, mapname.data(), gamemode.data(), pub_id.data());
|
game::CL_ConnectFromLobby(0, &info, &addr, 1, 0, mapname.data(), gamemode.data(), usermap_id.data());
|
||||||
}
|
}
|
||||||
|
|
||||||
void launch_mode(const game::eModes mode)
|
void launch_mode(const game::eModes mode)
|
||||||
@ -56,12 +56,12 @@ namespace party
|
|||||||
}
|
}
|
||||||
|
|
||||||
void connect_to_lobby_with_mode(const game::netadr_t& addr, const game::eModes mode, const std::string& mapname,
|
void connect_to_lobby_with_mode(const game::netadr_t& addr, const game::eModes mode, const std::string& mapname,
|
||||||
const std::string& gametype, const std::string& pub_id,
|
const std::string& gametype, const std::string& usermap_id, const std::string& mod_id,
|
||||||
const bool was_retried = false)
|
const bool was_retried = false)
|
||||||
{
|
{
|
||||||
if (game::Com_SessionMode_IsMode(mode))
|
if (game::Com_SessionMode_IsMode(mode))
|
||||||
{
|
{
|
||||||
connect_to_lobby(addr, mapname, gametype, pub_id);
|
connect_to_lobby(addr, mapname, gametype, usermap_id, mod_id);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -69,7 +69,7 @@ namespace party
|
|||||||
{
|
{
|
||||||
scheduler::once([=]
|
scheduler::once([=]
|
||||||
{
|
{
|
||||||
connect_to_lobby_with_mode(addr, mode, mapname, gametype, pub_id, true);
|
connect_to_lobby_with_mode(addr, mode, mapname, gametype, usermap_id, mod_id, true);
|
||||||
}, scheduler::main, 5s);
|
}, scheduler::main, 5s);
|
||||||
|
|
||||||
launch_mode(mode);
|
launch_mode(mode);
|
||||||
@ -177,6 +177,8 @@ namespace party
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const auto mod_id = info.get("fs_game");
|
||||||
|
|
||||||
//const auto hostname = info.get("sv_hostname");
|
//const auto hostname = info.get("sv_hostname");
|
||||||
const auto playmode = info.get("playmode");
|
const auto playmode = info.get("playmode");
|
||||||
const auto mode = static_cast<game::eModes>(std::atoi(playmode.data()));
|
const auto mode = static_cast<game::eModes>(std::atoi(playmode.data()));
|
||||||
@ -184,9 +186,10 @@ namespace party
|
|||||||
|
|
||||||
scheduler::once([=]
|
scheduler::once([=]
|
||||||
{
|
{
|
||||||
const auto publisher_id = workshop::get_usermap_publisher_id(mapname);
|
const auto usermap_id = workshop::get_usermap_publisher_id(mapname);
|
||||||
|
|
||||||
if (workshop::check_valid_publisher_id(mapname, publisher_id))
|
if (workshop::check_valid_usermap_id(mapname, usermap_id) &&
|
||||||
|
workshop::check_valid_mod_id(mod_id))
|
||||||
{
|
{
|
||||||
if (is_connecting_to_dedi)
|
if (is_connecting_to_dedi)
|
||||||
{
|
{
|
||||||
@ -194,7 +197,7 @@ namespace party
|
|||||||
}
|
}
|
||||||
|
|
||||||
//connect_to_session(target, hostname, xuid, mode);
|
//connect_to_session(target, hostname, xuid, mode);
|
||||||
connect_to_lobby_with_mode(target, mode, mapname, gametype, publisher_id);
|
connect_to_lobby_with_mode(target, mode, mapname, gametype, usermap_id, mod_id);
|
||||||
}
|
}
|
||||||
}, scheduler::main);
|
}, scheduler::main);
|
||||||
}
|
}
|
||||||
|
@ -5,48 +5,39 @@
|
|||||||
#include "game/game.hpp"
|
#include "game/game.hpp"
|
||||||
|
|
||||||
#include <utils/hook.hpp>
|
#include <utils/hook.hpp>
|
||||||
|
#include <utils/string.hpp>
|
||||||
|
#include <utils/io.hpp>
|
||||||
|
|
||||||
namespace workshop
|
namespace workshop
|
||||||
{
|
{
|
||||||
const std::string get_usermap_publisher_id(const std::string& mapname)
|
|
||||||
{
|
|
||||||
const auto total_usermaps = *reinterpret_cast<unsigned int*>(0x1567B3580_g);
|
|
||||||
|
|
||||||
for (unsigned int i = 0; i < total_usermaps; ++i)
|
|
||||||
{
|
|
||||||
const auto usermap_data = reinterpret_cast<game::workshop_data*>(0x1567B3588_g + (sizeof(game::workshop_data) * i));
|
|
||||||
if (usermap_data->folderName == mapname)
|
|
||||||
{
|
|
||||||
return usermap_data->publisherId;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
bool check_valid_publisher_id(const std::string& mapname, const std::string& pub_id)
|
|
||||||
{
|
|
||||||
if (!game::DB_FileExists(mapname.data(), 0) && pub_id.empty())
|
|
||||||
{
|
|
||||||
game::Com_Error(0, "Can't find usermap: %s!\nMake sure you're subscribed to the workshop item.", mapname.data());
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void load_usermap_mod_if_needed(const std::string& pub_id)
|
|
||||||
{
|
|
||||||
if (!game::isModLoaded() && !pub_id.empty())
|
|
||||||
{
|
|
||||||
game::loadMod(0, "usermaps", 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
utils::hook::detour setup_server_map_hook;
|
utils::hook::detour setup_server_map_hook;
|
||||||
|
|
||||||
|
bool has_mod(const std::string& pub_id)
|
||||||
|
{
|
||||||
|
const auto total_mods = *reinterpret_cast<unsigned int*>(0x15678D170_g);
|
||||||
|
|
||||||
|
for (unsigned int i = 0; i < total_mods; ++i)
|
||||||
|
{
|
||||||
|
const auto mod_data = reinterpret_cast<game::workshop_data*>(0x15678D178_g + (sizeof(game::workshop_data) * i));
|
||||||
|
if (mod_data->publisherId == pub_id)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void load_usermap_mod_if_needed(const std::string& publisher_id)
|
||||||
|
{
|
||||||
|
if (!game::isModLoaded() && !publisher_id.empty())
|
||||||
|
{
|
||||||
|
game::loadMod(0, "usermaps", true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void setup_server_map_stub(int localClientNum, const char* mapname, const char* gametype)
|
void setup_server_map_stub(int localClientNum, const char* mapname, const char* gametype)
|
||||||
{
|
{
|
||||||
const auto publisher_id = get_usermap_publisher_id(mapname);
|
const auto publisher_id = get_usermap_publisher_id(mapname);
|
||||||
@ -72,6 +63,102 @@ namespace workshop
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::string get_mod_name(const std::string& mod_id)
|
||||||
|
{
|
||||||
|
if (mod_id == "usermaps" || !game::is_server())
|
||||||
|
{
|
||||||
|
return mod_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
const utils::nt::library host{};
|
||||||
|
const auto base_path = host.get_folder().generic_string();
|
||||||
|
const auto path = utils::string::va("%s/mods/%s/zone/workshop.json", base_path.data(), mod_id.data());
|
||||||
|
const auto json_str = utils::io::read_file(path);
|
||||||
|
|
||||||
|
if (json_str.empty())
|
||||||
|
{
|
||||||
|
printf("[ Workshop ] workshop.json has not been found in mod folder: %s\n", mod_id.data());
|
||||||
|
return mod_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
rapidjson::Document doc;
|
||||||
|
const rapidjson::ParseResult parse_result = doc.Parse(json_str);
|
||||||
|
|
||||||
|
if (parse_result.IsError() || !doc.IsObject())
|
||||||
|
{
|
||||||
|
printf("[ Workshop ] Unable to parse workshop.json\n");
|
||||||
|
return mod_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (doc.HasMember("Title"))
|
||||||
|
{
|
||||||
|
std::string title = doc["Title"].GetString();
|
||||||
|
|
||||||
|
if (title.size() > 31)
|
||||||
|
{
|
||||||
|
title.resize(31);
|
||||||
|
}
|
||||||
|
|
||||||
|
return title;
|
||||||
|
}
|
||||||
|
|
||||||
|
printf("[ Workshop ] workshop.json has no \"Title\" member.\n");
|
||||||
|
return mod_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string get_usermap_publisher_id(const std::string& mapname)
|
||||||
|
{
|
||||||
|
const auto total_usermaps = *reinterpret_cast<unsigned int*>(0x1567B3580_g);
|
||||||
|
|
||||||
|
for (unsigned int i = 0; i < total_usermaps; ++i)
|
||||||
|
{
|
||||||
|
const auto usermap_data = reinterpret_cast<game::workshop_data*>(0x1567B3588_g + (sizeof(game::workshop_data) * i));
|
||||||
|
if (usermap_data->folderName == mapname)
|
||||||
|
{
|
||||||
|
return usermap_data->publisherId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
bool check_valid_usermap_id(const std::string& mapname, const std::string& pub_id)
|
||||||
|
{
|
||||||
|
if (!game::DB_FileExists(mapname.data(), 0) && pub_id.empty())
|
||||||
|
{
|
||||||
|
game::UI_OpenErrorPopupWithMessage(0, 0x100,
|
||||||
|
utils::string::va("Can't find usermap: %s!\nMake sure you're subscribed to the workshop item.", mapname.data()));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool check_valid_mod_id(const std::string& mod)
|
||||||
|
{
|
||||||
|
if (mod.empty() || mod == "usermaps")
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!has_mod(mod))
|
||||||
|
{
|
||||||
|
game::UI_OpenErrorPopupWithMessage(0, 0x100,
|
||||||
|
utils::string::va("Can't find mod with publisher id: %s!\nMake sure you're subscribed to the workshop item.", mod.data()));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void load_mod_if_needed(const std::string& usermap, const std::string& mod)
|
||||||
|
{
|
||||||
|
if (!usermap.empty() || mod != "usermaps")
|
||||||
|
{
|
||||||
|
game::loadMod(0, mod.data(), true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class component final : public client_component
|
class component final : public client_component
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
@ -2,7 +2,9 @@
|
|||||||
|
|
||||||
namespace workshop
|
namespace workshop
|
||||||
{
|
{
|
||||||
const std::string get_usermap_publisher_id(const std::string& mapname);
|
std::string get_usermap_publisher_id(const std::string& mapname);
|
||||||
bool check_valid_publisher_id(const std::string& mapname, const std::string& pub_id);
|
std::string get_mod_name(const std::string& mod_id);
|
||||||
void load_usermap_mod_if_needed(const std::string& pub_id);
|
bool check_valid_usermap_id(const std::string& mapname, const std::string& pub_id);
|
||||||
|
bool check_valid_mod_id(const std::string& pub_id);
|
||||||
|
void load_mod_if_needed(const std::string& usermap, const std::string& mod);
|
||||||
}
|
}
|
||||||
|
@ -104,7 +104,7 @@ namespace game
|
|||||||
WEAK symbol<const char*(const char* name)> CopyString{0x1422AC220, 0x14056BD70};
|
WEAK symbol<const char*(const char* name)> CopyString{0x1422AC220, 0x14056BD70};
|
||||||
|
|
||||||
WEAK symbol<bool()> isModLoaded{0x1420D5020};
|
WEAK symbol<bool()> isModLoaded{0x1420D5020};
|
||||||
WEAK symbol<void(int, const char*, int)> loadMod{0x1420D6930};
|
WEAK symbol<void(int, const char*, bool)> loadMod{0x1420D6930};
|
||||||
|
|
||||||
// Dvar
|
// Dvar
|
||||||
WEAK symbol<bool(const dvar_t* dvar)> Dvar_IsSessionModeBaseDvar{0x1422C23A0, 0x140576890};
|
WEAK symbol<bool(const dvar_t* dvar)> Dvar_IsSessionModeBaseDvar{0x1422C23A0, 0x140576890};
|
||||||
@ -145,6 +145,7 @@ namespace game
|
|||||||
};
|
};
|
||||||
|
|
||||||
// UI
|
// UI
|
||||||
|
WEAK symbol<void(int localClientNumber, int errorcode, const char* errorMessage)> UI_OpenErrorPopupWithMessage{0x14228DEE0};
|
||||||
WEAK symbol<void(bool frontend)> UI_CoD_Init{0x141F29010, 0x1404A0A50};
|
WEAK symbol<void(bool frontend)> UI_CoD_Init{0x141F29010, 0x1404A0A50};
|
||||||
WEAK symbol<void()> UI_CoD_LobbyUI_Init{0x141F2BD80, 0x1404A1F50};
|
WEAK symbol<void()> UI_CoD_LobbyUI_Init{0x141F2BD80, 0x1404A1F50};
|
||||||
WEAK symbol<void()> UI_CoD_Shutdown{0x141F32E10, 0x0};
|
WEAK symbol<void()> UI_CoD_Shutdown{0x141F32E10, 0x0};
|
||||||
|
@ -55,12 +55,14 @@ namespace steam
|
|||||||
const auto mode = game::eModes(std::atoi(playmode.data()));
|
const auto mode = game::eModes(std::atoi(playmode.data()));
|
||||||
|
|
||||||
const auto* tags = ::utils::string::va(
|
const auto* tags = ::utils::string::va(
|
||||||
R"(\gametype\%s\dedicated\%s\ranked\false\hardcore\%s\zombies\%s\modName\\playerCount\%d\bots\%d\)",
|
R"(\gametype\%s\dedicated\%s\ranked\false\hardcore\%s\zombies\%s\playerCount\%d\bots\%d\modName\%s\)",
|
||||||
info.get("gametype").data(),
|
info.get("gametype").data(),
|
||||||
info.get("dedicated") == "1" ? "true" : "false",
|
info.get("dedicated") == "1" ? "true" : "false",
|
||||||
info.get("hc") == "1" ? "true" : "false",
|
info.get("hc") == "1" ? "true" : "false",
|
||||||
mode == game::MODE_ZOMBIES ? "true" : "false",
|
mode == game::MODE_ZOMBIES ? "true" : "false",
|
||||||
server.m_nPlayers, atoi(info.get("bots").data()));
|
server.m_nPlayers,
|
||||||
|
atoi(info.get("bots").data()),
|
||||||
|
info.get("modname").data());
|
||||||
|
|
||||||
::utils::string::copy(server.m_szGameTags, tags);
|
::utils::string::copy(server.m_szGameTags, tags);
|
||||||
server.m_steamID.bits = strtoull(info.get("xuid").data(), nullptr, 16);
|
server.m_steamID.bits = strtoull(info.get("xuid").data(), nullptr, 16);
|
||||||
|
Loading…
Reference in New Issue
Block a user