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