Add serverlist support

This commit is contained in:
Maurice Heumann 2023-02-05 16:15:29 +01:00
parent 5a7be3732c
commit 513f673c01
8 changed files with 368 additions and 65 deletions

View File

@ -61,7 +61,7 @@ namespace getinfo
//info.set("clients", utils::string::va("%i", get_client_count())); //info.set("clients", utils::string::va("%i", get_client_count()));
//info.set("bots", utils::string::va("%i", get_bot_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("sv_maxclients", utils::string::va("%i", *game::mp::svs_numclients));
info.set("protocol", utils::string::va("%i", 1/*PROTOCOL*/)); info.set("protocol", utils::string::va("%i", PROTOCOL));
info.set("playmode", utils::string::va("%i", game::Com_SessionMode_GetMode())); info.set("playmode", utils::string::va("%i", game::Com_SessionMode_GetMode()));
info.set("gamemode", utils::string::va("%i", Com_SessionMode_GetGameMode())); info.set("gamemode", utils::string::va("%i", Com_SessionMode_GetGameMode()));
//info.set("sv_running", utils::string::va("%i", get_dvar_bool("sv_running"))); //info.set("sv_running", utils::string::va("%i", get_dvar_bool("sv_running")));

View File

@ -153,12 +153,12 @@ namespace network
return to; return to;
} }
void send_data(const game::netadr_t& address, const void* data, const size_t size) void send_data(const game::netadr_t& address, const void* data, const size_t length)
{ {
//game::NET_SendPacket(game::NS_CLIENT1, static_cast<int>(size), data, &address); //game::NET_SendPacket(game::NS_CLIENT1, static_cast<int>(size), data, &address);
const auto to = convert_to_sockaddr(address); const auto to = convert_to_sockaddr(address);
sendto(*game::ip_socket, static_cast<const char*>(data), static_cast<int>(size), 0, sendto(*game::ip_socket, static_cast<const char*>(data), static_cast<int>(length), 0,
reinterpret_cast<const sockaddr*>(&to), sizeof(to)); reinterpret_cast<const sockaddr*>(&to), sizeof(to));
} }

View File

