Some experiments
This commit is contained in:
parent
442a8f2f6f
commit
6b31b2a7ae
@ -205,11 +205,19 @@ namespace network
|
|||||||
return a.port == b.port && a.addr == b.addr;
|
return a.port == b.port && a.addr == b.addr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
uint64_t ret2()
|
||||||
|
{
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
|
||||||
class component final : public component_interface
|
class component final : public component_interface
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
void post_unpack() override
|
void post_unpack() override
|
||||||
{
|
{
|
||||||
|
//utils::hook::jump(0x14143CAB0_g, ret2); // patch dwGetConnectionStatus
|
||||||
|
//utils::hook::jump(0x14233307E_g, 0x1423330C7_g);
|
||||||
|
|
||||||
utils::hook::nop(0x142332E76_g, 4); // don't increment data pointer to optionally skip socket byte
|
utils::hook::nop(0x142332E76_g, 4); // don't increment data pointer to optionally skip socket byte
|
||||||
utils::hook::call(0x142332E43_g, read_socket_byte_stub); // optionally read socket byte
|
utils::hook::call(0x142332E43_g, read_socket_byte_stub); // optionally read socket byte
|
||||||
utils::hook::call(0x142332E81_g, verify_checksum_stub); // skip checksum verification
|
utils::hook::call(0x142332E81_g, verify_checksum_stub); // skip checksum verification
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
#include <std_include.hpp>
|
#include <std_include.hpp>
|
||||||
#include "loader/component_loader.hpp"
|
#include "loader/component_loader.hpp"
|
||||||
|
|
||||||
|
#include "party.hpp"
|
||||||
#include "network.hpp"
|
#include "network.hpp"
|
||||||
#include "scheduler.hpp"
|
#include "scheduler.hpp"
|
||||||
#include "game/game.hpp"
|
#include "game/game.hpp"
|
||||||
@ -10,6 +11,7 @@
|
|||||||
#include <utils/string.hpp>
|
#include <utils/string.hpp>
|
||||||
#include <utils/info_string.hpp>
|
#include <utils/info_string.hpp>
|
||||||
#include <utils/cryptography.hpp>
|
#include <utils/cryptography.hpp>
|
||||||
|
#include <utils/concurrency.hpp>
|
||||||
|
|
||||||
#include <version.hpp>
|
#include <version.hpp>
|
||||||
|
|
||||||
@ -17,24 +19,20 @@ namespace party
|
|||||||
{
|
{
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
struct
|
game::netadr_t connect_host{{}, {}, game::NA_BAD, {}};
|
||||||
{
|
|
||||||
game::netadr_t host{{}, {}, game::NA_BAD, {}};
|
|
||||||
std::string challenge{};
|
|
||||||
} connect_state{};
|
|
||||||
|
|
||||||
void connect_stub(const char* address)
|
struct server_query
|
||||||
{
|
{
|
||||||
const auto target = network::address_from_string(address);
|
game::netadr_t host;
|
||||||
if (target.type == game::NA_BAD)
|
std::string challenge;
|
||||||
|
query_callback callback;
|
||||||
|
std::chrono::high_resolution_clock::time_point query_time;
|
||||||
|
};
|
||||||
|
|
||||||
|
utils::concurrency::container<std::vector<server_query>>& get_server_queries()
|
||||||
{
|
{
|
||||||
return;
|
static utils::concurrency::container<std::vector<server_query>> server_queries;
|
||||||
}
|
return server_queries;
|
||||||
|
|
||||||
connect_state.host = target;
|
|
||||||
connect_state.challenge = utils::cryptography::random::get_challenge();
|
|
||||||
|
|
||||||
network::send(target, "getInfo", connect_state.challenge);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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)
|
||||||
@ -85,61 +83,71 @@ namespace party
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string get_dvar_string(const char* dvar_name)
|
game::LobbyMainMode convert_mode(const game::eModes mode)
|
||||||
{
|
{
|
||||||
const auto dvar = game::Dvar_FindVar(dvar_name);
|
switch (mode)
|
||||||
if (!dvar)
|
|
||||||
{
|
{
|
||||||
return {};
|
case game::MODE_CAMPAIGN:
|
||||||
}
|
return game::LOBBY_MAINMODE_CP;
|
||||||
|
case game::MODE_MULTIPLAYER:
|
||||||
return game::Dvar_GetString(dvar);
|
return game::LOBBY_MAINMODE_MP;
|
||||||
|
case game::MODE_ZOMBIES:
|
||||||
|
return game::LOBBY_MAINMODE_ZM;
|
||||||
|
default:
|
||||||
|
return game::LOBBY_MAINMODE_INVALID;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class component final : public component_interface
|
void connect_to_session(const game::netadr_t& addr, const std::string& hostname, const uint64_t xuid,
|
||||||
|
const game::eModes mode)
|
||||||
{
|
{
|
||||||
public:
|
const auto LobbyJoin_Begin = reinterpret_cast<bool(*)(int actionId, game::ControllerIndex_t controllerIndex,
|
||||||
void post_unpack() override
|
game::LobbyType sourceLobbyType,
|
||||||
{
|
game::LobbyType targetLobbyType)>(0x141ED9540_g);
|
||||||
utils::hook::jump(0x141EE6030_g, connect_stub);
|
|
||||||
|
|
||||||
network::on("getInfo", [](const game::netadr_t& target, const network::data_view& data)
|
if (!LobbyJoin_Begin(0, game::CONTROLLER_INDEX_FIRST, game::LOBBY_TYPE_PRIVATE, game::LOBBY_TYPE_PRIVATE))
|
||||||
{
|
|
||||||
utils::info_string info{};
|
|
||||||
info.set("challenge", std::string(data.begin(), data.end()));
|
|
||||||
info.set("gamename", "T7");
|
|
||||||
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));
|
|
||||||
info.set("mapname", get_dvar_string("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()));
|
|
||||||
//info.set("sv_maxclients", utils::string::va("%i", *game::mp::svs_numclients));
|
|
||||||
info.set("protocol", utils::string::va("%i", 1/*PROTOCOL*/));
|
|
||||||
info.set("playmode", utils::string::va("%i", game::Com_SessionMode_GetMode()));
|
|
||||||
//info.set("sv_running", utils::string::va("%i", get_dvar_bool("sv_running")));
|
|
||||||
//info.set("dedicated", utils::string::va("%i", get_dvar_bool("dedicated")));
|
|
||||||
info.set("shortversion", SHORTVERSION);
|
|
||||||
|
|
||||||
network::send(target, "infoResponse", info.build(), '\n');
|
|
||||||
});
|
|
||||||
|
|
||||||
network::on("infoResponse", [](const game::netadr_t& target, const network::data_view& data)
|
|
||||||
{
|
|
||||||
const utils::info_string info{data};
|
|
||||||
|
|
||||||
if (connect_state.host != target)
|
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (info.get("challenge") != connect_state.challenge)
|
auto& join = *game::s_join;
|
||||||
|
|
||||||
|
auto& host = join.hostList[0];
|
||||||
|
memset(&host, 0, sizeof(host));
|
||||||
|
|
||||||
|
host.info.netAdr = addr;
|
||||||
|
host.info.xuid = xuid;
|
||||||
|
strcpy_s(host.info.name, hostname.data());
|
||||||
|
|
||||||
|
host.lobbyType = game::LOBBY_TYPE_PRIVATE;
|
||||||
|
host.lobbyParams.networkMode = game::LOBBY_NETWORKMODE_LIVE;
|
||||||
|
host.lobbyParams.mainMode = convert_mode(mode);
|
||||||
|
|
||||||
|
host.retryCount = 0;
|
||||||
|
host.retryTime = game::Sys_Milliseconds();
|
||||||
|
|
||||||
|
join.potentialHost = host;
|
||||||
|
join.hostCount = 1;
|
||||||
|
join.processedCount = 1;
|
||||||
|
join.state = game::JOIN_SOURCE_STATE_ASSOCIATING;
|
||||||
|
join.startTime = game::Sys_Milliseconds();
|
||||||
|
|
||||||
|
/*join.targetLobbyType = game::LOBBY_TYPE_PRIVATE;
|
||||||
|
join.sourceLobbyType = game::LOBBY_TYPE_PRIVATE;
|
||||||
|
join.controllerIndex = game::CONTROLLER_INDEX_FIRST;
|
||||||
|
join.joinType = game::JOIN_TYPE_NORMAL;
|
||||||
|
join.joinResult = game::JOIN_RESULT_INVALID;
|
||||||
|
join.isFinalized = false;*/
|
||||||
|
|
||||||
|
// LobbyJoinSource_Finalize
|
||||||
|
join.isFinalized = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void handle_connect_query_response(const bool success, const game::netadr_t& target,
|
||||||
|
const utils::info_string& info)
|
||||||
|
{
|
||||||
|
if (!success)
|
||||||
{
|
{
|
||||||
const auto str = "Invalid challenge.";
|
|
||||||
printf("%s\n", str);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -167,14 +175,144 @@ namespace party
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//const auto hostname = info.get("sv_hostname");
|
||||||
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 xuid = strtoull(info.get("xuid").data(), nullptr, 16);
|
||||||
|
|
||||||
scheduler::once([=]
|
scheduler::once([=]
|
||||||
{
|
{
|
||||||
|
//connect_to_session(target, hostname, xuid, mode);
|
||||||
connect_to_lobby_with_mode(target, mode, mapname, gametype);
|
connect_to_lobby_with_mode(target, mode, mapname, gametype);
|
||||||
}, scheduler::main);
|
}, scheduler::main);
|
||||||
|
}
|
||||||
|
|
||||||
|
void connect_stub(const char* address)
|
||||||
|
{
|
||||||
|
const auto target = network::address_from_string(address);
|
||||||
|
if (target.type == game::NA_BAD)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
connect_host = target;
|
||||||
|
query_server(target, handle_connect_query_response);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string get_dvar_string(const char* dvar_name)
|
||||||
|
{
|
||||||
|
const auto dvar = game::Dvar_FindVar(dvar_name);
|
||||||
|
if (!dvar)
|
||||||
|
{
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
return game::Dvar_GetString(dvar);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void query_server(const game::netadr_t& host, query_callback callback)
|
||||||
|
{
|
||||||
|
const auto challenge = utils::cryptography::random::get_challenge();
|
||||||
|
|
||||||
|
server_query query{};
|
||||||
|
query.host = host;
|
||||||
|
query.query_time = std::chrono::high_resolution_clock::now();
|
||||||
|
query.callback = std::move(callback);
|
||||||
|
query.challenge = challenge;
|
||||||
|
|
||||||
|
get_server_queries().access([&](std::vector<server_query>& server_queries)
|
||||||
|
{
|
||||||
|
server_queries.emplace_back(std::move(query));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
network::send(host, "getInfo", challenge);
|
||||||
|
}
|
||||||
|
|
||||||
|
class component final : public component_interface
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
void post_unpack() override
|
||||||
|
{
|
||||||
|
utils::hook::jump(0x141E19B60_g, 0x141E19BB6_g); // patch steam id validity check
|
||||||
|
utils::hook::jump(0x141EE6030_g, connect_stub);
|
||||||
|
|
||||||
|
network::on("getInfo", [](const game::netadr_t& target, const network::data_view& data)
|
||||||
|
{
|
||||||
|
utils::info_string info{};
|
||||||
|
info.set("challenge", std::string(data.begin(), data.end()));
|
||||||
|
info.set("gamename", "T7");
|
||||||
|
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));
|
||||||
|
info.set("mapname", get_dvar_string("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()));
|
||||||
|
//info.set("sv_maxclients", utils::string::va("%i", *game::mp::svs_numclients));
|
||||||
|
info.set("protocol", utils::string::va("%i", 1/*PROTOCOL*/));
|
||||||
|
info.set("playmode", utils::string::va("%i", game::Com_SessionMode_GetMode()));
|
||||||
|
//info.set("sv_running", utils::string::va("%i", get_dvar_bool("sv_running")));
|
||||||
|
//info.set("dedicated", utils::string::va("%i", get_dvar_bool("dedicated")));
|
||||||
|
info.set("shortversion", SHORTVERSION);
|
||||||
|
|
||||||
|
network::send(target, "infoResponse", info.build(), '\n');
|
||||||
|
});
|
||||||
|
|
||||||
|
network::on("infoResponse", [](const game::netadr_t& target, const network::data_view& data)
|
||||||
|
{
|
||||||
|
bool found_query = false;
|
||||||
|
server_query query{};
|
||||||
|
|
||||||
|
const utils::info_string info{data};
|
||||||
|
|
||||||
|
get_server_queries().access([&](std::vector<server_query>& server_queries)
|
||||||
|
{
|
||||||
|
for (auto i = server_queries.begin(); i != server_queries.end(); ++i)
|
||||||
|
{
|
||||||
|
if (i->host == target && i->challenge == info.get("challenge"))
|
||||||
|
{
|
||||||
|
found_query = true;
|
||||||
|
query = std::move(*i);
|
||||||
|
i = server_queries.erase(i);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (found_query)
|
||||||
|
{
|
||||||
|
query.callback(true, query.host, info);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
scheduler::loop([]
|
||||||
|
{
|
||||||
|
std::vector<server_query> removed_queries{};
|
||||||
|
|
||||||
|
get_server_queries().access([&](std::vector<server_query>& server_queries)
|
||||||
|
{
|
||||||
|
const auto now = std::chrono::high_resolution_clock::now();
|
||||||
|
for (auto i = server_queries.begin(); i != server_queries.end();)
|
||||||
|
{
|
||||||
|
if ((now - i->query_time) < 10s)
|
||||||
|
{
|
||||||
|
++i;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
removed_queries.emplace_back(std::move(*i));
|
||||||
|
i = server_queries.erase(i);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const utils::info_string empty{};
|
||||||
|
for (const auto& query : removed_queries)
|
||||||
|
{
|
||||||
|
query.callback(false, query.host, empty);
|
||||||
|
}
|
||||||
|
}, scheduler::async, 1s);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
12
src/client/component/party.hpp
Normal file
12
src/client/component/party.hpp
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <game/game.hpp>
|
||||||
|
#include <utils/info_string.hpp>
|
||||||
|
|
||||||
|
namespace party
|
||||||
|
{
|
||||||
|
using query_callback_func = void(bool success, const game::netadr_t& host, const ::utils::info_string& info);
|
||||||
|
using query_callback = std::function<query_callback_func>;
|
||||||
|
|
||||||
|
void query_server(const game::netadr_t& host, query_callback callback);
|
||||||
|
}
|
@ -1,5 +1,8 @@
|
|||||||
#include <std_include.hpp>
|
#include <std_include.hpp>
|
||||||
#include "../steam.hpp"
|
#include "../steam.hpp"
|
||||||
|
#include "component/party.hpp"
|
||||||
|
#include "component/network.hpp"
|
||||||
|
#include "utils/string.hpp"
|
||||||
|
|
||||||
namespace steam
|
namespace steam
|
||||||
{
|
{
|
||||||
@ -8,9 +11,9 @@ namespace steam
|
|||||||
gameserveritem_t* get_server_item()
|
gameserveritem_t* get_server_item()
|
||||||
{
|
{
|
||||||
static gameserveritem_t server{};
|
static gameserveritem_t server{};
|
||||||
server.m_NetAdr.m_usConnectionPort = 27017;
|
server.m_NetAdr.m_usConnectionPort = 28960;
|
||||||
server.m_NetAdr.m_usQueryPort = 27017;
|
server.m_NetAdr.m_usQueryPort = 28960;
|
||||||
server.m_NetAdr.m_unIP = 0x7F000001;
|
server.m_NetAdr.m_unIP = ntohl(inet_addr("192.168.178.34"));
|
||||||
server.m_nPing = 10;
|
server.m_nPing = 10;
|
||||||
server.m_bHadSuccessfulResponse = true;
|
server.m_bHadSuccessfulResponse = true;
|
||||||
server.m_bDoNotRefresh = false;
|
server.m_bDoNotRefresh = false;
|
||||||
@ -27,11 +30,46 @@ namespace steam
|
|||||||
server.m_nServerVersion = 1000;
|
server.m_nServerVersion = 1000;
|
||||||
strcpy_s(server.m_szServerName, "BO^3I^5I^6I ^7Server");
|
strcpy_s(server.m_szServerName, "BO^3I^5I^6I ^7Server");
|
||||||
strcpy_s(server.m_szGameTags,
|
strcpy_s(server.m_szGameTags,
|
||||||
R"(\gametype\gun\dedicated\true\ranked\true\hardcore\false\zombies\false\modName\usermaps\playerCount\0)");
|
R"(\gametype\gun\dedicated\true\ranked\true\hardcore\false\zombies\false\modName\\playerCount\0)");
|
||||||
server.m_steamID = steam_id();
|
server.m_steamID = steam_id();
|
||||||
|
|
||||||
return &server;
|
return &server;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
gameserveritem_t create_server_item(const game::netadr_t& address, const ::utils::info_string& info)
|
||||||
|
{
|
||||||
|
gameserveritem_t server{};
|
||||||
|
server.m_NetAdr.m_usConnectionPort = address.port;
|
||||||
|
server.m_NetAdr.m_usQueryPort = address.port;
|
||||||
|
server.m_NetAdr.m_unIP = address.addr;
|
||||||
|
server.m_nPing = 10;
|
||||||
|
server.m_bHadSuccessfulResponse = true;
|
||||||
|
server.m_bDoNotRefresh = false;
|
||||||
|
strcpy_s(server.m_szGameDir, "");
|
||||||
|
strcpy_s(server.m_szMap, info.get("mapname").data());
|
||||||
|
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, info.get("sv_hostname").data());
|
||||||
|
|
||||||
|
const auto playmode = info.get("playmode");
|
||||||
|
const auto mode = game::eModes(std::atoi(playmode.data()));
|
||||||
|
|
||||||
|
const auto* tags = ::utils::string::va(
|
||||||
|
R"(\gametype\%s\dedicated\true\ranked\true\hardcore\false\zombies\%s\modName\\playerCount\0)",
|
||||||
|
info.get("gametype").data(), mode == game::MODE_ZOMBIES ? "true" : "false");
|
||||||
|
|
||||||
|
strcpy_s(server.m_szGameTags, tags);
|
||||||
|
server.m_steamID.bits = strtoull(info.get("xuid").data(), nullptr, 16);
|
||||||
|
|
||||||
|
return server;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void* matchmaking_servers::RequestInternetServerList(unsigned int iApp, void** ppchFilters, unsigned int nFilters,
|
void* matchmaking_servers::RequestInternetServerList(unsigned int iApp, void** ppchFilters, unsigned int nFilters,
|
||||||
@ -106,8 +144,27 @@ namespace steam
|
|||||||
void* matchmaking_servers::PingServer(unsigned int unIP, unsigned short usPort,
|
void* matchmaking_servers::PingServer(unsigned int unIP, unsigned short usPort,
|
||||||
matchmaking_ping_response* pRequestServersResponse)
|
matchmaking_ping_response* pRequestServersResponse)
|
||||||
{
|
{
|
||||||
pRequestServersResponse->ServerResponded(*get_server_item());
|
auto response = pRequestServersResponse;
|
||||||
return reinterpret_cast<void*>(7);
|
const auto addr = network::address_from_ip(htonl(unIP), usPort);
|
||||||
|
|
||||||
|
OutputDebugStringA(::utils::string::va("Sending: %u", (uint32_t)usPort));
|
||||||
|
|
||||||
|
party::query_server(
|
||||||
|
addr, [response](const bool success, const game::netadr_t& host, const ::utils::info_string& info)
|
||||||
|
{
|
||||||
|
OutputDebugStringA(::utils::string::va("Responded: %s", success ? "true" : "false"));
|
||||||
|
if (success)
|
||||||
|
{
|
||||||
|
auto server_item = create_server_item(host, info);
|
||||||
|
response->ServerResponded(server_item);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
response->ServerFailedToRespond();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return reinterpret_cast<void*>(static_cast<uint64_t>(7 + rand()));
|
||||||
}
|
}
|
||||||
|
|
||||||
int matchmaking_servers::PlayerDetails(unsigned int unIP, unsigned short usPort, void* pRequestServersResponse)
|
int matchmaking_servers::PlayerDetails(unsigned int unIP, unsigned short usPort, void* pRequestServersResponse)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user