ff8d63552384903277721e25b43318b9c76fd847
[carveJwlIkooP6JGAAIwe30JlM.git] / gameserver.c
1 /*
2 * Copyright (C) 2021-2023 Mt.ZERO Software, Harry Godden - All Rights Reserved
3 */
4
5 #define _DEFAULT_SOURCE
6 #include <signal.h>
7 #include <unistd.h>
8 #include <time.h>
9
10 volatile sig_atomic_t sig_stop;
11
12 static void inthandler( int signum ) {
13 sig_stop = 1;
14 }
15
16 #include "gameserver.h"
17 #include "highscores.c"
18 #include "servermonitor_server.c"
19 #include "vg/vg_opt.h"
20
21 static const u64 k_connection_unauthorized = 0xffffffffffffffff;
22
23 static u64_steamid get_connection_authsteamid( SteamNetworkingMessage_t *msg ){
24 i64 userdata = SteamAPI_ISteamNetworkingSockets_GetConnectionUserData(
25 hSteamNetworkingSockets, msg->m_conn );
26
27 return *((u64_steamid *)&userdata);
28 }
29
30 static void set_connection_authsteamid(HSteamNetConnection con, u64_steamid id){
31 i64 userdata = *((i64 *)&id);
32
33 SteamAPI_ISteamNetworkingSockets_SetConnectionUserData(
34 hSteamNetworkingSockets, con, userdata );
35 }
36
37 static void gameserver_send_to_all( int ignore,
38 const void *pData, u32 cbData,
39 int nSendFlags ){
40 for( int i=0; i<vg_list_size(gameserver.clients); i++ ){
41 struct gameserver_client *client = &gameserver.clients[i];
42
43 if( (i==ignore) || !client->active )
44 continue;
45
46 SteamAPI_ISteamNetworkingSockets_SendMessageToConnection(
47 hSteamNetworkingSockets, client->connection,
48 pData, cbData, nSendFlags, NULL );
49 }
50 }
51
52 static void gameserver_populate_join_msg( int index, netmsg_playerjoin *msg ){
53 memset( msg, 0, sizeof(*msg) );
54 msg->inetmsg_id = k_inetmsg_playerjoin;
55 msg->index = index;
56 vg_strncpy( gameserver.clients[index].username, msg->username,
57 sizeof(msg->username), k_strncpy_always_add_null );
58 }
59
60 static void gameserver_player_join( int index ){
61 struct gameserver_client *joiner = &gameserver.clients[index];
62
63 netmsg_playerjoin join;
64 gameserver_populate_join_msg( index, &join );
65 gameserver_send_to_all( index, &join, sizeof(join),
66 k_nSteamNetworkingSend_Reliable );
67
68 /* update the joining user about current connections */
69 for( int i=0; i<vg_list_size(gameserver.clients); i++ ){
70 struct gameserver_client *client = &gameserver.clients[i];
71
72 if( (i==index) || !client->active )
73 continue;
74
75 netmsg_playerjoin init;
76 gameserver_populate_join_msg( i, &init );
77
78 SteamAPI_ISteamNetworkingSockets_SendMessageToConnection(
79 hSteamNetworkingSockets, joiner->connection,
80 &init, sizeof(init), k_nSteamNetworkingSend_Reliable, NULL );
81 }
82 }
83
84 static void gameserver_player_leave( int index ){
85 netmsg_playerjoin leave;
86 leave.inetmsg_id = k_inetmsg_playerleave;
87 leave.index = index;
88
89 vg_info( "Player leave (%d)\n", index );
90 gameserver_send_to_all( index, &leave, sizeof(leave),
91 k_nSteamNetworkingSend_Reliable );
92 }
93
94 static void new_client_connecting( HSteamNetConnection client ){
95 int index = -1;
96
97 /* TODO: LRU */
98 for( int i=0; i<vg_list_size(gameserver.clients); i++ ){
99 if( !gameserver.clients[i].active ){
100 index = i;
101 break;
102 }
103 }
104
105 if( index == -1 ){
106 vg_error( "Server full\n" );
107 SteamAPI_ISteamNetworkingSockets_CloseConnection(
108 hSteamNetworkingSockets, client,
109 4500,
110 NULL, 1 );
111 return;
112 }
113
114 EResult accept_status = SteamAPI_ISteamNetworkingSockets_AcceptConnection(
115 hSteamNetworkingSockets, client );
116 if( accept_status == k_EResultOK ){
117 vg_success( "Accepted client (id: %u, index: %d)\n", client, index );
118
119 gameserver.clients[index].active = 1;
120 gameserver.clients[index].connection = client;
121
122 SteamAPI_ISteamNetworkingSockets_SetConnectionPollGroup(
123 hSteamNetworkingSockets,
124 client, gameserver.client_group );
125
126 /* Just to be sure */
127 set_connection_authsteamid( client, -1 );
128 gameserver_player_join( index );
129 }
130 else{
131 vg_warn( "Error accepting client (id: %u)\n", client );
132 SteamAPI_ISteamNetworkingSockets_CloseConnection(
133 hSteamNetworkingSockets, client,
134 k_ESteamNetConnectionEnd_Misc_InternalError,
135 NULL, 1 );
136 gameserver.clients[index].active = 0;
137 gameserver.clients[index].connection = 0;
138 }
139 }
140
141 static void on_auth_status( CallbackMsg_t *msg ){
142 SteamNetAuthenticationStatus_t *info = (void *)msg->m_pubParam;
143 vg_info( " Authentication availibility: %s\n",
144 string_ESteamNetworkingAvailability(info->m_eAvail) );
145 vg_info( " %s\n", info->m_debugMsg );
146 }
147
148 static int gameserver_client_index( HSteamNetConnection hconn ){
149 for( int i=0; i<vg_list_size(gameserver.clients); i++ ){
150 struct gameserver_client *client = &gameserver.clients[i];
151
152 if( client->active ){
153 if( client->connection == hconn ){
154 return i;
155 }
156 }
157 }
158 return -1;
159 }
160
161 static void on_connect_status( CallbackMsg_t *msg ){
162 SteamNetConnectionStatusChangedCallback_t *info = (void *)msg->m_pubParam;
163 vg_info( " Connection status changed for %lu\n", info->m_hConn );
164
165 vg_info( " %s -> %s\n",
166 string_ESteamNetworkingConnectionState(info->m_eOldState),
167 string_ESteamNetworkingConnectionState(info->m_info.m_eState) );
168
169 if( info->m_info.m_eState==k_ESteamNetworkingConnectionState_Connecting ){
170 new_client_connecting( info->m_hConn );
171 }
172
173 if( (info->m_info.m_eState ==
174 k_ESteamNetworkingConnectionState_ClosedByPeer ) ||
175 (info->m_info.m_eState ==
176 k_ESteamNetworkingConnectionState_ProblemDetectedLocally ) ){
177
178 int client_id = gameserver_client_index( info->m_hConn );
179 if( client_id != -1 ){
180 struct gameserver_client *client = &gameserver.clients[client_id];
181 client->connection = 0;
182 client->active = 0;
183 gameserver_player_leave(client_id);
184 }
185
186 vg_info( "End reason: %d\n", info->m_info.m_eEndReason );
187 SteamAPI_ISteamNetworkingSockets_CloseConnection(
188 hSteamNetworkingSockets, info->m_hConn, 0, NULL, 0 );
189 }
190 }
191
192 static void gameserver_rx_auth( SteamNetworkingMessage_t *msg ){
193 if( gameserver.auth_mode != eServerModeAuthentication ){
194 vg_error( "Running server without authentication. "
195 "Connection %u tried to authenticate.\n", msg->m_conn );
196 return;
197 }
198
199 if( get_connection_authsteamid( msg ) != k_connection_unauthorized ){
200 vg_warn( "Already authorized this user but app ticket was sent"
201 " again (%u)\n", msg->m_conn );
202 return;
203 }
204
205 vg_low( "Attempting to verify user\n" );
206
207 if( msg->m_cbSize < sizeof(netmsg_auth) ){
208 vg_error( "Malformed auth ticket, too small (%u)\n", msg->m_conn );
209 return;
210 }
211
212 netmsg_auth *auth = msg->m_pData;
213
214 if( msg->m_cbSize < sizeof(netmsg_auth)+auth->ticket_length ||
215 auth->ticket_length > 1024 ){
216 vg_error( "Malformed auth ticket, ticket_length incorrect (%u)\n",
217 auth->ticket_length );
218 return;
219 }
220
221 u8 decrypted[1024];
222 u32 ticket_len = 1024;
223
224 int success = SteamEncryptedAppTicket_BDecryptTicket(
225 auth->ticket, auth->ticket_length, decrypted,
226 &ticket_len, gameserver.app_symmetric_key,
227 k_nSteamEncryptedAppTicketSymmetricKeyLen );
228
229 if( !success ){
230 vg_error( "Failed to decrypt users ticket (client %u)\n", msg->m_conn );
231 vg_error( " ticket length: %u\n", auth->ticket_length );
232
233 SteamAPI_ISteamNetworkingSockets_CloseConnection(
234 hSteamNetworkingSockets,
235 msg->m_conn, 0, NULL, 1 );
236 return;
237 }
238
239 if( SteamEncryptedAppTicket_GetTicketIssueTime( decrypted, ticket_len )){
240 RTime32 ctime = time(NULL),
241 tickettime = SteamEncryptedAppTicket_GetTicketIssueTime(
242 decrypted, ticket_len ),
243 expiretime = tickettime + 24*3*60*60;
244
245 if( ctime > expiretime ){
246 vg_error( "Ticket expired (client %u)\n", msg->m_conn );
247
248 /* TODO: Send expired information */
249 SteamAPI_ISteamNetworkingSockets_CloseConnection(
250 hSteamNetworkingSockets,
251 msg->m_conn, 0, NULL, 1 );
252 return;
253 }
254 }
255
256 CSteamID steamid;
257 SteamEncryptedAppTicket_GetTicketSteamID( decrypted, ticket_len, &steamid );
258 vg_success( "User is authenticated! steamid %lu (%u)\n",
259 steamid.m_unAll64Bits, msg->m_conn );
260
261 set_connection_authsteamid( msg->m_conn, steamid.m_unAll64Bits );
262 }
263
264 static int inet_require_auth( SteamNetworkingMessage_t *msg ){
265 if( gameserver.auth_mode == eServerModeNoAuthentication )
266 return 1;
267
268 if( get_connection_authsteamid( msg ) == k_connection_unauthorized ){
269 vg_warn( "Unauthorized request! Disconnecting client: %u\n",
270 msg->m_conn );
271
272 SteamAPI_ISteamNetworkingSockets_CloseConnection(
273 hSteamNetworkingSockets,
274 msg->m_conn, 0, NULL, 1 );
275
276 return 0;
277 }
278 else return 1;
279 }
280
281 /*
282 * Player updates sent to us
283 * -----------------------------------------------------------------------------
284 */
285
286 static int packet_minsize( SteamNetworkingMessage_t *msg, u32 size ){
287 if( msg->m_cbSize < size ) {
288 vg_error( "Invalid packet size (must be at least %u)\n", size );
289 return 0;
290 }
291 else{
292 return 1;
293 }
294 }
295
296 static void gameserver_rx_200_300( SteamNetworkingMessage_t *msg ){
297 netmsg_blank *tmp = msg->m_pData;
298
299 int client_id = gameserver_client_index( msg->m_conn );
300 if( client_id == -1 ) return;
301
302 if( tmp->inetmsg_id == k_inetmsg_playerusername ){
303 if( !packet_minsize( msg, sizeof(netmsg_playerusername) ))
304 return;
305
306 struct gameserver_client *client = &gameserver.clients[ client_id ];
307 netmsg_playerusername *src = msg->m_pData;
308
309 vg_info( "%d change name '%s' -> '%s'\n",
310 client_id, client->username, src->username );
311
312 vg_strncpy( src->username, client->username, sizeof(client->username),
313 k_strncpy_always_add_null );
314
315 /* update other users about this change */
316 netmsg_playerusername msg;
317 memset( &msg, 0, sizeof(msg) );
318 msg.inetmsg_id = k_inetmsg_playerusername;
319 msg.index = client_id;
320 vg_strncpy( client->username, msg.username, sizeof(msg.username),
321 k_strncpy_always_add_null );
322
323 gameserver_send_to_all( client_id, &msg, sizeof(msg),
324 k_nSteamNetworkingSend_Reliable );
325 }
326 else if( tmp->inetmsg_id == k_inetmsg_playerframe ){
327 /* propogate */
328
329 netmsg_playerframe *frame = alloca(msg->m_cbSize);
330 memcpy( frame, msg->m_pData, msg->m_cbSize );
331 frame->client = client_id;
332 gameserver_send_to_all( client_id, frame, msg->m_cbSize,
333 k_nSteamNetworkingSend_Unreliable );
334 }
335 }
336
337 #if 0
338 static void on_inet_score_request( SteamNetworkingMessage_t *msg ){
339 if( !inet_require_auth(msg) ) return;
340
341 SteamAPI_ISteamNetworkingSockets_SendMessageToConnection(
342 hSteamNetworkingSockets, msg->m_conn,
343 &scoreboard_client_data, sizeof(netmsg_scoreboard),
344 k_nSteamNetworkingSend_Reliable, NULL );
345 }
346
347 static void on_inet_set_nickname( SteamNetworkingMessage_t *msg ){
348 if(!inet_require_auth(msg)) return;
349
350 u64_steamid steamid = get_connection_authsteamid(msg);
351 netmsg_set_nickname *setnick = msg->m_pData;
352 if( msg->m_cbSize < sizeof(netmsg_set_nickname) ){
353 vg_warn( "Invalid nickname request from client: %u, steamid: %lu\n",
354 msg->m_conn, steamid );
355 return;
356 }
357
358 highscore_set_user_nickname( steamid, setnick->nickname );
359 }
360
361 static void on_inet_set_score( SteamNetworkingMessage_t *msg ){
362 if(!inet_require_auth(msg)) return;
363
364 u64_steamid steamid = get_connection_authsteamid(msg);
365
366 if( msg->m_cbSize < sizeof(netmsg_set_score) ){
367 vg_warn( "Invalid set score post from client: %u, steamid: %lu\n",
368 msg->m_conn, steamid );
369 return;
370 }
371
372 netmsg_set_score *info = msg->m_pData;
373
374 if( msg->m_cbSize < sizeof(netmsg_set_score) +
375 sizeof(struct netmsg_score_record)*info->record_count ){
376 vg_warn( "Malformed set score post from client: %u, steamid: %lu\n",
377 msg->m_conn, steamid );
378 return;
379 }
380
381 for( int i=0; i<info->record_count; i++ ){
382 highscore_record temp;
383 temp.trackid = info->records[i].trackid;
384 temp.datetime = time(NULL);
385 temp.playerid = steamid;
386 temp.points = info->records[i].points;
387 temp.time = info->records[i].time;
388
389 highscores_push_record( &temp );
390 }
391 }
392 #endif
393
394 static void poll_connections(void){
395 SteamNetworkingMessage_t *messages[32];
396 int len;
397
398 while(1){
399 len = SteamAPI_ISteamNetworkingSockets_ReceiveMessagesOnPollGroup(
400 hSteamNetworkingSockets,
401 gameserver.client_group, messages, vg_list_size(messages) );
402
403 if( len <= 0 )
404 return;
405
406 for( int i=0; i<len; i++ ){
407 SteamNetworkingMessage_t *msg = messages[i];
408
409 if( msg->m_cbSize < sizeof(netmsg_blank) ){
410 vg_warn( "Discarding message (too small: %d)\n",
411 msg->m_cbSize );
412 continue;
413 }
414
415 netmsg_blank *tmp = msg->m_pData;
416
417 if( (tmp->inetmsg_id >= 200) && (tmp->inetmsg_id < 300) ){
418 gameserver_rx_200_300( msg );
419 }
420 else{
421 if( tmp->inetmsg_id == k_inetmsg_auth )
422 gameserver_rx_auth( msg );
423 #if 0
424 else if( tmp->inetmsg_id == k_inetmsg_scores_request )
425 on_inet_score_request( msg );
426 else if( tmp->inetmsg_id == k_inetmsg_set_nickname )
427 on_inet_set_nickname( msg );
428 else if( tmp->inetmsg_id == k_inetmsg_set_score )
429 on_inet_set_score( msg );
430 else if( tmp->inetmsg_id == k_inetmsg_playerframe )
431 on_inet_playerframe( msg );
432 #endif
433 else {
434 vg_warn( "Unknown inetmsg_id recieved from client. (%u)\n",
435 tmp->inetmsg_id );
436 }
437 }
438
439
440 SteamAPI_SteamNetworkingMessage_t_Release( msg );
441 }
442 }
443 }
444
445 static u64 seconds_to_server_ticks( double s ){
446 return s / 0.01;
447 }
448
449 static void generate_boards(void){
450 FILE *fp = fopen( "www/html/srhighscores.txt", "w" );
451
452 if( !fp ){
453 vg_error( "Can't write boards to www/html/srhighscores.txt\n" );
454 return;
455 }
456
457 for( int i=0; i<vg_list_size(track_infos); i++ ){
458 struct netmsg_board *board = &scoreboard_client_data.boards[i];
459
460 highscores_board_generate( board->data, i, 10 );
461 highscores_board_printf( fp, board->data, 10 );
462 }
463
464 fclose( fp );
465 }
466
467 int main( int argc, char *argv[] ){
468 signal( SIGINT, inthandler );
469 signal( SIGQUIT, inthandler );
470 signal( SIGPIPE, SIG_IGN );
471
472 char *arg;
473 while( vg_argp( argc, argv ) ){
474 if( vg_long_opt( "noauth" ) )
475 gameserver.auth_mode = eServerModeNoAuthentication;
476 }
477
478 /* TODO: Options to override, ammend, remove etc */
479
480 vg_set_mem_quota( 80*1024*1024 );
481 vg_alloc_quota();
482
483 highscores_init( 250000, 10000 );
484
485 if( !highscores_read() )
486 highscores_create_db();
487
488 steamworks_ensure_txt( "2103940" );
489
490 if( gameserver.auth_mode == eServerModeAuthentication ){
491 if( !vg_load_steam_symetric_key( "application_key",
492 gameserver.app_symmetric_key )){
493 return 0;
494 }
495 }
496 else{
497 vg_warn( "Running without user authentication.\n" );
498 }
499
500 if( !SteamGameServer_Init( 0, 27400, 27401,
501 gameserver.auth_mode, "1.0.0.0" ) ){
502 vg_error( "SteamGameServer_Init failed\n" );
503 return 0;
504 }
505
506 void *hSteamGameServer = SteamAPI_SteamGameServer();
507 SteamAPI_ISteamGameServer_LogOnAnonymous( hSteamGameServer );
508
509 SteamAPI_ManualDispatch_Init();
510 HSteamPipe hsteampipe = SteamGameServer_GetHSteamPipe();
511
512 //hSteamHTTP = SteamAPI_SteamGameServerHTTP();
513 hSteamNetworkingSockets =
514 SteamAPI_SteamGameServerNetworkingSockets_SteamAPI();
515
516 /*
517 * Server code
518 */
519
520 steam_register_callback( k_iSteamNetAuthenticationStatus, on_auth_status );
521 steam_register_callback( k_iSteamNetConnectionStatusChangedCallBack,
522 on_connect_status );
523
524 vg_success( "Steamworks API running\n" );
525 steamworks_event_loop( hsteampipe );
526
527 /*
528 * Create a listener
529 */
530
531 HSteamListenSocket listener;
532 SteamNetworkingIPAddr localAddr;
533 SteamAPI_SteamNetworkingIPAddr_Clear( &localAddr );
534 localAddr.m_port = 27402;
535
536 listener = SteamAPI_ISteamNetworkingSockets_CreateListenSocketIP(
537 hSteamNetworkingSockets, &localAddr, 0, NULL );
538 gameserver.client_group = SteamAPI_ISteamNetworkingSockets_CreatePollGroup(
539 hSteamNetworkingSockets );
540
541 u64 server_ticks = 8000,
542 last_record_save = 8000,
543 last_scoreboard_gen = 0,
544 last_monitor_heartbeat = 0;
545
546 generate_boards();
547 monitor_start_server();
548
549 while( !sig_stop ){
550 monitor_event_loop();
551 steamworks_event_loop( hsteampipe );
552 poll_connections();
553
554 usleep(10000);
555 server_ticks ++;
556
557 if( server_ticks >
558 (last_monitor_heartbeat + seconds_to_server_ticks(10.0))){
559 last_monitor_heartbeat = server_ticks;
560 monitor_heartbeat();
561 }
562
563 if( server_ticks > last_scoreboard_gen + seconds_to_server_ticks(60.0) ){
564 last_scoreboard_gen = server_ticks;
565 generate_boards();
566 }
567
568 if( server_ticks > last_record_save + seconds_to_server_ticks(10.0*60.0)){
569 last_record_save = server_ticks;
570 highscores_serialize_all();
571 }
572 }
573
574 highscores_serialize_all();
575
576 SteamAPI_ISteamNetworkingSockets_DestroyPollGroup( hSteamNetworkingSockets,
577 gameserver.client_group );
578 SteamAPI_ISteamNetworkingSockets_CloseListenSocket(
579 hSteamNetworkingSockets, listener );
580
581 vg_info( "Shutting down\n..." );
582 SteamGameServer_Shutdown();
583
584 return 0;
585 }