@ -20,10 +20,11 @@ namespace party
struct server_query struct server_query
{ {
game::netadr_t host; bool sent{false};
std::string challenge; game::netadr_t host{};
query_callback callback; std::string challenge{};
std::chrono::high_resolution_clock::time_point query_time; query_callback callback{};
std::chrono::high_resolution_clock::time_point query_time{};
}; };
utils::concurrency::container<std::vector<server_query>>& get_server_queries() utils::concurrency::container<std::vector<server_query>>& get_server_queries()
@ -153,7 +154,7 @@ namespace party
} }
void handle_connect_query_response(const bool success, const game::netadr_t& target, void handle_connect_query_response(const bool success, const game::netadr_t& target,
const utils::info_string& info) const utils::info_string& info, uint32_t ping)
{ {
if (!success) if (!success)
{ {
@ -209,24 +210,28 @@ namespace party
connect_host = target; connect_host = target;
query_server(target, handle_connect_query_response); query_server(target, handle_connect_query_response);
} }
void send_server_query(server_query& query)
{
query.sent = true;
query.query_time = std::chrono::high_resolution_clock::now();
query.challenge = utils::cryptography::random::get_challenge();
network::send(query.host, "getInfo", query.challenge);
}
} }
void query_server(const game::netadr_t& host, query_callback callback) void query_server(const game::netadr_t& host, query_callback callback)
{ {
const auto challenge = utils::cryptography::random::get_challenge();
server_query query{}; server_query query{};
query.sent = false;
query.host = host; query.host = host;
query.query_time = std::chrono::high_resolution_clock::now();
query.callback = std::move(callback); query.callback = std::move(callback);
query.challenge = challenge;
get_server_queries().access([&](std::vector<server_query>& server_queries) get_server_queries().access([&](std::vector<server_query>& server_queries)
{ {
server_queries.emplace_back(std::move(query)); server_queries.emplace_back(std::move(query));
}); });
network::send(host, "getInfo", challenge);
} }
int should_transfer_stub(uint8_t* storage_file_info) int should_transfer_stub(uint8_t* storage_file_info)
@ -274,7 +279,10 @@ namespace party
if (found_query) if (found_query)
{ {
query.callback(true, query.host, info); const auto ping = std::chrono::high_resolution_clock::now() - query.query_time;
const auto ping_ms = std::chrono::duration_cast<std::chrono::milliseconds>(ping).count();
query.callback(true, query.host, info, static_cast<uint32_t>(ping_ms));
} }
}); });
@ -284,10 +292,23 @@ namespace party
get_server_queries().access([&](std::vector<server_query>& server_queries) get_server_queries().access([&](std::vector<server_query>& server_queries)
{ {
size_t sent_queries = 0;
const auto now = std::chrono::high_resolution_clock::now(); const auto now = std::chrono::high_resolution_clock::now();
for (auto i = server_queries.begin(); i != server_queries.end();) for (auto i = server_queries.begin(); i != server_queries.end();)
{ {
if ((now - i->query_time) < 10s) if (!i->sent)
{
if (++sent_queries < 10)
{
send_server_query(*i);
}
++i;
continue;
}
if ((now - i->query_time) < 2s)
{ {
++i; ++i;
continue; continue;
@ -301,9 +322,17 @@ namespace party
const utils::info_string empty{}; const utils::info_string empty{};
for (const auto& query : removed_queries) for (const auto& query : removed_queries)
{ {
query.callback(false, query.host, empty); query.callback(false, query.host, empty, 0);
} }
}, scheduler::async, 1s); }, scheduler::async, 200ms);
}
void pre_destroy() override
{
get_server_queries().access([](std::vector<server_query>& s)
{
s = {};
});
} }
}; };
} }

View File

