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