From c38601fdcdde1aa21ad2eeaf1aee7406a2b293bb Mon Sep 17 00:00:00 2001 From: otavepto Date: Tue, 27 Feb 2024 21:17:53 +0200 Subject: [PATCH] Restore networking, source query, avatar, friends changes/additions from: 75e6d7c8ab466655f563963a3e4ec2edc6a4e1de, 4399c0b12bbbb85de8d121ab81a163c7883b70a7 --- dll/dll/network.h | 6 +- dll/dll/settings.h | 4 + dll/dll/source_query.h | 28 ++ dll/dll/steam_friends.h | 89 ++++- dll/dll/steam_gameserver.h | 14 +- dll/dll/steam_matchmaking.h | 126 ++++++- dll/dll/steam_matchmaking_servers.h | 14 + dll/dll/steam_user.h | 1 + dll/net.proto | 2 + dll/network.cpp | 87 +++++ dll/settings_parser.cpp | 10 + dll/source_query.cpp | 267 +++++++++++++ dll/steam_gameserver.cpp | 106 +++++- dll/steam_matchmaking_servers.cpp | 356 +++++++++++++++--- post_build/README.release.md | 9 + .../account_avatar.EXAMPLE.jpg | Bin 0 -> 3409 bytes .../account_avatar_default.EXAMPLE.jpg | Bin 0 -> 3409 bytes .../disable_account_avatar.EXAMPLE.txt | 1 + .../disable_source_query.EXAMPLE.txt | 1 + .../generate_emu_config.py | 2 + z_original_repo_files/Readme_release.txt | 5 + 21 files changed, 1063 insertions(+), 65 deletions(-) create mode 100644 dll/dll/source_query.h create mode 100644 dll/source_query.cpp create mode 100644 post_build/steam_settings.EXAMPLE/account_avatar.EXAMPLE.jpg create mode 100644 post_build/steam_settings.EXAMPLE/account_avatar_default.EXAMPLE.jpg create mode 100644 post_build/steam_settings.EXAMPLE/disable_account_avatar.EXAMPLE.txt create mode 100644 post_build/steam_settings.EXAMPLE/disable_source_query.EXAMPLE.txt diff --git a/dll/dll/network.h b/dll/dll/network.h index c99447c7..f370a4bc 100644 --- a/dll/dll/network.h +++ b/dll/dll/network.h @@ -91,8 +91,9 @@ struct Connection { class Networking { bool enabled = false; + bool query_alive; std::chrono::high_resolution_clock::time_point last_run; - sock_t udp_socket, tcp_socket; + sock_t query_socket, udp_socket, tcp_socket; uint16 udp_port, tcp_port; uint32 own_ip; std::vector connections; @@ -139,6 +140,9 @@ public: uint32 getIP(CSteamID id); uint32 getOwnIP(); + void startQuery(IP_PORT ip_port); + void shutDownQuery(); + bool isQueryAlive(); }; #endif diff --git a/dll/dll/settings.h b/dll/dll/settings.h index bbb80647..8de20d66 100644 --- a/dll/dll/settings.h +++ b/dll/dll/settings.h @@ -248,6 +248,7 @@ public: //images std::map images; int add_image(std::string data, uint32 width, uint32 height); + bool disable_account_avatar = false; //installed app ids, Steam_Apps::BIsAppInstalled() std::set installed_app_ids; @@ -265,6 +266,9 @@ public: //networking bool disable_networking = false; + //gameserver source query + bool disable_source_query = false; + //overlay bool disable_overlay = false; bool disable_overlay_achievement_notification = false; diff --git a/dll/dll/source_query.h b/dll/dll/source_query.h new file mode 100644 index 00000000..5f9742a7 --- /dev/null +++ b/dll/dll/source_query.h @@ -0,0 +1,28 @@ +/* Copyright (C) 2019 Mr Goldberg + This file is part of the Goldberg Emulator + + The Goldberg Emulator is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 3 of the License, or (at your option) any later version. + + The Goldberg Emulator is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with the Goldberg Emulator; if not, see + . */ + +#include "base.h" + +class Source_Query +{ + Source_Query () = delete; + ~Source_Query() = delete; + +public: + static std::vector handle_source_query(const void* buffer, size_t len, Gameserver const& gs); +}; + diff --git a/dll/dll/steam_friends.h b/dll/dll/steam_friends.h index 10fcb05f..64cc739c 100644 --- a/dll/dll/steam_friends.h +++ b/dll/dll/steam_friends.h @@ -102,12 +102,95 @@ struct Avatar_Numbers add_friend_avatars(CSteamID id) return avatar_ids->second; } - //TODO: get real image data from self/other peers struct Avatar_Numbers avatar_numbers; std::string small_avatar(32 * 32 * 4, 0); std::string medium_avatar(64 * 64 * 4, 0); std::string large_avatar(184 * 184 * 4, 0); + if (!(settings->disable_account_avatar) && (id == settings->get_local_steam_id())) { + std::string file_path; + unsigned long long file_size; + + for (int i = 0; i < 3; i++) { + std::string file_name; + if (i == 0) file_name = "account_avatar.png"; + if (i == 1) file_name = "account_avatar.jpg"; + if (i == 2) file_name = "account_avatar.jpeg"; + file_path = Local_Storage::get_game_settings_path() + file_name; + file_size = file_size_(file_path); + if (file_size) break; + } + + if (!file_size) { + for (int i = 0; i < 3; i++) { + std::string file_name; + if (i == 0) file_name = "account_avatar.png"; + if (i == 1) file_name = "account_avatar.jpg"; + if (i == 2) file_name = "account_avatar.jpeg"; + + if (settings->local_save.length() > 0) { + file_path = settings->local_save + "/settings/" + file_name; + } else { + file_path = Local_Storage::get_user_appdata_path() + "/settings/" + file_name; + } + + file_size = file_size_(file_path); + if (file_size) break; + } + } + + // no else statement here for default or else this breaks default images for friends + if (file_size) { + small_avatar = Local_Storage::load_image_resized(file_path, "", 32); + medium_avatar = Local_Storage::load_image_resized(file_path, "", 64); + large_avatar = Local_Storage::load_image_resized(file_path, "", 184); + } + } else if (!(settings->disable_account_avatar)) { + Friend *f = find_friend(id); + if (f && (large_avatar.compare(f->avatar()) != 0)) { + large_avatar = f->avatar(); + medium_avatar = Local_Storage::load_image_resized("", f->avatar(), 64); + small_avatar = Local_Storage::load_image_resized("", f->avatar(), 32); + } else { + std::string file_path; + unsigned long long file_size; + + for (int i = 0; i < 3; i++) { + std::string file_name; + if (i == 0) file_name = "account_avatar_default.png"; + if (i == 1) file_name = "account_avatar_default.jpg"; + if (i == 2) file_name = "account_avatar_default.jpeg"; + file_path = Local_Storage::get_game_settings_path() + file_name; + file_size = file_size_(file_path); + if (file_size) break; + } + + if (!file_size) { + for (int i = 0; i < 3; i++) { + std::string file_name; + if (i == 0) file_name = "account_avatar_default.png"; + if (i == 1) file_name = "account_avatar_default.jpg"; + if (i == 2) file_name = "account_avatar_default.jpeg"; + + if (settings->local_save.length() > 0) { + file_path = settings->local_save + "/settings/" + file_name; + } else { + file_path = Local_Storage::get_user_appdata_path() + "/settings/" + file_name; + } + + file_size = file_size_(file_path); + if (file_size) break; + } + } + + if (file_size) { + small_avatar = Local_Storage::load_image_resized(file_path, "", 32); + medium_avatar = Local_Storage::load_image_resized(file_path, "", 64); + large_avatar = Local_Storage::load_image_resized(file_path, "", 184); + } + } + } + avatar_numbers.smallest = settings->add_image(small_avatar, 32, 32); avatar_numbers.medium = settings->add_image(medium_avatar, 64, 64); avatar_numbers.large = settings->add_image(large_avatar, 184, 184); @@ -1114,6 +1197,9 @@ void Callback(Common_Message *msg) f->set_name(settings->get_local_name()); f->set_appid(settings->get_local_game_id().AppID()); f->set_lobby_id(settings->get_lobby().ConvertToUint64()); + int avatar_number = GetLargeFriendAvatar(settings->get_local_steam_id()); + if (settings->images[avatar_number].data.length() > 0) f->set_avatar(settings->images[avatar_number].data); + else f->set_avatar(""); msg_.set_allocated_friend_(f); network->sendTo(&msg_, true); } @@ -1127,6 +1213,7 @@ void Callback(Common_Message *msg) friends.push_back(msg->friend_()); overlay->FriendConnect(msg->friend_()); persona_change((uint64)msg->friend_().id(), k_EPersonaChangeName); + GetLargeFriendAvatar((uint64)msg->friend_().id()); } } else { std::map map1(f->rich_presence().begin(), f->rich_presence().end()); diff --git a/dll/dll/steam_gameserver.h b/dll/dll/steam_gameserver.h index 4f2ed46c..04244485 100644 --- a/dll/dll/steam_gameserver.h +++ b/dll/dll/steam_gameserver.h @@ -17,18 +17,24 @@ #include "base.h" #include "auth.h" - + //----------------------------------------------------------------------------- // Purpose: Functions for authenticating users via Steam to play on a game server //----------------------------------------------------------------------------- struct Gameserver_Outgoing_Packet { - std::string data; + std::vector data; uint32 ip; uint16 port; }; +struct Gameserver_Player_Info_t { + std::chrono::steady_clock::time_point join_time; + std::string name; + uint32 score; +}; + class Steam_GameServer : public ISteamGameServer004, public ISteamGameServer005, @@ -51,6 +57,7 @@ public ISteamGameServer bool logged_in = false; bool call_servers_disconnected = false; Gameserver server_data; + std::vector> players; uint32 flags; bool policy_response_called; @@ -63,6 +70,9 @@ public: Steam_GameServer(class Settings *settings, class Networking *network, class SteamCallBacks *callbacks); ~Steam_GameServer(); + + std::vector>* get_players(); + // // Basic server data. These properties, if set, must be set before before calling LogOn. They // may not be changed after logged in. diff --git a/dll/dll/steam_matchmaking.h b/dll/dll/steam_matchmaking.h index e484b074..ad9066e7 100644 --- a/dll/dll/steam_matchmaking.h +++ b/dll/dll/steam_matchmaking.h @@ -343,6 +343,16 @@ static Lobby_Member *get_lobby_member(Lobby *lobby, CSteamID user_id) int GetFavoriteGameCount() { PRINT_DEBUG("Steam_MatchMaking::GetFavoriteGameCount\n"); + std::string file_path = Local_Storage::get_user_appdata_path() + "/7/" + Local_Storage::remote_storage_folder + "/serverbrowser_favorites.txt"; + unsigned long long file_size = file_size_(file_path); + if (file_size) { + std::string list; + list.resize(file_size); + Local_Storage::get_file_data(file_path, (char *)list.data(), file_size, 0); + auto list_lines = std::count(list.begin(), list.end(), '\n'); + list_lines += (!list.empty() && list.back() != '\n'); + return list_lines; + } return 0; } @@ -363,8 +373,72 @@ bool GetFavoriteGame( int iGame, AppId_t *pnAppID, uint32 *pnIP, uint16 *pnConnP int AddFavoriteGame( AppId_t nAppID, uint32 nIP, uint16 nConnPort, uint16 nQueryPort, uint32 unFlags, uint32 rTime32LastPlayedOnServer ) { PRINT_DEBUG("Steam_MatchMaking::AddFavoriteGame %u %u %hu %hu %u %u\n", nAppID, nIP, nConnPort, nQueryPort, unFlags, rTime32LastPlayedOnServer); - //TODO: what should this return? - return 0; + + std::string file_path; + unsigned long long file_size; + + if (unFlags == 1) { + file_path = Local_Storage::get_user_appdata_path() + "/7/" + Local_Storage::remote_storage_folder + "/serverbrowser_favorites.txt"; + file_size = file_size_(file_path); + } + else if (unFlags == 2) { + file_path = Local_Storage::get_user_appdata_path() + "/7/" + Local_Storage::remote_storage_folder + "/serverbrowser_history.txt"; + file_size = file_size_(file_path); + } + else { + return 0; + } + + unsigned char ip[4]; + ip[0] = nIP & 0xFF; + ip[1] = (nIP >> 8) & 0xFF; + ip[2] = (nIP >> 16) & 0xFF; + ip[3] = (nIP >> 24) & 0xFF; + char newip[24]; + snprintf(newip, sizeof(newip), "%d.%d.%d.%d:%d\n", ip[3], ip[2], ip[1], ip[0], nConnPort); + std::string newip_string; + newip_string.append(newip); + + if (file_size) { + std::string list; + list.resize(file_size); + Local_Storage::get_file_data(file_path, (char *)list.data(), file_size, 0); + auto list_lines = std::count(list.begin(), list.end(), '\n'); + list_lines += (!list.empty() && list.back() != '\n'); + + std::size_t find_ip = list.find(newip_string); + if (find_ip == std::string::npos) { + list.append(newip_string); + list.append("\n"); + + std::size_t file_directory = file_path.rfind("/"); + std::string directory_path; + std::string file_name; + if (file_directory != std::string::npos) { + directory_path = file_path.substr(0, file_directory); + file_name = file_path.substr(file_directory); + } + Local_Storage::store_file_data(directory_path, file_name, (char *)list.data(), list.size()); + + return ++list_lines; + } + + return list_lines; + } + else { + newip_string.append("\n"); + + std::size_t file_directory = file_path.rfind("/"); + std::string directory_path; + std::string file_name; + if (file_directory != std::string::npos) { + directory_path = file_path.substr(0, file_directory); + file_name = file_path.substr(file_directory); + } + Local_Storage::store_file_data(directory_path, file_name, (char *)newip_string.data(), newip_string.size()); + + return 1; + } } @@ -372,6 +446,54 @@ int AddFavoriteGame( AppId_t nAppID, uint32 nIP, uint16 nConnPort, uint16 nQuery bool RemoveFavoriteGame( AppId_t nAppID, uint32 nIP, uint16 nConnPort, uint16 nQueryPort, uint32 unFlags ) { PRINT_DEBUG("Steam_MatchMaking::RemoveFavoriteGame\n"); + + std::string file_path; + unsigned long long file_size; + + if (unFlags == 1) { + file_path = Local_Storage::get_user_appdata_path() + "/7/" + Local_Storage::remote_storage_folder + "/serverbrowser_favorites.txt"; + file_size = file_size_(file_path); + } + else if (unFlags == 2) { + file_path = Local_Storage::get_user_appdata_path() + "/7/" + Local_Storage::remote_storage_folder + "/serverbrowser_history.txt"; + file_size = file_size_(file_path); + } + else { + return false; + } + + if (file_size) { + std::string list; + list.resize(file_size); + Local_Storage::get_file_data(file_path, (char *)list.data(), file_size, 0); + + unsigned char ip[4]; + ip[0] = nIP & 0xFF; + ip[1] = (nIP >> 8) & 0xFF; + ip[2] = (nIP >> 16) & 0xFF; + ip[3] = (nIP >> 24) & 0xFF; + char newip[24]; + snprintf((char *)newip, sizeof(newip), "%d.%d.%d.%d:%d\n", ip[3], ip[2], ip[1], ip[0], nConnPort); + std::string newip_string; + newip_string.append(newip); + + std::size_t list_ip = list.find(newip_string); + if (list_ip != std::string::npos) { + list.erase(list_ip, newip_string.length()); + + std::size_t file_directory = file_path.rfind("/"); + std::string directory_path; + std::string file_name; + if (file_directory != std::string::npos) { + directory_path = file_path.substr(0, file_directory); + file_name = file_path.substr(file_directory); + } + Local_Storage::store_file_data(directory_path, file_name, (char *)list.data(), list.size()); + + return true; + } + } + return false; } diff --git a/dll/dll/steam_matchmaking_servers.h b/dll/dll/steam_matchmaking_servers.h index 21a643fa..91248893 100644 --- a/dll/dll/steam_matchmaking_servers.h +++ b/dll/dll/steam_matchmaking_servers.h @@ -16,6 +16,7 @@ . */ #include "base.h" +#include #define SERVER_TIMEOUT 10.0 #define DIRECT_IP_DELAY 0.05 @@ -31,9 +32,17 @@ struct Steam_Matchmaking_Servers_Direct_IP_Request { ISteamMatchmakingPingResponse *ping_response = NULL; }; +struct Steam_Matchmaking_Servers_Gameserver_Friends { + uint64 source_id; + uint32 ip; + uint16 port; + std::chrono::high_resolution_clock::time_point last_recv; +}; + struct Steam_Matchmaking_Servers_Gameserver { Gameserver server; std::chrono::high_resolution_clock::time_point last_recv; + EMatchMakingType type; }; struct Steam_Matchmaking_Request { @@ -43,6 +52,7 @@ struct Steam_Matchmaking_Request { ISteamMatchmakingServerListResponse001 *old_callbacks; bool completed, cancelled, released; std::vector gameservers_filtered; + EMatchMakingType type; }; class Steam_Matchmaking_Servers : public ISteamMatchmakingServers, @@ -52,8 +62,10 @@ public ISteamMatchmakingServers001 class Networking *network; std::vector gameservers; + std::vector gameservers_friends; std::vector requests; std::vector direct_ip_requests; + HServerListRequest RequestServerList(AppId_t iApp, ISteamMatchmakingServerListResponse *pRequestServersResponse, EMatchMakingType type); void RequestOldServerList(AppId_t iApp, ISteamMatchmakingServerListResponse001 *pRequestServersResponse, EMatchMakingType type); public: Steam_Matchmaking_Servers(class Settings *settings, class Networking *network); @@ -222,4 +234,6 @@ public: void RunCallbacks(); void Callback(Common_Message *msg); void server_details(Gameserver *g, gameserveritem_t *server); + void server_details_players(Gameserver *g, Steam_Matchmaking_Servers_Direct_IP_Request *r); + void server_details_rules(Gameserver *g, Steam_Matchmaking_Servers_Direct_IP_Request *r); }; diff --git a/dll/dll/steam_user.h b/dll/dll/steam_user.h index bcaee8cb..c8bd1bf8 100644 --- a/dll/dll/steam_user.h +++ b/dll/dll/steam_user.h @@ -385,6 +385,7 @@ void AdvertiseGame( CSteamID steamIDGameServer, uint32 unIPServer, uint16 usPort server->set_port(usPortServer); server->set_query_port(usPortServer); server->set_appid(settings->get_local_game_id().ToUint64()); + server->set_type(eFriendsServer); Common_Message msg; msg.set_allocated_gameserver(server); msg.set_source_id(settings->get_local_steam_id().ConvertToUint64()); diff --git a/dll/net.proto b/dll/net.proto index 32e78529..a5522404 100644 --- a/dll/net.proto +++ b/dll/net.proto @@ -169,6 +169,7 @@ message Gameserver { uint32 appid = 35; bool offline = 48; + uint32 type = 49; } message Friend { @@ -177,6 +178,7 @@ message Friend { map rich_presence = 3; uint32 appid = 4; uint64 lobby_id = 5; + bytes avatar = 6; } message Auth_Ticket { diff --git a/dll/network.cpp b/dll/network.cpp index 31cc4296..0bae1452 100644 --- a/dll/network.cpp +++ b/dll/network.cpp @@ -942,6 +942,22 @@ void Networking::Run() char data[MAX_UDP_SIZE]; int len; + if (query_alive && is_socket_valid(query_socket)) { + PRINT_DEBUG("RECV QUERY\n"); + Steam_Client* client = get_steam_client(); + sockaddr_in addr; + addr.sin_family = AF_INET; + + while ((len = receive_packet(query_socket, &ip_port, data, sizeof(data))) >= 0) { + client->steam_gameserver->HandleIncomingPacket(data, len, htonl(ip_port.ip), htons(ip_port.port)); + len = client->steam_gameserver->GetNextOutgoingPacket(data, sizeof(data), &ip_port.ip, &ip_port.port); + + addr.sin_addr.s_addr = htonl(ip_port.ip); + addr.sin_port = htons(ip_port.port); + sendto(query_socket, data, len, 0, (sockaddr*)&addr, sizeof(addr)); + } + } + PRINT_DEBUG("RECV UDP\n"); while((len = receive_packet(udp_socket, &ip_port, data, sizeof(data))) >= 0) { PRINT_DEBUG("recv %i %hhu.%hhu.%hhu.%hhu:%hu\n", len, ((unsigned char *)&ip_port.ip)[0], ((unsigned char *)&ip_port.ip)[1], ((unsigned char *)&ip_port.ip)[2], ((unsigned char *)&ip_port.ip)[3], htons(ip_port.port)); @@ -1300,3 +1316,74 @@ uint32 Networking::getOwnIP() { return own_ip; } + +void Networking::startQuery(IP_PORT ip_port) +{ + if (ip_port.port <= 1024) + return; + + if (!query_alive) + { + if (ip_port.port == MASTERSERVERUPDATERPORT_USEGAMESOCKETSHARE) + { + PRINT_DEBUG("Source Query in Shared Mode\n"); + return; + } + + int retry = 0; + constexpr auto max_retry = 10; + + while (retry++ < max_retry) + { + query_socket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + if (is_socket_valid(query_socket)) + break; + if (retry > max_retry) + { + reset_last_error(); + return; + } + } + retry = 0; + + sockaddr_in addr; + addr.sin_addr.s_addr = htonl(ip_port.ip); + addr.sin_port = htons(ip_port.port); + addr.sin_family = AF_INET; + + while (retry++ < max_retry) + { + int res = bind(query_socket, (sockaddr*)&addr, sizeof(sockaddr_in)); + if (res == 0) + { + set_socket_nonblocking(query_socket); + break; + } + + if (retry >= max_retry) + { + kill_socket(query_socket); + query_socket = -1; + reset_last_error(); + return; + } + } + + char str_ip[16]; + inet_ntop(AF_INET, &(addr.sin_addr), str_ip, 16); + + PRINT_DEBUG("Started query server on %s:%d\n", str_ip, htons(addr.sin_port)); + } + query_alive = true; +} + +void Networking::shutDownQuery() +{ + query_alive = false; + kill_socket(query_socket); +} + +bool Networking::isQueryAlive() +{ + return query_alive; +} diff --git a/dll/settings_parser.cpp b/dll/settings_parser.cpp index 5f3f09bc..0fea9167 100644 --- a/dll/settings_parser.cpp +++ b/dll/settings_parser.cpp @@ -1205,6 +1205,8 @@ uint32 create_localstorage_settings(Settings **settings_client_out, Settings **s bool disable_overlay_warning_bad_appid = false; bool disable_overlay_warning_any = false; bool disable_lobby_creation = false; + bool disable_source_query = false; + bool disable_account_avatar = false; bool achievement_bypass = false; bool is_beta_branch = false; bool use_gc_token = false; @@ -1246,6 +1248,10 @@ uint32 create_localstorage_settings(Settings **settings_client_out, Settings **s disable_overlay_warning_any = true; } else if (p == "disable_lobby_creation.txt") { disable_lobby_creation = true; + } else if (p == "disable_source_query.txt") { + disable_source_query = true; + } else if (p == "disable_account_avatar.txt") { + disable_account_avatar = true; } else if (p == "achievements_bypass.txt") { achievement_bypass = true; } else if (p == "is_beta_branch.txt") { @@ -1302,6 +1308,10 @@ uint32 create_localstorage_settings(Settings **settings_client_out, Settings **s settings_client->disable_lobby_creation = disable_lobby_creation; settings_server->disable_lobby_creation = disable_lobby_creation; + settings_client->disable_source_query = disable_source_query; + settings_server->disable_source_query = disable_source_query; + settings_client->disable_account_avatar = disable_account_avatar; + settings_server->disable_account_avatar = disable_account_avatar; settings_client->build_id = build_id; settings_server->build_id = build_id; settings_client->supported_languages = supported_languages; diff --git a/dll/source_query.cpp b/dll/source_query.cpp new file mode 100644 index 00000000..f4dace4e --- /dev/null +++ b/dll/source_query.cpp @@ -0,0 +1,267 @@ +/* Copyright (C) 2019 Mr Goldberg, , Nemirtingas + This file is part of the Goldberg Emulator + + The Goldberg Emulator is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 3 of the License, or (at your option) any later version. + + The Goldberg Emulator is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with the Goldberg Emulator; if not, see + . */ + +#include "dll/source_query.h" +#include "dll/dll.h" + +using lock_t = std::lock_guard; + +enum class source_query_magic : uint32_t +{ + simple = 0xFFFFFFFFul, + multi = 0xFFFFFFFEul, // <--- TODO ? +}; + +enum class source_query_header : uint8_t +{ + A2S_INFO = 'T', + A2S_PLAYER = 'U', + A2S_RULES = 'V', +}; + +enum class source_response_header : uint8_t +{ + A2S_CHALLENGE = 'A', + A2S_INFO = 'I', + A2S_PLAYER = 'D', + A2S_RULES = 'E', +}; + +enum class source_server_type : uint8_t +{ + dedicated = 'd', + non_dedicated = 'i', + source_tc = 'p', +}; + +enum class source_server_env : uint8_t +{ + linux = 'l', + windows = 'w', + old_mac = 'm', + mac = 'o', +}; + +enum class source_server_visibility : uint8_t +{ + _public = 0, + _private = 1, +}; + +enum class source_server_vac : uint8_t +{ + unsecured = 0, + secured = 1, +}; + +enum source_server_extra_flag : uint8_t +{ + none = 0x00, + gameid = 0x01, + steamid = 0x10, + keywords = 0x20, + spectator = 0x40, + port = 0x80, +}; + +#if defined(STEAM_WIN32) +static constexpr source_server_env my_server_env = source_server_env::windows; +#else +static constexpr source_server_env my_server_env = source_server_env::linux; +#endif + +#pragma pack(push) +#pragma pack(1) + +constexpr char a2s_info_payload[] = "Source Engine Query"; +constexpr size_t a2s_info_payload_size = sizeof(a2s_info_payload); + +struct source_query_data +{ + source_query_magic magic; + source_query_header header; + union + { + char a2s_info_payload[a2s_info_payload_size]; + uint32_t challenge; + }; +}; + +static constexpr size_t source_query_header_size = sizeof(source_query_magic) + sizeof(source_query_header); +static constexpr size_t a2s_query_info_size = source_query_header_size + sizeof(source_query_data::a2s_info_payload); +static constexpr size_t a2s_query_challenge_size = source_query_header_size + sizeof(source_query_data::challenge); + +#pragma pack(pop) + +void serialize_response(std::vector& buffer, const void* _data, size_t len) +{ + const uint8_t* data = reinterpret_cast(_data); + + buffer.insert(buffer.end(), data, data + len); +} + +template +void serialize_response(std::vector& buffer, T const& v) +{ + uint8_t const* data = reinterpret_cast(&v); + serialize_response(buffer, data, sizeof(T)); +} + +template<> +void serialize_response(std::vector& buffer, std::string const& v) +{ + uint8_t const* str = reinterpret_cast(v.c_str()); + serialize_response(buffer, str, v.length()+1); +} + +template +void serialize_response(std::vector & buffer, char(&str)[N]) +{ + serialize_response(buffer, reinterpret_cast(str), N); +} + +void get_challenge(std::vector &challenge_buff) +{ + // TODO: generate the challenge id + serialize_response(challenge_buff, source_query_magic::simple); + serialize_response(challenge_buff, source_response_header::A2S_CHALLENGE); + serialize_response(challenge_buff, static_cast(0x00112233ul)); +} + +std::vector Source_Query::handle_source_query(const void* buffer, size_t len, Gameserver const& gs) +{ + std::vector output_buffer; + + if (len < source_query_header_size) // its not at least 5 bytes long (0xFF 0xFF 0xFF 0xFF 0x??) + return output_buffer; + + source_query_data const& query = *reinterpret_cast(buffer); + + // || gs.max_player_count() == 0 + if (gs.offline() || query.magic != source_query_magic::simple) + return output_buffer; + + switch (query.header) + { + case source_query_header::A2S_INFO: + if (len >= a2s_query_info_size && !strncmp(query.a2s_info_payload, a2s_info_payload, a2s_info_payload_size)) + { + std::vector> const& players = *get_steam_client()->steam_gameserver->get_players(); + + serialize_response(output_buffer, source_query_magic::simple); + serialize_response(output_buffer, source_response_header::A2S_INFO); + serialize_response(output_buffer, static_cast(2)); + serialize_response(output_buffer, gs.server_name()); + serialize_response(output_buffer, gs.map_name()); + serialize_response(output_buffer, gs.mod_dir()); + serialize_response(output_buffer, gs.product()); + serialize_response(output_buffer, static_cast(gs.appid())); + serialize_response(output_buffer, static_cast(players.size())); + serialize_response(output_buffer, static_cast(gs.max_player_count())); + serialize_response(output_buffer, static_cast(gs.bot_player_count())); + serialize_response(output_buffer, (gs.dedicated_server() ? source_server_type::dedicated : source_server_type::non_dedicated));; + serialize_response(output_buffer, my_server_env); + serialize_response(output_buffer, (gs.password_protected() ? source_server_visibility::_private : source_server_visibility::_public)); + serialize_response(output_buffer, (gs.secure() ? source_server_vac::secured : source_server_vac::unsecured)); + serialize_response(output_buffer, std::to_string(gs.version())); + + uint8_t flags = source_server_extra_flag::none; + + if (gs.port() != 0) + flags |= source_server_extra_flag::port; + + if (gs.spectator_port() != 0) + flags |= source_server_extra_flag::spectator; + + if(CGameID(gs.appid()).IsValid()) + flags |= source_server_extra_flag::gameid; + + if (flags != source_server_extra_flag::none) + serialize_response(output_buffer, flags); + + if (flags & source_server_extra_flag::port) + serialize_response(output_buffer, static_cast(gs.port())); + + // add steamid + + if (flags & source_server_extra_flag::spectator) + { + serialize_response(output_buffer, static_cast(gs.spectator_port())); + serialize_response(output_buffer, gs.spectator_server_name()); + } + + // keywords + + if (flags & source_server_extra_flag::gameid) + serialize_response(output_buffer, CGameID(gs.appid()).ToUint64()); + + } + break; + + case source_query_header::A2S_PLAYER: + if (len >= a2s_query_challenge_size) + { + if (query.challenge == 0xFFFFFFFFul) + { + get_challenge(output_buffer); + } + else if (query.challenge == 0x00112233ul) + { + std::vector> const& players = *get_steam_client()->steam_gameserver->get_players(); + + serialize_response(output_buffer, source_query_magic::simple); + serialize_response(output_buffer, source_response_header::A2S_PLAYER); + serialize_response(output_buffer, static_cast(players.size())); // num_players + + for (int i = 0; i < players.size(); ++i) + { + serialize_response(output_buffer, static_cast(i)); // player index + serialize_response(output_buffer, players[i].second.name); // player name + serialize_response(output_buffer, players[i].second.score); // player score + serialize_response(output_buffer, static_cast(std::chrono::duration_cast(std::chrono::steady_clock::now() - players[i].second.join_time).count())); + } + + } + } + break; + + case source_query_header::A2S_RULES: + if (len >= a2s_query_challenge_size) + { + if (query.challenge == 0xFFFFFFFFul) + { + get_challenge(output_buffer); + } + else if (query.challenge == 0x00112233ul) + { + auto values = gs.values(); + + serialize_response(output_buffer, source_query_magic::simple); + serialize_response(output_buffer, source_response_header::A2S_RULES); + serialize_response(output_buffer, static_cast(values.size())); + + for (auto const& i : values) + { + serialize_response(output_buffer, i.first); + serialize_response(output_buffer, i.second); + } + } + } + break; + } + return output_buffer; +} diff --git a/dll/steam_gameserver.cpp b/dll/steam_gameserver.cpp index f9696ede..8014dff9 100644 --- a/dll/steam_gameserver.cpp +++ b/dll/steam_gameserver.cpp @@ -16,6 +16,7 @@ . */ #include "dll/steam_gameserver.h" +#include "dll/source_query.h" #define SEND_SERVER_RATE 5.0 @@ -33,6 +34,11 @@ Steam_GameServer::~Steam_GameServer() delete auth_manager; } +std::vector>* Steam_GameServer::get_players() +{ + return &players; +} + // // Basic server data. These properties, if set, must be set before before calling LogOn. They // may not be changed after logged in. @@ -61,6 +67,10 @@ bool Steam_GameServer::InitGameServer( uint32 unIP, uint16 usGamePort, uint16 us server_data.set_port(usGamePort); server_data.set_query_port(usQueryPort); server_data.set_offline(false); + + if (!settings->disable_source_query) + network->startQuery({ unIP, usQueryPort }); + if (!settings->get_local_game_id().AppID()) settings->set_game_id(CGameID(nGameAppId)); //TODO: flags should be k_unServerFlag flags = unFlags; @@ -78,6 +88,8 @@ void Steam_GameServer::SetProduct( const char *pszProduct ) { PRINT_DEBUG("Steam_GameServer::SetProduct\n"); std::lock_guard lock(global_mutex); + // pszGameDescription should be used instead of pszProduct for accurate information + // Example: 'Counter-Strike: Source' instead of 'cstrike' server_data.set_product(pszProduct); } @@ -89,6 +101,7 @@ void Steam_GameServer::SetGameDescription( const char *pszGameDescription ) PRINT_DEBUG("Steam_GameServer::SetGameDescription\n"); std::lock_guard lock(global_mutex); server_data.set_game_description(pszGameDescription); + //server_data.set_product(pszGameDescription); } @@ -183,8 +196,13 @@ bool Steam_GameServer::BSecure() { PRINT_DEBUG("Steam_GameServer::BSecure\n"); std::lock_guard lock(global_mutex); - if (!policy_response_called) return false; - return !!(flags & k_unServerFlagSecure); + if (!policy_response_called) { + server_data.set_secure(0); + return false; + } + const bool res = !!(flags & k_unServerFlagSecure); + server_data.set_secure(res); + return res; } CSteamID Steam_GameServer::GetSteamID() @@ -351,7 +369,18 @@ bool Steam_GameServer::SendUserConnectAndAuthenticate( uint32 unIPClient, const PRINT_DEBUG("Steam_GameServer::SendUserConnectAndAuthenticate %u %u\n", unIPClient, cubAuthBlobSize); std::lock_guard lock(global_mutex); - return auth_manager->SendUserConnectAndAuthenticate(unIPClient, pvAuthBlob, cubAuthBlobSize, pSteamIDUser); + bool res = auth_manager->SendUserConnectAndAuthenticate(unIPClient, pvAuthBlob, cubAuthBlobSize, pSteamIDUser); + + if (res) { + std::pair infos; + infos.first = *pSteamIDUser; + infos.second.join_time = std::chrono::steady_clock::now(); + infos.second.score = 0; + infos.second.name = "unnamed"; + players.emplace_back(std::move(infos)); + } + + return res; } void Steam_GameServer::SendUserConnectAndAuthenticate( CSteamID steamIDUser, uint32 unIPClient, void *pvAuthBlob, uint32 cubAuthBlobSize ) @@ -368,7 +397,15 @@ CSteamID Steam_GameServer::CreateUnauthenticatedUserConnection() PRINT_DEBUG("Steam_GameServer::CreateUnauthenticatedUserConnection\n"); std::lock_guard lock(global_mutex); - return auth_manager->fakeUser(); + CSteamID bot_id = auth_manager->fakeUser(); + std::pair infos; + infos.first = bot_id; + infos.second.join_time = std::chrono::steady_clock::now(); + infos.second.score = 0; + infos.second.name = "unnamed"; + players.emplace_back(std::move(infos)); + + return bot_id; } @@ -380,6 +417,16 @@ void Steam_GameServer::SendUserDisconnect( CSteamID steamIDUser ) PRINT_DEBUG("Steam_GameServer::SendUserDisconnect\n"); std::lock_guard lock(global_mutex); + auto player_it = std::find_if(players.begin(), players.end(), [&steamIDUser](std::pair& player) + { + return player.first == steamIDUser; + }); + + if (player_it != players.end()) + { + players.erase(player_it); + } + auth_manager->endAuth(steamIDUser); } @@ -392,7 +439,22 @@ void Steam_GameServer::SendUserDisconnect( CSteamID steamIDUser ) bool Steam_GameServer::BUpdateUserData( CSteamID steamIDUser, const char *pchPlayerName, uint32 uScore ) { PRINT_DEBUG("Steam_GameServer::BUpdateUserData %llu %s %u\n", steamIDUser.ConvertToUint64(), pchPlayerName, uScore); - return true; + std::lock_guard lock(global_mutex); + + auto player_it = std::find_if(players.begin(), players.end(), [&steamIDUser](std::pair& player) + { + return player.first == steamIDUser; + }); + + if (player_it != players.end()) + { + if (pchPlayerName != nullptr) + player_it->second.name = pchPlayerName; + + player_it->second.score = uScore; + return true; + } + return false; } // You shouldn't need to call this as it is called internally by SteamGameServer_Init() and can only be called once. @@ -505,6 +567,13 @@ EBeginAuthSessionResult Steam_GameServer::BeginAuthSession( const void *pAuthTic PRINT_DEBUG("Steam_GameServer::BeginAuthSession %i %llu\n", cbAuthTicket, steamID.ConvertToUint64()); std::lock_guard lock(global_mutex); + std::pair infos; + infos.first = steamID; + infos.second.join_time = std::chrono::steady_clock::now(); + infos.second.score = 0; + infos.second.name = "unnamed"; + players.emplace_back(std::move(infos)); + return auth_manager->beginAuth(pAuthTicket, cbAuthTicket, steamID ); } @@ -515,6 +584,16 @@ void Steam_GameServer::EndAuthSession( CSteamID steamID ) PRINT_DEBUG("Steam_GameServer::EndAuthSession %llu\n", steamID.ConvertToUint64()); std::lock_guard lock(global_mutex); + auto player_it = std::find_if(players.begin(), players.end(), [&steamID](std::pair& player) + { + return player.first == steamID; + }); + + if (player_it != players.end()) + { + players.erase(player_it); + } + auth_manager->endAuth(steamID); } @@ -612,6 +691,18 @@ bool Steam_GameServer::HandleIncomingPacket( const void *pData, int cbData, uint { PRINT_DEBUG("Steam_GameServer::HandleIncomingPacket %i %X %i\n", cbData, srcIP, srcPort); std::lock_guard lock(global_mutex); + if (settings->disable_source_query) return true; + + Gameserver_Outgoing_Packet packet; + packet.data = std::move(Source_Query::handle_source_query(pData, cbData, server_data)); + if (packet.data.empty()) + return false; + + + packet.ip = srcIP; + packet.port = srcPort; + + outgoing_packets.emplace_back(std::move(packet)); return true; } @@ -624,6 +715,7 @@ int Steam_GameServer::GetNextOutgoingPacket( void *pOut, int cbMaxOut, uint32 *p { PRINT_DEBUG("Steam_GameServer::GetNextOutgoingPacket\n"); std::lock_guard lock(global_mutex); + if (settings->disable_source_query) return 0; if (outgoing_packets.size() == 0) return 0; if (outgoing_packets.back().data.size() < cbMaxOut) cbMaxOut = outgoing_packets.back().data.size(); @@ -747,6 +839,10 @@ void Steam_GameServer::RunCallbacks() msg.set_allocated_gameserver(new Gameserver(server_data)); msg.mutable_gameserver()->set_offline(true); network->sendToAllIndividuals(&msg, true); + // Shutdown Source Query + network->shutDownQuery(); + // And empty the queue if needed + outgoing_packets.clear(); } } } diff --git a/dll/steam_matchmaking_servers.cpp b/dll/steam_matchmaking_servers.cpp index 690d61ba..10846a96 100644 --- a/dll/steam_matchmaking_servers.cpp +++ b/dll/steam_matchmaking_servers.cpp @@ -33,7 +33,111 @@ Steam_Matchmaking_Servers::Steam_Matchmaking_Servers(class Settings *settings, c this->network->setCallback(CALLBACK_ID_GAMESERVER, (uint64) 0, &network_callback, this); } -static int server_list_request = 0; +static int server_list_request; + +HServerListRequest Steam_Matchmaking_Servers::RequestServerList(AppId_t iApp, ISteamMatchmakingServerListResponse *pRequestServersResponse, EMatchMakingType type) +{ + PRINT_DEBUG("Steam_Matchmaking_Servers::RequestServerList %u %p, %i\n", iApp, pRequestServersResponse, (int)type); + std::lock_guard lock(global_mutex); + ++server_list_request; + HServerListRequest id = (char *)0 + server_list_request; // (char *)0 silences the compiler warning + + struct Steam_Matchmaking_Request request; + request.appid = iApp; + request.callbacks = pRequestServersResponse; + request.old_callbacks = NULL; + request.cancelled = false; + request.completed = false; + request.type = type; + request.id = id; + requests.push_back(request); + PRINT_DEBUG("Steam_Matchmaking_Servers::request id: %p\n", id); + + if (type == eLANServer) return id; + + if (type == eFriendsServer) { + for (auto &g : gameservers_friends) { + if (g.source_id != settings->get_local_steam_id().ConvertToUint64()) { + Gameserver server; + server.set_ip(g.ip); + server.set_port(g.port); + server.set_query_port(g.port); + server.set_appid(iApp); + + struct Steam_Matchmaking_Servers_Gameserver g2; + g2.last_recv = std::chrono::high_resolution_clock::now(); + g2.server = server; + g2.type = type; + gameservers.push_back(g2); + PRINT_DEBUG(" eFriendsServer SERVER ADDED\n"); + } + } + return id; + } + + std::string file_path; + unsigned long long file_size; + if (type == eInternetServer || type == eSpectatorServer) { + file_path = Local_Storage::get_user_appdata_path() + "/7/" + Local_Storage::remote_storage_folder + "/serverbrowser.txt"; + file_size = file_size_(file_path); + } else if (type == eFavoritesServer) { + file_path = Local_Storage::get_user_appdata_path() + "/7/" + Local_Storage::remote_storage_folder + "/serverbrowser_favorites.txt"; + file_size = file_size_(file_path); + } else if (type == eHistoryServer) { + file_path = Local_Storage::get_user_appdata_path() + "/7/" + Local_Storage::remote_storage_folder + "/serverbrowser_history.txt"; + file_size = file_size_(file_path); + } + + PRINT_DEBUG("Steam_Matchmaking_Servers::Server list file '%s' [%llu bytes]\n", file_path.c_str(), file_size); + std::string list; + if (file_size) { + list.resize(file_size); + Local_Storage::get_file_data(file_path, (char *)list.data(), file_size, 0); + } else { + return id; + } + + std::istringstream list_ss (list); + std::string list_ip; + while (std::getline(list_ss, list_ip)) { + if (list_ip.length() < 0) continue; + + unsigned int byte4, byte3, byte2, byte1, byte0; + uint32 ip_int; + uint16 port_int; + char newip[24]; + if (sscanf(list_ip.c_str(), "%u.%u.%u.%u:%u", &byte3, &byte2, &byte1, &byte0, &byte4) == 5) { + ip_int = (byte3 << 24) + (byte2 << 16) + (byte1 << 8) + byte0; + port_int = byte4; + + unsigned char ip_tmp[4]; + ip_tmp[0] = ip_int & 0xFF; + ip_tmp[1] = (ip_int >> 8) & 0xFF; + ip_tmp[2] = (ip_int >> 16) & 0xFF; + ip_tmp[3] = (ip_int >> 24) & 0xFF; + snprintf(newip, sizeof(newip), "%d.%d.%d.%d", ip_tmp[3], ip_tmp[2], ip_tmp[1], ip_tmp[0]); + } else { + continue; + } + + Gameserver server; + server.set_ip(ip_int); + server.set_port(port_int); + server.set_query_port(port_int); + server.set_appid(iApp); + + struct Steam_Matchmaking_Servers_Gameserver g; + g.last_recv = std::chrono::high_resolution_clock::now(); + g.server = server; + g.type = type; + gameservers.push_back(g); + PRINT_DEBUG(" SERVER ADDED\n"); + + list_ip = ""; + } + + return id; +} // Request a new list of servers of a particular type. These calls each correspond to one of the EMatchMakingType values. // Each call allocates a new asynchronous request object. @@ -41,55 +145,37 @@ static int server_list_request = 0; HServerListRequest Steam_Matchmaking_Servers::RequestInternetServerList( AppId_t iApp, STEAM_ARRAY_COUNT(nFilters) MatchMakingKeyValuePair_t **ppchFilters, uint32 nFilters, ISteamMatchmakingServerListResponse *pRequestServersResponse ) { PRINT_DEBUG("Steam_Matchmaking_Servers::RequestInternetServerList\n"); - //TODO - return RequestLANServerList(iApp, pRequestServersResponse); + return RequestServerList(iApp, pRequestServersResponse, eInternetServer); } HServerListRequest Steam_Matchmaking_Servers::RequestLANServerList( AppId_t iApp, ISteamMatchmakingServerListResponse *pRequestServersResponse ) { - PRINT_DEBUG("Steam_Matchmaking_Servers::RequestLANServerList %u\n", iApp); - std::lock_guard lock(global_mutex); - ++server_list_request; - HServerListRequest id = (char *)0 + server_list_request; // (char *)0 silences the compiler warning - - struct Steam_Matchmaking_Request request{}; - request.appid = iApp; - request.callbacks = pRequestServersResponse; - request.old_callbacks = NULL; - request.cancelled = false; - request.completed = false; - request.id = id; - requests.push_back(request); - PRINT_DEBUG("Steam_Matchmaking_Servers::RequestLANServerList request id: %p\n", id); - return id; + PRINT_DEBUG("Steam_Matchmaking_Servers::RequestLANServerList\n"); + return RequestServerList(iApp, pRequestServersResponse, eLANServer); } HServerListRequest Steam_Matchmaking_Servers::RequestFriendsServerList( AppId_t iApp, STEAM_ARRAY_COUNT(nFilters) MatchMakingKeyValuePair_t **ppchFilters, uint32 nFilters, ISteamMatchmakingServerListResponse *pRequestServersResponse ) { PRINT_DEBUG("Steam_Matchmaking_Servers::RequestFriendsServerList\n"); - //TODO - return RequestLANServerList(iApp, pRequestServersResponse); + return RequestServerList(iApp, pRequestServersResponse, eFriendsServer); } HServerListRequest Steam_Matchmaking_Servers::RequestFavoritesServerList( AppId_t iApp, STEAM_ARRAY_COUNT(nFilters) MatchMakingKeyValuePair_t **ppchFilters, uint32 nFilters, ISteamMatchmakingServerListResponse *pRequestServersResponse ) { PRINT_DEBUG("Steam_Matchmaking_Servers::RequestFavoritesServerList\n"); - //TODO - return RequestLANServerList(iApp, pRequestServersResponse); + return RequestServerList(iApp, pRequestServersResponse, eFavoritesServer); } HServerListRequest Steam_Matchmaking_Servers::RequestHistoryServerList( AppId_t iApp, STEAM_ARRAY_COUNT(nFilters) MatchMakingKeyValuePair_t **ppchFilters, uint32 nFilters, ISteamMatchmakingServerListResponse *pRequestServersResponse ) { PRINT_DEBUG("Steam_Matchmaking_Servers::RequestHistoryServerList\n"); - //TODO - return RequestLANServerList(iApp, pRequestServersResponse); + return RequestServerList(iApp, pRequestServersResponse, eHistoryServer); } HServerListRequest Steam_Matchmaking_Servers::RequestSpectatorServerList( AppId_t iApp, STEAM_ARRAY_COUNT(nFilters) MatchMakingKeyValuePair_t **ppchFilters, uint32 nFilters, ISteamMatchmakingServerListResponse *pRequestServersResponse ) { PRINT_DEBUG("Steam_Matchmaking_Servers::RequestSpectatorServerList\n"); - //TODO - return RequestLANServerList(iApp, pRequestServersResponse); + return RequestServerList(iApp, pRequestServersResponse, eSpectatorServer); } void Steam_Matchmaking_Servers::RequestOldServerList(AppId_t iApp, ISteamMatchmakingServerListResponse001 *pRequestServersResponse, EMatchMakingType type) @@ -111,6 +197,7 @@ void Steam_Matchmaking_Servers::RequestOldServerList(AppId_t iApp, ISteamMatchma request.old_callbacks = pRequestServersResponse; request.cancelled = false; request.completed = false; + request.type = type; requests.push_back(request); requests[requests.size() - 1].id = (void *)type; } @@ -251,15 +338,85 @@ void Steam_Matchmaking_Servers::ReleaseRequest( HServerListRequest hServerListRe void Steam_Matchmaking_Servers::server_details(Gameserver *g, gameserveritem_t *server) { + long long latency = 10; + if (!(g->ip() < 0) && !(g->query_port() < 0)) { + unsigned char ip[4]{}; + char newip[24]; + ip[0] = g->ip() & 0xFF; + ip[1] = (g->ip() >> 8) & 0xFF; + ip[2] = (g->ip() >> 16) & 0xFF; + ip[3] = (g->ip() >> 24) & 0xFF; + snprintf(newip, sizeof(newip), "%d.%d.%d.%d", ip[3], ip[2], ip[1], ip[0]); + + PRINT_DEBUG(" server_details() connecting to ssq server on %s:%u\n", newip, g->query_port()); + SSQ_SERVER *ssq = ssq_server_new(newip, g->query_port()); + if (ssq != NULL && ssq_server_eok(ssq)) { + PRINT_DEBUG(" server_details() ssq server connection ok\n"); + ssq_server_timeout(ssq, (SSQ_TIMEOUT_SELECTOR)(SSQ_TIMEOUT_RECV | SSQ_TIMEOUT_SEND), 1200); + + std::chrono::high_resolution_clock::time_point t1 = std::chrono::high_resolution_clock::now(); + A2S_INFO *ssq_a2s_info = ssq_info(ssq); + std::chrono::high_resolution_clock::time_point t2 = std::chrono::high_resolution_clock::now(); + latency = std::chrono::duration_cast(t2 - t1).count(); + + if (ssq_server_eok(ssq)) { + PRINT_DEBUG(" server_details() ssq server info ok\n"); + if (ssq_info_has_steamid(ssq_a2s_info)) g->set_id(ssq_a2s_info->steamid); + g->set_game_description(ssq_a2s_info->game); + g->set_mod_dir(ssq_a2s_info->folder); + if (ssq_a2s_info->server_type == A2S_SERVER_TYPE_DEDICATED) g->set_dedicated_server(true); + else if (ssq_a2s_info->server_type == A2S_SERVER_TYPE_STV_RELAY) g->set_dedicated_server(true); + else g->set_dedicated_server(false); + g->set_max_player_count(ssq_a2s_info->max_players); + g->set_bot_player_count(ssq_a2s_info->bots); + g->set_server_name(ssq_a2s_info->name); + g->set_map_name(ssq_a2s_info->map); + if (ssq_a2s_info->visibility) g->set_password_protected(true); + else g->set_password_protected(false); + if (ssq_info_has_stv(ssq_a2s_info)) { + g->set_spectator_port(ssq_a2s_info->stv_port); + g->set_spectator_server_name(ssq_a2s_info->stv_name); + } + //g->set_tags(ssq_a2s_info->keywords); + //g->set_gamedata(); + //g->set_region(); + g->set_product(ssq_a2s_info->game); + if (ssq_a2s_info->vac) g->set_secure(true); + else g->set_secure(false); + g->set_num_players(ssq_a2s_info->players); + g->set_version(std::stoull(ssq_a2s_info->version, NULL, 0)); + if (ssq_info_has_port(ssq_a2s_info)) g->set_port(ssq_a2s_info->port); + if (ssq_info_has_gameid(ssq_a2s_info)) g->set_appid(ssq_a2s_info->gameid); + else g->set_appid(ssq_a2s_info->id); + g->set_offline(false); + } else { + PRINT_DEBUG(" server_details() ssq server info failed: %s\n", ssq_server_emsg(ssq)); + } + + if (ssq_a2s_info != NULL) ssq_info_free(ssq_a2s_info); + } else { + PRINT_DEBUG(" server_details() ssq server connection failed: %s\n", (ssq ? ssq_server_emsg(ssq) : "NULL instance")); + } + + if (ssq != NULL) ssq_server_free(ssq); + } + uint16 query_port = g->query_port(); if (g->query_port() == 0xFFFF) { query_port = g->port(); } server->m_NetAdr.Init(g->ip(), query_port, g->port()); - server->m_nPing = 10; //TODO + server->m_nPing = latency; server->m_bHadSuccessfulResponse = true; server->m_bDoNotRefresh = false; + strncpy(server->m_szGameDir, g->mod_dir().c_str(), k_cbMaxGameServerGameDir - 1); + strncpy(server->m_szMap, g->map_name().c_str(), k_cbMaxGameServerMapName - 1); + strncpy(server->m_szGameDescription, g->game_description().c_str(), k_cbMaxGameServerGameDescription - 1); + + server->m_szGameDir[k_cbMaxGameServerGameDir - 1] = 0; + server->m_szMap[k_cbMaxGameServerMapName - 1] = 0; + server->m_szGameDescription[k_cbMaxGameServerGameDescription - 1] = 0; server->m_nAppID = g->appid(); server->m_nPlayers = g->num_players(); @@ -271,20 +428,91 @@ void Steam_Matchmaking_Servers::server_details(Gameserver *g, gameserveritem_t * server->m_nServerVersion = g->version(); server->SetName(g->server_name().c_str()); server->m_steamID = CSteamID((uint64)g->id()); - - memset(server->m_szGameDir, 0, sizeof(server->m_szGameDir)); - g->mod_dir().copy(server->m_szGameDir, sizeof(server->m_szGameDir) - 1); - - memset(server->m_szMap, 0, sizeof(server->m_szMap)); - g->map_name().copy(server->m_szMap, sizeof(server->m_szMap) - 1); - - memset(server->m_szGameDescription, 0, sizeof(server->m_szGameDescription)); - g->game_description().copy(server->m_szGameDescription, sizeof(server->m_szGameDescription) - 1); + PRINT_DEBUG(" Steam_Matchmaking_Servers::server_details " "%" PRIu64 "\n", g->id()); memset(server->m_szGameTags, 0, sizeof(server->m_szGameTags)); g->tags().copy(server->m_szGameTags, sizeof(server->m_szGameTags) - 1); + // strncpy(server->m_szGameTags, g->tags().c_str(), k_cbMaxGameServerTags - 1); + // server->m_szGameTags[k_cbMaxGameServerTags - 1] = 0; +} - PRINT_DEBUG(" Steam_Matchmaking_Servers::server_details " "%" PRIu64 "\n", g->id()); +void Steam_Matchmaking_Servers::server_details_players(Gameserver *g, Steam_Matchmaking_Servers_Direct_IP_Request *r) +{ + if (!(g->ip() < 0) && !(g->query_port() < 0)) { + unsigned char ip[4]{}; + char newip[24]; + ip[0] = g->ip() & 0xFF; + ip[1] = (g->ip() >> 8) & 0xFF; + ip[2] = (g->ip() >> 16) & 0xFF; + ip[3] = (g->ip() >> 24) & 0xFF; + snprintf(newip, sizeof(newip), "%d.%d.%d.%d", ip[3], ip[2], ip[1], ip[0]); + + PRINT_DEBUG(" server_details_players() connecting to ssq server on %s:%u\n", newip, g->query_port()); + SSQ_SERVER *ssq = ssq_server_new(newip, g->query_port()); + if (ssq != NULL && ssq_server_eok(ssq)) { + PRINT_DEBUG(" server_details_players() ssq server connection ok\n"); + ssq_server_timeout(ssq, (SSQ_TIMEOUT_SELECTOR)(SSQ_TIMEOUT_RECV | SSQ_TIMEOUT_SEND), 1200); + + uint8_t ssq_a2s_player_count = 0; + A2S_PLAYER *ssq_a2s_player = ssq_player(ssq, &ssq_a2s_player_count); + + if (ssq_server_eok(ssq)) { + PRINT_DEBUG(" server_details_players() ssq server players ok\n"); + for (int i = 0; i < ssq_a2s_player_count; i++) { + r->players_response->AddPlayerToList(ssq_a2s_player[i].name, ssq_a2s_player[i].score, ssq_a2s_player[i].duration); + } + } else { + PRINT_DEBUG(" server_details_players() ssq server players failed: %s\n", ssq_server_emsg(ssq)); + } + + if (ssq_a2s_player != NULL) ssq_player_free(ssq_a2s_player, ssq_a2s_player_count); + } else { + PRINT_DEBUG(" server_details_players() ssq server connection failed: %s\n", (ssq ? ssq_server_emsg(ssq) : "NULL instance")); + } + + if (ssq != NULL) ssq_server_free(ssq); + } + + PRINT_DEBUG(" Steam_Matchmaking_Servers::server_details_players " "%" PRIu64 "\n", g->id()); +} + +void Steam_Matchmaking_Servers::server_details_rules(Gameserver *g, Steam_Matchmaking_Servers_Direct_IP_Request *r) +{ + if (!(g->ip() < 0) && !(g->query_port() < 0)) { + unsigned char ip[4]{}; + char newip[24]; + ip[0] = g->ip() & 0xFF; + ip[1] = (g->ip() >> 8) & 0xFF; + ip[2] = (g->ip() >> 16) & 0xFF; + ip[3] = (g->ip() >> 24) & 0xFF; + snprintf(newip, sizeof(newip), "%d.%d.%d.%d", ip[3], ip[2], ip[1], ip[0]); + + PRINT_DEBUG(" server_details_rules() connecting to ssq server on %s:%u\n", newip, g->query_port()); + SSQ_SERVER *ssq = ssq_server_new(newip, g->query_port()); + if (ssq != NULL && ssq_server_eok(ssq)) { + ssq_server_timeout(ssq, (SSQ_TIMEOUT_SELECTOR)(SSQ_TIMEOUT_RECV | SSQ_TIMEOUT_SEND), 1200); + + uint16_t ssq_a2s_rules_count = 0; + A2S_RULES *ssq_a2s_rules = ssq_rules(ssq, &ssq_a2s_rules_count); + + if (ssq_server_eok(ssq)) { + PRINT_DEBUG(" server_details_rules() ssq server rules ok\n"); + for (int i = 0; i < ssq_a2s_rules_count; i++) { + r->rules_response->RulesResponded(ssq_a2s_rules[i].name, ssq_a2s_rules[i].value); + } + } else { + PRINT_DEBUG(" server_details_rules() ssq server rules failed: %s\n", ssq_server_emsg(ssq)); + } + + if (ssq_a2s_rules != NULL) ssq_rules_free(ssq_a2s_rules, ssq_a2s_rules_count); + } else { + PRINT_DEBUG(" server_details_rules() ssq server connection failed: %s\n", (ssq ? ssq_server_emsg(ssq) : "NULL instance")); + } + + if (ssq != NULL) ssq_server_free(ssq); + } + + PRINT_DEBUG(" Steam_Matchmaking_Servers::server_details_rules " "%" PRIu64 "\n", g->id()); } // Get details on a given server in the list, you can get the valid range of index @@ -482,7 +710,7 @@ void Steam_Matchmaking_Servers::RunCallbacks() r.gameservers_filtered.clear(); for (auto &g : gameservers) { PRINT_DEBUG("Steam_Matchmaking_Servers::game_server_check %u %u\n", g.server.appid(), r.appid); - if (g.server.appid() == r.appid) { + if ((g.server.appid() == r.appid) && (g.type == r.type)) { PRINT_DEBUG("Steam_Matchmaking_Servers::REQUESTS server found\n"); r.gameservers_filtered.push_back(g); } @@ -540,9 +768,9 @@ void Steam_Matchmaking_Servers::RunCallbacks() } for (auto &r : direct_ip_requests_temp) { - PRINT_DEBUG("Steam_Matchmaking_Servers dip request: %u:%hu\n", r.ip, r.port); + PRINT_DEBUG("Steam_Matchmaking_Servers::dip request: %u:%hu\n", r.ip, r.port); for (auto &g : gameservers) { - PRINT_DEBUG("Steam_Matchmaking_Servers server: %u:%u\n", g.server.ip(), g.server.query_port()); + PRINT_DEBUG("Steam_Matchmaking_Servers::server: %u:%u\n", g.server.ip(), g.server.query_port()); uint16 query_port = g.server.query_port(); if (query_port == 0xFFFF) { query_port = g.server.port(); @@ -550,31 +778,27 @@ void Steam_Matchmaking_Servers::RunCallbacks() if (query_port == r.port && g.server.ip() == r.ip) { if (r.rules_response) { - int number_rules = (int)g.server.values().size(); - PRINT_DEBUG("Steam_Matchmaking_Servers rules: %i\n", number_rules); - auto rule = g.server.values().begin(); - for (int i = 0; i < number_rules; ++i) { - PRINT_DEBUG("Steam_Matchmaking_Servers RULE '%s' '%s'\n", rule->first.c_str(), rule->second.c_str()); - r.rules_response->RulesResponded(rule->first.c_str(), rule->second.c_str()); - ++rule; - } - + server_details_rules(&(g.server), &r); r.rules_response->RulesRefreshComplete(); r.rules_response = NULL; } + if (r.players_response) { + server_details_players(&(g.server), &r); + r.players_response->PlayersRefreshComplete(); + r.players_response = NULL; + } + if (r.ping_response) { gameserveritem_t server; server_details(&(g.server), &server); r.ping_response->ServerResponded(server); r.ping_response = NULL; } - //TODO: players response } } if (r.rules_response) r.rules_response->RulesRefreshComplete(); - //TODO: player response if (r.players_response) r.players_response->PlayersRefreshComplete(); if (r.ping_response) r.ping_response->ServerFailedToRespond(); } @@ -582,12 +806,13 @@ void Steam_Matchmaking_Servers::RunCallbacks() void Steam_Matchmaking_Servers::Callback(Common_Message *msg) { - if (msg->has_gameserver()) { - PRINT_DEBUG("Steam_Matchmaking_Servers got SERVER " "%" PRIu64 ", offline:%u\n", msg->gameserver().id(), msg->gameserver().offline()); + if (msg->has_gameserver() && msg->gameserver().type() != eFriendsServer) { + PRINT_DEBUG("Steam_Matchmaking_Servers::got SERVER " "%" PRIu64 ", offline:%u\n", msg->gameserver().id(), msg->gameserver().offline()); if (msg->gameserver().offline()) { for (auto &g : gameservers) { if (g.server.id() == msg->gameserver().id()) { g.last_recv = std::chrono::high_resolution_clock::time_point(); + g.type = eLANServer; } } } else { @@ -597,6 +822,7 @@ void Steam_Matchmaking_Servers::Callback(Common_Message *msg) g.last_recv = std::chrono::high_resolution_clock::now(); g.server = msg->gameserver(); g.server.set_ip(msg->source_ip()); + g.type = eLANServer; already = true; } } @@ -606,9 +832,31 @@ void Steam_Matchmaking_Servers::Callback(Common_Message *msg) g.last_recv = std::chrono::high_resolution_clock::now(); g.server = msg->gameserver(); g.server.set_ip(msg->source_ip()); + g.type = eLANServer; gameservers.push_back(g); - PRINT_DEBUG("Steam_Matchmaking_Servers SERVER ADDED\n"); + PRINT_DEBUG("Steam_Matchmaking_Servers::SERVER ADDED\n"); } } } + + if (msg->has_gameserver() && msg->gameserver().type() == eFriendsServer) { + bool addserver = true; + for (auto &g : gameservers_friends) { + if (g.source_id == msg->source_id()) { + g.ip = msg->gameserver().ip(); + g.port = msg->gameserver().port(); + g.last_recv = std::chrono::high_resolution_clock::now(); + addserver = false; + } + } + + if (addserver) { + struct Steam_Matchmaking_Servers_Gameserver_Friends gameserver_friend; + gameserver_friend.source_id = msg->source_id(); + gameserver_friend.ip = msg->gameserver().ip(); + gameserver_friend.port = msg->gameserver().port(); + gameserver_friend.last_recv = std::chrono::high_resolution_clock::now(); + gameservers_friends.push_back(gameserver_friend); + } + } } diff --git a/post_build/README.release.md b/post_build/README.release.md index 1cc528d5..1ff5b46e 100644 --- a/post_build/README.release.md +++ b/post_build/README.release.md @@ -243,6 +243,15 @@ To allow external downloads which will be stored in this `steam_settings\http` f --- +## Avatar: +Copy a `PNG` or `JPG` image to your `Goldberg SteamEmu Settings\settings` folder and name it `account_avatar` + +You can also set a default profile picture for users who are missing one by copying a similar file called `account_avatar_default` + +You can find example in `steam_settings.EXAMPLE` + +--- + ## Support for CPY steam_api(64).dll cracks: See the build in the experimental folder. diff --git a/post_build/steam_settings.EXAMPLE/account_avatar.EXAMPLE.jpg b/post_build/steam_settings.EXAMPLE/account_avatar.EXAMPLE.jpg new file mode 100644 index 0000000000000000000000000000000000000000..aa490cb4ea49d648b7d333c98c8f2acd01695589 GIT binary patch literal 3409 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 None: diff --git a/z_original_repo_files/Readme_release.txt b/z_original_repo_files/Readme_release.txt index 73de5820..45db81c8 100644 --- a/z_original_repo_files/Readme_release.txt +++ b/z_original_repo_files/Readme_release.txt @@ -130,6 +130,11 @@ The Main_Page file would contain the data returned by the steamHTTP api when it An example that was made for payday 2 can be found in steam_settings.EXAMPLE To allow external downloads which will be stored in this steam_settings\http folder copy the disable_lan_only file to the steam_settings folder with .EXAMPLE removed from the file name. +Avatar: +Copy a PNG or JPG image to your Goldberg SteamEmu Settings\settings folder and name it account_avatar +You can also set a default profile picture for users who are missing one by copying a similar file called account_avatar_default +You can find example in steam_settings.EXAMPLE + Support for CPY steam_api(64).dll cracks: See the build in the experimental folder.