2010-11-20 10:37:14 +00:00
/* (c) Magnus Auvinen. See licence.txt in the root of the distribution for more information. */
/* If you are missing that file, acquire a complete release at teeworlds.com. */
2017-03-12 13:49:18 +00:00
2021-01-26 20:22:32 +00:00
# include "server.h"
2022-04-22 23:04:48 +00:00
# include <base/logger.h>
2011-07-31 11:17:38 +00:00
# include <base/math.h>
2010-05-29 07:25:38 +00:00
# include <base/system.h>
2011-02-27 14:03:57 +00:00
# include <engine/config.h>
# include <engine/console.h>
# include <engine/engine.h>
# include <engine/map.h>
# include <engine/server.h>
# include <engine/storage.h>
2010-05-29 07:25:38 +00:00
# include <engine/shared/compression.h>
# include <engine/shared/config.h>
2022-06-17 16:52:05 +00:00
# include <engine/shared/console.h>
2010-09-12 10:16:51 +00:00
# include <engine/shared/demo.h>
2011-07-30 11:40:01 +00:00
# include <engine/shared/econ.h>
2020-09-26 19:41:58 +00:00
# include <engine/shared/fifo.h>
2011-07-30 16:29:40 +00:00
# include <engine/shared/filecollection.h>
2023-10-22 10:30:23 +00:00
# include <engine/shared/host_lookup.h>
2022-05-23 15:19:17 +00:00
# include <engine/shared/http.h>
Add HTTP masterserver registering and HTTP masterserver
Registering
-----------
The idea is that game servers push their server info to the
masterservers every 15 seconds or when the server info changes, but not
more than once per second.
The game servers do not support the old registering protocol anymore,
the backward compatibility is handled by the masterserver.
The register call is a HTTP POST to a URL like
`https://master1.ddnet.tw/ddnet/15/register` and looks like this:
```json
POST /ddnet/15/register HTTP/1.1
Address: tw-0.6+udp://connecting-address.invalid:8303
Secret: 81fa3955-6f83-4290-818d-31c0906b1118
Challenge-Secret: 81fa3955-6f83-4290-818d-31c0906b1118:tw0.6/ipv6
Info-Serial: 0
{
"max_clients": 64,
"max_players": 64,
"passworded": false,
"game_type": "TestDDraceNetwork",
"name": "My DDNet server",
"map": {
"name": "dm1",
"sha256": "0b0c481d77519c32fbe85624ef16ec0fa9991aec7367ad538bd280f28d8c26cf",
"size": 5805
},
"version": "0.6.4, 16.0.3",
"clients": []
}
```
The `Address` header declares that the server wants to register itself as
a `tw-0.6+udp` server, i.e. a server speaking a Teeworlds-0.6-compatible
protocol.
The free-form `Secret` header is used as a server identity, the server
list will be deduplicated via this secret.
The free-form `Challenge-Secret` is sent back via UDP for a port forward
check. This might have security implications as the masterserver can be
asked to send a UDP packet containing some user-controlled bytes. This
is somewhat mitigated by the fact that it can only go to an
attacker-controlled IP address.
The `Info-Serial` header is an integer field that should increase each
time the server info (in the body) changes. The masterserver uses that
field to ensure that it doesn't use old server infos.
The body is a free-form JSON object set by the game server. It should
contain certain keys in the correct form to be accepted by clients. The
body is optional if the masterserver already confirmed the reception of
the info with the given `Info-Serial`.
Not shown in this payload is the `Connless-Token` header that is used
for Teeworlds 0.7 style communication.
Also not shown is the `Challenge-Token` that should be included once the
server receives the challenge token via UDP.
The masterserver responds with a `200 OK` with a body like this:
```
{"status":"success"}
```
The `status` field can be `success` if the server was successfully
registered on the masterserver, `need_challenge` if the masterserver
wants the correct `Challenge-Token` header before the register process
is successful, `need_info` if the server sent an empty body but the
masterserver doesn't actually know the server info.
It can also be `error` if the request was malformed, only in this case
an HTTP status code except `200 OK` is sent.
Synchronization
---------------
The masterserver keeps state and outputs JSON files every second.
```json
{
"servers": [
{
"addresses": [
"tw-0.6+udp://127.0.0.1:8303",
"tw-0.6+udp://[::1]:8303"
],
"info_serial": 0,
"info": {
"max_clients": 64,
"max_players": 64,
"passworded": false,
"game_type": "TestDDraceNetwork",
"name": "My DDNet server",
"map": {
"name": "dm1",
"sha256": "0b0c481d77519c32fbe85624ef16ec0fa9991aec7367ad538bd280f28d8c26cf",
"size": 5805
},
"version": "0.6.4, 16.0.3",
"clients": []
}
}
]
}
```
`servers.json` (or configured by `--out`) is a server list that is
compatible with DDNet 15.5+ clients. It is a JSON object containing a
single key `servers` with a list of game servers. Each game server is
represented by a JSON object with an `addresses` key containing a list
of all known addresses of the server and an `info` key containing the
free-form server info sent by the game server. The free-form `info` JSON
object re-encoded by the master server and thus canonicalized and
stripped of any whitespace characters outside strings.
```json
{
"kind": "mastersrv",
"now": 1816002,
"secrets": {
"tw-0.6+udp://127.0.0.1:8303": {
"ping_time": 1811999,
"secret": "42d8f991-f2fa-46e5-a9ae-ebcc93846feb"
},
"tw-0.6+udp://[::1]:8303": {
"ping_time": 1811999,
"secret": "42d8f991-f2fa-46e5-a9ae-ebcc93846feb"
}
},
"servers": {
"42d8f991-f2fa-46e5-a9ae-ebcc93846feb": {
"info_serial": 0,
"info": {
"max_clients": 64,
"max_players": 64,
"passworded": false,
"game_type": "TestDDraceNetwork",
"name": "My DDNet server",
"map": {
"name": "dm1",
"sha256": "0b0c481d77519c32fbe85624ef16ec0fa9991aec7367ad538bd280f28d8c26cf",
"size": 5805
},
"version": "0.6.4, 16.0.3",
"clients": []
}
}
}
}
```
`--write-dump` outputs a JSON file compatible with `--read-dump-dir`,
this can be used to synchronize servers across different masterservers.
`--read-dump-dir` is also used to ingest servers from the backward
compatibility layer that pings each server for their server info using
the old protocol.
The `kind` field describe that this is `mastersrv` output and not from a
`backcompat`. This is used for prioritizing `mastersrv` information over
`backcompat` information.
The `now` field contains an integer describing the current time in
milliseconds relative an unspecified epoch that is fixed for each JSON
file. This is done instead of using the current time as the epoch for
better compression of non-changing data.
`secrets` is a map from each server address and to a JSON object
containing the last ping time (`ping_time`) in milliseconds relative to
the same epoch as before, and the server secret (`secret`) that is used
to unify server infos from different addresses of the same logical
server.
`servers` is a map from the aforementioned `secret`s to the
corresponding `info_serial` and `info`.
```json
[
"tw-0.6+udp://127.0.0.1:8303",
"tw-0.6+udp://[::1]:8303"
]
```
`--write-addresses` outputs a JSON file containing all addresses
corresponding to servers that are registered to HTTP masterservers. It
does not contain the servers that are obtained via backward
compatibility measures.
This file can be used by an old-style masterserver to also list
new-style servers without the game servers having to register there.
An implementation of this can be found at
https://github.com/heinrich5991/teeworlds/tree/mastersrv_6_backcompat
for Teeworlds 0.5/0.6 masterservers and at
https://github.com/heinrich5991/teeworlds/tree/mastersrv_7_backcompat
for Teeworlds 0.7 masterservers.
All these JSON files can be sent over the network in an efficient way
using https://github.com/heinrich5991/twmaster-collect. It establishes a
zstd-compressed TCP connection authenticated by a string token that is
sent in plain-text. It watches the specified file and transmits it every
time it changes. Due to the zstd-compression, the data sent over the
network is similar to the size of a diff.
Implementation
--------------
The masterserver implementation was done in Rust.
The current gameserver register implementation doesn't support more than
one masterserver for registering.
2022-05-19 20:03:17 +00:00
# include <engine/shared/json.h>
# include <engine/shared/masterserver.h>
2011-12-29 22:36:53 +00:00
# include <engine/shared/netban.h>
2011-02-27 14:03:57 +00:00
# include <engine/shared/network.h>
# include <engine/shared/packer.h>
# include <engine/shared/protocol.h>
2023-02-14 10:34:15 +00:00
# include <engine/shared/protocol7.h>
2017-05-21 23:07:13 +00:00
# include <engine/shared/protocol_ex.h>
2022-10-19 21:46:06 +00:00
# include <engine/shared/rust_version.h>
2011-02-27 14:03:57 +00:00
# include <engine/shared/snapshot.h>
2010-05-29 07:25:38 +00:00
2021-12-21 22:05:44 +00:00
# include <game/version.h>
2011-04-26 09:51:02 +00:00
// DDRace
# include <engine/shared/linereader.h>
2020-09-26 19:41:58 +00:00
# include <vector>
2020-06-19 21:52:13 +00:00
# include <zlib.h>
2011-04-26 09:51:02 +00:00
2021-01-26 20:22:32 +00:00
# include "databases/connection.h"
# include "databases/connection_pool.h"
2010-05-29 07:25:38 +00:00
# include "register.h"
2022-05-26 23:17:01 +00:00
extern bool IsInterrupted ( ) ;
2021-06-12 14:51:55 +00:00
2017-03-21 10:24:44 +00:00
void CServerBan : : InitServerBan ( IConsole * pConsole , IStorage * pStorage , CServer * pServer )
2011-12-29 22:36:53 +00:00
{
CNetBan : : Init ( pConsole , pStorage ) ;
m_pServer = pServer ;
// overwrites base command, todo: improve this
2020-09-26 19:41:58 +00:00
Console ( ) - > Register ( " ban " , " s[ip|id] ?i[minutes] r[reason] " , CFGFLAG_SERVER | CFGFLAG_STORE , ConBanExt , this , " Ban player with ip/client id for x minutes for any reason " ) ;
Console ( ) - > Register ( " ban_region " , " s[region] s[ip|id] ?i[minutes] r[reason] " , CFGFLAG_SERVER | CFGFLAG_STORE , ConBanRegion , this , " Ban player in a region " ) ;
Console ( ) - > Register ( " ban_region_range " , " s[region] s[first ip] s[last ip] ?i[minutes] r[reason] " , CFGFLAG_SERVER | CFGFLAG_STORE , ConBanRegionRange , this , " Ban range in a region " ) ;
2011-12-29 22:36:53 +00:00
}
template < class T >
int CServerBan : : BanExt ( T * pBanPool , const typename T : : CDataType * pData , int Seconds , const char * pReason )
{
// validate address
if ( Server ( ) - > m_RconClientID > = 0 & & Server ( ) - > m_RconClientID < MAX_CLIENTS & &
Server ( ) - > m_aClients [ Server ( ) - > m_RconClientID ] . m_State ! = CServer : : CClient : : STATE_EMPTY )
{
if ( NetMatch ( pData , Server ( ) - > m_NetServer . ClientAddr ( Server ( ) - > m_RconClientID ) ) )
{
Console ( ) - > Print ( IConsole : : OUTPUT_LEVEL_STANDARD , " net_ban " , " ban error (you can't ban yourself) " ) ;
return - 1 ;
}
for ( int i = 0 ; i < MAX_CLIENTS ; + + i )
{
if ( i = = Server ( ) - > m_RconClientID | | Server ( ) - > m_aClients [ i ] . m_State = = CServer : : CClient : : STATE_EMPTY )
continue ;
if ( Server ( ) - > m_aClients [ i ] . m_Authed > = Server ( ) - > m_RconAuthLevel & & NetMatch ( pData , Server ( ) - > m_NetServer . ClientAddr ( i ) ) )
{
Console ( ) - > Print ( IConsole : : OUTPUT_LEVEL_STANDARD , " net_ban " , " ban error (command denied) " ) ;
return - 1 ;
}
}
}
2011-12-31 11:11:48 +00:00
else if ( Server ( ) - > m_RconClientID = = IServer : : RCON_CID_VOTE )
{
for ( int i = 0 ; i < MAX_CLIENTS ; + + i )
{
if ( Server ( ) - > m_aClients [ i ] . m_State = = CServer : : CClient : : STATE_EMPTY )
continue ;
2019-03-02 10:50:33 +00:00
if ( Server ( ) - > m_aClients [ i ] . m_Authed ! = AUTHED_NO & & NetMatch ( pData , Server ( ) - > m_NetServer . ClientAddr ( i ) ) )
2011-12-31 11:11:48 +00:00
{
Console ( ) - > Print ( IConsole : : OUTPUT_LEVEL_STANDARD , " net_ban " , " ban error (command denied) " ) ;
return - 1 ;
}
}
}
2011-12-29 22:36:53 +00:00
int Result = Ban ( pBanPool , pData , Seconds , pReason ) ;
if ( Result ! = 0 )
return Result ;
// drop banned clients
2011-12-30 18:35:57 +00:00
typename T : : CDataType Data = * pData ;
2011-12-29 22:36:53 +00:00
for ( int i = 0 ; i < MAX_CLIENTS ; + + i )
{
if ( Server ( ) - > m_aClients [ i ] . m_State = = CServer : : CClient : : STATE_EMPTY )
continue ;
if ( NetMatch ( & Data , Server ( ) - > m_NetServer . ClientAddr ( i ) ) )
{
CNetHash NetHash ( & Data ) ;
char aBuf [ 256 ] ;
MakeBanInfo ( pBanPool - > Find ( & Data , & NetHash ) , aBuf , sizeof ( aBuf ) , MSGTYPE_PLAYER ) ;
Server ( ) - > m_NetServer . Drop ( i , aBuf ) ;
}
}
return Result ;
}
int CServerBan : : BanAddr ( const NETADDR * pAddr , int Seconds , const char * pReason )
{
return BanExt ( & m_BanAddrPool , pAddr , Seconds , pReason ) ;
}
int CServerBan : : BanRange ( const CNetRange * pRange , int Seconds , const char * pReason )
{
if ( pRange - > IsValid ( ) )
return BanExt ( & m_BanRangePool , pRange , Seconds , pReason ) ;
Console ( ) - > Print ( IConsole : : OUTPUT_LEVEL_STANDARD , " net_ban " , " ban failed (invalid range) " ) ;
return - 1 ;
}
void CServerBan : : ConBanExt ( IConsole : : IResult * pResult , void * pUser )
{
CServerBan * pThis = static_cast < CServerBan * > ( pUser ) ;
const char * pStr = pResult - > GetString ( 0 ) ;
2021-10-20 18:26:44 +00:00
int Minutes = pResult - > NumArguments ( ) > 1 ? clamp ( pResult - > GetInteger ( 1 ) , 0 , 525600 ) : 10 ;
const char * pReason = pResult - > NumArguments ( ) > 2 ? pResult - > GetString ( 2 ) : " Follow the server rules. Type /rules into the chat. " ;
2011-12-29 22:36:53 +00:00
2019-02-27 19:24:31 +00:00
if ( str_isallnum ( pStr ) )
2011-12-29 22:36:53 +00:00
{
int ClientID = str_toint ( pStr ) ;
if ( ClientID < 0 | | ClientID > = MAX_CLIENTS | | pThis - > Server ( ) - > m_aClients [ ClientID ] . m_State = = CServer : : CClient : : STATE_EMPTY )
pThis - > Console ( ) - > Print ( IConsole : : OUTPUT_LEVEL_STANDARD , " net_ban " , " ban error (invalid client id) " ) ;
else
2020-09-26 19:41:58 +00:00
pThis - > BanAddr ( pThis - > Server ( ) - > m_NetServer . ClientAddr ( ClientID ) , Minutes * 60 , pReason ) ;
2011-12-29 22:36:53 +00:00
}
else
ConBan ( pResult , pUser ) ;
}
2020-08-19 09:38:49 +00:00
void CServerBan : : ConBanRegion ( IConsole : : IResult * pResult , void * pUser )
{
const char * pRegion = pResult - > GetString ( 0 ) ;
2020-08-19 11:07:55 +00:00
if ( str_comp_nocase ( pRegion , g_Config . m_SvRegionName ) )
2020-08-19 09:38:49 +00:00
return ;
pResult - > RemoveArgument ( 0 ) ;
ConBanExt ( pResult , pUser ) ;
}
2011-12-29 22:36:53 +00:00
2020-08-19 11:07:55 +00:00
void CServerBan : : ConBanRegionRange ( IConsole : : IResult * pResult , void * pUser )
{
CServerBan * pServerBan = static_cast < CServerBan * > ( pUser ) ;
const char * pRegion = pResult - > GetString ( 0 ) ;
if ( str_comp_nocase ( pRegion , g_Config . m_SvRegionName ) )
return ;
pResult - > RemoveArgument ( 0 ) ;
ConBanRange ( pResult , static_cast < CNetBan * > ( pServerBan ) ) ;
}
2022-04-22 23:04:48 +00:00
// Not thread-safe!
class CRconClientLogger : public ILogger
{
CServer * m_pServer ;
int m_ClientID ;
public :
CRconClientLogger ( CServer * pServer , int ClientID ) :
m_pServer ( pServer ) ,
m_ClientID ( ClientID )
{
}
void Log ( const CLogMessage * pMessage ) override ;
} ;
void CRconClientLogger : : Log ( const CLogMessage * pMessage )
{
2022-06-13 19:43:05 +00:00
if ( m_Filter . Filters ( pMessage ) )
{
return ;
}
2022-04-22 23:04:48 +00:00
m_pServer - > SendRconLogLine ( m_ClientID , pMessage ) ;
}
2010-05-29 07:25:38 +00:00
void CServer : : CClient : : Reset ( )
{
// reset input
2020-10-26 14:14:07 +00:00
for ( auto & Input : m_aInputs )
Input . m_GameTick = - 1 ;
2010-05-29 07:25:38 +00:00
m_CurrentInput = 0 ;
mem_zero ( & m_LatestInput , sizeof ( m_LatestInput ) ) ;
m_Snapshots . PurgeAll ( ) ;
m_LastAckedSnapshot = - 1 ;
m_LastInputTick = - 1 ;
m_SnapRate = CClient : : SNAPRATE_INIT ;
2022-10-12 14:07:35 +00:00
m_Score = - 1 ;
2016-11-18 14:00:42 +00:00
m_NextMapChunk = 0 ;
2019-04-03 13:07:05 +00:00
m_Flags = 0 ;
2023-06-23 08:18:47 +00:00
m_RedirectDropTime = 0 ;
2010-05-29 07:25:38 +00:00
}
Add HTTP masterserver registering and HTTP masterserver
Registering
-----------
The idea is that game servers push their server info to the
masterservers every 15 seconds or when the server info changes, but not
more than once per second.
The game servers do not support the old registering protocol anymore,
the backward compatibility is handled by the masterserver.
The register call is a HTTP POST to a URL like
`https://master1.ddnet.tw/ddnet/15/register` and looks like this:
```json
POST /ddnet/15/register HTTP/1.1
Address: tw-0.6+udp://connecting-address.invalid:8303
Secret: 81fa3955-6f83-4290-818d-31c0906b1118
Challenge-Secret: 81fa3955-6f83-4290-818d-31c0906b1118:tw0.6/ipv6
Info-Serial: 0
{
"max_clients": 64,
"max_players": 64,
"passworded": false,
"game_type": "TestDDraceNetwork",
"name": "My DDNet server",
"map": {
"name": "dm1",
"sha256": "0b0c481d77519c32fbe85624ef16ec0fa9991aec7367ad538bd280f28d8c26cf",
"size": 5805
},
"version": "0.6.4, 16.0.3",
"clients": []
}
```
The `Address` header declares that the server wants to register itself as
a `tw-0.6+udp` server, i.e. a server speaking a Teeworlds-0.6-compatible
protocol.
The free-form `Secret` header is used as a server identity, the server
list will be deduplicated via this secret.
The free-form `Challenge-Secret` is sent back via UDP for a port forward
check. This might have security implications as the masterserver can be
asked to send a UDP packet containing some user-controlled bytes. This
is somewhat mitigated by the fact that it can only go to an
attacker-controlled IP address.
The `Info-Serial` header is an integer field that should increase each
time the server info (in the body) changes. The masterserver uses that
field to ensure that it doesn't use old server infos.
The body is a free-form JSON object set by the game server. It should
contain certain keys in the correct form to be accepted by clients. The
body is optional if the masterserver already confirmed the reception of
the info with the given `Info-Serial`.
Not shown in this payload is the `Connless-Token` header that is used
for Teeworlds 0.7 style communication.
Also not shown is the `Challenge-Token` that should be included once the
server receives the challenge token via UDP.
The masterserver responds with a `200 OK` with a body like this:
```
{"status":"success"}
```
The `status` field can be `success` if the server was successfully
registered on the masterserver, `need_challenge` if the masterserver
wants the correct `Challenge-Token` header before the register process
is successful, `need_info` if the server sent an empty body but the
masterserver doesn't actually know the server info.
It can also be `error` if the request was malformed, only in this case
an HTTP status code except `200 OK` is sent.
Synchronization
---------------
The masterserver keeps state and outputs JSON files every second.
```json
{
"servers": [
{
"addresses": [
"tw-0.6+udp://127.0.0.1:8303",
"tw-0.6+udp://[::1]:8303"
],
"info_serial": 0,
"info": {
"max_clients": 64,
"max_players": 64,
"passworded": false,
"game_type": "TestDDraceNetwork",
"name": "My DDNet server",
"map": {
"name": "dm1",
"sha256": "0b0c481d77519c32fbe85624ef16ec0fa9991aec7367ad538bd280f28d8c26cf",
"size": 5805
},
"version": "0.6.4, 16.0.3",
"clients": []
}
}
]
}
```
`servers.json` (or configured by `--out`) is a server list that is
compatible with DDNet 15.5+ clients. It is a JSON object containing a
single key `servers` with a list of game servers. Each game server is
represented by a JSON object with an `addresses` key containing a list
of all known addresses of the server and an `info` key containing the
free-form server info sent by the game server. The free-form `info` JSON
object re-encoded by the master server and thus canonicalized and
stripped of any whitespace characters outside strings.
```json
{
"kind": "mastersrv",
"now": 1816002,
"secrets": {
"tw-0.6+udp://127.0.0.1:8303": {
"ping_time": 1811999,
"secret": "42d8f991-f2fa-46e5-a9ae-ebcc93846feb"
},
"tw-0.6+udp://[::1]:8303": {
"ping_time": 1811999,
"secret": "42d8f991-f2fa-46e5-a9ae-ebcc93846feb"
}
},
"servers": {
"42d8f991-f2fa-46e5-a9ae-ebcc93846feb": {
"info_serial": 0,
"info": {
"max_clients": 64,
"max_players": 64,
"passworded": false,
"game_type": "TestDDraceNetwork",
"name": "My DDNet server",
"map": {
"name": "dm1",
"sha256": "0b0c481d77519c32fbe85624ef16ec0fa9991aec7367ad538bd280f28d8c26cf",
"size": 5805
},
"version": "0.6.4, 16.0.3",
"clients": []
}
}
}
}
```
`--write-dump` outputs a JSON file compatible with `--read-dump-dir`,
this can be used to synchronize servers across different masterservers.
`--read-dump-dir` is also used to ingest servers from the backward
compatibility layer that pings each server for their server info using
the old protocol.
The `kind` field describe that this is `mastersrv` output and not from a
`backcompat`. This is used for prioritizing `mastersrv` information over
`backcompat` information.
The `now` field contains an integer describing the current time in
milliseconds relative an unspecified epoch that is fixed for each JSON
file. This is done instead of using the current time as the epoch for
better compression of non-changing data.
`secrets` is a map from each server address and to a JSON object
containing the last ping time (`ping_time`) in milliseconds relative to
the same epoch as before, and the server secret (`secret`) that is used
to unify server infos from different addresses of the same logical
server.
`servers` is a map from the aforementioned `secret`s to the
corresponding `info_serial` and `info`.
```json
[
"tw-0.6+udp://127.0.0.1:8303",
"tw-0.6+udp://[::1]:8303"
]
```
`--write-addresses` outputs a JSON file containing all addresses
corresponding to servers that are registered to HTTP masterservers. It
does not contain the servers that are obtained via backward
compatibility measures.
This file can be used by an old-style masterserver to also list
new-style servers without the game servers having to register there.
An implementation of this can be found at
https://github.com/heinrich5991/teeworlds/tree/mastersrv_6_backcompat
for Teeworlds 0.5/0.6 masterservers and at
https://github.com/heinrich5991/teeworlds/tree/mastersrv_7_backcompat
for Teeworlds 0.7 masterservers.
All these JSON files can be sent over the network in an efficient way
using https://github.com/heinrich5991/twmaster-collect. It establishes a
zstd-compressed TCP connection authenticated by a string token that is
sent in plain-text. It watches the specified file and transmits it every
time it changes. Due to the zstd-compression, the data sent over the
network is similar to the size of a diff.
Implementation
--------------
The masterserver implementation was done in Rust.
The current gameserver register implementation doesn't support more than
one masterserver for registering.
2022-05-19 20:03:17 +00:00
CServer : : CServer ( )
2010-05-29 07:25:38 +00:00
{
2021-12-15 00:32:13 +00:00
m_pConfig = & g_Config ;
2014-12-02 14:44:54 +00:00
for ( int i = 0 ; i < MAX_CLIENTS ; i + + )
m_aDemoRecorder [ i ] = CDemoRecorder ( & m_SnapshotDelta , true ) ;
2023-12-25 14:02:10 +00:00
m_aDemoRecorder [ RECORDER_MANUAL ] = CDemoRecorder ( & m_SnapshotDelta , false ) ;
m_aDemoRecorder [ RECORDER_AUTO ] = CDemoRecorder ( & m_SnapshotDelta , false ) ;
2014-09-26 00:05:22 +00:00
2010-05-29 07:25:38 +00:00
m_pGameServer = 0 ;
2011-04-13 18:37:12 +00:00
2023-01-03 12:07:03 +00:00
m_CurrentGameTick = MIN_TICK ;
2020-07-07 08:23:04 +00:00
m_RunServer = UNINITIALIZED ;
2010-05-29 07:25:38 +00:00
2021-12-08 17:41:16 +00:00
m_aShutdownReason [ 0 ] = 0 ;
2022-02-17 18:50:02 +00:00
for ( int i = 0 ; i < NUM_MAP_TYPES ; i + + )
2020-06-19 21:52:13 +00:00
{
m_apCurrentMapData [ i ] = 0 ;
m_aCurrentMapSize [ i ] = 0 ;
}
2011-04-13 18:37:12 +00:00
2020-04-23 17:02:10 +00:00
m_MapReload = false ;
2015-10-22 15:27:30 +00:00
m_ReloadedWhenEmpty = false ;
2021-12-21 11:23:17 +00:00
m_aCurrentMap [ 0 ] = ' \0 ' ;
2010-05-29 07:25:38 +00:00
2011-12-31 11:11:48 +00:00
m_RconClientID = IServer : : RCON_CID_SERV ;
2011-07-05 19:54:10 +00:00
m_RconAuthLevel = AUTHED_ADMIN ;
2010-09-12 11:52:25 +00:00
2016-01-22 15:02:40 +00:00
m_ServerInfoFirstRequest = 0 ;
m_ServerInfoNumRequests = 0 ;
2019-11-03 00:53:50 +00:00
m_ServerInfoNeedsUpdate = false ;
2016-01-22 15:02:40 +00:00
2017-12-20 15:56:34 +00:00
# ifdef CONF_FAMILY_UNIX
m_ConnLoggingSocketCreated = false ;
# endif
2020-07-04 17:53:27 +00:00
m_pConnectionPool = new CDbConnectionPool ( ) ;
Add HTTP masterserver registering and HTTP masterserver
Registering
-----------
The idea is that game servers push their server info to the
masterservers every 15 seconds or when the server info changes, but not
more than once per second.
The game servers do not support the old registering protocol anymore,
the backward compatibility is handled by the masterserver.
The register call is a HTTP POST to a URL like
`https://master1.ddnet.tw/ddnet/15/register` and looks like this:
```json
POST /ddnet/15/register HTTP/1.1
Address: tw-0.6+udp://connecting-address.invalid:8303
Secret: 81fa3955-6f83-4290-818d-31c0906b1118
Challenge-Secret: 81fa3955-6f83-4290-818d-31c0906b1118:tw0.6/ipv6
Info-Serial: 0
{
"max_clients": 64,
"max_players": 64,
"passworded": false,
"game_type": "TestDDraceNetwork",
"name": "My DDNet server",
"map": {
"name": "dm1",
"sha256": "0b0c481d77519c32fbe85624ef16ec0fa9991aec7367ad538bd280f28d8c26cf",
"size": 5805
},
"version": "0.6.4, 16.0.3",
"clients": []
}
```
The `Address` header declares that the server wants to register itself as
a `tw-0.6+udp` server, i.e. a server speaking a Teeworlds-0.6-compatible
protocol.
The free-form `Secret` header is used as a server identity, the server
list will be deduplicated via this secret.
The free-form `Challenge-Secret` is sent back via UDP for a port forward
check. This might have security implications as the masterserver can be
asked to send a UDP packet containing some user-controlled bytes. This
is somewhat mitigated by the fact that it can only go to an
attacker-controlled IP address.
The `Info-Serial` header is an integer field that should increase each
time the server info (in the body) changes. The masterserver uses that
field to ensure that it doesn't use old server infos.
The body is a free-form JSON object set by the game server. It should
contain certain keys in the correct form to be accepted by clients. The
body is optional if the masterserver already confirmed the reception of
the info with the given `Info-Serial`.
Not shown in this payload is the `Connless-Token` header that is used
for Teeworlds 0.7 style communication.
Also not shown is the `Challenge-Token` that should be included once the
server receives the challenge token via UDP.
The masterserver responds with a `200 OK` with a body like this:
```
{"status":"success"}
```
The `status` field can be `success` if the server was successfully
registered on the masterserver, `need_challenge` if the masterserver
wants the correct `Challenge-Token` header before the register process
is successful, `need_info` if the server sent an empty body but the
masterserver doesn't actually know the server info.
It can also be `error` if the request was malformed, only in this case
an HTTP status code except `200 OK` is sent.
Synchronization
---------------
The masterserver keeps state and outputs JSON files every second.
```json
{
"servers": [
{
"addresses": [
"tw-0.6+udp://127.0.0.1:8303",
"tw-0.6+udp://[::1]:8303"
],
"info_serial": 0,
"info": {
"max_clients": 64,
"max_players": 64,
"passworded": false,
"game_type": "TestDDraceNetwork",
"name": "My DDNet server",
"map": {
"name": "dm1",
"sha256": "0b0c481d77519c32fbe85624ef16ec0fa9991aec7367ad538bd280f28d8c26cf",
"size": 5805
},
"version": "0.6.4, 16.0.3",
"clients": []
}
}
]
}
```
`servers.json` (or configured by `--out`) is a server list that is
compatible with DDNet 15.5+ clients. It is a JSON object containing a
single key `servers` with a list of game servers. Each game server is
represented by a JSON object with an `addresses` key containing a list
of all known addresses of the server and an `info` key containing the
free-form server info sent by the game server. The free-form `info` JSON
object re-encoded by the master server and thus canonicalized and
stripped of any whitespace characters outside strings.
```json
{
"kind": "mastersrv",
"now": 1816002,
"secrets": {
"tw-0.6+udp://127.0.0.1:8303": {
"ping_time": 1811999,
"secret": "42d8f991-f2fa-46e5-a9ae-ebcc93846feb"
},
"tw-0.6+udp://[::1]:8303": {
"ping_time": 1811999,
"secret": "42d8f991-f2fa-46e5-a9ae-ebcc93846feb"
}
},
"servers": {
"42d8f991-f2fa-46e5-a9ae-ebcc93846feb": {
"info_serial": 0,
"info": {
"max_clients": 64,
"max_players": 64,
"passworded": false,
"game_type": "TestDDraceNetwork",
"name": "My DDNet server",
"map": {
"name": "dm1",
"sha256": "0b0c481d77519c32fbe85624ef16ec0fa9991aec7367ad538bd280f28d8c26cf",
"size": 5805
},
"version": "0.6.4, 16.0.3",
"clients": []
}
}
}
}
```
`--write-dump` outputs a JSON file compatible with `--read-dump-dir`,
this can be used to synchronize servers across different masterservers.
`--read-dump-dir` is also used to ingest servers from the backward
compatibility layer that pings each server for their server info using
the old protocol.
The `kind` field describe that this is `mastersrv` output and not from a
`backcompat`. This is used for prioritizing `mastersrv` information over
`backcompat` information.
The `now` field contains an integer describing the current time in
milliseconds relative an unspecified epoch that is fixed for each JSON
file. This is done instead of using the current time as the epoch for
better compression of non-changing data.
`secrets` is a map from each server address and to a JSON object
containing the last ping time (`ping_time`) in milliseconds relative to
the same epoch as before, and the server secret (`secret`) that is used
to unify server infos from different addresses of the same logical
server.
`servers` is a map from the aforementioned `secret`s to the
corresponding `info_serial` and `info`.
```json
[
"tw-0.6+udp://127.0.0.1:8303",
"tw-0.6+udp://[::1]:8303"
]
```
`--write-addresses` outputs a JSON file containing all addresses
corresponding to servers that are registered to HTTP masterservers. It
does not contain the servers that are obtained via backward
compatibility measures.
This file can be used by an old-style masterserver to also list
new-style servers without the game servers having to register there.
An implementation of this can be found at
https://github.com/heinrich5991/teeworlds/tree/mastersrv_6_backcompat
for Teeworlds 0.5/0.6 masterservers and at
https://github.com/heinrich5991/teeworlds/tree/mastersrv_7_backcompat
for Teeworlds 0.7 masterservers.
All these JSON files can be sent over the network in an efficient way
using https://github.com/heinrich5991/twmaster-collect. It establishes a
zstd-compressed TCP connection authenticated by a string token that is
sent in plain-text. It watches the specified file and transmits it every
time it changes. Due to the zstd-compression, the data sent over the
network is similar to the size of a diff.
Implementation
--------------
The masterserver implementation was done in Rust.
The current gameserver register implementation doesn't support more than
one masterserver for registering.
2022-05-19 20:03:17 +00:00
m_pRegister = nullptr ;
2015-11-23 21:49:18 +00:00
2017-10-13 00:25:50 +00:00
m_aErrorShutdownReason [ 0 ] = 0 ;
2010-05-29 07:25:38 +00:00
Init ( ) ;
}
2020-11-11 16:21:02 +00:00
CServer : : ~ CServer ( )
{
for ( auto & pCurrentMapData : m_apCurrentMapData )
{
2022-03-01 22:19:49 +00:00
free ( pCurrentMapData ) ;
2020-11-11 16:21:02 +00:00
}
2022-06-06 22:10:19 +00:00
if ( m_RunServer ! = UNINITIALIZED )
{
for ( auto & Client : m_aClients )
{
free ( Client . m_pPersistentData ) ;
}
}
2023-08-25 12:52:37 +00:00
free ( m_pPersistentData ) ;
2022-06-06 22:10:19 +00:00
Add HTTP masterserver registering and HTTP masterserver
Registering
-----------
The idea is that game servers push their server info to the
masterservers every 15 seconds or when the server info changes, but not
more than once per second.
The game servers do not support the old registering protocol anymore,
the backward compatibility is handled by the masterserver.
The register call is a HTTP POST to a URL like
`https://master1.ddnet.tw/ddnet/15/register` and looks like this:
```json
POST /ddnet/15/register HTTP/1.1
Address: tw-0.6+udp://connecting-address.invalid:8303
Secret: 81fa3955-6f83-4290-818d-31c0906b1118
Challenge-Secret: 81fa3955-6f83-4290-818d-31c0906b1118:tw0.6/ipv6
Info-Serial: 0
{
"max_clients": 64,
"max_players": 64,
"passworded": false,
"game_type": "TestDDraceNetwork",
"name": "My DDNet server",
"map": {
"name": "dm1",
"sha256": "0b0c481d77519c32fbe85624ef16ec0fa9991aec7367ad538bd280f28d8c26cf",
"size": 5805
},
"version": "0.6.4, 16.0.3",
"clients": []
}
```
The `Address` header declares that the server wants to register itself as
a `tw-0.6+udp` server, i.e. a server speaking a Teeworlds-0.6-compatible
protocol.
The free-form `Secret` header is used as a server identity, the server
list will be deduplicated via this secret.
The free-form `Challenge-Secret` is sent back via UDP for a port forward
check. This might have security implications as the masterserver can be
asked to send a UDP packet containing some user-controlled bytes. This
is somewhat mitigated by the fact that it can only go to an
attacker-controlled IP address.
The `Info-Serial` header is an integer field that should increase each
time the server info (in the body) changes. The masterserver uses that
field to ensure that it doesn't use old server infos.
The body is a free-form JSON object set by the game server. It should
contain certain keys in the correct form to be accepted by clients. The
body is optional if the masterserver already confirmed the reception of
the info with the given `Info-Serial`.
Not shown in this payload is the `Connless-Token` header that is used
for Teeworlds 0.7 style communication.
Also not shown is the `Challenge-Token` that should be included once the
server receives the challenge token via UDP.
The masterserver responds with a `200 OK` with a body like this:
```
{"status":"success"}
```
The `status` field can be `success` if the server was successfully
registered on the masterserver, `need_challenge` if the masterserver
wants the correct `Challenge-Token` header before the register process
is successful, `need_info` if the server sent an empty body but the
masterserver doesn't actually know the server info.
It can also be `error` if the request was malformed, only in this case
an HTTP status code except `200 OK` is sent.
Synchronization
---------------
The masterserver keeps state and outputs JSON files every second.
```json
{
"servers": [
{
"addresses": [
"tw-0.6+udp://127.0.0.1:8303",
"tw-0.6+udp://[::1]:8303"
],
"info_serial": 0,
"info": {
"max_clients": 64,
"max_players": 64,
"passworded": false,
"game_type": "TestDDraceNetwork",
"name": "My DDNet server",
"map": {
"name": "dm1",
"sha256": "0b0c481d77519c32fbe85624ef16ec0fa9991aec7367ad538bd280f28d8c26cf",
"size": 5805
},
"version": "0.6.4, 16.0.3",
"clients": []
}
}
]
}
```
`servers.json` (or configured by `--out`) is a server list that is
compatible with DDNet 15.5+ clients. It is a JSON object containing a
single key `servers` with a list of game servers. Each game server is
represented by a JSON object with an `addresses` key containing a list
of all known addresses of the server and an `info` key containing the
free-form server info sent by the game server. The free-form `info` JSON
object re-encoded by the master server and thus canonicalized and
stripped of any whitespace characters outside strings.
```json
{
"kind": "mastersrv",
"now": 1816002,
"secrets": {
"tw-0.6+udp://127.0.0.1:8303": {
"ping_time": 1811999,
"secret": "42d8f991-f2fa-46e5-a9ae-ebcc93846feb"
},
"tw-0.6+udp://[::1]:8303": {
"ping_time": 1811999,
"secret": "42d8f991-f2fa-46e5-a9ae-ebcc93846feb"
}
},
"servers": {
"42d8f991-f2fa-46e5-a9ae-ebcc93846feb": {
"info_serial": 0,
"info": {
"max_clients": 64,
"max_players": 64,
"passworded": false,
"game_type": "TestDDraceNetwork",
"name": "My DDNet server",
"map": {
"name": "dm1",
"sha256": "0b0c481d77519c32fbe85624ef16ec0fa9991aec7367ad538bd280f28d8c26cf",
"size": 5805
},
"version": "0.6.4, 16.0.3",
"clients": []
}
}
}
}
```
`--write-dump` outputs a JSON file compatible with `--read-dump-dir`,
this can be used to synchronize servers across different masterservers.
`--read-dump-dir` is also used to ingest servers from the backward
compatibility layer that pings each server for their server info using
the old protocol.
The `kind` field describe that this is `mastersrv` output and not from a
`backcompat`. This is used for prioritizing `mastersrv` information over
`backcompat` information.
The `now` field contains an integer describing the current time in
milliseconds relative an unspecified epoch that is fixed for each JSON
file. This is done instead of using the current time as the epoch for
better compression of non-changing data.
`secrets` is a map from each server address and to a JSON object
containing the last ping time (`ping_time`) in milliseconds relative to
the same epoch as before, and the server secret (`secret`) that is used
to unify server infos from different addresses of the same logical
server.
`servers` is a map from the aforementioned `secret`s to the
corresponding `info_serial` and `info`.
```json
[
"tw-0.6+udp://127.0.0.1:8303",
"tw-0.6+udp://[::1]:8303"
]
```
`--write-addresses` outputs a JSON file containing all addresses
corresponding to servers that are registered to HTTP masterservers. It
does not contain the servers that are obtained via backward
compatibility measures.
This file can be used by an old-style masterserver to also list
new-style servers without the game servers having to register there.
An implementation of this can be found at
https://github.com/heinrich5991/teeworlds/tree/mastersrv_6_backcompat
for Teeworlds 0.5/0.6 masterservers and at
https://github.com/heinrich5991/teeworlds/tree/mastersrv_7_backcompat
for Teeworlds 0.7 masterservers.
All these JSON files can be sent over the network in an efficient way
using https://github.com/heinrich5991/twmaster-collect. It establishes a
zstd-compressed TCP connection authenticated by a string token that is
sent in plain-text. It watches the specified file and transmits it every
time it changes. Due to the zstd-compression, the data sent over the
network is similar to the size of a diff.
Implementation
--------------
The masterserver implementation was done in Rust.
The current gameserver register implementation doesn't support more than
one masterserver for registering.
2022-05-19 20:03:17 +00:00
delete m_pRegister ;
2020-11-11 16:21:02 +00:00
delete m_pConnectionPool ;
}
2020-10-14 14:42:35 +00:00
bool CServer : : IsClientNameAvailable ( int ClientID , const char * pNameRequest )
2010-05-29 07:25:38 +00:00
{
2012-07-08 09:40:23 +00:00
// check for empty names
2020-10-14 14:42:35 +00:00
if ( ! pNameRequest [ 0 ] )
return false ;
2011-03-15 10:23:49 +00:00
2016-07-08 14:38:05 +00:00
// check for names starting with /, as they can be abused to make people
// write chat commands
2020-10-14 14:42:35 +00:00
if ( pNameRequest [ 0 ] = = ' / ' )
return false ;
2016-07-08 14:38:05 +00:00
2015-12-16 19:15:43 +00:00
// make sure that two clients don't have the same name
2010-05-29 07:25:38 +00:00
for ( int i = 0 ; i < MAX_CLIENTS ; i + + )
2015-12-18 12:17:45 +00:00
{
2010-05-29 07:25:38 +00:00
if ( i ! = ClientID & & m_aClients [ i ] . m_State > = CClient : : STATE_READY )
{
2020-10-14 14:42:35 +00:00
if ( str_utf8_comp_confusable ( pNameRequest , m_aClients [ i ] . m_aName ) = = 0 )
return false ;
2010-05-29 07:25:38 +00:00
}
2015-12-18 12:17:45 +00:00
}
2010-05-29 07:25:38 +00:00
2020-10-14 14:42:35 +00:00
return true ;
2010-05-29 07:25:38 +00:00
}
2020-10-14 14:42:35 +00:00
bool CServer : : SetClientNameImpl ( int ClientID , const char * pNameRequest , bool Set )
2010-05-29 07:25:38 +00:00
{
2020-10-14 14:42:35 +00:00
dbg_assert ( 0 < = ClientID & & ClientID < MAX_CLIENTS , " invalid client id " ) ;
if ( m_aClients [ ClientID ] . m_State < CClient : : STATE_READY )
return false ;
2011-04-13 18:37:12 +00:00
2023-11-25 16:09:14 +00:00
const CNameBan * pBanned = m_NameBans . IsBanned ( pNameRequest ) ;
2020-12-19 10:20:52 +00:00
if ( pBanned )
2018-03-06 17:41:18 +00:00
{
2020-12-19 10:20:52 +00:00
if ( m_aClients [ ClientID ] . m_State = = CClient : : STATE_READY & & Set )
2018-03-06 17:41:18 +00:00
{
2020-12-19 10:20:52 +00:00
char aBuf [ 256 ] ;
if ( pBanned - > m_aReason [ 0 ] )
{
str_format ( aBuf , sizeof ( aBuf ) , " Kicked (your name is banned: %s) " , pBanned - > m_aReason ) ;
}
else
2018-03-12 22:29:46 +00:00
{
2022-07-09 16:14:56 +00:00
str_copy ( aBuf , " Kicked (your name is banned) " ) ;
2018-03-12 22:29:46 +00:00
}
2020-12-19 10:20:52 +00:00
Kick ( ClientID , aBuf ) ;
2018-03-06 17:41:18 +00:00
}
2020-12-19 10:20:52 +00:00
return false ;
2018-03-06 17:41:18 +00:00
}
2020-10-14 14:42:35 +00:00
// trim the name
char aTrimmedName [ MAX_NAME_LENGTH ] ;
2022-07-09 16:14:56 +00:00
str_copy ( aTrimmedName , str_utf8_skip_whitespaces ( pNameRequest ) ) ;
2020-10-14 14:42:35 +00:00
str_utf8_trim_right ( aTrimmedName ) ;
2010-05-29 07:25:38 +00:00
char aNameTry [ MAX_NAME_LENGTH ] ;
2022-07-09 16:14:56 +00:00
str_copy ( aNameTry , aTrimmedName ) ;
2020-10-14 14:42:35 +00:00
if ( ! IsClientNameAvailable ( ClientID , aNameTry ) )
2010-05-29 07:25:38 +00:00
{
// auto rename
for ( int i = 1 ; ; i + + )
{
2021-12-20 01:34:02 +00:00
str_format ( aNameTry , sizeof ( aNameTry ) , " (%d)%s " , i , aTrimmedName ) ;
2020-10-14 14:42:35 +00:00
if ( IsClientNameAvailable ( ClientID , aNameTry ) )
2010-05-29 07:25:38 +00:00
break ;
}
}
2020-10-14 14:42:35 +00:00
bool Changed = str_comp ( m_aClients [ ClientID ] . m_aName , aNameTry ) ! = 0 ;
if ( Set )
{
// set the client name
2022-07-09 16:14:56 +00:00
str_copy ( m_aClients [ ClientID ] . m_aName , aNameTry ) ;
2020-10-14 14:42:35 +00:00
}
return Changed ;
}
2023-11-25 17:10:24 +00:00
bool CServer : : SetClientClanImpl ( int ClientID , const char * pClanRequest , bool Set )
{
dbg_assert ( 0 < = ClientID & & ClientID < MAX_CLIENTS , " invalid client id " ) ;
if ( m_aClients [ ClientID ] . m_State < CClient : : STATE_READY )
return false ;
const CNameBan * pBanned = m_NameBans . IsBanned ( pClanRequest ) ;
if ( pBanned )
{
if ( m_aClients [ ClientID ] . m_State = = CClient : : STATE_READY & & Set )
{
char aBuf [ 256 ] ;
if ( pBanned - > m_aReason [ 0 ] )
{
str_format ( aBuf , sizeof ( aBuf ) , " Kicked (your clan is banned: %s) " , pBanned - > m_aReason ) ;
}
else
{
str_copy ( aBuf , " Kicked (your clan is banned) " ) ;
}
Kick ( ClientID , aBuf ) ;
}
return false ;
}
// trim the clan
char aTrimmedClan [ MAX_CLAN_LENGTH ] ;
str_copy ( aTrimmedClan , str_utf8_skip_whitespaces ( pClanRequest ) ) ;
str_utf8_trim_right ( aTrimmedClan ) ;
bool Changed = str_comp ( m_aClients [ ClientID ] . m_aClan , aTrimmedClan ) ! = 0 ;
if ( Set )
{
// set the client clan
str_copy ( m_aClients [ ClientID ] . m_aClan , aTrimmedClan ) ;
}
return Changed ;
}
2020-10-14 14:42:35 +00:00
bool CServer : : WouldClientNameChange ( int ClientID , const char * pNameRequest )
{
return SetClientNameImpl ( ClientID , pNameRequest , false ) ;
}
2023-11-25 17:10:24 +00:00
bool CServer : : WouldClientClanChange ( int ClientID , const char * pClanRequest )
{
return SetClientClanImpl ( ClientID , pClanRequest , false ) ;
}
2020-10-14 14:42:35 +00:00
void CServer : : SetClientName ( int ClientID , const char * pName )
{
SetClientNameImpl ( ClientID , pName , true ) ;
2010-05-29 07:25:38 +00:00
}
2011-03-15 10:23:49 +00:00
void CServer : : SetClientClan ( int ClientID , const char * pClan )
{
2023-11-25 17:10:24 +00:00
SetClientClanImpl ( ClientID , pClan , true ) ;
2011-03-15 10:23:49 +00:00
}
void CServer : : SetClientCountry ( int ClientID , int Country )
2010-05-29 07:25:38 +00:00
{
if ( ClientID < 0 | | ClientID > = MAX_CLIENTS | | m_aClients [ ClientID ] . m_State < CClient : : STATE_READY )
return ;
2011-04-13 18:37:12 +00:00
2011-03-15 10:23:49 +00:00
m_aClients [ ClientID ] . m_Country = Country ;
2010-05-29 07:25:38 +00:00
}
2023-05-22 14:32:48 +00:00
void CServer : : SetClientScore ( int ClientID , std : : optional < int > Score )
2010-05-29 07:25:38 +00:00
{
if ( ClientID < 0 | | ClientID > = MAX_CLIENTS | | m_aClients [ ClientID ] . m_State < CClient : : STATE_READY )
return ;
2019-11-04 14:33:17 +00:00
2023-05-22 14:32:48 +00:00
if ( m_aClients [ ClientID ] . m_Score ! = Score )
2019-11-04 14:33:17 +00:00
ExpireServerInfo ( ) ;
2010-05-29 07:25:38 +00:00
m_aClients [ ClientID ] . m_Score = Score ;
}
2019-04-03 13:07:05 +00:00
void CServer : : SetClientFlags ( int ClientID , int Flags )
{
if ( ClientID < 0 | | ClientID > = MAX_CLIENTS | | m_aClients [ ClientID ] . m_State < CClient : : STATE_READY )
return ;
2022-06-30 21:46:59 +00:00
m_aClients [ ClientID ] . m_Flags = Flags ;
2019-04-03 13:07:05 +00:00
}
2010-05-29 07:25:38 +00:00
void CServer : : Kick ( int ClientID , const char * pReason )
{
2010-09-12 10:07:10 +00:00
if ( ClientID < 0 | | ClientID > = MAX_CLIENTS | | m_aClients [ ClientID ] . m_State = = CClient : : STATE_EMPTY )
2010-09-12 11:52:25 +00:00
{
Console ( ) - > Print ( IConsole : : OUTPUT_LEVEL_STANDARD , " server " , " invalid client id to kick " ) ;
2010-05-29 07:25:38 +00:00
return ;
2010-09-12 11:52:25 +00:00
}
2011-02-12 10:40:36 +00:00
else if ( m_RconClientID = = ClientID )
2010-09-12 11:52:25 +00:00
{
Console ( ) - > Print ( IConsole : : OUTPUT_LEVEL_STANDARD , " server " , " you can't kick yourself " ) ;
2015-07-09 00:08:14 +00:00
return ;
2010-09-12 10:07:10 +00:00
}
2011-07-05 19:54:10 +00:00
else if ( m_aClients [ ClientID ] . m_Authed > m_RconAuthLevel )
{
Console ( ) - > Print ( IConsole : : OUTPUT_LEVEL_STANDARD , " server " , " kick command denied " ) ;
2015-07-09 00:08:14 +00:00
return ;
2011-07-05 19:54:10 +00:00
}
2011-04-13 18:37:12 +00:00
2010-09-12 10:07:10 +00:00
m_NetServer . Drop ( ClientID , pReason ) ;
2010-05-29 07:25:38 +00:00
}
2019-02-06 12:06:28 +00:00
void CServer : : Ban ( int ClientID , int Seconds , const char * pReason )
2019-02-04 22:09:14 +00:00
{
NETADDR Addr ;
GetClientAddr ( ClientID , & Addr ) ;
2019-02-06 12:06:28 +00:00
m_NetServer . NetBan ( ) - > BanAddr ( & Addr , Seconds , pReason ) ;
2019-02-04 22:09:14 +00:00
}
2023-06-23 08:18:47 +00:00
void CServer : : RedirectClient ( int ClientID , int Port , bool Verbose )
{
if ( ClientID < 0 | | ClientID > = MAX_CLIENTS )
return ;
char aBuf [ 512 ] ;
bool SupportsRedirect = GetClientVersion ( ClientID ) > = VERSION_DDNET_REDIRECT ;
if ( Verbose )
{
str_format ( aBuf , sizeof ( aBuf ) , " redirecting '%s' to port %d supported=%d " , ClientName ( ClientID ) , Port , SupportsRedirect ) ;
Console ( ) - > Print ( IConsole : : OUTPUT_LEVEL_STANDARD , " redirect " , aBuf ) ;
}
if ( ! SupportsRedirect )
{
bool SamePort = Port = = m_NetServer . Address ( ) . port ;
str_format ( aBuf , sizeof ( aBuf ) , " Redirect unsupported: please connect to port %d " , Port ) ;
Kick ( ClientID , SamePort ? " Redirect unsupported: please reconnect " : aBuf ) ;
return ;
}
CMsgPacker Msg ( NETMSG_REDIRECT , true ) ;
Msg . AddInt ( Port ) ;
SendMsg ( & Msg , MSGFLAG_VITAL | MSGFLAG_FLUSH , ClientID ) ;
m_aClients [ ClientID ] . m_RedirectDropTime = time_get ( ) + time_freq ( ) * 10 ;
m_aClients [ ClientID ] . m_State = CClient : : STATE_REDIRECTED ;
}
2021-06-23 05:05:49 +00:00
int64_t CServer : : TickStartTime ( int Tick )
2010-05-29 07:25:38 +00:00
{
2023-11-23 13:42:30 +00:00
return m_GameStartTime + ( time_freq ( ) * Tick ) / TickSpeed ( ) ;
2010-05-29 07:25:38 +00:00
}
int CServer : : Init ( )
{
2020-10-26 14:14:07 +00:00
for ( auto & Client : m_aClients )
2010-05-29 07:25:38 +00:00
{
2020-10-26 14:14:07 +00:00
Client . m_State = CClient : : STATE_EMPTY ;
Client . m_aName [ 0 ] = 0 ;
Client . m_aClan [ 0 ] = 0 ;
Client . m_Country = - 1 ;
Client . m_Snapshots . Init ( ) ;
Client . m_Traffic = 0 ;
Client . m_TrafficSince = 0 ;
Client . m_ShowIps = false ;
2023-11-22 16:57:42 +00:00
Client . m_DebugDummy = false ;
2020-10-26 14:14:07 +00:00
Client . m_AuthKey = - 1 ;
Client . m_Latency = 0 ;
Client . m_Sixup = false ;
2023-06-23 08:18:47 +00:00
Client . m_RedirectDropTime = 0 ;
2010-05-29 07:25:38 +00:00
}
2023-01-03 12:07:03 +00:00
m_CurrentGameTick = MIN_TICK ;
2010-05-29 07:25:38 +00:00
2010-12-06 02:27:35 +00:00
m_AnnouncementLastLine = 0 ;
2023-01-13 14:49:02 +00:00
m_aAnnouncementFile [ 0 ] = ' \0 ' ;
2022-09-02 12:32:39 +00:00
mem_zero ( m_aPrevStates , sizeof ( m_aPrevStates ) ) ;
2010-05-29 07:25:38 +00:00
return 0 ;
}
2022-04-22 23:04:48 +00:00
void CServer : : SendLogLine ( const CLogMessage * pMessage )
{
2022-06-13 19:43:05 +00:00
if ( pMessage - > m_Level < = IConsole : : ToLogLevelFilter ( g_Config . m_ConsoleOutputLevel ) )
2022-04-22 23:04:48 +00:00
{
SendRconLogLine ( - 1 , pMessage ) ;
}
2022-06-13 19:43:05 +00:00
if ( pMessage - > m_Level < = IConsole : : ToLogLevelFilter ( g_Config . m_EcOutputLevel ) )
2022-04-22 23:04:48 +00:00
{
m_Econ . Send ( - 1 , pMessage - > m_aLine ) ;
}
}
2011-12-31 11:11:48 +00:00
void CServer : : SetRconCID ( int ClientID )
{
m_RconClientID = ClientID ;
}
2021-02-08 21:26:26 +00:00
int CServer : : GetAuthedState ( int ClientID ) const
2010-06-02 02:42:17 +00:00
{
2010-10-10 13:36:58 +00:00
return m_aClients [ ClientID ] . m_Authed ;
2010-06-02 02:42:17 +00:00
}
2021-02-08 21:26:26 +00:00
const char * CServer : : GetAuthName ( int ClientID ) const
2018-01-28 02:13:05 +00:00
{
int Key = m_aClients [ ClientID ] . m_AuthKey ;
if ( Key = = - 1 )
{
return 0 ;
}
return m_AuthManager . KeyIdent ( Key ) ;
}
Handle `CServer::GetClientInfo` return to fix use of undefined value
```
/home/runner/work/ddnet/ddnet/src/engine/server/server.cpp:790:2: error: Undefined or garbage value returned to caller [clang-analyzer-core.uninitialized.UndefReturn,-warnings-as-errors]
return Info.m_DDNetVersion;
^
/home/runner/work/ddnet/ddnet/src/engine/server/server.cpp:785:5: note: Assuming 'ClientID' is not equal to SERVER_DEMO_CLIENT
if(ClientID == SERVER_DEMO_CLIENT)
^
/home/runner/work/ddnet/ddnet/src/engine/server/server.cpp:785:2: note: Taking false branch
if(ClientID == SERVER_DEMO_CLIENT)
^
/home/runner/work/ddnet/ddnet/src/engine/server/server.cpp:789:2: note: Calling 'CServer::GetClientInfo'
GetClientInfo(ClientID, &Info);
^
/home/runner/work/ddnet/ddnet/src/engine/server/server.cpp:646:13: note: Assuming 'ClientID' is >= 0
dbg_assert(ClientID >= 0 && ClientID < MAX_CLIENTS, "client_id is not valid");
^
/home/runner/work/ddnet/ddnet/src/base/tl/../system.h:58:38: note: expanded from macro 'dbg_assert'
#define dbg_assert(test, msg) assert(test)
^
/usr/include/assert.h:93:27: note: expanded from macro 'assert'
(static_cast <bool> (expr) \
^
/home/runner/work/ddnet/ddnet/src/engine/server/server.cpp:646:13: note: Left side of '&&' is true
dbg_assert(ClientID >= 0 && ClientID < MAX_CLIENTS, "client_id is not valid");
^
/home/runner/work/ddnet/ddnet/src/engine/server/server.cpp:646:30: note: Assuming 'ClientID' is < MAX_CLIENTS
dbg_assert(ClientID >= 0 && ClientID < MAX_CLIENTS, "client_id is not valid");
^
/home/runner/work/ddnet/ddnet/src/base/tl/../system.h:58:38: note: expanded from macro 'dbg_assert'
#define dbg_assert(test, msg) assert(test)
^
/usr/include/assert.h:93:27: note: expanded from macro 'assert'
(static_cast <bool> (expr) \
^
/home/runner/work/ddnet/ddnet/src/engine/server/server.cpp:646:2: note: '?' condition is true
dbg_assert(ClientID >= 0 && ClientID < MAX_CLIENTS, "client_id is not valid");
^
/home/runner/work/ddnet/ddnet/src/base/tl/../system.h:58:31: note: expanded from macro 'dbg_assert'
#define dbg_assert(test, msg) assert(test)
^
/usr/include/assert.h:93:7: note: expanded from macro 'assert'
(static_cast <bool> (expr) \
^
/home/runner/work/ddnet/ddnet/src/engine/server/server.cpp:647:13: note: 'pInfo' is not equal to null
dbg_assert(pInfo != 0, "info can not be null");
^
/home/runner/work/ddnet/ddnet/src/base/tl/../system.h:58:38: note: expanded from macro 'dbg_assert'
#define dbg_assert(test, msg) assert(test)
^
/usr/include/assert.h:93:27: note: expanded from macro 'assert'
(static_cast <bool> (expr) \
^
/home/runner/work/ddnet/ddnet/src/engine/server/server.cpp:647:2: note: '?' condition is true
dbg_assert(pInfo != 0, "info can not be null");
^
/home/runner/work/ddnet/ddnet/src/base/tl/../system.h:58:31: note: expanded from macro 'dbg_assert'
#define dbg_assert(test, msg) assert(test)
^
/usr/include/assert.h:93:7: note: expanded from macro 'assert'
(static_cast <bool> (expr) \
^
/home/runner/work/ddnet/ddnet/src/engine/server/server.cpp:649:5: note: Assuming field 'm_State' is not equal to STATE_INGAME
if(m_aClients[ClientID].m_State == CClient::STATE_INGAME)
^
/home/runner/work/ddnet/ddnet/src/engine/server/server.cpp:649:2: note: Taking false branch
if(m_aClients[ClientID].m_State == CClient::STATE_INGAME)
^
/home/runner/work/ddnet/ddnet/src/engine/server/server.cpp:667:2: note: Returning without writing to 'pInfo->m_DDNetVersion'
return 0;
^
/home/runner/work/ddnet/ddnet/src/engine/server/server.cpp:789:2: note: Returning from 'CServer::GetClientInfo'
GetClientInfo(ClientID, &Info);
^
/home/runner/work/ddnet/ddnet/src/engine/server/server.cpp:790:2: note: Undefined or garbage value returned to caller
return Info.m_DDNetVersion;
^
```
2022-07-31 10:53:26 +00:00
bool CServer : : GetClientInfo ( int ClientID , CClientInfo * pInfo ) const
2010-05-29 07:25:38 +00:00
{
2022-07-31 21:37:56 +00:00
dbg_assert ( ClientID > = 0 & & ClientID < MAX_CLIENTS , " ClientID is not valid " ) ;
dbg_assert ( pInfo ! = nullptr , " pInfo cannot be null " ) ;
2010-05-29 07:25:38 +00:00
if ( m_aClients [ ClientID ] . m_State = = CClient : : STATE_INGAME )
{
pInfo - > m_pName = m_aClients [ ClientID ] . m_aName ;
pInfo - > m_Latency = m_aClients [ ClientID ] . m_Latency ;
2020-05-22 15:58:41 +00:00
pInfo - > m_GotDDNetVersion = m_aClients [ ClientID ] . m_DDNetVersionSettled ;
pInfo - > m_DDNetVersion = m_aClients [ ClientID ] . m_DDNetVersion > = 0 ? m_aClients [ ClientID ] . m_DDNetVersion : VERSION_VANILLA ;
if ( m_aClients [ ClientID ] . m_GotDDNetVersionPacket )
{
pInfo - > m_pConnectionID = & m_aClients [ ClientID ] . m_ConnectionID ;
pInfo - > m_pDDNetVersionStr = m_aClients [ ClientID ] . m_aDDNetVersionStr ;
}
else
{
2022-07-31 21:37:56 +00:00
pInfo - > m_pConnectionID = nullptr ;
pInfo - > m_pDDNetVersionStr = nullptr ;
2020-05-22 15:58:41 +00:00
}
Handle `CServer::GetClientInfo` return to fix use of undefined value
```
/home/runner/work/ddnet/ddnet/src/engine/server/server.cpp:790:2: error: Undefined or garbage value returned to caller [clang-analyzer-core.uninitialized.UndefReturn,-warnings-as-errors]
return Info.m_DDNetVersion;
^
/home/runner/work/ddnet/ddnet/src/engine/server/server.cpp:785:5: note: Assuming 'ClientID' is not equal to SERVER_DEMO_CLIENT
if(ClientID == SERVER_DEMO_CLIENT)
^
/home/runner/work/ddnet/ddnet/src/engine/server/server.cpp:785:2: note: Taking false branch
if(ClientID == SERVER_DEMO_CLIENT)
^
/home/runner/work/ddnet/ddnet/src/engine/server/server.cpp:789:2: note: Calling 'CServer::GetClientInfo'
GetClientInfo(ClientID, &Info);
^
/home/runner/work/ddnet/ddnet/src/engine/server/server.cpp:646:13: note: Assuming 'ClientID' is >= 0
dbg_assert(ClientID >= 0 && ClientID < MAX_CLIENTS, "client_id is not valid");
^
/home/runner/work/ddnet/ddnet/src/base/tl/../system.h:58:38: note: expanded from macro 'dbg_assert'
#define dbg_assert(test, msg) assert(test)
^
/usr/include/assert.h:93:27: note: expanded from macro 'assert'
(static_cast <bool> (expr) \
^
/home/runner/work/ddnet/ddnet/src/engine/server/server.cpp:646:13: note: Left side of '&&' is true
dbg_assert(ClientID >= 0 && ClientID < MAX_CLIENTS, "client_id is not valid");
^
/home/runner/work/ddnet/ddnet/src/engine/server/server.cpp:646:30: note: Assuming 'ClientID' is < MAX_CLIENTS
dbg_assert(ClientID >= 0 && ClientID < MAX_CLIENTS, "client_id is not valid");
^
/home/runner/work/ddnet/ddnet/src/base/tl/../system.h:58:38: note: expanded from macro 'dbg_assert'
#define dbg_assert(test, msg) assert(test)
^
/usr/include/assert.h:93:27: note: expanded from macro 'assert'
(static_cast <bool> (expr) \
^
/home/runner/work/ddnet/ddnet/src/engine/server/server.cpp:646:2: note: '?' condition is true
dbg_assert(ClientID >= 0 && ClientID < MAX_CLIENTS, "client_id is not valid");
^
/home/runner/work/ddnet/ddnet/src/base/tl/../system.h:58:31: note: expanded from macro 'dbg_assert'
#define dbg_assert(test, msg) assert(test)
^
/usr/include/assert.h:93:7: note: expanded from macro 'assert'
(static_cast <bool> (expr) \
^
/home/runner/work/ddnet/ddnet/src/engine/server/server.cpp:647:13: note: 'pInfo' is not equal to null
dbg_assert(pInfo != 0, "info can not be null");
^
/home/runner/work/ddnet/ddnet/src/base/tl/../system.h:58:38: note: expanded from macro 'dbg_assert'
#define dbg_assert(test, msg) assert(test)
^
/usr/include/assert.h:93:27: note: expanded from macro 'assert'
(static_cast <bool> (expr) \
^
/home/runner/work/ddnet/ddnet/src/engine/server/server.cpp:647:2: note: '?' condition is true
dbg_assert(pInfo != 0, "info can not be null");
^
/home/runner/work/ddnet/ddnet/src/base/tl/../system.h:58:31: note: expanded from macro 'dbg_assert'
#define dbg_assert(test, msg) assert(test)
^
/usr/include/assert.h:93:7: note: expanded from macro 'assert'
(static_cast <bool> (expr) \
^
/home/runner/work/ddnet/ddnet/src/engine/server/server.cpp:649:5: note: Assuming field 'm_State' is not equal to STATE_INGAME
if(m_aClients[ClientID].m_State == CClient::STATE_INGAME)
^
/home/runner/work/ddnet/ddnet/src/engine/server/server.cpp:649:2: note: Taking false branch
if(m_aClients[ClientID].m_State == CClient::STATE_INGAME)
^
/home/runner/work/ddnet/ddnet/src/engine/server/server.cpp:667:2: note: Returning without writing to 'pInfo->m_DDNetVersion'
return 0;
^
/home/runner/work/ddnet/ddnet/src/engine/server/server.cpp:789:2: note: Returning from 'CServer::GetClientInfo'
GetClientInfo(ClientID, &Info);
^
/home/runner/work/ddnet/ddnet/src/engine/server/server.cpp:790:2: note: Undefined or garbage value returned to caller
return Info.m_DDNetVersion;
^
```
2022-07-31 10:53:26 +00:00
return true ;
2010-05-29 07:25:38 +00:00
}
Handle `CServer::GetClientInfo` return to fix use of undefined value
```
/home/runner/work/ddnet/ddnet/src/engine/server/server.cpp:790:2: error: Undefined or garbage value returned to caller [clang-analyzer-core.uninitialized.UndefReturn,-warnings-as-errors]
return Info.m_DDNetVersion;
^
/home/runner/work/ddnet/ddnet/src/engine/server/server.cpp:785:5: note: Assuming 'ClientID' is not equal to SERVER_DEMO_CLIENT
if(ClientID == SERVER_DEMO_CLIENT)
^
/home/runner/work/ddnet/ddnet/src/engine/server/server.cpp:785:2: note: Taking false branch
if(ClientID == SERVER_DEMO_CLIENT)
^
/home/runner/work/ddnet/ddnet/src/engine/server/server.cpp:789:2: note: Calling 'CServer::GetClientInfo'
GetClientInfo(ClientID, &Info);
^
/home/runner/work/ddnet/ddnet/src/engine/server/server.cpp:646:13: note: Assuming 'ClientID' is >= 0
dbg_assert(ClientID >= 0 && ClientID < MAX_CLIENTS, "client_id is not valid");
^
/home/runner/work/ddnet/ddnet/src/base/tl/../system.h:58:38: note: expanded from macro 'dbg_assert'
#define dbg_assert(test, msg) assert(test)
^
/usr/include/assert.h:93:27: note: expanded from macro 'assert'
(static_cast <bool> (expr) \
^
/home/runner/work/ddnet/ddnet/src/engine/server/server.cpp:646:13: note: Left side of '&&' is true
dbg_assert(ClientID >= 0 && ClientID < MAX_CLIENTS, "client_id is not valid");
^
/home/runner/work/ddnet/ddnet/src/engine/server/server.cpp:646:30: note: Assuming 'ClientID' is < MAX_CLIENTS
dbg_assert(ClientID >= 0 && ClientID < MAX_CLIENTS, "client_id is not valid");
^
/home/runner/work/ddnet/ddnet/src/base/tl/../system.h:58:38: note: expanded from macro 'dbg_assert'
#define dbg_assert(test, msg) assert(test)
^
/usr/include/assert.h:93:27: note: expanded from macro 'assert'
(static_cast <bool> (expr) \
^
/home/runner/work/ddnet/ddnet/src/engine/server/server.cpp:646:2: note: '?' condition is true
dbg_assert(ClientID >= 0 && ClientID < MAX_CLIENTS, "client_id is not valid");
^
/home/runner/work/ddnet/ddnet/src/base/tl/../system.h:58:31: note: expanded from macro 'dbg_assert'
#define dbg_assert(test, msg) assert(test)
^
/usr/include/assert.h:93:7: note: expanded from macro 'assert'
(static_cast <bool> (expr) \
^
/home/runner/work/ddnet/ddnet/src/engine/server/server.cpp:647:13: note: 'pInfo' is not equal to null
dbg_assert(pInfo != 0, "info can not be null");
^
/home/runner/work/ddnet/ddnet/src/base/tl/../system.h:58:38: note: expanded from macro 'dbg_assert'
#define dbg_assert(test, msg) assert(test)
^
/usr/include/assert.h:93:27: note: expanded from macro 'assert'
(static_cast <bool> (expr) \
^
/home/runner/work/ddnet/ddnet/src/engine/server/server.cpp:647:2: note: '?' condition is true
dbg_assert(pInfo != 0, "info can not be null");
^
/home/runner/work/ddnet/ddnet/src/base/tl/../system.h:58:31: note: expanded from macro 'dbg_assert'
#define dbg_assert(test, msg) assert(test)
^
/usr/include/assert.h:93:7: note: expanded from macro 'assert'
(static_cast <bool> (expr) \
^
/home/runner/work/ddnet/ddnet/src/engine/server/server.cpp:649:5: note: Assuming field 'm_State' is not equal to STATE_INGAME
if(m_aClients[ClientID].m_State == CClient::STATE_INGAME)
^
/home/runner/work/ddnet/ddnet/src/engine/server/server.cpp:649:2: note: Taking false branch
if(m_aClients[ClientID].m_State == CClient::STATE_INGAME)
^
/home/runner/work/ddnet/ddnet/src/engine/server/server.cpp:667:2: note: Returning without writing to 'pInfo->m_DDNetVersion'
return 0;
^
/home/runner/work/ddnet/ddnet/src/engine/server/server.cpp:789:2: note: Returning from 'CServer::GetClientInfo'
GetClientInfo(ClientID, &Info);
^
/home/runner/work/ddnet/ddnet/src/engine/server/server.cpp:790:2: note: Undefined or garbage value returned to caller
return Info.m_DDNetVersion;
^
```
2022-07-31 10:53:26 +00:00
return false ;
2010-05-29 07:25:38 +00:00
}
2020-05-22 15:58:41 +00:00
void CServer : : SetClientDDNetVersion ( int ClientID , int DDNetVersion )
{
2022-07-31 21:37:56 +00:00
dbg_assert ( ClientID > = 0 & & ClientID < MAX_CLIENTS , " ClientID is not valid " ) ;
2020-05-22 15:58:41 +00:00
if ( m_aClients [ ClientID ] . m_State = = CClient : : STATE_INGAME )
{
m_aClients [ ClientID ] . m_DDNetVersion = DDNetVersion ;
m_aClients [ ClientID ] . m_DDNetVersionSettled = true ;
}
}
2021-02-08 21:26:26 +00:00
void CServer : : GetClientAddr ( int ClientID , char * pAddrStr , int Size ) const
2010-05-29 07:25:38 +00:00
{
if ( ClientID > = 0 & & ClientID < MAX_CLIENTS & & m_aClients [ ClientID ] . m_State = = CClient : : STATE_INGAME )
2011-12-29 22:36:53 +00:00
net_addr_str ( m_NetServer . ClientAddr ( ClientID ) , pAddrStr , Size , false ) ;
2010-05-29 07:25:38 +00:00
}
2011-04-13 18:37:12 +00:00
2021-02-08 21:26:26 +00:00
const char * CServer : : ClientName ( int ClientID ) const
2010-05-29 07:25:38 +00:00
{
2011-02-12 10:40:36 +00:00
if ( ClientID < 0 | | ClientID > = MAX_CLIENTS | | m_aClients [ ClientID ] . m_State = = CServer : : CClient : : STATE_EMPTY )
2011-04-16 16:41:44 +00:00
return " (invalid) " ;
2011-03-15 10:23:49 +00:00
if ( m_aClients [ ClientID ] . m_State = = CServer : : CClient : : STATE_INGAME )
return m_aClients [ ClientID ] . m_aName ;
else
2011-04-16 16:41:44 +00:00
return " (connecting) " ;
2011-03-15 10:23:49 +00:00
}
2021-02-08 21:26:26 +00:00
const char * CServer : : ClientClan ( int ClientID ) const
2011-03-15 10:23:49 +00:00
{
if ( ClientID < 0 | | ClientID > = MAX_CLIENTS | | m_aClients [ ClientID ] . m_State = = CServer : : CClient : : STATE_EMPTY )
return " " ;
if ( m_aClients [ ClientID ] . m_State = = CServer : : CClient : : STATE_INGAME )
return m_aClients [ ClientID ] . m_aClan ;
else
return " " ;
}
2021-02-08 21:26:26 +00:00
int CServer : : ClientCountry ( int ClientID ) const
2011-03-15 10:23:49 +00:00
{
if ( ClientID < 0 | | ClientID > = MAX_CLIENTS | | m_aClients [ ClientID ] . m_State = = CServer : : CClient : : STATE_EMPTY )
return - 1 ;
if ( m_aClients [ ClientID ] . m_State = = CServer : : CClient : : STATE_INGAME )
return m_aClients [ ClientID ] . m_Country ;
else
return - 1 ;
}
2010-05-29 07:25:38 +00:00
2024-02-19 20:36:21 +00:00
bool CServer : : ClientSlotEmpty ( int ClientID ) const
2024-02-11 21:12:12 +00:00
{
return ClientID > = 0 & & ClientID < MAX_CLIENTS & & m_aClients [ ClientID ] . m_State = = CServer : : CClient : : STATE_EMPTY ;
}
2021-02-08 21:26:26 +00:00
bool CServer : : ClientIngame ( int ClientID ) const
2010-05-29 07:25:38 +00:00
{
return ClientID > = 0 & & ClientID < MAX_CLIENTS & & m_aClients [ ClientID ] . m_State = = CServer : : CClient : : STATE_INGAME ;
}
2021-02-08 21:26:26 +00:00
bool CServer : : ClientAuthed ( int ClientID ) const
2018-04-03 08:27:19 +00:00
{
return ClientID > = 0 & & ClientID < MAX_CLIENTS & & m_aClients [ ClientID ] . m_Authed ;
}
2020-08-29 12:14:37 +00:00
int CServer : : Port ( ) const
{
return m_NetServer . Address ( ) . port ;
}
2011-12-04 15:51:33 +00:00
int CServer : : MaxClients ( ) const
{
Fix motd undef behavior
Conditional jump or move depends on uninitialised value(s)
at 0x2158DA: int IServer::SendPackMsg<CNetMsg_Sv_Motd, 0>(CNetMsg_Sv_Motd*, int, int) (server.h:73)
by 0x1FE02F: CGameContext::SendMotd(int) (gamecontext.cpp:538)
by 0x20A874: CGameContext::ConchainSpecialMotdupdate(IConsole::IResult*, void*, void (*)(IConsole::IResult*, void*), void*) (gamecontext.cpp:3124)
by 0x145A23: CConsole::Con_Chain(IConsole::IResult*, void*) (console.cpp:1202)
by 0x132086: CConsole::ExecuteLineStroked(int, char const*, int, bool) (console.cpp:528)
by 0x1323A7: CConsole::ExecuteLine(char const*, int, bool) (console.cpp:582)
by 0x1326B1: CConsole::ExecuteFile(char const*, int, bool, int) (console.cpp:625)
by 0x1C60E9: main (server.cpp:3874)
Uninitialised value was created by a heap allocation
at 0x483BE63: operator new(unsigned long) (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so)
by 0x1C5900: CreateServer() (server.cpp:3755)
by 0x1C5C4C: main (server.cpp:3818)
2022-06-05 18:17:31 +00:00
return m_RunServer = = UNINITIALIZED ? 0 : m_NetServer . MaxClients ( ) ;
2011-12-04 15:51:33 +00:00
}
2021-02-08 21:26:26 +00:00
int CServer : : ClientCount ( ) const
2018-10-07 22:59:07 +00:00
{
int ClientCount = 0 ;
2021-02-08 21:26:26 +00:00
for ( const auto & Client : m_aClients )
2018-10-07 22:59:07 +00:00
{
2020-10-26 14:14:07 +00:00
if ( Client . m_State ! = CClient : : STATE_EMPTY )
2018-10-07 22:59:07 +00:00
{
ClientCount + + ;
}
}
return ClientCount ;
}
2021-02-08 21:26:26 +00:00
int CServer : : DistinctClientCount ( ) const
2018-10-07 22:59:07 +00:00
{
NETADDR aAddresses [ MAX_CLIENTS ] ;
for ( int i = 0 ; i < MAX_CLIENTS ; i + + )
{
if ( m_aClients [ i ] . m_State ! = CClient : : STATE_EMPTY )
{
GetClientAddr ( i , & aAddresses [ i ] ) ;
}
}
int ClientCount = 0 ;
for ( int i = 0 ; i < MAX_CLIENTS ; i + + )
{
2023-01-02 10:10:05 +00:00
// connecting clients with spoofed ips can clog slots without being ingame
if ( ClientIngame ( i ) )
2018-10-07 22:59:07 +00:00
{
ClientCount + + ;
2018-10-08 17:56:49 +00:00
for ( int j = 0 ; j < i ; j + + )
2018-10-07 22:59:07 +00:00
{
2018-10-08 16:56:51 +00:00
if ( ! net_addr_comp_noport ( & aAddresses [ i ] , & aAddresses [ j ] ) )
2018-10-07 22:59:07 +00:00
{
ClientCount - - ;
break ;
}
}
}
}
return ClientCount ;
}
2022-07-30 08:57:46 +00:00
int CServer : : GetClientVersion ( int ClientID ) const
{
// Assume latest client version for server demos
if ( ClientID = = SERVER_DEMO_CLIENT )
2023-09-08 20:06:48 +00:00
return DDNET_VERSION_NUMBER ;
2022-07-30 08:57:46 +00:00
CClientInfo Info ;
Handle `CServer::GetClientInfo` return to fix use of undefined value
```
/home/runner/work/ddnet/ddnet/src/engine/server/server.cpp:790:2: error: Undefined or garbage value returned to caller [clang-analyzer-core.uninitialized.UndefReturn,-warnings-as-errors]
return Info.m_DDNetVersion;
^
/home/runner/work/ddnet/ddnet/src/engine/server/server.cpp:785:5: note: Assuming 'ClientID' is not equal to SERVER_DEMO_CLIENT
if(ClientID == SERVER_DEMO_CLIENT)
^
/home/runner/work/ddnet/ddnet/src/engine/server/server.cpp:785:2: note: Taking false branch
if(ClientID == SERVER_DEMO_CLIENT)
^
/home/runner/work/ddnet/ddnet/src/engine/server/server.cpp:789:2: note: Calling 'CServer::GetClientInfo'
GetClientInfo(ClientID, &Info);
^
/home/runner/work/ddnet/ddnet/src/engine/server/server.cpp:646:13: note: Assuming 'ClientID' is >= 0
dbg_assert(ClientID >= 0 && ClientID < MAX_CLIENTS, "client_id is not valid");
^
/home/runner/work/ddnet/ddnet/src/base/tl/../system.h:58:38: note: expanded from macro 'dbg_assert'
#define dbg_assert(test, msg) assert(test)
^
/usr/include/assert.h:93:27: note: expanded from macro 'assert'
(static_cast <bool> (expr) \
^
/home/runner/work/ddnet/ddnet/src/engine/server/server.cpp:646:13: note: Left side of '&&' is true
dbg_assert(ClientID >= 0 && ClientID < MAX_CLIENTS, "client_id is not valid");
^
/home/runner/work/ddnet/ddnet/src/engine/server/server.cpp:646:30: note: Assuming 'ClientID' is < MAX_CLIENTS
dbg_assert(ClientID >= 0 && ClientID < MAX_CLIENTS, "client_id is not valid");
^
/home/runner/work/ddnet/ddnet/src/base/tl/../system.h:58:38: note: expanded from macro 'dbg_assert'
#define dbg_assert(test, msg) assert(test)
^
/usr/include/assert.h:93:27: note: expanded from macro 'assert'
(static_cast <bool> (expr) \
^
/home/runner/work/ddnet/ddnet/src/engine/server/server.cpp:646:2: note: '?' condition is true
dbg_assert(ClientID >= 0 && ClientID < MAX_CLIENTS, "client_id is not valid");
^
/home/runner/work/ddnet/ddnet/src/base/tl/../system.h:58:31: note: expanded from macro 'dbg_assert'
#define dbg_assert(test, msg) assert(test)
^
/usr/include/assert.h:93:7: note: expanded from macro 'assert'
(static_cast <bool> (expr) \
^
/home/runner/work/ddnet/ddnet/src/engine/server/server.cpp:647:13: note: 'pInfo' is not equal to null
dbg_assert(pInfo != 0, "info can not be null");
^
/home/runner/work/ddnet/ddnet/src/base/tl/../system.h:58:38: note: expanded from macro 'dbg_assert'
#define dbg_assert(test, msg) assert(test)
^
/usr/include/assert.h:93:27: note: expanded from macro 'assert'
(static_cast <bool> (expr) \
^
/home/runner/work/ddnet/ddnet/src/engine/server/server.cpp:647:2: note: '?' condition is true
dbg_assert(pInfo != 0, "info can not be null");
^
/home/runner/work/ddnet/ddnet/src/base/tl/../system.h:58:31: note: expanded from macro 'dbg_assert'
#define dbg_assert(test, msg) assert(test)
^
/usr/include/assert.h:93:7: note: expanded from macro 'assert'
(static_cast <bool> (expr) \
^
/home/runner/work/ddnet/ddnet/src/engine/server/server.cpp:649:5: note: Assuming field 'm_State' is not equal to STATE_INGAME
if(m_aClients[ClientID].m_State == CClient::STATE_INGAME)
^
/home/runner/work/ddnet/ddnet/src/engine/server/server.cpp:649:2: note: Taking false branch
if(m_aClients[ClientID].m_State == CClient::STATE_INGAME)
^
/home/runner/work/ddnet/ddnet/src/engine/server/server.cpp:667:2: note: Returning without writing to 'pInfo->m_DDNetVersion'
return 0;
^
/home/runner/work/ddnet/ddnet/src/engine/server/server.cpp:789:2: note: Returning from 'CServer::GetClientInfo'
GetClientInfo(ClientID, &Info);
^
/home/runner/work/ddnet/ddnet/src/engine/server/server.cpp:790:2: note: Undefined or garbage value returned to caller
return Info.m_DDNetVersion;
^
```
2022-07-31 10:53:26 +00:00
if ( GetClientInfo ( ClientID , & Info ) )
return Info . m_DDNetVersion ;
return VERSION_NONE ;
2022-07-30 08:57:46 +00:00
}
2020-04-13 13:34:53 +00:00
static inline bool RepackMsg ( const CMsgPacker * pMsg , CPacker & Packer , bool Sixup )
{
int MsgId = pMsg - > m_MsgID ;
Packer . Reset ( ) ;
2020-12-23 00:25:04 +00:00
if ( Sixup & & ! pMsg - > m_NoTranslate )
2020-04-13 13:34:53 +00:00
{
2020-12-23 00:25:04 +00:00
if ( pMsg - > m_System )
2020-04-13 13:34:53 +00:00
{
2020-12-23 00:25:04 +00:00
if ( MsgId > = OFFSET_UUID )
;
else if ( MsgId > = NETMSG_MAP_CHANGE & & MsgId < = NETMSG_MAP_DATA )
;
else if ( MsgId > = NETMSG_CON_READY & & MsgId < = NETMSG_INPUTTIMING )
MsgId + = 1 ;
else if ( MsgId = = NETMSG_RCON_LINE )
2023-02-14 10:34:15 +00:00
MsgId = protocol7 : : NETMSG_RCON_LINE ;
2023-08-15 10:13:29 +00:00
else if ( MsgId > = NETMSG_PING & & MsgId < = NETMSG_PING_REPLY )
2020-12-23 00:25:04 +00:00
MsgId + = 4 ;
else if ( MsgId > = NETMSG_RCON_CMD_ADD & & MsgId < = NETMSG_RCON_CMD_REM )
MsgId - = 11 ;
2020-04-13 13:34:53 +00:00
else
{
2020-12-23 00:25:04 +00:00
dbg_msg ( " net " , " DROP send sys %d " , MsgId ) ;
return true ;
2020-04-13 13:34:53 +00:00
}
}
2020-12-23 00:25:04 +00:00
else
{
if ( MsgId > = 0 & & MsgId < OFFSET_UUID )
MsgId = Msg_SixToSeven ( MsgId ) ;
if ( MsgId < 0 )
return true ;
}
}
2020-04-13 13:34:53 +00:00
2020-12-23 00:25:04 +00:00
if ( MsgId < OFFSET_UUID )
{
2020-09-26 19:41:58 +00:00
Packer . AddInt ( ( MsgId < < 1 ) | ( pMsg - > m_System ? 1 : 0 ) ) ;
2020-04-13 13:34:53 +00:00
}
2020-12-23 00:25:04 +00:00
else
2020-04-13 13:34:53 +00:00
{
2022-10-30 13:10:52 +00:00
Packer . AddInt ( pMsg - > m_System ? 1 : 0 ) ; // NETMSG_EX, NETMSGTYPE_EX
2020-04-13 13:34:53 +00:00
g_UuidManager . PackUuid ( MsgId , & Packer ) ;
}
Packer . AddRaw ( pMsg - > Data ( ) , pMsg - > Size ( ) ) ;
return false ;
}
2011-02-12 10:40:36 +00:00
int CServer : : SendMsg ( CMsgPacker * pMsg , int Flags , int ClientID )
2010-05-29 07:25:38 +00:00
{
CNetChunk Packet ;
mem_zero ( & Packet , sizeof ( CNetChunk ) ) ;
2020-09-26 19:41:58 +00:00
if ( Flags & MSGFLAG_VITAL )
2010-05-29 07:25:38 +00:00
Packet . m_Flags | = NETSENDFLAG_VITAL ;
2020-09-26 19:41:58 +00:00
if ( Flags & MSGFLAG_FLUSH )
2010-05-29 07:25:38 +00:00
Packet . m_Flags | = NETSENDFLAG_FLUSH ;
2011-04-13 18:37:12 +00:00
2020-06-10 16:12:10 +00:00
if ( ClientID < 0 )
2014-09-26 00:05:22 +00:00
{
2020-06-10 16:12:10 +00:00
CPacker Pack6 , Pack7 ;
if ( RepackMsg ( pMsg , Pack6 , false ) )
return - 1 ;
if ( RepackMsg ( pMsg , Pack7 , true ) )
return - 1 ;
2010-05-29 07:25:38 +00:00
2022-08-25 15:01:24 +00:00
// write message to demo recorders
2020-09-26 19:41:58 +00:00
if ( ! ( Flags & MSGFLAG_NORECORD ) )
2022-08-25 15:01:24 +00:00
{
for ( auto & Recorder : m_aDemoRecorder )
if ( Recorder . IsRecording ( ) )
Recorder . RecordMessage ( Pack6 . Data ( ) , Pack6 . Size ( ) ) ;
}
2020-06-10 16:12:10 +00:00
2020-09-26 19:41:58 +00:00
if ( ! ( Flags & MSGFLAG_NOSEND ) )
2010-05-29 07:25:38 +00:00
{
2020-06-10 16:12:10 +00:00
for ( int i = 0 ; i < MAX_CLIENTS ; i + + )
{
2010-05-29 07:25:38 +00:00
if ( m_aClients [ i ] . m_State = = CClient : : STATE_INGAME )
{
2020-10-27 17:57:14 +00:00
CPacker * pPack = m_aClients [ i ] . m_Sixup ? & Pack7 : & Pack6 ;
Packet . m_pData = pPack - > Data ( ) ;
Packet . m_DataSize = pPack - > Size ( ) ;
2010-05-29 07:25:38 +00:00
Packet . m_ClientID = i ;
2021-06-24 18:32:41 +00:00
if ( Antibot ( ) - > OnEngineServerMessage ( i , Packet . m_pData , Packet . m_DataSize , Flags ) )
{
continue ;
}
2010-05-29 07:25:38 +00:00
m_NetServer . Send ( & Packet ) ;
}
2020-06-10 16:12:10 +00:00
}
2010-05-29 07:25:38 +00:00
}
2020-06-10 16:12:10 +00:00
}
else
{
CPacker Pack ;
if ( RepackMsg ( pMsg , Pack , m_aClients [ ClientID ] . m_Sixup ) )
return - 1 ;
Packet . m_ClientID = ClientID ;
Packet . m_pData = Pack . Data ( ) ;
Packet . m_DataSize = Pack . Size ( ) ;
2021-06-24 18:32:41 +00:00
if ( Antibot ( ) - > OnEngineServerMessage ( ClientID , Packet . m_pData , Packet . m_DataSize , Flags ) )
{
return 0 ;
}
2022-08-25 15:01:24 +00:00
// write message to demo recorders
2020-09-26 19:41:58 +00:00
if ( ! ( Flags & MSGFLAG_NORECORD ) )
2020-06-10 16:12:10 +00:00
{
2022-08-25 15:01:24 +00:00
if ( m_aDemoRecorder [ ClientID ] . IsRecording ( ) )
m_aDemoRecorder [ ClientID ] . RecordMessage ( Pack . Data ( ) , Pack . Size ( ) ) ;
2023-12-25 14:02:10 +00:00
if ( m_aDemoRecorder [ RECORDER_MANUAL ] . IsRecording ( ) )
m_aDemoRecorder [ RECORDER_MANUAL ] . RecordMessage ( Pack . Data ( ) , Pack . Size ( ) ) ;
if ( m_aDemoRecorder [ RECORDER_AUTO ] . IsRecording ( ) )
m_aDemoRecorder [ RECORDER_AUTO ] . RecordMessage ( Pack . Data ( ) , Pack . Size ( ) ) ;
2020-06-10 16:12:10 +00:00
}
2020-09-26 19:41:58 +00:00
if ( ! ( Flags & MSGFLAG_NOSEND ) )
2010-05-29 07:25:38 +00:00
m_NetServer . Send ( & Packet ) ;
}
2020-06-10 16:12:10 +00:00
2010-05-29 07:25:38 +00:00
return 0 ;
}
2020-05-13 20:27:49 +00:00
void CServer : : SendMsgRaw ( int ClientID , const void * pData , int Size , int Flags )
{
CNetChunk Packet ;
mem_zero ( & Packet , sizeof ( CNetChunk ) ) ;
Packet . m_ClientID = ClientID ;
Packet . m_pData = pData ;
Packet . m_DataSize = Size ;
Packet . m_Flags = 0 ;
2020-09-26 19:41:58 +00:00
if ( Flags & MSGFLAG_VITAL )
2020-05-13 20:27:49 +00:00
{
Packet . m_Flags | = NETSENDFLAG_VITAL ;
}
2020-09-26 19:41:58 +00:00
if ( Flags & MSGFLAG_FLUSH )
2020-05-13 20:27:49 +00:00
{
Packet . m_Flags | = NETSENDFLAG_FLUSH ;
}
m_NetServer . Send ( & Packet ) ;
}
2010-05-29 07:25:38 +00:00
void CServer : : DoSnapshot ( )
{
GameServer ( ) - > OnPreSnap ( ) ;
2011-04-13 18:37:12 +00:00
2023-12-25 14:02:10 +00:00
if ( m_aDemoRecorder [ RECORDER_MANUAL ] . IsRecording ( ) | | m_aDemoRecorder [ RECORDER_AUTO ] . IsRecording ( ) )
2010-05-29 07:25:38 +00:00
{
2023-12-25 14:02:10 +00:00
// create snapshot for demo recording
2010-05-29 07:25:38 +00:00
char aData [ CSnapshot : : MAX_SIZE ] ;
// build snap and possibly add some messages
m_SnapshotBuilder . Init ( ) ;
GameServer ( ) - > OnSnap ( - 1 ) ;
2022-03-20 11:57:50 +00:00
int SnapshotSize = m_SnapshotBuilder . Finish ( aData ) ;
2011-04-13 18:37:12 +00:00
2010-05-29 07:25:38 +00:00
// write snapshot
2023-12-25 14:02:10 +00:00
if ( m_aDemoRecorder [ RECORDER_MANUAL ] . IsRecording ( ) )
m_aDemoRecorder [ RECORDER_MANUAL ] . RecordSnapshot ( Tick ( ) , aData , SnapshotSize ) ;
if ( m_aDemoRecorder [ RECORDER_AUTO ] . IsRecording ( ) )
m_aDemoRecorder [ RECORDER_AUTO ] . RecordSnapshot ( Tick ( ) , aData , SnapshotSize ) ;
2010-05-29 07:25:38 +00:00
}
// create snapshots for all clients
2022-02-21 15:33:51 +00:00
for ( int i = 0 ; i < MaxClients ( ) ; i + + )
2010-05-29 07:25:38 +00:00
{
2018-07-10 09:29:02 +00:00
// client must be ingame to receive snapshots
2010-05-29 07:25:38 +00:00
if ( m_aClients [ i ] . m_State ! = CClient : : STATE_INGAME )
continue ;
2011-04-13 18:37:12 +00:00
2010-05-29 07:25:38 +00:00
// this client is trying to recover, don't spam snapshots
2023-11-23 13:42:30 +00:00
if ( m_aClients [ i ] . m_SnapRate = = CClient : : SNAPRATE_RECOVER & & ( Tick ( ) % TickSpeed ( ) ) ! = 0 )
2010-05-29 07:25:38 +00:00
continue ;
2011-04-13 18:37:12 +00:00
2010-05-29 07:25:38 +00:00
// this client is trying to recover, don't spam snapshots
2020-09-26 19:41:58 +00:00
if ( m_aClients [ i ] . m_SnapRate = = CClient : : SNAPRATE_INIT & & ( Tick ( ) % 10 ) ! = 0 )
2010-05-29 07:25:38 +00:00
continue ;
2011-04-13 18:37:12 +00:00
2010-05-29 07:25:38 +00:00
{
2020-03-29 02:36:38 +00:00
m_SnapshotBuilder . Init ( m_aClients [ i ] . m_Sixup ) ;
2010-05-29 07:25:38 +00:00
GameServer ( ) - > OnSnap ( i ) ;
// finish snapshot
2022-04-08 17:53:22 +00:00
char aData [ CSnapshot : : MAX_SIZE ] ;
CSnapshot * pData = ( CSnapshot * ) aData ; // Fix compiler warning for strict-aliasing
2022-03-20 11:57:50 +00:00
int SnapshotSize = m_SnapshotBuilder . Finish ( pData ) ;
2014-09-26 00:05:22 +00:00
if ( m_aDemoRecorder [ i ] . IsRecording ( ) )
2014-12-02 01:32:07 +00:00
{
// write snapshot
2021-01-17 16:18:08 +00:00
m_aDemoRecorder [ i ] . RecordSnapshot ( Tick ( ) , aData , SnapshotSize ) ;
2014-12-02 01:32:07 +00:00
}
2014-09-26 00:05:22 +00:00
2022-04-08 17:53:22 +00:00
int Crc = pData - > Crc ( ) ;
2010-05-29 07:25:38 +00:00
2022-05-24 15:26:49 +00:00
// remove old snapshots
2010-05-29 07:25:38 +00:00
// keep 3 seconds worth of snapshots
2023-11-23 13:42:30 +00:00
m_aClients [ i ] . m_Snapshots . PurgeUntil ( m_CurrentGameTick - TickSpeed ( ) * 3 ) ;
2011-04-13 18:37:12 +00:00
2022-05-24 15:26:49 +00:00
// save the snapshot
2022-06-22 21:46:25 +00:00
m_aClients [ i ] . m_Snapshots . Add ( m_CurrentGameTick , time_get ( ) , SnapshotSize , pData , 0 , nullptr ) ;
2011-04-13 18:37:12 +00:00
2018-07-10 09:29:02 +00:00
// find snapshot that we can perform delta against
2022-04-08 17:53:22 +00:00
int DeltaTick = - 1 ;
2023-09-26 18:03:45 +00:00
const CSnapshot * pDeltashot = CSnapshot : : EmptySnapshot ( ) ;
2010-05-29 07:25:38 +00:00
{
2022-10-09 15:57:43 +00:00
int DeltashotSize = m_aClients [ i ] . m_Snapshots . Get ( m_aClients [ i ] . m_LastAckedSnapshot , nullptr , & pDeltashot , nullptr ) ;
2010-05-29 07:25:38 +00:00
if ( DeltashotSize > = 0 )
DeltaTick = m_aClients [ i ] . m_LastAckedSnapshot ;
else
{
// no acked package found, force client to recover rate
if ( m_aClients [ i ] . m_SnapRate = = CClient : : SNAPRATE_FULL )
m_aClients [ i ] . m_SnapRate = CClient : : SNAPRATE_RECOVER ;
}
}
2011-04-13 18:37:12 +00:00
2010-05-29 07:25:38 +00:00
// create delta
2020-04-16 08:46:43 +00:00
m_SnapshotDelta . SetStaticsize ( protocol7 : : NETEVENTTYPE_SOUNDWORLD , m_aClients [ i ] . m_Sixup ) ;
m_SnapshotDelta . SetStaticsize ( protocol7 : : NETEVENTTYPE_DAMAGE , m_aClients [ i ] . m_Sixup ) ;
2022-04-08 17:53:22 +00:00
char aDeltaData [ CSnapshot : : MAX_SIZE ] ;
int DeltaSize = m_SnapshotDelta . CreateDelta ( pDeltashot , pData , aDeltaData ) ;
2011-04-13 18:37:12 +00:00
2010-05-29 07:25:38 +00:00
if ( DeltaSize )
{
// compress it
const int MaxSize = MAX_SNAPSHOT_PACKSIZE ;
2022-04-08 17:53:22 +00:00
char aCompData [ CSnapshot : : MAX_SIZE ] ;
2017-09-16 17:30:08 +00:00
SnapshotSize = CVariableInt : : Compress ( aDeltaData , DeltaSize , aCompData , sizeof ( aCompData ) ) ;
2022-04-08 17:53:22 +00:00
int NumPackets = ( SnapshotSize + MaxSize - 1 ) / MaxSize ;
2011-04-13 18:37:12 +00:00
2017-09-16 17:30:08 +00:00
for ( int n = 0 , Left = SnapshotSize ; Left > 0 ; n + + )
2010-05-29 07:25:38 +00:00
{
int Chunk = Left < MaxSize ? Left : MaxSize ;
Left - = Chunk ;
if ( NumPackets = = 1 )
{
2012-08-09 08:30:04 +00:00
CMsgPacker Msg ( NETMSG_SNAPSINGLE , true ) ;
2010-05-29 07:25:38 +00:00
Msg . AddInt ( m_CurrentGameTick ) ;
2020-09-26 19:41:58 +00:00
Msg . AddInt ( m_CurrentGameTick - DeltaTick ) ;
2010-05-29 07:25:38 +00:00
Msg . AddInt ( Crc ) ;
Msg . AddInt ( Chunk ) ;
2020-09-26 19:41:58 +00:00
Msg . AddRaw ( & aCompData [ n * MaxSize ] , Chunk ) ;
2012-08-09 08:30:04 +00:00
SendMsg ( & Msg , MSGFLAG_FLUSH , i ) ;
2010-05-29 07:25:38 +00:00
}
else
{
2012-08-09 08:30:04 +00:00
CMsgPacker Msg ( NETMSG_SNAP , true ) ;
2010-05-29 07:25:38 +00:00
Msg . AddInt ( m_CurrentGameTick ) ;
2020-09-26 19:41:58 +00:00
Msg . AddInt ( m_CurrentGameTick - DeltaTick ) ;
2010-05-29 07:25:38 +00:00
Msg . AddInt ( NumPackets ) ;
2011-04-13 18:37:12 +00:00
Msg . AddInt ( n ) ;
2010-05-29 07:25:38 +00:00
Msg . AddInt ( Crc ) ;
Msg . AddInt ( Chunk ) ;
2020-09-26 19:41:58 +00:00
Msg . AddRaw ( & aCompData [ n * MaxSize ] , Chunk ) ;
2012-08-09 08:30:04 +00:00
SendMsg ( & Msg , MSGFLAG_FLUSH , i ) ;
2010-05-29 07:25:38 +00:00
}
}
}
else
{
2012-08-09 08:30:04 +00:00
CMsgPacker Msg ( NETMSG_SNAPEMPTY , true ) ;
2010-05-29 07:25:38 +00:00
Msg . AddInt ( m_CurrentGameTick ) ;
2020-09-26 19:41:58 +00:00
Msg . AddInt ( m_CurrentGameTick - DeltaTick ) ;
2012-08-09 08:30:04 +00:00
SendMsg ( & Msg , MSGFLAG_FLUSH , i ) ;
2010-05-29 07:25:38 +00:00
}
}
}
GameServer ( ) - > OnPostSnap ( ) ;
}
2015-08-23 15:51:28 +00:00
int CServer : : ClientRejoinCallback ( int ClientID , void * pUser )
{
CServer * pThis = ( CServer * ) pUser ;
pThis - > m_aClients [ ClientID ] . m_Authed = AUTHED_NO ;
2017-03-06 11:35:09 +00:00
pThis - > m_aClients [ ClientID ] . m_AuthKey = - 1 ;
2023-12-23 14:35:16 +00:00
pThis - > m_aClients [ ClientID ] . m_pRconCmdToSend = nullptr ;
2021-12-14 05:50:30 +00:00
pThis - > m_aClients [ ClientID ] . m_DDNetVersion = VERSION_NONE ;
pThis - > m_aClients [ ClientID ] . m_GotDDNetVersionPacket = false ;
pThis - > m_aClients [ ClientID ] . m_DDNetVersionSettled = false ;
2015-08-23 15:51:28 +00:00
pThis - > m_aClients [ ClientID ] . Reset ( ) ;
2023-08-15 20:42:09 +00:00
pThis - > GameServer ( ) - > TeehistorianRecordPlayerRejoin ( ClientID ) ;
pThis - > Antibot ( ) - > OnEngineClientDrop ( ClientID , " rejoin " ) ;
pThis - > Antibot ( ) - > OnEngineClientJoin ( ClientID , false ) ;
2023-06-15 14:09:31 +00:00
2015-08-23 15:51:28 +00:00
pThis - > SendMap ( ClientID ) ;
return 0 ;
}
2018-05-09 21:50:25 +00:00
int CServer : : NewClientNoAuthCallback ( int ClientID , void * pUser )
2015-08-13 08:58:47 +00:00
{
CServer * pThis = ( CServer * ) pUser ;
2016-09-05 09:38:11 +00:00
pThis - > m_aClients [ ClientID ] . m_DnsblState = CClient : : DNSBL_STATE_NONE ;
2018-05-09 21:50:25 +00:00
pThis - > m_aClients [ ClientID ] . m_State = CClient : : STATE_CONNECTING ;
pThis - > m_aClients [ ClientID ] . m_aName [ 0 ] = 0 ;
pThis - > m_aClients [ ClientID ] . m_aClan [ 0 ] = 0 ;
pThis - > m_aClients [ ClientID ] . m_Country = - 1 ;
pThis - > m_aClients [ ClientID ] . m_Authed = AUTHED_NO ;
pThis - > m_aClients [ ClientID ] . m_AuthKey = - 1 ;
pThis - > m_aClients [ ClientID ] . m_AuthTries = 0 ;
2023-12-23 14:35:16 +00:00
pThis - > m_aClients [ ClientID ] . m_pRconCmdToSend = nullptr ;
2019-04-29 19:39:14 +00:00
pThis - > m_aClients [ ClientID ] . m_ShowIps = false ;
2023-11-22 16:57:42 +00:00
pThis - > m_aClients [ ClientID ] . m_DebugDummy = false ;
2021-12-14 05:50:30 +00:00
pThis - > m_aClients [ ClientID ] . m_DDNetVersion = VERSION_NONE ;
pThis - > m_aClients [ ClientID ] . m_GotDDNetVersionPacket = false ;
pThis - > m_aClients [ ClientID ] . m_DDNetVersionSettled = false ;
2018-05-09 21:50:25 +00:00
pThis - > m_aClients [ ClientID ] . Reset ( ) ;
2015-08-13 08:58:47 +00:00
2023-06-15 19:00:14 +00:00
pThis - > GameServer ( ) - > TeehistorianRecordPlayerJoin ( ClientID , false ) ;
2023-06-15 14:09:31 +00:00
pThis - > Antibot ( ) - > OnEngineClientJoin ( ClientID , false ) ;
2019-06-03 19:52:14 +00:00
pThis - > SendCapabilities ( ClientID ) ;
2015-08-13 08:58:47 +00:00
pThis - > SendMap ( ClientID ) ;
2017-12-20 15:56:34 +00:00
# if defined(CONF_FAMILY_UNIX)
pThis - > SendConnLoggingCommand ( OPEN_SESSION , pThis - > m_NetServer . ClientAddr ( ClientID ) ) ;
# endif
2015-08-13 08:58:47 +00:00
return 0 ;
}
2010-05-29 07:25:38 +00:00
2020-03-29 02:36:38 +00:00
int CServer : : NewClientCallback ( int ClientID , void * pUser , bool Sixup )
2010-05-29 07:25:38 +00:00
{
CServer * pThis = ( CServer * ) pUser ;
2020-05-22 15:58:41 +00:00
pThis - > m_aClients [ ClientID ] . m_State = CClient : : STATE_PREAUTH ;
2016-09-05 09:38:11 +00:00
pThis - > m_aClients [ ClientID ] . m_DnsblState = CClient : : DNSBL_STATE_NONE ;
2011-02-12 10:40:36 +00:00
pThis - > m_aClients [ ClientID ] . m_aName [ 0 ] = 0 ;
pThis - > m_aClients [ ClientID ] . m_aClan [ 0 ] = 0 ;
2011-03-15 10:23:49 +00:00
pThis - > m_aClients [ ClientID ] . m_Country = - 1 ;
2011-07-05 19:54:10 +00:00
pThis - > m_aClients [ ClientID ] . m_Authed = AUTHED_NO ;
2017-03-06 11:35:09 +00:00
pThis - > m_aClients [ ClientID ] . m_AuthKey = - 1 ;
2011-02-12 10:40:36 +00:00
pThis - > m_aClients [ ClientID ] . m_AuthTries = 0 ;
2023-12-23 14:35:16 +00:00
pThis - > m_aClients [ ClientID ] . m_pRconCmdToSend = nullptr ;
2013-08-04 16:09:28 +00:00
pThis - > m_aClients [ ClientID ] . m_Traffic = 0 ;
pThis - > m_aClients [ ClientID ] . m_TrafficSince = 0 ;
2019-04-29 19:39:14 +00:00
pThis - > m_aClients [ ClientID ] . m_ShowIps = false ;
2023-11-22 16:57:42 +00:00
pThis - > m_aClients [ ClientID ] . m_DebugDummy = false ;
2021-12-14 05:50:30 +00:00
pThis - > m_aClients [ ClientID ] . m_DDNetVersion = VERSION_NONE ;
pThis - > m_aClients [ ClientID ] . m_GotDDNetVersionPacket = false ;
pThis - > m_aClients [ ClientID ] . m_DDNetVersionSettled = false ;
2022-08-25 12:07:47 +00:00
mem_zero ( & pThis - > m_aClients [ ClientID ] . m_Addr , sizeof ( NETADDR ) ) ;
2011-02-12 10:40:36 +00:00
pThis - > m_aClients [ ClientID ] . Reset ( ) ;
2020-05-13 20:27:49 +00:00
2023-06-15 19:00:14 +00:00
pThis - > GameServer ( ) - > TeehistorianRecordPlayerJoin ( ClientID , Sixup ) ;
2020-07-01 12:11:37 +00:00
pThis - > Antibot ( ) - > OnEngineClientJoin ( ClientID , Sixup ) ;
2017-12-20 15:56:34 +00:00
2020-03-29 02:36:38 +00:00
pThis - > m_aClients [ ClientID ] . m_Sixup = Sixup ;
2017-12-20 15:56:34 +00:00
# if defined(CONF_FAMILY_UNIX)
pThis - > SendConnLoggingCommand ( OPEN_SESSION , pThis - > m_NetServer . ClientAddr ( ClientID ) ) ;
# endif
2010-05-29 07:25:38 +00:00
return 0 ;
}
2016-09-05 09:38:11 +00:00
void CServer : : InitDnsbl ( int ClientID )
{
NETADDR Addr = * m_NetServer . ClientAddr ( ClientID ) ;
//TODO: support ipv6
2017-11-23 14:47:38 +00:00
if ( Addr . type ! = NETTYPE_IPV4 )
2016-09-05 09:38:11 +00:00
return ;
// build dnsbl host lookup
char aBuf [ 256 ] ;
2021-12-14 23:28:51 +00:00
if ( Config ( ) - > m_SvDnsblKey [ 0 ] = = ' \0 ' )
2017-11-23 14:47:38 +00:00
{
2016-09-05 09:38:11 +00:00
// without key
2021-12-14 23:28:51 +00:00
str_format ( aBuf , sizeof ( aBuf ) , " %d.%d.%d.%d.%s " , Addr . ip [ 3 ] , Addr . ip [ 2 ] , Addr . ip [ 1 ] , Addr . ip [ 0 ] , Config ( ) - > m_SvDnsblHost ) ;
2017-11-23 14:47:38 +00:00
}
2016-09-05 09:38:11 +00:00
else
2017-11-23 14:47:38 +00:00
{
2016-09-05 09:38:11 +00:00
// with key
2021-12-14 23:28:51 +00:00
str_format ( aBuf , sizeof ( aBuf ) , " %s.%d.%d.%d.%d.%s " , Config ( ) - > m_SvDnsblKey , Addr . ip [ 3 ] , Addr . ip [ 2 ] , Addr . ip [ 1 ] , Addr . ip [ 0 ] , Config ( ) - > m_SvDnsblHost ) ;
2017-11-23 14:47:38 +00:00
}
2016-09-05 09:38:11 +00:00
Allow background jobs to be aborted, refactoring
Add `IJob::Abortable(bool)` function which jobs can call to specify whether they can be aborted. Jobs are not abortable per default. Abortable jobs may have their state set to `IJob::STATE_ABORTED` at any point if the job was aborted. The job state should be checked periodically in the `IJob::Run` function and the job should terminate at the earliest, safe opportunity when aborted. Scheduled jobs which are not abortable are guaranteed to fully complete before the job pool is shut down. However, if the job pool is already shutting down, no additional jobs will be enqueue anymore and abortable jobs will immediately be aborted.
In particular, the sound loading, community icon loading, master chooser and host lookup jobs are specified as being abortable. Conversely, the jobs saving replay demos, editor maps and screenshots are expected to finish before the client is shut down.
When the client is quitting/restarting, it will now disconnect from the current server first, before saving the config, to ensure that any actions that happen on disconnect (demo recorders being stopped etc.) happen first. The shutdown message is rendered before disconnecting and waiting for background jobs to finish.
The HTTP client is now initialized later during server launch, after the network initialization. Error handling is added and the server stops if the HTTP client could not be initialized, same as the client.
The `RunBlocking` functions are removed, as they are not used anymore after curl-multi was added.
The function `IJob::Status` is renamed to `State` and `IJob::STATE_PENDING` is renamed to `STATE_QUEUED` for consistency with naming of the HTTP client.
The member variables of the engine interface are encapsulated and the `jobs.h` include is removed from `engine.h`, which removes transitive includes of `system.h`.
Documentation for all job and job pool API is added.
2024-02-16 19:44:33 +00:00
m_aClients [ ClientID ] . m_pDnsblLookup = std : : make_shared < CHostLookup > ( aBuf , NETTYPE_IPV4 ) ;
Engine ( ) - > AddJob ( m_aClients [ ClientID ] . m_pDnsblLookup ) ;
2018-08-24 12:24:33 +00:00
m_aClients [ ClientID ] . m_DnsblState = CClient : : DNSBL_STATE_PENDING ;
2016-09-05 09:38:11 +00:00
}
2017-12-20 15:56:34 +00:00
# ifdef CONF_FAMILY_UNIX
2020-09-26 19:41:58 +00:00
void CServer : : SendConnLoggingCommand ( CONN_LOGGING_CMD Cmd , const NETADDR * pAddr )
2017-12-20 15:56:34 +00:00
{
2021-12-14 23:28:51 +00:00
if ( ! Config ( ) - > m_SvConnLoggingServer [ 0 ] | | ! m_ConnLoggingSocketCreated )
2017-12-20 15:56:34 +00:00
return ;
// pack the data and send it
unsigned char aData [ 23 ] = { 0 } ;
aData [ 0 ] = Cmd ;
mem_copy ( & aData [ 1 ] , & pAddr - > type , 4 ) ;
mem_copy ( & aData [ 5 ] , pAddr - > ip , 16 ) ;
mem_copy ( & aData [ 21 ] , & pAddr - > port , 2 ) ;
2017-12-20 15:56:44 +00:00
net_unix_send ( m_ConnLoggingSocket , & m_ConnLoggingDestAddr , aData , sizeof ( aData ) ) ;
2017-12-20 15:56:34 +00:00
}
# endif
2011-02-12 10:40:36 +00:00
int CServer : : DelClientCallback ( int ClientID , const char * pReason , void * pUser )
2010-05-29 07:25:38 +00:00
{
CServer * pThis = ( CServer * ) pUser ;
2011-04-13 18:37:12 +00:00
2011-03-28 18:11:28 +00:00
char aAddrStr [ NETADDR_MAXSTRSIZE ] ;
2011-12-29 22:36:53 +00:00
net_addr_str ( pThis - > m_NetServer . ClientAddr ( ClientID ) , aAddrStr , sizeof ( aAddrStr ) , true ) ;
2017-04-17 10:13:58 +00:00
2010-08-17 22:06:00 +00:00
char aBuf [ 256 ] ;
2019-04-04 17:16:10 +00:00
str_format ( aBuf , sizeof ( aBuf ) , " client dropped. cid=%d addr=<{%s}> reason='%s' " , ClientID , aAddrStr , pReason ) ;
2010-08-17 22:06:00 +00:00
pThis - > Console ( ) - > Print ( IConsole : : OUTPUT_LEVEL_ADDINFO , " server " , aBuf ) ;
2010-05-29 07:25:38 +00:00
// notify the mod about the drop
2011-02-12 10:40:36 +00:00
if ( pThis - > m_aClients [ ClientID ] . m_State > = CClient : : STATE_READY )
2011-02-14 18:41:32 +00:00
pThis - > GameServer ( ) - > OnClientDrop ( ClientID , pReason ) ;
2011-04-13 18:37:12 +00:00
2011-02-12 10:40:36 +00:00
pThis - > m_aClients [ ClientID ] . m_State = CClient : : STATE_EMPTY ;
pThis - > m_aClients [ ClientID ] . m_aName [ 0 ] = 0 ;
pThis - > m_aClients [ ClientID ] . m_aClan [ 0 ] = 0 ;
2011-03-15 10:23:49 +00:00
pThis - > m_aClients [ ClientID ] . m_Country = - 1 ;
2011-07-05 19:54:10 +00:00
pThis - > m_aClients [ ClientID ] . m_Authed = AUTHED_NO ;
2017-03-06 11:35:09 +00:00
pThis - > m_aClients [ ClientID ] . m_AuthKey = - 1 ;
2011-02-12 10:40:36 +00:00
pThis - > m_aClients [ ClientID ] . m_AuthTries = 0 ;
2023-12-23 14:35:16 +00:00
pThis - > m_aClients [ ClientID ] . m_pRconCmdToSend = nullptr ;
2013-08-04 16:09:28 +00:00
pThis - > m_aClients [ ClientID ] . m_Traffic = 0 ;
pThis - > m_aClients [ ClientID ] . m_TrafficSince = 0 ;
2019-04-29 19:39:14 +00:00
pThis - > m_aClients [ ClientID ] . m_ShowIps = false ;
2023-11-22 16:57:42 +00:00
pThis - > m_aClients [ ClientID ] . m_DebugDummy = false ;
2011-02-13 05:35:13 +00:00
pThis - > m_aPrevStates [ ClientID ] = CClient : : STATE_EMPTY ;
2011-02-12 10:40:36 +00:00
pThis - > m_aClients [ ClientID ] . m_Snapshots . PurgeAll ( ) ;
2020-06-17 09:58:04 +00:00
pThis - > m_aClients [ ClientID ] . m_Sixup = false ;
2023-06-23 08:18:47 +00:00
pThis - > m_aClients [ ClientID ] . m_RedirectDropTime = 0 ;
2017-09-13 20:35:09 +00:00
2023-06-15 19:00:14 +00:00
pThis - > GameServer ( ) - > TeehistorianRecordPlayerDrop ( ClientID , pReason ) ;
2020-05-13 20:27:49 +00:00
pThis - > Antibot ( ) - > OnEngineClientDrop ( ClientID , pReason ) ;
2017-12-20 15:56:34 +00:00
# if defined(CONF_FAMILY_UNIX)
pThis - > SendConnLoggingCommand ( CLOSE_SESSION , pThis - > m_NetServer . ClientAddr ( ClientID ) ) ;
# endif
2010-05-29 07:25:38 +00:00
return 0 ;
}
2017-07-24 19:43:55 +00:00
void CServer : : SendRconType ( int ClientID , bool UsernameReq )
{
2012-08-09 08:30:04 +00:00
CMsgPacker Msg ( NETMSG_RCONTYPE , true ) ;
2017-07-24 19:43:55 +00:00
Msg . AddInt ( UsernameReq ) ;
2012-08-09 08:30:04 +00:00
SendMsg ( & Msg , MSGFLAG_VITAL , ClientID ) ;
2017-07-24 19:43:55 +00:00
}
2018-06-05 19:22:40 +00:00
void CServer : : GetMapInfo ( char * pMapName , int MapNameSize , int * pMapSize , SHA256_DIGEST * pMapSha256 , int * pMapCrc )
2017-09-12 12:58:44 +00:00
{
str_copy ( pMapName , GetMapName ( ) , MapNameSize ) ;
2022-02-17 18:50:02 +00:00
* pMapSize = m_aCurrentMapSize [ MAP_TYPE_SIX ] ;
* pMapSha256 = m_aCurrentMapSha256 [ MAP_TYPE_SIX ] ;
* pMapCrc = m_aCurrentMapCrc [ MAP_TYPE_SIX ] ;
2017-09-12 12:58:44 +00:00
}
2019-06-03 19:52:14 +00:00
void CServer : : SendCapabilities ( int ClientID )
{
2012-08-09 08:30:04 +00:00
CMsgPacker Msg ( NETMSG_CAPABILITIES , true ) ;
2019-06-03 19:52:14 +00:00
Msg . AddInt ( SERVERCAP_CURVERSION ) ; // version
2022-01-09 13:14:45 +00:00
Msg . AddInt ( SERVERCAPFLAG_DDNET | SERVERCAPFLAG_CHATTIMEOUTCODE | SERVERCAPFLAG_ANYPLAYERFLAG | SERVERCAPFLAG_PINGEX | SERVERCAPFLAG_ALLOWDUMMY | SERVERCAPFLAG_SYNCWEAPONINPUT ) ; // flags
2012-08-09 08:30:04 +00:00
SendMsg ( & Msg , MSGFLAG_VITAL , ClientID ) ;
2019-06-03 19:52:14 +00:00
}
2011-02-12 10:40:36 +00:00
void CServer : : SendMap ( int ClientID )
2010-05-29 07:25:38 +00:00
{
2022-02-17 18:50:02 +00:00
int MapType = IsSixup ( ClientID ) ? MAP_TYPE_SIXUP : MAP_TYPE_SIX ;
2018-06-05 19:22:40 +00:00
{
2012-08-09 08:30:04 +00:00
CMsgPacker Msg ( NETMSG_MAP_DETAILS , true ) ;
2018-06-05 19:22:40 +00:00
Msg . AddString ( GetMapName ( ) , 0 ) ;
2022-02-17 18:50:02 +00:00
Msg . AddRaw ( & m_aCurrentMapSha256 [ MapType ] . data , sizeof ( m_aCurrentMapSha256 [ MapType ] . data ) ) ;
Msg . AddInt ( m_aCurrentMapCrc [ MapType ] ) ;
Msg . AddInt ( m_aCurrentMapSize [ MapType ] ) ;
2022-09-11 22:43:57 +00:00
Msg . AddString ( " " , 0 ) ; // HTTPS map download URL
2012-08-09 08:30:04 +00:00
SendMsg ( & Msg , MSGFLAG_VITAL , ClientID ) ;
2018-06-05 19:22:40 +00:00
}
{
2012-08-09 08:30:04 +00:00
CMsgPacker Msg ( NETMSG_MAP_CHANGE , true ) ;
2018-06-05 19:22:40 +00:00
Msg . AddString ( GetMapName ( ) , 0 ) ;
2022-02-17 18:50:02 +00:00
Msg . AddInt ( m_aCurrentMapCrc [ MapType ] ) ;
Msg . AddInt ( m_aCurrentMapSize [ MapType ] ) ;
if ( MapType = = MAP_TYPE_SIXUP )
2020-03-29 02:36:38 +00:00
{
2021-12-14 23:28:51 +00:00
Msg . AddInt ( Config ( ) - > m_SvMapWindow ) ;
2020-09-26 19:41:58 +00:00
Msg . AddInt ( 1024 - 128 ) ;
2022-02-17 18:50:02 +00:00
Msg . AddRaw ( m_aCurrentMapSha256 [ MapType ] . data , sizeof ( m_aCurrentMapSha256 [ MapType ] . data ) ) ;
2020-03-29 02:36:38 +00:00
}
2020-09-26 19:41:58 +00:00
SendMsg ( & Msg , MSGFLAG_VITAL | MSGFLAG_FLUSH , ClientID ) ;
2018-06-05 19:22:40 +00:00
}
2016-11-18 14:00:42 +00:00
m_aClients [ ClientID ] . m_NextMapChunk = 0 ;
}
void CServer : : SendMapData ( int ClientID , int Chunk )
{
2022-02-17 18:50:02 +00:00
int MapType = IsSixup ( ClientID ) ? MAP_TYPE_SIXUP : MAP_TYPE_SIX ;
2020-09-26 19:41:58 +00:00
unsigned int ChunkSize = 1024 - 128 ;
2016-11-18 14:00:42 +00:00
unsigned int Offset = Chunk * ChunkSize ;
int Last = 0 ;
// drop faulty map data requests
2022-02-17 18:50:02 +00:00
if ( Chunk < 0 | | Offset > m_aCurrentMapSize [ MapType ] )
2016-11-18 14:00:42 +00:00
return ;
2022-02-17 18:50:02 +00:00
if ( Offset + ChunkSize > = m_aCurrentMapSize [ MapType ] )
2016-11-18 14:00:42 +00:00
{
2022-02-17 18:50:02 +00:00
ChunkSize = m_aCurrentMapSize [ MapType ] - Offset ;
2016-11-18 14:00:42 +00:00
Last = 1 ;
}
2020-06-17 09:58:04 +00:00
CMsgPacker Msg ( NETMSG_MAP_DATA , true ) ;
2022-02-17 18:50:02 +00:00
if ( MapType = = MAP_TYPE_SIX )
2020-03-29 02:36:38 +00:00
{
Msg . AddInt ( Last ) ;
2022-02-17 18:50:02 +00:00
Msg . AddInt ( m_aCurrentMapCrc [ MAP_TYPE_SIX ] ) ;
2020-03-29 02:36:38 +00:00
Msg . AddInt ( Chunk ) ;
Msg . AddInt ( ChunkSize ) ;
}
2022-02-17 18:50:02 +00:00
Msg . AddRaw ( & m_apCurrentMapData [ MapType ] [ Offset ] , ChunkSize ) ;
2020-09-26 19:41:58 +00:00
SendMsg ( & Msg , MSGFLAG_VITAL | MSGFLAG_FLUSH , ClientID ) ;
2016-11-18 14:00:42 +00:00
2021-12-14 23:28:51 +00:00
if ( Config ( ) - > m_Debug )
2016-11-18 14:00:42 +00:00
{
char aBuf [ 256 ] ;
str_format ( aBuf , sizeof ( aBuf ) , " sending chunk %d with size %d " , Chunk , ChunkSize ) ;
Console ( ) - > Print ( IConsole : : OUTPUT_LEVEL_DEBUG , " server " , aBuf ) ;
}
2010-05-29 07:25:38 +00:00
}
2011-03-15 08:58:57 +00:00
void CServer : : SendConnectionReady ( int ClientID )
{
2012-08-09 08:30:04 +00:00
CMsgPacker Msg ( NETMSG_CON_READY , true ) ;
2020-09-26 19:41:58 +00:00
SendMsg ( & Msg , MSGFLAG_VITAL | MSGFLAG_FLUSH , ClientID ) ;
2010-05-29 07:25:38 +00:00
}
2011-02-12 10:40:36 +00:00
void CServer : : SendRconLine ( int ClientID , const char * pLine )
2010-05-29 07:25:38 +00:00
{
2012-08-09 08:30:04 +00:00
CMsgPacker Msg ( NETMSG_RCON_LINE , true ) ;
2010-05-29 07:25:38 +00:00
Msg . AddString ( pLine , 512 ) ;
2012-08-09 08:30:04 +00:00
SendMsg ( & Msg , MSGFLAG_VITAL , ClientID ) ;
2010-05-29 07:25:38 +00:00
}
2022-04-22 23:04:48 +00:00
void CServer : : SendRconLogLine ( int ClientID , const CLogMessage * pMessage )
2010-05-29 07:25:38 +00:00
{
2022-04-22 23:04:48 +00:00
const char * pLine = pMessage - > m_aLine ;
2019-04-04 17:16:10 +00:00
const char * pStart = str_find ( pLine , " <{ " ) ;
const char * pEnd = pStart = = NULL ? NULL : str_find ( pStart + 2 , " }> " ) ;
const char * pLineWithoutIps ;
char aLine [ 512 ] ;
char aLineWithoutIps [ 512 ] ;
aLine [ 0 ] = ' \0 ' ;
aLineWithoutIps [ 0 ] = ' \0 ' ;
if ( pStart = = NULL | | pEnd = = NULL )
{
pLineWithoutIps = pLine ;
}
else
{
str_append ( aLine , pLine , pStart - pLine + 1 ) ;
2020-09-27 08:07:26 +00:00
str_append ( aLine , pStart + 2 , pStart - pLine + pEnd - pStart - 1 ) ;
2023-06-13 19:06:48 +00:00
str_append ( aLine , pEnd + 2 ) ;
2019-04-04 17:16:10 +00:00
str_append ( aLineWithoutIps , pLine , pStart - pLine + 1 ) ;
2023-06-13 19:06:48 +00:00
str_append ( aLineWithoutIps , " XXX " ) ;
str_append ( aLineWithoutIps , pEnd + 2 ) ;
2019-04-04 17:16:10 +00:00
pLine = aLine ;
pLineWithoutIps = aLineWithoutIps ;
}
2022-04-22 23:04:48 +00:00
if ( ClientID = = - 1 )
{
for ( int i = 0 ; i < MAX_CLIENTS ; i + + )
{
if ( m_aClients [ i ] . m_State ! = CClient : : STATE_EMPTY & & m_aClients [ i ] . m_Authed > = AUTHED_ADMIN )
SendRconLine ( i , m_aClients [ i ] . m_ShowIps ? pLine : pLineWithoutIps ) ;
}
}
else
2010-05-29 07:25:38 +00:00
{
2022-04-22 23:04:48 +00:00
if ( m_aClients [ ClientID ] . m_State ! = CClient : : STATE_EMPTY )
SendRconLine ( ClientID , m_aClients [ ClientID ] . m_ShowIps ? pLine : pLineWithoutIps ) ;
2010-05-29 07:25:38 +00:00
}
}
2011-07-14 20:07:21 +00:00
void CServer : : SendRconCmdAdd ( const IConsole : : CCommandInfo * pCommandInfo , int ClientID )
{
2012-08-09 08:30:04 +00:00
CMsgPacker Msg ( NETMSG_RCON_CMD_ADD , true ) ;
2011-07-30 16:19:15 +00:00
Msg . AddString ( pCommandInfo - > m_pName , IConsole : : TEMPCMD_NAME_LENGTH ) ;
Msg . AddString ( pCommandInfo - > m_pHelp , IConsole : : TEMPCMD_HELP_LENGTH ) ;
2015-12-28 15:14:52 +00:00
Msg . AddString ( pCommandInfo - > m_pParams , IConsole : : TEMPCMD_PARAMS_LENGTH ) ;
2012-08-09 08:30:04 +00:00
SendMsg ( & Msg , MSGFLAG_VITAL , ClientID ) ;
2011-07-14 20:07:21 +00:00
}
void CServer : : SendRconCmdRem ( const IConsole : : CCommandInfo * pCommandInfo , int ClientID )
{
2012-08-09 08:30:04 +00:00
CMsgPacker Msg ( NETMSG_RCON_CMD_REM , true ) ;
2011-07-14 20:07:21 +00:00
Msg . AddString ( pCommandInfo - > m_pName , 256 ) ;
2012-08-09 08:30:04 +00:00
SendMsg ( & Msg , MSGFLAG_VITAL , ClientID ) ;
2011-07-14 20:07:21 +00:00
}
void CServer : : UpdateClientRconCommands ( )
{
int ClientID = Tick ( ) % MAX_CLIENTS ;
2011-08-11 08:59:14 +00:00
2011-07-14 20:07:21 +00:00
if ( m_aClients [ ClientID ] . m_State ! = CClient : : STATE_EMPTY & & m_aClients [ ClientID ] . m_Authed )
{
2015-10-23 00:33:10 +00:00
int ConsoleAccessLevel = m_aClients [ ClientID ] . m_Authed = = AUTHED_ADMIN ? IConsole : : ACCESS_LEVEL_ADMIN : m_aClients [ ClientID ] . m_Authed = = AUTHED_MOD ? IConsole : : ACCESS_LEVEL_MOD : IConsole : : ACCESS_LEVEL_HELPER ;
2011-07-14 20:07:21 +00:00
for ( int i = 0 ; i < MAX_RCONCMD_SEND & & m_aClients [ ClientID ] . m_pRconCmdToSend ; + + i )
{
SendRconCmdAdd ( m_aClients [ ClientID ] . m_pRconCmdToSend , ClientID ) ;
m_aClients [ ClientID ] . m_pRconCmdToSend = m_aClients [ ClientID ] . m_pRconCmdToSend - > NextCommandInfo ( ConsoleAccessLevel , CFGFLAG_SERVER ) ;
2023-12-23 14:35:16 +00:00
if ( m_aClients [ ClientID ] . m_pRconCmdToSend = = nullptr )
{
CMsgPacker Msg ( NETMSG_RCON_CMD_GROUP_END , true ) ;
SendMsg ( & Msg , MSGFLAG_VITAL , ClientID ) ;
}
2011-07-14 20:07:21 +00:00
}
}
}
2020-04-13 13:47:56 +00:00
static inline int MsgFromSixup ( int Msg , bool System )
{
if ( System )
{
if ( Msg = = NETMSG_INFO )
;
2020-06-16 14:55:37 +00:00
else if ( Msg > = 14 & & Msg < = 15 )
Msg + = 11 ;
2020-04-13 13:47:56 +00:00
else if ( Msg > = 18 & & Msg < = 28 )
Msg = NETMSG_READY + Msg - 18 ;
2020-12-23 00:25:04 +00:00
else if ( Msg < OFFSET_UUID )
2020-04-13 13:47:56 +00:00
return - 1 ;
}
return Msg ;
}
2023-05-24 14:00:39 +00:00
bool CServer : : CheckReservedSlotAuth ( int ClientID , const char * pPassword )
{
char aBuf [ 256 ] ;
if ( Config ( ) - > m_SvReservedSlotsPass [ 0 ] & & ! str_comp ( Config ( ) - > m_SvReservedSlotsPass , pPassword ) )
{
str_format ( aBuf , sizeof ( aBuf ) , " cid=%d joining reserved slot with reserved pass " , ClientID ) ;
Console ( ) - > Print ( IConsole : : OUTPUT_LEVEL_STANDARD , " server " , aBuf ) ;
return true ;
}
2023-06-02 17:55:27 +00:00
// "^([^:]*):(.*)$"
if ( Config ( ) - > m_SvReservedSlotsAuthLevel ! = 4 )
2023-05-24 14:00:39 +00:00
{
char aName [ sizeof ( Config ( ) - > m_Password ) ] ;
2023-06-02 17:55:27 +00:00
const char * pInnerPassword = str_next_token ( pPassword , " : " , aName , sizeof ( aName ) ) ;
if ( ! pInnerPassword )
{
return false ;
}
2023-05-24 14:00:39 +00:00
int Slot = m_AuthManager . FindKey ( aName ) ;
2023-06-02 17:55:27 +00:00
if ( m_AuthManager . CheckKey ( Slot , pInnerPassword + 1 ) & & m_AuthManager . KeyLevel ( Slot ) > = Config ( ) - > m_SvReservedSlotsAuthLevel )
2023-05-24 14:00:39 +00:00
{
str_format ( aBuf , sizeof ( aBuf ) , " cid=%d joining reserved slot with key=%s " , ClientID , m_AuthManager . KeyIdent ( Slot ) ) ;
Console ( ) - > Print ( IConsole : : OUTPUT_LEVEL_STANDARD , " server " , aBuf ) ;
return true ;
}
}
return false ;
}
2010-05-29 07:25:38 +00:00
void CServer : : ProcessClientPacket ( CNetChunk * pPacket )
{
2011-02-12 10:40:36 +00:00
int ClientID = pPacket - > m_ClientID ;
2010-05-29 07:25:38 +00:00
CUnpacker Unpacker ;
Unpacker . Reset ( pPacket - > m_pData , pPacket - > m_DataSize ) ;
2012-08-09 08:30:04 +00:00
CMsgPacker Packer ( NETMSG_EX , true ) ;
2011-04-13 18:37:12 +00:00
2010-05-29 07:25:38 +00:00
// unpack msgid and system flag
2017-05-21 23:07:13 +00:00
int Msg ;
bool Sys ;
CUuid Uuid ;
2011-04-13 18:37:12 +00:00
2017-05-21 23:07:13 +00:00
int Result = UnpackMessageID ( & Msg , & Sys , & Uuid , & Unpacker , & Packer ) ;
if ( Result = = UNPACKMESSAGE_ERROR )
{
2010-05-29 07:25:38 +00:00
return ;
2017-05-21 23:07:13 +00:00
}
2011-04-13 18:37:12 +00:00
2020-04-13 13:47:56 +00:00
if ( m_aClients [ ClientID ] . m_Sixup & & ( Msg = MsgFromSixup ( Msg , Sys ) ) < 0 )
{
return ;
}
2021-12-14 23:28:51 +00:00
if ( Config ( ) - > m_SvNetlimit & & Msg ! = NETMSG_REQUEST_MAP_DATA )
2013-08-04 02:24:03 +00:00
{
2021-06-23 05:05:49 +00:00
int64_t Now = time_get ( ) ;
int64_t Diff = Now - m_aClients [ ClientID ] . m_TrafficSince ;
2022-05-27 17:40:27 +00:00
double Alpha = Config ( ) - > m_SvNetlimitAlpha / 100.0 ;
double Limit = ( double ) ( Config ( ) - > m_SvNetlimit * 1024 ) / time_freq ( ) ;
2013-08-04 02:37:15 +00:00
2020-09-26 19:41:58 +00:00
if ( m_aClients [ ClientID ] . m_Traffic > Limit )
2013-08-04 02:24:03 +00:00
{
2013-11-23 19:19:30 +00:00
m_NetServer . NetBan ( ) - > BanAddr ( & pPacket - > m_Address , 600 , " Stressing network " ) ;
2013-08-04 15:50:12 +00:00
return ;
2013-08-04 02:24:03 +00:00
}
2020-09-26 19:41:58 +00:00
if ( Diff > 100 )
2013-08-04 02:24:03 +00:00
{
2022-05-27 17:40:27 +00:00
m_aClients [ ClientID ] . m_Traffic = ( Alpha * ( ( double ) pPacket - > m_DataSize / Diff ) ) + ( 1.0 - Alpha ) * m_aClients [ ClientID ] . m_Traffic ;
2013-08-04 15:50:12 +00:00
m_aClients [ ClientID ] . m_TrafficSince = Now ;
2013-08-04 02:24:03 +00:00
}
}
2017-05-21 23:07:13 +00:00
if ( Result = = UNPACKMESSAGE_ANSWER )
{
2012-08-09 08:30:04 +00:00
SendMsg ( & Packer , MSGFLAG_VITAL , ClientID ) ;
2017-05-21 23:07:13 +00:00
}
2011-03-15 08:58:57 +00:00
if ( Sys )
2010-05-29 07:25:38 +00:00
{
2011-03-15 08:58:57 +00:00
// system message
2020-05-22 15:58:41 +00:00
if ( Msg = = NETMSG_CLIENTVER )
2010-05-29 07:25:38 +00:00
{
2020-09-26 19:41:58 +00:00
if ( ( pPacket - > m_Flags & NET_CHUNKFLAG_VITAL ) ! = 0 & & m_aClients [ ClientID ] . m_State = = CClient : : STATE_PREAUTH )
2020-05-22 15:58:41 +00:00
{
CUuid * pConnectionID = ( CUuid * ) Unpacker . GetRaw ( sizeof ( * pConnectionID ) ) ;
int DDNetVersion = Unpacker . GetInt ( ) ;
const char * pDDNetVersionStr = Unpacker . GetString ( CUnpacker : : SANITIZE_CC ) ;
2023-08-07 18:27:22 +00:00
if ( Unpacker . Error ( ) | | DDNetVersion < 0 )
2020-05-22 15:58:41 +00:00
{
return ;
}
m_aClients [ ClientID ] . m_ConnectionID = * pConnectionID ;
m_aClients [ ClientID ] . m_DDNetVersion = DDNetVersion ;
2022-07-09 16:14:56 +00:00
str_copy ( m_aClients [ ClientID ] . m_aDDNetVersionStr , pDDNetVersionStr ) ;
2020-05-22 15:58:41 +00:00
m_aClients [ ClientID ] . m_DDNetVersionSettled = true ;
m_aClients [ ClientID ] . m_GotDDNetVersionPacket = true ;
m_aClients [ ClientID ] . m_State = CClient : : STATE_AUTH ;
}
}
else if ( Msg = = NETMSG_INFO )
{
2020-09-26 19:41:58 +00:00
if ( ( pPacket - > m_Flags & NET_CHUNKFLAG_VITAL ) ! = 0 & & ( m_aClients [ ClientID ] . m_State = = CClient : : STATE_PREAUTH | | m_aClients [ ClientID ] . m_State = = CClient : : STATE_AUTH ) )
2010-05-29 07:25:38 +00:00
{
2011-03-15 08:58:57 +00:00
const char * pVersion = Unpacker . GetString ( CUnpacker : : SANITIZE_CC ) ;
2023-08-07 18:27:22 +00:00
if ( Unpacker . Error ( ) )
2016-01-27 00:24:02 +00:00
{
return ;
}
2020-03-29 02:36:38 +00:00
if ( str_comp ( pVersion , GameServer ( ) - > NetVersion ( ) ) ! = 0 & & str_comp ( pVersion , " 0.7 802f1be60a05665f " ) ! = 0 )
2011-03-15 08:58:57 +00:00
{
// wrong version
char aReason [ 256 ] ;
str_format ( aReason , sizeof ( aReason ) , " Wrong version. Server is running '%s' and client '%s' " , GameServer ( ) - > NetVersion ( ) , pVersion ) ;
m_NetServer . Drop ( ClientID , aReason ) ;
return ;
}
2011-04-13 18:37:12 +00:00
2011-03-15 08:58:57 +00:00
const char * pPassword = Unpacker . GetString ( CUnpacker : : SANITIZE_CC ) ;
2023-08-07 18:27:22 +00:00
if ( Unpacker . Error ( ) )
2016-01-27 00:24:02 +00:00
{
return ;
}
2021-12-14 23:28:51 +00:00
if ( Config ( ) - > m_Password [ 0 ] ! = 0 & & str_comp ( Config ( ) - > m_Password , pPassword ) ! = 0 )
2011-03-15 08:58:57 +00:00
{
// wrong password
m_NetServer . Drop ( ClientID , " Wrong password " ) ;
return ;
}
2011-04-09 06:41:31 +00:00
// reserved slot
2023-05-24 14:00:39 +00:00
if ( ClientID > = Config ( ) - > m_SvMaxClients - Config ( ) - > m_SvReservedSlots & & ! CheckReservedSlotAuth ( ClientID , pPassword ) )
2011-04-09 06:41:31 +00:00
{
m_NetServer . Drop ( ClientID , " This server is full " ) ;
return ;
}
2011-04-13 23:27:49 +00:00
2015-01-27 22:13:47 +00:00
m_aClients [ ClientID ] . m_State = CClient : : STATE_CONNECTING ;
2017-07-24 19:43:55 +00:00
SendRconType ( ClientID , m_AuthManager . NumNonDefaultKeys ( ) > 0 ) ;
2019-06-03 19:52:14 +00:00
SendCapabilities ( ClientID ) ;
2015-01-27 22:13:47 +00:00
SendMap ( ClientID ) ;
2010-10-11 01:50:43 +00:00
}
2010-05-29 07:25:38 +00:00
}
2011-03-15 08:58:57 +00:00
else if ( Msg = = NETMSG_REQUEST_MAP_DATA )
2010-05-29 07:25:38 +00:00
{
2020-09-26 19:41:58 +00:00
if ( ( pPacket - > m_Flags & NET_CHUNKFLAG_VITAL ) = = 0 | | m_aClients [ ClientID ] . m_State < CClient : : STATE_CONNECTING )
2015-03-19 08:57:47 +00:00
return ;
2013-02-08 12:12:50 +00:00
2020-03-29 02:36:38 +00:00
if ( m_aClients [ ClientID ] . m_Sixup )
{
2021-12-14 23:28:51 +00:00
for ( int i = 0 ; i < Config ( ) - > m_SvMapWindow ; i + + )
2020-09-04 13:45:49 +00:00
{
SendMapData ( ClientID , m_aClients [ ClientID ] . m_NextMapChunk + + ) ;
}
2020-03-29 02:36:38 +00:00
return ;
}
2011-03-15 08:58:57 +00:00
int Chunk = Unpacker . GetInt ( ) ;
2023-08-07 18:27:22 +00:00
if ( Unpacker . Error ( ) )
{
return ;
}
2021-12-14 23:28:51 +00:00
if ( Chunk ! = m_aClients [ ClientID ] . m_NextMapChunk | | ! Config ( ) - > m_SvFastDownload )
2012-04-13 23:30:18 +00:00
{
2016-11-18 14:00:42 +00:00
SendMapData ( ClientID , Chunk ) ;
2011-03-15 08:58:57 +00:00
return ;
}
2011-04-13 18:37:12 +00:00
2016-11-18 14:00:42 +00:00
if ( Chunk = = 0 )
2011-03-15 08:58:57 +00:00
{
2021-12-14 23:28:51 +00:00
for ( int i = 0 ; i < Config ( ) - > m_SvMapWindow ; i + + )
2016-11-18 14:00:42 +00:00
{
SendMapData ( ClientID , i ) ;
}
2010-07-29 19:55:33 +00:00
}
2021-12-14 23:28:51 +00:00
SendMapData ( ClientID , Config ( ) - > m_SvMapWindow + m_aClients [ ClientID ] . m_NextMapChunk ) ;
2016-11-18 14:00:42 +00:00
m_aClients [ ClientID ] . m_NextMapChunk + + ;
2011-03-15 08:58:57 +00:00
}
else if ( Msg = = NETMSG_READY )
{
2021-02-07 15:03:31 +00:00
if ( ( pPacket - > m_Flags & NET_CHUNKFLAG_VITAL ) ! = 0 & & ( m_aClients [ ClientID ] . m_State = = CClient : : STATE_CONNECTING ) )
2010-05-29 07:25:38 +00:00
{
2011-03-28 18:11:28 +00:00
char aAddrStr [ NETADDR_MAXSTRSIZE ] ;
2011-12-29 22:36:53 +00:00
net_addr_str ( m_NetServer . ClientAddr ( ClientID ) , aAddrStr , sizeof ( aAddrStr ) , true ) ;
2011-04-13 18:37:12 +00:00
2011-03-15 08:58:57 +00:00
char aBuf [ 256 ] ;
2020-09-26 19:41:58 +00:00
str_format ( aBuf , sizeof ( aBuf ) , " player is ready. ClientID=%d addr=<{%s}> secure=%s " , ClientID , aAddrStr , m_NetServer . HasSecurityToken ( ClientID ) ? " yes " : " no " ) ;
2011-03-15 08:58:57 +00:00
Console ( ) - > Print ( IConsole : : OUTPUT_LEVEL_ADDINFO , " server " , aBuf ) ;
2021-02-07 15:03:31 +00:00
void * pPersistentData = 0 ;
if ( m_aClients [ ClientID ] . m_HasPersistentData )
{
pPersistentData = m_aClients [ ClientID ] . m_pPersistentData ;
m_aClients [ ClientID ] . m_HasPersistentData = false ;
}
2011-03-15 08:58:57 +00:00
m_aClients [ ClientID ] . m_State = CClient : : STATE_READY ;
2021-02-07 15:03:31 +00:00
GameServer ( ) - > OnClientConnected ( ClientID , pPersistentData ) ;
2010-05-29 07:25:38 +00:00
}
2015-08-23 15:01:01 +00:00
SendConnectionReady ( ClientID ) ;
2011-03-15 08:58:57 +00:00
}
else if ( Msg = = NETMSG_ENTERGAME )
{
2020-09-26 19:41:58 +00:00
if ( ( pPacket - > m_Flags & NET_CHUNKFLAG_VITAL ) ! = 0 & & m_aClients [ ClientID ] . m_State = = CClient : : STATE_READY & & GameServer ( ) - > IsClientReady ( ClientID ) )
2010-05-29 07:25:38 +00:00
{
2011-03-28 18:11:28 +00:00
char aAddrStr [ NETADDR_MAXSTRSIZE ] ;
2011-12-29 22:36:53 +00:00
net_addr_str ( m_NetServer . ClientAddr ( ClientID ) , aAddrStr , sizeof ( aAddrStr ) , true ) ;
2011-04-13 18:37:12 +00:00
2011-03-15 08:58:57 +00:00
char aBuf [ 256 ] ;
2020-06-23 15:30:57 +00:00
str_format ( aBuf , sizeof ( aBuf ) , " player has entered the game. ClientID=%d addr=<{%s}> sixup=%d " , ClientID , aAddrStr , IsSixup ( ClientID ) ) ;
2011-03-15 08:58:57 +00:00
Console ( ) - > Print ( IConsole : : OUTPUT_LEVEL_STANDARD , " server " , aBuf ) ;
m_aClients [ ClientID ] . m_State = CClient : : STATE_INGAME ;
2022-09-18 11:00:24 +00:00
if ( ! IsSixup ( ClientID ) )
{
SendServerInfo ( m_NetServer . ClientAddr ( ClientID ) , - 1 , SERVERINFO_EXTENDED , false ) ;
}
else
2020-06-16 12:41:30 +00:00
{
2023-08-22 11:49:18 +00:00
CMsgPacker Msgp ( protocol7 : : NETMSG_SERVERINFO , true , true ) ;
2022-03-20 11:57:50 +00:00
GetServerInfoSixup ( & Msgp , - 1 , false ) ;
SendMsg ( & Msgp , MSGFLAG_VITAL | MSGFLAG_FLUSH , ClientID ) ;
2020-06-16 12:41:30 +00:00
}
2011-03-15 08:58:57 +00:00
GameServer ( ) - > OnClientEnter ( ClientID ) ;
2010-09-06 02:59:02 +00:00
}
2011-03-15 08:58:57 +00:00
}
else if ( Msg = = NETMSG_INPUT )
{
2023-08-07 18:27:22 +00:00
const int LastAckedSnapshot = Unpacker . GetInt ( ) ;
2011-03-15 08:58:57 +00:00
int IntendedTick = Unpacker . GetInt ( ) ;
int Size = Unpacker . GetInt ( ) ;
2023-01-03 13:07:57 +00:00
if ( Unpacker . Error ( ) | | Size / 4 > MAX_INPUT_SIZE | | IntendedTick < MIN_TICK | | IntendedTick > = MAX_TICK )
2023-08-07 18:27:22 +00:00
{
2011-03-15 08:58:57 +00:00
return ;
2023-08-07 18:27:22 +00:00
}
2010-08-22 16:06:20 +00:00
2023-08-07 18:27:22 +00:00
m_aClients [ ClientID ] . m_LastAckedSnapshot = LastAckedSnapshot ;
2011-03-15 08:58:57 +00:00
if ( m_aClients [ ClientID ] . m_LastAckedSnapshot > 0 )
m_aClients [ ClientID ] . m_SnapRate = CClient : : SNAPRATE_FULL ;
2011-04-13 18:37:12 +00:00
2023-01-03 13:07:57 +00:00
int64_t TagTime ;
2022-10-09 15:57:43 +00:00
if ( m_aClients [ ClientID ] . m_Snapshots . Get ( m_aClients [ ClientID ] . m_LastAckedSnapshot , & TagTime , nullptr , nullptr ) > = 0 )
2020-09-26 19:41:58 +00:00
m_aClients [ ClientID ] . m_Latency = ( int ) ( ( ( time_get ( ) - TagTime ) * 1000 ) / time_freq ( ) ) ;
2010-08-22 16:06:20 +00:00
2011-03-15 08:58:57 +00:00
// add message to report the input timing
// skip packets that are old
if ( IntendedTick > m_aClients [ ClientID ] . m_LastInputTick )
{
2023-01-03 13:07:57 +00:00
const int TimeLeft = ( TickStartTime ( IntendedTick ) - time_get ( ) ) / ( time_freq ( ) / 1000 ) ;
2011-04-13 18:37:12 +00:00
2022-03-20 11:57:50 +00:00
CMsgPacker Msgp ( NETMSG_INPUTTIMING , true ) ;
Msgp . AddInt ( IntendedTick ) ;
Msgp . AddInt ( TimeLeft ) ;
SendMsg ( & Msgp , 0 , ClientID ) ;
2011-03-15 08:58:57 +00:00
}
2010-09-06 02:59:02 +00:00
2011-03-15 08:58:57 +00:00
m_aClients [ ClientID ] . m_LastInputTick = IntendedTick ;
2010-10-10 13:36:58 +00:00
2023-01-03 13:07:57 +00:00
CClient : : CInput * pInput = & m_aClients [ ClientID ] . m_aInputs [ m_aClients [ ClientID ] . m_CurrentInput ] ;
2011-04-13 18:37:12 +00:00
2011-03-15 08:58:57 +00:00
if ( IntendedTick < = Tick ( ) )
2020-09-26 19:41:58 +00:00
IntendedTick = Tick ( ) + 1 ;
2010-10-10 13:36:58 +00:00
2011-03-15 08:58:57 +00:00
pInput - > m_GameTick = IntendedTick ;
2011-04-13 18:37:12 +00:00
2020-09-26 19:41:58 +00:00
for ( int i = 0 ; i < Size / 4 ; i + + )
2023-08-07 18:27:22 +00:00
{
2011-03-15 08:58:57 +00:00
pInput - > m_aData [ i ] = Unpacker . GetInt ( ) ;
2023-08-07 18:27:22 +00:00
}
if ( Unpacker . Error ( ) )
{
return ;
}
2011-04-13 18:37:12 +00:00
2022-06-30 21:52:30 +00:00
GameServer ( ) - > OnClientPrepareInput ( ClientID , pInput - > m_aData ) ;
2020-09-26 19:41:58 +00:00
mem_copy ( m_aClients [ ClientID ] . m_LatestInput . m_aData , pInput - > m_aData , MAX_INPUT_SIZE * sizeof ( int ) ) ;
2011-04-13 18:37:12 +00:00
2011-03-15 08:58:57 +00:00
m_aClients [ ClientID ] . m_CurrentInput + + ;
m_aClients [ ClientID ] . m_CurrentInput % = 200 ;
2011-04-13 18:37:12 +00:00
2011-03-15 08:58:57 +00:00
// call the mod with the fresh input data
if ( m_aClients [ ClientID ] . m_State = = CClient : : STATE_INGAME )
GameServer ( ) - > OnClientDirectInput ( ClientID , m_aClients [ ClientID ] . m_LatestInput . m_aData ) ;
}
else if ( Msg = = NETMSG_RCON_CMD )
{
const char * pCmd = Unpacker . GetString ( ) ;
2023-08-07 18:27:22 +00:00
if ( Unpacker . Error ( ) )
2016-01-27 00:24:02 +00:00
{
return ;
}
2023-08-07 18:27:22 +00:00
if ( ! str_comp ( pCmd , " crashmeplx " ) )
2013-12-31 05:13:57 +00:00
{
2020-05-22 15:58:41 +00:00
int Version = m_aClients [ ClientID ] . m_DDNetVersion ;
2020-09-26 19:41:58 +00:00
if ( GameServer ( ) - > PlayerExists ( ClientID ) & & Version < VERSION_DDNET_OLD )
2020-05-22 15:58:41 +00:00
{
m_aClients [ ClientID ] . m_DDNetVersion = VERSION_DDNET_OLD ;
}
}
2023-08-07 18:27:22 +00:00
else if ( ( pPacket - > m_Flags & NET_CHUNKFLAG_VITAL ) ! = 0 & & m_aClients [ ClientID ] . m_Authed )
2010-05-29 07:25:38 +00:00
{
2020-09-26 19:41:58 +00:00
if ( GameServer ( ) - > PlayerExists ( ClientID ) )
2014-08-22 11:54:13 +00:00
{
char aBuf [ 256 ] ;
str_format ( aBuf , sizeof ( aBuf ) , " ClientID=%d rcon='%s' " , ClientID , pCmd ) ;
2022-06-05 10:44:15 +00:00
Console ( ) - > Print ( IConsole : : OUTPUT_LEVEL_STANDARD , " server " , aBuf ) ;
2014-08-22 11:54:13 +00:00
m_RconClientID = ClientID ;
m_RconAuthLevel = m_aClients [ ClientID ] . m_Authed ;
2015-10-23 00:33:10 +00:00
Console ( ) - > SetAccessLevel ( m_aClients [ ClientID ] . m_Authed = = AUTHED_ADMIN ? IConsole : : ACCESS_LEVEL_ADMIN : m_aClients [ ClientID ] . m_Authed = = AUTHED_MOD ? IConsole : : ACCESS_LEVEL_MOD : m_aClients [ ClientID ] . m_Authed = = AUTHED_HELPER ? IConsole : : ACCESS_LEVEL_HELPER : IConsole : : ACCESS_LEVEL_USER ) ;
2022-04-22 23:04:48 +00:00
{
CRconClientLogger Logger ( this , ClientID ) ;
CLogScope Scope ( & Logger ) ;
Console ( ) - > ExecuteLineFlag ( pCmd , CFGFLAG_SERVER , ClientID ) ;
}
2014-08-22 11:54:13 +00:00
Console ( ) - > SetAccessLevel ( IConsole : : ACCESS_LEVEL_ADMIN ) ;
m_RconClientID = IServer : : RCON_CID_SERV ;
m_RconAuthLevel = AUTHED_ADMIN ;
}
2010-10-10 13:36:58 +00:00
}
2011-03-15 08:58:57 +00:00
}
else if ( Msg = = NETMSG_RCON_AUTH )
{
2023-08-07 18:27:22 +00:00
if ( ( pPacket - > m_Flags & NET_CHUNKFLAG_VITAL ) = = 0 )
{
return ;
}
2020-06-16 14:55:37 +00:00
const char * pName = " " ;
if ( ! IsSixup ( ClientID ) )
2023-08-07 18:27:22 +00:00
{
2020-06-16 14:55:37 +00:00
pName = Unpacker . GetString ( CUnpacker : : SANITIZE_CC ) ; // login name, now used
2023-08-07 18:27:22 +00:00
}
2017-03-02 15:16:29 +00:00
const char * pPw = Unpacker . GetString ( CUnpacker : : SANITIZE_CC ) ;
2023-08-07 18:27:22 +00:00
if ( Unpacker . Error ( ) )
2016-01-27 00:24:02 +00:00
{
return ;
}
2010-10-12 07:31:47 +00:00
2023-08-07 18:27:22 +00:00
int AuthLevel = - 1 ;
int KeySlot = - 1 ;
2015-10-26 23:33:26 +00:00
2023-08-07 18:27:22 +00:00
if ( ! pName [ 0 ] )
{
if ( m_AuthManager . CheckKey ( ( KeySlot = m_AuthManager . DefaultKey ( AUTHED_ADMIN ) ) , pPw ) )
AuthLevel = AUTHED_ADMIN ;
else if ( m_AuthManager . CheckKey ( ( KeySlot = m_AuthManager . DefaultKey ( AUTHED_MOD ) ) , pPw ) )
AuthLevel = AUTHED_MOD ;
else if ( m_AuthManager . CheckKey ( ( KeySlot = m_AuthManager . DefaultKey ( AUTHED_HELPER ) ) , pPw ) )
AuthLevel = AUTHED_HELPER ;
}
else
{
KeySlot = m_AuthManager . FindKey ( pName ) ;
if ( m_AuthManager . CheckKey ( KeySlot , pPw ) )
AuthLevel = m_AuthManager . KeyLevel ( KeySlot ) ;
}
2015-10-26 23:33:26 +00:00
2023-08-07 18:27:22 +00:00
if ( AuthLevel ! = - 1 )
{
if ( m_aClients [ ClientID ] . m_Authed ! = AuthLevel )
2011-07-05 19:54:10 +00:00
{
2023-08-07 18:27:22 +00:00
if ( ! IsSixup ( ClientID ) )
2014-08-22 11:54:13 +00:00
{
2023-08-07 18:27:22 +00:00
CMsgPacker Msgp ( NETMSG_RCON_AUTH_STATUS , true ) ;
Msgp . AddInt ( 1 ) ; //authed
Msgp . AddInt ( 1 ) ; //cmdlist
SendMsg ( & Msgp , MSGFLAG_VITAL , ClientID ) ;
}
else
{
CMsgPacker Msgp ( protocol7 : : NETMSG_RCON_AUTH_ON , true , true ) ;
SendMsg ( & Msgp , MSGFLAG_VITAL , ClientID ) ;
}
2015-10-26 23:33:26 +00:00
2023-08-07 18:27:22 +00:00
m_aClients [ ClientID ] . m_Authed = AuthLevel ; // Keeping m_Authed around is unwise...
m_aClients [ ClientID ] . m_AuthKey = KeySlot ;
int SendRconCmds = IsSixup ( ClientID ) ? true : Unpacker . GetInt ( ) ;
if ( ! Unpacker . Error ( ) & & SendRconCmds )
{
// AUTHED_ADMIN - AuthLevel gets the proper IConsole::ACCESS_LEVEL_<x>
m_aClients [ ClientID ] . m_pRconCmdToSend = Console ( ) - > FirstCommandInfo ( AUTHED_ADMIN - AuthLevel , CFGFLAG_SERVER ) ;
2023-12-23 14:35:16 +00:00
CMsgPacker MsgStart ( NETMSG_RCON_CMD_GROUP_START , true ) ;
SendMsg ( & MsgStart , MSGFLAG_VITAL , ClientID ) ;
if ( m_aClients [ ClientID ] . m_pRconCmdToSend = = nullptr )
{
CMsgPacker MsgEnd ( NETMSG_RCON_CMD_GROUP_END , true ) ;
SendMsg ( & MsgEnd , MSGFLAG_VITAL , ClientID ) ;
}
2023-08-07 18:27:22 +00:00
}
2014-08-22 11:54:13 +00:00
2023-08-07 18:27:22 +00:00
char aBuf [ 256 ] ;
const char * pIdent = m_AuthManager . KeyIdent ( KeySlot ) ;
switch ( AuthLevel )
{
case AUTHED_ADMIN :
{
SendRconLine ( ClientID , " Admin authentication successful. Full remote console access granted. " ) ;
str_format ( aBuf , sizeof ( aBuf ) , " ClientID=%d authed with key=%s (admin) " , ClientID , pIdent ) ;
break ;
2014-08-22 11:54:13 +00:00
}
2023-08-07 18:27:22 +00:00
case AUTHED_MOD :
2010-10-11 01:50:43 +00:00
{
2023-08-07 18:27:22 +00:00
SendRconLine ( ClientID , " Moderator authentication successful. Limited remote console access granted. " ) ;
str_format ( aBuf , sizeof ( aBuf ) , " ClientID=%d authed with key=%s (moderator) " , ClientID , pIdent ) ;
break ;
}
case AUTHED_HELPER :
{
SendRconLine ( ClientID , " Helper authentication successful. Limited remote console access granted. " ) ;
str_format ( aBuf , sizeof ( aBuf ) , " ClientID=%d authed with key=%s (helper) " , ClientID , pIdent ) ;
break ;
2010-10-11 01:50:43 +00:00
}
2023-08-07 18:27:22 +00:00
}
Console ( ) - > Print ( IConsole : : OUTPUT_LEVEL_STANDARD , " server " , aBuf ) ;
// DDRace
GameServer ( ) - > OnSetAuthed ( ClientID , AuthLevel ) ;
2010-05-29 07:25:38 +00:00
}
2023-08-07 18:27:22 +00:00
}
else if ( Config ( ) - > m_SvRconMaxTries )
{
m_aClients [ ClientID ] . m_AuthTries + + ;
char aBuf [ 128 ] ;
str_format ( aBuf , sizeof ( aBuf ) , " Wrong password %d/%d. " , m_aClients [ ClientID ] . m_AuthTries , Config ( ) - > m_SvRconMaxTries ) ;
SendRconLine ( ClientID , aBuf ) ;
if ( m_aClients [ ClientID ] . m_AuthTries > = Config ( ) - > m_SvRconMaxTries )
2010-05-29 07:25:38 +00:00
{
2023-08-07 18:27:22 +00:00
if ( ! Config ( ) - > m_SvRconBantime )
m_NetServer . Drop ( ClientID , " Too many remote console authentication tries " ) ;
else
m_ServerBan . BanAddr ( m_NetServer . ClientAddr ( ClientID ) , Config ( ) - > m_SvRconBantime * 60 , " Too many remote console authentication tries " ) ;
2011-01-19 14:39:04 +00:00
}
2011-08-13 00:11:06 +00:00
}
2023-08-07 18:27:22 +00:00
else
{
SendRconLine ( ClientID , " Wrong password. " ) ;
}
2010-05-29 07:25:38 +00:00
}
2011-03-15 08:58:57 +00:00
else if ( Msg = = NETMSG_PING )
{
2022-03-20 11:57:50 +00:00
CMsgPacker Msgp ( NETMSG_PING_REPLY , true ) ;
2024-02-22 10:18:12 +00:00
int Vital = ( pPacket - > m_Flags & NET_CHUNKFLAG_VITAL ) ! = 0 ? MSGFLAG_VITAL : 0 ;
SendMsg ( & Msgp , MSGFLAG_FLUSH | Vital , ClientID ) ;
2010-05-29 07:25:38 +00:00
}
2021-04-23 21:29:01 +00:00
else if ( Msg = = NETMSG_PINGEX )
{
CUuid * pID = ( CUuid * ) Unpacker . GetRaw ( sizeof ( * pID ) ) ;
if ( Unpacker . Error ( ) )
{
return ;
}
2022-03-20 11:57:50 +00:00
CMsgPacker Msgp ( NETMSG_PONGEX , true ) ;
Msgp . AddRaw ( pID , sizeof ( * pID ) ) ;
2024-02-22 10:18:12 +00:00
int Vital = ( pPacket - > m_Flags & NET_CHUNKFLAG_VITAL ) ! = 0 ? MSGFLAG_VITAL : 0 ;
SendMsg ( & Msgp , MSGFLAG_FLUSH | Vital , ClientID ) ;
2021-04-23 21:29:01 +00:00
}
2010-05-29 07:25:38 +00:00
else
{
2021-12-14 23:28:51 +00:00
if ( Config ( ) - > m_Debug )
2011-03-15 08:58:57 +00:00
{
2021-09-14 17:53:35 +00:00
constexpr int MaxDumpedDataSize = 32 ;
char aBuf [ MaxDumpedDataSize * 3 + 1 ] ;
str_hex ( aBuf , sizeof ( aBuf ) , pPacket - > m_pData , minimum ( pPacket - > m_DataSize , MaxDumpedDataSize ) ) ;
2011-03-15 08:58:57 +00:00
char aBufMsg [ 256 ] ;
str_format ( aBufMsg , sizeof ( aBufMsg ) , " strange message ClientID=%d msg=%d data_size=%d " , ClientID , Msg , pPacket - > m_DataSize ) ;
Console ( ) - > Print ( IConsole : : OUTPUT_LEVEL_DEBUG , " server " , aBufMsg ) ;
Console ( ) - > Print ( IConsole : : OUTPUT_LEVEL_DEBUG , " server " , aBuf ) ;
}
2010-05-29 07:25:38 +00:00
}
2010-09-06 02:59:02 +00:00
}
2023-08-07 18:27:22 +00:00
else if ( ( pPacket - > m_Flags & NET_CHUNKFLAG_VITAL ) ! = 0 & & m_aClients [ ClientID ] . m_State > = CClient : : STATE_READY )
2011-03-15 08:58:57 +00:00
{
// game message
2023-08-07 18:27:22 +00:00
GameServer ( ) - > OnMessage ( Msg , & Unpacker , ClientID ) ;
2011-03-15 08:58:57 +00:00
}
2010-05-29 07:25:38 +00:00
}
2011-04-13 18:37:12 +00:00
2020-06-16 12:41:30 +00:00
bool CServer : : RateLimitServerInfoConnless ( )
2016-01-22 15:02:40 +00:00
{
2019-06-11 16:12:41 +00:00
bool SendClients = true ;
2021-12-14 23:28:51 +00:00
if ( Config ( ) - > m_SvServerInfoPerSecond )
2016-01-22 15:02:40 +00:00
{
2021-12-14 23:28:51 +00:00
SendClients = m_ServerInfoNumRequests < = Config ( ) - > m_SvServerInfoPerSecond ;
2021-06-23 05:05:49 +00:00
const int64_t Now = Tick ( ) ;
2019-06-11 16:12:41 +00:00
if ( Now < = m_ServerInfoFirstRequest + TickSpeed ( ) )
{
m_ServerInfoNumRequests + + ;
}
else
{
m_ServerInfoNumRequests = 1 ;
m_ServerInfoFirstRequest = Now ;
}
2016-01-22 15:02:40 +00:00
}
2020-06-16 12:41:30 +00:00
return SendClients ;
}
void CServer : : SendServerInfoConnless ( const NETADDR * pAddr , int Token , int Type )
{
SendServerInfo ( pAddr , Token , Type , RateLimitServerInfoConnless ( ) ) ;
2016-01-22 15:02:40 +00:00
}
2019-11-04 00:06:09 +00:00
static inline int GetCacheIndex ( int Type , bool SendClient )
{
if ( Type = = SERVERINFO_INGAME )
Type = SERVERINFO_VANILLA ;
else if ( Type = = SERVERINFO_EXTENDED_MORE )
Type = SERVERINFO_EXTENDED ;
return Type * 2 + SendClient ;
}
2019-11-03 00:53:50 +00:00
CServer : : CCache : : CCache ( )
{
2023-06-28 20:14:51 +00:00
m_vCache . clear ( ) ;
2019-11-03 00:53:50 +00:00
}
CServer : : CCache : : ~ CCache ( )
{
Clear ( ) ;
}
2019-11-03 14:39:32 +00:00
CServer : : CCache : : CCacheChunk : : CCacheChunk ( const void * pData , int Size )
2019-11-02 23:33:30 +00:00
{
2022-05-10 18:34:46 +00:00
m_vData . assign ( ( const uint8_t * ) pData , ( const uint8_t * ) pData + Size ) ;
2019-11-03 14:39:32 +00:00
}
2019-11-02 23:33:30 +00:00
2019-11-03 14:39:32 +00:00
void CServer : : CCache : : AddChunk ( const void * pData , int Size )
2010-05-29 07:25:38 +00:00
{
2023-06-28 20:14:51 +00:00
m_vCache . emplace_back ( pData , Size ) ;
2019-11-02 23:33:30 +00:00
}
void CServer : : CCache : : Clear ( )
{
2023-06-28 20:14:51 +00:00
m_vCache . clear ( ) ;
2019-11-02 23:33:30 +00:00
}
void CServer : : CacheServerInfo ( CCache * pCache , int Type , bool SendClients )
2010-05-29 07:25:38 +00:00
{
2019-11-03 00:07:10 +00:00
pCache - > Clear ( ) ;
2017-03-29 10:56:13 +00:00
// One chance to improve the protocol!
2010-05-29 07:25:38 +00:00
CPacker p ;
char aBuf [ 128 ] ;
// count the players
2011-03-20 14:33:49 +00:00
int PlayerCount = 0 , ClientCount = 0 ;
2010-05-29 07:25:38 +00:00
for ( int i = 0 ; i < MAX_CLIENTS ; i + + )
{
2023-11-22 16:57:42 +00:00
if ( m_aClients [ i ] . IncludedInServerInfo ( ) )
2011-03-20 14:33:49 +00:00
{
if ( GameServer ( ) - > IsClientPlayer ( i ) )
PlayerCount + + ;
2011-04-13 18:37:12 +00:00
2011-03-20 14:33:49 +00:00
ClientCount + + ;
}
2010-05-29 07:25:38 +00:00
}
2011-04-13 18:37:12 +00:00
2010-05-29 07:25:38 +00:00
p . Reset ( ) ;
2020-09-26 19:41:58 +00:00
# define ADD_RAW(p, x) (p).AddRaw(x, sizeof(x))
# define ADD_INT(p, x) \
do \
{ \
2023-08-20 11:03:01 +00:00
str_from_int ( x , aBuf ) ; \
2020-09-26 19:41:58 +00:00
( p ) . AddString ( aBuf , 0 ) ; \
} while ( 0 )
2014-01-01 13:58:39 +00:00
2010-05-29 07:25:38 +00:00
p . AddString ( GameServer ( ) - > Version ( ) , 32 ) ;
2017-03-29 10:56:13 +00:00
if ( Type ! = SERVERINFO_VANILLA )
2013-12-31 05:13:57 +00:00
{
2021-12-14 23:28:51 +00:00
p . AddString ( Config ( ) - > m_SvName , 256 ) ;
2013-12-31 05:13:57 +00:00
}
else
{
2017-03-29 10:56:13 +00:00
if ( m_NetServer . MaxClients ( ) < = VANILLA_MAX_CLIENTS )
{
2021-12-14 23:28:51 +00:00
p . AddString ( Config ( ) - > m_SvName , 64 ) ;
2017-03-29 10:56:13 +00:00
}
2013-12-31 05:13:57 +00:00
else
{
2022-09-18 11:21:04 +00:00
const int MaxClients = maximum ( ClientCount , m_NetServer . MaxClients ( ) - Config ( ) - > m_SvReservedSlots ) ;
str_format ( aBuf , sizeof ( aBuf ) , " %s [%d/%d] " , Config ( ) - > m_SvName , ClientCount , MaxClients ) ;
2013-12-31 05:13:57 +00:00
p . AddString ( aBuf , 64 ) ;
}
}
2010-10-10 13:36:58 +00:00
p . AddString ( GetMapName ( ) , 32 ) ;
2010-05-29 07:25:38 +00:00
2017-03-29 10:56:13 +00:00
if ( Type = = SERVERINFO_EXTENDED )
{
2022-02-17 18:50:02 +00:00
ADD_INT ( p , m_aCurrentMapCrc [ MAP_TYPE_SIX ] ) ;
ADD_INT ( p , m_aCurrentMapSize [ MAP_TYPE_SIX ] ) ;
2017-03-29 10:56:13 +00:00
}
2010-05-29 07:25:38 +00:00
// gametype
2011-02-16 11:31:47 +00:00
p . AddString ( GameServer ( ) - > GameType ( ) , 16 ) ;
2010-05-29 07:25:38 +00:00
// flags
2021-12-14 23:28:51 +00:00
ADD_INT ( p , Config ( ) - > m_Password [ 0 ] ? SERVER_FLAG_PASSWORD : 0 ) ;
2010-05-29 07:25:38 +00:00
2013-12-31 05:13:57 +00:00
int MaxClients = m_NetServer . MaxClients ( ) ;
2021-03-07 10:11:52 +00:00
// How many clients the used serverinfo protocol supports, has to be tracked
// separately to make sure we don't subtract the reserved slots from it
int MaxClientsProtocol = MAX_CLIENTS ;
2017-03-29 10:56:13 +00:00
if ( Type = = SERVERINFO_VANILLA | | Type = = SERVERINFO_INGAME )
2013-12-31 05:13:57 +00:00
{
2017-03-29 10:56:13 +00:00
if ( ClientCount > = VANILLA_MAX_CLIENTS )
2013-12-31 05:13:57 +00:00
{
2017-03-29 10:56:13 +00:00
if ( ClientCount < MaxClients )
2013-12-31 05:13:57 +00:00
ClientCount = VANILLA_MAX_CLIENTS - 1 ;
else
ClientCount = VANILLA_MAX_CLIENTS ;
}
2021-03-07 10:11:52 +00:00
MaxClientsProtocol = VANILLA_MAX_CLIENTS ;
2017-03-29 10:56:13 +00:00
if ( PlayerCount > ClientCount )
PlayerCount = ClientCount ;
2013-12-31 05:13:57 +00:00
}
2017-03-29 10:56:13 +00:00
ADD_INT ( p , PlayerCount ) ; // num players
2021-12-14 23:28:51 +00:00
ADD_INT ( p , minimum ( MaxClientsProtocol , maximum ( MaxClients - maximum ( Config ( ) - > m_SvSpectatorSlots , Config ( ) - > m_SvReservedSlots ) , PlayerCount ) ) ) ; // max players
2017-03-29 10:56:13 +00:00
ADD_INT ( p , ClientCount ) ; // num clients
2021-12-14 23:28:51 +00:00
ADD_INT ( p , minimum ( MaxClientsProtocol , maximum ( MaxClients - Config ( ) - > m_SvReservedSlots , ClientCount ) ) ) ; // max clients
2017-03-29 10:56:13 +00:00
if ( Type = = SERVERINFO_EXTENDED )
p . AddString ( " " , 0 ) ; // extra info, reserved
const void * pPrefix = p . Data ( ) ;
int PrefixSize = p . Size ( ) ;
2020-10-27 17:57:14 +00:00
CPacker q ;
2019-11-03 14:39:32 +00:00
int ChunksStored = 0 ;
int PlayersStored = 0 ;
2017-03-29 10:56:13 +00:00
2020-09-26 19:41:58 +00:00
# define SAVE(size) \
do \
{ \
2020-10-27 17:57:14 +00:00
pCache - > AddChunk ( q . Data ( ) , size ) ; \
2020-09-26 19:41:58 +00:00
ChunksStored + + ; \
} while ( 0 )
2017-03-29 10:56:13 +00:00
2020-09-26 19:41:58 +00:00
# define RESET() \
do \
{ \
2020-10-27 17:57:14 +00:00
q . Reset ( ) ; \
q . AddRaw ( pPrefix , PrefixSize ) ; \
2020-09-26 19:41:58 +00:00
} while ( 0 )
2013-12-31 05:13:57 +00:00
2017-03-29 10:56:13 +00:00
RESET ( ) ;
2013-12-31 05:13:57 +00:00
2017-03-29 10:56:13 +00:00
if ( Type = = SERVERINFO_64_LEGACY )
2020-10-27 17:57:14 +00:00
q . AddInt ( PlayersStored ) ; // offset
2013-12-31 05:13:57 +00:00
2017-03-29 10:56:13 +00:00
if ( ! SendClients )
2016-01-22 15:02:40 +00:00
{
2020-10-27 17:57:14 +00:00
SAVE ( q . Size ( ) ) ;
2016-01-22 15:02:40 +00:00
return ;
}
2017-03-29 10:56:13 +00:00
if ( Type = = SERVERINFO_EXTENDED )
{
2019-11-02 23:33:30 +00:00
pPrefix = " " ;
PrefixSize = 0 ;
2017-03-29 10:56:13 +00:00
}
int Remaining ;
switch ( Type )
{
case SERVERINFO_EXTENDED : Remaining = - 1 ; break ;
case SERVERINFO_64_LEGACY : Remaining = 24 ; break ;
case SERVERINFO_VANILLA : Remaining = VANILLA_MAX_CLIENTS ; break ;
case SERVERINFO_INGAME : Remaining = VANILLA_MAX_CLIENTS ; break ;
default : dbg_assert ( 0 , " caught earlier, unreachable " ) ; return ;
}
// Use the following strategy for sending:
// For vanilla, send the first 16 players.
// For legacy 64p, send 24 players per packet.
// For extended, send as much players as possible.
2010-05-29 07:25:38 +00:00
2017-03-29 10:56:13 +00:00
for ( int i = 0 ; i < MAX_CLIENTS ; i + + )
2010-05-29 07:25:38 +00:00
{
2023-11-22 16:57:42 +00:00
if ( m_aClients [ i ] . IncludedInServerInfo ( ) )
2010-05-29 07:25:38 +00:00
{
2017-03-29 10:56:13 +00:00
if ( Remaining = = 0 )
{
if ( Type = = SERVERINFO_VANILLA | | Type = = SERVERINFO_INGAME )
break ;
// Otherwise we're SERVERINFO_64_LEGACY.
2020-10-27 17:57:14 +00:00
SAVE ( q . Size ( ) ) ;
2017-03-29 10:56:13 +00:00
RESET ( ) ;
2020-10-27 17:57:14 +00:00
q . AddInt ( PlayersStored ) ; // offset
2017-03-29 10:56:13 +00:00
Remaining = 24 ;
}
if ( Remaining > 0 )
{
Remaining - - ;
}
2020-10-27 17:57:14 +00:00
int PreviousSize = q . Size ( ) ;
2017-03-29 10:56:13 +00:00
2020-10-27 17:57:14 +00:00
q . AddString ( ClientName ( i ) , MAX_NAME_LENGTH ) ; // client name
q . AddString ( ClientClan ( i ) , MAX_CLAN_LENGTH ) ; // client clan
2013-12-31 05:13:57 +00:00
2020-10-27 17:57:14 +00:00
ADD_INT ( q , m_aClients [ i ] . m_Country ) ; // client country
2023-05-22 14:32:48 +00:00
int Score ;
if ( m_aClients [ i ] . m_Score . has_value ( ) )
{
Score = m_aClients [ i ] . m_Score . value ( ) ;
if ( Score = = 9999 )
Score = - 10000 ;
else if ( Score = = 0 ) // 0 time isn't displayed otherwise.
Score = - 1 ;
else
Score = - Score ;
}
else
{
Score = - 9999 ;
}
ADD_INT ( q , Score ) ; // client score
2020-10-27 17:57:14 +00:00
ADD_INT ( q , GameServer ( ) - > IsClientPlayer ( i ) ? 1 : 0 ) ; // is player?
2017-03-29 10:56:13 +00:00
if ( Type = = SERVERINFO_EXTENDED )
2020-10-27 17:57:14 +00:00
q . AddString ( " " , 0 ) ; // extra info, reserved
2013-12-31 05:13:57 +00:00
2017-03-29 10:56:13 +00:00
if ( Type = = SERVERINFO_EXTENDED )
{
2020-10-27 17:57:14 +00:00
if ( q . Size ( ) > = NET_MAX_PAYLOAD - 18 ) // 8 bytes for type, 10 bytes for the largest token
2017-03-29 10:56:13 +00:00
{
// Retry current player.
i - - ;
2019-11-02 23:33:30 +00:00
SAVE ( PreviousSize ) ;
2017-03-29 10:56:13 +00:00
RESET ( ) ;
2020-10-27 17:57:14 +00:00
ADD_INT ( q , ChunksStored ) ;
q . AddString ( " " , 0 ) ; // extra info, reserved
2017-03-29 10:56:13 +00:00
continue ;
}
}
2019-11-03 14:39:32 +00:00
PlayersStored + + ;
2010-05-29 07:25:38 +00:00
}
}
2011-04-13 18:37:12 +00:00
2020-10-27 17:57:14 +00:00
SAVE ( q . Size ( ) ) ;
2020-09-26 19:41:58 +00:00
# undef SAVE
# undef RESET
# undef ADD_RAW
# undef ADD_INT
2010-05-29 07:25:38 +00:00
}
2020-06-16 12:41:30 +00:00
void CServer : : CacheServerInfoSixup ( CCache * pCache , bool SendClients )
{
pCache - > Clear ( ) ;
CPacker Packer ;
Packer . Reset ( ) ;
// Could be moved to a separate function and cached
// count the players
int PlayerCount = 0 , ClientCount = 0 ;
for ( int i = 0 ; i < MAX_CLIENTS ; i + + )
{
2023-11-22 16:57:42 +00:00
if ( m_aClients [ i ] . IncludedInServerInfo ( ) )
2020-06-16 12:41:30 +00:00
{
if ( GameServer ( ) - > IsClientPlayer ( i ) )
PlayerCount + + ;
ClientCount + + ;
}
}
2020-06-17 07:32:28 +00:00
char aVersion [ 32 ] ;
2020-06-23 15:58:31 +00:00
str_format ( aVersion , sizeof ( aVersion ) , " 0.7↔%s " , GameServer ( ) - > Version ( ) ) ;
2020-06-17 07:32:28 +00:00
Packer . AddString ( aVersion , 32 ) ;
2021-12-14 23:28:51 +00:00
Packer . AddString ( Config ( ) - > m_SvName , 64 ) ;
Packer . AddString ( Config ( ) - > m_SvHostname , 128 ) ;
2020-06-16 12:41:30 +00:00
Packer . AddString ( GetMapName ( ) , 32 ) ;
// gametype
Packer . AddString ( GameServer ( ) - > GameType ( ) , 16 ) ;
// flags
int Flags = SERVER_FLAG_TIMESCORE ;
2021-12-14 23:28:51 +00:00
if ( Config ( ) - > m_Password [ 0 ] ) // password set
2020-06-16 12:41:30 +00:00
Flags | = SERVER_FLAG_PASSWORD ;
Packer . AddInt ( Flags ) ;
int MaxClients = m_NetServer . MaxClients ( ) ;
2021-12-14 23:28:51 +00:00
Packer . AddInt ( Config ( ) - > m_SvSkillLevel ) ; // server skill level
2020-06-16 12:41:30 +00:00
Packer . AddInt ( PlayerCount ) ; // num players
2021-12-14 23:28:51 +00:00
Packer . AddInt ( maximum ( MaxClients - maximum ( Config ( ) - > m_SvSpectatorSlots , Config ( ) - > m_SvReservedSlots ) , PlayerCount ) ) ; // max players
2020-06-16 12:41:30 +00:00
Packer . AddInt ( ClientCount ) ; // num clients
2021-12-14 23:28:51 +00:00
Packer . AddInt ( maximum ( MaxClients - Config ( ) - > m_SvReservedSlots , ClientCount ) ) ; // max clients
2020-06-16 12:41:30 +00:00
if ( SendClients )
{
for ( int i = 0 ; i < MAX_CLIENTS ; i + + )
{
2023-11-22 16:57:42 +00:00
if ( m_aClients [ i ] . IncludedInServerInfo ( ) )
2020-06-16 12:41:30 +00:00
{
Packer . AddString ( ClientName ( i ) , MAX_NAME_LENGTH ) ; // client name
Packer . AddString ( ClientClan ( i ) , MAX_CLAN_LENGTH ) ; // client clan
Packer . AddInt ( m_aClients [ i ] . m_Country ) ; // client country
2023-05-22 11:00:06 +00:00
Packer . AddInt ( m_aClients [ i ] . m_Score . value_or ( - 1 ) ) ; // client score
2020-09-26 19:41:58 +00:00
Packer . AddInt ( GameServer ( ) - > IsClientPlayer ( i ) ? 0 : 1 ) ; // flag spectator=1, bot=2 (player=0)
2020-06-16 12:41:30 +00:00
}
}
}
pCache - > AddChunk ( Packer . Data ( ) , Packer . Size ( ) ) ;
}
2019-11-02 23:33:30 +00:00
void CServer : : SendServerInfo ( const NETADDR * pAddr , int Token , int Type , bool SendClients )
2010-05-29 07:25:38 +00:00
{
2019-11-02 23:33:30 +00:00
CPacker p ;
char aBuf [ 128 ] ;
p . Reset ( ) ;
2020-10-27 17:57:14 +00:00
CCache * pCache = & m_aServerInfoCache [ GetCacheIndex ( Type , SendClients ) ] ;
2019-11-02 23:33:30 +00:00
2020-09-26 19:41:58 +00:00
# define ADD_RAW(p, x) (p).AddRaw(x, sizeof(x))
# define ADD_INT(p, x) \
do \
{ \
2023-08-20 11:03:01 +00:00
str_from_int ( x , aBuf ) ; \
2020-09-26 19:41:58 +00:00
( p ) . AddString ( aBuf , 0 ) ; \
} while ( 0 )
2019-11-02 23:33:30 +00:00
CNetChunk Packet ;
Packet . m_ClientID = - 1 ;
Packet . m_Address = * pAddr ;
Packet . m_Flags = NETSENDFLAG_CONNLESS ;
2023-06-28 20:14:51 +00:00
for ( const auto & Chunk : pCache - > m_vCache )
2019-11-02 23:33:30 +00:00
{
p . Reset ( ) ;
if ( Type = = SERVERINFO_EXTENDED )
{
2023-06-28 20:14:51 +00:00
if ( & Chunk = = & pCache - > m_vCache . front ( ) )
2020-07-12 22:14:54 +00:00
p . AddRaw ( SERVERBROWSE_INFO_EXTENDED , sizeof ( SERVERBROWSE_INFO_EXTENDED ) ) ;
else
p . AddRaw ( SERVERBROWSE_INFO_EXTENDED_MORE , sizeof ( SERVERBROWSE_INFO_EXTENDED_MORE ) ) ;
2019-11-02 23:33:30 +00:00
ADD_INT ( p , Token ) ;
}
else if ( Type = = SERVERINFO_64_LEGACY )
2014-01-14 19:40:29 +00:00
{
2020-07-12 22:14:54 +00:00
ADD_RAW ( p , SERVERBROWSE_INFO_64_LEGACY ) ;
ADD_INT ( p , Token ) ;
}
2020-09-26 19:41:58 +00:00
else if ( Type = = SERVERINFO_VANILLA | | Type = = SERVERINFO_INGAME )
2020-07-12 22:14:54 +00:00
{
ADD_RAW ( p , SERVERBROWSE_INFO ) ;
ADD_INT ( p , Token ) ;
}
else
{
dbg_assert ( false , " unknown serverinfo type " ) ;
2014-01-14 19:40:29 +00:00
}
2019-11-02 23:33:30 +00:00
2022-05-10 18:34:46 +00:00
p . AddRaw ( Chunk . m_vData . data ( ) , Chunk . m_vData . size ( ) ) ;
2019-11-02 23:33:30 +00:00
Packet . m_pData = p . Data ( ) ;
Packet . m_DataSize = p . Size ( ) ;
m_NetServer . Send ( & Packet ) ;
2010-05-29 07:25:38 +00:00
}
}
2020-06-16 12:41:30 +00:00
void CServer : : GetServerInfoSixup ( CPacker * pPacker , int Token , bool SendClients )
{
if ( Token ! = - 1 )
{
pPacker - > Reset ( ) ;
pPacker - > AddRaw ( SERVERBROWSE_INFO , sizeof ( SERVERBROWSE_INFO ) ) ;
pPacker - > AddInt ( Token ) ;
}
SendClients = SendClients & & Token ! = - 1 ;
2023-06-28 20:14:51 +00:00
CCache : : CCacheChunk & FirstChunk = m_aSixupServerInfoCache [ SendClients ] . m_vCache . front ( ) ;
2022-05-10 18:34:46 +00:00
pPacker - > AddRaw ( FirstChunk . m_vData . data ( ) , FirstChunk . m_vData . size ( ) ) ;
2020-06-16 12:41:30 +00:00
}
2023-01-11 00:35:50 +00:00
void CServer : : FillAntibot ( CAntibotRoundData * pData )
{
for ( int i = 0 ; i < MAX_CLIENTS ; i + + )
{
CAntibotPlayerData * pPlayer = & pData - > m_aPlayers [ i ] ;
net_addr_str ( m_NetServer . ClientAddr ( i ) , pPlayer - > m_aAddress , sizeof ( pPlayer - > m_aAddress ) , true ) ;
}
}
2019-11-03 00:07:10 +00:00
void CServer : : ExpireServerInfo ( )
2010-05-29 07:25:38 +00:00
{
2019-11-03 00:07:10 +00:00
m_ServerInfoNeedsUpdate = true ;
}
Add HTTP masterserver registering and HTTP masterserver
Registering
-----------
The idea is that game servers push their server info to the
masterservers every 15 seconds or when the server info changes, but not
more than once per second.
The game servers do not support the old registering protocol anymore,
the backward compatibility is handled by the masterserver.
The register call is a HTTP POST to a URL like
`https://master1.ddnet.tw/ddnet/15/register` and looks like this:
```json
POST /ddnet/15/register HTTP/1.1
Address: tw-0.6+udp://connecting-address.invalid:8303
Secret: 81fa3955-6f83-4290-818d-31c0906b1118
Challenge-Secret: 81fa3955-6f83-4290-818d-31c0906b1118:tw0.6/ipv6
Info-Serial: 0
{
"max_clients": 64,
"max_players": 64,
"passworded": false,
"game_type": "TestDDraceNetwork",
"name": "My DDNet server",
"map": {
"name": "dm1",
"sha256": "0b0c481d77519c32fbe85624ef16ec0fa9991aec7367ad538bd280f28d8c26cf",
"size": 5805
},
"version": "0.6.4, 16.0.3",
"clients": []
}
```
The `Address` header declares that the server wants to register itself as
a `tw-0.6+udp` server, i.e. a server speaking a Teeworlds-0.6-compatible
protocol.
The free-form `Secret` header is used as a server identity, the server
list will be deduplicated via this secret.
The free-form `Challenge-Secret` is sent back via UDP for a port forward
check. This might have security implications as the masterserver can be
asked to send a UDP packet containing some user-controlled bytes. This
is somewhat mitigated by the fact that it can only go to an
attacker-controlled IP address.
The `Info-Serial` header is an integer field that should increase each
time the server info (in the body) changes. The masterserver uses that
field to ensure that it doesn't use old server infos.
The body is a free-form JSON object set by the game server. It should
contain certain keys in the correct form to be accepted by clients. The
body is optional if the masterserver already confirmed the reception of
the info with the given `Info-Serial`.
Not shown in this payload is the `Connless-Token` header that is used
for Teeworlds 0.7 style communication.
Also not shown is the `Challenge-Token` that should be included once the
server receives the challenge token via UDP.
The masterserver responds with a `200 OK` with a body like this:
```
{"status":"success"}
```
The `status` field can be `success` if the server was successfully
registered on the masterserver, `need_challenge` if the masterserver
wants the correct `Challenge-Token` header before the register process
is successful, `need_info` if the server sent an empty body but the
masterserver doesn't actually know the server info.
It can also be `error` if the request was malformed, only in this case
an HTTP status code except `200 OK` is sent.
Synchronization
---------------
The masterserver keeps state and outputs JSON files every second.
```json
{
"servers": [
{
"addresses": [
"tw-0.6+udp://127.0.0.1:8303",
"tw-0.6+udp://[::1]:8303"
],
"info_serial": 0,
"info": {
"max_clients": 64,
"max_players": 64,
"passworded": false,
"game_type": "TestDDraceNetwork",
"name": "My DDNet server",
"map": {
"name": "dm1",
"sha256": "0b0c481d77519c32fbe85624ef16ec0fa9991aec7367ad538bd280f28d8c26cf",
"size": 5805
},
"version": "0.6.4, 16.0.3",
"clients": []
}
}
]
}
```
`servers.json` (or configured by `--out`) is a server list that is
compatible with DDNet 15.5+ clients. It is a JSON object containing a
single key `servers` with a list of game servers. Each game server is
represented by a JSON object with an `addresses` key containing a list
of all known addresses of the server and an `info` key containing the
free-form server info sent by the game server. The free-form `info` JSON
object re-encoded by the master server and thus canonicalized and
stripped of any whitespace characters outside strings.
```json
{
"kind": "mastersrv",
"now": 1816002,
"secrets": {
"tw-0.6+udp://127.0.0.1:8303": {
"ping_time": 1811999,
"secret": "42d8f991-f2fa-46e5-a9ae-ebcc93846feb"
},
"tw-0.6+udp://[::1]:8303": {
"ping_time": 1811999,
"secret": "42d8f991-f2fa-46e5-a9ae-ebcc93846feb"
}
},
"servers": {
"42d8f991-f2fa-46e5-a9ae-ebcc93846feb": {
"info_serial": 0,
"info": {
"max_clients": 64,
"max_players": 64,
"passworded": false,
"game_type": "TestDDraceNetwork",
"name": "My DDNet server",
"map": {
"name": "dm1",
"sha256": "0b0c481d77519c32fbe85624ef16ec0fa9991aec7367ad538bd280f28d8c26cf",
"size": 5805
},
"version": "0.6.4, 16.0.3",
"clients": []
}
}
}
}
```
`--write-dump` outputs a JSON file compatible with `--read-dump-dir`,
this can be used to synchronize servers across different masterservers.
`--read-dump-dir` is also used to ingest servers from the backward
compatibility layer that pings each server for their server info using
the old protocol.
The `kind` field describe that this is `mastersrv` output and not from a
`backcompat`. This is used for prioritizing `mastersrv` information over
`backcompat` information.
The `now` field contains an integer describing the current time in
milliseconds relative an unspecified epoch that is fixed for each JSON
file. This is done instead of using the current time as the epoch for
better compression of non-changing data.
`secrets` is a map from each server address and to a JSON object
containing the last ping time (`ping_time`) in milliseconds relative to
the same epoch as before, and the server secret (`secret`) that is used
to unify server infos from different addresses of the same logical
server.
`servers` is a map from the aforementioned `secret`s to the
corresponding `info_serial` and `info`.
```json
[
"tw-0.6+udp://127.0.0.1:8303",
"tw-0.6+udp://[::1]:8303"
]
```
`--write-addresses` outputs a JSON file containing all addresses
corresponding to servers that are registered to HTTP masterservers. It
does not contain the servers that are obtained via backward
compatibility measures.
This file can be used by an old-style masterserver to also list
new-style servers without the game servers having to register there.
An implementation of this can be found at
https://github.com/heinrich5991/teeworlds/tree/mastersrv_6_backcompat
for Teeworlds 0.5/0.6 masterservers and at
https://github.com/heinrich5991/teeworlds/tree/mastersrv_7_backcompat
for Teeworlds 0.7 masterservers.
All these JSON files can be sent over the network in an efficient way
using https://github.com/heinrich5991/twmaster-collect. It establishes a
zstd-compressed TCP connection authenticated by a string token that is
sent in plain-text. It watches the specified file and transmits it every
time it changes. Due to the zstd-compression, the data sent over the
network is similar to the size of a diff.
Implementation
--------------
The masterserver implementation was done in Rust.
The current gameserver register implementation doesn't support more than
one masterserver for registering.
2022-05-19 20:03:17 +00:00
void CServer : : UpdateRegisterServerInfo ( )
{
// count the players
int PlayerCount = 0 , ClientCount = 0 ;
for ( int i = 0 ; i < MAX_CLIENTS ; i + + )
{
2023-11-22 16:57:42 +00:00
if ( m_aClients [ i ] . IncludedInServerInfo ( ) )
Add HTTP masterserver registering and HTTP masterserver
Registering
-----------
The idea is that game servers push their server info to the
masterservers every 15 seconds or when the server info changes, but not
more than once per second.
The game servers do not support the old registering protocol anymore,
the backward compatibility is handled by the masterserver.
The register call is a HTTP POST to a URL like
`https://master1.ddnet.tw/ddnet/15/register` and looks like this:
```json
POST /ddnet/15/register HTTP/1.1
Address: tw-0.6+udp://connecting-address.invalid:8303
Secret: 81fa3955-6f83-4290-818d-31c0906b1118
Challenge-Secret: 81fa3955-6f83-4290-818d-31c0906b1118:tw0.6/ipv6
Info-Serial: 0
{
"max_clients": 64,
"max_players": 64,
"passworded": false,
"game_type": "TestDDraceNetwork",
"name": "My DDNet server",
"map": {
"name": "dm1",
"sha256": "0b0c481d77519c32fbe85624ef16ec0fa9991aec7367ad538bd280f28d8c26cf",
"size": 5805
},
"version": "0.6.4, 16.0.3",
"clients": []
}
```
The `Address` header declares that the server wants to register itself as
a `tw-0.6+udp` server, i.e. a server speaking a Teeworlds-0.6-compatible
protocol.
The free-form `Secret` header is used as a server identity, the server
list will be deduplicated via this secret.
The free-form `Challenge-Secret` is sent back via UDP for a port forward
check. This might have security implications as the masterserver can be
asked to send a UDP packet containing some user-controlled bytes. This
is somewhat mitigated by the fact that it can only go to an
attacker-controlled IP address.
The `Info-Serial` header is an integer field that should increase each
time the server info (in the body) changes. The masterserver uses that
field to ensure that it doesn't use old server infos.
The body is a free-form JSON object set by the game server. It should
contain certain keys in the correct form to be accepted by clients. The
body is optional if the masterserver already confirmed the reception of
the info with the given `Info-Serial`.
Not shown in this payload is the `Connless-Token` header that is used
for Teeworlds 0.7 style communication.
Also not shown is the `Challenge-Token` that should be included once the
server receives the challenge token via UDP.
The masterserver responds with a `200 OK` with a body like this:
```
{"status":"success"}
```
The `status` field can be `success` if the server was successfully
registered on the masterserver, `need_challenge` if the masterserver
wants the correct `Challenge-Token` header before the register process
is successful, `need_info` if the server sent an empty body but the
masterserver doesn't actually know the server info.
It can also be `error` if the request was malformed, only in this case
an HTTP status code except `200 OK` is sent.
Synchronization
---------------
The masterserver keeps state and outputs JSON files every second.
```json
{
"servers": [
{
"addresses": [
"tw-0.6+udp://127.0.0.1:8303",
"tw-0.6+udp://[::1]:8303"
],
"info_serial": 0,
"info": {
"max_clients": 64,
"max_players": 64,
"passworded": false,
"game_type": "TestDDraceNetwork",
"name": "My DDNet server",
"map": {
"name": "dm1",
"sha256": "0b0c481d77519c32fbe85624ef16ec0fa9991aec7367ad538bd280f28d8c26cf",
"size": 5805
},
"version": "0.6.4, 16.0.3",
"clients": []
}
}
]
}
```
`servers.json` (or configured by `--out`) is a server list that is
compatible with DDNet 15.5+ clients. It is a JSON object containing a
single key `servers` with a list of game servers. Each game server is
represented by a JSON object with an `addresses` key containing a list
of all known addresses of the server and an `info` key containing the
free-form server info sent by the game server. The free-form `info` JSON
object re-encoded by the master server and thus canonicalized and
stripped of any whitespace characters outside strings.
```json
{
"kind": "mastersrv",
"now": 1816002,
"secrets": {
"tw-0.6+udp://127.0.0.1:8303": {
"ping_time": 1811999,
"secret": "42d8f991-f2fa-46e5-a9ae-ebcc93846feb"
},
"tw-0.6+udp://[::1]:8303": {
"ping_time": 1811999,
"secret": "42d8f991-f2fa-46e5-a9ae-ebcc93846feb"
}
},
"servers": {
"42d8f991-f2fa-46e5-a9ae-ebcc93846feb": {
"info_serial": 0,
"info": {
"max_clients": 64,
"max_players": 64,
"passworded": false,
"game_type": "TestDDraceNetwork",
"name": "My DDNet server",
"map": {
"name": "dm1",
"sha256": "0b0c481d77519c32fbe85624ef16ec0fa9991aec7367ad538bd280f28d8c26cf",
"size": 5805
},
"version": "0.6.4, 16.0.3",
"clients": []
}
}
}
}
```
`--write-dump` outputs a JSON file compatible with `--read-dump-dir`,
this can be used to synchronize servers across different masterservers.
`--read-dump-dir` is also used to ingest servers from the backward
compatibility layer that pings each server for their server info using
the old protocol.
The `kind` field describe that this is `mastersrv` output and not from a
`backcompat`. This is used for prioritizing `mastersrv` information over
`backcompat` information.
The `now` field contains an integer describing the current time in
milliseconds relative an unspecified epoch that is fixed for each JSON
file. This is done instead of using the current time as the epoch for
better compression of non-changing data.
`secrets` is a map from each server address and to a JSON object
containing the last ping time (`ping_time`) in milliseconds relative to
the same epoch as before, and the server secret (`secret`) that is used
to unify server infos from different addresses of the same logical
server.
`servers` is a map from the aforementioned `secret`s to the
corresponding `info_serial` and `info`.
```json
[
"tw-0.6+udp://127.0.0.1:8303",
"tw-0.6+udp://[::1]:8303"
]
```
`--write-addresses` outputs a JSON file containing all addresses
corresponding to servers that are registered to HTTP masterservers. It
does not contain the servers that are obtained via backward
compatibility measures.
This file can be used by an old-style masterserver to also list
new-style servers without the game servers having to register there.
An implementation of this can be found at
https://github.com/heinrich5991/teeworlds/tree/mastersrv_6_backcompat
for Teeworlds 0.5/0.6 masterservers and at
https://github.com/heinrich5991/teeworlds/tree/mastersrv_7_backcompat
for Teeworlds 0.7 masterservers.
All these JSON files can be sent over the network in an efficient way
using https://github.com/heinrich5991/twmaster-collect. It establishes a
zstd-compressed TCP connection authenticated by a string token that is
sent in plain-text. It watches the specified file and transmits it every
time it changes. Due to the zstd-compression, the data sent over the
network is similar to the size of a diff.
Implementation
--------------
The masterserver implementation was done in Rust.
The current gameserver register implementation doesn't support more than
one masterserver for registering.
2022-05-19 20:03:17 +00:00
{
if ( GameServer ( ) - > IsClientPlayer ( i ) )
PlayerCount + + ;
ClientCount + + ;
}
}
int MaxPlayers = maximum ( m_NetServer . MaxClients ( ) - maximum ( g_Config . m_SvSpectatorSlots , g_Config . m_SvReservedSlots ) , PlayerCount ) ;
int MaxClients = maximum ( m_NetServer . MaxClients ( ) - g_Config . m_SvReservedSlots , ClientCount ) ;
char aName [ 256 ] ;
char aGameType [ 32 ] ;
char aMapName [ 64 ] ;
char aVersion [ 64 ] ;
char aMapSha256 [ SHA256_MAXSTRSIZE ] ;
sha256_str ( m_aCurrentMapSha256 [ MAP_TYPE_SIX ] , aMapSha256 , sizeof ( aMapSha256 ) ) ;
char aInfo [ 16384 ] ;
str_format ( aInfo , sizeof ( aInfo ) ,
" { "
" \" max_clients \" :%d, "
" \" max_players \" :%d, "
" \" passworded \" :%s, "
" \" game_type \" : \" %s \" , "
" \" name \" : \" %s \" , "
" \" map \" :{ "
" \" name \" : \" %s \" , "
" \" sha256 \" : \" %s \" , "
" \" size \" :%d "
" }, "
" \" version \" : \" %s \" , "
2022-10-11 19:52:27 +00:00
" \" client_score_kind \" : \" time \" , "
Add HTTP masterserver registering and HTTP masterserver
Registering
-----------
The idea is that game servers push their server info to the
masterservers every 15 seconds or when the server info changes, but not
more than once per second.
The game servers do not support the old registering protocol anymore,
the backward compatibility is handled by the masterserver.
The register call is a HTTP POST to a URL like
`https://master1.ddnet.tw/ddnet/15/register` and looks like this:
```json
POST /ddnet/15/register HTTP/1.1
Address: tw-0.6+udp://connecting-address.invalid:8303
Secret: 81fa3955-6f83-4290-818d-31c0906b1118
Challenge-Secret: 81fa3955-6f83-4290-818d-31c0906b1118:tw0.6/ipv6
Info-Serial: 0
{
"max_clients": 64,
"max_players": 64,
"passworded": false,
"game_type": "TestDDraceNetwork",
"name": "My DDNet server",
"map": {
"name": "dm1",
"sha256": "0b0c481d77519c32fbe85624ef16ec0fa9991aec7367ad538bd280f28d8c26cf",
"size": 5805
},
"version": "0.6.4, 16.0.3",
"clients": []
}
```
The `Address` header declares that the server wants to register itself as
a `tw-0.6+udp` server, i.e. a server speaking a Teeworlds-0.6-compatible
protocol.
The free-form `Secret` header is used as a server identity, the server
list will be deduplicated via this secret.
The free-form `Challenge-Secret` is sent back via UDP for a port forward
check. This might have security implications as the masterserver can be
asked to send a UDP packet containing some user-controlled bytes. This
is somewhat mitigated by the fact that it can only go to an
attacker-controlled IP address.
The `Info-Serial` header is an integer field that should increase each
time the server info (in the body) changes. The masterserver uses that
field to ensure that it doesn't use old server infos.
The body is a free-form JSON object set by the game server. It should
contain certain keys in the correct form to be accepted by clients. The
body is optional if the masterserver already confirmed the reception of
the info with the given `Info-Serial`.
Not shown in this payload is the `Connless-Token` header that is used
for Teeworlds 0.7 style communication.
Also not shown is the `Challenge-Token` that should be included once the
server receives the challenge token via UDP.
The masterserver responds with a `200 OK` with a body like this:
```
{"status":"success"}
```
The `status` field can be `success` if the server was successfully
registered on the masterserver, `need_challenge` if the masterserver
wants the correct `Challenge-Token` header before the register process
is successful, `need_info` if the server sent an empty body but the
masterserver doesn't actually know the server info.
It can also be `error` if the request was malformed, only in this case
an HTTP status code except `200 OK` is sent.
Synchronization
---------------
The masterserver keeps state and outputs JSON files every second.
```json
{
"servers": [
{
"addresses": [
"tw-0.6+udp://127.0.0.1:8303",
"tw-0.6+udp://[::1]:8303"
],
"info_serial": 0,
"info": {
"max_clients": 64,
"max_players": 64,
"passworded": false,
"game_type": "TestDDraceNetwork",
"name": "My DDNet server",
"map": {
"name": "dm1",
"sha256": "0b0c481d77519c32fbe85624ef16ec0fa9991aec7367ad538bd280f28d8c26cf",
"size": 5805
},
"version": "0.6.4, 16.0.3",
"clients": []
}
}
]
}
```
`servers.json` (or configured by `--out`) is a server list that is
compatible with DDNet 15.5+ clients. It is a JSON object containing a
single key `servers` with a list of game servers. Each game server is
represented by a JSON object with an `addresses` key containing a list
of all known addresses of the server and an `info` key containing the
free-form server info sent by the game server. The free-form `info` JSON
object re-encoded by the master server and thus canonicalized and
stripped of any whitespace characters outside strings.
```json
{
"kind": "mastersrv",
"now": 1816002,
"secrets": {
"tw-0.6+udp://127.0.0.1:8303": {
"ping_time": 1811999,
"secret": "42d8f991-f2fa-46e5-a9ae-ebcc93846feb"
},
"tw-0.6+udp://[::1]:8303": {
"ping_time": 1811999,
"secret": "42d8f991-f2fa-46e5-a9ae-ebcc93846feb"
}
},
"servers": {
"42d8f991-f2fa-46e5-a9ae-ebcc93846feb": {
"info_serial": 0,
"info": {
"max_clients": 64,
"max_players": 64,
"passworded": false,
"game_type": "TestDDraceNetwork",
"name": "My DDNet server",
"map": {
"name": "dm1",
"sha256": "0b0c481d77519c32fbe85624ef16ec0fa9991aec7367ad538bd280f28d8c26cf",
"size": 5805
},
"version": "0.6.4, 16.0.3",
"clients": []
}
}
}
}
```
`--write-dump` outputs a JSON file compatible with `--read-dump-dir`,
this can be used to synchronize servers across different masterservers.
`--read-dump-dir` is also used to ingest servers from the backward
compatibility layer that pings each server for their server info using
the old protocol.
The `kind` field describe that this is `mastersrv` output and not from a
`backcompat`. This is used for prioritizing `mastersrv` information over
`backcompat` information.
The `now` field contains an integer describing the current time in
milliseconds relative an unspecified epoch that is fixed for each JSON
file. This is done instead of using the current time as the epoch for
better compression of non-changing data.
`secrets` is a map from each server address and to a JSON object
containing the last ping time (`ping_time`) in milliseconds relative to
the same epoch as before, and the server secret (`secret`) that is used
to unify server infos from different addresses of the same logical
server.
`servers` is a map from the aforementioned `secret`s to the
corresponding `info_serial` and `info`.
```json
[
"tw-0.6+udp://127.0.0.1:8303",
"tw-0.6+udp://[::1]:8303"
]
```
`--write-addresses` outputs a JSON file containing all addresses
corresponding to servers that are registered to HTTP masterservers. It
does not contain the servers that are obtained via backward
compatibility measures.
This file can be used by an old-style masterserver to also list
new-style servers without the game servers having to register there.
An implementation of this can be found at
https://github.com/heinrich5991/teeworlds/tree/mastersrv_6_backcompat
for Teeworlds 0.5/0.6 masterservers and at
https://github.com/heinrich5991/teeworlds/tree/mastersrv_7_backcompat
for Teeworlds 0.7 masterservers.
All these JSON files can be sent over the network in an efficient way
using https://github.com/heinrich5991/twmaster-collect. It establishes a
zstd-compressed TCP connection authenticated by a string token that is
sent in plain-text. It watches the specified file and transmits it every
time it changes. Due to the zstd-compression, the data sent over the
network is similar to the size of a diff.
Implementation
--------------
The masterserver implementation was done in Rust.
The current gameserver register implementation doesn't support more than
one masterserver for registering.
2022-05-19 20:03:17 +00:00
" \" clients \" :[ " ,
MaxClients ,
MaxPlayers ,
JsonBool ( g_Config . m_Password [ 0 ] ) ,
EscapeJson ( aGameType , sizeof ( aGameType ) , GameServer ( ) - > GameType ( ) ) ,
EscapeJson ( aName , sizeof ( aName ) , g_Config . m_SvName ) ,
EscapeJson ( aMapName , sizeof ( aMapName ) , m_aCurrentMap ) ,
aMapSha256 ,
m_aCurrentMapSize [ MAP_TYPE_SIX ] ,
EscapeJson ( aVersion , sizeof ( aVersion ) , GameServer ( ) - > Version ( ) ) ) ;
bool FirstPlayer = true ;
for ( int i = 0 ; i < MAX_CLIENTS ; i + + )
{
2023-11-22 16:57:42 +00:00
if ( m_aClients [ i ] . IncludedInServerInfo ( ) )
Add HTTP masterserver registering and HTTP masterserver
Registering
-----------
The idea is that game servers push their server info to the
masterservers every 15 seconds or when the server info changes, but not
more than once per second.
The game servers do not support the old registering protocol anymore,
the backward compatibility is handled by the masterserver.
The register call is a HTTP POST to a URL like
`https://master1.ddnet.tw/ddnet/15/register` and looks like this:
```json
POST /ddnet/15/register HTTP/1.1
Address: tw-0.6+udp://connecting-address.invalid:8303
Secret: 81fa3955-6f83-4290-818d-31c0906b1118
Challenge-Secret: 81fa3955-6f83-4290-818d-31c0906b1118:tw0.6/ipv6
Info-Serial: 0
{
"max_clients": 64,
"max_players": 64,
"passworded": false,
"game_type": "TestDDraceNetwork",
"name": "My DDNet server",
"map": {
"name": "dm1",
"sha256": "0b0c481d77519c32fbe85624ef16ec0fa9991aec7367ad538bd280f28d8c26cf",
"size": 5805
},
"version": "0.6.4, 16.0.3",
"clients": []
}
```
The `Address` header declares that the server wants to register itself as
a `tw-0.6+udp` server, i.e. a server speaking a Teeworlds-0.6-compatible
protocol.
The free-form `Secret` header is used as a server identity, the server
list will be deduplicated via this secret.
The free-form `Challenge-Secret` is sent back via UDP for a port forward
check. This might have security implications as the masterserver can be
asked to send a UDP packet containing some user-controlled bytes. This
is somewhat mitigated by the fact that it can only go to an
attacker-controlled IP address.
The `Info-Serial` header is an integer field that should increase each
time the server info (in the body) changes. The masterserver uses that
field to ensure that it doesn't use old server infos.
The body is a free-form JSON object set by the game server. It should
contain certain keys in the correct form to be accepted by clients. The
body is optional if the masterserver already confirmed the reception of
the info with the given `Info-Serial`.
Not shown in this payload is the `Connless-Token` header that is used
for Teeworlds 0.7 style communication.
Also not shown is the `Challenge-Token` that should be included once the
server receives the challenge token via UDP.
The masterserver responds with a `200 OK` with a body like this:
```
{"status":"success"}
```
The `status` field can be `success` if the server was successfully
registered on the masterserver, `need_challenge` if the masterserver
wants the correct `Challenge-Token` header before the register process
is successful, `need_info` if the server sent an empty body but the
masterserver doesn't actually know the server info.
It can also be `error` if the request was malformed, only in this case
an HTTP status code except `200 OK` is sent.
Synchronization
---------------
The masterserver keeps state and outputs JSON files every second.
```json
{
"servers": [
{
"addresses": [
"tw-0.6+udp://127.0.0.1:8303",
"tw-0.6+udp://[::1]:8303"
],
"info_serial": 0,
"info": {
"max_clients": 64,
"max_players": 64,
"passworded": false,
"game_type": "TestDDraceNetwork",
"name": "My DDNet server",
"map": {
"name": "dm1",
"sha256": "0b0c481d77519c32fbe85624ef16ec0fa9991aec7367ad538bd280f28d8c26cf",
"size": 5805
},
"version": "0.6.4, 16.0.3",
"clients": []
}
}
]
}
```
`servers.json` (or configured by `--out`) is a server list that is
compatible with DDNet 15.5+ clients. It is a JSON object containing a
single key `servers` with a list of game servers. Each game server is
represented by a JSON object with an `addresses` key containing a list
of all known addresses of the server and an `info` key containing the
free-form server info sent by the game server. The free-form `info` JSON
object re-encoded by the master server and thus canonicalized and
stripped of any whitespace characters outside strings.
```json
{
"kind": "mastersrv",
"now": 1816002,
"secrets": {
"tw-0.6+udp://127.0.0.1:8303": {
"ping_time": 1811999,
"secret": "42d8f991-f2fa-46e5-a9ae-ebcc93846feb"
},
"tw-0.6+udp://[::1]:8303": {
"ping_time": 1811999,
"secret": "42d8f991-f2fa-46e5-a9ae-ebcc93846feb"
}
},
"servers": {
"42d8f991-f2fa-46e5-a9ae-ebcc93846feb": {
"info_serial": 0,
"info": {
"max_clients": 64,
"max_players": 64,
"passworded": false,
"game_type": "TestDDraceNetwork",
"name": "My DDNet server",
"map": {
"name": "dm1",
"sha256": "0b0c481d77519c32fbe85624ef16ec0fa9991aec7367ad538bd280f28d8c26cf",
"size": 5805
},
"version": "0.6.4, 16.0.3",
"clients": []
}
}
}
}
```
`--write-dump` outputs a JSON file compatible with `--read-dump-dir`,
this can be used to synchronize servers across different masterservers.
`--read-dump-dir` is also used to ingest servers from the backward
compatibility layer that pings each server for their server info using
the old protocol.
The `kind` field describe that this is `mastersrv` output and not from a
`backcompat`. This is used for prioritizing `mastersrv` information over
`backcompat` information.
The `now` field contains an integer describing the current time in
milliseconds relative an unspecified epoch that is fixed for each JSON
file. This is done instead of using the current time as the epoch for
better compression of non-changing data.
`secrets` is a map from each server address and to a JSON object
containing the last ping time (`ping_time`) in milliseconds relative to
the same epoch as before, and the server secret (`secret`) that is used
to unify server infos from different addresses of the same logical
server.
`servers` is a map from the aforementioned `secret`s to the
corresponding `info_serial` and `info`.
```json
[
"tw-0.6+udp://127.0.0.1:8303",
"tw-0.6+udp://[::1]:8303"
]
```
`--write-addresses` outputs a JSON file containing all addresses
corresponding to servers that are registered to HTTP masterservers. It
does not contain the servers that are obtained via backward
compatibility measures.
This file can be used by an old-style masterserver to also list
new-style servers without the game servers having to register there.
An implementation of this can be found at
https://github.com/heinrich5991/teeworlds/tree/mastersrv_6_backcompat
for Teeworlds 0.5/0.6 masterservers and at
https://github.com/heinrich5991/teeworlds/tree/mastersrv_7_backcompat
for Teeworlds 0.7 masterservers.
All these JSON files can be sent over the network in an efficient way
using https://github.com/heinrich5991/twmaster-collect. It establishes a
zstd-compressed TCP connection authenticated by a string token that is
sent in plain-text. It watches the specified file and transmits it every
time it changes. Due to the zstd-compression, the data sent over the
network is similar to the size of a diff.
Implementation
--------------
The masterserver implementation was done in Rust.
The current gameserver register implementation doesn't support more than
one masterserver for registering.
2022-05-19 20:03:17 +00:00
{
char aCName [ 32 ] ;
char aCClan [ 32 ] ;
2022-11-14 07:04:34 +00:00
char aExtraPlayerInfo [ 512 ] ;
GameServer ( ) - > OnUpdatePlayerServerInfo ( aExtraPlayerInfo , sizeof ( aExtraPlayerInfo ) , i ) ;
char aClientInfo [ 1024 ] ;
Add HTTP masterserver registering and HTTP masterserver
Registering
-----------
The idea is that game servers push their server info to the
masterservers every 15 seconds or when the server info changes, but not
more than once per second.
The game servers do not support the old registering protocol anymore,
the backward compatibility is handled by the masterserver.
The register call is a HTTP POST to a URL like
`https://master1.ddnet.tw/ddnet/15/register` and looks like this:
```json
POST /ddnet/15/register HTTP/1.1
Address: tw-0.6+udp://connecting-address.invalid:8303
Secret: 81fa3955-6f83-4290-818d-31c0906b1118
Challenge-Secret: 81fa3955-6f83-4290-818d-31c0906b1118:tw0.6/ipv6
Info-Serial: 0
{
"max_clients": 64,
"max_players": 64,
"passworded": false,
"game_type": "TestDDraceNetwork",
"name": "My DDNet server",
"map": {
"name": "dm1",
"sha256": "0b0c481d77519c32fbe85624ef16ec0fa9991aec7367ad538bd280f28d8c26cf",
"size": 5805
},
"version": "0.6.4, 16.0.3",
"clients": []
}
```
The `Address` header declares that the server wants to register itself as
a `tw-0.6+udp` server, i.e. a server speaking a Teeworlds-0.6-compatible
protocol.
The free-form `Secret` header is used as a server identity, the server
list will be deduplicated via this secret.
The free-form `Challenge-Secret` is sent back via UDP for a port forward
check. This might have security implications as the masterserver can be
asked to send a UDP packet containing some user-controlled bytes. This
is somewhat mitigated by the fact that it can only go to an
attacker-controlled IP address.
The `Info-Serial` header is an integer field that should increase each
time the server info (in the body) changes. The masterserver uses that
field to ensure that it doesn't use old server infos.
The body is a free-form JSON object set by the game server. It should
contain certain keys in the correct form to be accepted by clients. The
body is optional if the masterserver already confirmed the reception of
the info with the given `Info-Serial`.
Not shown in this payload is the `Connless-Token` header that is used
for Teeworlds 0.7 style communication.
Also not shown is the `Challenge-Token` that should be included once the
server receives the challenge token via UDP.
The masterserver responds with a `200 OK` with a body like this:
```
{"status":"success"}
```
The `status` field can be `success` if the server was successfully
registered on the masterserver, `need_challenge` if the masterserver
wants the correct `Challenge-Token` header before the register process
is successful, `need_info` if the server sent an empty body but the
masterserver doesn't actually know the server info.
It can also be `error` if the request was malformed, only in this case
an HTTP status code except `200 OK` is sent.
Synchronization
---------------
The masterserver keeps state and outputs JSON files every second.
```json
{
"servers": [
{
"addresses": [
"tw-0.6+udp://127.0.0.1:8303",
"tw-0.6+udp://[::1]:8303"
],
"info_serial": 0,
"info": {
"max_clients": 64,
"max_players": 64,
"passworded": false,
"game_type": "TestDDraceNetwork",
"name": "My DDNet server",
"map": {
"name": "dm1",
"sha256": "0b0c481d77519c32fbe85624ef16ec0fa9991aec7367ad538bd280f28d8c26cf",
"size": 5805
},
"version": "0.6.4, 16.0.3",
"clients": []
}
}
]
}
```
`servers.json` (or configured by `--out`) is a server list that is
compatible with DDNet 15.5+ clients. It is a JSON object containing a
single key `servers` with a list of game servers. Each game server is
represented by a JSON object with an `addresses` key containing a list
of all known addresses of the server and an `info` key containing the
free-form server info sent by the game server. The free-form `info` JSON
object re-encoded by the master server and thus canonicalized and
stripped of any whitespace characters outside strings.
```json
{
"kind": "mastersrv",
"now": 1816002,
"secrets": {
"tw-0.6+udp://127.0.0.1:8303": {
"ping_time": 1811999,
"secret": "42d8f991-f2fa-46e5-a9ae-ebcc93846feb"
},
"tw-0.6+udp://[::1]:8303": {
"ping_time": 1811999,
"secret": "42d8f991-f2fa-46e5-a9ae-ebcc93846feb"
}
},
"servers": {
"42d8f991-f2fa-46e5-a9ae-ebcc93846feb": {
"info_serial": 0,
"info": {
"max_clients": 64,
"max_players": 64,
"passworded": false,
"game_type": "TestDDraceNetwork",
"name": "My DDNet server",
"map": {
"name": "dm1",
"sha256": "0b0c481d77519c32fbe85624ef16ec0fa9991aec7367ad538bd280f28d8c26cf",
"size": 5805
},
"version": "0.6.4, 16.0.3",
"clients": []
}
}
}
}
```
`--write-dump` outputs a JSON file compatible with `--read-dump-dir`,
this can be used to synchronize servers across different masterservers.
`--read-dump-dir` is also used to ingest servers from the backward
compatibility layer that pings each server for their server info using
the old protocol.
The `kind` field describe that this is `mastersrv` output and not from a
`backcompat`. This is used for prioritizing `mastersrv` information over
`backcompat` information.
The `now` field contains an integer describing the current time in
milliseconds relative an unspecified epoch that is fixed for each JSON
file. This is done instead of using the current time as the epoch for
better compression of non-changing data.
`secrets` is a map from each server address and to a JSON object
containing the last ping time (`ping_time`) in milliseconds relative to
the same epoch as before, and the server secret (`secret`) that is used
to unify server infos from different addresses of the same logical
server.
`servers` is a map from the aforementioned `secret`s to the
corresponding `info_serial` and `info`.
```json
[
"tw-0.6+udp://127.0.0.1:8303",
"tw-0.6+udp://[::1]:8303"
]
```
`--write-addresses` outputs a JSON file containing all addresses
corresponding to servers that are registered to HTTP masterservers. It
does not contain the servers that are obtained via backward
compatibility measures.
This file can be used by an old-style masterserver to also list
new-style servers without the game servers having to register there.
An implementation of this can be found at
https://github.com/heinrich5991/teeworlds/tree/mastersrv_6_backcompat
for Teeworlds 0.5/0.6 masterservers and at
https://github.com/heinrich5991/teeworlds/tree/mastersrv_7_backcompat
for Teeworlds 0.7 masterservers.
All these JSON files can be sent over the network in an efficient way
using https://github.com/heinrich5991/twmaster-collect. It establishes a
zstd-compressed TCP connection authenticated by a string token that is
sent in plain-text. It watches the specified file and transmits it every
time it changes. Due to the zstd-compression, the data sent over the
network is similar to the size of a diff.
Implementation
--------------
The masterserver implementation was done in Rust.
The current gameserver register implementation doesn't support more than
one masterserver for registering.
2022-05-19 20:03:17 +00:00
str_format ( aClientInfo , sizeof ( aClientInfo ) ,
" %s{ "
" \" name \" : \" %s \" , "
" \" clan \" : \" %s \" , "
" \" country \" :%d, "
" \" score \" :%d, "
" \" is_player \" :%s "
2022-11-14 07:04:34 +00:00
" %s "
Add HTTP masterserver registering and HTTP masterserver
Registering
-----------
The idea is that game servers push their server info to the
masterservers every 15 seconds or when the server info changes, but not
more than once per second.
The game servers do not support the old registering protocol anymore,
the backward compatibility is handled by the masterserver.
The register call is a HTTP POST to a URL like
`https://master1.ddnet.tw/ddnet/15/register` and looks like this:
```json
POST /ddnet/15/register HTTP/1.1
Address: tw-0.6+udp://connecting-address.invalid:8303
Secret: 81fa3955-6f83-4290-818d-31c0906b1118
Challenge-Secret: 81fa3955-6f83-4290-818d-31c0906b1118:tw0.6/ipv6
Info-Serial: 0
{
"max_clients": 64,
"max_players": 64,
"passworded": false,
"game_type": "TestDDraceNetwork",
"name": "My DDNet server",
"map": {
"name": "dm1",
"sha256": "0b0c481d77519c32fbe85624ef16ec0fa9991aec7367ad538bd280f28d8c26cf",
"size": 5805
},
"version": "0.6.4, 16.0.3",
"clients": []
}
```
The `Address` header declares that the server wants to register itself as
a `tw-0.6+udp` server, i.e. a server speaking a Teeworlds-0.6-compatible
protocol.
The free-form `Secret` header is used as a server identity, the server
list will be deduplicated via this secret.
The free-form `Challenge-Secret` is sent back via UDP for a port forward
check. This might have security implications as the masterserver can be
asked to send a UDP packet containing some user-controlled bytes. This
is somewhat mitigated by the fact that it can only go to an
attacker-controlled IP address.
The `Info-Serial` header is an integer field that should increase each
time the server info (in the body) changes. The masterserver uses that
field to ensure that it doesn't use old server infos.
The body is a free-form JSON object set by the game server. It should
contain certain keys in the correct form to be accepted by clients. The
body is optional if the masterserver already confirmed the reception of
the info with the given `Info-Serial`.
Not shown in this payload is the `Connless-Token` header that is used
for Teeworlds 0.7 style communication.
Also not shown is the `Challenge-Token` that should be included once the
server receives the challenge token via UDP.
The masterserver responds with a `200 OK` with a body like this:
```
{"status":"success"}
```
The `status` field can be `success` if the server was successfully
registered on the masterserver, `need_challenge` if the masterserver
wants the correct `Challenge-Token` header before the register process
is successful, `need_info` if the server sent an empty body but the
masterserver doesn't actually know the server info.
It can also be `error` if the request was malformed, only in this case
an HTTP status code except `200 OK` is sent.
Synchronization
---------------
The masterserver keeps state and outputs JSON files every second.
```json
{
"servers": [
{
"addresses": [
"tw-0.6+udp://127.0.0.1:8303",
"tw-0.6+udp://[::1]:8303"
],
"info_serial": 0,
"info": {
"max_clients": 64,
"max_players": 64,
"passworded": false,
"game_type": "TestDDraceNetwork",
"name": "My DDNet server",
"map": {
"name": "dm1",
"sha256": "0b0c481d77519c32fbe85624ef16ec0fa9991aec7367ad538bd280f28d8c26cf",
"size": 5805
},
"version": "0.6.4, 16.0.3",
"clients": []
}
}
]
}
```
`servers.json` (or configured by `--out`) is a server list that is
compatible with DDNet 15.5+ clients. It is a JSON object containing a
single key `servers` with a list of game servers. Each game server is
represented by a JSON object with an `addresses` key containing a list
of all known addresses of the server and an `info` key containing the
free-form server info sent by the game server. The free-form `info` JSON
object re-encoded by the master server and thus canonicalized and
stripped of any whitespace characters outside strings.
```json
{
"kind": "mastersrv",
"now": 1816002,
"secrets": {
"tw-0.6+udp://127.0.0.1:8303": {
"ping_time": 1811999,
"secret": "42d8f991-f2fa-46e5-a9ae-ebcc93846feb"
},
"tw-0.6+udp://[::1]:8303": {
"ping_time": 1811999,
"secret": "42d8f991-f2fa-46e5-a9ae-ebcc93846feb"
}
},
"servers": {
"42d8f991-f2fa-46e5-a9ae-ebcc93846feb": {
"info_serial": 0,
"info": {
"max_clients": 64,
"max_players": 64,
"passworded": false,
"game_type": "TestDDraceNetwork",
"name": "My DDNet server",
"map": {
"name": "dm1",
"sha256": "0b0c481d77519c32fbe85624ef16ec0fa9991aec7367ad538bd280f28d8c26cf",
"size": 5805
},
"version": "0.6.4, 16.0.3",
"clients": []
}
}
}
}
```
`--write-dump` outputs a JSON file compatible with `--read-dump-dir`,
this can be used to synchronize servers across different masterservers.
`--read-dump-dir` is also used to ingest servers from the backward
compatibility layer that pings each server for their server info using
the old protocol.
The `kind` field describe that this is `mastersrv` output and not from a
`backcompat`. This is used for prioritizing `mastersrv` information over
`backcompat` information.
The `now` field contains an integer describing the current time in
milliseconds relative an unspecified epoch that is fixed for each JSON
file. This is done instead of using the current time as the epoch for
better compression of non-changing data.
`secrets` is a map from each server address and to a JSON object
containing the last ping time (`ping_time`) in milliseconds relative to
the same epoch as before, and the server secret (`secret`) that is used
to unify server infos from different addresses of the same logical
server.
`servers` is a map from the aforementioned `secret`s to the
corresponding `info_serial` and `info`.
```json
[
"tw-0.6+udp://127.0.0.1:8303",
"tw-0.6+udp://[::1]:8303"
]
```
`--write-addresses` outputs a JSON file containing all addresses
corresponding to servers that are registered to HTTP masterservers. It
does not contain the servers that are obtained via backward
compatibility measures.
This file can be used by an old-style masterserver to also list
new-style servers without the game servers having to register there.
An implementation of this can be found at
https://github.com/heinrich5991/teeworlds/tree/mastersrv_6_backcompat
for Teeworlds 0.5/0.6 masterservers and at
https://github.com/heinrich5991/teeworlds/tree/mastersrv_7_backcompat
for Teeworlds 0.7 masterservers.
All these JSON files can be sent over the network in an efficient way
using https://github.com/heinrich5991/twmaster-collect. It establishes a
zstd-compressed TCP connection authenticated by a string token that is
sent in plain-text. It watches the specified file and transmits it every
time it changes. Due to the zstd-compression, the data sent over the
network is similar to the size of a diff.
Implementation
--------------
The masterserver implementation was done in Rust.
The current gameserver register implementation doesn't support more than
one masterserver for registering.
2022-05-19 20:03:17 +00:00
" } " ,
! FirstPlayer ? " , " : " " ,
EscapeJson ( aCName , sizeof ( aCName ) , ClientName ( i ) ) ,
EscapeJson ( aCClan , sizeof ( aCClan ) , ClientClan ( i ) ) ,
m_aClients [ i ] . m_Country ,
2023-05-22 11:00:06 +00:00
m_aClients [ i ] . m_Score . value_or ( - 9999 ) ,
2022-11-14 07:04:34 +00:00
JsonBool ( GameServer ( ) - > IsClientPlayer ( i ) ) ,
aExtraPlayerInfo ) ;
2023-06-13 19:06:48 +00:00
str_append ( aInfo , aClientInfo ) ;
Add HTTP masterserver registering and HTTP masterserver
Registering
-----------
The idea is that game servers push their server info to the
masterservers every 15 seconds or when the server info changes, but not
more than once per second.
The game servers do not support the old registering protocol anymore,
the backward compatibility is handled by the masterserver.
The register call is a HTTP POST to a URL like
`https://master1.ddnet.tw/ddnet/15/register` and looks like this:
```json
POST /ddnet/15/register HTTP/1.1
Address: tw-0.6+udp://connecting-address.invalid:8303
Secret: 81fa3955-6f83-4290-818d-31c0906b1118
Challenge-Secret: 81fa3955-6f83-4290-818d-31c0906b1118:tw0.6/ipv6
Info-Serial: 0
{
"max_clients": 64,
"max_players": 64,
"passworded": false,
"game_type": "TestDDraceNetwork",
"name": "My DDNet server",
"map": {
"name": "dm1",
"sha256": "0b0c481d77519c32fbe85624ef16ec0fa9991aec7367ad538bd280f28d8c26cf",
"size": 5805
},
"version": "0.6.4, 16.0.3",
"clients": []
}
```
The `Address` header declares that the server wants to register itself as
a `tw-0.6+udp` server, i.e. a server speaking a Teeworlds-0.6-compatible
protocol.
The free-form `Secret` header is used as a server identity, the server
list will be deduplicated via this secret.
The free-form `Challenge-Secret` is sent back via UDP for a port forward
check. This might have security implications as the masterserver can be
asked to send a UDP packet containing some user-controlled bytes. This
is somewhat mitigated by the fact that it can only go to an
attacker-controlled IP address.
The `Info-Serial` header is an integer field that should increase each
time the server info (in the body) changes. The masterserver uses that
field to ensure that it doesn't use old server infos.
The body is a free-form JSON object set by the game server. It should
contain certain keys in the correct form to be accepted by clients. The
body is optional if the masterserver already confirmed the reception of
the info with the given `Info-Serial`.
Not shown in this payload is the `Connless-Token` header that is used
for Teeworlds 0.7 style communication.
Also not shown is the `Challenge-Token` that should be included once the
server receives the challenge token via UDP.
The masterserver responds with a `200 OK` with a body like this:
```
{"status":"success"}
```
The `status` field can be `success` if the server was successfully
registered on the masterserver, `need_challenge` if the masterserver
wants the correct `Challenge-Token` header before the register process
is successful, `need_info` if the server sent an empty body but the
masterserver doesn't actually know the server info.
It can also be `error` if the request was malformed, only in this case
an HTTP status code except `200 OK` is sent.
Synchronization
---------------
The masterserver keeps state and outputs JSON files every second.
```json
{
"servers": [
{
"addresses": [
"tw-0.6+udp://127.0.0.1:8303",
"tw-0.6+udp://[::1]:8303"
],
"info_serial": 0,
"info": {
"max_clients": 64,
"max_players": 64,
"passworded": false,
"game_type": "TestDDraceNetwork",
"name": "My DDNet server",
"map": {
"name": "dm1",
"sha256": "0b0c481d77519c32fbe85624ef16ec0fa9991aec7367ad538bd280f28d8c26cf",
"size": 5805
},
"version": "0.6.4, 16.0.3",
"clients": []
}
}
]
}
```
`servers.json` (or configured by `--out`) is a server list that is
compatible with DDNet 15.5+ clients. It is a JSON object containing a
single key `servers` with a list of game servers. Each game server is
represented by a JSON object with an `addresses` key containing a list
of all known addresses of the server and an `info` key containing the
free-form server info sent by the game server. The free-form `info` JSON
object re-encoded by the master server and thus canonicalized and
stripped of any whitespace characters outside strings.
```json
{
"kind": "mastersrv",
"now": 1816002,
"secrets": {
"tw-0.6+udp://127.0.0.1:8303": {
"ping_time": 1811999,
"secret": "42d8f991-f2fa-46e5-a9ae-ebcc93846feb"
},
"tw-0.6+udp://[::1]:8303": {
"ping_time": 1811999,
"secret": "42d8f991-f2fa-46e5-a9ae-ebcc93846feb"
}
},
"servers": {
"42d8f991-f2fa-46e5-a9ae-ebcc93846feb": {
"info_serial": 0,
"info": {
"max_clients": 64,
"max_players": 64,
"passworded": false,
"game_type": "TestDDraceNetwork",
"name": "My DDNet server",
"map": {
"name": "dm1",
"sha256": "0b0c481d77519c32fbe85624ef16ec0fa9991aec7367ad538bd280f28d8c26cf",
"size": 5805
},
"version": "0.6.4, 16.0.3",
"clients": []
}
}
}
}
```
`--write-dump` outputs a JSON file compatible with `--read-dump-dir`,
this can be used to synchronize servers across different masterservers.
`--read-dump-dir` is also used to ingest servers from the backward
compatibility layer that pings each server for their server info using
the old protocol.
The `kind` field describe that this is `mastersrv` output and not from a
`backcompat`. This is used for prioritizing `mastersrv` information over
`backcompat` information.
The `now` field contains an integer describing the current time in
milliseconds relative an unspecified epoch that is fixed for each JSON
file. This is done instead of using the current time as the epoch for
better compression of non-changing data.
`secrets` is a map from each server address and to a JSON object
containing the last ping time (`ping_time`) in milliseconds relative to
the same epoch as before, and the server secret (`secret`) that is used
to unify server infos from different addresses of the same logical
server.
`servers` is a map from the aforementioned `secret`s to the
corresponding `info_serial` and `info`.
```json
[
"tw-0.6+udp://127.0.0.1:8303",
"tw-0.6+udp://[::1]:8303"
]
```
`--write-addresses` outputs a JSON file containing all addresses
corresponding to servers that are registered to HTTP masterservers. It
does not contain the servers that are obtained via backward
compatibility measures.
This file can be used by an old-style masterserver to also list
new-style servers without the game servers having to register there.
An implementation of this can be found at
https://github.com/heinrich5991/teeworlds/tree/mastersrv_6_backcompat
for Teeworlds 0.5/0.6 masterservers and at
https://github.com/heinrich5991/teeworlds/tree/mastersrv_7_backcompat
for Teeworlds 0.7 masterservers.
All these JSON files can be sent over the network in an efficient way
using https://github.com/heinrich5991/twmaster-collect. It establishes a
zstd-compressed TCP connection authenticated by a string token that is
sent in plain-text. It watches the specified file and transmits it every
time it changes. Due to the zstd-compression, the data sent over the
network is similar to the size of a diff.
Implementation
--------------
The masterserver implementation was done in Rust.
The current gameserver register implementation doesn't support more than
one masterserver for registering.
2022-05-19 20:03:17 +00:00
FirstPlayer = false ;
}
}
2023-06-13 19:06:48 +00:00
str_append ( aInfo , " ]} " ) ;
Add HTTP masterserver registering and HTTP masterserver
Registering
-----------
The idea is that game servers push their server info to the
masterservers every 15 seconds or when the server info changes, but not
more than once per second.
The game servers do not support the old registering protocol anymore,
the backward compatibility is handled by the masterserver.
The register call is a HTTP POST to a URL like
`https://master1.ddnet.tw/ddnet/15/register` and looks like this:
```json
POST /ddnet/15/register HTTP/1.1
Address: tw-0.6+udp://connecting-address.invalid:8303
Secret: 81fa3955-6f83-4290-818d-31c0906b1118
Challenge-Secret: 81fa3955-6f83-4290-818d-31c0906b1118:tw0.6/ipv6
Info-Serial: 0
{
"max_clients": 64,
"max_players": 64,
"passworded": false,
"game_type": "TestDDraceNetwork",
"name": "My DDNet server",
"map": {
"name": "dm1",
"sha256": "0b0c481d77519c32fbe85624ef16ec0fa9991aec7367ad538bd280f28d8c26cf",
"size": 5805
},
"version": "0.6.4, 16.0.3",
"clients": []
}
```
The `Address` header declares that the server wants to register itself as
a `tw-0.6+udp` server, i.e. a server speaking a Teeworlds-0.6-compatible
protocol.
The free-form `Secret` header is used as a server identity, the server
list will be deduplicated via this secret.
The free-form `Challenge-Secret` is sent back via UDP for a port forward
check. This might have security implications as the masterserver can be
asked to send a UDP packet containing some user-controlled bytes. This
is somewhat mitigated by the fact that it can only go to an
attacker-controlled IP address.
The `Info-Serial` header is an integer field that should increase each
time the server info (in the body) changes. The masterserver uses that
field to ensure that it doesn't use old server infos.
The body is a free-form JSON object set by the game server. It should
contain certain keys in the correct form to be accepted by clients. The
body is optional if the masterserver already confirmed the reception of
the info with the given `Info-Serial`.
Not shown in this payload is the `Connless-Token` header that is used
for Teeworlds 0.7 style communication.
Also not shown is the `Challenge-Token` that should be included once the
server receives the challenge token via UDP.
The masterserver responds with a `200 OK` with a body like this:
```
{"status":"success"}
```
The `status` field can be `success` if the server was successfully
registered on the masterserver, `need_challenge` if the masterserver
wants the correct `Challenge-Token` header before the register process
is successful, `need_info` if the server sent an empty body but the
masterserver doesn't actually know the server info.
It can also be `error` if the request was malformed, only in this case
an HTTP status code except `200 OK` is sent.
Synchronization
---------------
The masterserver keeps state and outputs JSON files every second.
```json
{
"servers": [
{
"addresses": [
"tw-0.6+udp://127.0.0.1:8303",
"tw-0.6+udp://[::1]:8303"
],
"info_serial": 0,
"info": {
"max_clients": 64,
"max_players": 64,
"passworded": false,
"game_type": "TestDDraceNetwork",
"name": "My DDNet server",
"map": {
"name": "dm1",
"sha256": "0b0c481d77519c32fbe85624ef16ec0fa9991aec7367ad538bd280f28d8c26cf",
"size": 5805
},
"version": "0.6.4, 16.0.3",
"clients": []
}
}
]
}
```
`servers.json` (or configured by `--out`) is a server list that is
compatible with DDNet 15.5+ clients. It is a JSON object containing a
single key `servers` with a list of game servers. Each game server is
represented by a JSON object with an `addresses` key containing a list
of all known addresses of the server and an `info` key containing the
free-form server info sent by the game server. The free-form `info` JSON
object re-encoded by the master server and thus canonicalized and
stripped of any whitespace characters outside strings.
```json
{
"kind": "mastersrv",
"now": 1816002,
"secrets": {
"tw-0.6+udp://127.0.0.1:8303": {
"ping_time": 1811999,
"secret": "42d8f991-f2fa-46e5-a9ae-ebcc93846feb"
},
"tw-0.6+udp://[::1]:8303": {
"ping_time": 1811999,
"secret": "42d8f991-f2fa-46e5-a9ae-ebcc93846feb"
}
},
"servers": {
"42d8f991-f2fa-46e5-a9ae-ebcc93846feb": {
"info_serial": 0,
"info": {
"max_clients": 64,
"max_players": 64,
"passworded": false,
"game_type": "TestDDraceNetwork",
"name": "My DDNet server",
"map": {
"name": "dm1",
"sha256": "0b0c481d77519c32fbe85624ef16ec0fa9991aec7367ad538bd280f28d8c26cf",
"size": 5805
},
"version": "0.6.4, 16.0.3",
"clients": []
}
}
}
}
```
`--write-dump` outputs a JSON file compatible with `--read-dump-dir`,
this can be used to synchronize servers across different masterservers.
`--read-dump-dir` is also used to ingest servers from the backward
compatibility layer that pings each server for their server info using
the old protocol.
The `kind` field describe that this is `mastersrv` output and not from a
`backcompat`. This is used for prioritizing `mastersrv` information over
`backcompat` information.
The `now` field contains an integer describing the current time in
milliseconds relative an unspecified epoch that is fixed for each JSON
file. This is done instead of using the current time as the epoch for
better compression of non-changing data.
`secrets` is a map from each server address and to a JSON object
containing the last ping time (`ping_time`) in milliseconds relative to
the same epoch as before, and the server secret (`secret`) that is used
to unify server infos from different addresses of the same logical
server.
`servers` is a map from the aforementioned `secret`s to the
corresponding `info_serial` and `info`.
```json
[
"tw-0.6+udp://127.0.0.1:8303",
"tw-0.6+udp://[::1]:8303"
]
```
`--write-addresses` outputs a JSON file containing all addresses
corresponding to servers that are registered to HTTP masterservers. It
does not contain the servers that are obtained via backward
compatibility measures.
This file can be used by an old-style masterserver to also list
new-style servers without the game servers having to register there.
An implementation of this can be found at
https://github.com/heinrich5991/teeworlds/tree/mastersrv_6_backcompat
for Teeworlds 0.5/0.6 masterservers and at
https://github.com/heinrich5991/teeworlds/tree/mastersrv_7_backcompat
for Teeworlds 0.7 masterservers.
All these JSON files can be sent over the network in an efficient way
using https://github.com/heinrich5991/twmaster-collect. It establishes a
zstd-compressed TCP connection authenticated by a string token that is
sent in plain-text. It watches the specified file and transmits it every
time it changes. Due to the zstd-compression, the data sent over the
network is similar to the size of a diff.
Implementation
--------------
The masterserver implementation was done in Rust.
The current gameserver register implementation doesn't support more than
one masterserver for registering.
2022-05-19 20:03:17 +00:00
m_pRegister - > OnNewInfo ( aInfo ) ;
}
2019-11-03 00:07:10 +00:00
void CServer : : UpdateServerInfo ( bool Resend )
{
2020-07-07 08:23:04 +00:00
if ( m_RunServer = = UNINITIALIZED )
return ;
Add HTTP masterserver registering and HTTP masterserver
Registering
-----------
The idea is that game servers push their server info to the
masterservers every 15 seconds or when the server info changes, but not
more than once per second.
The game servers do not support the old registering protocol anymore,
the backward compatibility is handled by the masterserver.
The register call is a HTTP POST to a URL like
`https://master1.ddnet.tw/ddnet/15/register` and looks like this:
```json
POST /ddnet/15/register HTTP/1.1
Address: tw-0.6+udp://connecting-address.invalid:8303
Secret: 81fa3955-6f83-4290-818d-31c0906b1118
Challenge-Secret: 81fa3955-6f83-4290-818d-31c0906b1118:tw0.6/ipv6
Info-Serial: 0
{
"max_clients": 64,
"max_players": 64,
"passworded": false,
"game_type": "TestDDraceNetwork",
"name": "My DDNet server",
"map": {
"name": "dm1",
"sha256": "0b0c481d77519c32fbe85624ef16ec0fa9991aec7367ad538bd280f28d8c26cf",
"size": 5805
},
"version": "0.6.4, 16.0.3",
"clients": []
}
```
The `Address` header declares that the server wants to register itself as
a `tw-0.6+udp` server, i.e. a server speaking a Teeworlds-0.6-compatible
protocol.
The free-form `Secret` header is used as a server identity, the server
list will be deduplicated via this secret.
The free-form `Challenge-Secret` is sent back via UDP for a port forward
check. This might have security implications as the masterserver can be
asked to send a UDP packet containing some user-controlled bytes. This
is somewhat mitigated by the fact that it can only go to an
attacker-controlled IP address.
The `Info-Serial` header is an integer field that should increase each
time the server info (in the body) changes. The masterserver uses that
field to ensure that it doesn't use old server infos.
The body is a free-form JSON object set by the game server. It should
contain certain keys in the correct form to be accepted by clients. The
body is optional if the masterserver already confirmed the reception of
the info with the given `Info-Serial`.
Not shown in this payload is the `Connless-Token` header that is used
for Teeworlds 0.7 style communication.
Also not shown is the `Challenge-Token` that should be included once the
server receives the challenge token via UDP.
The masterserver responds with a `200 OK` with a body like this:
```
{"status":"success"}
```
The `status` field can be `success` if the server was successfully
registered on the masterserver, `need_challenge` if the masterserver
wants the correct `Challenge-Token` header before the register process
is successful, `need_info` if the server sent an empty body but the
masterserver doesn't actually know the server info.
It can also be `error` if the request was malformed, only in this case
an HTTP status code except `200 OK` is sent.
Synchronization
---------------
The masterserver keeps state and outputs JSON files every second.
```json
{
"servers": [
{
"addresses": [
"tw-0.6+udp://127.0.0.1:8303",
"tw-0.6+udp://[::1]:8303"
],
"info_serial": 0,
"info": {
"max_clients": 64,
"max_players": 64,
"passworded": false,
"game_type": "TestDDraceNetwork",
"name": "My DDNet server",
"map": {
"name": "dm1",
"sha256": "0b0c481d77519c32fbe85624ef16ec0fa9991aec7367ad538bd280f28d8c26cf",
"size": 5805
},
"version": "0.6.4, 16.0.3",
"clients": []
}
}
]
}
```
`servers.json` (or configured by `--out`) is a server list that is
compatible with DDNet 15.5+ clients. It is a JSON object containing a
single key `servers` with a list of game servers. Each game server is
represented by a JSON object with an `addresses` key containing a list
of all known addresses of the server and an `info` key containing the
free-form server info sent by the game server. The free-form `info` JSON
object re-encoded by the master server and thus canonicalized and
stripped of any whitespace characters outside strings.
```json
{
"kind": "mastersrv",
"now": 1816002,
"secrets": {
"tw-0.6+udp://127.0.0.1:8303": {
"ping_time": 1811999,
"secret": "42d8f991-f2fa-46e5-a9ae-ebcc93846feb"
},
"tw-0.6+udp://[::1]:8303": {
"ping_time": 1811999,
"secret": "42d8f991-f2fa-46e5-a9ae-ebcc93846feb"
}
},
"servers": {
"42d8f991-f2fa-46e5-a9ae-ebcc93846feb": {
"info_serial": 0,
"info": {
"max_clients": 64,
"max_players": 64,
"passworded": false,
"game_type": "TestDDraceNetwork",
"name": "My DDNet server",
"map": {
"name": "dm1",
"sha256": "0b0c481d77519c32fbe85624ef16ec0fa9991aec7367ad538bd280f28d8c26cf",
"size": 5805
},
"version": "0.6.4, 16.0.3",
"clients": []
}
}
}
}
```
`--write-dump` outputs a JSON file compatible with `--read-dump-dir`,
this can be used to synchronize servers across different masterservers.
`--read-dump-dir` is also used to ingest servers from the backward
compatibility layer that pings each server for their server info using
the old protocol.
The `kind` field describe that this is `mastersrv` output and not from a
`backcompat`. This is used for prioritizing `mastersrv` information over
`backcompat` information.
The `now` field contains an integer describing the current time in
milliseconds relative an unspecified epoch that is fixed for each JSON
file. This is done instead of using the current time as the epoch for
better compression of non-changing data.
`secrets` is a map from each server address and to a JSON object
containing the last ping time (`ping_time`) in milliseconds relative to
the same epoch as before, and the server secret (`secret`) that is used
to unify server infos from different addresses of the same logical
server.
`servers` is a map from the aforementioned `secret`s to the
corresponding `info_serial` and `info`.
```json
[
"tw-0.6+udp://127.0.0.1:8303",
"tw-0.6+udp://[::1]:8303"
]
```
`--write-addresses` outputs a JSON file containing all addresses
corresponding to servers that are registered to HTTP masterservers. It
does not contain the servers that are obtained via backward
compatibility measures.
This file can be used by an old-style masterserver to also list
new-style servers without the game servers having to register there.
An implementation of this can be found at
https://github.com/heinrich5991/teeworlds/tree/mastersrv_6_backcompat
for Teeworlds 0.5/0.6 masterservers and at
https://github.com/heinrich5991/teeworlds/tree/mastersrv_7_backcompat
for Teeworlds 0.7 masterservers.
All these JSON files can be sent over the network in an efficient way
using https://github.com/heinrich5991/twmaster-collect. It establishes a
zstd-compressed TCP connection authenticated by a string token that is
sent in plain-text. It watches the specified file and transmits it every
time it changes. Due to the zstd-compression, the data sent over the
network is similar to the size of a diff.
Implementation
--------------
The masterserver implementation was done in Rust.
The current gameserver register implementation doesn't support more than
one masterserver for registering.
2022-05-19 20:03:17 +00:00
UpdateRegisterServerInfo ( ) ;
2019-11-03 00:07:10 +00:00
for ( int i = 0 ; i < 3 ; i + + )
for ( int j = 0 ; j < 2 ; j + + )
2020-10-27 17:57:14 +00:00
CacheServerInfo ( & m_aServerInfoCache [ i * 2 + j ] , i , j ) ;
2019-11-03 00:07:10 +00:00
2020-06-16 12:41:30 +00:00
for ( int i = 0 ; i < 2 ; i + + )
2020-10-27 17:57:14 +00:00
CacheServerInfoSixup ( & m_aSixupServerInfoCache [ i ] , i ) ;
2020-06-16 12:41:30 +00:00
2019-11-03 00:07:10 +00:00
if ( Resend )
2010-05-29 07:25:38 +00:00
{
2022-02-21 15:33:51 +00:00
for ( int i = 0 ; i < MaxClients ( ) ; + + i )
2014-01-14 19:40:29 +00:00
{
2019-11-03 00:07:10 +00:00
if ( m_aClients [ i ] . m_State ! = CClient : : STATE_EMPTY )
{
2020-06-16 12:41:30 +00:00
if ( ! IsSixup ( i ) )
SendServerInfo ( m_NetServer . ClientAddr ( i ) , - 1 , SERVERINFO_INGAME , false ) ;
else
{
2023-08-22 11:49:18 +00:00
CMsgPacker Msg ( protocol7 : : NETMSG_SERVERINFO , true , true ) ;
2020-06-16 12:41:30 +00:00
GetServerInfoSixup ( & Msg , - 1 , false ) ;
2020-09-26 19:41:58 +00:00
SendMsg ( & Msg , MSGFLAG_VITAL | MSGFLAG_FLUSH , i ) ;
2020-06-16 12:41:30 +00:00
}
2019-11-03 00:07:10 +00:00
}
2014-01-14 19:40:29 +00:00
}
2010-05-29 07:25:38 +00:00
}
2019-11-03 00:07:10 +00:00
m_ServerInfoNeedsUpdate = false ;
}
2010-05-29 07:25:38 +00:00
2020-09-13 21:23:50 +00:00
void CServer : : PumpNetwork ( bool PacketWaiting )
2010-05-29 07:25:38 +00:00
{
CNetChunk Packet ;
2020-06-16 12:41:30 +00:00
SECURITY_TOKEN ResponseToken ;
2010-05-29 07:25:38 +00:00
m_NetServer . Update ( ) ;
2011-04-13 18:37:12 +00:00
2020-09-13 21:23:50 +00:00
if ( PacketWaiting )
2010-05-29 07:25:38 +00:00
{
2020-09-13 21:23:50 +00:00
// process packets
while ( m_NetServer . Recv ( & Packet , & ResponseToken ) )
2010-05-29 07:25:38 +00:00
{
2020-09-13 21:23:50 +00:00
if ( Packet . m_ClientID = = - 1 )
2020-06-16 12:41:30 +00:00
{
Add HTTP masterserver registering and HTTP masterserver
Registering
-----------
The idea is that game servers push their server info to the
masterservers every 15 seconds or when the server info changes, but not
more than once per second.
The game servers do not support the old registering protocol anymore,
the backward compatibility is handled by the masterserver.
The register call is a HTTP POST to a URL like
`https://master1.ddnet.tw/ddnet/15/register` and looks like this:
```json
POST /ddnet/15/register HTTP/1.1
Address: tw-0.6+udp://connecting-address.invalid:8303
Secret: 81fa3955-6f83-4290-818d-31c0906b1118
Challenge-Secret: 81fa3955-6f83-4290-818d-31c0906b1118:tw0.6/ipv6
Info-Serial: 0
{
"max_clients": 64,
"max_players": 64,
"passworded": false,
"game_type": "TestDDraceNetwork",
"name": "My DDNet server",
"map": {
"name": "dm1",
"sha256": "0b0c481d77519c32fbe85624ef16ec0fa9991aec7367ad538bd280f28d8c26cf",
"size": 5805
},
"version": "0.6.4, 16.0.3",
"clients": []
}
```
The `Address` header declares that the server wants to register itself as
a `tw-0.6+udp` server, i.e. a server speaking a Teeworlds-0.6-compatible
protocol.
The free-form `Secret` header is used as a server identity, the server
list will be deduplicated via this secret.
The free-form `Challenge-Secret` is sent back via UDP for a port forward
check. This might have security implications as the masterserver can be
asked to send a UDP packet containing some user-controlled bytes. This
is somewhat mitigated by the fact that it can only go to an
attacker-controlled IP address.
The `Info-Serial` header is an integer field that should increase each
time the server info (in the body) changes. The masterserver uses that
field to ensure that it doesn't use old server infos.
The body is a free-form JSON object set by the game server. It should
contain certain keys in the correct form to be accepted by clients. The
body is optional if the masterserver already confirmed the reception of
the info with the given `Info-Serial`.
Not shown in this payload is the `Connless-Token` header that is used
for Teeworlds 0.7 style communication.
Also not shown is the `Challenge-Token` that should be included once the
server receives the challenge token via UDP.
The masterserver responds with a `200 OK` with a body like this:
```
{"status":"success"}
```
The `status` field can be `success` if the server was successfully
registered on the masterserver, `need_challenge` if the masterserver
wants the correct `Challenge-Token` header before the register process
is successful, `need_info` if the server sent an empty body but the
masterserver doesn't actually know the server info.
It can also be `error` if the request was malformed, only in this case
an HTTP status code except `200 OK` is sent.
Synchronization
---------------
The masterserver keeps state and outputs JSON files every second.
```json
{
"servers": [
{
"addresses": [
"tw-0.6+udp://127.0.0.1:8303",
"tw-0.6+udp://[::1]:8303"
],
"info_serial": 0,
"info": {
"max_clients": 64,
"max_players": 64,
"passworded": false,
"game_type": "TestDDraceNetwork",
"name": "My DDNet server",
"map": {
"name": "dm1",
"sha256": "0b0c481d77519c32fbe85624ef16ec0fa9991aec7367ad538bd280f28d8c26cf",
"size": 5805
},
"version": "0.6.4, 16.0.3",
"clients": []
}
}
]
}
```
`servers.json` (or configured by `--out`) is a server list that is
compatible with DDNet 15.5+ clients. It is a JSON object containing a
single key `servers` with a list of game servers. Each game server is
represented by a JSON object with an `addresses` key containing a list
of all known addresses of the server and an `info` key containing the
free-form server info sent by the game server. The free-form `info` JSON
object re-encoded by the master server and thus canonicalized and
stripped of any whitespace characters outside strings.
```json
{
"kind": "mastersrv",
"now": 1816002,
"secrets": {
"tw-0.6+udp://127.0.0.1:8303": {
"ping_time": 1811999,
"secret": "42d8f991-f2fa-46e5-a9ae-ebcc93846feb"
},
"tw-0.6+udp://[::1]:8303": {
"ping_time": 1811999,
"secret": "42d8f991-f2fa-46e5-a9ae-ebcc93846feb"
}
},
"servers": {
"42d8f991-f2fa-46e5-a9ae-ebcc93846feb": {
"info_serial": 0,
"info": {
"max_clients": 64,
"max_players": 64,
"passworded": false,
"game_type": "TestDDraceNetwork",
"name": "My DDNet server",
"map": {
"name": "dm1",
"sha256": "0b0c481d77519c32fbe85624ef16ec0fa9991aec7367ad538bd280f28d8c26cf",
"size": 5805
},
"version": "0.6.4, 16.0.3",
"clients": []
}
}
}
}
```
`--write-dump` outputs a JSON file compatible with `--read-dump-dir`,
this can be used to synchronize servers across different masterservers.
`--read-dump-dir` is also used to ingest servers from the backward
compatibility layer that pings each server for their server info using
the old protocol.
The `kind` field describe that this is `mastersrv` output and not from a
`backcompat`. This is used for prioritizing `mastersrv` information over
`backcompat` information.
The `now` field contains an integer describing the current time in
milliseconds relative an unspecified epoch that is fixed for each JSON
file. This is done instead of using the current time as the epoch for
better compression of non-changing data.
`secrets` is a map from each server address and to a JSON object
containing the last ping time (`ping_time`) in milliseconds relative to
the same epoch as before, and the server secret (`secret`) that is used
to unify server infos from different addresses of the same logical
server.
`servers` is a map from the aforementioned `secret`s to the
corresponding `info_serial` and `info`.
```json
[
"tw-0.6+udp://127.0.0.1:8303",
"tw-0.6+udp://[::1]:8303"
]
```
`--write-addresses` outputs a JSON file containing all addresses
corresponding to servers that are registered to HTTP masterservers. It
does not contain the servers that are obtained via backward
compatibility measures.
This file can be used by an old-style masterserver to also list
new-style servers without the game servers having to register there.
An implementation of this can be found at
https://github.com/heinrich5991/teeworlds/tree/mastersrv_6_backcompat
for Teeworlds 0.5/0.6 masterservers and at
https://github.com/heinrich5991/teeworlds/tree/mastersrv_7_backcompat
for Teeworlds 0.7 masterservers.
All these JSON files can be sent over the network in an efficient way
using https://github.com/heinrich5991/twmaster-collect. It establishes a
zstd-compressed TCP connection authenticated by a string token that is
sent in plain-text. It watches the specified file and transmits it every
time it changes. Due to the zstd-compression, the data sent over the
network is similar to the size of a diff.
Implementation
--------------
The masterserver implementation was done in Rust.
The current gameserver register implementation doesn't support more than
one masterserver for registering.
2022-05-19 20:03:17 +00:00
if ( ResponseToken = = NET_SECURITY_TOKEN_UNKNOWN & & m_pRegister - > OnPacket ( & Packet ) )
2020-09-13 21:23:50 +00:00
continue ;
2020-06-16 12:41:30 +00:00
2010-05-29 07:25:38 +00:00
{
2020-09-13 21:23:50 +00:00
int ExtraToken = 0 ;
int Type = - 1 ;
if ( Packet . m_DataSize > = ( int ) sizeof ( SERVERBROWSE_GETINFO ) + 1 & &
mem_comp ( Packet . m_pData , SERVERBROWSE_GETINFO , sizeof ( SERVERBROWSE_GETINFO ) ) = = 0 )
2017-03-29 10:56:13 +00:00
{
2020-09-13 21:23:50 +00:00
if ( Packet . m_Flags & NETSENDFLAG_EXTENDED )
{
Type = SERVERINFO_EXTENDED ;
ExtraToken = ( Packet . m_aExtraData [ 0 ] < < 8 ) | Packet . m_aExtraData [ 1 ] ;
}
else
Type = SERVERINFO_VANILLA ;
2017-03-29 10:56:13 +00:00
}
2020-09-13 21:23:50 +00:00
else if ( Packet . m_DataSize > = ( int ) sizeof ( SERVERBROWSE_GETINFO_64_LEGACY ) + 1 & &
mem_comp ( Packet . m_pData , SERVERBROWSE_GETINFO_64_LEGACY , sizeof ( SERVERBROWSE_GETINFO_64_LEGACY ) ) = = 0 )
{
Type = SERVERINFO_64_LEGACY ;
}
2021-12-14 23:28:51 +00:00
if ( Type = = SERVERINFO_VANILLA & & ResponseToken ! = NET_SECURITY_TOKEN_UNKNOWN & & Config ( ) - > m_SvSixup )
2020-09-13 21:23:50 +00:00
{
CUnpacker Unpacker ;
Unpacker . Reset ( ( unsigned char * ) Packet . m_pData + sizeof ( SERVERBROWSE_GETINFO ) , Packet . m_DataSize - sizeof ( SERVERBROWSE_GETINFO ) ) ;
int SrvBrwsToken = Unpacker . GetInt ( ) ;
if ( Unpacker . Error ( ) )
2023-08-07 18:27:22 +00:00
{
2020-09-13 21:23:50 +00:00
continue ;
2023-08-07 18:27:22 +00:00
}
2020-06-16 12:41:30 +00:00
2020-09-13 21:23:50 +00:00
CPacker Packer ;
GetServerInfoSixup ( & Packer , SrvBrwsToken , RateLimitServerInfoConnless ( ) ) ;
2020-06-16 12:41:30 +00:00
2023-08-07 18:27:22 +00:00
CNetChunk Response ;
2020-09-13 21:23:50 +00:00
Response . m_ClientID = - 1 ;
Response . m_Address = Packet . m_Address ;
Response . m_Flags = NETSENDFLAG_CONNLESS ;
Response . m_pData = Packer . Data ( ) ;
Response . m_DataSize = Packer . Size ( ) ;
m_NetServer . SendConnlessSixup ( & Response , ResponseToken ) ;
}
else if ( Type ! = - 1 )
{
int Token = ( ( unsigned char * ) Packet . m_pData ) [ sizeof ( SERVERBROWSE_GETINFO ) ] ;
Token | = ExtraToken < < 8 ;
SendServerInfoConnless ( & Packet . m_Address , Token , Type ) ;
}
2013-12-31 05:13:57 +00:00
}
2010-05-29 07:25:38 +00:00
}
2020-09-13 21:23:50 +00:00
else
{
2023-06-23 08:18:47 +00:00
if ( m_aClients [ Packet . m_ClientID ] . m_State = = CClient : : STATE_REDIRECTED )
continue ;
2021-06-24 18:32:41 +00:00
int GameFlags = 0 ;
if ( Packet . m_Flags & NET_CHUNKFLAG_VITAL )
{
GameFlags | = MSGFLAG_VITAL ;
}
if ( Antibot ( ) - > OnEngineClientMessage ( Packet . m_ClientID , Packet . m_pData , Packet . m_DataSize , GameFlags ) )
{
continue ;
}
2020-09-13 21:23:50 +00:00
ProcessClientPacket ( & Packet ) ;
}
2017-09-13 20:35:09 +00:00
}
2010-05-29 07:25:38 +00:00
}
2021-06-24 18:32:41 +00:00
{
unsigned char aBuffer [ NET_MAX_PAYLOAD ] ;
int Flags ;
mem_zero ( & Packet , sizeof ( Packet ) ) ;
Packet . m_pData = aBuffer ;
while ( Antibot ( ) - > OnEngineSimulateClientMessage ( & Packet . m_ClientID , aBuffer , sizeof ( aBuffer ) , & Packet . m_DataSize , & Flags ) )
{
Packet . m_Flags = 0 ;
if ( Flags & MSGFLAG_VITAL )
{
Packet . m_Flags | = NET_CHUNKFLAG_VITAL ;
}
ProcessClientPacket ( & Packet ) ;
}
}
2012-04-13 23:30:18 +00:00
2011-12-29 22:36:53 +00:00
m_ServerBan . Update ( ) ;
2011-07-30 11:40:01 +00:00
m_Econ . Update ( ) ;
2010-05-29 07:25:38 +00:00
}
2021-12-14 23:26:15 +00:00
const char * CServer : : GetMapName ( ) const
2010-10-10 13:36:58 +00:00
{
// get the name of the map without his path
2021-12-14 23:28:51 +00:00
const char * pMapShortName = & Config ( ) - > m_SvMap [ 0 ] ;
for ( int i = 0 ; i < str_length ( Config ( ) - > m_SvMap ) - 1 ; i + + )
2010-10-10 13:36:58 +00:00
{
2021-12-14 23:28:51 +00:00
if ( Config ( ) - > m_SvMap [ i ] = = ' / ' | | Config ( ) - > m_SvMap [ i ] = = ' \\ ' )
pMapShortName = & Config ( ) - > m_SvMap [ i + 1 ] ;
2010-10-10 13:36:58 +00:00
}
return pMapShortName ;
}
2020-04-23 19:34:55 +00:00
void CServer : : ChangeMap ( const char * pMap )
{
2022-07-09 16:14:56 +00:00
str_copy ( Config ( ) - > m_SvMap , pMap ) ;
2020-04-23 19:34:55 +00:00
m_MapReload = str_comp ( Config ( ) - > m_SvMap , m_aCurrentMap ) ! = 0 ;
}
2010-05-29 07:25:38 +00:00
int CServer : : LoadMap ( const char * pMapName )
{
2022-05-14 19:48:40 +00:00
m_MapReload = false ;
2021-12-21 11:23:17 +00:00
char aBuf [ IO_MAX_PATH_LENGTH ] ;
2010-05-29 07:25:38 +00:00
str_format ( aBuf , sizeof ( aBuf ) , " maps/%s.map " , pMapName ) ;
2015-07-14 20:08:29 +00:00
GameServer ( ) - > OnMapChange ( aBuf , sizeof ( aBuf ) ) ;
2011-04-13 18:37:12 +00:00
2010-05-29 07:25:38 +00:00
if ( ! m_pMap - > Load ( aBuf ) )
return 0 ;
2011-04-13 18:37:12 +00:00
2010-05-29 07:25:38 +00:00
// reinit snapshot ids
m_IDPool . TimeoutIDs ( ) ;
2011-04-13 18:37:12 +00:00
2010-05-29 07:25:38 +00:00
// get the crc of the map
2022-02-17 18:50:02 +00:00
m_aCurrentMapSha256 [ MAP_TYPE_SIX ] = m_pMap - > Sha256 ( ) ;
m_aCurrentMapCrc [ MAP_TYPE_SIX ] = m_pMap - > Crc ( ) ;
2010-08-17 22:06:00 +00:00
char aBufMsg [ 256 ] ;
2018-06-05 19:22:40 +00:00
char aSha256 [ SHA256_MAXSTRSIZE ] ;
2022-02-17 18:50:02 +00:00
sha256_str ( m_aCurrentMapSha256 [ MAP_TYPE_SIX ] , aSha256 , sizeof ( aSha256 ) ) ;
2018-06-05 19:22:40 +00:00
str_format ( aBufMsg , sizeof ( aBufMsg ) , " %s sha256 is %s " , aBuf , aSha256 ) ;
Console ( ) - > Print ( IConsole : : OUTPUT_LEVEL_ADDINFO , " server " , aBufMsg ) ;
2011-04-13 18:37:12 +00:00
2022-07-09 16:14:56 +00:00
str_copy ( m_aCurrentMap , pMapName ) ;
2011-04-13 18:37:12 +00:00
2011-08-02 10:14:11 +00:00
// load complete map into memory for download
2010-05-29 07:25:38 +00:00
{
2022-02-17 18:50:02 +00:00
free ( m_apCurrentMapData [ MAP_TYPE_SIX ] ) ;
2022-06-14 19:02:08 +00:00
void * pData ;
Storage ( ) - > ReadFile ( aBuf , IStorage : : TYPE_ALL , & pData , & m_aCurrentMapSize [ MAP_TYPE_SIX ] ) ;
m_apCurrentMapData [ MAP_TYPE_SIX ] = ( unsigned char * ) pData ;
2010-05-29 07:25:38 +00:00
}
2011-01-20 20:25:09 +00:00
2020-06-19 21:52:13 +00:00
// load sixup version of the map
2021-12-14 23:28:51 +00:00
if ( Config ( ) - > m_SvSixup )
2020-06-19 21:52:13 +00:00
{
str_format ( aBuf , sizeof ( aBuf ) , " maps7/%s.map " , pMapName ) ;
2022-06-14 19:02:08 +00:00
void * pData ;
if ( ! Storage ( ) - > ReadFile ( aBuf , IStorage : : TYPE_ALL , & pData , & m_aCurrentMapSize [ MAP_TYPE_SIXUP ] ) )
2020-06-19 21:52:13 +00:00
{
2021-12-14 23:28:51 +00:00
Config ( ) - > m_SvSixup = 0 ;
Add HTTP masterserver registering and HTTP masterserver
Registering
-----------
The idea is that game servers push their server info to the
masterservers every 15 seconds or when the server info changes, but not
more than once per second.
The game servers do not support the old registering protocol anymore,
the backward compatibility is handled by the masterserver.
The register call is a HTTP POST to a URL like
`https://master1.ddnet.tw/ddnet/15/register` and looks like this:
```json
POST /ddnet/15/register HTTP/1.1
Address: tw-0.6+udp://connecting-address.invalid:8303
Secret: 81fa3955-6f83-4290-818d-31c0906b1118
Challenge-Secret: 81fa3955-6f83-4290-818d-31c0906b1118:tw0.6/ipv6
Info-Serial: 0
{
"max_clients": 64,
"max_players": 64,
"passworded": false,
"game_type": "TestDDraceNetwork",
"name": "My DDNet server",
"map": {
"name": "dm1",
"sha256": "0b0c481d77519c32fbe85624ef16ec0fa9991aec7367ad538bd280f28d8c26cf",
"size": 5805
},
"version": "0.6.4, 16.0.3",
"clients": []
}
```
The `Address` header declares that the server wants to register itself as
a `tw-0.6+udp` server, i.e. a server speaking a Teeworlds-0.6-compatible
protocol.
The free-form `Secret` header is used as a server identity, the server
list will be deduplicated via this secret.
The free-form `Challenge-Secret` is sent back via UDP for a port forward
check. This might have security implications as the masterserver can be
asked to send a UDP packet containing some user-controlled bytes. This
is somewhat mitigated by the fact that it can only go to an
attacker-controlled IP address.
The `Info-Serial` header is an integer field that should increase each
time the server info (in the body) changes. The masterserver uses that
field to ensure that it doesn't use old server infos.
The body is a free-form JSON object set by the game server. It should
contain certain keys in the correct form to be accepted by clients. The
body is optional if the masterserver already confirmed the reception of
the info with the given `Info-Serial`.
Not shown in this payload is the `Connless-Token` header that is used
for Teeworlds 0.7 style communication.
Also not shown is the `Challenge-Token` that should be included once the
server receives the challenge token via UDP.
The masterserver responds with a `200 OK` with a body like this:
```
{"status":"success"}
```
The `status` field can be `success` if the server was successfully
registered on the masterserver, `need_challenge` if the masterserver
wants the correct `Challenge-Token` header before the register process
is successful, `need_info` if the server sent an empty body but the
masterserver doesn't actually know the server info.
It can also be `error` if the request was malformed, only in this case
an HTTP status code except `200 OK` is sent.
Synchronization
---------------
The masterserver keeps state and outputs JSON files every second.
```json
{
"servers": [
{
"addresses": [
"tw-0.6+udp://127.0.0.1:8303",
"tw-0.6+udp://[::1]:8303"
],
"info_serial": 0,
"info": {
"max_clients": 64,
"max_players": 64,
"passworded": false,
"game_type": "TestDDraceNetwork",
"name": "My DDNet server",
"map": {
"name": "dm1",
"sha256": "0b0c481d77519c32fbe85624ef16ec0fa9991aec7367ad538bd280f28d8c26cf",
"size": 5805
},
"version": "0.6.4, 16.0.3",
"clients": []
}
}
]
}
```
`servers.json` (or configured by `--out`) is a server list that is
compatible with DDNet 15.5+ clients. It is a JSON object containing a
single key `servers` with a list of game servers. Each game server is
represented by a JSON object with an `addresses` key containing a list
of all known addresses of the server and an `info` key containing the
free-form server info sent by the game server. The free-form `info` JSON
object re-encoded by the master server and thus canonicalized and
stripped of any whitespace characters outside strings.
```json
{
"kind": "mastersrv",
"now": 1816002,
"secrets": {
"tw-0.6+udp://127.0.0.1:8303": {
"ping_time": 1811999,
"secret": "42d8f991-f2fa-46e5-a9ae-ebcc93846feb"
},
"tw-0.6+udp://[::1]:8303": {
"ping_time": 1811999,
"secret": "42d8f991-f2fa-46e5-a9ae-ebcc93846feb"
}
},
"servers": {
"42d8f991-f2fa-46e5-a9ae-ebcc93846feb": {
"info_serial": 0,
"info": {
"max_clients": 64,
"max_players": 64,
"passworded": false,
"game_type": "TestDDraceNetwork",
"name": "My DDNet server",
"map": {
"name": "dm1",
"sha256": "0b0c481d77519c32fbe85624ef16ec0fa9991aec7367ad538bd280f28d8c26cf",
"size": 5805
},
"version": "0.6.4, 16.0.3",
"clients": []
}
}
}
}
```
`--write-dump` outputs a JSON file compatible with `--read-dump-dir`,
this can be used to synchronize servers across different masterservers.
`--read-dump-dir` is also used to ingest servers from the backward
compatibility layer that pings each server for their server info using
the old protocol.
The `kind` field describe that this is `mastersrv` output and not from a
`backcompat`. This is used for prioritizing `mastersrv` information over
`backcompat` information.
The `now` field contains an integer describing the current time in
milliseconds relative an unspecified epoch that is fixed for each JSON
file. This is done instead of using the current time as the epoch for
better compression of non-changing data.
`secrets` is a map from each server address and to a JSON object
containing the last ping time (`ping_time`) in milliseconds relative to
the same epoch as before, and the server secret (`secret`) that is used
to unify server infos from different addresses of the same logical
server.
`servers` is a map from the aforementioned `secret`s to the
corresponding `info_serial` and `info`.
```json
[
"tw-0.6+udp://127.0.0.1:8303",
"tw-0.6+udp://[::1]:8303"
]
```
`--write-addresses` outputs a JSON file containing all addresses
corresponding to servers that are registered to HTTP masterservers. It
does not contain the servers that are obtained via backward
compatibility measures.
This file can be used by an old-style masterserver to also list
new-style servers without the game servers having to register there.
An implementation of this can be found at
https://github.com/heinrich5991/teeworlds/tree/mastersrv_6_backcompat
for Teeworlds 0.5/0.6 masterservers and at
https://github.com/heinrich5991/teeworlds/tree/mastersrv_7_backcompat
for Teeworlds 0.7 masterservers.
All these JSON files can be sent over the network in an efficient way
using https://github.com/heinrich5991/twmaster-collect. It establishes a
zstd-compressed TCP connection authenticated by a string token that is
sent in plain-text. It watches the specified file and transmits it every
time it changes. Due to the zstd-compression, the data sent over the
network is similar to the size of a diff.
Implementation
--------------
The masterserver implementation was done in Rust.
The current gameserver register implementation doesn't support more than
one masterserver for registering.
2022-05-19 20:03:17 +00:00
if ( m_pRegister )
{
m_pRegister - > OnConfigChange ( ) ;
}
2021-12-21 11:23:17 +00:00
str_format ( aBufMsg , sizeof ( aBufMsg ) , " couldn't load map %s " , aBuf ) ;
Console ( ) - > Print ( IConsole : : OUTPUT_LEVEL_ADDINFO , " sixup " , aBufMsg ) ;
Console ( ) - > Print ( IConsole : : OUTPUT_LEVEL_ADDINFO , " sixup " , " disabling 0.7 compatibility " ) ;
2020-06-19 21:52:13 +00:00
}
else
{
2022-02-17 18:50:02 +00:00
free ( m_apCurrentMapData [ MAP_TYPE_SIXUP ] ) ;
2022-06-14 19:02:08 +00:00
m_apCurrentMapData [ MAP_TYPE_SIXUP ] = ( unsigned char * ) pData ;
2020-06-19 21:52:13 +00:00
2022-02-17 18:50:02 +00:00
m_aCurrentMapSha256 [ MAP_TYPE_SIXUP ] = sha256 ( m_apCurrentMapData [ MAP_TYPE_SIXUP ] , m_aCurrentMapSize [ MAP_TYPE_SIXUP ] ) ;
m_aCurrentMapCrc [ MAP_TYPE_SIXUP ] = crc32 ( 0 , m_apCurrentMapData [ MAP_TYPE_SIXUP ] , m_aCurrentMapSize [ MAP_TYPE_SIXUP ] ) ;
sha256_str ( m_aCurrentMapSha256 [ MAP_TYPE_SIXUP ] , aSha256 , sizeof ( aSha256 ) ) ;
2020-06-19 21:52:13 +00:00
str_format ( aBufMsg , sizeof ( aBufMsg ) , " %s sha256 is %s " , aBuf , aSha256 ) ;
Console ( ) - > Print ( IConsole : : OUTPUT_LEVEL_ADDINFO , " sixup " , aBufMsg ) ;
}
}
2022-03-01 22:19:49 +00:00
if ( ! Config ( ) - > m_SvSixup )
2021-12-21 11:23:17 +00:00
{
2022-02-17 18:50:02 +00:00
free ( m_apCurrentMapData [ MAP_TYPE_SIXUP ] ) ;
m_apCurrentMapData [ MAP_TYPE_SIXUP ] = 0 ;
2021-12-21 11:23:17 +00:00
}
2020-06-19 21:52:13 +00:00
2020-09-26 19:41:58 +00:00
for ( int i = 0 ; i < MAX_CLIENTS ; i + + )
2011-01-20 20:25:09 +00:00
m_aPrevStates [ i ] = m_aClients [ i ] . m_State ;
2010-05-29 07:25:38 +00:00
return 1 ;
}
2023-11-12 14:21:47 +00:00
# ifdef CONF_DEBUG
void CServer : : UpdateDebugDummies ( bool ForceDisconnect )
{
if ( m_PreviousDebugDummies = = g_Config . m_DbgDummies & & ! ForceDisconnect )
return ;
for ( int DummyIndex = 0 ; DummyIndex < maximum ( m_PreviousDebugDummies , g_Config . m_DbgDummies ) ; + + DummyIndex )
{
const bool AddDummy = ! ForceDisconnect & & DummyIndex < g_Config . m_DbgDummies ;
const int ClientID = MAX_CLIENTS - DummyIndex - 1 ;
if ( AddDummy & & m_aClients [ ClientID ] . m_State = = CClient : : STATE_EMPTY )
{
NewClientCallback ( ClientID , this , false ) ;
2023-11-22 16:57:42 +00:00
m_aClients [ ClientID ] . m_DebugDummy = true ;
2023-11-12 14:21:47 +00:00
GameServer ( ) - > OnClientConnected ( ClientID , nullptr ) ;
m_aClients [ ClientID ] . m_State = CClient : : STATE_INGAME ;
str_format ( m_aClients [ ClientID ] . m_aName , sizeof ( m_aClients [ ClientID ] . m_aName ) , " Debug dummy %d " , DummyIndex + 1 ) ;
GameServer ( ) - > OnClientEnter ( ClientID ) ;
}
2023-11-22 16:57:42 +00:00
else if ( ! AddDummy & & m_aClients [ ClientID ] . m_DebugDummy )
2023-11-12 14:21:47 +00:00
{
DelClientCallback ( ClientID , " Dropping debug dummy " , this ) ;
}
2023-11-22 16:57:42 +00:00
if ( AddDummy & & m_aClients [ ClientID ] . m_DebugDummy )
2023-11-12 14:21:47 +00:00
{
CNetObj_PlayerInput Input = { 0 } ;
Input . m_Direction = ( ClientID & 1 ) ? - 1 : 1 ;
m_aClients [ ClientID ] . m_aInputs [ 0 ] . m_GameTick = Tick ( ) + 1 ;
mem_copy ( m_aClients [ ClientID ] . m_aInputs [ 0 ] . m_aData , & Input , minimum ( sizeof ( Input ) , sizeof ( m_aClients [ ClientID ] . m_aInputs [ 0 ] . m_aData ) ) ) ;
m_aClients [ ClientID ] . m_LatestInput = m_aClients [ ClientID ] . m_aInputs [ 0 ] ;
m_aClients [ ClientID ] . m_CurrentInput = 0 ;
}
}
m_PreviousDebugDummies = ForceDisconnect ? 0 : g_Config . m_DbgDummies ;
}
# endif
2010-05-29 07:25:38 +00:00
int CServer : : Run ( )
{
2020-07-07 08:23:04 +00:00
if ( m_RunServer = = UNINITIALIZED )
m_RunServer = RUNNING ;
2017-03-02 15:16:29 +00:00
m_AuthManager . Init ( ) ;
2021-12-14 23:28:51 +00:00
if ( Config ( ) - > m_Debug )
2017-05-21 23:07:13 +00:00
{
g_UuidManager . DebugDump ( ) ;
}
2021-02-07 15:03:31 +00:00
{
int Size = GameServer ( ) - > PersistentClientDataSize ( ) ;
for ( auto & Client : m_aClients )
{
2021-04-21 07:45:58 +00:00
Client . m_HasPersistentData = false ;
2021-02-07 15:03:31 +00:00
Client . m_pPersistentData = malloc ( Size ) ;
}
}
2023-08-25 12:52:37 +00:00
m_pPersistentData = malloc ( GameServer ( ) - > PersistentDataSize ( ) ) ;
2021-02-07 15:03:31 +00:00
2010-05-29 07:25:38 +00:00
// load map
2021-12-14 23:28:51 +00:00
if ( ! LoadMap ( Config ( ) - > m_SvMap ) )
2010-05-29 07:25:38 +00:00
{
2023-12-31 14:44:04 +00:00
log_error ( " server " , " failed to load map. mapname='%s' " , Config ( ) - > m_SvMap ) ;
2010-05-29 07:25:38 +00:00
return - 1 ;
}
2011-04-13 18:37:12 +00:00
2021-12-14 23:28:51 +00:00
if ( Config ( ) - > m_SvSqliteFile [ 0 ] ! = ' \0 ' )
2020-07-07 17:29:44 +00:00
{
2022-11-05 15:30:51 +00:00
char aFullPath [ IO_MAX_PATH_LENGTH ] ;
2022-11-06 09:32:26 +00:00
Storage ( ) - > GetCompletePath ( IStorage : : TYPE_SAVE_OR_ABSOLUTE , Config ( ) - > m_SvSqliteFile , aFullPath , sizeof ( aFullPath ) ) ;
2020-07-07 17:29:44 +00:00
2021-12-14 23:28:51 +00:00
if ( Config ( ) - > m_SvUseSQL )
2020-07-07 17:29:44 +00:00
{
2022-12-12 13:13:30 +00:00
DbPool ( ) - > RegisterSqliteDatabase ( CDbConnectionPool : : WRITE_BACKUP , aFullPath ) ;
2020-07-07 17:29:44 +00:00
}
else
{
2022-12-12 13:13:30 +00:00
DbPool ( ) - > RegisterSqliteDatabase ( CDbConnectionPool : : READ , aFullPath ) ;
DbPool ( ) - > RegisterSqliteDatabase ( CDbConnectionPool : : WRITE , aFullPath ) ;
2020-07-07 17:29:44 +00:00
}
}
2010-05-29 07:25:38 +00:00
// start server
NETADDR BindAddr ;
2023-01-11 21:48:52 +00:00
if ( g_Config . m_Bindaddr [ 0 ] = = ' \0 ' )
{
2010-05-29 07:25:38 +00:00
mem_zero ( & BindAddr , sizeof ( BindAddr ) ) ;
2023-01-11 21:48:52 +00:00
}
else if ( net_host_lookup ( g_Config . m_Bindaddr , & BindAddr , NETTYPE_ALL ) ! = 0 )
{
2023-12-31 14:44:04 +00:00
log_error ( " server " , " The configured bindaddr '%s' cannot be resolved " , g_Config . m_Bindaddr ) ;
2023-01-11 21:48:52 +00:00
return - 1 ;
}
BindAddr . type = Config ( ) - > m_SvIpv4Only ? NETTYPE_IPV4 : NETTYPE_ALL ;
2020-04-14 10:11:50 +00:00
2021-12-14 23:28:51 +00:00
int Port = Config ( ) - > m_SvPort ;
2022-03-01 16:34:42 +00:00
for ( BindAddr . port = Port ! = 0 ? Port : 8303 ; ! m_NetServer . Open ( BindAddr , & m_ServerBan , Config ( ) - > m_SvMaxClients , Config ( ) - > m_SvMaxClientsPerIP ) ; BindAddr . port + + )
2010-05-29 07:25:38 +00:00
{
2020-09-26 19:41:58 +00:00
if ( Port ! = 0 | | BindAddr . port > = 8310 )
2020-06-30 17:01:57 +00:00
{
2023-12-31 14:44:04 +00:00
log_error ( " server " , " couldn't open socket. port %d might already be in use " , BindAddr . port ) ;
2020-06-30 17:01:57 +00:00
return - 1 ;
}
}
2020-07-09 16:01:52 +00:00
2020-09-26 19:41:58 +00:00
if ( Port = = 0 )
2023-12-31 14:44:04 +00:00
log_info ( " server " , " using port %d " , BindAddr . port ) ;
2020-07-09 16:01:52 +00:00
# if defined(CONF_UPNP)
m_UPnP . Open ( BindAddr ) ;
# endif
2010-05-29 07:25:38 +00:00
Allow background jobs to be aborted, refactoring
Add `IJob::Abortable(bool)` function which jobs can call to specify whether they can be aborted. Jobs are not abortable per default. Abortable jobs may have their state set to `IJob::STATE_ABORTED` at any point if the job was aborted. The job state should be checked periodically in the `IJob::Run` function and the job should terminate at the earliest, safe opportunity when aborted. Scheduled jobs which are not abortable are guaranteed to fully complete before the job pool is shut down. However, if the job pool is already shutting down, no additional jobs will be enqueue anymore and abortable jobs will immediately be aborted.
In particular, the sound loading, community icon loading, master chooser and host lookup jobs are specified as being abortable. Conversely, the jobs saving replay demos, editor maps and screenshots are expected to finish before the client is shut down.
When the client is quitting/restarting, it will now disconnect from the current server first, before saving the config, to ensure that any actions that happen on disconnect (demo recorders being stopped etc.) happen first. The shutdown message is rendered before disconnecting and waiting for background jobs to finish.
The HTTP client is now initialized later during server launch, after the network initialization. Error handling is added and the server stops if the HTTP client could not be initialized, same as the client.
The `RunBlocking` functions are removed, as they are not used anymore after curl-multi was added.
The function `IJob::Status` is renamed to `State` and `IJob::STATE_PENDING` is renamed to `STATE_QUEUED` for consistency with naming of the HTTP client.
The member variables of the engine interface are encapsulated and the `jobs.h` include is removed from `engine.h`, which removes transitive includes of `system.h`.
Documentation for all job and job pool API is added.
2024-02-16 19:44:33 +00:00
if ( ! m_Http . Init ( std : : chrono : : seconds { 2 } ) )
{
log_error ( " server " , " Failed to initialize the HTTP client. " ) ;
return - 1 ;
}
m_pEngine = Kernel ( ) - > RequestInterface < IEngine > ( ) ;
m_pRegister = CreateRegister ( & g_Config , m_pConsole , m_pEngine , & m_Http , this - > Port ( ) , m_NetServer . GetGlobalToken ( ) ) ;
Add HTTP masterserver registering and HTTP masterserver
Registering
-----------
The idea is that game servers push their server info to the
masterservers every 15 seconds or when the server info changes, but not
more than once per second.
The game servers do not support the old registering protocol anymore,
the backward compatibility is handled by the masterserver.
The register call is a HTTP POST to a URL like
`https://master1.ddnet.tw/ddnet/15/register` and looks like this:
```json
POST /ddnet/15/register HTTP/1.1
Address: tw-0.6+udp://connecting-address.invalid:8303
Secret: 81fa3955-6f83-4290-818d-31c0906b1118
Challenge-Secret: 81fa3955-6f83-4290-818d-31c0906b1118:tw0.6/ipv6
Info-Serial: 0
{
"max_clients": 64,
"max_players": 64,
"passworded": false,
"game_type": "TestDDraceNetwork",
"name": "My DDNet server",
"map": {
"name": "dm1",
"sha256": "0b0c481d77519c32fbe85624ef16ec0fa9991aec7367ad538bd280f28d8c26cf",
"size": 5805
},
"version": "0.6.4, 16.0.3",
"clients": []
}
```
The `Address` header declares that the server wants to register itself as
a `tw-0.6+udp` server, i.e. a server speaking a Teeworlds-0.6-compatible
protocol.
The free-form `Secret` header is used as a server identity, the server
list will be deduplicated via this secret.
The free-form `Challenge-Secret` is sent back via UDP for a port forward
check. This might have security implications as the masterserver can be
asked to send a UDP packet containing some user-controlled bytes. This
is somewhat mitigated by the fact that it can only go to an
attacker-controlled IP address.
The `Info-Serial` header is an integer field that should increase each
time the server info (in the body) changes. The masterserver uses that
field to ensure that it doesn't use old server infos.
The body is a free-form JSON object set by the game server. It should
contain certain keys in the correct form to be accepted by clients. The
body is optional if the masterserver already confirmed the reception of
the info with the given `Info-Serial`.
Not shown in this payload is the `Connless-Token` header that is used
for Teeworlds 0.7 style communication.
Also not shown is the `Challenge-Token` that should be included once the
server receives the challenge token via UDP.
The masterserver responds with a `200 OK` with a body like this:
```
{"status":"success"}
```
The `status` field can be `success` if the server was successfully
registered on the masterserver, `need_challenge` if the masterserver
wants the correct `Challenge-Token` header before the register process
is successful, `need_info` if the server sent an empty body but the
masterserver doesn't actually know the server info.
It can also be `error` if the request was malformed, only in this case
an HTTP status code except `200 OK` is sent.
Synchronization
---------------
The masterserver keeps state and outputs JSON files every second.
```json
{
"servers": [
{
"addresses": [
"tw-0.6+udp://127.0.0.1:8303",
"tw-0.6+udp://[::1]:8303"
],
"info_serial": 0,
"info": {
"max_clients": 64,
"max_players": 64,
"passworded": false,
"game_type": "TestDDraceNetwork",
"name": "My DDNet server",
"map": {
"name": "dm1",
"sha256": "0b0c481d77519c32fbe85624ef16ec0fa9991aec7367ad538bd280f28d8c26cf",
"size": 5805
},
"version": "0.6.4, 16.0.3",
"clients": []
}
}
]
}
```
`servers.json` (or configured by `--out`) is a server list that is
compatible with DDNet 15.5+ clients. It is a JSON object containing a
single key `servers` with a list of game servers. Each game server is
represented by a JSON object with an `addresses` key containing a list
of all known addresses of the server and an `info` key containing the
free-form server info sent by the game server. The free-form `info` JSON
object re-encoded by the master server and thus canonicalized and
stripped of any whitespace characters outside strings.
```json
{
"kind": "mastersrv",
"now": 1816002,
"secrets": {
"tw-0.6+udp://127.0.0.1:8303": {
"ping_time": 1811999,
"secret": "42d8f991-f2fa-46e5-a9ae-ebcc93846feb"
},
"tw-0.6+udp://[::1]:8303": {
"ping_time": 1811999,
"secret": "42d8f991-f2fa-46e5-a9ae-ebcc93846feb"
}
},
"servers": {
"42d8f991-f2fa-46e5-a9ae-ebcc93846feb": {
"info_serial": 0,
"info": {
"max_clients": 64,
"max_players": 64,
"passworded": false,
"game_type": "TestDDraceNetwork",
"name": "My DDNet server",
"map": {
"name": "dm1",
"sha256": "0b0c481d77519c32fbe85624ef16ec0fa9991aec7367ad538bd280f28d8c26cf",
"size": 5805
},
"version": "0.6.4, 16.0.3",
"clients": []
}
}
}
}
```
`--write-dump` outputs a JSON file compatible with `--read-dump-dir`,
this can be used to synchronize servers across different masterservers.
`--read-dump-dir` is also used to ingest servers from the backward
compatibility layer that pings each server for their server info using
the old protocol.
The `kind` field describe that this is `mastersrv` output and not from a
`backcompat`. This is used for prioritizing `mastersrv` information over
`backcompat` information.
The `now` field contains an integer describing the current time in
milliseconds relative an unspecified epoch that is fixed for each JSON
file. This is done instead of using the current time as the epoch for
better compression of non-changing data.
`secrets` is a map from each server address and to a JSON object
containing the last ping time (`ping_time`) in milliseconds relative to
the same epoch as before, and the server secret (`secret`) that is used
to unify server infos from different addresses of the same logical
server.
`servers` is a map from the aforementioned `secret`s to the
corresponding `info_serial` and `info`.
```json
[
"tw-0.6+udp://127.0.0.1:8303",
"tw-0.6+udp://[::1]:8303"
]
```
`--write-addresses` outputs a JSON file containing all addresses
corresponding to servers that are registered to HTTP masterservers. It
does not contain the servers that are obtained via backward
compatibility measures.
This file can be used by an old-style masterserver to also list
new-style servers without the game servers having to register there.
An implementation of this can be found at
https://github.com/heinrich5991/teeworlds/tree/mastersrv_6_backcompat
for Teeworlds 0.5/0.6 masterservers and at
https://github.com/heinrich5991/teeworlds/tree/mastersrv_7_backcompat
for Teeworlds 0.7 masterservers.
All these JSON files can be sent over the network in an efficient way
using https://github.com/heinrich5991/twmaster-collect. It establishes a
zstd-compressed TCP connection authenticated by a string token that is
sent in plain-text. It watches the specified file and transmits it every
time it changes. Due to the zstd-compression, the data sent over the
network is similar to the size of a diff.
Implementation
--------------
The masterserver implementation was done in Rust.
The current gameserver register implementation doesn't support more than
one masterserver for registering.
2022-05-19 20:03:17 +00:00
2015-08-23 15:51:28 +00:00
m_NetServer . SetCallbacks ( NewClientCallback , NewClientNoAuthCallback , ClientRejoinCallback , DelClientCallback , this ) ;
2011-04-13 18:37:12 +00:00
2021-01-10 12:47:07 +00:00
m_Econ . Init ( Config ( ) , Console ( ) , & m_ServerBan ) ;
2011-07-02 06:36:14 +00:00
2021-12-14 23:28:51 +00:00
m_Fifo . Init ( Console ( ) , Config ( ) - > m_SvInputFifo , CFGFLAG_SERVER ) ;
2016-05-02 21:36:21 +00:00
2010-08-17 22:06:00 +00:00
char aBuf [ 256 ] ;
2021-12-14 23:28:51 +00:00
str_format ( aBuf , sizeof ( aBuf ) , " server name is '%s' " , Config ( ) - > m_SvName ) ;
2010-08-17 22:06:00 +00:00
Console ( ) - > Print ( IConsole : : OUTPUT_LEVEL_STANDARD , " server " , aBuf ) ;
2011-04-13 18:37:12 +00:00
2020-05-13 20:27:49 +00:00
Antibot ( ) - > Init ( ) ;
2023-08-25 12:52:37 +00:00
GameServer ( ) - > OnInit ( nullptr ) ;
2017-10-13 00:25:50 +00:00
if ( ErrorShutdown ( ) )
{
2020-07-07 08:23:04 +00:00
m_RunServer = STOPPING ;
2017-10-13 00:25:50 +00:00
}
2021-12-21 22:05:44 +00:00
Console ( ) - > Print ( IConsole : : OUTPUT_LEVEL_STANDARD , " server " , " version " GAME_RELEASE_VERSION " on " CONF_PLATFORM_STRING " " CONF_ARCH_STRING ) ;
if ( GIT_SHORTREV_HASH )
{
str_format ( aBuf , sizeof ( aBuf ) , " git revision hash: %s " , GIT_SHORTREV_HASH ) ;
Console ( ) - > Print ( IConsole : : OUTPUT_LEVEL_STANDARD , " server " , aBuf ) ;
}
2010-05-29 07:25:38 +00:00
2010-08-07 18:22:25 +00:00
// process pending commands
2011-08-13 00:11:06 +00:00
m_pConsole - > StoreCommands ( false ) ;
Add HTTP masterserver registering and HTTP masterserver
Registering
-----------
The idea is that game servers push their server info to the
masterservers every 15 seconds or when the server info changes, but not
more than once per second.
The game servers do not support the old registering protocol anymore,
the backward compatibility is handled by the masterserver.
The register call is a HTTP POST to a URL like
`https://master1.ddnet.tw/ddnet/15/register` and looks like this:
```json
POST /ddnet/15/register HTTP/1.1
Address: tw-0.6+udp://connecting-address.invalid:8303
Secret: 81fa3955-6f83-4290-818d-31c0906b1118
Challenge-Secret: 81fa3955-6f83-4290-818d-31c0906b1118:tw0.6/ipv6
Info-Serial: 0
{
"max_clients": 64,
"max_players": 64,
"passworded": false,
"game_type": "TestDDraceNetwork",
"name": "My DDNet server",
"map": {
"name": "dm1",
"sha256": "0b0c481d77519c32fbe85624ef16ec0fa9991aec7367ad538bd280f28d8c26cf",
"size": 5805
},
"version": "0.6.4, 16.0.3",
"clients": []
}
```
The `Address` header declares that the server wants to register itself as
a `tw-0.6+udp` server, i.e. a server speaking a Teeworlds-0.6-compatible
protocol.
The free-form `Secret` header is used as a server identity, the server
list will be deduplicated via this secret.
The free-form `Challenge-Secret` is sent back via UDP for a port forward
check. This might have security implications as the masterserver can be
asked to send a UDP packet containing some user-controlled bytes. This
is somewhat mitigated by the fact that it can only go to an
attacker-controlled IP address.
The `Info-Serial` header is an integer field that should increase each
time the server info (in the body) changes. The masterserver uses that
field to ensure that it doesn't use old server infos.
The body is a free-form JSON object set by the game server. It should
contain certain keys in the correct form to be accepted by clients. The
body is optional if the masterserver already confirmed the reception of
the info with the given `Info-Serial`.
Not shown in this payload is the `Connless-Token` header that is used
for Teeworlds 0.7 style communication.
Also not shown is the `Challenge-Token` that should be included once the
server receives the challenge token via UDP.
The masterserver responds with a `200 OK` with a body like this:
```
{"status":"success"}
```
The `status` field can be `success` if the server was successfully
registered on the masterserver, `need_challenge` if the masterserver
wants the correct `Challenge-Token` header before the register process
is successful, `need_info` if the server sent an empty body but the
masterserver doesn't actually know the server info.
It can also be `error` if the request was malformed, only in this case
an HTTP status code except `200 OK` is sent.
Synchronization
---------------
The masterserver keeps state and outputs JSON files every second.
```json
{
"servers": [
{
"addresses": [
"tw-0.6+udp://127.0.0.1:8303",
"tw-0.6+udp://[::1]:8303"
],
"info_serial": 0,
"info": {
"max_clients": 64,
"max_players": 64,
"passworded": false,
"game_type": "TestDDraceNetwork",
"name": "My DDNet server",
"map": {
"name": "dm1",
"sha256": "0b0c481d77519c32fbe85624ef16ec0fa9991aec7367ad538bd280f28d8c26cf",
"size": 5805
},
"version": "0.6.4, 16.0.3",
"clients": []
}
}
]
}
```
`servers.json` (or configured by `--out`) is a server list that is
compatible with DDNet 15.5+ clients. It is a JSON object containing a
single key `servers` with a list of game servers. Each game server is
represented by a JSON object with an `addresses` key containing a list
of all known addresses of the server and an `info` key containing the
free-form server info sent by the game server. The free-form `info` JSON
object re-encoded by the master server and thus canonicalized and
stripped of any whitespace characters outside strings.
```json
{
"kind": "mastersrv",
"now": 1816002,
"secrets": {
"tw-0.6+udp://127.0.0.1:8303": {
"ping_time": 1811999,
"secret": "42d8f991-f2fa-46e5-a9ae-ebcc93846feb"
},
"tw-0.6+udp://[::1]:8303": {
"ping_time": 1811999,
"secret": "42d8f991-f2fa-46e5-a9ae-ebcc93846feb"
}
},
"servers": {
"42d8f991-f2fa-46e5-a9ae-ebcc93846feb": {
"info_serial": 0,
"info": {
"max_clients": 64,
"max_players": 64,
"passworded": false,
"game_type": "TestDDraceNetwork",
"name": "My DDNet server",
"map": {
"name": "dm1",
"sha256": "0b0c481d77519c32fbe85624ef16ec0fa9991aec7367ad538bd280f28d8c26cf",
"size": 5805
},
"version": "0.6.4, 16.0.3",
"clients": []
}
}
}
}
```
`--write-dump` outputs a JSON file compatible with `--read-dump-dir`,
this can be used to synchronize servers across different masterservers.
`--read-dump-dir` is also used to ingest servers from the backward
compatibility layer that pings each server for their server info using
the old protocol.
The `kind` field describe that this is `mastersrv` output and not from a
`backcompat`. This is used for prioritizing `mastersrv` information over
`backcompat` information.
The `now` field contains an integer describing the current time in
milliseconds relative an unspecified epoch that is fixed for each JSON
file. This is done instead of using the current time as the epoch for
better compression of non-changing data.
`secrets` is a map from each server address and to a JSON object
containing the last ping time (`ping_time`) in milliseconds relative to
the same epoch as before, and the server secret (`secret`) that is used
to unify server infos from different addresses of the same logical
server.
`servers` is a map from the aforementioned `secret`s to the
corresponding `info_serial` and `info`.
```json
[
"tw-0.6+udp://127.0.0.1:8303",
"tw-0.6+udp://[::1]:8303"
]
```
`--write-addresses` outputs a JSON file containing all addresses
corresponding to servers that are registered to HTTP masterservers. It
does not contain the servers that are obtained via backward
compatibility measures.
This file can be used by an old-style masterserver to also list
new-style servers without the game servers having to register there.
An implementation of this can be found at
https://github.com/heinrich5991/teeworlds/tree/mastersrv_6_backcompat
for Teeworlds 0.5/0.6 masterservers and at
https://github.com/heinrich5991/teeworlds/tree/mastersrv_7_backcompat
for Teeworlds 0.7 masterservers.
All these JSON files can be sent over the network in an efficient way
using https://github.com/heinrich5991/twmaster-collect. It establishes a
zstd-compressed TCP connection authenticated by a string token that is
sent in plain-text. It watches the specified file and transmits it every
time it changes. Due to the zstd-compression, the data sent over the
network is similar to the size of a diff.
Implementation
--------------
The masterserver implementation was done in Rust.
The current gameserver register implementation doesn't support more than
one masterserver for registering.
2022-05-19 20:03:17 +00:00
m_pRegister - > OnConfigChange ( ) ;
2010-08-07 18:22:25 +00:00
2017-03-04 20:16:02 +00:00
if ( m_AuthManager . IsGenerated ( ) )
2015-06-21 17:46:55 +00:00
{
2023-12-31 14:44:04 +00:00
log_info ( " server " , " +-------------------------+ " ) ;
log_info ( " server " , " | rcon password: '%s' | " , Config ( ) - > m_SvRconPassword ) ;
log_info ( " server " , " +-------------------------+ " ) ;
2015-06-21 17:46:55 +00:00
}
2010-05-29 07:25:38 +00:00
// start game
{
2014-11-24 21:31:13 +00:00
bool NonActive = false ;
2020-09-13 21:23:50 +00:00
bool PacketWaiting = false ;
2011-04-13 18:37:12 +00:00
2010-05-29 07:25:38 +00:00
m_GameStartTime = time_get ( ) ;
2011-04-13 18:37:12 +00:00
2019-11-03 00:07:10 +00:00
UpdateServerInfo ( ) ;
2020-07-07 08:23:04 +00:00
while ( m_RunServer < STOPPING )
2010-05-29 07:25:38 +00:00
{
2014-11-24 21:31:13 +00:00
if ( NonActive )
2020-09-13 21:23:50 +00:00
PumpNetwork ( PacketWaiting ) ;
2014-11-24 21:31:13 +00:00
2014-11-09 23:08:50 +00:00
set_new_tick ( ) ;
2014-11-24 21:31:13 +00:00
2021-06-23 05:05:49 +00:00
int64_t t = time_get ( ) ;
2010-05-29 07:25:38 +00:00
int NewTicks = 0 ;
2011-04-13 18:37:12 +00:00
2022-05-31 15:33:00 +00:00
// load new map
2023-01-03 12:07:03 +00:00
if ( m_MapReload | | m_CurrentGameTick > = MAX_TICK ) // force reload to make sure the ticks stay within a valid range
2010-05-29 07:25:38 +00:00
{
// load map
2021-12-14 23:28:51 +00:00
if ( LoadMap ( Config ( ) - > m_SvMap ) )
2010-05-29 07:25:38 +00:00
{
// new map loaded
2021-02-07 15:03:31 +00:00
// ask the game to for the data it wants to persist past a map change
for ( int i = 0 ; i < MAX_CLIENTS ; i + + )
{
if ( m_aClients [ i ] . m_State = = CClient : : STATE_INGAME )
{
m_aClients [ i ] . m_HasPersistentData = GameServer ( ) - > OnClientDataPersist ( i , m_aClients [ i ] . m_pPersistentData ) ;
}
}
2023-11-12 14:21:47 +00:00
# ifdef CONF_DEBUG
UpdateDebugDummies ( true ) ;
# endif
2023-08-25 12:52:37 +00:00
GameServer ( ) - > OnShutdown ( m_pPersistentData ) ;
2011-04-13 18:37:12 +00:00
2017-04-17 10:13:58 +00:00
for ( int ClientID = 0 ; ClientID < MAX_CLIENTS ; ClientID + + )
2010-05-29 07:25:38 +00:00
{
2017-04-17 10:13:58 +00:00
if ( m_aClients [ ClientID ] . m_State < = CClient : : STATE_AUTH )
2010-05-29 07:25:38 +00:00
continue ;
2011-04-13 18:37:12 +00:00
2017-04-17 10:13:58 +00:00
SendMap ( ClientID ) ;
2021-02-07 15:03:31 +00:00
bool HasPersistentData = m_aClients [ ClientID ] . m_HasPersistentData ;
2017-04-17 10:13:58 +00:00
m_aClients [ ClientID ] . Reset ( ) ;
2021-02-07 15:03:31 +00:00
m_aClients [ ClientID ] . m_HasPersistentData = HasPersistentData ;
2017-04-17 10:13:58 +00:00
m_aClients [ ClientID ] . m_State = CClient : : STATE_CONNECTING ;
2010-05-29 07:25:38 +00:00
}
2011-04-13 18:37:12 +00:00
2010-05-29 07:25:38 +00:00
m_GameStartTime = time_get ( ) ;
2023-01-03 12:07:03 +00:00
m_CurrentGameTick = MIN_TICK ;
2016-01-22 16:53:46 +00:00
m_ServerInfoFirstRequest = 0 ;
2010-05-29 07:25:38 +00:00
Kernel ( ) - > ReregisterInterface ( GameServer ( ) ) ;
2023-08-25 12:52:37 +00:00
GameServer ( ) - > OnInit ( m_pPersistentData ) ;
2017-10-13 00:25:50 +00:00
if ( ErrorShutdown ( ) )
{
break ;
}
2019-11-03 00:07:10 +00:00
UpdateServerInfo ( true ) ;
2023-06-15 14:09:31 +00:00
for ( int ClientID = 0 ; ClientID < MAX_CLIENTS ; ClientID + + )
{
if ( m_aClients [ ClientID ] . m_State ! = CClient : : STATE_CONNECTING )
continue ;
// When doing a map change, a new Teehistorian file is created. For players that are already
// on the server, no PlayerJoin event is produced in Teehistorian from the network engine.
// Record PlayerJoin events here to record the Sixup version and player join event.
2023-06-15 19:00:14 +00:00
GameServer ( ) - > TeehistorianRecordPlayerJoin ( ClientID , m_aClients [ ClientID ] . m_Sixup ) ;
2023-06-15 14:09:31 +00:00
}
2010-05-29 07:25:38 +00:00
}
else
{
2021-12-14 23:28:51 +00:00
str_format ( aBuf , sizeof ( aBuf ) , " failed to load map. mapname='%s' " , Config ( ) - > m_SvMap ) ;
2010-08-17 22:06:00 +00:00
Console ( ) - > Print ( IConsole : : OUTPUT_LEVEL_STANDARD , " server " , aBuf ) ;
2022-07-09 16:14:56 +00:00
str_copy ( Config ( ) - > m_SvMap , m_aCurrentMap ) ;
2010-05-29 07:25:38 +00:00
}
}
2011-04-13 18:37:12 +00:00
2016-09-05 09:38:11 +00:00
// handle dnsbl
2021-12-14 23:28:51 +00:00
if ( Config ( ) - > m_SvDnsbl )
2016-09-05 09:38:11 +00:00
{
2020-09-26 19:41:58 +00:00
for ( int ClientID = 0 ; ClientID < MAX_CLIENTS ; ClientID + + )
2016-09-05 09:38:11 +00:00
{
2020-09-26 19:41:58 +00:00
if ( m_aClients [ ClientID ] . m_State = = CClient : : STATE_EMPTY )
2016-09-05 09:38:11 +00:00
continue ;
2020-09-26 19:41:58 +00:00
if ( m_aClients [ ClientID ] . m_DnsblState = = CClient : : DNSBL_STATE_NONE )
2016-09-05 09:38:11 +00:00
{
// initiate dnsbl lookup
2017-04-17 10:13:58 +00:00
InitDnsbl ( ClientID ) ;
2016-09-05 09:38:11 +00:00
}
2020-09-26 19:41:58 +00:00
else if ( m_aClients [ ClientID ] . m_DnsblState = = CClient : : DNSBL_STATE_PENDING & &
Allow background jobs to be aborted, refactoring
Add `IJob::Abortable(bool)` function which jobs can call to specify whether they can be aborted. Jobs are not abortable per default. Abortable jobs may have their state set to `IJob::STATE_ABORTED` at any point if the job was aborted. The job state should be checked periodically in the `IJob::Run` function and the job should terminate at the earliest, safe opportunity when aborted. Scheduled jobs which are not abortable are guaranteed to fully complete before the job pool is shut down. However, if the job pool is already shutting down, no additional jobs will be enqueue anymore and abortable jobs will immediately be aborted.
In particular, the sound loading, community icon loading, master chooser and host lookup jobs are specified as being abortable. Conversely, the jobs saving replay demos, editor maps and screenshots are expected to finish before the client is shut down.
When the client is quitting/restarting, it will now disconnect from the current server first, before saving the config, to ensure that any actions that happen on disconnect (demo recorders being stopped etc.) happen first. The shutdown message is rendered before disconnecting and waiting for background jobs to finish.
The HTTP client is now initialized later during server launch, after the network initialization. Error handling is added and the server stops if the HTTP client could not be initialized, same as the client.
The `RunBlocking` functions are removed, as they are not used anymore after curl-multi was added.
The function `IJob::Status` is renamed to `State` and `IJob::STATE_PENDING` is renamed to `STATE_QUEUED` for consistency with naming of the HTTP client.
The member variables of the engine interface are encapsulated and the `jobs.h` include is removed from `engine.h`, which removes transitive includes of `system.h`.
Documentation for all job and job pool API is added.
2024-02-16 19:44:33 +00:00
m_aClients [ ClientID ] . m_pDnsblLookup - > State ( ) = = IJob : : STATE_DONE )
2016-09-05 09:38:11 +00:00
{
2023-10-22 11:29:25 +00:00
if ( m_aClients [ ClientID ] . m_pDnsblLookup - > Result ( ) ! = 0 )
2016-09-05 09:38:11 +00:00
{
// entry not found -> whitelisted
2017-04-17 10:13:58 +00:00
m_aClients [ ClientID ] . m_DnsblState = CClient : : DNSBL_STATE_WHITELISTED ;
2016-09-05 09:38:11 +00:00
}
else
{
// entry found -> blacklisted
2017-04-17 10:13:58 +00:00
m_aClients [ ClientID ] . m_DnsblState = CClient : : DNSBL_STATE_BLACKLISTED ;
2016-09-05 09:38:11 +00:00
// console output
char aAddrStr [ NETADDR_MAXSTRSIZE ] ;
2017-04-17 10:13:58 +00:00
net_addr_str ( m_NetServer . ClientAddr ( ClientID ) , aAddrStr , sizeof ( aAddrStr ) , true ) ;
2016-09-05 09:38:11 +00:00
2020-09-26 19:41:58 +00:00
str_format ( aBuf , sizeof ( aBuf ) , " ClientID=%d addr=<{%s}> secure=%s blacklisted " , ClientID , aAddrStr , m_NetServer . HasSecurityToken ( ClientID ) ? " yes " : " no " ) ;
2018-10-07 22:59:07 +00:00
2016-09-05 09:38:11 +00:00
Console ( ) - > Print ( IConsole : : OUTPUT_LEVEL_ADDINFO , " dnsbl " , aBuf ) ;
}
}
2020-09-26 19:41:58 +00:00
if ( m_aClients [ ClientID ] . m_DnsblState = = CClient : : DNSBL_STATE_BLACKLISTED & &
2021-12-14 23:28:51 +00:00
Config ( ) - > m_SvDnsblBan )
2020-09-26 19:41:58 +00:00
m_NetServer . NetBan ( ) - > BanAddr ( m_NetServer . ClientAddr ( ClientID ) , 60 * 10 , " VPN detected, try connecting without. Contact admin if mistaken " ) ;
2016-09-05 09:38:11 +00:00
}
}
2020-09-26 19:41:58 +00:00
while ( t > TickStartTime ( m_CurrentGameTick + 1 ) )
2010-05-29 07:25:38 +00:00
{
2021-09-14 20:11:22 +00:00
GameServer ( ) - > OnPreTickTeehistorian ( ) ;
2023-11-12 14:21:47 +00:00
# ifdef CONF_DEBUG
UpdateDebugDummies ( false ) ;
# endif
2019-01-29 19:32:11 +00:00
for ( int c = 0 ; c < MAX_CLIENTS ; c + + )
2022-03-27 11:31:07 +00:00
{
if ( m_aClients [ c ] . m_State ! = CClient : : STATE_INGAME )
continue ;
bool ClientHadInput = false ;
for ( auto & Input : m_aClients [ c ] . m_aInputs )
{
if ( Input . m_GameTick = = Tick ( ) + 1 )
{
GameServer ( ) - > OnClientPredictedEarlyInput ( c , Input . m_aData ) ;
ClientHadInput = true ;
}
}
if ( ! ClientHadInput )
GameServer ( ) - > OnClientPredictedEarlyInput ( c , nullptr ) ;
}
2019-01-29 19:32:11 +00:00
2010-05-29 07:25:38 +00:00
m_CurrentGameTick + + ;
NewTicks + + ;
2011-04-13 18:37:12 +00:00
2010-05-29 07:25:38 +00:00
// apply new input
for ( int c = 0 ; c < MAX_CLIENTS ; c + + )
{
2014-01-15 15:52:22 +00:00
if ( m_aClients [ c ] . m_State ! = CClient : : STATE_INGAME )
2010-05-29 07:25:38 +00:00
continue ;
2022-03-27 11:31:07 +00:00
bool ClientHadInput = false ;
2020-10-26 14:14:07 +00:00
for ( auto & Input : m_aClients [ c ] . m_aInputs )
2010-05-29 07:25:38 +00:00
{
2020-10-26 14:14:07 +00:00
if ( Input . m_GameTick = = Tick ( ) )
2010-05-29 07:25:38 +00:00
{
2020-10-26 14:14:07 +00:00
GameServer ( ) - > OnClientPredictedInput ( c , Input . m_aData ) ;
2022-03-27 11:31:07 +00:00
ClientHadInput = true ;
2010-05-29 07:25:38 +00:00
break ;
}
}
2022-03-27 11:31:07 +00:00
if ( ! ClientHadInput )
GameServer ( ) - > OnClientPredictedInput ( c , nullptr ) ;
2010-05-29 07:25:38 +00:00
}
GameServer ( ) - > OnTick ( ) ;
2017-10-13 00:25:50 +00:00
if ( ErrorShutdown ( ) )
{
break ;
}
2010-05-29 07:25:38 +00:00
}
2011-04-13 18:37:12 +00:00
2010-05-29 07:25:38 +00:00
// snap game
if ( NewTicks )
{
2021-12-14 23:28:51 +00:00
if ( Config ( ) - > m_SvHighBandwidth | | ( m_CurrentGameTick % 2 ) = = 0 )
2010-05-29 07:25:38 +00:00
DoSnapshot ( ) ;
2011-07-14 20:07:21 +00:00
UpdateClientRconCommands ( ) ;
2018-12-17 19:05:50 +00:00
2018-12-20 08:17:31 +00:00
m_Fifo . Update ( ) ;
2010-05-29 07:25:38 +00:00
}
2011-04-13 18:37:12 +00:00
2010-05-29 07:25:38 +00:00
// master server stuff
Add HTTP masterserver registering and HTTP masterserver
Registering
-----------
The idea is that game servers push their server info to the
masterservers every 15 seconds or when the server info changes, but not
more than once per second.
The game servers do not support the old registering protocol anymore,
the backward compatibility is handled by the masterserver.
The register call is a HTTP POST to a URL like
`https://master1.ddnet.tw/ddnet/15/register` and looks like this:
```json
POST /ddnet/15/register HTTP/1.1
Address: tw-0.6+udp://connecting-address.invalid:8303
Secret: 81fa3955-6f83-4290-818d-31c0906b1118
Challenge-Secret: 81fa3955-6f83-4290-818d-31c0906b1118:tw0.6/ipv6
Info-Serial: 0
{
"max_clients": 64,
"max_players": 64,
"passworded": false,
"game_type": "TestDDraceNetwork",
"name": "My DDNet server",
"map": {
"name": "dm1",
"sha256": "0b0c481d77519c32fbe85624ef16ec0fa9991aec7367ad538bd280f28d8c26cf",
"size": 5805
},
"version": "0.6.4, 16.0.3",
"clients": []
}
```
The `Address` header declares that the server wants to register itself as
a `tw-0.6+udp` server, i.e. a server speaking a Teeworlds-0.6-compatible
protocol.
The free-form `Secret` header is used as a server identity, the server
list will be deduplicated via this secret.
The free-form `Challenge-Secret` is sent back via UDP for a port forward
check. This might have security implications as the masterserver can be
asked to send a UDP packet containing some user-controlled bytes. This
is somewhat mitigated by the fact that it can only go to an
attacker-controlled IP address.
The `Info-Serial` header is an integer field that should increase each
time the server info (in the body) changes. The masterserver uses that
field to ensure that it doesn't use old server infos.
The body is a free-form JSON object set by the game server. It should
contain certain keys in the correct form to be accepted by clients. The
body is optional if the masterserver already confirmed the reception of
the info with the given `Info-Serial`.
Not shown in this payload is the `Connless-Token` header that is used
for Teeworlds 0.7 style communication.
Also not shown is the `Challenge-Token` that should be included once the
server receives the challenge token via UDP.
The masterserver responds with a `200 OK` with a body like this:
```
{"status":"success"}
```
The `status` field can be `success` if the server was successfully
registered on the masterserver, `need_challenge` if the masterserver
wants the correct `Challenge-Token` header before the register process
is successful, `need_info` if the server sent an empty body but the
masterserver doesn't actually know the server info.
It can also be `error` if the request was malformed, only in this case
an HTTP status code except `200 OK` is sent.
Synchronization
---------------
The masterserver keeps state and outputs JSON files every second.
```json
{
"servers": [
{
"addresses": [
"tw-0.6+udp://127.0.0.1:8303",
"tw-0.6+udp://[::1]:8303"
],
"info_serial": 0,
"info": {
"max_clients": 64,
"max_players": 64,
"passworded": false,
"game_type": "TestDDraceNetwork",
"name": "My DDNet server",
"map": {
"name": "dm1",
"sha256": "0b0c481d77519c32fbe85624ef16ec0fa9991aec7367ad538bd280f28d8c26cf",
"size": 5805
},
"version": "0.6.4, 16.0.3",
"clients": []
}
}
]
}
```
`servers.json` (or configured by `--out`) is a server list that is
compatible with DDNet 15.5+ clients. It is a JSON object containing a
single key `servers` with a list of game servers. Each game server is
represented by a JSON object with an `addresses` key containing a list
of all known addresses of the server and an `info` key containing the
free-form server info sent by the game server. The free-form `info` JSON
object re-encoded by the master server and thus canonicalized and
stripped of any whitespace characters outside strings.
```json
{
"kind": "mastersrv",
"now": 1816002,
"secrets": {
"tw-0.6+udp://127.0.0.1:8303": {
"ping_time": 1811999,
"secret": "42d8f991-f2fa-46e5-a9ae-ebcc93846feb"
},
"tw-0.6+udp://[::1]:8303": {
"ping_time": 1811999,
"secret": "42d8f991-f2fa-46e5-a9ae-ebcc93846feb"
}
},
"servers": {
"42d8f991-f2fa-46e5-a9ae-ebcc93846feb": {
"info_serial": 0,
"info": {
"max_clients": 64,
"max_players": 64,
"passworded": false,
"game_type": "TestDDraceNetwork",
"name": "My DDNet server",
"map": {
"name": "dm1",
"sha256": "0b0c481d77519c32fbe85624ef16ec0fa9991aec7367ad538bd280f28d8c26cf",
"size": 5805
},
"version": "0.6.4, 16.0.3",
"clients": []
}
}
}
}
```
`--write-dump` outputs a JSON file compatible with `--read-dump-dir`,
this can be used to synchronize servers across different masterservers.
`--read-dump-dir` is also used to ingest servers from the backward
compatibility layer that pings each server for their server info using
the old protocol.
The `kind` field describe that this is `mastersrv` output and not from a
`backcompat`. This is used for prioritizing `mastersrv` information over
`backcompat` information.
The `now` field contains an integer describing the current time in
milliseconds relative an unspecified epoch that is fixed for each JSON
file. This is done instead of using the current time as the epoch for
better compression of non-changing data.
`secrets` is a map from each server address and to a JSON object
containing the last ping time (`ping_time`) in milliseconds relative to
the same epoch as before, and the server secret (`secret`) that is used
to unify server infos from different addresses of the same logical
server.
`servers` is a map from the aforementioned `secret`s to the
corresponding `info_serial` and `info`.
```json
[
"tw-0.6+udp://127.0.0.1:8303",
"tw-0.6+udp://[::1]:8303"
]
```
`--write-addresses` outputs a JSON file containing all addresses
corresponding to servers that are registered to HTTP masterservers. It
does not contain the servers that are obtained via backward
compatibility measures.
This file can be used by an old-style masterserver to also list
new-style servers without the game servers having to register there.
An implementation of this can be found at
https://github.com/heinrich5991/teeworlds/tree/mastersrv_6_backcompat
for Teeworlds 0.5/0.6 masterservers and at
https://github.com/heinrich5991/teeworlds/tree/mastersrv_7_backcompat
for Teeworlds 0.7 masterservers.
All these JSON files can be sent over the network in an efficient way
using https://github.com/heinrich5991/twmaster-collect. It establishes a
zstd-compressed TCP connection authenticated by a string token that is
sent in plain-text. It watches the specified file and transmits it every
time it changes. Due to the zstd-compression, the data sent over the
network is similar to the size of a diff.
Implementation
--------------
The masterserver implementation was done in Rust.
The current gameserver register implementation doesn't support more than
one masterserver for registering.
2022-05-19 20:03:17 +00:00
m_pRegister - > Update ( ) ;
2011-04-13 18:37:12 +00:00
2019-11-03 00:07:10 +00:00
if ( m_ServerInfoNeedsUpdate )
UpdateServerInfo ( ) ;
2019-11-02 23:33:30 +00:00
2020-05-15 22:39:17 +00:00
Antibot ( ) - > OnEngineTick ( ) ;
2014-11-24 21:31:13 +00:00
if ( ! NonActive )
2020-09-13 21:23:50 +00:00
PumpNetwork ( PacketWaiting ) ;
2011-04-13 18:37:12 +00:00
2014-11-24 21:31:13 +00:00
NonActive = true ;
2014-01-15 15:52:22 +00:00
2023-06-23 08:18:47 +00:00
for ( int i = 0 ; i < MAX_CLIENTS ; + + i )
2022-05-23 06:26:57 +00:00
{
2023-06-23 08:18:47 +00:00
if ( m_aClients [ i ] . m_State = = CClient : : STATE_REDIRECTED )
if ( time_get ( ) > m_aClients [ i ] . m_RedirectDropTime )
m_NetServer . Drop ( i , " redirected " ) ;
if ( m_aClients [ i ] . m_State ! = CClient : : STATE_EMPTY )
2022-05-23 06:26:57 +00:00
{
2014-01-15 15:52:22 +00:00
NonActive = false ;
2022-05-23 06:26:57 +00:00
break ;
}
}
2014-01-15 15:52:22 +00:00
2014-01-14 23:02:19 +00:00
// wait for incoming data
2020-09-26 19:41:58 +00:00
if ( NonActive )
2014-08-15 12:32:21 +00:00
{
2021-12-14 23:28:51 +00:00
if ( Config ( ) - > m_SvReloadWhenEmpty = = 1 )
2015-10-22 15:51:02 +00:00
{
2015-10-22 15:27:30 +00:00
m_MapReload = true ;
2021-12-14 23:28:51 +00:00
Config ( ) - > m_SvReloadWhenEmpty = 0 ;
2015-10-22 15:51:02 +00:00
}
2021-12-14 23:28:51 +00:00
else if ( Config ( ) - > m_SvReloadWhenEmpty = = 2 & & ! m_ReloadedWhenEmpty )
2015-10-22 15:51:02 +00:00
{
2015-10-22 15:27:30 +00:00
m_MapReload = true ;
m_ReloadedWhenEmpty = true ;
}
2021-12-14 23:28:51 +00:00
if ( Config ( ) - > m_SvShutdownWhenEmpty )
2020-07-07 08:23:04 +00:00
m_RunServer = STOPPING ;
2014-08-15 12:32:21 +00:00
else
2020-09-13 21:23:50 +00:00
PacketWaiting = net_socket_read_wait ( m_NetServer . Socket ( ) , 1000000 ) ;
2014-08-15 12:32:21 +00:00
}
2014-01-15 15:52:22 +00:00
else
2014-11-11 12:00:02 +00:00
{
2015-10-22 15:27:30 +00:00
m_ReloadedWhenEmpty = false ;
2014-11-11 12:00:02 +00:00
set_new_tick ( ) ;
2022-03-20 11:57:50 +00:00
t = time_get ( ) ;
2020-09-26 19:41:58 +00:00
int x = ( TickStartTime ( m_CurrentGameTick + 1 ) - t ) * 1000000 / time_freq ( ) + 1 ;
2014-11-15 17:14:20 +00:00
2020-09-13 21:23:50 +00:00
PacketWaiting = x > 0 ? net_socket_read_wait ( m_NetServer . Socket ( ) , x ) : true ;
2014-11-11 12:00:02 +00:00
}
2022-05-26 23:17:01 +00:00
if ( IsInterrupted ( ) )
2021-06-12 14:51:55 +00:00
{
Console ( ) - > Print ( IConsole : : OUTPUT_LEVEL_STANDARD , " server " , " interrupted " ) ;
break ;
}
2010-05-29 07:25:38 +00:00
}
}
2017-10-13 00:25:50 +00:00
const char * pDisconnectReason = " Server shutdown " ;
2021-12-08 17:41:16 +00:00
if ( m_aShutdownReason [ 0 ] )
pDisconnectReason = m_aShutdownReason ;
2017-10-13 00:25:50 +00:00
if ( ErrorShutdown ( ) )
{
2023-12-31 14:44:04 +00:00
log_info ( " server " , " shutdown from game server (%s) " , m_aErrorShutdownReason ) ;
2017-10-13 00:25:50 +00:00
pDisconnectReason = m_aErrorShutdownReason ;
}
2010-05-29 07:25:38 +00:00
// disconnect all clients on shutdown
for ( int i = 0 ; i < MAX_CLIENTS ; + + i )
{
if ( m_aClients [ i ] . m_State ! = CClient : : STATE_EMPTY )
2017-10-13 00:25:50 +00:00
m_NetServer . Drop ( i , pDisconnectReason ) ;
2010-05-29 07:25:38 +00:00
}
Allow background jobs to be aborted, refactoring
Add `IJob::Abortable(bool)` function which jobs can call to specify whether they can be aborted. Jobs are not abortable per default. Abortable jobs may have their state set to `IJob::STATE_ABORTED` at any point if the job was aborted. The job state should be checked periodically in the `IJob::Run` function and the job should terminate at the earliest, safe opportunity when aborted. Scheduled jobs which are not abortable are guaranteed to fully complete before the job pool is shut down. However, if the job pool is already shutting down, no additional jobs will be enqueue anymore and abortable jobs will immediately be aborted.
In particular, the sound loading, community icon loading, master chooser and host lookup jobs are specified as being abortable. Conversely, the jobs saving replay demos, editor maps and screenshots are expected to finish before the client is shut down.
When the client is quitting/restarting, it will now disconnect from the current server first, before saving the config, to ensure that any actions that happen on disconnect (demo recorders being stopped etc.) happen first. The shutdown message is rendered before disconnecting and waiting for background jobs to finish.
The HTTP client is now initialized later during server launch, after the network initialization. Error handling is added and the server stops if the HTTP client could not be initialized, same as the client.
The `RunBlocking` functions are removed, as they are not used anymore after curl-multi was added.
The function `IJob::Status` is renamed to `State` and `IJob::STATE_PENDING` is renamed to `STATE_QUEUED` for consistency with naming of the HTTP client.
The member variables of the engine interface are encapsulated and the `jobs.h` include is removed from `engine.h`, which removes transitive includes of `system.h`.
Documentation for all job and job pool API is added.
2024-02-16 19:44:33 +00:00
m_pRegister - > OnShutdown ( ) ;
2016-05-02 21:36:21 +00:00
m_Econ . Shutdown ( ) ;
m_Fifo . Shutdown ( ) ;
Allow background jobs to be aborted, refactoring
Add `IJob::Abortable(bool)` function which jobs can call to specify whether they can be aborted. Jobs are not abortable per default. Abortable jobs may have their state set to `IJob::STATE_ABORTED` at any point if the job was aborted. The job state should be checked periodically in the `IJob::Run` function and the job should terminate at the earliest, safe opportunity when aborted. Scheduled jobs which are not abortable are guaranteed to fully complete before the job pool is shut down. However, if the job pool is already shutting down, no additional jobs will be enqueue anymore and abortable jobs will immediately be aborted.
In particular, the sound loading, community icon loading, master chooser and host lookup jobs are specified as being abortable. Conversely, the jobs saving replay demos, editor maps and screenshots are expected to finish before the client is shut down.
When the client is quitting/restarting, it will now disconnect from the current server first, before saving the config, to ensure that any actions that happen on disconnect (demo recorders being stopped etc.) happen first. The shutdown message is rendered before disconnecting and waiting for background jobs to finish.
The HTTP client is now initialized later during server launch, after the network initialization. Error handling is added and the server stops if the HTTP client could not be initialized, same as the client.
The `RunBlocking` functions are removed, as they are not used anymore after curl-multi was added.
The function `IJob::Status` is renamed to `State` and `IJob::STATE_PENDING` is renamed to `STATE_QUEUED` for consistency with naming of the HTTP client.
The member variables of the engine interface are encapsulated and the `jobs.h` include is removed from `engine.h`, which removes transitive includes of `system.h`.
Documentation for all job and job pool API is added.
2024-02-16 19:44:33 +00:00
Engine ( ) - > ShutdownJobs ( ) ;
2016-05-02 21:36:21 +00:00
2023-08-25 12:52:37 +00:00
GameServer ( ) - > OnShutdown ( nullptr ) ;
2010-05-29 07:25:38 +00:00
m_pMap - > Unload ( ) ;
2020-07-04 17:53:27 +00:00
DbPool ( ) - > OnShutdown ( ) ;
2015-11-23 21:49:18 +00:00
2020-09-26 19:41:58 +00:00
# if defined(CONF_UPNP)
2020-04-19 13:14:21 +00:00
m_UPnP . Shutdown ( ) ;
# endif
2022-04-14 09:50:10 +00:00
m_NetServer . Close ( ) ;
2017-10-13 00:25:50 +00:00
return ErrorShutdown ( ) ;
2010-05-29 07:25:38 +00:00
}
2011-08-13 00:11:06 +00:00
void CServer : : ConKick ( IConsole : : IResult * pResult , void * pUser )
2010-05-29 07:25:38 +00:00
{
2011-08-13 00:11:06 +00:00
if ( pResult - > NumArguments ( ) > 1 )
2010-10-09 18:19:58 +00:00
{
char aBuf [ 128 ] ;
2011-08-13 00:11:06 +00:00
str_format ( aBuf , sizeof ( aBuf ) , " Kicked (%s) " , pResult - > GetString ( 1 ) ) ;
( ( CServer * ) pUser ) - > Kick ( pResult - > GetInteger ( 0 ) , aBuf ) ;
2010-10-09 18:19:58 +00:00
}
else
2011-08-13 00:11:06 +00:00
( ( CServer * ) pUser ) - > Kick ( pResult - > GetInteger ( 0 ) , " Kicked by console " ) ;
2010-05-29 07:25:38 +00:00
}
2019-04-03 13:07:05 +00:00
void CServer : : ConStatus ( IConsole : : IResult * pResult , void * pUser )
2010-05-29 07:25:38 +00:00
{
char aBuf [ 1024 ] ;
2011-03-28 18:11:28 +00:00
char aAddrStr [ NETADDR_MAXSTRSIZE ] ;
2017-03-21 10:24:44 +00:00
CServer * pThis = static_cast < CServer * > ( pUser ) ;
2020-01-24 00:55:09 +00:00
const char * pName = pResult - > NumArguments ( ) = = 1 ? pResult - > GetString ( 0 ) : " " ;
2010-05-29 07:25:38 +00:00
2011-12-29 22:36:53 +00:00
for ( int i = 0 ; i < MAX_CLIENTS ; i + + )
2010-05-29 07:25:38 +00:00
{
2019-04-03 13:07:05 +00:00
if ( pThis - > m_aClients [ i ] . m_State = = CClient : : STATE_EMPTY )
continue ;
2020-01-24 00:55:09 +00:00
if ( ! str_utf8_find_nocase ( pThis - > m_aClients [ i ] . m_aName , pName ) )
continue ;
2019-04-03 13:07:05 +00:00
net_addr_str ( pThis - > m_NetServer . ClientAddr ( i ) , aAddrStr , sizeof ( aAddrStr ) , true ) ;
if ( pThis - > m_aClients [ i ] . m_State = = CClient : : STATE_INGAME )
2010-05-29 07:25:38 +00:00
{
2019-04-03 13:07:05 +00:00
char aDnsblStr [ 64 ] ;
aDnsblStr [ 0 ] = ' \0 ' ;
2021-12-14 23:28:51 +00:00
if ( pThis - > Config ( ) - > m_SvDnsbl )
2019-04-03 13:07:05 +00:00
{
const char * pDnsblStr = pThis - > m_aClients [ i ] . m_DnsblState = = CClient : : DNSBL_STATE_WHITELISTED ? " white " :
2020-09-26 19:41:58 +00:00
pThis - > m_aClients [ i ] . m_DnsblState = = CClient : : DNSBL_STATE_BLACKLISTED ? " black " :
pThis - > m_aClients [ i ] . m_DnsblState = = CClient : : DNSBL_STATE_PENDING ? " pending " : " n/a " ;
2019-04-03 13:07:05 +00:00
str_format ( aDnsblStr , sizeof ( aDnsblStr ) , " dnsbl=%s " , pDnsblStr ) ;
}
char aAuthStr [ 128 ] ;
aAuthStr [ 0 ] = ' \0 ' ;
if ( pThis - > m_aClients [ i ] . m_AuthKey > = 0 )
2012-07-29 10:17:16 +00:00
{
2019-03-02 10:50:33 +00:00
const char * pAuthStr = pThis - > m_aClients [ i ] . m_Authed = = AUTHED_ADMIN ? " (Admin) " :
2020-09-26 19:41:58 +00:00
pThis - > m_aClients [ i ] . m_Authed = = AUTHED_MOD ? " (Mod) " :
pThis - > m_aClients [ i ] . m_Authed = = AUTHED_HELPER ? " (Helper) " : " " ;
2019-04-03 13:07:05 +00:00
str_format ( aAuthStr , sizeof ( aAuthStr ) , " key=%s %s " , pThis - > m_AuthManager . KeyIdent ( pThis - > m_aClients [ i ] . m_AuthKey ) , pAuthStr ) ;
2017-04-17 10:13:58 +00:00
}
2019-04-03 13:07:05 +00:00
2021-01-15 21:39:18 +00:00
const char * pClientPrefix = " " ;
2021-01-15 18:26:40 +00:00
if ( pThis - > m_aClients [ i ] . m_Sixup )
{
2021-01-15 21:39:18 +00:00
pClientPrefix = " 0.7: " ;
2021-01-15 18:26:40 +00:00
}
2021-01-15 21:39:18 +00:00
str_format ( aBuf , sizeof ( aBuf ) , " id=%d addr=<{%s}> name='%s' client=%s%d secure=%s flags=%d%s%s " ,
i , aAddrStr , pThis - > m_aClients [ i ] . m_aName , pClientPrefix , pThis - > m_aClients [ i ] . m_DDNetVersion ,
2019-04-03 13:07:05 +00:00
pThis - > m_NetServer . HasSecurityToken ( i ) ? " yes " : " no " , pThis - > m_aClients [ i ] . m_Flags , aDnsblStr , aAuthStr ) ;
}
else
{
2019-04-04 17:16:10 +00:00
str_format ( aBuf , sizeof ( aBuf ) , " id=%d addr=<{%s}> connecting " , i , aAddrStr ) ;
2010-05-29 07:25:38 +00:00
}
2019-04-29 23:01:43 +00:00
pThis - > Console ( ) - > Print ( IConsole : : OUTPUT_LEVEL_STANDARD , " server " , aBuf ) ;
2010-05-29 07:25:38 +00:00
}
}
2017-03-02 15:16:29 +00:00
static int GetAuthLevel ( const char * pLevel )
{
int Level = - 1 ;
if ( ! str_comp_nocase ( pLevel , " admin " ) )
2019-03-02 10:50:33 +00:00
Level = AUTHED_ADMIN ;
2022-03-13 10:42:45 +00:00
else if ( str_startswith ( pLevel , " mod " ) )
2019-03-02 10:50:33 +00:00
Level = AUTHED_MOD ;
2017-03-02 15:16:29 +00:00
else if ( ! str_comp_nocase ( pLevel , " helper " ) )
2019-03-02 10:50:33 +00:00
Level = AUTHED_HELPER ;
2017-03-02 15:16:29 +00:00
return Level ;
}
2017-03-06 17:02:19 +00:00
void CServer : : AuthRemoveKey ( int KeySlot )
{
2023-09-22 18:55:27 +00:00
m_AuthManager . RemoveKey ( KeySlot ) ;
2017-03-06 17:02:19 +00:00
LogoutKey ( KeySlot , " key removal " ) ;
// Update indices.
2023-09-22 18:55:27 +00:00
for ( auto & Client : m_aClients )
2017-03-06 17:02:19 +00:00
{
2023-09-22 18:55:27 +00:00
if ( Client . m_AuthKey = = KeySlot )
{
Client . m_AuthKey = - 1 ;
}
else if ( Client . m_AuthKey > KeySlot )
{
- - Client . m_AuthKey ;
}
2017-03-06 17:02:19 +00:00
}
}
2017-03-02 15:16:29 +00:00
void CServer : : ConAuthAdd ( IConsole : : IResult * pResult , void * pUser )
{
CServer * pThis = ( CServer * ) pUser ;
CAuthManager * pManager = & pThis - > m_AuthManager ;
const char * pIdent = pResult - > GetString ( 0 ) ;
const char * pLevel = pResult - > GetString ( 1 ) ;
const char * pPw = pResult - > GetString ( 2 ) ;
int Level = GetAuthLevel ( pLevel ) ;
if ( Level = = - 1 )
{
pThis - > Console ( ) - > Print ( IConsole : : OUTPUT_LEVEL_STANDARD , " auth " , " level can be one of { \" admin \" , \" mod(erator) \" , \" helper \" } " ) ;
return ;
}
2017-07-24 19:43:55 +00:00
bool NeedUpdate = ! pManager - > NumNonDefaultKeys ( ) ;
2017-03-04 20:16:02 +00:00
if ( pManager - > AddKey ( pIdent , pPw , Level ) < 0 )
pThis - > Console ( ) - > Print ( IConsole : : OUTPUT_LEVEL_STANDARD , " auth " , " ident already exists " ) ;
else
2017-07-24 19:43:55 +00:00
{
if ( NeedUpdate )
pThis - > SendRconType ( - 1 , true ) ;
2017-03-04 20:16:02 +00:00
pThis - > Console ( ) - > Print ( IConsole : : OUTPUT_LEVEL_STANDARD , " auth " , " key added " ) ;
2017-07-24 19:43:55 +00:00
}
2017-03-02 15:16:29 +00:00
}
void CServer : : ConAuthAddHashed ( IConsole : : IResult * pResult , void * pUser )
{
CServer * pThis = ( CServer * ) pUser ;
CAuthManager * pManager = & pThis - > m_AuthManager ;
const char * pIdent = pResult - > GetString ( 0 ) ;
const char * pLevel = pResult - > GetString ( 1 ) ;
const char * pPw = pResult - > GetString ( 2 ) ;
const char * pSalt = pResult - > GetString ( 3 ) ;
int Level = GetAuthLevel ( pLevel ) ;
if ( Level = = - 1 )
{
pThis - > Console ( ) - > Print ( IConsole : : OUTPUT_LEVEL_STANDARD , " auth " , " level can be one of { \" admin \" , \" mod(erator) \" , \" helper \" } " ) ;
return ;
}
2019-04-06 00:46:56 +00:00
MD5_DIGEST Hash ;
2017-03-02 15:16:29 +00:00
unsigned char aSalt [ SALT_BYTES ] ;
2019-04-06 00:46:56 +00:00
if ( md5_from_str ( & Hash , pPw ) )
2017-03-06 11:59:47 +00:00
{
2017-03-06 14:18:52 +00:00
pThis - > Console ( ) - > Print ( IConsole : : OUTPUT_LEVEL_STANDARD , " auth " , " Malformed password hash " ) ;
2017-03-06 11:59:47 +00:00
return ;
}
2017-03-06 19:11:23 +00:00
if ( str_hex_decode ( aSalt , sizeof ( aSalt ) , pSalt ) )
2017-03-06 11:59:47 +00:00
{
2017-03-06 14:18:52 +00:00
pThis - > Console ( ) - > Print ( IConsole : : OUTPUT_LEVEL_STANDARD , " auth " , " Malformed salt hash " ) ;
2017-03-06 11:59:47 +00:00
return ;
}
2017-03-02 15:16:29 +00:00
2017-07-24 19:43:55 +00:00
bool NeedUpdate = ! pManager - > NumNonDefaultKeys ( ) ;
2019-04-06 00:46:56 +00:00
if ( pManager - > AddKeyHash ( pIdent , Hash , aSalt , Level ) < 0 )
2017-03-04 20:16:02 +00:00
pThis - > Console ( ) - > Print ( IConsole : : OUTPUT_LEVEL_STANDARD , " auth " , " ident already exists " ) ;
else
2017-07-24 19:43:55 +00:00
{
if ( NeedUpdate )
pThis - > SendRconType ( - 1 , true ) ;
2017-03-04 20:16:02 +00:00
pThis - > Console ( ) - > Print ( IConsole : : OUTPUT_LEVEL_STANDARD , " auth " , " key added " ) ;
2017-07-24 19:43:55 +00:00
}
2017-03-02 15:16:29 +00:00
}
void CServer : : ConAuthUpdate ( IConsole : : IResult * pResult , void * pUser )
{
CServer * pThis = ( CServer * ) pUser ;
CAuthManager * pManager = & pThis - > m_AuthManager ;
const char * pIdent = pResult - > GetString ( 0 ) ;
const char * pLevel = pResult - > GetString ( 1 ) ;
const char * pPw = pResult - > GetString ( 2 ) ;
int KeySlot = pManager - > FindKey ( pIdent ) ;
2017-03-06 19:11:23 +00:00
if ( KeySlot = = - 1 )
{
2017-03-02 15:16:29 +00:00
pThis - > Console ( ) - > Print ( IConsole : : OUTPUT_LEVEL_STANDARD , " auth " , " ident couldn't be found " ) ;
return ;
}
int Level = GetAuthLevel ( pLevel ) ;
if ( Level = = - 1 )
{
pThis - > Console ( ) - > Print ( IConsole : : OUTPUT_LEVEL_STANDARD , " auth " , " level can be one of { \" admin \" , \" mod(erator) \" , \" helper \" } " ) ;
return ;
}
pManager - > UpdateKey ( KeySlot , pPw , Level ) ;
2017-07-24 19:43:55 +00:00
pThis - > LogoutKey ( KeySlot , " key update " ) ;
2017-03-02 15:16:29 +00:00
pThis - > Console ( ) - > Print ( IConsole : : OUTPUT_LEVEL_STANDARD , " auth " , " key updated " ) ;
}
void CServer : : ConAuthUpdateHashed ( IConsole : : IResult * pResult , void * pUser )
{
CServer * pThis = ( CServer * ) pUser ;
CAuthManager * pManager = & pThis - > m_AuthManager ;
const char * pIdent = pResult - > GetString ( 0 ) ;
const char * pLevel = pResult - > GetString ( 1 ) ;
const char * pPw = pResult - > GetString ( 2 ) ;
const char * pSalt = pResult - > GetString ( 3 ) ;
int KeySlot = pManager - > FindKey ( pIdent ) ;
2017-03-06 19:11:23 +00:00
if ( KeySlot = = - 1 )
{
2017-03-02 15:16:29 +00:00
pThis - > Console ( ) - > Print ( IConsole : : OUTPUT_LEVEL_STANDARD , " auth " , " ident couldn't be found " ) ;
return ;
}
int Level = GetAuthLevel ( pLevel ) ;
if ( Level = = - 1 )
{
pThis - > Console ( ) - > Print ( IConsole : : OUTPUT_LEVEL_STANDARD , " auth " , " level can be one of { \" admin \" , \" mod(erator) \" , \" helper \" } " ) ;
return ;
}
2019-04-06 00:46:56 +00:00
MD5_DIGEST Hash ;
2017-03-02 15:16:29 +00:00
unsigned char aSalt [ SALT_BYTES ] ;
2019-04-06 00:46:56 +00:00
if ( md5_from_str ( & Hash , pPw ) )
2017-03-06 11:59:47 +00:00
{
2017-03-06 14:18:52 +00:00
pThis - > Console ( ) - > Print ( IConsole : : OUTPUT_LEVEL_STANDARD , " auth " , " Malformed password hash " ) ;
2017-03-06 11:59:47 +00:00
return ;
}
2017-03-06 19:11:23 +00:00
if ( str_hex_decode ( aSalt , sizeof ( aSalt ) , pSalt ) )
2017-03-06 11:59:47 +00:00
{
2017-03-06 14:18:52 +00:00
pThis - > Console ( ) - > Print ( IConsole : : OUTPUT_LEVEL_STANDARD , " auth " , " Malformed salt hash " ) ;
2017-03-06 11:59:47 +00:00
return ;
}
2017-03-02 15:16:29 +00:00
2019-04-06 00:46:56 +00:00
pManager - > UpdateKeyHash ( KeySlot , Hash , aSalt , Level ) ;
2017-03-02 15:16:29 +00:00
pThis - > LogoutKey ( KeySlot , " key update " ) ;
pThis - > Console ( ) - > Print ( IConsole : : OUTPUT_LEVEL_STANDARD , " auth " , " key updated " ) ;
}
void CServer : : ConAuthRemove ( IConsole : : IResult * pResult , void * pUser )
{
CServer * pThis = ( CServer * ) pUser ;
CAuthManager * pManager = & pThis - > m_AuthManager ;
const char * pIdent = pResult - > GetString ( 0 ) ;
int KeySlot = pManager - > FindKey ( pIdent ) ;
2017-03-06 19:11:23 +00:00
if ( KeySlot = = - 1 )
{
2017-03-02 15:16:29 +00:00
pThis - > Console ( ) - > Print ( IConsole : : OUTPUT_LEVEL_STANDARD , " auth " , " ident couldn't be found " ) ;
return ;
}
2017-03-06 17:02:19 +00:00
pThis - > AuthRemoveKey ( KeySlot ) ;
2017-07-24 19:43:55 +00:00
if ( ! pManager - > NumNonDefaultKeys ( ) )
pThis - > SendRconType ( - 1 , false ) ;
2017-03-02 15:16:29 +00:00
pThis - > Console ( ) - > Print ( IConsole : : OUTPUT_LEVEL_STANDARD , " auth " , " key removed, all users logged out " ) ;
}
static void ListKeysCallback ( const char * pIdent , int Level , void * pUser )
{
2017-03-06 19:11:23 +00:00
static const char LSTRING [ ] [ 10 ] = { " helper " , " moderator " , " admin " } ;
2017-03-02 15:16:29 +00:00
char aBuf [ 256 ] ;
2017-03-06 19:11:23 +00:00
str_format ( aBuf , sizeof ( aBuf ) , " %s %s " , pIdent , LSTRING [ Level - 1 ] ) ;
2017-03-02 15:16:29 +00:00
( ( CServer * ) pUser ) - > Console ( ) - > Print ( IConsole : : OUTPUT_LEVEL_STANDARD , " auth " , aBuf ) ;
}
void CServer : : ConAuthList ( IConsole : : IResult * pResult , void * pUser )
{
CServer * pThis = ( CServer * ) pUser ;
CAuthManager * pManager = & pThis - > m_AuthManager ;
pManager - > ListKeys ( ListKeysCallback , pThis ) ;
}
2011-08-13 00:11:06 +00:00
void CServer : : ConShutdown ( IConsole : : IResult * pResult , void * pUser )
2010-05-29 07:25:38 +00:00
{
2021-12-08 17:41:16 +00:00
CServer * pThis = static_cast < CServer * > ( pUser ) ;
pThis - > m_RunServer = STOPPING ;
const char * pReason = pResult - > GetString ( 0 ) ;
if ( pReason [ 0 ] )
{
2022-07-09 16:14:56 +00:00
str_copy ( pThis - > m_aShutdownReason , pReason ) ;
2021-12-08 17:41:16 +00:00
}
2010-05-29 07:25:38 +00:00
}
2011-07-22 21:17:16 +00:00
void CServer : : DemoRecorder_HandleAutoStart ( )
{
2021-12-14 23:28:51 +00:00
if ( Config ( ) - > m_SvAutoDemoRecord )
2011-07-22 21:17:16 +00:00
{
2024-01-01 17:17:05 +00:00
m_aDemoRecorder [ RECORDER_AUTO ] . Stop ( IDemoRecorder : : EStopMode : : KEEP_FILE ) ;
char aTimestamp [ 20 ] ;
str_timestamp ( aTimestamp , sizeof ( aTimestamp ) ) ;
2021-09-13 08:06:34 +00:00
char aFilename [ IO_MAX_PATH_LENGTH ] ;
2024-01-01 17:17:05 +00:00
str_format ( aFilename , sizeof ( aFilename ) , " demos/auto/server/%s_%s.demo " , m_aCurrentMap , aTimestamp ) ;
2023-12-25 14:02:10 +00:00
m_aDemoRecorder [ RECORDER_AUTO ] . Start ( Storage ( ) , m_pConsole , aFilename , GameServer ( ) - > NetVersion ( ) , m_aCurrentMap , m_aCurrentMapSha256 [ MAP_TYPE_SIX ] , m_aCurrentMapCrc [ MAP_TYPE_SIX ] , " server " , m_aCurrentMapSize [ MAP_TYPE_SIX ] , m_apCurrentMapData [ MAP_TYPE_SIX ] ) ;
2024-01-01 17:17:05 +00:00
2021-12-14 23:28:51 +00:00
if ( Config ( ) - > m_SvAutoDemoMax )
2011-07-22 21:17:16 +00:00
{
// clean up auto recorded demos
CFileCollection AutoDemos ;
2023-12-25 14:19:12 +00:00
AutoDemos . Init ( Storage ( ) , " demos/auto/server " , " " , " .demo " , Config ( ) - > m_SvAutoDemoMax ) ;
2011-07-22 21:17:16 +00:00
}
}
}
2014-09-26 00:05:22 +00:00
void CServer : : SaveDemo ( int ClientID , float Time )
{
if ( IsRecording ( ClientID ) )
{
2021-09-13 08:06:34 +00:00
char aNewFilename [ IO_MAX_PATH_LENGTH ] ;
2022-08-24 16:50:19 +00:00
str_format ( aNewFilename , sizeof ( aNewFilename ) , " demos/%s_%s_%05.2f.demo " , m_aCurrentMap , m_aClients [ ClientID ] . m_aName , Time ) ;
2024-01-01 17:17:05 +00:00
m_aDemoRecorder [ ClientID ] . Stop ( IDemoRecorder : : EStopMode : : KEEP_FILE , aNewFilename ) ;
2014-09-26 00:05:22 +00:00
}
}
void CServer : : StartRecord ( int ClientID )
{
2021-12-14 23:28:51 +00:00
if ( Config ( ) - > m_SvPlayerDemoRecord )
2014-09-26 00:05:22 +00:00
{
2021-09-13 08:06:34 +00:00
char aFilename [ IO_MAX_PATH_LENGTH ] ;
2020-08-27 16:54:44 +00:00
str_format ( aFilename , sizeof ( aFilename ) , " demos/%s_%d_%d_tmp.demo " , m_aCurrentMap , m_NetServer . Address ( ) . port , ClientID ) ;
2023-10-15 14:16:28 +00:00
m_aDemoRecorder [ ClientID ] . Start ( Storage ( ) , Console ( ) , aFilename , GameServer ( ) - > NetVersion ( ) , m_aCurrentMap , m_aCurrentMapSha256 [ MAP_TYPE_SIX ] , m_aCurrentMapCrc [ MAP_TYPE_SIX ] , " server " , m_aCurrentMapSize [ MAP_TYPE_SIX ] , m_apCurrentMapData [ MAP_TYPE_SIX ] ) ;
2014-09-26 00:05:22 +00:00
}
}
void CServer : : StopRecord ( int ClientID )
{
if ( IsRecording ( ClientID ) )
{
2024-01-01 17:17:05 +00:00
m_aDemoRecorder [ ClientID ] . Stop ( IDemoRecorder : : EStopMode : : REMOVE_FILE ) ;
2014-09-26 00:05:22 +00:00
}
}
bool CServer : : IsRecording ( int ClientID )
{
return m_aDemoRecorder [ ClientID ] . IsRecording ( ) ;
2012-01-08 23:49:20 +00:00
}
2023-12-02 10:10:20 +00:00
void CServer : : StopDemos ( )
{
2023-12-25 14:02:10 +00:00
for ( int i = 0 ; i < NUM_RECORDERS ; i + + )
2023-12-02 10:10:20 +00:00
{
if ( ! m_aDemoRecorder [ i ] . IsRecording ( ) )
continue ;
2024-01-01 17:17:05 +00:00
m_aDemoRecorder [ i ] . Stop ( i < MAX_CLIENTS ? IDemoRecorder : : EStopMode : : REMOVE_FILE : IDemoRecorder : : EStopMode : : KEEP_FILE ) ;
2023-12-02 10:10:20 +00:00
}
}
2010-05-29 07:25:38 +00:00
void CServer : : ConRecord ( IConsole : : IResult * pResult , void * pUser )
{
2017-03-21 10:24:44 +00:00
CServer * pServer = ( CServer * ) pUser ;
2010-12-07 23:42:32 +00:00
2023-12-25 14:02:10 +00:00
if ( pServer - > IsRecording ( RECORDER_MANUAL ) )
2023-12-01 21:48:23 +00:00
{
pServer - > Console ( ) - > Print ( IConsole : : OUTPUT_LEVEL_STANDARD , " demo_recorder " , " Demo recorder already recording " ) ;
return ;
}
2024-01-01 17:17:05 +00:00
char aFilename [ IO_MAX_PATH_LENGTH ] ;
2010-12-07 23:42:32 +00:00
if ( pResult - > NumArguments ( ) )
2024-01-01 17:17:05 +00:00
{
2010-12-07 23:42:32 +00:00
str_format ( aFilename , sizeof ( aFilename ) , " demos/%s.demo " , pResult - > GetString ( 0 ) ) ;
2024-01-01 17:17:05 +00:00
}
2010-12-07 23:42:32 +00:00
else
{
2024-01-01 17:17:05 +00:00
char aTimestamp [ 20 ] ;
str_timestamp ( aTimestamp , sizeof ( aTimestamp ) ) ;
str_format ( aFilename , sizeof ( aFilename ) , " demos/demo_%s.demo " , aTimestamp ) ;
2010-12-07 23:42:32 +00:00
}
2023-12-25 14:02:10 +00:00
pServer - > m_aDemoRecorder [ RECORDER_MANUAL ] . Start ( pServer - > Storage ( ) , pServer - > Console ( ) , aFilename , pServer - > GameServer ( ) - > NetVersion ( ) , pServer - > m_aCurrentMap , pServer - > m_aCurrentMapSha256 [ MAP_TYPE_SIX ] , pServer - > m_aCurrentMapCrc [ MAP_TYPE_SIX ] , " server " , pServer - > m_aCurrentMapSize [ MAP_TYPE_SIX ] , pServer - > m_apCurrentMapData [ MAP_TYPE_SIX ] ) ;
2010-05-29 07:25:38 +00:00
}
2011-08-13 00:11:06 +00:00
void CServer : : ConStopRecord ( IConsole : : IResult * pResult , void * pUser )
2010-05-29 07:25:38 +00:00
{
2024-01-01 17:17:05 +00:00
( ( CServer * ) pUser ) - > m_aDemoRecorder [ RECORDER_MANUAL ] . Stop ( IDemoRecorder : : EStopMode : : KEEP_FILE ) ;
2010-05-29 07:25:38 +00:00
}
2011-08-13 00:11:06 +00:00
void CServer : : ConMapReload ( IConsole : : IResult * pResult , void * pUser )
2010-05-30 12:01:11 +00:00
{
2020-04-23 17:02:10 +00:00
( ( CServer * ) pUser ) - > m_MapReload = true ;
2010-05-30 12:01:11 +00:00
}
2011-12-26 21:07:57 +00:00
void CServer : : ConLogout ( IConsole : : IResult * pResult , void * pUser )
{
CServer * pServer = ( CServer * ) pUser ;
2011-12-29 23:07:17 +00:00
if ( pServer - > m_RconClientID > = 0 & & pServer - > m_RconClientID < MAX_CLIENTS & &
pServer - > m_aClients [ pServer - > m_RconClientID ] . m_State ! = CServer : : CClient : : STATE_EMPTY )
{
2017-03-02 15:16:29 +00:00
pServer - > LogoutClient ( pServer - > m_RconClientID , " " ) ;
2011-12-29 23:07:17 +00:00
}
2011-12-26 21:07:57 +00:00
}
2019-04-04 17:16:10 +00:00
void CServer : : ConShowIps ( IConsole : : IResult * pResult , void * pUser )
{
CServer * pServer = ( CServer * ) pUser ;
if ( pServer - > m_RconClientID > = 0 & & pServer - > m_RconClientID < MAX_CLIENTS & &
pServer - > m_aClients [ pServer - > m_RconClientID ] . m_State ! = CServer : : CClient : : STATE_EMPTY )
{
if ( pResult - > NumArguments ( ) )
{
pServer - > m_aClients [ pServer - > m_RconClientID ] . m_ShowIps = pResult - > GetInteger ( 0 ) ;
}
else
{
2019-08-01 18:24:30 +00:00
char aStr [ 9 ] ;
str_format ( aStr , sizeof ( aStr ) , " Value: %d " , pServer - > m_aClients [ pServer - > m_RconClientID ] . m_ShowIps ) ;
char aBuf [ 32 ] ;
pServer - > SendRconLine ( pServer - > m_RconClientID , pServer - > Console ( ) - > Format ( aBuf , sizeof ( aBuf ) , " server " , aStr ) ) ;
2019-04-04 17:16:10 +00:00
}
}
}
2015-12-17 21:04:38 +00:00
void CServer : : ConAddSqlServer ( IConsole : : IResult * pResult , void * pUserData )
2015-11-23 21:49:18 +00:00
{
CServer * pSelf = ( CServer * ) pUserData ;
2022-10-13 11:52:19 +00:00
if ( ! MysqlAvailable ( ) )
{
pSelf - > Console ( ) - > Print ( IConsole : : OUTPUT_LEVEL_STANDARD , " server " , " can't add MySQL server: compiled without MySQL support " ) ;
return ;
}
2021-12-14 23:28:51 +00:00
if ( ! pSelf - > Config ( ) - > m_SvUseSQL )
return ;
2020-09-26 19:41:58 +00:00
if ( pResult - > NumArguments ( ) ! = 7 & & pResult - > NumArguments ( ) ! = 8 )
2015-11-23 21:49:18 +00:00
{
2015-12-20 15:07:32 +00:00
pSelf - > Console ( ) - > Print ( IConsole : : OUTPUT_LEVEL_STANDARD , " server " , " 7 or 8 arguments are required " ) ;
2015-11-23 21:49:18 +00:00
return ;
}
2022-10-13 11:52:19 +00:00
CMysqlConfig Config ;
bool Write ;
2020-09-26 19:41:58 +00:00
if ( str_comp_nocase ( pResult - > GetString ( 0 ) , " w " ) = = 0 )
2022-10-13 11:52:19 +00:00
Write = false ;
2020-09-26 19:41:58 +00:00
else if ( str_comp_nocase ( pResult - > GetString ( 0 ) , " r " ) = = 0 )
2022-10-13 11:52:19 +00:00
Write = true ;
2015-12-19 17:26:09 +00:00
else
{
pSelf - > Console ( ) - > Print ( IConsole : : OUTPUT_LEVEL_STANDARD , " server " , " choose either 'r' for SqlReadServer or 'w' for SqlWriteServer " ) ;
return ;
}
2015-12-17 21:04:38 +00:00
2022-10-13 11:52:19 +00:00
str_copy ( Config . m_aDatabase , pResult - > GetString ( 1 ) , sizeof ( Config . m_aDatabase ) ) ;
str_copy ( Config . m_aPrefix , pResult - > GetString ( 2 ) , sizeof ( Config . m_aPrefix ) ) ;
str_copy ( Config . m_aUser , pResult - > GetString ( 3 ) , sizeof ( Config . m_aUser ) ) ;
str_copy ( Config . m_aPass , pResult - > GetString ( 4 ) , sizeof ( Config . m_aPass ) ) ;
str_copy ( Config . m_aIp , pResult - > GetString ( 5 ) , sizeof ( Config . m_aIp ) ) ;
2023-05-19 19:02:39 +00:00
Config . m_aBindaddr [ 0 ] = ' \0 ' ;
2022-10-13 11:52:19 +00:00
Config . m_Port = pResult - > GetInteger ( 6 ) ;
Config . m_Setup = pResult - > NumArguments ( ) = = 8 ? pResult - > GetInteger ( 7 ) : true ;
2021-01-26 20:22:32 +00:00
2020-07-04 17:53:27 +00:00
char aBuf [ 512 ] ;
str_format ( aBuf , sizeof ( aBuf ) ,
2022-10-13 11:52:19 +00:00
" Adding new Sql%sServer: DB: '%s' Prefix: '%s' User: '%s' IP: <{%s}> Port: %d " ,
Write ? " Write " : " Read " ,
Config . m_aDatabase , Config . m_aPrefix , Config . m_aUser , Config . m_aIp , Config . m_Port ) ;
2020-07-04 17:53:27 +00:00
pSelf - > Console ( ) - > Print ( IConsole : : OUTPUT_LEVEL_STANDARD , " server " , aBuf ) ;
2022-10-13 11:52:19 +00:00
pSelf - > DbPool ( ) - > RegisterMysqlDatabase ( Write ? CDbConnectionPool : : WRITE : CDbConnectionPool : : READ , & Config ) ;
2015-11-23 21:49:18 +00:00
}
2015-12-17 21:04:38 +00:00
void CServer : : ConDumpSqlServers ( IConsole : : IResult * pResult , void * pUserData )
2015-11-23 21:49:18 +00:00
{
CServer * pSelf = ( CServer * ) pUserData ;
2020-08-03 14:18:22 +00:00
if ( str_comp_nocase ( pResult - > GetString ( 0 ) , " w " ) = = 0 )
{
pSelf - > DbPool ( ) - > Print ( pSelf - > Console ( ) , CDbConnectionPool : : WRITE ) ;
pSelf - > DbPool ( ) - > Print ( pSelf - > Console ( ) , CDbConnectionPool : : WRITE_BACKUP ) ;
}
else if ( str_comp_nocase ( pResult - > GetString ( 0 ) , " r " ) = = 0 )
{
pSelf - > DbPool ( ) - > Print ( pSelf - > Console ( ) , CDbConnectionPool : : READ ) ;
}
2015-12-19 17:26:09 +00:00
else
{
pSelf - > Console ( ) - > Print ( IConsole : : OUTPUT_LEVEL_STANDARD , " server " , " choose either 'r' for SqlReadServer or 'w' for SqlWriteServer " ) ;
return ;
}
2015-11-23 21:49:18 +00:00
}
2010-05-29 07:25:38 +00:00
void CServer : : ConchainSpecialInfoupdate ( IConsole : : IResult * pResult , void * pUserData , IConsole : : FCommandCallback pfnCallback , void * pCallbackUserData )
{
2011-08-13 00:11:06 +00:00
pfnCallback ( pResult , pCallbackUserData ) ;
2010-05-29 07:25:38 +00:00
if ( pResult - > NumArguments ( ) )
2013-09-04 14:44:04 +00:00
{
2021-12-14 23:28:51 +00:00
CServer * pThis = static_cast < CServer * > ( pUserData ) ;
str_clean_whitespaces ( pThis - > Config ( ) - > m_SvName ) ;
pThis - > UpdateServerInfo ( true ) ;
2013-09-04 14:44:04 +00:00
}
2010-05-29 07:25:38 +00:00
}
2010-06-03 12:48:32 +00:00
void CServer : : ConchainMaxclientsperipUpdate ( IConsole : : IResult * pResult , void * pUserData , IConsole : : FCommandCallback pfnCallback , void * pCallbackUserData )
{
2011-08-13 00:11:06 +00:00
pfnCallback ( pResult , pCallbackUserData ) ;
2010-06-03 12:48:32 +00:00
if ( pResult - > NumArguments ( ) )
( ( CServer * ) pUserData ) - > m_NetServer . SetMaxClientsPerIP ( pResult - > GetInteger ( 0 ) ) ;
}
2015-10-23 00:33:10 +00:00
void CServer : : ConchainCommandAccessUpdate ( IConsole : : IResult * pResult , void * pUserData , IConsole : : FCommandCallback pfnCallback , void * pCallbackUserData )
2011-07-14 20:07:21 +00:00
{
if ( pResult - > NumArguments ( ) = = 2 )
{
CServer * pThis = static_cast < CServer * > ( pUserData ) ;
const IConsole : : CCommandInfo * pInfo = pThis - > Console ( ) - > GetCommandInfo ( pResult - > GetString ( 0 ) , CFGFLAG_SERVER , false ) ;
2011-08-01 10:11:10 +00:00
int OldAccessLevel = 0 ;
2011-07-14 20:07:21 +00:00
if ( pInfo )
OldAccessLevel = pInfo - > GetAccessLevel ( ) ;
pfnCallback ( pResult , pCallbackUserData ) ;
if ( pInfo & & OldAccessLevel ! = pInfo - > GetAccessLevel ( ) )
{
for ( int i = 0 ; i < MAX_CLIENTS ; + + i )
{
2015-10-23 00:33:10 +00:00
if ( pThis - > m_aClients [ i ] . m_State = = CServer : : CClient : : STATE_EMPTY | |
2020-09-26 19:41:58 +00:00
( pInfo - > GetAccessLevel ( ) > AUTHED_ADMIN - pThis - > m_aClients [ i ] . m_Authed & & AUTHED_ADMIN - pThis - > m_aClients [ i ] . m_Authed < OldAccessLevel ) | |
( pInfo - > GetAccessLevel ( ) < AUTHED_ADMIN - pThis - > m_aClients [ i ] . m_Authed & & AUTHED_ADMIN - pThis - > m_aClients [ i ] . m_Authed > OldAccessLevel ) | |
( pThis - > m_aClients [ i ] . m_pRconCmdToSend & & str_comp ( pResult - > GetString ( 0 ) , pThis - > m_aClients [ i ] . m_pRconCmdToSend - > m_pName ) > = 0 ) )
2011-07-14 20:07:21 +00:00
continue ;
2015-10-23 00:33:10 +00:00
if ( OldAccessLevel < pInfo - > GetAccessLevel ( ) )
2011-07-14 20:07:21 +00:00
pThis - > SendRconCmdAdd ( pInfo , i ) ;
else
pThis - > SendRconCmdRem ( pInfo , i ) ;
}
}
}
else
pfnCallback ( pResult , pCallbackUserData ) ;
}
2017-03-02 15:16:29 +00:00
void CServer : : LogoutClient ( int ClientID , const char * pReason )
2015-10-26 23:33:26 +00:00
{
2020-06-16 14:55:37 +00:00
if ( ! IsSixup ( ClientID ) )
{
CMsgPacker Msg ( NETMSG_RCON_AUTH_STATUS , true ) ;
2020-09-26 19:41:58 +00:00
Msg . AddInt ( 0 ) ; //authed
Msg . AddInt ( 0 ) ; //cmdlist
2020-06-16 14:55:37 +00:00
SendMsg ( & Msg , MSGFLAG_VITAL , ClientID ) ;
}
else
{
2023-02-14 10:34:15 +00:00
CMsgPacker Msg ( protocol7 : : NETMSG_RCON_AUTH_OFF , true , true ) ;
2020-06-16 14:55:37 +00:00
SendMsg ( & Msg , MSGFLAG_VITAL , ClientID ) ;
}
2017-03-02 15:16:29 +00:00
m_aClients [ ClientID ] . m_AuthTries = 0 ;
2023-12-23 14:35:16 +00:00
m_aClients [ ClientID ] . m_pRconCmdToSend = nullptr ;
2017-03-02 15:16:29 +00:00
char aBuf [ 64 ] ;
2017-03-06 19:11:23 +00:00
if ( * pReason )
{
str_format ( aBuf , sizeof ( aBuf ) , " Logged out by %s. " , pReason ) ;
2017-03-02 15:16:29 +00:00
SendRconLine ( ClientID , aBuf ) ;
2017-03-06 19:11:23 +00:00
str_format ( aBuf , sizeof ( aBuf ) , " ClientID=%d with key=%s logged out by %s " , ClientID , m_AuthManager . KeyIdent ( m_aClients [ ClientID ] . m_AuthKey ) , pReason ) ;
2015-10-26 23:33:26 +00:00
}
2017-03-06 19:11:23 +00:00
else
{
2017-03-02 15:16:29 +00:00
SendRconLine ( ClientID , " Logout successful. " ) ;
2017-03-06 19:11:23 +00:00
str_format ( aBuf , sizeof ( aBuf ) , " ClientID=%d with key=%s logged out " , ClientID , m_AuthManager . KeyIdent ( m_aClients [ ClientID ] . m_AuthKey ) ) ;
2017-03-02 15:16:29 +00:00
}
m_aClients [ ClientID ] . m_Authed = AUTHED_NO ;
m_aClients [ ClientID ] . m_AuthKey = - 1 ;
2018-01-28 02:13:05 +00:00
GameServer ( ) - > OnSetAuthed ( ClientID , AUTHED_NO ) ;
2017-03-02 15:16:29 +00:00
Console ( ) - > Print ( IConsole : : OUTPUT_LEVEL_STANDARD , " server " , aBuf ) ;
}
void CServer : : LogoutKey ( int Key , const char * pReason )
{
for ( int i = 0 ; i < MAX_CLIENTS ; i + + )
if ( m_aClients [ i ] . m_AuthKey = = Key )
LogoutClient ( i , pReason ) ;
2015-10-26 23:33:26 +00:00
}
2018-02-26 17:01:02 +00:00
void CServer : : ConchainRconPasswordChangeGeneric ( int Level , const char * pCurrent , IConsole : : IResult * pResult )
2014-09-16 19:14:31 +00:00
{
if ( pResult - > NumArguments ( ) = = 1 )
{
2017-03-06 17:02:19 +00:00
int KeySlot = m_AuthManager . DefaultKey ( Level ) ;
2018-02-26 17:01:02 +00:00
const char * pNew = pResult - > GetString ( 0 ) ;
if ( str_comp ( pCurrent , pNew ) = = 0 )
2017-03-04 07:58:02 +00:00
{
2018-02-26 17:01:02 +00:00
return ;
}
if ( KeySlot = = - 1 & & pNew [ 0 ] )
{
m_AuthManager . AddDefaultKey ( Level , pNew ) ;
2017-03-04 07:58:02 +00:00
}
2017-03-06 19:11:23 +00:00
else if ( KeySlot > = 0 )
2017-03-04 07:58:02 +00:00
{
2018-02-26 17:01:02 +00:00
if ( ! pNew [ 0 ] )
2017-03-06 17:02:19 +00:00
{
AuthRemoveKey ( KeySlot ) ;
// Already logs users out.
}
2017-03-04 20:06:22 +00:00
else
2017-03-06 17:02:19 +00:00
{
2018-02-26 17:01:02 +00:00
m_AuthManager . UpdateKey ( KeySlot , pNew , Level ) ;
2017-03-06 17:02:19 +00:00
LogoutKey ( KeySlot , " key update " ) ;
}
2017-03-04 07:58:02 +00:00
}
2014-09-16 19:14:31 +00:00
}
}
2017-03-06 17:02:19 +00:00
void CServer : : ConchainRconPasswordChange ( IConsole : : IResult * pResult , void * pUserData , IConsole : : FCommandCallback pfnCallback , void * pCallbackUserData )
2014-09-16 19:14:31 +00:00
{
2021-12-14 23:28:51 +00:00
CServer * pThis = static_cast < CServer * > ( pUserData ) ;
pThis - > ConchainRconPasswordChangeGeneric ( AUTHED_ADMIN , pThis - > Config ( ) - > m_SvRconPassword , pResult ) ;
2014-09-16 19:14:31 +00:00
pfnCallback ( pResult , pCallbackUserData ) ;
2017-03-06 17:02:19 +00:00
}
2017-03-02 15:16:29 +00:00
2017-03-06 17:02:19 +00:00
void CServer : : ConchainRconModPasswordChange ( IConsole : : IResult * pResult , void * pUserData , IConsole : : FCommandCallback pfnCallback , void * pCallbackUserData )
{
2021-12-14 23:28:51 +00:00
CServer * pThis = static_cast < CServer * > ( pUserData ) ;
pThis - > ConchainRconPasswordChangeGeneric ( AUTHED_MOD , pThis - > Config ( ) - > m_SvRconModPassword , pResult ) ;
2017-03-06 17:02:19 +00:00
pfnCallback ( pResult , pCallbackUserData ) ;
2014-09-16 19:14:31 +00:00
}
2015-10-23 00:33:10 +00:00
void CServer : : ConchainRconHelperPasswordChange ( IConsole : : IResult * pResult , void * pUserData , IConsole : : FCommandCallback pfnCallback , void * pCallbackUserData )
{
2021-12-14 23:28:51 +00:00
CServer * pThis = static_cast < CServer * > ( pUserData ) ;
pThis - > ConchainRconPasswordChangeGeneric ( AUTHED_HELPER , pThis - > Config ( ) - > m_SvRconHelperPassword , pResult ) ;
2015-10-23 00:33:10 +00:00
pfnCallback ( pResult , pCallbackUserData ) ;
2014-09-16 19:14:31 +00:00
}
2020-04-23 17:02:10 +00:00
void CServer : : ConchainMapUpdate ( IConsole : : IResult * pResult , void * pUserData , IConsole : : FCommandCallback pfnCallback , void * pCallbackUserData )
{
pfnCallback ( pResult , pCallbackUserData ) ;
if ( pResult - > NumArguments ( ) > = 1 )
{
CServer * pThis = static_cast < CServer * > ( pUserData ) ;
pThis - > m_MapReload = str_comp ( pThis - > Config ( ) - > m_SvMap , pThis - > m_aCurrentMap ) ! = 0 ;
}
}
2021-12-21 11:23:17 +00:00
void CServer : : ConchainSixupUpdate ( IConsole : : IResult * pResult , void * pUserData , IConsole : : FCommandCallback pfnCallback , void * pCallbackUserData )
{
pfnCallback ( pResult , pCallbackUserData ) ;
CServer * pThis = static_cast < CServer * > ( pUserData ) ;
if ( pResult - > NumArguments ( ) > = 1 & & pThis - > m_aCurrentMap [ 0 ] ! = ' \0 ' )
2022-02-17 18:50:02 +00:00
pThis - > m_MapReload | = ( pThis - > m_apCurrentMapData [ MAP_TYPE_SIXUP ] ! = 0 ) ! = ( pResult - > GetInteger ( 0 ) ! = 0 ) ;
2021-12-21 11:23:17 +00:00
}
2022-06-13 19:43:05 +00:00
void CServer : : ConchainLoglevel ( IConsole : : IResult * pResult , void * pUserData , IConsole : : FCommandCallback pfnCallback , void * pCallbackUserData )
{
CServer * pSelf = ( CServer * ) pUserData ;
pfnCallback ( pResult , pCallbackUserData ) ;
if ( pResult - > NumArguments ( ) )
{
pSelf - > m_pFileLogger - > SetFilter ( CLogFilter { IConsole : : ToLogLevelFilter ( g_Config . m_Loglevel ) } ) ;
}
}
void CServer : : ConchainStdoutOutputLevel ( IConsole : : IResult * pResult , void * pUserData , IConsole : : FCommandCallback pfnCallback , void * pCallbackUserData )
{
CServer * pSelf = ( CServer * ) pUserData ;
pfnCallback ( pResult , pCallbackUserData ) ;
if ( pResult - > NumArguments ( ) & & pSelf - > m_pStdoutLogger )
{
pSelf - > m_pStdoutLogger - > SetFilter ( CLogFilter { IConsole : : ToLogLevelFilter ( g_Config . m_StdoutOutputLevel ) } ) ;
}
}
2017-12-20 15:56:34 +00:00
# if defined(CONF_FAMILY_UNIX)
void CServer : : ConchainConnLoggingServerChange ( IConsole : : IResult * pResult , void * pUserData , IConsole : : FCommandCallback pfnCallback , void * pCallbackUserData )
{
pfnCallback ( pResult , pCallbackUserData ) ;
if ( pResult - > NumArguments ( ) = = 1 )
{
CServer * pServer = ( CServer * ) pUserData ;
// open socket to send new connections
if ( ! pServer - > m_ConnLoggingSocketCreated )
{
2017-12-20 15:56:44 +00:00
pServer - > m_ConnLoggingSocket = net_unix_create_unnamed ( ) ;
2017-12-20 15:56:34 +00:00
if ( pServer - > m_ConnLoggingSocket = = - 1 )
{
pServer - > Console ( ) - > Print ( IConsole : : OUTPUT_LEVEL_STANDARD , " server " , " Failed to created socket for communication with the connection logging server. " ) ;
}
else
{
pServer - > m_ConnLoggingSocketCreated = true ;
}
}
// set the destination address for the connection logging
2017-12-20 15:56:44 +00:00
net_unix_set_addr ( & pServer - > m_ConnLoggingDestAddr , pResult - > GetString ( 0 ) ) ;
2017-12-20 15:56:34 +00:00
}
}
# endif
2010-05-29 07:25:38 +00:00
void CServer : : RegisterCommands ( )
{
m_pConsole = Kernel ( ) - > RequestInterface < IConsole > ( ) ;
2012-03-04 11:47:09 +00:00
m_pGameServer = Kernel ( ) - > RequestInterface < IGameServer > ( ) ;
m_pMap = Kernel ( ) - > RequestInterface < IEngineMap > ( ) ;
m_pStorage = Kernel ( ) - > RequestInterface < IStorage > ( ) ;
2020-05-13 20:27:49 +00:00
m_pAntibot = Kernel ( ) - > RequestInterface < IEngineAntibot > ( ) ;
2011-04-13 18:37:12 +00:00
2024-01-13 12:27:56 +00:00
Kernel ( ) - > RegisterInterface ( static_cast < IHttp * > ( & m_Http ) , false ) ;
2022-05-23 15:19:17 +00:00
2012-03-04 11:47:09 +00:00
// register console commands
2015-12-28 15:14:52 +00:00
Console ( ) - > Register ( " kick " , " i[id] ?r[reason] " , CFGFLAG_SERVER , ConKick , this , " Kick player with specified id for any reason " ) ;
2020-01-24 00:55:09 +00:00
Console ( ) - > Register ( " status " , " ?r[name] " , CFGFLAG_SERVER , ConStatus , this , " List players containing name or all players " ) ;
2021-12-08 17:41:16 +00:00
Console ( ) - > Register ( " shutdown " , " ?r[reason] " , CFGFLAG_SERVER , ConShutdown , this , " Shut down " ) ;
2011-12-29 23:07:17 +00:00
Console ( ) - > Register ( " logout " , " " , CFGFLAG_SERVER , ConLogout , this , " Logout of rcon " ) ;
2019-04-04 17:16:10 +00:00
Console ( ) - > Register ( " show_ips " , " ?i[show] " , CFGFLAG_SERVER , ConShowIps , this , " Show IP addresses in rcon commands (1 = on, 0 = off) " ) ;
2010-05-29 07:25:38 +00:00
2020-09-26 19:41:58 +00:00
Console ( ) - > Register ( " record " , " ?s[file] " , CFGFLAG_SERVER | CFGFLAG_STORE , ConRecord , this , " Record to a file " ) ;
2011-07-14 20:07:21 +00:00
Console ( ) - > Register ( " stoprecord " , " " , CFGFLAG_SERVER , ConStopRecord , this , " Stop recording " ) ;
2011-04-13 18:37:12 +00:00
2011-07-14 20:07:21 +00:00
Console ( ) - > Register ( " reload " , " " , CFGFLAG_SERVER , ConMapReload , this , " Reload the map " ) ;
2011-08-13 00:11:06 +00:00
2020-09-26 19:41:58 +00:00
Console ( ) - > Register ( " add_sqlserver " , " s['r'|'w'] s[Database] s[Prefix] s[User] s[Password] s[IP] i[Port] ?i[SetUpDatabase ?] " , CFGFLAG_SERVER | CFGFLAG_NONTEEHISTORIC , ConAddSqlServer , this , " add a sqlserver " ) ;
2016-01-17 16:57:38 +00:00
Console ( ) - > Register ( " dump_sqlservers " , " s['r'|'w'] " , CFGFLAG_SERVER , ConDumpSqlServers , this , " dumps all sqlservers readservers = r, writeservers = w " ) ;
2015-11-23 21:49:18 +00:00
2020-10-04 09:40:38 +00:00
Console ( ) - > Register ( " auth_add " , " s[ident] s[level] r[pw] " , CFGFLAG_SERVER | CFGFLAG_NONTEEHISTORIC , ConAuthAdd , this , " Add a rcon key " ) ;
2020-09-26 19:41:58 +00:00
Console ( ) - > Register ( " auth_add_p " , " s[ident] s[level] s[hash] s[salt] " , CFGFLAG_SERVER | CFGFLAG_NONTEEHISTORIC , ConAuthAddHashed , this , " Add a prehashed rcon key " ) ;
2020-10-04 09:40:38 +00:00
Console ( ) - > Register ( " auth_change " , " s[ident] s[level] r[pw] " , CFGFLAG_SERVER | CFGFLAG_NONTEEHISTORIC , ConAuthUpdate , this , " Update a rcon key " ) ;
2020-09-26 19:41:58 +00:00
Console ( ) - > Register ( " auth_change_p " , " s[ident] s[level] s[hash] s[salt] " , CFGFLAG_SERVER | CFGFLAG_NONTEEHISTORIC , ConAuthUpdateHashed , this , " Update a rcon key with prehashed data " ) ;
Console ( ) - > Register ( " auth_remove " , " s[ident] " , CFGFLAG_SERVER | CFGFLAG_NONTEEHISTORIC , ConAuthRemove , this , " Remove a rcon key " ) ;
2017-03-06 19:11:23 +00:00
Console ( ) - > Register ( " auth_list " , " " , CFGFLAG_SERVER , ConAuthList , this , " List all rcon keys " ) ;
2017-03-02 15:16:29 +00:00
2022-10-19 21:46:06 +00:00
RustVersionRegister ( * Console ( ) ) ;
2010-05-29 07:25:38 +00:00
Console ( ) - > Chain ( " sv_name " , ConchainSpecialInfoupdate , this ) ;
Console ( ) - > Chain ( " password " , ConchainSpecialInfoupdate , this ) ;
2023-09-04 12:03:56 +00:00
Console ( ) - > Chain ( " sv_spectator_slots " , ConchainSpecialInfoupdate , this ) ;
2010-06-03 12:48:32 +00:00
Console ( ) - > Chain ( " sv_max_clients_per_ip " , ConchainMaxclientsperipUpdate , this ) ;
2015-10-23 00:33:10 +00:00
Console ( ) - > Chain ( " access_level " , ConchainCommandAccessUpdate , this ) ;
2015-07-09 00:08:14 +00:00
2014-09-16 19:14:31 +00:00
Console ( ) - > Chain ( " sv_rcon_password " , ConchainRconPasswordChange , this ) ;
Console ( ) - > Chain ( " sv_rcon_mod_password " , ConchainRconModPasswordChange , this ) ;
2015-10-23 00:33:10 +00:00
Console ( ) - > Chain ( " sv_rcon_helper_password " , ConchainRconHelperPasswordChange , this ) ;
2020-04-23 17:02:10 +00:00
Console ( ) - > Chain ( " sv_map " , ConchainMapUpdate , this ) ;
2021-12-21 11:23:17 +00:00
Console ( ) - > Chain ( " sv_sixup " , ConchainSixupUpdate , this ) ;
2012-03-04 11:47:09 +00:00
2022-06-13 19:43:05 +00:00
Console ( ) - > Chain ( " loglevel " , ConchainLoglevel , this ) ;
Console ( ) - > Chain ( " stdout_output_level " , ConchainStdoutOutputLevel , this ) ;
2017-12-20 15:56:34 +00:00
# if defined(CONF_FAMILY_UNIX)
Console ( ) - > Chain ( " sv_conn_logging_server " , ConchainConnLoggingServerChange , this ) ;
# endif
2012-03-04 11:47:09 +00:00
// register console commands in sub parts
2012-08-16 22:03:53 +00:00
m_ServerBan . InitServerBan ( Console ( ) , Storage ( ) , this ) ;
2023-11-25 16:09:14 +00:00
m_NameBans . InitConsole ( Console ( ) ) ;
2012-03-04 11:47:09 +00:00
m_pGameServer - > OnConsoleInit ( ) ;
2011-04-13 18:37:12 +00:00
}
2010-05-29 07:25:38 +00:00
int CServer : : SnapNewID ( )
{
return m_IDPool . NewID ( ) ;
}
void CServer : : SnapFreeID ( int ID )
{
m_IDPool . FreeID ( ID ) ;
}
2011-02-12 10:40:36 +00:00
void * CServer : : SnapNewItem ( int Type , int ID , int Size )
2010-05-29 07:25:38 +00:00
{
2022-05-11 21:12:20 +00:00
dbg_assert ( ID > = - 1 & & ID < = 0xffff , " incorrect id " ) ;
2011-04-13 18:37:12 +00:00
return ID < 0 ? 0 : m_SnapshotBuilder . NewItem ( Type , ID , Size ) ;
2010-05-29 07:25:38 +00:00
}
void CServer : : SnapSetStaticsize ( int ItemType , int Size )
{
m_SnapshotDelta . SetStaticsize ( ItemType , Size ) ;
}
2022-05-26 23:17:01 +00:00
CServer * CreateServer ( ) { return new CServer ( ) ; }
2010-05-29 07:25:38 +00:00
2011-04-09 06:41:31 +00:00
// DDRace
2021-02-08 21:26:26 +00:00
void CServer : : GetClientAddr ( int ClientID , NETADDR * pAddr ) const
2011-04-09 06:41:31 +00:00
{
2017-03-06 19:11:23 +00:00
if ( ClientID > = 0 & & ClientID < MAX_CLIENTS & & m_aClients [ ClientID ] . m_State = = CClient : : STATE_INGAME )
{
2012-02-02 23:16:22 +00:00
* pAddr = * m_NetServer . ClientAddr ( ClientID ) ;
2011-12-31 22:00:00 +00:00
}
2011-04-09 06:41:31 +00:00
}
2023-11-20 21:23:39 +00:00
const char * CServer : : GetAnnouncementLine ( const char * pFileName )
2010-11-22 10:59:25 +00:00
{
2023-01-13 14:49:02 +00:00
if ( str_comp ( pFileName , m_aAnnouncementFile ) ! = 0 )
{
str_copy ( m_aAnnouncementFile , pFileName ) ;
m_vAnnouncements . clear ( ) ;
2017-07-08 11:38:27 +00:00
2023-01-13 14:49:02 +00:00
IOHANDLE File = m_pStorage - > OpenFile ( pFileName , IOFLAG_READ | IOFLAG_SKIP_BOM , IStorage : : TYPE_ALL ) ;
if ( ! File )
return 0 ;
2017-07-08 11:38:27 +00:00
2023-01-13 14:49:02 +00:00
char * pLine ;
CLineReader Reader ;
Reader . Init ( File ) ;
while ( ( pLine = Reader . Get ( ) ) )
if ( str_length ( pLine ) & & pLine [ 0 ] ! = ' # ' )
m_vAnnouncements . emplace_back ( pLine ) ;
2021-10-17 20:50:59 +00:00
2023-01-13 14:49:02 +00:00
io_close ( File ) ;
}
if ( m_vAnnouncements . empty ( ) )
2021-10-17 20:50:59 +00:00
{
return 0 ;
}
2023-01-13 14:49:02 +00:00
else if ( m_vAnnouncements . size ( ) = = 1 )
2010-11-22 10:59:25 +00:00
{
2017-07-08 11:38:27 +00:00
m_AnnouncementLastLine = 0 ;
}
2021-12-14 23:28:51 +00:00
else if ( ! Config ( ) - > m_SvAnnouncementRandom )
2017-07-08 11:38:27 +00:00
{
2023-01-13 14:49:02 +00:00
if ( + + m_AnnouncementLastLine > = m_vAnnouncements . size ( ) )
m_AnnouncementLastLine % = m_vAnnouncements . size ( ) ;
2017-07-08 11:38:27 +00:00
}
else
{
unsigned Rand ;
do
2021-10-17 20:50:59 +00:00
{
2023-01-13 14:49:02 +00:00
Rand = rand ( ) % m_vAnnouncements . size ( ) ;
2021-10-17 20:50:59 +00:00
} while ( Rand = = m_AnnouncementLastLine ) ;
2011-04-09 06:41:31 +00:00
2017-07-08 11:38:27 +00:00
m_AnnouncementLastLine = Rand ;
2010-11-22 10:59:25 +00:00
}
2017-07-08 11:38:27 +00:00
2023-01-13 14:49:02 +00:00
return m_vAnnouncements [ m_AnnouncementLastLine ] . c_str ( ) ;
2010-11-22 10:59:25 +00:00
}
2010-12-19 05:25:01 +00:00
2017-10-13 00:25:50 +00:00
int * CServer : : GetIdMap ( int ClientID )
2013-12-31 05:13:57 +00:00
{
2020-10-27 17:57:14 +00:00
return m_aIdMap + VANILLA_MAX_CLIENTS * ClientID ;
2013-12-31 05:13:57 +00:00
}
2017-06-06 03:51:12 +00:00
2017-10-13 00:25:50 +00:00
bool CServer : : SetTimedOut ( int ClientID , int OrigID )
{
2020-09-26 19:41:58 +00:00
if ( ! m_NetServer . SetTimedOut ( ClientID , OrigID ) )
2017-06-06 03:51:12 +00:00
{
return false ;
}
2020-06-20 16:50:14 +00:00
m_aClients [ ClientID ] . m_Sixup = m_aClients [ OrigID ] . m_Sixup ;
2021-01-17 16:44:48 +00:00
if ( m_aClients [ OrigID ] . m_Authed ! = AUTHED_NO )
{
LogoutClient ( ClientID , " Timeout Protection " ) ;
}
2017-06-06 03:51:12 +00:00
DelClientCallback ( OrigID , " Timeout Protection used " , this ) ;
2019-03-02 10:50:33 +00:00
m_aClients [ ClientID ] . m_Authed = AUTHED_NO ;
2019-07-25 23:39:53 +00:00
m_aClients [ ClientID ] . m_Flags = m_aClients [ OrigID ] . m_Flags ;
2021-12-14 05:50:30 +00:00
m_aClients [ ClientID ] . m_DDNetVersion = m_aClients [ OrigID ] . m_DDNetVersion ;
m_aClients [ ClientID ] . m_GotDDNetVersionPacket = m_aClients [ OrigID ] . m_GotDDNetVersionPacket ;
m_aClients [ ClientID ] . m_DDNetVersionSettled = m_aClients [ OrigID ] . m_DDNetVersionSettled ;
2017-06-06 03:51:12 +00:00
return true ;
}
2017-10-13 00:25:50 +00:00
void CServer : : SetErrorShutdown ( const char * pReason )
{
2022-07-09 16:14:56 +00:00
str_copy ( m_aErrorShutdownReason , pReason ) ;
2017-10-13 00:25:50 +00:00
}
2022-06-13 19:43:05 +00:00
void CServer : : SetLoggers ( std : : shared_ptr < ILogger > & & pFileLogger , std : : shared_ptr < ILogger > & & pStdoutLogger )
{
m_pFileLogger = pFileLogger ;
m_pStdoutLogger = pStdoutLogger ;
}