Merge remote-tracking branch 'upstream/main'

This commit is contained in:
Jari van der Kaap 2023-02-12 19:47:38 +01:00
commit 8d1825d12f
28 changed files with 843 additions and 145 deletions

View File

@ -25,7 +25,7 @@ jobs:
- Release
steps:
- name: Check out files
uses: actions/checkout@v3
uses: actions/checkout@v3.3.0
with:
submodules: true
fetch-depth: 0
@ -33,10 +33,10 @@ jobs:
lfs: false
- name: Add msbuild to PATH
uses: microsoft/setup-msbuild@v1.1.3
uses: microsoft/setup-msbuild@v1.3.1
- name: Generate project files
run: tools/premake5 vs2022
run: tools/premake5 vs2022 --dev-build
- name: Set up problem matching
uses: ammaraskar/msvc-problem-matcher@master
@ -45,7 +45,7 @@ jobs:
run: msbuild /m /v:minimal /p:Configuration=${{matrix.configuration}} /p:Platform=x64 build/boiii.sln
- name: Upload ${{matrix.configuration}} bundle
uses: actions/upload-artifact@v3.1.0
uses: actions/upload-artifact@v3.1.2
with:
name: ${{matrix.configuration}} Bundle
path: |
@ -54,7 +54,7 @@ jobs:
- name: Upload ${{matrix.configuration}} binary
if: matrix.configuration == 'Release'
uses: actions/upload-artifact@v3.1.0
uses: actions/upload-artifact@v3.1.2
with:
name: ${{matrix.configuration}} Binary
path: |
@ -62,7 +62,7 @@ jobs:
- name: Upload version
if: matrix.configuration == 'Release'
uses: actions/upload-artifact@v3.1.0
uses: actions/upload-artifact@v3.1.2
with:
name: Version
path: |

3
.gitmodules vendored
View File

@ -28,3 +28,6 @@
[submodule "deps/stb"]
path = deps/stb
url = https://github.com/nothings/stb.git
[submodule "deps/curl"]
path = deps/curl
url = https://github.com/curl/curl.git

View File

@ -21,7 +21,7 @@ Reverse engineering and analysis of Call of Duty: Black Ops 3. Very experimental
- [ ] Disable Anti-Debugging Mechanisms (probably never gonna happen cause who needs that if you have printf debugging)
- [x] Process wrapper
- [x] P2P multiplayer
- [ ] Dedicated Servers
- [x] Dedicated Servers
## Disclaimer

1
deps/curl vendored Submodule

@ -0,0 +1 @@
Subproject commit 7ce140ba97de1bf3e27299a72b0cc229c9e1364e

86
deps/premake/curl.lua vendored Normal file
View File

@ -0,0 +1,86 @@
curl = {
source = path.join(dependencies.basePath, "curl"),
}
function curl.import()
links { "curl" }
filter "toolset:msc*"
links { "Crypt32.lib" }
filter {}
curl.includes()
end
function curl.includes()
filter "toolset:msc*"
includedirs {
path.join(curl.source, "include"),
}
defines {
"CURL_STRICTER",
"CURL_STATICLIB",
"CURL_DISABLE_DICT",
"CURL_DISABLE_FILE",
"CURL_DISABLE_LDAP",
"CURL_DISABLE_LDAPS",
"CURL_DISABLE_FTP",
"CURL_DISABLE_GOPHER",
"CURL_DISABLE_IMAP",
"CURL_DISABLE_MQTT",
"CURL_DISABLE_POP3",
"CURL_DISABLE_RTSP",
"CURL_DISABLE_SMTP",
"CURL_DISABLE_SMB",
"CURL_DISABLE_TELNET",
"CURL_DISABLE_TFTP",
}
filter {}
end
function curl.project()
if not os.istarget("windows") then
return
end
project "curl"
language "C"
curl.includes()
includedirs {
path.join(curl.source, "lib"),
}
files {
path.join(curl.source, "lib/**.c"),
path.join(curl.source, "lib/**.h"),
}
defines {
"BUILDING_LIBCURL",
}
filter "toolset:msc*"
defines {
"USE_SCHANNEL",
"USE_WINDOWS_SSPI",
"USE_THREADS_WIN32",
}
filter "toolset:not msc*"
defines {
"USE_GNUTLS",
"USE_THREADS_POSIX",
}
filter {}
warnings "Off"
kind "StaticLib"
end
table.insert(dependencies, curl)

