Compare commits

...

7 Commits

Author SHA1 Message Date
Nemirtingas
ac24ab0600 Merge branch 'source_query_mr' into 'master'
Source query mr

See merge request Mr_Goldberg/goldberg_emulator!25
2021-02-16 22:23:12 +00:00
Mr_Goldberg
990a0eb71f
Steam networking messages implementation. 2021-02-16 12:37:22 -05:00
Mr_Goldberg
1648c14243
Fixed issue with game treating bool return values as an int. 2021-02-16 12:30:24 -05:00
Nemirtingas
b9129b3932 Merge branch 'master' into 'source_query_mr'
# Conflicts:
#   dll/settings.h
2020-10-13 10:01:41 +00:00
Nemirtingas
122979b276 Update source_query.cpp
Its a short not an int
2019-12-22 14:16:27 +00:00
Nemirtingas
d608f5f29f Merge remote-tracking branch 'Mr_Goldberg/master' into source_query_mr 2019-12-06 11:12:18 +01:00
Nemirtingas
2f17ae5c43 Source Query Support 2019-12-06 11:09:33 +01:00
14 changed files with 793 additions and 23 deletions

View File

@ -769,7 +769,7 @@ STEAMAPI_API void S_CALLTYPE SteamAPI_ManualDispatch_RunFrame( HSteamPipe hSteam
/// Fetch the next pending callback on the given pipe, if any. If a callback is available, true is returned
/// and the structure is populated. In this case, you MUST call SteamAPI_ManualDispatch_FreeLastCallback
/// (after dispatching the callback) before calling SteamAPI_ManualDispatch_GetNextCallback again.
STEAMAPI_API bool S_CALLTYPE SteamAPI_ManualDispatch_GetNextCallback( HSteamPipe hSteamPipe, CallbackMsg_t *pCallbackMsg )
STEAMAPI_API steam_bool S_CALLTYPE SteamAPI_ManualDispatch_GetNextCallback( HSteamPipe hSteamPipe, CallbackMsg_t *pCallbackMsg )
{
PRINT_DEBUG("%s\n", __FUNCTION__);
std::queue<struct cb_data> *q = NULL;
@ -829,7 +829,7 @@ STEAMAPI_API void S_CALLTYPE SteamAPI_ManualDispatch_FreeLastCallback( HSteamPip
/// Return the call result for the specified call on the specified pipe. You really should
/// only call this in a handler for SteamAPICallCompleted_t callback.
STEAMAPI_API bool S_CALLTYPE SteamAPI_ManualDispatch_GetAPICallResult( HSteamPipe hSteamPipe, SteamAPICall_t hSteamAPICall, void *pCallback, int cubCallback, int iCallbackExpected, bool *pbFailed )
STEAMAPI_API steam_bool S_CALLTYPE SteamAPI_ManualDispatch_GetAPICallResult( HSteamPipe hSteamPipe, SteamAPICall_t hSteamAPICall, void *pCallback, int cubCallback, int iCallbackExpected, bool *pbFailed )
{
PRINT_DEBUG("SteamAPI_ManualDispatch_GetAPICallResult %i %llu %i %i\n", hSteamPipe, hSteamAPICall, cubCallback, iCallbackExpected);
Steam_Client *steam_client = get_steam_client();
@ -961,7 +961,7 @@ SteamMasterServerUpdater
*/
STEAMCLIENT_API bool Steam_BGetCallback( HSteamPipe hSteamPipe, CallbackMsg_t *pCallbackMsg )
STEAMCLIENT_API steam_bool Steam_BGetCallback( HSteamPipe hSteamPipe, CallbackMsg_t *pCallbackMsg )
{
PRINT_DEBUG("%s %i\n", __FUNCTION__, hSteamPipe);
SteamAPI_ManualDispatch_Init();
@ -976,7 +976,7 @@ STEAMCLIENT_API void Steam_FreeLastCallback( HSteamPipe hSteamPipe )
SteamAPI_ManualDispatch_FreeLastCallback( hSteamPipe );
}
STEAMCLIENT_API bool Steam_GetAPICallResult( HSteamPipe hSteamPipe, SteamAPICall_t hSteamAPICall, void* pCallback, int cubCallback, int iCallbackExpected, bool* pbFailed )
STEAMCLIENT_API steam_bool Steam_GetAPICallResult( HSteamPipe hSteamPipe, SteamAPICall_t hSteamAPICall, void* pCallback, int cubCallback, int iCallbackExpected, bool* pbFailed )
{
PRINT_DEBUG("Steam_GetAPICallResult %i %llu %i %i\n", hSteamPipe, hSteamAPICall, cubCallback, iCallbackExpected);
return SteamAPI_ManualDispatch_GetAPICallResult(hSteamPipe, hSteamAPICall, pCallback, cubCallback, iCallbackExpected, pbFailed);

View File

@ -125,6 +125,20 @@ message Networking_Sockets {
bytes data = 5;
}
message Networking_Messages {
enum Types {
CONNECTION_NEW = 0;
CONNECTION_ACCEPT = 1;
CONNECTION_END = 2;
DATA = 3;
}
Types type = 1;
uint32 channel = 2;
uint32 id_from = 3;
bytes data = 5;
}
message Gameserver {
uint64 id = 1;
bytes game_description = 2;
@ -212,6 +226,7 @@ message Common_Message {
Network_Old network_old = 12;
Networking_Sockets networking_sockets = 13;
Steam_Messages steam_messages = 14;
Networking_Messages networking_messages = 15;
}
uint32 source_ip = 128;

View File

@ -16,6 +16,7 @@
<http://www.gnu.org/licenses/>. */
#include "network.h"
#include "dll.h"
#define MAX_BROADCASTS 16
static int number_broadcasts = -1;
@ -218,25 +219,26 @@ static int set_socket_nonblocking(sock_t sock)
static void kill_socket(sock_t sock)
{
if (is_socket_valid(sock))
{
#if defined(STEAM_WIN32)
closesocket(sock);
closesocket(sock);
#else
close(sock);
close(sock);
#endif
}
}
static void kill_tcp_socket(struct TCP_Socket &socket)
{
if (is_socket_valid(socket.sock)) {
kill_socket(socket.sock);
}
kill_socket(socket.sock);
socket = TCP_Socket();
}
static bool initialed;
static void run_at_startup()
{
static bool initialed = false;
if (initialed) {
return;
}
@ -554,6 +556,11 @@ void Networking::do_callbacks_message(Common_Message *msg)
PRINT_DEBUG("has_steam_messages\n");
run_callbacks(CALLBACK_ID_STEAM_MESSAGES, msg);
}
if (msg->has_networking_messages()) {
PRINT_DEBUG("has_networking_messages\n");
run_callbacks(CALLBACK_ID_NETWORKING_MESSAGES, msg);
}
}
bool Networking::handle_tcp(Common_Message *msg, struct TCP_Socket &socket)
@ -886,6 +893,22 @@ void Networking::Run()
char data[2048];
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));
@ -1247,3 +1270,74 @@ bool Networking::isAlive()
{
return alive;
}
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;
}

View File

@ -56,6 +56,7 @@ enum Callback_Ids {
CALLBACK_ID_FRIEND_MESSAGES,
CALLBACK_ID_NETWORKING_SOCKETS,
CALLBACK_ID_STEAM_MESSAGES,
CALLBACK_ID_NETWORKING_MESSAGES,
CALLBACK_IDS_MAX
};
@ -85,9 +86,10 @@ struct Connection {
class Networking {
bool enabled = false;
bool query_alive;
bool 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<struct Connection> connections;
@ -135,6 +137,10 @@ public:
void shutDown();
bool isAlive();
void startQuery(IP_PORT ip_port);
void shutDownQuery();
bool isQueryAlive();
};
#endif

View File

@ -145,6 +145,9 @@ public:
//networking
bool disable_networking = false;
//gameserver source query
bool disable_source_query = false;
//overlay
bool disable_overlay = false;
};

265
dll/source_query.cpp Normal file
View File

@ -0,0 +1,265 @@
/* 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
<http://www.gnu.org/licenses/>. */
#include "source_query.h"
#include "dll.h"
using lock_t = std::lock_guard<std::mutex>;
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<uint8_t>& buffer, const void* _data, size_t len)
{
const uint8_t* data = reinterpret_cast<const uint8_t*>(_data);
buffer.insert(buffer.end(), data, data + len);
}
template<typename T>
void serialize_response(std::vector<uint8_t>& buffer, T const& v)
{
uint8_t const* data = reinterpret_cast<uint8_t const*>(&v);
serialize_response(buffer, data, sizeof(T));
}
template<>
void serialize_response<std::string>(std::vector<uint8_t>& buffer, std::string const& v)
{
uint8_t const* str = reinterpret_cast<uint8_t const*>(v.c_str());
serialize_response(buffer, str, v.length()+1);
}
template<typename T, int N>
void serialize_response(std::vector <uint8_t>& buffer, char(&str)[N])
{
serialize_response(buffer, reinterpret_cast<uint8_t const*>(str), N);
}
void get_challenge(std::vector<uint8_t> &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<uint32_t>(0x00112233ul));
}
std::vector<uint8_t> Source_Query::handle_source_query(const void* buffer, size_t len, Gameserver const& gs)
{
std::vector<uint8_t> 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<source_query_data const*>(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))
{
serialize_response(output_buffer, source_query_magic::simple);
serialize_response(output_buffer, source_response_header::A2S_INFO);
serialize_response(output_buffer, static_cast<uint8_t>(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<uint16_t>(gs.appid()));
serialize_response(output_buffer, static_cast<uint8_t>(gs.num_players()));
serialize_response(output_buffer, static_cast<uint8_t>(gs.max_player_count()));
serialize_response(output_buffer, static_cast<uint8_t>(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, 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<uint16_t>(gs.port()));
// add steamid
if (flags & source_server_extra_flag::spectator)
{
serialize_response(output_buffer, static_cast<uint16_t>(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<std::pair<CSteamID, Gameserver_Player_Info_t>> 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<uint8_t>(players.size())); // num_players
for (int i = 0; i < players.size(); ++i)
{
serialize_response(output_buffer, static_cast<uint8_t>(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<float>(std::chrono::duration_cast<std::chrono::seconds>(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<uint16_t>(values.size()));
for (auto const& i : values)
{
serialize_response(output_buffer, i.first);
serialize_response(output_buffer, i.second);
}
}
}
break;
}
return output_buffer;
}

32
dll/source_query.h Normal file
View File

@ -0,0 +1,32 @@
/* 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
<http://www.gnu.org/licenses/>. */
#ifndef __INCLUDED_SOURCE_QUERY__
#define __INCLUDED_SOURCE_QUERY__
#include "base.h"
class Source_Query
{
Source_Query () = delete;
~Source_Query() = delete;
public:
static std::vector<uint8_t> handle_source_query(const void* buffer, size_t len, Gameserver const& gs);
};
#endif

View File

@ -16,6 +16,7 @@
<http://www.gnu.org/licenses/>. */
#include "steam_gameserver.h"
#include "source_query.h"
#define SEND_SERVER_RATE 5.0
@ -33,6 +34,11 @@ Steam_GameServer::~Steam_GameServer()
delete ticket_manager;
}
std::vector<std::pair<CSteamID, Gameserver_Player_Info_t>>* 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.
@ -54,6 +60,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;
@ -71,7 +81,7 @@ void Steam_GameServer::SetProduct( const char *pszProduct )
{
PRINT_DEBUG("SetProduct\n");
std::lock_guard<std::recursive_mutex> lock(global_mutex);
server_data.set_product(pszProduct);
server_data.set_product(pszProduct); // Set product to game name if this is empty
}
@ -344,7 +354,22 @@ bool Steam_GameServer::SendUserConnectAndAuthenticate( uint32 unIPClient, const
PRINT_DEBUG("SendUserConnectAndAuthenticate %u %u\n", unIPClient, cubAuthBlobSize);
std::lock_guard<std::recursive_mutex> lock(global_mutex);
return ticket_manager->SendUserConnectAndAuthenticate(unIPClient, pvAuthBlob, cubAuthBlobSize, pSteamIDUser);
bool res = ticket_manager->SendUserConnectAndAuthenticate(unIPClient, pvAuthBlob, cubAuthBlobSize, pSteamIDUser);
if (res)
{
std::pair<CSteamID, Gameserver_Player_Info_t> infos;
if( pSteamIDUser != nullptr)
infos.first = *pSteamIDUser;
infos.second.join_time = std::chrono::steady_clock::now();
infos.second.score = 0;
infos.second.name = "Player";
players.emplace_back(std::move(infos));
}
return res;
}
@ -357,7 +382,16 @@ CSteamID Steam_GameServer::CreateUnauthenticatedUserConnection()
PRINT_DEBUG("CreateUnauthenticatedUserConnection\n");
std::lock_guard<std::recursive_mutex> lock(global_mutex);
return ticket_manager->fakeUser();
CSteamID bot_id = ticket_manager->fakeUser();
std::pair<CSteamID, Gameserver_Player_Info_t> infos;
infos.first = bot_id;
infos.second.join_time = std::chrono::steady_clock::now();
infos.second.score = 0;
infos.second.name = "Bot";
players.emplace_back(std::move(infos));
return bot_id;
}
@ -369,6 +403,16 @@ void Steam_GameServer::SendUserDisconnect( CSteamID steamIDUser )
PRINT_DEBUG("SendUserDisconnect\n");
std::lock_guard<std::recursive_mutex> lock(global_mutex);
auto player_it = std::find_if(players.begin(), players.end(), [&steamIDUser](std::pair<CSteamID, Gameserver_Player_Info_t>& player)
{
return player.first == steamIDUser;
});
if (player_it != players.end())
{
players.erase(player_it);
}
ticket_manager->endAuth(steamIDUser);
}
@ -381,7 +425,21 @@ void Steam_GameServer::SendUserDisconnect( CSteamID steamIDUser )
bool Steam_GameServer::BUpdateUserData( CSteamID steamIDUser, const char *pchPlayerName, uint32 uScore )
{
PRINT_DEBUG("BUpdateUserData\n");
return true;
auto player_it = std::find_if(players.begin(), players.end(), [&steamIDUser](std::pair<CSteamID, Gameserver_Player_Info_t>& 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.
@ -578,6 +636,18 @@ bool Steam_GameServer::HandleIncomingPacket( const void *pData, int cbData, uint
{
PRINT_DEBUG("HandleIncomingPacket %i %X %i\n", cbData, srcIP, srcPort);
std::lock_guard<std::recursive_mutex> 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;
}
@ -590,6 +660,7 @@ int Steam_GameServer::GetNextOutgoingPacket( void *pOut, int cbMaxOut, uint32 *p
{
PRINT_DEBUG("GetNextOutgoingPacket\n");
std::lock_guard<std::recursive_mutex> 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();
@ -690,6 +761,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();
}
}
}

View File

@ -15,6 +15,9 @@
License along with the Goldberg Emulator; if not, see
<http://www.gnu.org/licenses/>. */
#ifndef __INCLUDED_GAMESERVER__
#define __INCLUDED_GAMESERVER__
#include "base.h"
//-----------------------------------------------------------------------------
@ -22,12 +25,18 @@
//-----------------------------------------------------------------------------
struct Gameserver_Outgoing_Packet {
std::string data;
std::vector<uint8_t> 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 ISteamGameServer005,
public ISteamGameServer008,
@ -47,6 +56,7 @@ public ISteamGameServer
bool logged_in = false;
bool call_servers_disconnected = false;
Gameserver server_data;
std::vector<std::pair<CSteamID, Gameserver_Player_Info_t>> players;
uint32 flags;
bool policy_response_called;
@ -59,6 +69,9 @@ public:
Steam_GameServer(class Settings *settings, class Networking *network, class SteamCallBacks *callbacks);
~Steam_GameServer();
std::vector<std::pair<CSteamID, Gameserver_Player_Info_t>>* get_players();
//
// Basic server data. These properties, if set, must be set before before calling LogOn. They
// may not be changed after logged in.
@ -329,3 +342,5 @@ public:
//
void RunCallbacks();
};
#endif

View File

@ -15,6 +15,9 @@
License along with the Goldberg Emulator; if not, see
<http://www.gnu.org/licenses/>. */
#ifndef __INCLUDED_STEAM_MATCHMAKING__
#define __INCLUDED_STEAM_MATCHMAKING__
#include "base.h"
#define SEND_LOBBY_RATE 5.0
@ -1479,3 +1482,5 @@ void Callback(Common_Message *msg)
};
#endif

View File

@ -17,6 +17,22 @@
#include "base.h"
#define NETWORKING_MESSAGES_TIMEOUT 30.0
struct Steam_Message_Connection {
SteamNetworkingIdentity remote_identity;
std::map<int, std::queue<std::string>> data;
std::list<int> channels;
bool accepted = false;
bool dead = false;
unsigned id;
unsigned remote_id = 0;
std::chrono::high_resolution_clock::time_point created = std::chrono::high_resolution_clock::now();
};
class Steam_Networking_Messages :
public ISteamNetworkingMessages
{
@ -26,7 +42,13 @@ public ISteamNetworkingMessages
class SteamCallBacks *callbacks;
class RunEveryRunCB *run_every_runcb;
std::map<CSteamID, Steam_Message_Connection> connections;
std::list<Common_Message> incoming_data;
unsigned id_counter = 0;
std::chrono::steady_clock::time_point created;
public:
static void steam_callback(void *object, Common_Message *msg)
{
PRINT_DEBUG("steam_networking_messages_callback\n");
@ -48,11 +70,14 @@ Steam_Networking_Messages(class Settings *settings, class Networking *network, c
this->settings = settings;
this->network = network;
this->run_every_runcb = run_every_runcb;
this->network->setCallback(CALLBACK_ID_NETWORKING_MESSAGES, settings->get_local_steam_id(), &Steam_Networking_Messages::steam_callback, this);
this->network->setCallback(CALLBACK_ID_USER_STATUS, settings->get_local_steam_id(), &Steam_Networking_Messages::steam_callback, this);
this->run_every_runcb->add(&Steam_Networking_Messages::steam_run_every_runcb, this);
this->callback_results = callback_results;
this->callbacks = callbacks;
this->created = std::chrono::steady_clock::now();
}
~Steam_Networking_Messages()
@ -61,6 +86,45 @@ Steam_Networking_Messages(class Settings *settings, class Networking *network, c
this->run_every_runcb->remove(&Steam_Networking_Messages::steam_run_every_runcb, this);
}
std::map<CSteamID, Steam_Message_Connection>::iterator find_or_create_message_connection(SteamNetworkingIdentity identityRemote, bool incoming, bool restartbroken)
{
auto conn = connections.find(identityRemote.GetSteamID());
if (conn == connections.end() || (conn->second.dead && restartbroken)) {
++id_counter;
struct Steam_Message_Connection con;
con.remote_identity = identityRemote;
con.id = id_counter;
connections[identityRemote.GetSteamID()] = con;
Common_Message msg;
msg.set_source_id(settings->get_local_steam_id().ConvertToUint64());
msg.set_dest_id(con.remote_identity.GetSteamID64());
msg.set_allocated_networking_messages(new Networking_Messages);
if (incoming) {
msg.mutable_networking_messages()->set_type(Networking_Messages::CONNECTION_ACCEPT);
} else {
msg.mutable_networking_messages()->set_type(Networking_Messages::CONNECTION_NEW);
}
msg.mutable_networking_messages()->set_channel(0);
msg.mutable_networking_messages()->set_id_from(con.id);
network->sendTo(&msg, true);
conn = connections.find(identityRemote.GetSteamID());
if (incoming) {
SteamNetworkingMessagesSessionRequest_t data;
data.m_identityRemote = con.remote_identity;
callbacks->addCBResult(data.k_iCallback, &data, sizeof(data));
}
}
if (!incoming) {
conn->second.accepted = true;
}
return conn;
}
/// Sends a message to the specified host. If we don't already have a session with that user,
/// a session is implicitly created. There might be some handshaking that needs to happen
/// before we can actually begin sending message data. If this handshaking fails and we can't
@ -106,7 +170,57 @@ Steam_Networking_Messages(class Settings *settings, class Networking *network, c
EResult SendMessageToUser( const SteamNetworkingIdentity &identityRemote, const void *pubData, uint32 cubData, int nSendFlags, int nRemoteChannel )
{
PRINT_DEBUG("Steam_Networking_Messages::SendMessageToUser\n");
return k_EResultNoConnection;
std::lock_guard<std::recursive_mutex> lock(global_mutex);
const SteamNetworkingIPAddr *ip = identityRemote.GetIPAddr();
bool reliable = false;
if (nSendFlags & k_nSteamNetworkingSend_Reliable) {
reliable = true;
}
bool restart_broken = false;
if (nSendFlags & k_nSteamNetworkingSend_AutoRestartBrokenSession) {
restart_broken = true;
}
if (identityRemote.m_eType == k_ESteamNetworkingIdentityType_SteamID) {
PRINT_DEBUG("Steam_Networking_Messages::SendMessageToUser %llu\n", identityRemote.GetSteamID64());
//steam id identity
} else if (ip) {
PRINT_DEBUG("Steam_Networking_Messages::SendMessageToUser %u:%u ipv4? %u\n", ip->GetIPv4(), ip->m_port, ip->IsIPv4());
//ip addr
return k_EResultNoConnection; //TODO
} else {
return k_EResultNoConnection;
}
auto conn = find_or_create_message_connection(identityRemote, false, restart_broken);
if (conn->second.dead) {
return k_EResultNoConnection;
}
Common_Message msg;
msg.set_source_id(settings->get_local_steam_id().ConvertToUint64());
msg.set_dest_id(conn->second.remote_identity.GetSteamID64());
msg.set_allocated_networking_messages(new Networking_Messages);
msg.mutable_networking_messages()->set_type(Networking_Messages::DATA);
msg.mutable_networking_messages()->set_channel(nRemoteChannel);
msg.mutable_networking_messages()->set_id_from(conn->second.id);
msg.mutable_networking_messages()->set_data(pubData, cubData);
network->sendTo(&msg, reliable);
return k_EResultOK;
}
static void free_steam_message_data(SteamNetworkingMessage_t *pMsg)
{
free(pMsg->m_pData);
pMsg->m_pData = NULL;
}
static void delete_steam_message(SteamNetworkingMessage_t *pMsg)
{
if (pMsg->m_pfnFreeData) pMsg->m_pfnFreeData(pMsg);
delete pMsg;
}
/// Reads the next message that has been sent from another user via SendMessageToUser() on the given channel.
@ -116,7 +230,42 @@ EResult SendMessageToUser( const SteamNetworkingIdentity &identityRemote, const
int ReceiveMessagesOnChannel( int nLocalChannel, SteamNetworkingMessage_t **ppOutMessages, int nMaxMessages )
{
PRINT_DEBUG("Steam_Networking_Messages::ReceiveMessagesOnChannel\n");
return 0;
std::lock_guard<std::recursive_mutex> lock(global_mutex);
int message_counter = 0;
for (auto & conn : connections) {
auto chan = conn.second.data.find(nLocalChannel);
if (chan != conn.second.data.end()) {
while (!chan->second.empty() && message_counter <= nMaxMessages) {
SteamNetworkingMessage_t *pMsg = new SteamNetworkingMessage_t(); //TODO size is wrong
unsigned long size = chan->second.front().size();
pMsg->m_pData = malloc(size);
pMsg->m_cbSize = size;
memcpy(pMsg->m_pData, chan->second.front().data(), size);
pMsg->m_conn = conn.second.id;
pMsg->m_identityPeer = conn.second.remote_identity;
pMsg->m_nConnUserData = -1;
pMsg->m_usecTimeReceived = std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::steady_clock::now() - created).count();
//TODO: messagenumber?
// pMsg->m_nMessageNumber = connect_socket->second.packet_receive_counter;
// ++connect_socket->second.packet_receive_counter;
pMsg->m_pfnFreeData = &free_steam_message_data;
pMsg->m_pfnRelease = &delete_steam_message;
pMsg->m_nChannel = nLocalChannel;
ppOutMessages[message_counter] = pMsg;
++message_counter;
chan->second.pop();
}
}
if (message_counter >= nMaxMessages) {
break;
}
}
PRINT_DEBUG("Steam_Networking_Messages::ReceiveMessagesOnChannel got %u\n", message_counter);
return message_counter;
}
/// AcceptSessionWithUser() should only be called in response to a SteamP2PSessionRequest_t callback
@ -129,7 +278,14 @@ int ReceiveMessagesOnChannel( int nLocalChannel, SteamNetworkingMessage_t **ppOu
bool AcceptSessionWithUser( const SteamNetworkingIdentity &identityRemote )
{
PRINT_DEBUG("Steam_Networking_Messages::AcceptSessionWithUser\n");
return false;
std::lock_guard<std::recursive_mutex> lock(global_mutex);
auto conn = connections.find(identityRemote.GetSteamID());
if (conn == connections.end()) {
return false;
}
conn->second.accepted = true;
return true;
}
/// Call this when you're done talking to a user to immediately free up resources under-the-hood.
@ -140,7 +296,23 @@ bool AcceptSessionWithUser( const SteamNetworkingIdentity &identityRemote )
bool CloseSessionWithUser( const SteamNetworkingIdentity &identityRemote )
{
PRINT_DEBUG("Steam_Networking_Messages::CloseSessionWithUser\n");
return false;
std::lock_guard<std::recursive_mutex> lock(global_mutex);
auto conn = connections.find(identityRemote.GetSteamID());
if (conn == connections.end()) {
return false;
}
Common_Message msg;
msg.set_source_id(settings->get_local_steam_id().ConvertToUint64());
msg.set_dest_id(conn->second.remote_identity.GetSteamID64());
msg.set_allocated_networking_messages(new Networking_Messages);
msg.mutable_networking_messages()->set_type(Networking_Messages::CONNECTION_END);
msg.mutable_networking_messages()->set_channel(0);
msg.mutable_networking_messages()->set_id_from(conn->second.id);
network->sendTo(&msg, true);
connections.erase(conn);
return true;
}
/// Call this when you're done talking to a user on a specific channel. Once all
@ -150,6 +322,7 @@ bool CloseSessionWithUser( const SteamNetworkingIdentity &identityRemote )
bool CloseChannelWithUser( const SteamNetworkingIdentity &identityRemote, int nLocalChannel )
{
PRINT_DEBUG("Steam_Networking_Messages::CloseChannelWithUser\n");
std::lock_guard<std::recursive_mutex> lock(global_mutex);
return false;
}
@ -165,12 +338,62 @@ bool CloseChannelWithUser( const SteamNetworkingIdentity &identityRemote, int nL
ESteamNetworkingConnectionState GetSessionConnectionInfo( const SteamNetworkingIdentity &identityRemote, SteamNetConnectionInfo_t *pConnectionInfo, SteamNetworkingQuickConnectionStatus *pQuickStatus )
{
PRINT_DEBUG("Steam_Networking_Messages::GetSessionConnectionInfo\n");
return k_ESteamNetworkingConnectionState_None;
std::lock_guard<std::recursive_mutex> lock(global_mutex);
auto conn = connections.find(identityRemote.GetSteamID());
if (conn == connections.end()) {
return k_ESteamNetworkingConnectionState_None;
}
if (pConnectionInfo) {
//TODO
}
if (pQuickStatus) {
//TODO
}
if (conn->second.remote_id == 0 || !conn->second.accepted) {
return k_ESteamNetworkingConnectionState_Connecting;
}
if (conn->second.dead) {
return k_ESteamNetworkingConnectionState_ClosedByPeer;
}
return k_ESteamNetworkingConnectionState_Connected;
}
void end_connection(CSteamID steam_id)
{
auto conn = connections.find(steam_id);
if (conn != connections.end()) {
conn->second.dead = true;
}
}
void RunCallbacks()
{
auto msg = std::begin(incoming_data);
while (msg != std::end(incoming_data)) {
CSteamID source_id((uint64)msg->source_id());
auto conn = connections.find(source_id);
if (conn != connections.end()) {
if (conn->second.remote_id == msg->networking_messages().id_from())
conn->second.data[msg->networking_messages().channel()].push(msg->networking_messages().data());
}
msg = incoming_data.erase(msg);
}
auto conn = std::begin(connections);
while (conn != std::end(connections)) {
if (!conn->second.accepted && check_timedout(conn->second.created, NETWORKING_MESSAGES_TIMEOUT)) {
conn = connections.erase(conn);
} else {
++conn;
}
}
}
void Callback(Common_Message *msg)
@ -181,7 +404,33 @@ void Callback(Common_Message *msg)
}
if (msg->low_level().type() == Low_Level::DISCONNECT) {
end_connection((uint64)msg->source_id());
}
}
if (msg->has_networking_messages()) {
PRINT_DEBUG("Steam_Networking_Messages: got network socket msg %u\n", msg->networking_messages().type());
if (msg->networking_messages().type() == Networking_Messages::CONNECTION_NEW) {
SteamNetworkingIdentity identity;
identity.SetSteamID64(msg->source_id());
auto conn = find_or_create_message_connection(identity, true, false);
conn->second.remote_id = msg->networking_messages().id_from();
conn->second.dead = false;
}
if (msg->networking_messages().type() == Networking_Messages::CONNECTION_ACCEPT) {
auto conn = connections.find((uint64)msg->source_id());
if (conn != connections.end()) {
conn->second.remote_id = msg->networking_messages().id_from();
}
}
if (msg->networking_messages().type() == Networking_Messages::CONNECTION_END) {
end_connection((uint64)msg->source_id());
}
if (msg->networking_messages().type() == Networking_Messages::DATA) {
incoming_data.push_back(Common_Message(*msg));
}
}
}

View File

@ -15,6 +15,9 @@
License along with the Goldberg Emulator; if not, see
<http://www.gnu.org/licenses/>. */
#ifndef __INCLUDED_STEAM_UTILS__
#define __INCLUDED_STEAM_UTILS__
#include "base.h"
#include "local_storage.h"
#include "../overlay_experimental/steam_overlay.h"
@ -406,3 +409,5 @@ ESteamIPv6ConnectivityState GetIPv6ConnectivityState( ESteamIPv6ConnectivityProt
}
};
#endif

View File

@ -311,14 +311,14 @@ S_API void S_CALLTYPE SteamAPI_ManualDispatch_RunFrame( HSteamPipe hSteamPipe );
/// Fetch the next pending callback on the given pipe, if any. If a callback is available, true is returned
/// and the structure is populated. In this case, you MUST call SteamAPI_ManualDispatch_FreeLastCallback
/// (after dispatching the callback) before calling SteamAPI_ManualDispatch_GetNextCallback again.
S_API bool S_CALLTYPE SteamAPI_ManualDispatch_GetNextCallback( HSteamPipe hSteamPipe, CallbackMsg_t *pCallbackMsg );
S_API steam_bool S_CALLTYPE SteamAPI_ManualDispatch_GetNextCallback( HSteamPipe hSteamPipe, CallbackMsg_t *pCallbackMsg );
/// You must call this after dispatching the callback, if SteamAPI_ManualDispatch_GetNextCallback returns true.
S_API void S_CALLTYPE SteamAPI_ManualDispatch_FreeLastCallback( HSteamPipe hSteamPipe );
/// Return the call result for the specified call on the specified pipe. You really should
/// only call this in a handler for SteamAPICallCompleted_t callback.
S_API bool S_CALLTYPE SteamAPI_ManualDispatch_GetAPICallResult( HSteamPipe hSteamPipe, SteamAPICall_t hSteamAPICall, void *pCallback, int cubCallback, int iCallbackExpected, bool *pbFailed );
S_API steam_bool S_CALLTYPE SteamAPI_ManualDispatch_GetAPICallResult( HSteamPipe hSteamPipe, SteamAPICall_t hSteamAPICall, void *pCallback, int cubCallback, int iCallbackExpected, bool *pbFailed );
//----------------------------------------------------------------------------------------------------------------------------------------------------------//
//

View File

@ -10,6 +10,12 @@
#pragma once
#endif
/*
for some dumb reason some games like carrion think a bool is an int and use the whole register as a return value
instead of using just al like a normal program.
*/
typedef unsigned steam_bool;
#define S_CALLTYPE __cdecl
// Steam-specific types. Defined here so this header file can be included in other code bases.