board pose
[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, NETWORK_USERNAME_MAX,
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 = msg->m_pData;
371
372 /* record */
373 struct gameserver_client *client = &gameserver.clients[ client_id ];
374
375 if( item->type == k_addon_type_board ){
376 network_msgstring( item->uid, msg->m_cbSize, sizeof(netmsg_playeritem),
377 client->item_board, ADDON_UID_MAX );
378 }
379 else if( item->type == k_addon_type_player ){
380 network_msgstring( item->uid, msg->m_cbSize, sizeof(netmsg_playeritem),
381 client->item_player, ADDON_UID_MAX );
382 }
383 else {
384 vg_warn( "Client #%d invalid equip type %u\n",
385 client_id, (u32)item->type );
386 return;
387 }
388 vg_info( "Client #%d equiped: [%u] %s\n",
389 item->client, item->type, item->uid );
390
391 /* propogate */
392 netmsg_playeritem *prop = alloca(msg->m_cbSize);
393 memcpy( prop, msg->m_pData, msg->m_cbSize );
394 prop->client = client_id;
395 gameserver_send_to_all( client_id, prop, msg->m_cbSize,
396 k_nSteamNetworkingSend_Reliable );
397 }
398 }
399
400 #if 0
401 static void on_inet_score_request( SteamNetworkingMessage_t *msg ){
402 if( !inet_require_auth(msg) ) return;
403
404 SteamAPI_ISteamNetworkingSockets_SendMessageToConnection(
405 hSteamNetworkingSockets, msg->m_conn,
406 &scoreboard_client_data, sizeof(netmsg_scoreboard),
407 k_nSteamNetworkingSend_Reliable, NULL );
408 }
409
410 static void on_inet_set_nickname( SteamNetworkingMessage_t *msg ){
411 if(!inet_require_auth(msg)) return;
412
413 u64_steamid steamid = get_connection_authsteamid(msg);
414 netmsg_set_nickname *setnick = msg->m_pData;
415 if( msg->m_cbSize < sizeof(netmsg_set_nickname) ){
416 vg_warn( "Invalid nickname request from client: %u, steamid: %lu\n",
417 msg->m_conn, steamid );
418 return;
419 }
420
421 highscore_set_user_nickname( steamid, setnick->nickname );
422 }
423
424 static void on_inet_set_score( SteamNetworkingMessage_t *msg ){
425 if(!inet_require_auth(msg)) return;
426
427 u64_steamid steamid = get_connection_authsteamid(msg);
428
429 if( msg->m_cbSize < sizeof(netmsg_set_score) ){
430 vg_warn( "Invalid set score post from client: %u, steamid: %lu\n",
431 msg->m_conn, steamid );
432 return;
433 }
434
435 netmsg_set_score *info = msg->m_pData;
436
437 if( msg->m_cbSize < sizeof(netmsg_set_score) +
438 sizeof(struct netmsg_score_record)*info->record_count ){
439 vg_warn( "Malformed set score post from client: %u, steamid: %lu\n",
440 msg->m_conn, steamid );
441 return;
442 }
443
444 for( int i=0; i<info->record_count; i++ ){
445 highscore_record temp;
446 temp.trackid = info->records[i].trackid;
447 temp.datetime = time(NULL);
448 temp.playerid = steamid;
449 temp.points = info->records[i].points;
450 temp.time = info->records[i].time;
451
452 highscores_push_record( &temp );
453 }
454 }
455 #endif
456
457 static void poll_connections(void){
458 SteamNetworkingMessage_t *messages[32];
459 int len;
460
461 while(1){
462 len = SteamAPI_ISteamNetworkingSockets_ReceiveMessagesOnPollGroup(
463 hSteamNetworkingSockets,
464 gameserver.client_group, messages, vg_list_size(messages) );
465
466 if( len <= 0 )
467 return;
468
469 for( int i=0; i<len; i++ ){
470 SteamNetworkingMessage_t *msg = messages[i];
471
472 if( msg->m_cbSize < sizeof(netmsg_blank) ){
473 vg_warn( "Discarding message (too small: %d)\n",
474 msg->m_cbSize );
475 continue;
476 }
477
478 netmsg_blank *tmp = msg->m_pData;
479
480 if( (tmp->inetmsg_id >= 200) && (tmp->inetmsg_id < 300) ){
481 gameserver_rx_200_300( msg );
482 }
483 else{
484 if( tmp->inetmsg_id == k_inetmsg_auth )
485 gameserver_rx_auth( msg );
486 #if 0
487 else if( tmp->inetmsg_id == k_inetmsg_scores_request )
488 on_inet_score_request( msg );
489 else if( tmp->inetmsg_id == k_inetmsg_set_nickname )
490 on_inet_set_nickname( msg );
491 else if( tmp->inetmsg_id == k_inetmsg_set_score )
492 on_inet_set_score( msg );
493 else if( tmp->inetmsg_id == k_inetmsg_playerframe )
494 on_inet_playerframe( msg );
495 #endif
496 else {
497 vg_warn( "Unknown inetmsg_id recieved from client. (%u)\n",
498 tmp->inetmsg_id );
499 }
500 }
501
502
503 SteamAPI_SteamNetworkingMessage_t_Release( msg );
504 }
505 }
506 }
507
508 static u64 seconds_to_server_ticks( double s ){
509 return s / 0.01;
510 }
511
512 static void generate_boards(void){
513 FILE *fp = fopen( "www/html/srhighscores.txt", "w" );
514
515 if( !fp ){
516 vg_error( "Can't write boards to www/html/srhighscores.txt\n" );
517 return;
518 }
519
520 for( int i=0; i<vg_list_size(track_infos); i++ ){
521 struct netmsg_board *board = &scoreboard_client_data.boards[i];
522
523 highscores_board_generate( board->data, i, 10 );
524 highscores_board_printf( fp, board->data, 10 );
525 }
526
527 fclose( fp );
528 }
529
530 int main( int argc, char *argv[] ){
531 signal( SIGINT, inthandler );
532 signal( SIGQUIT, inthandler );
533 signal( SIGPIPE, SIG_IGN );
534
535 char *arg;
536 while( vg_argp( argc, argv ) ){
537 if( vg_long_opt( "noauth" ) )
538 gameserver.auth_mode = eServerModeNoAuthentication;
539 }
540
541 /* TODO: Options to override, ammend, remove etc */
542
543 vg_set_mem_quota( 80*1024*1024 );
544 vg_alloc_quota();
545
546 highscores_init( 250000, 10000 );
547
548 if( !highscores_read() )
549 highscores_create_db();
550
551 steamworks_ensure_txt( "2103940" );
552
553 if( gameserver.auth_mode == eServerModeAuthentication ){
554 if( !vg_load_steam_symetric_key( "application_key",
555 gameserver.app_symmetric_key )){
556 return 0;
557 }
558 }
559 else{
560 vg_warn( "Running without user authentication.\n" );
561 }
562
563 if( !SteamGameServer_Init( 0, 27400, 27401,
564 gameserver.auth_mode, "1.0.0.0" ) ){
565 vg_error( "SteamGameServer_Init failed\n" );
566 return 0;
567 }
568
569 void *hSteamGameServer = SteamAPI_SteamGameServer();
570 SteamAPI_ISteamGameServer_LogOnAnonymous( hSteamGameServer );
571
572 SteamAPI_ManualDispatch_Init();
573 HSteamPipe hsteampipe = SteamGameServer_GetHSteamPipe();
574
575 //hSteamHTTP = SteamAPI_SteamGameServerHTTP();
576 hSteamNetworkingSockets =
577 SteamAPI_SteamGameServerNetworkingSockets_SteamAPI();
578
579 /*
580 * Server code
581 */
582
583 steam_register_callback( k_iSteamNetAuthenticationStatus, on_auth_status );
584 steam_register_callback( k_iSteamNetConnectionStatusChangedCallBack,
585 on_connect_status );
586
587 vg_success( "Steamworks API running\n" );
588 steamworks_event_loop( hsteampipe );
589
590 /*
591 * Create a listener
592 */
593
594 HSteamListenSocket listener;
595 SteamNetworkingIPAddr localAddr;
596 SteamAPI_SteamNetworkingIPAddr_Clear( &localAddr );
597 localAddr.m_port = 27402;
598
599 listener = SteamAPI_ISteamNetworkingSockets_CreateListenSocketIP(
600 hSteamNetworkingSockets, &localAddr, 0, NULL );
601 gameserver.client_group = SteamAPI_ISteamNetworkingSockets_CreatePollGroup(
602 hSteamNetworkingSockets );
603
604 u64 server_ticks = 8000,
605 last_record_save = 8000,
606 last_scoreboard_gen = 0,
607 last_monitor_heartbeat = 0;
608
609 generate_boards();
610 monitor_start_server();
611
612 while( !sig_stop ){
613 monitor_event_loop();
614 steamworks_event_loop( hsteampipe );
615 poll_connections();
616
617 usleep(10000);
618 server_ticks ++;
619
620 if( server_ticks >
621 (last_monitor_heartbeat + seconds_to_server_ticks(10.0))){
622 last_monitor_heartbeat = server_ticks;
623 monitor_heartbeat();
624 }
625
626 if( server_ticks > last_scoreboard_gen + seconds_to_server_ticks(60.0) ){
627 last_scoreboard_gen = server_ticks;
628 generate_boards();
629 }
630
631 if( server_ticks > last_record_save + seconds_to_server_ticks(10.0*60.0)){
632 last_record_save = server_ticks;
633 highscores_serialize_all();
634 }
635 }
636
637 highscores_serialize_all();
638
639 SteamAPI_ISteamNetworkingSockets_DestroyPollGroup( hSteamNetworkingSockets,
640 gameserver.client_group );
641 SteamAPI_ISteamNetworkingSockets_CloseListenSocket(
642 hSteamNetworkingSockets, listener );
643
644 vg_info( "Shutting down\n..." );
645 SteamGameServer_Shutdown();
646
647 return 0;
648 }