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