Usermap support
This commit is contained in:
parent
454b29f1c8
commit
d3124bf989
@ -11,6 +11,7 @@
|
||||
#include <utils/concurrency.hpp>
|
||||
#include <utils/http.hpp>
|
||||
#include <utils/io.hpp>
|
||||
#include <utils/cryptography.hpp>
|
||||
|
||||
namespace download
|
||||
{
|
||||
@ -56,9 +57,25 @@ namespace download
|
||||
});
|
||||
}
|
||||
|
||||
int progress_callback(size_t progress)
|
||||
auto last_update = std::chrono::high_resolution_clock::now();
|
||||
int progress_callback(size_t total, size_t progress)
|
||||
{
|
||||
console::debug("Download progress: %lli\n", progress);
|
||||
const auto now = std::chrono::high_resolution_clock::now();
|
||||
if (now - last_update > 20ms)
|
||||
{
|
||||
last_update = std::chrono::high_resolution_clock::now();
|
||||
const auto fraction = static_cast<float>(static_cast<double>(progress) /
|
||||
static_cast<double>(std::max(size_t(1), total)));
|
||||
scheduler::once([=]()
|
||||
{
|
||||
ui_scripting::notify("mod_download_progress",
|
||||
{
|
||||
{"fraction", fraction},
|
||||
});
|
||||
}, scheduler::pipeline::lui);
|
||||
}
|
||||
|
||||
console::debug("Download progress: %lli/%lli\n", progress, total);
|
||||
if (download_aborted())
|
||||
{
|
||||
return -1;
|
||||
@ -76,7 +93,7 @@ namespace download
|
||||
}
|
||||
}
|
||||
|
||||
void start_download(const game::netadr_s& target, const utils::info_string& info)
|
||||
void start_download(const game::netadr_s& target, const utils::info_string& info, const std::vector<file_t>& files)
|
||||
{
|
||||
if (download_active())
|
||||
{
|
||||
@ -84,7 +101,7 @@ namespace download
|
||||
{
|
||||
if (!download_active())
|
||||
{
|
||||
start_download(target, info);
|
||||
start_download(target, info, files);
|
||||
return scheduler::cond_end;
|
||||
}
|
||||
|
||||
@ -106,27 +123,17 @@ namespace download
|
||||
return;
|
||||
}
|
||||
|
||||
const auto mod = info.get("fs_game") + "/mod.ff";
|
||||
const auto url = base + "/" + mod;
|
||||
|
||||
console::debug("Downloading %s from %s: %s\n", mod.data(), base.data(), url.data());
|
||||
|
||||
scheduler::once([=]()
|
||||
{
|
||||
const ui_scripting::table mod_data_table{};
|
||||
mod_data_table.set("name", mod.data());
|
||||
|
||||
ui_scripting::notify("mod_download_start",
|
||||
{
|
||||
{"request", mod_data_table}
|
||||
});
|
||||
ui_scripting::notify("mod_download_start", {});
|
||||
}, scheduler::pipeline::lui);
|
||||
|
||||
scheduler::once([=]()
|
||||
{
|
||||
{
|
||||
const auto _0 = gsl::finally(&mark_unactive);
|
||||
|
||||
mark_active();
|
||||
|
||||
if (download_aborted())
|
||||
@ -134,26 +141,50 @@ namespace download
|
||||
return;
|
||||
}
|
||||
|
||||
const auto data = utils::http::get_data(url, {}, {}, &progress_callback);
|
||||
if (!data.has_value())
|
||||
for (const auto& file : files)
|
||||
{
|
||||
menu_error("Download failed: An unknown error occurred, please try again.");
|
||||
return;
|
||||
}
|
||||
scheduler::once([=]()
|
||||
{
|
||||
const ui_scripting::table data_table{};
|
||||
data_table.set("name", file.name.data());
|
||||
|
||||
if (download_aborted())
|
||||
{
|
||||
return;
|
||||
}
|
||||
ui_scripting::notify("mod_download_set_file",
|
||||
{
|
||||
{"request", data_table}
|
||||
});
|
||||
}, scheduler::pipeline::lui);
|
||||
|
||||
const auto& result = data.value();
|
||||
if (result.code != CURLE_OK)
|
||||
{
|
||||
menu_error(utils::string::va("Download failed: %s (%i)\n", result.code, curl_easy_strerror(result.code)));
|
||||
return;
|
||||
}
|
||||
const auto url = utils::string::va("%s/%s", base.data(), file.name.data());
|
||||
console::debug("Downloading %s from %s: %s\n", file.name.data(), base.data(), url);
|
||||
const auto data = utils::http::get_data(url, {}, {}, &progress_callback);
|
||||
if (!data.has_value())
|
||||
{
|
||||
menu_error("Download failed: An unknown error occurred, please try again.");
|
||||
return;
|
||||
}
|
||||
|
||||
utils::io::write_file(mod, result.buffer, false);
|
||||
if (download_aborted())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const auto& result = data.value();
|
||||
if (result.code != CURLE_OK)
|
||||
{
|
||||
menu_error(utils::string::va("Download failed: %s (%i)\n", result.code, curl_easy_strerror(result.code)));
|
||||
return;
|
||||
}
|
||||
|
||||
const auto hash = utils::cryptography::sha1::compute(result.buffer, true);
|
||||
if (hash != file.hash)
|
||||
{
|
||||
menu_error(utils::string::va("Download failed: file hash doesn't match the server's (%s: %s != %s)\n",
|
||||
file.name.data(), hash.data(), file.hash.data()));
|
||||
return;
|
||||
}
|
||||
|
||||
utils::io::write_file(file.name, result.buffer, false);
|
||||
}
|
||||
}
|
||||
|
||||
scheduler::once([]()
|
||||
|
@ -5,6 +5,12 @@
|
||||
|
||||
namespace download
|
||||
{
|
||||
void start_download(const game::netadr_s& target, const utils::info_string& info);
|
||||
struct file_t
|
||||
{
|
||||
std::string name;
|
||||
std::string hash;
|
||||
};
|
||||
|
||||
void start_download(const game::netadr_s& target, const utils::info_string& info, const std::vector<file_t>& files);
|
||||
void stop_download();
|
||||
}
|
||||
|
@ -16,6 +16,7 @@
|
||||
namespace fastfiles
|
||||
{
|
||||
static utils::concurrency::container<std::string> current_fastfile;
|
||||
static utils::concurrency::container<std::optional<std::string>> current_usermap;
|
||||
|
||||
namespace
|
||||
{
|
||||
@ -204,6 +205,32 @@ namespace fastfiles
|
||||
return handle;
|
||||
}
|
||||
|
||||
HANDLE find_usermap(const std::string& mapname)
|
||||
{
|
||||
const auto usermap = fastfiles::get_current_usermap();
|
||||
if (!usermap.has_value())
|
||||
{
|
||||
return INVALID_HANDLE_VALUE;
|
||||
}
|
||||
|
||||
const auto& usermap_value = usermap.value();
|
||||
const auto usermap_file = utils::string::va("%s.ff", usermap_value.data());
|
||||
const auto usermap_load_file = utils::string::va("%s_load.ff", usermap_value.data());
|
||||
|
||||
if (mapname == usermap_file || mapname == usermap_load_file)
|
||||
{
|
||||
const auto path = utils::string::va("usermaps\\%s\\%s",
|
||||
usermap_value.data(), mapname.data());
|
||||
if (utils::io::file_exists(path))
|
||||
{
|
||||
return CreateFileA(path, GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING,
|
||||
FILE_FLAG_OVERLAPPED | FILE_FLAG_NO_BUFFERING, nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
return INVALID_HANDLE_VALUE;
|
||||
}
|
||||
|
||||
utils::hook::detour sys_createfile_hook;
|
||||
HANDLE sys_create_file_stub(game::Sys_Folder folder, const char* base_filename)
|
||||
{
|
||||
@ -237,6 +264,12 @@ namespace fastfiles
|
||||
return handle;
|
||||
}
|
||||
|
||||
const auto usermap = find_usermap(name);
|
||||
if (usermap != INVALID_HANDLE_VALUE)
|
||||
{
|
||||
return usermap;
|
||||
}
|
||||
|
||||
if (name.ends_with(".ff"))
|
||||
{
|
||||
handle = find_fastfile(name, true);
|
||||
@ -245,6 +278,18 @@ namespace fastfiles
|
||||
return handle;
|
||||
}
|
||||
|
||||
utils::hook::detour db_file_exists_hook;
|
||||
bool db_file_exists_stub(const char* file, int a2)
|
||||
{
|
||||
const auto file_exists = db_file_exists_hook.invoke<bool>(file, a2);
|
||||
if (file_exists)
|
||||
{
|
||||
return file_exists;
|
||||
}
|
||||
|
||||
return fastfiles::usermap_exists(file);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline void merge(std::vector<T>* target, T* source, size_t length)
|
||||
{
|
||||
@ -311,6 +356,19 @@ namespace fastfiles
|
||||
return;
|
||||
}
|
||||
|
||||
const auto usermap = fastfiles::get_current_usermap();
|
||||
if (usermap.has_value())
|
||||
{
|
||||
const auto& usermap_value = usermap.value();
|
||||
const auto usermap_load = usermap_value + "_load";
|
||||
|
||||
if (fastfile == usermap_value || fastfile == usermap_load)
|
||||
{
|
||||
console::error("Usermap tried to load a lua file!\n");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
utils::hook::invoke<void>(0x39CA90_b, a1);
|
||||
}
|
||||
|
||||
@ -379,6 +437,54 @@ namespace fastfiles
|
||||
}
|
||||
}
|
||||
|
||||
void set_usermap(const std::string& usermap)
|
||||
{
|
||||
current_usermap.access([&](std::optional<std::string>& current_usermap_)
|
||||
{
|
||||
current_usermap_ = usermap;
|
||||
});
|
||||
}
|
||||
|
||||
void clear_usermap()
|
||||
{
|
||||
current_usermap.access([&](std::optional<std::string>& current_usermap_)
|
||||
{
|
||||
current_usermap_.reset();
|
||||
});
|
||||
}
|
||||
|
||||
std::optional<std::string> get_current_usermap()
|
||||
{
|
||||
return current_usermap.access<std::optional<std::string>>([&](
|
||||
std::optional<std::string>& current_usermap_)
|
||||
{
|
||||
return current_usermap_;
|
||||
});
|
||||
}
|
||||
|
||||
bool usermap_exists(const std::string& name)
|
||||
{
|
||||
if (is_stock_map(name))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return utils::io::file_exists(utils::string::va("usermaps\\%s\\%s.ff", name.data(), name.data()));
|
||||
}
|
||||
|
||||
bool is_stock_map(const std::string& name)
|
||||
{
|
||||
for (auto map = &game::maps[0]; map->unk; ++map)
|
||||
{
|
||||
if (map->name == name)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
class component final : public component_interface
|
||||
{
|
||||
public:
|
||||
@ -404,6 +510,7 @@ namespace fastfiles
|
||||
|
||||
// Add custom zone paths
|
||||
sys_createfile_hook.create(game::Sys_CreateFile, sys_create_file_stub);
|
||||
db_file_exists_hook.create(0x394DC0_b, db_file_exists_stub);
|
||||
|
||||
// load our custom pre_gfx zones
|
||||
utils::hook::call(SELECT_VALUE(0x3862ED_b, 0x15C3FD_b), load_pre_gfx_zones);
|
||||
|
@ -12,4 +12,10 @@ namespace fastfiles
|
||||
const std::function<void(game::XAssetHeader)>& callback, const bool includeOverride);
|
||||
|
||||
void close_fastfile_handles();
|
||||
|
||||
void set_usermap(const std::string& usermap);
|
||||
void clear_usermap();
|
||||
std::optional<std::string> get_current_usermap();
|
||||
bool usermap_exists(const std::string& name);
|
||||
bool is_stock_map(const std::string& name);
|
||||
}
|
||||
|
@ -8,6 +8,7 @@
|
||||
#include "scheduler.hpp"
|
||||
#include "server_list.hpp"
|
||||
#include "download.hpp"
|
||||
#include "fastfiles.hpp"
|
||||
|
||||
#include "game/game.hpp"
|
||||
|
||||
@ -33,8 +34,6 @@ namespace party
|
||||
std::string sv_motd;
|
||||
int sv_maxclients;
|
||||
|
||||
std::optional<std::string> mod_hash{};
|
||||
|
||||
void perform_game_initialization()
|
||||
{
|
||||
command::execute("onlinegame 1", true);
|
||||
@ -82,6 +81,11 @@ namespace party
|
||||
utils::hook::invoke<void>(0x13C9C0_b, 1);
|
||||
}
|
||||
|
||||
if (!fastfiles::is_stock_map(mapname))
|
||||
{
|
||||
fastfiles::set_usermap(mapname);
|
||||
}
|
||||
|
||||
// CL_ConnectFromParty
|
||||
char session_info[0x100] = {};
|
||||
utils::hook::invoke<void>(0x12DFF0_b, 0, session_info, &target, mapname.data(), gametype.data());
|
||||
@ -153,7 +157,50 @@ namespace party
|
||||
cl_disconnect_hook.invoke<void>(showMainMenu);
|
||||
}
|
||||
|
||||
bool download_mod(const game::netadr_s& target, const utils::info_string& info)
|
||||
std::optional<std::string> get_file_hash(const std::string& file)
|
||||
{
|
||||
if (!utils::io::file_exists(file))
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
const auto data = utils::io::read_file(file);
|
||||
const auto sha = utils::cryptography::sha1::compute(data, true);
|
||||
return {sha};
|
||||
}
|
||||
|
||||
void check_download_map(const utils::info_string& info, std::vector<download::file_t>& files)
|
||||
{
|
||||
const auto mapname = info.get("mapname");
|
||||
const auto usermap_hash = info.get("usermaphash");
|
||||
const auto usermap_load_hash = info.get("usermaploadhash");
|
||||
|
||||
const auto check_file = [&](const std::string& key, const std::string& filename)
|
||||
{
|
||||
if (mapname.contains('.') || mapname.contains("::"))
|
||||
{
|
||||
throw std::runtime_error(utils::string::va("Invalid server mapname value %s\n", mapname.data()));
|
||||
}
|
||||
|
||||
const auto source_hash = info.get(key);
|
||||
if (source_hash.empty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const auto hash = get_file_hash(filename);
|
||||
if (!hash.has_value() || hash.value() != source_hash)
|
||||
{
|
||||
files.emplace_back(filename, source_hash);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
check_file("usermaphash", utils::string::va("usermaps/%s/%s.ff", mapname.data(), mapname.data()));
|
||||
check_file("usermaploadhash", utils::string::va("usermaps/%s/%s_load.ff", mapname.data(), mapname.data()));
|
||||
}
|
||||
|
||||
bool check_download_mod(const utils::info_string& info, std::vector<download::file_t>& files)
|
||||
{
|
||||
const auto server_fs_game = utils::string::to_lower(info.get("fs_game"));
|
||||
if (server_fs_game.empty())
|
||||
@ -161,17 +208,15 @@ namespace party
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!server_fs_game.starts_with("mods/") || server_fs_game.contains('.'))
|
||||
if (!server_fs_game.starts_with("mods/") || server_fs_game.contains('.') || server_fs_game.contains("::"))
|
||||
{
|
||||
menu_error(utils::string::va("Invalid server fs_game value %s\n", server_fs_game.data()));
|
||||
return true;
|
||||
throw std::runtime_error(utils::string::va("Invalid server fs_game value %s\n", server_fs_game.data()));
|
||||
}
|
||||
|
||||
const auto source_hash = info.get("modHash");
|
||||
if (source_hash.empty())
|
||||
{
|
||||
menu_error("Connection failed: Server mod hash is empty.");
|
||||
return true;
|
||||
throw std::runtime_error("Connection failed: Server mod hash is empty.");
|
||||
}
|
||||
|
||||
static const auto fs_game = game::Dvar_FindVar("fs_game");
|
||||
@ -179,6 +224,7 @@ namespace party
|
||||
|
||||
const auto mod_path = server_fs_game + "/mod.ff";
|
||||
auto has_to_download = !utils::io::file_exists(mod_path);
|
||||
|
||||
if (!has_to_download)
|
||||
{
|
||||
const auto data = utils::io::read_file(mod_path);
|
||||
@ -186,38 +232,138 @@ namespace party
|
||||
|
||||
has_to_download = source_hash != hash;
|
||||
}
|
||||
else
|
||||
{
|
||||
console::debug("Failed to find mod, downloading\n");
|
||||
}
|
||||
|
||||
if (has_to_download)
|
||||
{
|
||||
console::debug("Starting download of mod '%s'\n", server_fs_game.data());
|
||||
|
||||
download::stop_download();
|
||||
download::start_download(target, info);
|
||||
|
||||
return true;
|
||||
files.emplace_back(mod_path, source_hash);
|
||||
return false;
|
||||
}
|
||||
else if (client_fs_game != server_fs_game)
|
||||
{
|
||||
game::Dvar_SetFromStringByNameFromSource("fs_game", server_fs_game.data(),
|
||||
game::Dvar_SetFromStringByNameFromSource("fs_game", server_fs_game.data(),
|
||||
game::DVAR_SOURCE_INTERNAL);
|
||||
command::execute("vid_restart");
|
||||
|
||||
// set fs_game to the mod the server is on, "restart" game, and then reconnect
|
||||
scheduler::once([=]()
|
||||
{
|
||||
command::execute("lui_open_popup popup_acceptinginvite", false);
|
||||
connect(target);
|
||||
}, scheduler::pipeline::main);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool needs_vid_restart = false;
|
||||
|
||||
bool download_files(const game::netadr_s& target, const utils::info_string& info)
|
||||
{
|
||||
try
|
||||
{
|
||||
std::vector<download::file_t> files{};
|
||||
|
||||
const auto needs_restart = check_download_mod(info, files);
|
||||
needs_vid_restart = needs_vid_restart || needs_restart;
|
||||
check_download_map(info, files);
|
||||
|
||||
if (files.size() > 0)
|
||||
{
|
||||
download::stop_download();
|
||||
download::start_download(target, info, files);
|
||||
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;
|
||||
}
|
||||
}
|
||||
catch (const std::exception& e)
|
||||
{
|
||||
menu_error(e.what());
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void set_new_map(const char* mapname, const char* gametype, game::msg_t* msg)
|
||||
{
|
||||
char buffer[0x400] = {0};
|
||||
// dont bother disconnecting just to update the usermap loadscreen
|
||||
const std::string usermap_hash = game::MSG_ReadStringLine(msg,
|
||||
buffer, static_cast<unsigned int>(sizeof(buffer)));
|
||||
|
||||
if (!fastfiles::is_stock_map(mapname))
|
||||
{
|
||||
const auto filename = utils::string::va("usermaps/%s/%s.ff", mapname, mapname);
|
||||
const auto hash = get_file_hash(filename);
|
||||
if (!hash.has_value() || hash.value() != usermap_hash)
|
||||
{
|
||||
command::execute("disconnect");
|
||||
scheduler::once([]
|
||||
{
|
||||
connect(connect_state.host);
|
||||
}, scheduler::pipeline::main);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
if (!strstr(data, "loadingnewmap"))
|
||||
{
|
||||
return net_out_of_band_print_hook.invoke<void>(sock, addr, data);
|
||||
}
|
||||
|
||||
char buffer[0x400] = {0};
|
||||
const auto is_usermap = fastfiles::usermap_exists(current_sv_mapname);
|
||||
std::string hash{};
|
||||
if (is_usermap)
|
||||
{
|
||||
const auto filename = utils::string::va("usermaps\\%s\\%s.ff",
|
||||
current_sv_mapname.data(), current_sv_mapname.data());
|
||||
const auto hash_value = get_file_hash(filename);
|
||||
if (hash_value.has_value())
|
||||
{
|
||||
hash = hash_value.value();
|
||||
}
|
||||
}
|
||||
|
||||
auto* sv_gametype = game::Dvar_FindVar("g_gametype");
|
||||
|
||||
sprintf_s(buffer, sizeof(buffer),
|
||||
"loadingnewmap\n%s\n%s\n%s", current_sv_mapname.data(), sv_gametype->current.string, hash.data());
|
||||
|
||||
net_out_of_band_print_hook.invoke<void>(sock, addr, buffer);
|
||||
}
|
||||
}
|
||||
|
||||
void menu_error(const std::string& error)
|
||||
@ -228,6 +374,11 @@ namespace party
|
||||
command::execute("lui_close popup_acceptinginvite", false);
|
||||
}
|
||||
|
||||
if (game::Menu_IsMenuOpenAndVisible(0, "generic_waiting_popup_"))
|
||||
{
|
||||
command::execute("lui_close generic_waiting_popup_", false);
|
||||
}
|
||||
|
||||
utils::hook::invoke<void>(0x17D770_b, error.data(), "MENU_NOTICE"); // Com_SetLocalizedErrorMessage
|
||||
*reinterpret_cast<int*>(0x2ED2F78_b) = 1;
|
||||
}
|
||||
@ -425,6 +576,11 @@ namespace party
|
||||
// allow custom didyouknow based on sv_motd
|
||||
utils::hook::call(0x1A8A3A_b, get_didyouknow_stub);
|
||||
|
||||
// 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);
|
||||
|
||||
command::add("map", [](const command::params& argument)
|
||||
{
|
||||
if (argument.size() != 2)
|
||||
@ -651,6 +807,8 @@ namespace party
|
||||
|
||||
network::on("getInfo", [](const game::netadr_s& target, const std::string_view& data)
|
||||
{
|
||||
const auto mapname = get_dvar_string("mapname");
|
||||
|
||||
utils::info_string info{};
|
||||
info.set("challenge", std::string{data});
|
||||
info.set("gamename", "H1");
|
||||
@ -658,7 +816,7 @@ namespace party
|
||||
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));
|
||||
info.set("mapname", get_dvar_string("mapname"));
|
||||
info.set("mapname", mapname);
|
||||
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()));
|
||||
@ -669,22 +827,30 @@ namespace party
|
||||
info.set("dedicated", utils::string::va("%i", get_dvar_bool("dedicated")));
|
||||
info.set("sv_wwwBaseUrl", get_dvar_string("sv_wwwBaseUrl"));
|
||||
|
||||
if (!fastfiles::is_stock_map(mapname))
|
||||
{
|
||||
const auto hash = get_file_hash(utils::string::va("usermaps/%s/%s.ff", mapname.data(), mapname.data()));
|
||||
if (hash.has_value())
|
||||
{
|
||||
info.set("usermaphash", hash.value());
|
||||
}
|
||||
|
||||
const auto load_file_hash = get_file_hash(utils::string::va("usermaps/%s/%s_load.ff", mapname.data(), mapname.data()));
|
||||
if (load_file_hash.has_value())
|
||||
{
|
||||
info.set("usermaploadhash", load_file_hash.value());
|
||||
}
|
||||
}
|
||||
|
||||
const auto fs_game = get_dvar_string("fs_game");
|
||||
info.set("fs_game", fs_game);
|
||||
|
||||
if (mod_hash.has_value())
|
||||
if (!fs_game.empty())
|
||||
{
|
||||
info.set("modHash", mod_hash.value());
|
||||
}
|
||||
else
|
||||
{
|
||||
const auto mod_path = fs_game + "/mod.ff";
|
||||
if (utils::io::file_exists(mod_path))
|
||||
const auto hash = get_file_hash(utils::string::va("%s/mod.ff", fs_game.data()));
|
||||
if (hash.has_value())
|
||||
{
|
||||
const auto mod_data = utils::io::read_file(mod_path);
|
||||
const auto sha = utils::cryptography::sha1::compute(mod_data, true);
|
||||
mod_hash = sha; // todo: clear this somewhere
|
||||
info.set("modHash", sha);
|
||||
info.set("modHash", hash.value());
|
||||
}
|
||||
}
|
||||
|
||||
@ -742,7 +908,7 @@ namespace party
|
||||
return;
|
||||
}
|
||||
|
||||
if (download_mod(target, info))
|
||||
if (download_files(target, info))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
@ -699,7 +699,7 @@ namespace scripting::lua
|
||||
return;
|
||||
}
|
||||
|
||||
const auto result = utils::http::get_data(url, fields_string, headers_map, [cur_task_id](size_t value)
|
||||
const auto result = utils::http::get_data(url, fields_string, headers_map, [cur_task_id](size_t total, size_t value)
|
||||
{
|
||||
::scheduler::once([cur_task_id, value]()
|
||||
{
|
||||
|
@ -120,6 +120,8 @@ namespace game
|
||||
|
||||
WEAK symbol<Material*(const char* material)> Material_RegisterHandle{0x56EA20, 0x692360};
|
||||
|
||||
WEAK symbol<char*(msg_t* msg, char* buffer, unsigned int max_chars)> MSG_ReadStringLine{0x0, 0x4EB9D0};
|
||||
|
||||
WEAK symbol<void(netadr_s*, sockaddr*)> NetadrToSockadr{0x416580, 0x59E580};
|
||||
WEAK symbol<void(netsrc_t, netadr_s*, const char*)> NET_OutOfBandPrint{0x3AA550, 0x4F1EB0};
|
||||
WEAK symbol<void(netsrc_t sock, int length, const void* data, const netadr_s* to)> NET_SendLoopPacket{0x0, 0x4F2070};
|
||||
|
@ -8,17 +8,17 @@ namespace utils::http
|
||||
{
|
||||
struct progress_helper
|
||||
{
|
||||
const std::function<int(size_t)>* callback{};
|
||||
const std::function<int(size_t, size_t)>* callback{};
|
||||
std::exception_ptr exception{};
|
||||
};
|
||||
|
||||
int progress_callback(void* clientp, const curl_off_t /*dltotal*/, const curl_off_t dlnow, const curl_off_t /*ultotal*/, const curl_off_t /*ulnow*/)
|
||||
int progress_callback(void* clientp, const curl_off_t dltotal, const curl_off_t dlnow, const curl_off_t /*ultotal*/, const curl_off_t /*ulnow*/)
|
||||
{
|
||||
auto* helper = static_cast<progress_helper*>(clientp);
|
||||
|
||||
try
|
||||
{
|
||||
if (*helper->callback && (*helper->callback)(dlnow) == -1)
|
||||
if (*helper->callback && (*helper->callback)(dltotal, dlnow) == -1)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
@ -43,7 +43,7 @@ namespace utils::http
|
||||
}
|
||||
|
||||
std::optional<result> get_data(const std::string& url, const std::string& fields,
|
||||
const headers& headers, const std::function<int(size_t)>& callback)
|
||||
const headers& headers, const std::function<int(size_t, size_t)>& callback)
|
||||
{
|
||||
curl_slist* header_list = nullptr;
|
||||
auto* curl = curl_easy_init();
|
||||
@ -74,6 +74,7 @@ namespace utils::http
|
||||
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &buffer);
|
||||
curl_easy_setopt(curl, CURLOPT_XFERINFOFUNCTION, progress_callback);
|
||||
curl_easy_setopt(curl, CURLOPT_XFERINFODATA, &helper);
|
||||
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1);
|
||||
curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0);
|
||||
|
||||
if (!fields.empty())
|
||||
@ -104,7 +105,7 @@ namespace utils::http
|
||||
}
|
||||
|
||||
std::future<std::optional<result>> get_data_async(const std::string& url, const std::string& fields,
|
||||
const headers& headers, const std::function<int(size_t)>& callback)
|
||||
const headers& headers, const std::function<int(size_t, size_t)>& callback)
|
||||
{
|
||||
return std::async(std::launch::async, [url, fields, headers, callback]()
|
||||
{
|
||||
|
@ -18,7 +18,7 @@ namespace utils::http
|
||||
using headers = std::unordered_map<std::string, std::string>;
|
||||
|
||||
std::optional<result> get_data(const std::string& url, const std::string& fields = {},
|
||||
const headers& headers = {}, const std::function<int(size_t)>& callback = {});
|
||||
const headers& headers = {}, const std::function<int(size_t, size_t)>& callback = {});
|
||||
std::future<std::optional<result>> get_data_async(const std::string& url, const std::string& fields = {},
|
||||
const headers& headers = {}, const std::function<int(size_t)>& callback = {});
|
||||
const headers& headers = {}, const std::function<int(size_t, size_t)>& callback = {});
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user