@ -3,7 +3,7 @@
namespace party namespace party
{ {
using query_callback_func = void(bool success, const game::netadr_t& host, const ::utils::info_string& info); using query_callback_func = void(bool success, const game::netadr_t& host, const ::utils::info_string& info, uint32_t ping);
using query_callback = std::function<query_callback_func>; using query_callback = std::function<query_callback_func>;
void query_server(const game::netadr_t& host, query_callback callback); void query_server(const game::netadr_t& host, query_callback callback);

View File

@ -0,0 +1,148 @@
#include <std_include.hpp>
#include "loader/component_loader.hpp"
#include "server_list.hpp"
#include "game/game.hpp"
#include <utils/string.hpp>
#include <utils/concurrency.hpp>
#include "network.hpp"
#include "scheduler.hpp"
namespace server_list
{
namespace
{
struct state
{
game::netadr_t address{};
bool requesting{false};
std::chrono::high_resolution_clock::time_point query_start{};
callback callback{};
};
utils::concurrency::container<state> master_state;
void handle_server_list_response(const game::netadr_t& target,
const network::data_view& data, state& s)
{
if (!s.requesting || s.address != target)
{
return;
}
s.requesting = false;
const auto callback = std::move(s.callback);
std::optional<size_t> start{};
for (size_t i = 0; i + 6 < data.size(); ++i)
{
if (data[i + 6] == '\\')
{
start.emplace(i);
break;
}
}
if (!start.has_value())
{
callback(true, {});
return;
}
std::unordered_set<game::netadr_t> result{};
for (auto i = start.value(); i + 6 < data.size(); i += 7)
{
if (data[i + 6] != '\\')
{
break;
}
game::netadr_t address{};
address.type = game::NA_RAWIP;
address.localNetID = game::NS_CLIENT1;
memcpy(&address.ipv4.a, data.data() + i + 0, 4);
memcpy(&address.port, data.data() + i + 4, 2);
address.port = ntohs(address.port);
result.emplace(address);
}
callback(true, result);
}
}
bool get_master_server(game::netadr_t& address)
{
address = network::address_from_string("server.boiii.re:20810");
return address.type != game::NA_BAD;
}
void request_servers(callback callback)
{
master_state.access([&callback](state& s)
{
game::netadr_t addr{};
if (!get_master_server(addr))
{
return;
}
s.requesting = true;
s.address = addr;
s.callback = std::move(callback);
s.query_start = std::chrono::high_resolution_clock::now();
network::send(s.address, "getservers", utils::string::va("T7 %i full empty", PROTOCOL));
});
}
struct component final : client_component
{
void post_unpack() override
{
network::on("getServersResponse", [](const game::netadr_t& target, const network::data_view& data)
{
master_state.access([&](state& s)
{
handle_server_list_response(target, data, s);
});
});
scheduler::loop([]
{
master_state.access([](state& s)
{
if (!s.requesting)
{
return;
}
const auto now = std::chrono::high_resolution_clock::now();
if ((now - s.query_start) < 2s)
{
return;
}
s.requesting = false;
s.callback(false, {});
s.callback = {};
});
}, scheduler::async, 200ms);
}
void pre_destroy() override
{
master_state.access([](state& s)
{
s.requesting = false;
s.callback = {};
});
}
};
}
REGISTER_COMPONENT(server_list::component)

View File

@ -0,0 +1,10 @@
#pragma once
#include <game/game.hpp>
namespace server_list
{
bool get_master_server(game::netadr_t& address);
using callback = std::function<void(bool, const std::unordered_set<game::netadr_t>&)>;
void request_servers(callback callback);
}

View File

@ -1,5 +1,7 @@
#pragma once #pragma once
#define PROTOCOL 1
#ifdef __cplusplus #ifdef __cplusplus
namespace game namespace game
{ {

View File

@ -5,86 +5,178 @@
#include "component/party.hpp" #include "component/party.hpp"
#include "component/network.hpp" #include "component/network.hpp"
#include "component/server_list.hpp"
#include <utils/string.hpp> #include <utils/string.hpp>
#include <utils/concurrency.hpp>
namespace steam namespace steam
{ {
namespace namespace
{ {
gameserveritem_t* get_server_item() struct server
{ {
static gameserveritem_t server{}; bool handled{false};
server.m_NetAdr.m_usConnectionPort = 28960; game::netadr_t address{};
server.m_NetAdr.m_usQueryPort = 28960; gameserveritem_t server_item{};
};
uint32_t address{}; using servers = std::vector<server>;
inet_pton(AF_INET, "192.168.178.34", &address);
server.m_NetAdr.m_unIP = ntohl(address);
server.m_nPing = 10; ::utils::concurrency::container<servers> queried_servers{};
server.m_bHadSuccessfulResponse = true; std::atomic<matchmaking_server_list_response*> current_response{};
server.m_bDoNotRefresh = false;
strcpy_s(server.m_szGameDir, "usermaps");
strcpy_s(server.m_szMap, "mp_nuketown_x");
strcpy_s(server.m_szGameDescription, "Example BO^3I^5I^6I ^7Server");
server.m_nAppID = 311210;
server.m_nPlayers = 0;
server.m_nMaxPlayers = 18;
server.m_nBotPlayers = 0;
server.m_bPassword = false;
server.m_bSecure = true;
server.m_ulTimeLastPlayed = 0;
server.m_nServerVersion = 1000;
strcpy_s(server.m_szServerName, "BO^3I^5I^6I ^7Server");
strcpy_s(server.m_szGameTags,
R"(\gametype\gun\dedicated\true\ranked\true\hardcore\false\zombies\false\modName\\playerCount\0)");
server.m_steamID = steam_id();
return &server; gameserveritem_t create_server_item(const game::netadr_t& address, const ::utils::info_string& info,
} const uint32_t ping, const bool success)
gameserveritem_t create_server_item(const game::netadr_t& address, const ::utils::info_string& info)
{ {
gameserveritem_t server{}; gameserveritem_t server{};
server.m_NetAdr.m_usConnectionPort = address.port; server.m_NetAdr.m_usConnectionPort = address.port;
server.m_NetAdr.m_usQueryPort = address.port; server.m_NetAdr.m_usQueryPort = address.port;
server.m_NetAdr.m_unIP = address.addr; server.m_NetAdr.m_unIP = address.addr;
server.m_nPing = 10; server.m_nPing = static_cast<int>(ping);
server.m_bHadSuccessfulResponse = true; server.m_bHadSuccessfulResponse = success;
server.m_bDoNotRefresh = false; server.m_bDoNotRefresh = false;
strcpy_s(server.m_szGameDir, ""); strcpy_s(server.m_szGameDir, "");
strcpy_s(server.m_szMap, info.get("mapname").data()); strcpy_s(server.m_szMap, info.get("mapname").data());
strcpy_s(server.m_szGameDescription, "Example BO^3I^5I^6I ^7Server"); strcpy_s(server.m_szGameDescription, "Example BO^3I^5I^6I ^7Server");
server.m_nAppID = 311210; server.m_nAppID = 311210;
server.m_nPlayers = 0; server.m_nPlayers = atoi(info.get("clients").data());
server.m_nMaxPlayers = 18; server.m_nMaxPlayers = atoi(info.get("sv_maxclients").data());
server.m_nBotPlayers = 0; server.m_nBotPlayers = atoi(info.get("bots").data());
server.m_bPassword = false; server.m_bPassword = false;
server.m_bSecure = true; server.m_bSecure = true;
server.m_ulTimeLastPlayed = 0; server.m_ulTimeLastPlayed = 0;
server.m_nServerVersion = 1000; server.m_nServerVersion = 1000;
strcpy_s(server.m_szServerName, info.get("sv_hostname").data()); strcpy_s(server.m_szServerName, info.get("hostname").data());
const auto playmode = info.get("playmode"); const auto playmode = info.get("playmode");
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\true\ranked\true\hardcore\false\zombies\%s\modName\\playerCount\0)", R"(\gametype\%s\dedicated\%s\ranked\false\hardcore\false\zombies\%s\modName\\playerCount\0)",
info.get("gametype").data(), mode == game::MODE_ZOMBIES ? "true" : "false"); info.get("gametype").data(),
info.get("dedicated") == "1" ? "true" : "false",
mode == game::MODE_ZOMBIES ? "true" : "false");
strcpy_s(server.m_szGameTags, tags); strcpy_s(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);
return server; return server;
} }
void handle_server_respone(const bool success, const game::netadr_t& host, const ::utils::info_string& info,
const uint32_t ping)
{
bool all_handled = false;
std::optional<int> index{};
queried_servers.access([&](servers& srvs)
{
size_t i = 0;
for (; i < srvs.size(); ++i)
{
if (srvs[i].address == host)
{
break;
}
}
if (i >= srvs.size())
{
return;
}
index = static_cast<int>(i);
auto& srv = srvs[i];
srv.handled = true;
srv.server_item = create_server_item(host, info, ping, success);
for (const auto& entry : srvs)
{
if (!entry.handled)
{
return;
}
}
all_handled = true;
});
const auto res = current_response.load();
if (!index || !res)
{
return;
}
if (success)
{
res->ServerResponded(reinterpret_cast<void*>(1), *index);
}
else
{
res->ServerFailedToRespond(reinterpret_cast<void*>(1), *index);
}
if (all_handled)
{
res->RefreshComplete(reinterpret_cast<void*>(1), eServerResponded);
}
}
void ping_server(const game::netadr_t& server)
{
party::query_server(server, handle_server_respone);
}
} }
void* matchmaking_servers::RequestInternetServerList(unsigned int iApp, void** ppchFilters, unsigned int nFilters, void* matchmaking_servers::RequestInternetServerList(unsigned int iApp, void** ppchFilters, unsigned int nFilters,
matchmaking_server_list_response* pRequestServersResponse) matchmaking_server_list_response* pRequestServersResponse)
{ {
pRequestServersResponse->ServerResponded(reinterpret_cast<void*>(1), 0); current_response = pRequestServersResponse;
pRequestServersResponse->RefreshComplete(reinterpret_cast<void*>(1), eServerResponded);
server_list::request_servers([](const bool success, const std::unordered_set<game::netadr_t>& s)
{
const auto res = current_response.load();
if (!res)
{
return;
}
if (!success)
{
res->RefreshComplete(reinterpret_cast<void*>(1), eServerFailedToRespond);
return;
}
if (s.empty())
{
res->RefreshComplete(reinterpret_cast<void*>(1), eNoServersListedOnMasterServer);
return;
}
queried_servers.access([&s](servers& srvs)
{
srvs = {};
srvs.reserve(s.size());
for (auto& address : s)
{
server new_server{};
new_server.address = address;
new_server.server_item = create_server_item(address, {}, 0, false);
srvs.push_back(new_server);
}
});
for (auto& srv : s)
{
ping_server(srv);
}
});
return reinterpret_cast<void*>(1); return reinterpret_cast<void*>(1);
} }
@ -120,11 +212,27 @@ namespace steam
void matchmaking_servers::ReleaseRequest(void* hServerListRequest) void matchmaking_servers::ReleaseRequest(void* hServerListRequest)
{ {
current_response = nullptr;
} }
gameserveritem_t* matchmaking_servers::GetServerDetails(void* hRequest, int iServer) gameserveritem_t* matchmaking_servers::GetServerDetails(void* hRequest, int iServer)
{ {
return get_server_item(); if (reinterpret_cast<void*>(1) != hRequest)
{
return nullptr;
}
static thread_local gameserveritem_t server_item{};
return queried_servers.access<gameserveritem_t*>([iServer](const servers& s) -> gameserveritem_t*
{
if (iServer < 0 || static_cast<size_t>(iServer) >= s.size())
{
return nullptr;
}
server_item = s[iServer].server_item;
return &server_item;
});
} }
void matchmaking_servers::CancelQuery(void* hRequest) void matchmaking_servers::CancelQuery(void* hRequest)
@ -142,7 +250,15 @@ namespace steam
int matchmaking_servers::GetServerCount(void* hRequest) int matchmaking_servers::GetServerCount(void* hRequest)
{ {
return (reinterpret_cast<void*>(1) == hRequest) ? 1 : 0; if (reinterpret_cast<void*>(1) != hRequest)
{
return 0;
}
return queried_servers.access<int>([](const servers& s)
{
return static_cast<int>(s.size());
});
} }
void matchmaking_servers::RefreshServer(void* hRequest, int iServer) void matchmaking_servers::RefreshServer(void* hRequest, int iServer)
@ -155,15 +271,13 @@ namespace steam
auto response = pRequestServersResponse; auto response = pRequestServersResponse;
const auto addr = network::address_from_ip(htonl(unIP), usPort); const auto addr = network::address_from_ip(htonl(unIP), usPort);
OutputDebugStringA(::utils::string::va("Sending: %u", (uint32_t)usPort));
party::query_server( party::query_server(
addr, [response](const bool success, const game::netadr_t& host, const ::utils::info_string& info) addr, [response](const bool success, const game::netadr_t& host, const ::utils::info_string& info,
const uint32_t ping)
{ {
OutputDebugStringA(::utils::string::va("Responded: %s", success ? "true" : "false"));
if (success) if (success)
{ {
auto server_item = create_server_item(host, info); auto server_item = create_server_item(host, info, ping, success);
response->ServerResponded(server_item); response->ServerResponded(server_item);
} }
else else