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