View File

@ -1,7 +1,13 @@
#include <std_include.hpp>
#include "dedicated.hpp"
#include "loader/component_loader.hpp"
#include "game/game.hpp"
#include "game/utils.hpp"
#include "command.hpp"
#include "network.hpp"
#include "scheduler.hpp"
#include "server_list.hpp"
#include <utils/hook.hpp>
@ -9,10 +15,42 @@ namespace dedicated
{
namespace
{
void sv_con_tell_f_stub(game::client_s* cl_0, game::svscmd_type type, [[maybe_unused]] const char* fmt, [[maybe_unused]] int c, char* text)
void sv_con_tell_f_stub(game::client_s* cl_0, game::svscmd_type type, [[maybe_unused]] const char* fmt,
[[maybe_unused]] int c, char* text)
{
game::SV_SendServerCommand(cl_0, type, "%c \"GAME_SERVER\x15: %s\"", 79, text);
}
void send_heartbeat_packet()
{
game::netadr_t target{};
if (server_list::get_master_server(target))
{
network::send(target, "heartbeat", "T7");
}
}
}
void send_heartbeat()
{
if (!game::is_server())
{
return;
}
scheduler::once(send_heartbeat_packet, scheduler::pipeline::main, 5s);
}
void trigger_map_rotation()
{
scheduler::once([]
{
if (!game::get_dvar_string("sv_maprotation").empty())
{
game::Cbuf_AddText(0, "map_rotate\n");
send_heartbeat();
}
}, scheduler::pipeline::main, 1s);
}
struct component final : server_component
@ -20,11 +58,17 @@ namespace dedicated
void post_unpack() override
{
// Ignore "bad stats"
utils::hook::set<uint8_t>(0x14052D523_g, 0xEB);
utils::hook::nop(0x14052D4E4_g, 2);
//utils::hook::set<uint8_t>(0x14052D523_g, 0xEB);
//utils::hook::nop(0x14052D4E4_g, 2);
// Fix tell command for IW4M
utils::hook::call(0x14052A8CF_g, sv_con_tell_f_stub);
scheduler::loop(send_heartbeat, scheduler::pipeline::main, 5min);
command::add("heartbeat", send_heartbeat);
// Hook GScr_ExitLevel
utils::hook::jump(0x1402D1AA0_g, trigger_map_rotation);
}
};
}

View File

@ -0,0 +1,6 @@
#pragma once
namespace dedicated
{
void send_heartbeat();
}

View File

@ -12,19 +12,38 @@
#include <version.hpp>
#include "game/utils.hpp"
namespace getinfo
{
namespace
{
std::string get_dvar_string(const char* dvar_name)
int get_max_client_count()
{
const auto dvar = game::Dvar_FindVar(dvar_name);
if (!dvar)
{
return {};
return game::get_dvar_int("com_maxclients");
}
return game::Dvar_GetString(dvar);
int get_client_count()
{
int count = 0;
const auto client_states = *reinterpret_cast<uint64_t*>(game::select(0x1576FB318, 0x14A178E98));
const auto object_length = game::is_server() ? 0xE5110 : 0xE5170;
for (int i = 0; i < get_max_client_count(); ++i)
{
const auto client_state = *reinterpret_cast<int*>(client_states + (i * object_length));
if (client_state > 0)
{
++count;
}
}
return count;
}
int Com_SessionMode_GetGameMode()
{
return *reinterpret_cast<int*>(game::select(0x1568EF7F4, 0x14948DB04)) << 14 >> 28;
}
}
@ -44,17 +63,19 @@ namespace getinfo
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("hostname", game::get_dvar_string(game::is_server() ? "live_steam_server_name" : "sv_hostname"));
info.set("gametype", game::get_dvar_string("g_gametype"));
//info.set("sv_motd", get_dvar_string("sv_motd"));
info.set("description", game::is_server() ? game::get_dvar_string("live_steam_server_description") : "");
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("mapname", game::get_dvar_string("mapname"));
info.set("isPrivate", game::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()*/0));
info.set("sv_maxclients", utils::string::va("%i", get_max_client_count()));
info.set("protocol", utils::string::va("%i", PROTOCOL));
info.set("playmode", utils::string::va("%i", game::Com_SessionMode_GetMode()));
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("dedicated", utils::string::va("%i", game::is_server() ? 1 : 0));
info.set("shortversion", SHORTVERSION);

View File

@ -0,0 +1,37 @@
#include <std_include.hpp>
#include "loader/component_loader.hpp"
#include "game/game.hpp"
#include <utils/hook.hpp>
namespace intro
{
namespace
{
utils::hook::detour cinematic_start_playback_hook;
void ccc(const char* name, const char* key, const unsigned int playback_flags, const float volume,
void* callback_info, const int id)
{
if (name == "BO3_Global_Logo_LogoSequence"s)
{
return;
}
cinematic_start_playback_hook.invoke(name, key, playback_flags, volume, callback_info, id);
}
}
class component final : public client_component
{
public:
void post_unpack() override
{
cinematic_start_playback_hook.create(game::Cinematic_StartPlayback, ccc);
}
};
}
#ifdef DEV_BUILD
REGISTER_COMPONENT(intro::component)
#endif

View File

@ -153,12 +153,12 @@ namespace network
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);
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));
}

