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