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 #include "vg/vg_msg.h"
21 static void inthandler( int signum
) {
25 static const u64 k_connection_unauthorized
= 0xffffffffffffffff;
27 static u64_steamid
get_connection_authsteamid( SteamNetworkingMessage_t
*msg
){
28 i64 userdata
= SteamAPI_ISteamNetworkingSockets_GetConnectionUserData(
29 hSteamNetworkingSockets
, msg
->m_conn
);
31 return *((u64_steamid
*)&userdata
);
34 static void set_connection_authsteamid(HSteamNetConnection con
, u64_steamid id
){
35 i64 userdata
= *((i64
*)&id
);
37 SteamAPI_ISteamNetworkingSockets_SetConnectionUserData(
38 hSteamNetworkingSockets
, con
, userdata
);
41 static void gameserver_send_to_all( int ignore
,
42 const void *pData
, u32 cbData
,
44 for( int i
=0; i
<vg_list_size(gameserver
.clients
); i
++ ){
45 struct gameserver_client
*client
= &gameserver
.clients
[i
];
47 if( (i
==ignore
) || !client
->active
)
50 SteamAPI_ISteamNetworkingSockets_SendMessageToConnection(
51 hSteamNetworkingSockets
, client
->connection
,
52 pData
, cbData
, nSendFlags
, NULL
);
56 static void gameserver_player_join( int index
){
57 struct gameserver_client
*joiner
= &gameserver
.clients
[index
];
59 netmsg_playerjoin join
= { .inetmsg_id
= k_inetmsg_playerjoin
,
61 gameserver_send_to_all( index
, &join
, sizeof(join
),
62 k_nSteamNetworkingSend_Reliable
);
64 /* update the joining user about current connections */
66 netmsg_playerusername
*username
=
67 alloca( sizeof(netmsg_playerusername
) + NETWORK_USERNAME_MAX
);
68 username
->inetmsg_id
= k_inetmsg_playerusername
;
70 netmsg_playeritem
*item
=
71 alloca( sizeof(netmsg_playeritem
) + ADDON_UID_MAX
);
72 item
->inetmsg_id
= k_inetmsg_playeritem
;
74 for( int i
=0; i
<vg_list_size(gameserver
.clients
); i
++ ){
75 struct gameserver_client
*client
= &gameserver
.clients
[i
];
77 if( (i
==index
) || !client
->active
)
81 netmsg_playerjoin init
= { .inetmsg_id
= k_inetmsg_playerjoin
,
83 SteamAPI_ISteamNetworkingSockets_SendMessageToConnection(
84 hSteamNetworkingSockets
, joiner
->connection
,
85 &init
, sizeof(init
), k_nSteamNetworkingSend_Reliable
, NULL
);
89 u32 chs
= vg_strncpy( client
->username
, username
->name
,
91 k_strncpy_always_add_null
);
92 u32 size
= sizeof(netmsg_playerusername
) + chs
+ 1;
93 SteamAPI_ISteamNetworkingSockets_SendMessageToConnection(
94 hSteamNetworkingSockets
, joiner
->connection
,
95 username
, size
, k_nSteamNetworkingSend_Reliable
, NULL
);
98 for( int j
=0; j
<k_netmsg_playeritem_max
; j
++ ){
99 chs
= vg_strncpy( client
->items
[j
], item
->uid
, ADDON_UID_MAX
,
100 k_strncpy_always_add_null
);
101 item
->type_index
= j
;
103 size
= sizeof(netmsg_playeritem
) + chs
+ 1;
104 SteamAPI_ISteamNetworkingSockets_SendMessageToConnection(
105 hSteamNetworkingSockets
, joiner
->connection
,
106 item
, size
, k_nSteamNetworkingSend_Reliable
, NULL
);
111 static void gameserver_player_leave( int index
){
112 netmsg_playerjoin leave
;
113 leave
.inetmsg_id
= k_inetmsg_playerleave
;
116 vg_info( "Player leave (%d)\n", index
);
117 gameserver_send_to_all( index
, &leave
, sizeof(leave
),
118 k_nSteamNetworkingSend_Reliable
);
121 static void new_client_connecting( HSteamNetConnection client
){
125 for( int i
=0; i
<vg_list_size(gameserver
.clients
); i
++ ){
126 if( !gameserver
.clients
[i
].active
){
133 vg_error( "Server full\n" );
134 SteamAPI_ISteamNetworkingSockets_CloseConnection(
135 hSteamNetworkingSockets
, client
,
141 EResult accept_status
= SteamAPI_ISteamNetworkingSockets_AcceptConnection(
142 hSteamNetworkingSockets
, client
);
143 if( accept_status
== k_EResultOK
){
144 vg_success( "Accepted client (id: %u, index: %d)\n", client
, index
);
145 memset( &gameserver
.clients
[index
], 0, sizeof(struct gameserver_client
) );
147 gameserver
.clients
[index
].active
= 1;
148 gameserver
.clients
[index
].connection
= client
;
150 SteamAPI_ISteamNetworkingSockets_SetConnectionPollGroup(
151 hSteamNetworkingSockets
,
152 client
, gameserver
.client_group
);
154 /* Just to be sure */
155 set_connection_authsteamid( client
, -1 );
156 gameserver_player_join( index
);
159 vg_warn( "Error accepting client (id: %u)\n", client
);
160 SteamAPI_ISteamNetworkingSockets_CloseConnection(
161 hSteamNetworkingSockets
, client
,
162 k_ESteamNetConnectionEnd_Misc_InternalError
,
164 gameserver
.clients
[index
].active
= 0;
165 gameserver
.clients
[index
].connection
= 0;
169 static void on_auth_status( CallbackMsg_t
*msg
){
170 SteamNetAuthenticationStatus_t
*info
= (void *)msg
->m_pubParam
;
171 vg_info( " Authentication availibility: %s\n",
172 string_ESteamNetworkingAvailability(info
->m_eAvail
) );
173 vg_info( " %s\n", info
->m_debugMsg
);
176 static int gameserver_client_index( HSteamNetConnection hconn
){
177 for( int i
=0; i
<vg_list_size(gameserver
.clients
); i
++ ){
178 struct gameserver_client
*client
= &gameserver
.clients
[i
];
180 if( client
->active
){
181 if( client
->connection
== hconn
){
189 static void on_connect_status( CallbackMsg_t
*msg
){
190 SteamNetConnectionStatusChangedCallback_t
*info
= (void *)msg
->m_pubParam
;
191 vg_info( " Connection status changed for %lu\n", info
->m_hConn
);
193 vg_info( " %s -> %s\n",
194 string_ESteamNetworkingConnectionState(info
->m_eOldState
),
195 string_ESteamNetworkingConnectionState(info
->m_info
.m_eState
) );
197 if( info
->m_info
.m_eState
==k_ESteamNetworkingConnectionState_Connecting
){
198 new_client_connecting( info
->m_hConn
);
201 if( (info
->m_info
.m_eState
==
202 k_ESteamNetworkingConnectionState_ClosedByPeer
) ||
203 (info
->m_info
.m_eState
==
204 k_ESteamNetworkingConnectionState_ProblemDetectedLocally
) ){
206 int client_id
= gameserver_client_index( info
->m_hConn
);
207 if( client_id
!= -1 ){
208 struct gameserver_client
*client
= &gameserver
.clients
[client_id
];
209 client
->connection
= 0;
211 gameserver_player_leave(client_id
);
214 vg_info( "End reason: %d\n", info
->m_info
.m_eEndReason
);
215 SteamAPI_ISteamNetworkingSockets_CloseConnection(
216 hSteamNetworkingSockets
, info
->m_hConn
, 0, NULL
, 0 );
220 static void gameserver_rx_auth( SteamNetworkingMessage_t
*msg
){
221 if( gameserver
.auth_mode
!= eServerModeAuthentication
){
222 vg_error( "Running server without authentication. "
223 "Connection %u tried to authenticate.\n", msg
->m_conn
);
227 if( get_connection_authsteamid( msg
) != k_connection_unauthorized
){
228 vg_warn( "Already authorized this user but app ticket was sent"
229 " again (%u)\n", msg
->m_conn
);
233 vg_low( "Attempting to verify user\n" );
235 if( msg
->m_cbSize
< sizeof(netmsg_auth
) ){
236 vg_error( "Malformed auth ticket, too small (%u)\n", msg
->m_conn
);
240 netmsg_auth
*auth
= msg
->m_pData
;
242 if( msg
->m_cbSize
< sizeof(netmsg_auth
)+auth
->ticket_length
||
243 auth
->ticket_length
> 1024 ){
244 vg_error( "Malformed auth ticket, ticket_length incorrect (%u)\n",
245 auth
->ticket_length
);
250 u32 ticket_len
= 1024;
252 int success
= SteamEncryptedAppTicket_BDecryptTicket(
253 auth
->ticket
, auth
->ticket_length
, decrypted
,
254 &ticket_len
, gameserver
.app_symmetric_key
,
255 k_nSteamEncryptedAppTicketSymmetricKeyLen
);
258 vg_error( "Failed to decrypt users ticket (client %u)\n", msg
->m_conn
);
259 vg_error( " ticket length: %u\n", auth
->ticket_length
);
261 SteamAPI_ISteamNetworkingSockets_CloseConnection(
262 hSteamNetworkingSockets
,
263 msg
->m_conn
, 0, NULL
, 1 );
267 if( SteamEncryptedAppTicket_GetTicketIssueTime( decrypted
, ticket_len
)){
268 RTime32 ctime
= time(NULL
),
269 tickettime
= SteamEncryptedAppTicket_GetTicketIssueTime(
270 decrypted
, ticket_len
),
271 expiretime
= tickettime
+ 24*3*60*60;
273 if( ctime
> expiretime
){
274 vg_error( "Ticket expired (client %u)\n", msg
->m_conn
);
276 /* TODO: Send expired information */
277 SteamAPI_ISteamNetworkingSockets_CloseConnection(
278 hSteamNetworkingSockets
,
279 msg
->m_conn
, 0, NULL
, 1 );
285 SteamEncryptedAppTicket_GetTicketSteamID( decrypted
, ticket_len
, &steamid
);
286 vg_success( "User is authenticated! steamid %lu (%u)\n",
287 steamid
.m_unAll64Bits
, msg
->m_conn
);
289 set_connection_authsteamid( msg
->m_conn
, steamid
.m_unAll64Bits
);
292 static int inet_require_auth( SteamNetworkingMessage_t
*msg
){
293 if( gameserver
.auth_mode
== eServerModeNoAuthentication
)
296 if( get_connection_authsteamid( msg
) == k_connection_unauthorized
){
297 vg_warn( "Unauthorized request! Disconnecting client: %u\n",
300 SteamAPI_ISteamNetworkingSockets_CloseConnection(
301 hSteamNetworkingSockets
,
302 msg
->m_conn
, 0, NULL
, 1 );
310 * Player updates sent to us
311 * -----------------------------------------------------------------------------
314 static int packet_minsize( SteamNetworkingMessage_t
*msg
, u32 size
){
315 if( msg
->m_cbSize
< size
) {
316 vg_error( "Invalid packet size (must be at least %u)\n", size
);
324 static void gameserver_rx_200_300( SteamNetworkingMessage_t
*msg
){
325 netmsg_blank
*tmp
= msg
->m_pData
;
327 int client_id
= gameserver_client_index( msg
->m_conn
);
328 if( client_id
== -1 ) return;
330 if( tmp
->inetmsg_id
== k_inetmsg_playerusername
){
331 if( !packet_minsize( msg
, sizeof(netmsg_playerusername
)+1 ))
334 struct gameserver_client
*client
= &gameserver
.clients
[ client_id
];
335 netmsg_playerusername
*src
= msg
->m_pData
;
337 u32 name_len
= network_msgstring( src
->name
, msg
->m_cbSize
,
338 sizeof(netmsg_playerusername
),
340 NETWORK_USERNAME_MAX
);
342 /* update other users about this change */
343 netmsg_playerusername
*prop
= alloca(sizeof(netmsg_playerusername
)+
344 NETWORK_USERNAME_MAX
);
346 prop
->inetmsg_id
= k_inetmsg_playerusername
;
347 prop
->index
= client_id
;
348 u32 chs
= vg_strncpy( client
->username
, prop
->name
, NETWORK_USERNAME_MAX
,
349 k_strncpy_always_add_null
);
351 vg_info( "client #%d changed name to: %s\n", client_id
, prop
->name
);
353 u32 propsize
= sizeof(netmsg_playerusername
) + chs
+ 1;
354 gameserver_send_to_all( client_id
, prop
, propsize
,
355 k_nSteamNetworkingSend_Reliable
);
357 else if( tmp
->inetmsg_id
== k_inetmsg_playerframe
){
359 netmsg_playerframe
*frame
= alloca(msg
->m_cbSize
);
360 memcpy( frame
, msg
->m_pData
, msg
->m_cbSize
);
361 frame
->client
= client_id
;
362 gameserver_send_to_all( client_id
, frame
, msg
->m_cbSize
,
363 k_nSteamNetworkingSend_Unreliable
);
365 else if( tmp
->inetmsg_id
== k_inetmsg_playeritem
){
366 netmsg_playeritem
*item
= msg
->m_pData
;
369 struct gameserver_client
*client
= &gameserver
.clients
[ client_id
];
371 if( item
->type_index
>= k_netmsg_playeritem_max
){
372 vg_warn( "Client #%d invalid equip type %u\n",
373 client_id
, (u32
)item
->type_index
);
377 char *dest
= client
->items
[ item
->type_index
];
379 network_msgstring( item
->uid
, msg
->m_cbSize
, sizeof(netmsg_playeritem
),
380 dest
, ADDON_UID_MAX
);
382 vg_info( "Client #%d equiped: [%s] %s\n",
384 (const char *[]){[k_netmsg_playeritem_board
]="board",
385 [k_netmsg_playeritem_player
]="player",
386 [k_netmsg_playeritem_world0
]="world0",
387 [k_netmsg_playeritem_world1
]="world1"
388 }[item
->type_index
], item
->uid
);
391 netmsg_playeritem
*prop
= alloca(msg
->m_cbSize
);
392 memcpy( prop
, msg
->m_pData
, msg
->m_cbSize
);
393 prop
->client
= client_id
;
394 gameserver_send_to_all( client_id
, prop
, msg
->m_cbSize
,
395 k_nSteamNetworkingSend_Reliable
);
398 vg_warn( "Unknown inetmsg_id recieved from client. (%u)\n",
403 static void gameserver_request_respond( enum request_status status
,
404 netmsg_request
*res
, vg_msg
*body
,
405 SteamNetworkingMessage_t
*msg
){
406 int client_id
= gameserver_client_index( msg
->m_conn
);
408 if( status
== k_request_status_ok
){
411 vg_success( "[%d#%d] Response: %d\n", client_id
, (i32
)res
->id
, status
);
412 vg_msg_print( body
);
415 vg_warn( "[%d#%d] Response: %d\n", client_id
, (i32
)res
->id
, status
);
418 res
->status
= status
;
420 SteamAPI_ISteamNetworkingSockets_SendMessageToConnection(
421 hSteamNetworkingSockets
, msg
->m_conn
,
422 res
, sizeof(netmsg_request
) + len
,
423 k_nSteamNetworkingSend_Reliable
, NULL
);
424 SteamAPI_SteamNetworkingMessage_t_Release( msg
);
427 struct user_request_thread_data
{
428 SteamNetworkingMessage_t
*msg
;
431 static void gameserver_process_user_request( db_request
*db_req
){
432 struct user_request_thread_data
*inf
= (void *)db_req
->data
;
433 SteamNetworkingMessage_t
*msg
= inf
->msg
;
435 int client_id
= gameserver_client_index( msg
->m_conn
);
436 if( client_id
== -1 ){
437 SteamAPI_SteamNetworkingMessage_t_Release( msg
);
441 netmsg_request
*req
= (netmsg_request
*)msg
->m_pData
;
444 data
.len
= msg
->m_cbSize
- sizeof(netmsg_request
);
447 /* create response packet */
448 netmsg_request
*res
= alloca( sizeof(netmsg_request
) + 512 );
449 res
->inetmsg_id
= k_inetmsg_response
;
455 const char *endpoint
= vg_msg_seekkvstr( &data
, "endpoint", 0 );
458 gameserver_request_respond( k_request_status_invalid_endpoint
,
463 if( !strcmp( endpoint
, "scoreboard" ) ){
464 const char *mod
= vg_msg_seekkvstr( &data
, "mod", 0 );
465 const char *route
= vg_msg_seekkvstr( &data
, "route", 0 );
466 u32 week
= vg_msg_seekkvu32( &data
, "week", 0 );
468 char table_name
[ DB_TABLE_UID_MAX
];
469 if( !db_get_highscore_table_name( mod
, route
, week
, table_name
) ){
470 gameserver_request_respond( k_request_status_out_of_memory
,
477 vg_strnull( &q
, buf
, 512 );
478 vg_strcat( &q
, "SELECT * FROM \"" );
479 vg_strcat( &q
, table_name
);
480 vg_strcat( &q
, "\" ORDER BY time DESC LIMIT 10;" );
481 if( !vg_strgood(&q
) ) {
482 gameserver_request_respond( k_request_status_out_of_memory
,
487 sqlite3_stmt
*stmt
= db_stmt( q
.buffer
);
490 gameserver_request_respond( k_request_status_database_error
,
495 vg_msg_frame( &body
, "rows" );
496 for( u32 i
=0; i
<10; i
++ ){
497 int fc
= sqlite3_step( stmt
);
499 if( fc
== SQLITE_ROW
){
500 i32 time
= sqlite3_column_int( stmt
, 1 );
501 i64 steamid_i64
= sqlite3_column_int64( stmt
, 0 );
502 u64 steamid
= *((u64
*)&steamid_i64
);
504 vg_msg_frame( &body
, "" );
505 vg_msg_wkvu32( &body
, "time", time
);
506 vg_msg_wkvu64( &body
, "steamid", steamid
);
509 if( db_getuserinfo( steamid
, username
, sizeof(username
), NULL
) ){
510 vg_msg_wkvstr( &body
, "username", username
);
513 vg_msg_end_frame( &body
);
515 else if( fc
== SQLITE_DONE
){
524 sqlite3_finalize( stmt
);
525 vg_msg_end_frame( &body
);
527 if( body
.error
!= k_vg_msg_error_OK
){
528 gameserver_request_respond( k_request_status_out_of_memory
,
533 gameserver_request_respond( k_request_status_ok
, res
, &body
, msg
);
536 gameserver_request_respond( k_request_status_invalid_endpoint
,
541 static void gameserver_rx_300_400( SteamNetworkingMessage_t
*msg
){
542 netmsg_blank
*tmp
= msg
->m_pData
;
544 int client_id
= gameserver_client_index( msg
->m_conn
);
545 if( client_id
== -1 ){
546 SteamAPI_SteamNetworkingMessage_t_Release( msg
);
550 if( tmp
->inetmsg_id
== k_inetmsg_request
){
551 if( !packet_minsize( msg
, sizeof(netmsg_request
)+1 ))
554 db_request
*call
= db_alloc_request(
555 sizeof(struct user_request_thread_data
) );
556 struct user_request_thread_data
*inf
= (void *)call
->data
;
558 call
->handler
= gameserver_process_user_request
;
559 db_send_request( call
);
562 vg_warn( "Unknown inetmsg_id recieved from client. (%u)\n",
564 SteamAPI_SteamNetworkingMessage_t_Release( msg
);
568 static void poll_connections(void){
569 SteamNetworkingMessage_t
*messages
[32];
573 len
= SteamAPI_ISteamNetworkingSockets_ReceiveMessagesOnPollGroup(
574 hSteamNetworkingSockets
,
575 gameserver
.client_group
, messages
, vg_list_size(messages
) );
580 for( int i
=0; i
<len
; i
++ ){
581 SteamNetworkingMessage_t
*msg
= messages
[i
];
583 if( msg
->m_cbSize
< sizeof(netmsg_blank
) ){
584 vg_warn( "Discarding message (too small: %d)\n",
589 netmsg_blank
*tmp
= msg
->m_pData
;
591 if( (tmp
->inetmsg_id
>= 200) && (tmp
->inetmsg_id
< 300) ){
592 gameserver_rx_200_300( msg
);
593 SteamAPI_SteamNetworkingMessage_t_Release( msg
);
595 else if( (tmp
->inetmsg_id
>= 300) && (tmp
->inetmsg_id
< 400) ){
596 gameserver_rx_300_400( msg
);
599 if( tmp
->inetmsg_id
== k_inetmsg_auth
)
600 gameserver_rx_auth( msg
);
602 vg_warn( "Unknown inetmsg_id recieved from client. (%u)\n",
605 SteamAPI_SteamNetworkingMessage_t_Release( msg
);
611 static u64
seconds_to_server_ticks( double s
){
615 static void test_runner( db_request
*req
){
616 vg_warn( "RUNNER\n" );
617 char table
[DB_TABLE_UID_MAX
];
618 if( db_get_highscore_table_name( "sr002-local-mp_mtzero",
619 "Coastal Run", 0, table
) ){
620 if( db_writeusertime( table
, 76561198072130043, 232, 1 ) ){
621 vg_success( "Written time\n" );
622 i32 v
= db_readusertime( table
, 76561198072130043 );
623 vg_success( "Returned time: %u\n", v
);
628 int main( int argc
, char *argv
[] ){
629 signal( SIGINT
, inthandler
);
630 signal( SIGQUIT
, inthandler
);
631 signal( SIGPIPE
, SIG_IGN
);
634 while( vg_argp( argc
, argv
) ){
635 if( vg_long_opt( "noauth" ) )
636 gameserver
.auth_mode
= eServerModeNoAuthentication
;
638 /* TODO: Options to override, ammend, remove etc */
641 vg_set_mem_quota( 80*1024*1024 );
645 db_request
*req
= db_alloc_request(0);
647 req
->handler
= test_runner
;
648 db_send_request(req
);
651 monitor_start_server(); /* UNIX socket monitor */
654 * --------------------------------------------------------------- */
655 steamworks_ensure_txt( "2103940" );
656 if( gameserver
.auth_mode
== eServerModeAuthentication
){
657 if( !vg_load_steam_symetric_key( "application_key",
658 gameserver
.app_symmetric_key
)){
663 vg_warn( "Running without user authentication.\n" );
666 if( !SteamGameServer_Init( 0, 27400, 27401,
667 gameserver
.auth_mode
, "1.0.0.0" ) ){
668 vg_error( "SteamGameServer_Init failed\n" );
672 void *hSteamGameServer
= SteamAPI_SteamGameServer();
673 SteamAPI_ISteamGameServer_LogOnAnonymous( hSteamGameServer
);
675 SteamAPI_ManualDispatch_Init();
676 HSteamPipe hsteampipe
= SteamGameServer_GetHSteamPipe();
677 hSteamNetworkingSockets
=
678 SteamAPI_SteamGameServerNetworkingSockets_SteamAPI();
680 steam_register_callback( k_iSteamNetAuthenticationStatus
, on_auth_status
);
681 steam_register_callback( k_iSteamNetConnectionStatusChangedCallBack
,
684 vg_success( "Steamworks API running\n" );
685 steamworks_event_loop( hsteampipe
);
690 HSteamListenSocket listener
;
691 SteamNetworkingIPAddr localAddr
;
692 SteamAPI_SteamNetworkingIPAddr_Clear( &localAddr
);
693 localAddr
.m_port
= 27402;
695 listener
= SteamAPI_ISteamNetworkingSockets_CreateListenSocketIP(
696 hSteamNetworkingSockets
, &localAddr
, 0, NULL
);
697 gameserver
.client_group
= SteamAPI_ISteamNetworkingSockets_CreatePollGroup(
698 hSteamNetworkingSockets
);
700 u64 server_ticks
= 8000,
701 last_record_save
= 8000,
702 last_scoreboard_gen
= 0,
703 last_monitor_heartbeat
= 0;
706 monitor_event_loop();
707 steamworks_event_loop( hsteampipe
);
714 (last_monitor_heartbeat
+ seconds_to_server_ticks(10.0))){
715 last_monitor_heartbeat
= server_ticks
;
723 SteamAPI_ISteamNetworkingSockets_DestroyPollGroup( hSteamNetworkingSockets
,
724 gameserver
.client_group
);
725 SteamAPI_ISteamNetworkingSockets_CloseListenSocket(
726 hSteamNetworkingSockets
, listener
);
728 vg_info( "Shutting down\n..." );
729 SteamGameServer_Shutdown();