2 * Copyright (C) 2021-2023 Mt.ZERO Software, Harry Godden - All Rights Reserved
5 #define _DEFAULT_SOURCE
10 volatile sig_atomic_t sig_stop
;
12 #include "gameserver.h"
13 #include "highscores.c"
14 #include "servermonitor_server.c"
15 #include "vg/vg_opt.h"
16 #include "network_common.h"
17 #include "gameserver_db.h"
19 static void inthandler( int signum
) {
23 static const u64 k_connection_unauthorized
= 0xffffffffffffffff;
25 static u64_steamid
get_connection_authsteamid( SteamNetworkingMessage_t
*msg
){
26 i64 userdata
= SteamAPI_ISteamNetworkingSockets_GetConnectionUserData(
27 hSteamNetworkingSockets
, msg
->m_conn
);
29 return *((u64_steamid
*)&userdata
);
32 static void set_connection_authsteamid(HSteamNetConnection con
, u64_steamid id
){
33 i64 userdata
= *((i64
*)&id
);
35 SteamAPI_ISteamNetworkingSockets_SetConnectionUserData(
36 hSteamNetworkingSockets
, con
, userdata
);
39 static void gameserver_send_to_all( int ignore
,
40 const void *pData
, u32 cbData
,
42 for( int i
=0; i
<vg_list_size(gameserver
.clients
); i
++ ){
43 struct gameserver_client
*client
= &gameserver
.clients
[i
];
45 if( (i
==ignore
) || !client
->active
)
48 SteamAPI_ISteamNetworkingSockets_SendMessageToConnection(
49 hSteamNetworkingSockets
, client
->connection
,
50 pData
, cbData
, nSendFlags
, NULL
);
54 static void gameserver_player_join( int index
){
55 struct gameserver_client
*joiner
= &gameserver
.clients
[index
];
57 netmsg_playerjoin join
= { .inetmsg_id
= k_inetmsg_playerjoin
,
59 gameserver_send_to_all( index
, &join
, sizeof(join
),
60 k_nSteamNetworkingSend_Reliable
);
62 /* update the joining user about current connections */
64 netmsg_playerusername
*username
=
65 alloca( sizeof(netmsg_playerusername
) + NETWORK_USERNAME_MAX
);
66 username
->inetmsg_id
= k_inetmsg_playerusername
;
68 netmsg_playeritem
*item
=
69 alloca( sizeof(netmsg_playeritem
) + ADDON_UID_MAX
);
70 item
->inetmsg_id
= k_inetmsg_playeritem
;
72 for( int i
=0; i
<vg_list_size(gameserver
.clients
); i
++ ){
73 struct gameserver_client
*client
= &gameserver
.clients
[i
];
75 if( (i
==index
) || !client
->active
)
79 netmsg_playerjoin init
= { .inetmsg_id
= k_inetmsg_playerjoin
,
81 SteamAPI_ISteamNetworkingSockets_SendMessageToConnection(
82 hSteamNetworkingSockets
, joiner
->connection
,
83 &init
, sizeof(init
), k_nSteamNetworkingSend_Reliable
, NULL
);
87 u32 chs
= vg_strncpy( client
->username
, username
->name
,
89 k_strncpy_always_add_null
);
90 u32 size
= sizeof(netmsg_playerusername
) + chs
+ 1;
91 SteamAPI_ISteamNetworkingSockets_SendMessageToConnection(
92 hSteamNetworkingSockets
, joiner
->connection
,
93 username
, size
, k_nSteamNetworkingSend_Reliable
, NULL
);
96 for( int j
=0; j
<k_netmsg_playeritem_max
; j
++ ){
97 chs
= vg_strncpy( client
->items
[j
], item
->uid
, ADDON_UID_MAX
,
98 k_strncpy_always_add_null
);
101 size
= sizeof(netmsg_playeritem
) + chs
+ 1;
102 SteamAPI_ISteamNetworkingSockets_SendMessageToConnection(
103 hSteamNetworkingSockets
, joiner
->connection
,
104 item
, size
, k_nSteamNetworkingSend_Reliable
, NULL
);
109 static void gameserver_player_leave( int index
){
110 netmsg_playerjoin leave
;
111 leave
.inetmsg_id
= k_inetmsg_playerleave
;
114 vg_info( "Player leave (%d)\n", index
);
115 gameserver_send_to_all( index
, &leave
, sizeof(leave
),
116 k_nSteamNetworkingSend_Reliable
);
119 static void new_client_connecting( HSteamNetConnection client
){
123 for( int i
=0; i
<vg_list_size(gameserver
.clients
); i
++ ){
124 if( !gameserver
.clients
[i
].active
){
131 vg_error( "Server full\n" );
132 SteamAPI_ISteamNetworkingSockets_CloseConnection(
133 hSteamNetworkingSockets
, client
,
139 EResult accept_status
= SteamAPI_ISteamNetworkingSockets_AcceptConnection(
140 hSteamNetworkingSockets
, client
);
141 if( accept_status
== k_EResultOK
){
142 vg_success( "Accepted client (id: %u, index: %d)\n", client
, index
);
143 memset( &gameserver
.clients
[index
], 0, sizeof(struct gameserver_client
) );
145 gameserver
.clients
[index
].active
= 1;
146 gameserver
.clients
[index
].connection
= client
;
148 SteamAPI_ISteamNetworkingSockets_SetConnectionPollGroup(
149 hSteamNetworkingSockets
,
150 client
, gameserver
.client_group
);
152 /* Just to be sure */
153 set_connection_authsteamid( client
, -1 );
154 gameserver_player_join( index
);
157 vg_warn( "Error accepting client (id: %u)\n", client
);
158 SteamAPI_ISteamNetworkingSockets_CloseConnection(
159 hSteamNetworkingSockets
, client
,
160 k_ESteamNetConnectionEnd_Misc_InternalError
,
162 gameserver
.clients
[index
].active
= 0;
163 gameserver
.clients
[index
].connection
= 0;
167 static void on_auth_status( CallbackMsg_t
*msg
){
168 SteamNetAuthenticationStatus_t
*info
= (void *)msg
->m_pubParam
;
169 vg_info( " Authentication availibility: %s\n",
170 string_ESteamNetworkingAvailability(info
->m_eAvail
) );
171 vg_info( " %s\n", info
->m_debugMsg
);
174 static int gameserver_client_index( HSteamNetConnection hconn
){
175 for( int i
=0; i
<vg_list_size(gameserver
.clients
); i
++ ){
176 struct gameserver_client
*client
= &gameserver
.clients
[i
];
178 if( client
->active
){
179 if( client
->connection
== hconn
){
187 static void on_connect_status( CallbackMsg_t
*msg
){
188 SteamNetConnectionStatusChangedCallback_t
*info
= (void *)msg
->m_pubParam
;
189 vg_info( " Connection status changed for %lu\n", info
->m_hConn
);
191 vg_info( " %s -> %s\n",
192 string_ESteamNetworkingConnectionState(info
->m_eOldState
),
193 string_ESteamNetworkingConnectionState(info
->m_info
.m_eState
) );
195 if( info
->m_info
.m_eState
==k_ESteamNetworkingConnectionState_Connecting
){
196 new_client_connecting( info
->m_hConn
);
199 if( (info
->m_info
.m_eState
==
200 k_ESteamNetworkingConnectionState_ClosedByPeer
) ||
201 (info
->m_info
.m_eState
==
202 k_ESteamNetworkingConnectionState_ProblemDetectedLocally
) ){
204 int client_id
= gameserver_client_index( info
->m_hConn
);
205 if( client_id
!= -1 ){
206 struct gameserver_client
*client
= &gameserver
.clients
[client_id
];
207 client
->connection
= 0;
209 gameserver_player_leave(client_id
);
212 vg_info( "End reason: %d\n", info
->m_info
.m_eEndReason
);
213 SteamAPI_ISteamNetworkingSockets_CloseConnection(
214 hSteamNetworkingSockets
, info
->m_hConn
, 0, NULL
, 0 );
218 static void gameserver_rx_auth( SteamNetworkingMessage_t
*msg
){
219 if( gameserver
.auth_mode
!= eServerModeAuthentication
){
220 vg_error( "Running server without authentication. "
221 "Connection %u tried to authenticate.\n", msg
->m_conn
);
225 if( get_connection_authsteamid( msg
) != k_connection_unauthorized
){
226 vg_warn( "Already authorized this user but app ticket was sent"
227 " again (%u)\n", msg
->m_conn
);
231 vg_low( "Attempting to verify user\n" );
233 if( msg
->m_cbSize
< sizeof(netmsg_auth
) ){
234 vg_error( "Malformed auth ticket, too small (%u)\n", msg
->m_conn
);
238 netmsg_auth
*auth
= msg
->m_pData
;
240 if( msg
->m_cbSize
< sizeof(netmsg_auth
)+auth
->ticket_length
||
241 auth
->ticket_length
> 1024 ){
242 vg_error( "Malformed auth ticket, ticket_length incorrect (%u)\n",
243 auth
->ticket_length
);
248 u32 ticket_len
= 1024;
250 int success
= SteamEncryptedAppTicket_BDecryptTicket(
251 auth
->ticket
, auth
->ticket_length
, decrypted
,
252 &ticket_len
, gameserver
.app_symmetric_key
,
253 k_nSteamEncryptedAppTicketSymmetricKeyLen
);
256 vg_error( "Failed to decrypt users ticket (client %u)\n", msg
->m_conn
);
257 vg_error( " ticket length: %u\n", auth
->ticket_length
);
259 SteamAPI_ISteamNetworkingSockets_CloseConnection(
260 hSteamNetworkingSockets
,
261 msg
->m_conn
, 0, NULL
, 1 );
265 if( SteamEncryptedAppTicket_GetTicketIssueTime( decrypted
, ticket_len
)){
266 RTime32 ctime
= time(NULL
),
267 tickettime
= SteamEncryptedAppTicket_GetTicketIssueTime(
268 decrypted
, ticket_len
),
269 expiretime
= tickettime
+ 24*3*60*60;
271 if( ctime
> expiretime
){
272 vg_error( "Ticket expired (client %u)\n", msg
->m_conn
);
274 /* TODO: Send expired information */
275 SteamAPI_ISteamNetworkingSockets_CloseConnection(
276 hSteamNetworkingSockets
,
277 msg
->m_conn
, 0, NULL
, 1 );
283 SteamEncryptedAppTicket_GetTicketSteamID( decrypted
, ticket_len
, &steamid
);
284 vg_success( "User is authenticated! steamid %lu (%u)\n",
285 steamid
.m_unAll64Bits
, msg
->m_conn
);
287 set_connection_authsteamid( msg
->m_conn
, steamid
.m_unAll64Bits
);
290 static int inet_require_auth( SteamNetworkingMessage_t
*msg
){
291 if( gameserver
.auth_mode
== eServerModeNoAuthentication
)
294 if( get_connection_authsteamid( msg
) == k_connection_unauthorized
){
295 vg_warn( "Unauthorized request! Disconnecting client: %u\n",
298 SteamAPI_ISteamNetworkingSockets_CloseConnection(
299 hSteamNetworkingSockets
,
300 msg
->m_conn
, 0, NULL
, 1 );
308 * Player updates sent to us
309 * -----------------------------------------------------------------------------
312 static int packet_minsize( SteamNetworkingMessage_t
*msg
, u32 size
){
313 if( msg
->m_cbSize
< size
) {
314 vg_error( "Invalid packet size (must be at least %u)\n", size
);
322 static void gameserver_rx_200_300( SteamNetworkingMessage_t
*msg
){
323 netmsg_blank
*tmp
= msg
->m_pData
;
325 int client_id
= gameserver_client_index( msg
->m_conn
);
326 if( client_id
== -1 ) return;
328 if( tmp
->inetmsg_id
== k_inetmsg_playerusername
){
329 if( !packet_minsize( msg
, sizeof(netmsg_playerusername
)+1 ))
332 struct gameserver_client
*client
= &gameserver
.clients
[ client_id
];
333 netmsg_playerusername
*src
= msg
->m_pData
;
335 u32 name_len
= network_msgstring( src
->name
, msg
->m_cbSize
,
336 sizeof(netmsg_playerusername
),
338 NETWORK_USERNAME_MAX
);
340 /* update other users about this change */
341 netmsg_playerusername
*prop
= alloca(sizeof(netmsg_playerusername
)+
342 NETWORK_USERNAME_MAX
);
344 prop
->inetmsg_id
= k_inetmsg_playerusername
;
345 prop
->index
= client_id
;
346 u32 chs
= vg_strncpy( client
->username
, prop
->name
, NETWORK_USERNAME_MAX
,
347 k_strncpy_always_add_null
);
349 vg_info( "client #%d changed name to: %s\n", client_id
, prop
->name
);
351 u32 propsize
= sizeof(netmsg_playerusername
) + chs
+ 1;
352 gameserver_send_to_all( client_id
, prop
, propsize
,
353 k_nSteamNetworkingSend_Reliable
);
355 else if( tmp
->inetmsg_id
== k_inetmsg_playerframe
){
357 netmsg_playerframe
*frame
= alloca(msg
->m_cbSize
);
358 memcpy( frame
, msg
->m_pData
, msg
->m_cbSize
);
359 frame
->client
= client_id
;
360 gameserver_send_to_all( client_id
, frame
, msg
->m_cbSize
,
361 k_nSteamNetworkingSend_Unreliable
);
363 else if( tmp
->inetmsg_id
== k_inetmsg_playeritem
){
364 netmsg_playeritem
*item
= msg
->m_pData
;
367 struct gameserver_client
*client
= &gameserver
.clients
[ client_id
];
369 if( item
->type_index
>= k_netmsg_playeritem_max
){
370 vg_warn( "Client #%d invalid equip type %u\n",
371 client_id
, (u32
)item
->type_index
);
375 char *dest
= client
->items
[ item
->type_index
];
377 network_msgstring( item
->uid
, msg
->m_cbSize
, sizeof(netmsg_playeritem
),
378 dest
, ADDON_UID_MAX
);
380 vg_info( "Client #%d equiped: [%s] %s\n",
382 (const char *[]){[k_netmsg_playeritem_board
]="board",
383 [k_netmsg_playeritem_player
]="player",
384 [k_netmsg_playeritem_world0
]="world0",
385 [k_netmsg_playeritem_world1
]="world1"
386 }[item
->type_index
], item
->uid
);
389 netmsg_playeritem
*prop
= alloca(msg
->m_cbSize
);
390 memcpy( prop
, msg
->m_pData
, msg
->m_cbSize
);
391 prop
->client
= client_id
;
392 gameserver_send_to_all( client_id
, prop
, msg
->m_cbSize
,
393 k_nSteamNetworkingSend_Reliable
);
398 static void on_inet_score_request( SteamNetworkingMessage_t
*msg
){
399 if( !inet_require_auth(msg
) ) return;
401 SteamAPI_ISteamNetworkingSockets_SendMessageToConnection(
402 hSteamNetworkingSockets
, msg
->m_conn
,
403 &scoreboard_client_data
, sizeof(netmsg_scoreboard
),
404 k_nSteamNetworkingSend_Reliable
, NULL
);
407 static void on_inet_set_nickname( SteamNetworkingMessage_t
*msg
){
408 if(!inet_require_auth(msg
)) return;
410 u64_steamid steamid
= get_connection_authsteamid(msg
);
411 netmsg_set_nickname
*setnick
= msg
->m_pData
;
412 if( msg
->m_cbSize
< sizeof(netmsg_set_nickname
) ){
413 vg_warn( "Invalid nickname request from client: %u, steamid: %lu\n",
414 msg
->m_conn
, steamid
);
418 highscore_set_user_nickname( steamid
, setnick
->nickname
);
421 static void on_inet_set_score( SteamNetworkingMessage_t
*msg
){
422 if(!inet_require_auth(msg
)) return;
424 u64_steamid steamid
= get_connection_authsteamid(msg
);
426 if( msg
->m_cbSize
< sizeof(netmsg_set_score
) ){
427 vg_warn( "Invalid set score post from client: %u, steamid: %lu\n",
428 msg
->m_conn
, steamid
);
432 netmsg_set_score
*info
= msg
->m_pData
;
434 if( msg
->m_cbSize
< sizeof(netmsg_set_score
) +
435 sizeof(struct netmsg_score_record
)*info
->record_count
){
436 vg_warn( "Malformed set score post from client: %u, steamid: %lu\n",
437 msg
->m_conn
, steamid
);
441 for( int i
=0; i
<info
->record_count
; i
++ ){
442 highscore_record temp
;
443 temp
.trackid
= info
->records
[i
].trackid
;
444 temp
.datetime
= time(NULL
);
445 temp
.playerid
= steamid
;
446 temp
.points
= info
->records
[i
].points
;
447 temp
.time
= info
->records
[i
].time
;
449 highscores_push_record( &temp
);
454 static void poll_connections(void){
455 SteamNetworkingMessage_t
*messages
[32];
459 len
= SteamAPI_ISteamNetworkingSockets_ReceiveMessagesOnPollGroup(
460 hSteamNetworkingSockets
,
461 gameserver
.client_group
, messages
, vg_list_size(messages
) );
466 for( int i
=0; i
<len
; i
++ ){
467 SteamNetworkingMessage_t
*msg
= messages
[i
];
469 if( msg
->m_cbSize
< sizeof(netmsg_blank
) ){
470 vg_warn( "Discarding message (too small: %d)\n",
475 netmsg_blank
*tmp
= msg
->m_pData
;
477 if( (tmp
->inetmsg_id
>= 200) && (tmp
->inetmsg_id
< 300) ){
478 gameserver_rx_200_300( msg
);
481 if( tmp
->inetmsg_id
== k_inetmsg_auth
)
482 gameserver_rx_auth( msg
);
484 else if( tmp
->inetmsg_id
== k_inetmsg_scores_request
)
485 on_inet_score_request( msg
);
486 else if( tmp
->inetmsg_id
== k_inetmsg_set_nickname
)
487 on_inet_set_nickname( msg
);
488 else if( tmp
->inetmsg_id
== k_inetmsg_set_score
)
489 on_inet_set_score( msg
);
490 else if( tmp
->inetmsg_id
== k_inetmsg_playerframe
)
491 on_inet_playerframe( msg
);
494 vg_warn( "Unknown inetmsg_id recieved from client. (%u)\n",
500 SteamAPI_SteamNetworkingMessage_t_Release( msg
);
505 static u64
seconds_to_server_ticks( double s
){
509 static void generate_boards(void){
510 FILE *fp
= fopen( "www/html/srhighscores.txt", "w" );
513 vg_error( "Can't write boards to www/html/srhighscores.txt\n" );
517 for( int i
=0; i
<vg_list_size(track_infos
); i
++ ){
518 struct netmsg_board
*board
= &scoreboard_client_data
.boards
[i
];
520 highscores_board_generate( board
->data
, i
, 10 );
521 highscores_board_printf( fp
, board
->data
, 10 );
527 int main( int argc
, char *argv
[] ){
529 char table
[DB_TABLE_UID_MAX
];
530 if( db_get_highscore_table_name( "sr003-local-mp_mtzero",
531 "megapark-yellow", 302, table
) ){
532 if( db_writeusertime( table
, 76561198072130043, 232, 1 ) ){
533 vg_success( "Written time\n" );
534 i32 v
= db_readusertime( table
, 76561198072130043 );
535 vg_success( "Returned time: %u\n", v
);
541 signal( SIGINT
, inthandler
);
542 signal( SIGQUIT
, inthandler
);
543 signal( SIGPIPE
, SIG_IGN
);
546 while( vg_argp( argc
, argv
) ){
547 if( vg_long_opt( "noauth" ) )
548 gameserver
.auth_mode
= eServerModeNoAuthentication
;
551 /* TODO: Options to override, ammend, remove etc */
553 vg_set_mem_quota( 80*1024*1024 );
556 highscores_init( 250000, 10000 );
558 if( !highscores_read(NULL
) )
559 highscores_create_db();
561 steamworks_ensure_txt( "2103940" );
563 if( gameserver
.auth_mode
== eServerModeAuthentication
){
564 if( !vg_load_steam_symetric_key( "application_key",
565 gameserver
.app_symmetric_key
)){
570 vg_warn( "Running without user authentication.\n" );
573 if( !SteamGameServer_Init( 0, 27400, 27401,
574 gameserver
.auth_mode
, "1.0.0.0" ) ){
575 vg_error( "SteamGameServer_Init failed\n" );
579 void *hSteamGameServer
= SteamAPI_SteamGameServer();
580 SteamAPI_ISteamGameServer_LogOnAnonymous( hSteamGameServer
);
582 SteamAPI_ManualDispatch_Init();
583 HSteamPipe hsteampipe
= SteamGameServer_GetHSteamPipe();
585 //hSteamHTTP = SteamAPI_SteamGameServerHTTP();
586 hSteamNetworkingSockets
=
587 SteamAPI_SteamGameServerNetworkingSockets_SteamAPI();
593 steam_register_callback( k_iSteamNetAuthenticationStatus
, on_auth_status
);
594 steam_register_callback( k_iSteamNetConnectionStatusChangedCallBack
,
597 vg_success( "Steamworks API running\n" );
598 steamworks_event_loop( hsteampipe
);
604 HSteamListenSocket listener
;
605 SteamNetworkingIPAddr localAddr
;
606 SteamAPI_SteamNetworkingIPAddr_Clear( &localAddr
);
607 localAddr
.m_port
= 27402;
609 listener
= SteamAPI_ISteamNetworkingSockets_CreateListenSocketIP(
610 hSteamNetworkingSockets
, &localAddr
, 0, NULL
);
611 gameserver
.client_group
= SteamAPI_ISteamNetworkingSockets_CreatePollGroup(
612 hSteamNetworkingSockets
);
614 u64 server_ticks
= 8000,
615 last_record_save
= 8000,
616 last_scoreboard_gen
= 0,
617 last_monitor_heartbeat
= 0;
620 monitor_start_server();
623 monitor_event_loop();
624 steamworks_event_loop( hsteampipe
);
631 (last_monitor_heartbeat
+ seconds_to_server_ticks(10.0))){
632 last_monitor_heartbeat
= server_ticks
;
636 if( server_ticks
> last_scoreboard_gen
+ seconds_to_server_ticks(60.0) ){
637 last_scoreboard_gen
= server_ticks
;
641 if( server_ticks
> last_record_save
+ seconds_to_server_ticks(10.0*60.0)){
642 last_record_save
= server_ticks
;
643 highscores_serialize_all();
647 highscores_serialize_all();
649 SteamAPI_ISteamNetworkingSockets_DestroyPollGroup( hSteamNetworkingSockets
,
650 gameserver
.client_group
);
651 SteamAPI_ISteamNetworkingSockets_CloseListenSocket(
652 hSteamNetworkingSockets
, listener
);
654 vg_info( "Shutting down\n..." );
655 SteamGameServer_Shutdown();