View File

@ -7,6 +7,7 @@
#include "scheduler.hpp"
#include <utils/hook.hpp>
#include <utils/string.hpp>
#include <utils/info_string.hpp>
#include <utils/cryptography.hpp>
#include <utils/concurrency.hpp>
@ -15,14 +16,16 @@ namespace party
{
namespace
{
std::atomic_bool is_connecting_to_dedi{false};
game::netadr_t connect_host{{}, {}, game::NA_BAD, {}};
struct server_query
{
game::netadr_t host;
std::string challenge;
query_callback callback;
std::chrono::high_resolution_clock::time_point query_time;
bool sent{false};
game::netadr_t host{};
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()
@ -125,7 +128,7 @@ namespace party
host.info.netAdr = addr;
host.info.xuid = xuid;
strcpy_s(host.info.name, hostname.data());
utils::string::copy(host.info.name, hostname.data());
host.lobbyType = game::LOBBY_TYPE_PRIVATE;
host.lobbyParams.networkMode = game::LOBBY_NETWORKMODE_LIVE;
@ -152,13 +155,15 @@ namespace party
}
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)
{
return;
}
is_connecting_to_dedi = info.get("dedicated") == "1";
const auto gamename = info.get("gamename");
if (gamename != "T7"s)
{
@ -185,7 +190,7 @@ namespace party
//const auto hostname = info.get("sv_hostname");
const auto playmode = info.get("playmode");
const auto mode = game::eModes(std::atoi(playmode.data()));
const auto mode = static_cast<game::eModes>(std::atoi(playmode.data()));
//const auto xuid = strtoull(info.get("xuid").data(), nullptr, 16);
scheduler::once([=]
@ -206,24 +211,43 @@ namespace party
connect_host = target;
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)
{
const auto challenge = utils::cryptography::random::get_challenge();
server_query query{};
query.sent = false;
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);
int should_transfer_stub(uint8_t* storage_file_info)
{
auto should_transfer = game::ShouldTransfer(storage_file_info);
const auto offset = storage_file_info - reinterpret_cast<uint8_t*>(0x14343CDF0_g);
const auto index = offset / 120;
if (is_connecting_to_dedi && index >= 12 && index <= 15)
{
should_transfer = !should_transfer;
}
return should_transfer;
}
struct component final : client_component
@ -231,6 +255,7 @@ namespace party
void post_unpack() override
{
utils::hook::jump(0x141EE6030_g, connect_stub);
utils::hook::call(0x1422781E3_g, should_transfer_stub);
network::on("infoResponse", [](const game::netadr_t& target, const network::data_view& data)
{
@ -255,7 +280,10 @@ namespace party
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));
}
});
@ -265,10 +293,23 @@ namespace party
get_server_queries().access([&](std::vector<server_query>& server_queries)
{
size_t sent_queries = 0;
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)
if (!i->sent)
{
if (++sent_queries < 10)
{
send_server_query(*i);
}
++i;
continue;
}
if ((now - i->query_time) < 2s)
{
++i;
continue;
@ -282,9 +323,17 @@ namespace party
const utils::info_string empty{};
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
{
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>;
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

@ -205,7 +205,7 @@ namespace steam_proxy
}
}
struct component final : generic_component
struct component final : client_component
{
void post_load() override
{

View File

@ -1,5 +1,6 @@
#include <std_include.hpp>
#include "byte_buffer.hpp"
#include <utils/string.hpp>
namespace demonware
{
@ -90,7 +91,7 @@ namespace demonware
{
if (!this->read_data_type(16)) return false;
strcpy_s(output, length, const_cast<char*>(this->buffer_.data()) + this->current_byte_);
utils::string::copy(output, static_cast<size_t>(length), const_cast<char*>(this->buffer_.data()) + this->current_byte_);
this->current_byte_ += strlen(output) + 1;
return true;

View File

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

View File

@ -86,6 +86,7 @@ namespace game
0x1422D1360
};
WEAK symbol<void (void (*callback)(const dvar_t*, void*), void* userData)> Dvar_ForEach{ 0x1422BD760 };
WEAK symbol<int(const dvar_t* dvar)> Dvar_GetInt{0x1422BF2C0, 0x140575C20};
WEAK symbol<void(const char* dvarName, const char* string, bool createIfMissing)> Dvar_SetFromStringByName{
0x1422C7F60
};
@ -111,8 +112,11 @@ namespace game
};
WEAK symbol<unsigned int(scriptInstance_t inst)> Scr_GetNumParam{0x0, 0x140171320};
WEAK symbol<void(const char* name, const char* key, unsigned int playbackFlags, float volume, void* callbackInfo, int id)> Cinematic_StartPlayback{0x1412BE3A0};
WEAK symbol<void(uint64_t id, bool cancelAll)> Cinematic_StopPlayback{0x1412BEA70};
WEAK symbol<bool(void* storageFileInfo)> ShouldTransfer{0x142276E10};
// Rendering
WEAK symbol<void(const char*, int, const void*, float, float, float, float, float, const float*, int)>
R_AddCmdDrawText{

29
src/client/game/utils.cpp Normal file
View File

@ -0,0 +1,29 @@
#include <std_include.hpp>
#include "game.hpp"
#include "utils.hpp"
namespace game
{
std::string get_dvar_string(const char* dvar_name)
{
const auto dvar = Dvar_FindVar(dvar_name);
if (!dvar)
{
return {};
}
return Dvar_GetString(dvar);
}
int get_dvar_int(const char* dvar_name)
{
const auto dvar = Dvar_FindVar(dvar_name);
if (!dvar)
{
return {};
}
return Dvar_GetInt(dvar);
}
}

View File

@ -0,0 +1,7 @@
#pragma once
namespace game
{
std::string get_dvar_string(const char* dvar_name);
int get_dvar_int(const char* dvar_name);
}

View File

@ -262,7 +262,7 @@ namespace
throw std::runtime_error("Unable to load binary into memory");
}
if (has_server != game::is_server())
if (is_server != game::is_server())
{
throw std::runtime_error("Bad binary loaded into memory");
}

View File

@ -1,5 +1,6 @@
#include <std_include.hpp>
#include "../steam.hpp"
#include "../../component/dedicated.hpp"
namespace steam
{
@ -182,6 +183,7 @@ namespace steam
void game_server::EnableHeartbeats(bool bActive)
{
dedicated::send_heartbeat();
}
void game_server::SetHeartbeatInterval(int iHeartbeatInterval)

View File

@ -5,87 +5,180 @@
#include "component/party.hpp"
#include "component/network.hpp"
#include "component/server_list.hpp"
#include <utils/string.hpp>
#include <utils/concurrency.hpp>
namespace steam
{
namespace
{
gameserveritem_t* get_server_item()
struct server
{
static gameserveritem_t server{};
server.m_NetAdr.m_usConnectionPort = 28960;
server.m_NetAdr.m_usQueryPort = 28960;
bool handled{false};
game::netadr_t address{};
gameserveritem_t server_item{};
};
uint32_t address{};
inet_pton(AF_INET, "192.168.178.34", &address);
server.m_NetAdr.m_unIP = ntohl(address);
auto* const internet_request = reinterpret_cast<void*>(1);
server.m_nPing = 10;
server.m_bHadSuccessfulResponse = true;
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();
using servers = std::vector<server>;
return &server;
}
::utils::concurrency::container<servers> queried_servers{};
std::atomic<matchmaking_server_list_response*> current_response{};
gameserveritem_t create_server_item(const game::netadr_t& address, const ::utils::info_string& info)
gameserveritem_t create_server_item(const game::netadr_t& address, const ::utils::info_string& info,
const uint32_t ping, const bool success)
{
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_NetAdr.m_unIP = ntohl(address.addr);
server.m_nPing = static_cast<int>(ping);
server.m_bHadSuccessfulResponse = success;
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");
::utils::string::copy(server.m_szGameDir, "");
::utils::string::copy(server.m_szMap, info.get("mapname").data());
::utils::string::copy(server.m_szGameDescription, info.get("description").data());
server.m_nAppID = 311210;
server.m_nPlayers = 0;
server.m_nMaxPlayers = 18;
server.m_nBotPlayers = 0;
server.m_bPassword = false;
server.m_nPlayers = atoi(info.get("clients").data());
server.m_nMaxPlayers = atoi(info.get("sv_maxclients").data());
server.m_nBotPlayers = atoi(info.get("bots").data());
server.m_bPassword = info.get("isPrivate") == "1";
server.m_bSecure = true;
server.m_ulTimeLastPlayed = 0;
server.m_nServerVersion = 1000;
strcpy_s(server.m_szServerName, info.get("sv_hostname").data());
::utils::string::copy(server.m_szServerName, info.get("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");
R"(\gametype\%s\dedicated\%s\ranked\false\hardcore\false\zombies\%s\modName\\playerCount\%d)",
info.get("gametype").data(),
info.get("dedicated") == "1" ? "true" : "false",
mode == game::MODE_ZOMBIES ? "true" : "false", server.m_nPlayers);
strcpy_s(server.m_szGameTags, tags);
::utils::string::copy(server.m_szGameTags, tags);
server.m_steamID.bits = strtoull(info.get("xuid").data(), nullptr, 16);
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(internet_request, *index);
}
else
{
res->ServerFailedToRespond(internet_request, *index);
}
if (all_handled)
{
res->RefreshComplete(internet_request, 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,
matchmaking_server_list_response* pRequestServersResponse)
{
pRequestServersResponse->ServerResponded(reinterpret_cast<void*>(1), 0);
pRequestServersResponse->RefreshComplete(reinterpret_cast<void*>(1), eServerResponded);
return reinterpret_cast<void*>(1);
current_response = pRequestServersResponse;
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(internet_request, eServerFailedToRespond);
return;
}
if (s.empty())
{
res->RefreshComplete(internet_request, 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 internet_request;
}
void* matchmaking_servers::RequestLANServerList(unsigned int iApp,
@ -120,11 +213,30 @@ namespace steam
void matchmaking_servers::ReleaseRequest(void* hServerListRequest)
{
if (internet_request == hServerListRequest)
{
current_response = nullptr;
}
}
gameserveritem_t* matchmaking_servers::GetServerDetails(void* hRequest, int iServer)
{
return get_server_item();
if (internet_request != 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)
@ -142,28 +254,54 @@ namespace steam
int matchmaking_servers::GetServerCount(void* hRequest)
{
return (reinterpret_cast<void*>(1) == hRequest) ? 1 : 0;
}
void matchmaking_servers::RefreshServer(void* hRequest, int iServer)
if (internet_request != hRequest)
{
return 0;
}
void* matchmaking_servers::PingServer(unsigned int unIP, unsigned short usPort,
return queried_servers.access<int>([](const servers& s)
{
return static_cast<int>(s.size());
});
}
void matchmaking_servers::RefreshServer(void* hRequest, const int iServer)
{
if (internet_request != hRequest)
{
return;
}
std::optional<game::netadr_t> address{};
queried_servers.access([&](const servers& s)
{
if (iServer < 0 || static_cast<size_t>(iServer) >= s.size())
{
return;
}
address = s[iServer].address;
});
if (address)
{
ping_server(*address);
}
}
void* matchmaking_servers::PingServer(const unsigned int unIP, const unsigned short usPort,
matchmaking_ping_response* pRequestServersResponse)
{
auto response = pRequestServersResponse;
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)
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)
{
auto server_item = create_server_item(host, info);
auto server_item = create_server_item(host, info, ping, success);
response->ServerResponded(server_item);
}
else

View File

@ -104,8 +104,6 @@ namespace steam
results_.clear();
}
//extern "C" {
bool SteamAPI_RestartAppIfNecessary()
{
return false;
@ -282,6 +280,4 @@ namespace steam
static user_stats user_stats;
return &user_stats;
}
//}
}

View File

@ -1,48 +1,128 @@
#include "http.hpp"
#include "nt.hpp"
#include <atlcomcli.h>
#include <curl/curl.h>
#include "finally.hpp"
#pragma comment(lib, "ws2_32.lib")
namespace utils::http
{
std::optional<std::string> get_data(const std::string& url)
namespace
{
CComPtr<IStream> stream;
struct progress_helper
{
const std::function<void(size_t)>* callback{};
std::exception_ptr exception{};
};
if (FAILED(URLOpenBlockingStreamA(nullptr, url.data(), &stream, 0, nullptr)))
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);
}
}
catch (...)
{
helper->exception = std::current_exception();
return -1;
}
return 0;
}
size_t write_callback(void* contents, const size_t size, const size_t nmemb, void* userp)
{
auto* buffer = static_cast<std::string*>(userp);
const auto total_size = size * nmemb;
buffer->append(static_cast<char*>(contents), total_size);
return total_size;
}
}
std::optional<std::string> get_data(const std::string& url, const headers& headers,
const std::function<void(size_t)>& callback, const uint32_t retries)
{
curl_slist* header_list = nullptr;
auto* curl = curl_easy_init();
if (!curl)
{
return {};
}
char buffer[0x1000];
std::string result;
HRESULT status{};
do
auto _ = utils::finally([&]()
{
DWORD bytes_read = 0;
status = stream->Read(buffer, sizeof(buffer), &bytes_read);
curl_slist_free_all(header_list);
curl_easy_cleanup(curl);
});
if (bytes_read > 0)
for (const auto& header : headers)
{
result.append(buffer, bytes_read);
auto data = header.first + ": " + header.second;
header_list = curl_slist_append(header_list, data.data());
}
std::string buffer{};
progress_helper helper{};
helper.callback = &callback;
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, header_list);
curl_easy_setopt(curl, CURLOPT_URL, url.data());
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback);
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_NOPROGRESS, 0L);
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
curl_easy_setopt(curl, CURLOPT_USERAGENT, "xlabs-updater/1.0");
curl_easy_setopt(curl, CURLOPT_FAILONERROR, 1L);
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L);
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L);
for (auto i = 0u; i < retries + 1; ++i)
{
// Due to CURLOPT_FAILONERROR, CURLE_OK will not be met when the server returns 400 or 500
if (curl_easy_perform(curl) == CURLE_OK)
{
long http_code = 0;
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code);
if (http_code >= 200)
{
return { std::move(buffer) };
}
throw std::runtime_error(
"Bad status code " + std::to_string(http_code) + " met while trying to download file " + url);
}
if (helper.exception)
{
std::rethrow_exception(helper.exception);
}
long http_code = 0;
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code);
if (http_code > 0)
{
break;
}
}
while (SUCCEEDED(status) && status != S_FALSE);
if (FAILED(status))
{
return {};
}
return {result};
}
std::future<std::optional<std::string>> get_data_async(const std::string& url)
std::future<std::optional<std::string>> get_data_async(const std::string& url, const headers& headers)
{
return std::async(std::launch::async, [url]()
return std::async(std::launch::async, [url, headers]()
{
return get_data(url);
return get_data(url, headers);
});
}
}

View File

@ -6,6 +6,8 @@
namespace utils::http
{
std::optional<std::string> get_data(const std::string& url);
std::future<std::optional<std::string>> get_data_async(const std::string& url);
using headers = std::unordered_map<std::string, std::string>;
std::optional<std::string> get_data(const std::string& url, const headers& headers = {}, const std::function<void(size_t)>& callback = {}, uint32_t retries = 2);
std::future<std::optional<std::string>> get_data_async(const std::string& url, const headers& headers = {});
}

View File

@ -174,4 +174,28 @@ namespace utils::string
return str;
}
void copy(char* dest, const size_t max_size, const char* src)
{
if (!max_size)
{
return;
}
for (size_t i = 0;; ++i)
{
if (i + 1 == max_size)
{
dest[i] = 0;
break;
}
dest[i] = src[i];
if (!src[i])
{
break;
}
}
}
}

View File

@ -94,4 +94,12 @@ namespace utils::string
std::wstring convert(const std::string& str);
std::string replace(std::string str, const std::string& from, const std::string& to);
void copy(char* dest, size_t max_size, const char* src);
template <size_t Size>
void copy(char (&dest)[Size], const char* src)
{
copy(dest, Size, src);
}
}