update helpers/location to 'frosted' ui
[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 #include <string.h>
10
11 volatile sig_atomic_t sig_stop;
12
13 #include "gameserver.h"
14 #include "vg/vg_opt.h"
15 #include "network_common.h"
16 #include "gameserver_db.h"
17 #include "vg/vg_m.h"
18 #include "vg/vg_msg.h"
19
20 static u64 const k_steamid_max = 0xffffffffffffffff;
21
22 static void inthandler( int signum ) {
23 sig_stop = 1;
24 }
25
26 static void release_message( SteamNetworkingMessage_t *msg )
27 {
28 msg->m_nUserData --;
29
30 if( msg->m_nUserData == 0 )
31 SteamAPI_SteamNetworkingMessage_t_Release( msg );
32 }
33
34 /*
35 * Send message to single client, with authentication checking
36 */
37 static void gameserver_send_to_client( i32 client_id,
38 const void *pData, u32 cbData,
39 int nSendFlags )
40 {
41 struct gameserver_client *client = &gameserver.clients[ client_id ];
42
43 if( gameserver.loopback_test && !client->connection )
44 return;
45
46 if( !client->steamid )
47 return;
48
49 SteamAPI_ISteamNetworkingSockets_SendMessageToConnection(
50 hSteamNetworkingSockets, client->connection,
51 pData, cbData, nSendFlags, NULL );
52 }
53
54 /*
55 * Send message to all clients if they are authenticated
56 */
57 static void gameserver_send_to_all( int ignore,
58 const void *pData, u32 cbData,
59 int nSendFlags )
60 {
61 for( int i=0; i<vg_list_size(gameserver.clients); i++ )
62 {
63 struct gameserver_client *client = &gameserver.clients[i];
64
65 if( i != ignore )
66 gameserver_send_to_client( i, pData, cbData, nSendFlags );
67 }
68 }
69
70 static void gameserver_send_version_to_client( int index )
71 {
72 struct gameserver_client *client = &gameserver.clients[index];
73
74 if( gameserver.loopback_test && !client->connection )
75 return;
76
77 netmsg_version version;
78 version.inetmsg_id = k_inetmsg_version;
79 version.version = NETWORK_SKATERIFT_VERSION;
80 SteamAPI_ISteamNetworkingSockets_SendMessageToConnection(
81 hSteamNetworkingSockets, client->connection,
82 &version, sizeof(netmsg_version),
83 k_nSteamNetworkingSend_Reliable, NULL );
84 }
85
86 /*
87 * handle server update that client #'index' has joined
88 */
89 static void gameserver_player_join( int index )
90 {
91 struct gameserver_client *joiner = &gameserver.clients[index];
92
93 netmsg_playerjoin join = { .inetmsg_id = k_inetmsg_playerjoin,
94 .index = index,
95 .steamid = joiner->steamid };
96
97 gameserver_send_to_all( index, &join, sizeof(join),
98 k_nSteamNetworkingSend_Reliable );
99
100 /*
101 * update the joining user about current connections and our version
102 */
103 gameserver_send_version_to_client( index );
104
105 netmsg_playerusername *username =
106 alloca( sizeof(netmsg_playerusername) + NETWORK_USERNAME_MAX );
107 username->inetmsg_id = k_inetmsg_playerusername;
108
109 netmsg_playeritem *item =
110 alloca( sizeof(netmsg_playeritem) + ADDON_UID_MAX );
111 item->inetmsg_id = k_inetmsg_playeritem;
112
113 netmsg_region *region = alloca( sizeof(netmsg_region) + NETWORK_REGION_MAX );
114 region->inetmsg_id = k_inetmsg_region;
115
116 for( int i=0; i<vg_list_size(gameserver.clients); i++ )
117 {
118 struct gameserver_client *client = &gameserver.clients[i];
119
120 if( (i == index) || !client->steamid )
121 continue;
122
123 /* join */
124 netmsg_playerjoin init = { .inetmsg_id = k_inetmsg_playerjoin,
125 .index = i,
126 .steamid = client->steamid };
127 gameserver_send_to_client( index, &init, sizeof(init),
128 k_nSteamNetworkingSend_Reliable );
129
130 /* username */
131 username->index = i;
132 u32 chs = vg_strncpy( client->username, username->name,
133 NETWORK_USERNAME_MAX,
134 k_strncpy_always_add_null );
135 u32 size = sizeof(netmsg_playerusername) + chs + 1;
136 gameserver_send_to_client( index, username, size,
137 k_nSteamNetworkingSend_Reliable );
138
139 /* items */
140 for( int j=0; j<k_netmsg_playeritem_max; j++ )
141 {
142 chs = vg_strncpy( client->items[j].uid, item->uid, ADDON_UID_MAX,
143 k_strncpy_always_add_null );
144 item->type_index = j;
145 item->client = i;
146 size = sizeof(netmsg_playeritem) + chs + 1;
147 gameserver_send_to_client( index, item, size,
148 k_nSteamNetworkingSend_Reliable );
149 }
150
151 /* region */
152
153 region->client = i;
154 region->flags = client->region_flags;
155 u32 l = vg_strncpy( client->region, region->loc, NETWORK_REGION_MAX,
156 k_strncpy_always_add_null );
157 size = sizeof(netmsg_region) + l + 1;
158
159 gameserver_send_to_client( index, region, size,
160 k_nSteamNetworkingSend_Reliable );
161 }
162 }
163
164 /*
165 * Handle server update that player has left
166 */
167 static void gameserver_player_leave( int index ){
168 if( gameserver.auth_mode == eServerModeAuthentication ){
169 if( !gameserver.clients[ index ].steamid )
170 return;
171 }
172
173 netmsg_playerleave leave;
174 leave.inetmsg_id = k_inetmsg_playerleave;
175 leave.index = index;
176
177 vg_info( "Player leave (%d)\n", index );
178 gameserver_send_to_all( index, &leave, sizeof(leave),
179 k_nSteamNetworkingSend_Reliable );
180 }
181
182 static void gameserver_update_all_knowledge( int client, int clear );
183
184 /*
185 * Deletes client at index and disconnects the connection handle if it was
186 * set.
187 */
188 static void remove_client( int index ){
189 struct gameserver_client *client = &gameserver.clients[index];
190 if( client->connection ){
191 SteamAPI_ISteamNetworkingSockets_SetConnectionUserData(
192 hSteamNetworkingSockets, client->connection, -1 );
193 SteamAPI_ISteamNetworkingSockets_CloseConnection(
194 hSteamNetworkingSockets, client->connection,
195 k_ESteamNetConnectionEnd_Misc_InternalError,
196 NULL, 1 );
197 }
198 memset( client, 0, sizeof(struct gameserver_client) );
199 gameserver_update_all_knowledge( index, 1 );
200 }
201
202 /*
203 * Handle incoming new connection and init flags on the steam handle. if the
204 * server is full the userdata (client_id) will be set to -1 on the handle.
205 */
206 static void handle_new_connection( HSteamNetConnection conn )
207 {
208 SteamAPI_ISteamNetworkingSockets_SetConnectionUserData(
209 hSteamNetworkingSockets, conn, -1 );
210
211 int index = -1;
212
213 for( int i=0; i<vg_list_size(gameserver.clients); i++ ){
214 if( !gameserver.clients[i].active ){
215 index = i;
216 break;
217 }
218 }
219
220 if( index == -1 ){
221 vg_error( "Server full\n" );
222 SteamAPI_ISteamNetworkingSockets_CloseConnection(
223 hSteamNetworkingSockets, conn,
224 4500,
225 NULL, 1 );
226 return;
227 }
228
229 struct gameserver_client *client = &gameserver.clients[index];
230 EResult accept_status = SteamAPI_ISteamNetworkingSockets_AcceptConnection(
231 hSteamNetworkingSockets, conn );
232
233 if( accept_status == k_EResultOK )
234 {
235 vg_success( "Accepted client (id: %u, index: %d)\n", conn, index );
236
237 client->active = 1;
238 client->connection = conn;
239
240 SteamAPI_ISteamNetworkingSockets_SetConnectionPollGroup(
241 hSteamNetworkingSockets, conn, gameserver.client_group );
242
243 SteamAPI_ISteamNetworkingSockets_SetConnectionUserData(
244 hSteamNetworkingSockets, conn, index );
245
246 if( gameserver.loopback_test )
247 {
248 vg_warn( "[DEV] Creating loopback client\n" );
249 struct gameserver_client *loopback = &gameserver.clients[1];
250 loopback->active = 1;
251 loopback->connection = 0;
252 }
253 }
254 else
255 {
256 vg_warn( "Error accepting connection (id: %u)\n", conn );
257 SteamAPI_ISteamNetworkingSockets_CloseConnection(
258 hSteamNetworkingSockets, conn,
259 k_ESteamNetConnectionEnd_Misc_InternalError,
260 NULL, 1 );
261 }
262 }
263
264 static void on_auth_status( CallbackMsg_t *msg ){
265 SteamNetAuthenticationStatus_t *info = (void *)msg->m_pubParam;
266 vg_info( " Authentication availibility: %s\n",
267 string_ESteamNetworkingAvailability(info->m_eAvail) );
268 vg_info( " %s\n", info->m_debugMsg );
269 }
270
271 /*
272 * Get client id of connection handle. Will be -1 if unkown to us either because
273 * the server is full or we already disconnected them
274 */
275 static i32 gameserver_conid( HSteamNetConnection hconn )
276 {
277 i64 id;
278
279 if( hconn == 0 )
280 {
281 if( gameserver.loopback_test )
282 return 1;
283 else
284 return -1;
285 }
286 else
287 id = SteamAPI_ISteamNetworkingSockets_GetConnectionUserData(
288 hSteamNetworkingSockets, hconn );
289
290 if( (id < 0) || (id >= NETWORK_MAX_PLAYERS) )
291 return -1;
292
293 return id;
294 }
295
296 /*
297 * Callback for steam connection state change
298 */
299 static void on_connect_status( CallbackMsg_t *msg )
300 {
301 SteamNetConnectionStatusChangedCallback_t *info = (void *)msg->m_pubParam;
302 vg_info( " Connection status changed for %lu\n", info->m_hConn );
303
304 vg_info( " %s -> %s\n",
305 string_ESteamNetworkingConnectionState(info->m_eOldState),
306 string_ESteamNetworkingConnectionState(info->m_info.m_eState) );
307
308 if( info->m_info.m_eState==k_ESteamNetworkingConnectionState_Connecting )
309 {
310 handle_new_connection( info->m_hConn );
311 }
312
313 if( (info->m_info.m_eState ==
314 k_ESteamNetworkingConnectionState_ClosedByPeer ) ||
315 (info->m_info.m_eState ==
316 k_ESteamNetworkingConnectionState_ProblemDetectedLocally ) ||
317 (info->m_info.m_eState ==
318 k_ESteamNetworkingConnectionState_Dead) ||
319 (info->m_info.m_eState ==
320 k_ESteamNetworkingConnectionState_None) )
321 {
322 vg_info( "End reason: %d\n", info->m_info.m_eEndReason );
323
324 int client_id = gameserver_conid( info->m_hConn );
325 if( client_id != -1 )
326 {
327 gameserver_player_leave( client_id );
328 remove_client( client_id );
329
330 if( gameserver.loopback_test )
331 {
332 gameserver_player_leave( 1 );
333 remove_client( 1 );
334 }
335 }
336 else
337 {
338 SteamAPI_ISteamNetworkingSockets_CloseConnection(
339 hSteamNetworkingSockets, info->m_hConn, 0, NULL, 0 );
340 }
341 }
342 }
343
344 static void gameserver_rx_version( SteamNetworkingMessage_t *msg )
345 {
346 netmsg_version *version = msg->m_pData;
347
348 int client_id = gameserver_conid( msg->m_conn );
349 if( client_id == -1 )
350 {
351 vg_warn( "Recieved version from unkown connection (%u)\n", msg->m_conn );
352 SteamAPI_ISteamNetworkingSockets_CloseConnection(
353 hSteamNetworkingSockets, msg->m_conn,
354 k_ESteamNetConnectionEnd_Misc_InternalError,
355 NULL, 1 );
356 return;
357 }
358
359 struct gameserver_client *client = &gameserver.clients[ client_id ];
360
361 if( client->version )
362 {
363 vg_warn( "Already have version for this client (%d conn: %u)",
364 client_id, msg->m_conn );
365 return;
366 }
367
368 client->version = version->version;
369
370 if( client->version != NETWORK_SKATERIFT_VERSION )
371 {
372 gameserver_send_version_to_client( client_id );
373 remove_client( client_id );
374 return;
375 }
376
377 /* this is the sign on point for non-auth servers,
378 * for auth servers it comes at the end of rx_auth
379 */
380 if( gameserver.auth_mode != eServerModeAuthentication )
381 {
382 client->steamid = k_steamid_max;
383 gameserver_player_join( client_id );
384
385 if( gameserver.loopback_test )
386 {
387 struct gameserver_client *loopback = &gameserver.clients[1];
388 loopback->steamid = k_steamid_max;
389 gameserver_player_join( 1 );
390 }
391 }
392 }
393
394 /*
395 * recieve auth ticket from connection. will only accept it if we've added them
396 * to the client list first.
397 */
398 static void gameserver_rx_auth( SteamNetworkingMessage_t *msg ){
399 if( gameserver.auth_mode != eServerModeAuthentication ){
400 vg_warn( "Running server without authentication. "
401 "Connection %u tried to authenticate.\n", msg->m_conn );
402 return;
403 }
404
405 int client_id = gameserver_conid( msg->m_conn );
406 if( client_id == -1 ) {
407 vg_warn( "Recieved auth ticket from unkown connection (%u)\n",
408 msg->m_conn );
409 SteamAPI_ISteamNetworkingSockets_CloseConnection(
410 hSteamNetworkingSockets, msg->m_conn,
411 k_ESteamNetConnectionEnd_Misc_InternalError, NULL, 1 );
412 return;
413 }
414
415 struct gameserver_client *client = &gameserver.clients[ client_id ];
416 if( client->steamid ){
417 vg_warn( "Already authorized this user but another app ticket was sent"
418 " again (%d conn: %u)\n", client_id, msg->m_conn );
419 return;
420 }
421
422 if( client->version == 0 ){
423 vg_error( "Client has not sent their version yet (%u)\n", msg->m_conn );
424 remove_client( client_id );
425 return;
426 }
427
428 vg_low( "Attempting to verify user\n" );
429
430 if( msg->m_cbSize < sizeof(netmsg_auth) ){
431 vg_error( "Malformed auth ticket, too small (%u)\n", msg->m_conn );
432 remove_client( client_id );
433 return;
434 }
435
436 netmsg_auth *auth = msg->m_pData;
437
438 if( msg->m_cbSize < sizeof(netmsg_auth)+auth->ticket_length ||
439 auth->ticket_length > 1024 ){
440 vg_error( "Malformed auth ticket, ticket_length incorrect (%u)\n",
441 auth->ticket_length );
442 remove_client( client_id );
443 return;
444 }
445
446 u8 decrypted[1024];
447 u32 ticket_len = 1024;
448
449 int success = SteamEncryptedAppTicket_BDecryptTicket(
450 auth->ticket, auth->ticket_length, decrypted,
451 &ticket_len, gameserver.app_symmetric_key,
452 k_nSteamEncryptedAppTicketSymmetricKeyLen );
453
454 if( !success ){
455 vg_error( "Failed to decrypt users ticket (client %u)\n", msg->m_conn );
456 vg_error( " ticket length: %u\n", auth->ticket_length );
457 remove_client( client_id );
458 return;
459 }
460
461 if( SteamEncryptedAppTicket_GetTicketIssueTime( decrypted, ticket_len )){
462 RTime32 ctime = time(NULL),
463 tickettime = SteamEncryptedAppTicket_GetTicketIssueTime(
464 decrypted, ticket_len ),
465 expiretime = tickettime + 24*3*60*60;
466
467 if( ctime > expiretime ){
468 vg_error( "Ticket expired (client %u)\n", msg->m_conn );
469 remove_client( client_id );
470 return;
471 }
472 }
473
474 CSteamID steamid;
475 SteamEncryptedAppTicket_GetTicketSteamID( decrypted, ticket_len, &steamid );
476 vg_success( "User is authenticated! steamid %lu (%u)\n",
477 steamid.m_unAll64Bits, msg->m_conn );
478
479 client->steamid = steamid.m_unAll64Bits;
480 gameserver_player_join( client_id );
481 }
482
483 /*
484 * Player updates sent to us
485 * -----------------------------------------------------------------------------
486 */
487
488 static int packet_minsize( SteamNetworkingMessage_t *msg, u32 size ){
489 if( msg->m_cbSize < size ) {
490 vg_error( "Invalid packet size (must be at least %u)\n", size );
491 return 0;
492 }
493 else{
494 return 1;
495 }
496 }
497
498 struct db_set_username_thread_data {
499 u64 steamid;
500 char username[ NETWORK_USERNAME_MAX ];
501 };
502
503 static void gameserver_update_db_username( db_request *db_req ){
504 struct db_set_username_thread_data *inf = (void *)db_req->data;
505
506 if( inf->steamid == k_steamid_max )
507 return;
508
509 int admin = 0;
510 if( inf->steamid == 76561198072130043 )
511 admin = 2;
512
513 db_updateuser( inf->steamid, inf->username, admin );
514 }
515
516 static int gameserver_item_eq( struct gameserver_item *ia,
517 struct gameserver_item *ib ){
518 if( ia->hash == ib->hash )
519 if( !strcmp(ia->uid,ib->uid) )
520 return 1;
521
522 return 0;
523 }
524
525 /*
526 * Match addons between two player IDs. if clear is set, then the flags between
527 * those two IDs will all be set to 0.
528 */
529 static void gameserver_update_knowledge_table( int client0, int client1,
530 int clear ){
531 u32 idx = network_pair_index( client0, client1 );
532
533 struct gameserver_client *c0 = &gameserver.clients[client0],
534 *c1 = &gameserver.clients[client1];
535
536 u8 flags = 0x00;
537
538 if( !clear ){
539 if( gameserver_item_eq(&c0->items[k_netmsg_playeritem_world0],
540 &c1->items[k_netmsg_playeritem_world0]))
541 flags |= CLIENT_KNOWLEDGE_SAME_WORLD0;
542
543 if( gameserver_item_eq(&c0->items[k_netmsg_playeritem_world1],
544 &c1->items[k_netmsg_playeritem_world1]))
545 flags |= CLIENT_KNOWLEDGE_SAME_WORLD1;
546 }
547
548 gameserver.client_knowledge_mask[idx] = flags;
549 }
550
551 /*
552 * If a change has been made on this client, then it will adjust the entire
553 * table of other players. if clear is set, all references to client will be set
554 * to 0.
555 */
556 static void gameserver_update_all_knowledge( int client, int clear ){
557 for( int i=0; i<NETWORK_MAX_PLAYERS; i ++ ){
558 if( i == client )
559 continue;
560
561 struct gameserver_client *ci = &gameserver.clients[i];
562
563 if( ci->steamid )
564 gameserver_update_knowledge_table( client, i, clear );
565 }
566 }
567
568 static void gameserver_propogate_player_frame( int client_id,
569 netmsg_playerframe *frame,
570 u32 size ){
571 u32 basic_size = sizeof(netmsg_playerframe) + ((24*3)/8);
572 netmsg_playerframe *full = alloca(size),
573 *basic= alloca(basic_size);
574
575 memcpy( full, frame, size );
576 memcpy( basic, frame, basic_size );
577
578 full->client = client_id;
579 basic->client = client_id;
580 basic->subsystem = 4; /* (.._basic_info: 24f*3 animator ) */
581 basic->sound_effects = 0;
582
583 struct gameserver_client *c0 = &gameserver.clients[client_id];
584 c0->instance = frame->flags & NETMSG_PLAYERFRAME_INSTANCE_ID;
585
586 for( int i=0; i<vg_list_size(gameserver.clients); i++ )
587 {
588 if( i == client_id )
589 continue;
590
591 struct gameserver_client *ci = &gameserver.clients[i];
592
593 int send_full = 0;
594
595 if( c0->instance == ci->instance )
596 {
597 u32 k_index = network_pair_index( client_id, i );
598 u8 k_mask = gameserver.client_knowledge_mask[ k_index ];
599
600 if( (k_mask & (CLIENT_KNOWLEDGE_SAME_WORLD0<<c0->instance)) )
601 send_full = 1;
602 }
603
604 if( send_full )
605 {
606 gameserver_send_to_client( i, full, size,
607 k_nSteamNetworkingSend_Unreliable );
608 }
609 else
610 {
611 gameserver_send_to_client( i, basic, basic_size,
612 k_nSteamNetworkingSend_Unreliable );
613 }
614 }
615 }
616
617 static void gameserver_rx_200_300( SteamNetworkingMessage_t *msg )
618 {
619 netmsg_blank *tmp = msg->m_pData;
620
621 int client_id = gameserver_conid( msg->m_conn );
622 if( client_id == -1 ) return;
623
624 struct gameserver_client *client = &gameserver.clients[ client_id ];
625
626 if( tmp->inetmsg_id == k_inetmsg_playerusername )
627 {
628 if( !packet_minsize( msg, sizeof(netmsg_playerusername)+1 ))
629 return;
630
631 netmsg_playerusername *src = msg->m_pData;
632
633 u32 name_len = network_msgstring( src->name, msg->m_cbSize,
634 sizeof(netmsg_playerusername),
635 client->username,
636 NETWORK_USERNAME_MAX );
637
638 /* update other users about this change */
639 netmsg_playerusername *prop = alloca(sizeof(netmsg_playerusername)+
640 NETWORK_USERNAME_MAX );
641
642 prop->inetmsg_id = k_inetmsg_playerusername;
643 prop->index = client_id;
644 u32 chs = vg_strncpy( client->username, prop->name, NETWORK_USERNAME_MAX,
645 k_strncpy_always_add_null );
646
647 vg_info( "client #%d changed name to: %s\n", client_id, prop->name );
648
649 u32 propsize = sizeof(netmsg_playerusername) + chs + 1;
650 gameserver_send_to_all( client_id, prop, propsize,
651 k_nSteamNetworkingSend_Reliable );
652
653 /* update database about this */
654 db_request *call = db_alloc_request(
655 sizeof(struct db_set_username_thread_data) );
656 struct db_set_username_thread_data *inf = (void *)call->data;
657 inf->steamid = client->steamid;
658 vg_strncpy( client->username, inf->username,
659 sizeof(inf->username), k_strncpy_always_add_null );
660 call->handler = gameserver_update_db_username;
661 db_send_request( call );
662 }
663 else if( tmp->inetmsg_id == k_inetmsg_playerframe )
664 {
665 gameserver_propogate_player_frame( client_id,
666 msg->m_pData, msg->m_cbSize );
667 }
668 else if( tmp->inetmsg_id == k_inetmsg_playeritem )
669 {
670 netmsg_playeritem *item = msg->m_pData;
671
672 /* record */
673 if( item->type_index >= k_netmsg_playeritem_max )
674 {
675 vg_warn( "Client #%d invalid equip type %u\n",
676 client_id, (u32)item->type_index );
677 return;
678 }
679
680 char *dest = client->items[ item->type_index ].uid;
681
682 network_msgstring( item->uid, msg->m_cbSize, sizeof(netmsg_playeritem),
683 dest, ADDON_UID_MAX );
684
685 vg_info( "Client #%d equiped: [%s] %s\n",
686 client_id,
687 (const char *[]){[k_netmsg_playeritem_board]="board",
688 [k_netmsg_playeritem_player]="player",
689 [k_netmsg_playeritem_world0]="world0",
690 [k_netmsg_playeritem_world1]="world1"
691 }[item->type_index], item->uid );
692
693 gameserver_update_all_knowledge( client_id, 0 );
694
695 /* propogate */
696 netmsg_playeritem *prop = alloca(msg->m_cbSize);
697 memcpy( prop, msg->m_pData, msg->m_cbSize );
698 prop->client = client_id;
699 gameserver_send_to_all( client_id, prop, msg->m_cbSize,
700 k_nSteamNetworkingSend_Reliable );
701 }
702 else if( tmp->inetmsg_id == k_inetmsg_chat )
703 {
704 netmsg_chat *chat = msg->m_pData,
705 *prop = alloca( sizeof(netmsg_chat) + NETWORK_MAX_CHAT );
706 prop->inetmsg_id = k_inetmsg_chat;
707 prop->client = client_id;
708
709 u32 l = network_msgstring( chat->msg, msg->m_cbSize, sizeof(netmsg_chat),
710 prop->msg, NETWORK_MAX_CHAT );
711 vg_info( "[%d]: %s\n", client_id, prop->msg );
712
713 gameserver_send_to_all( client_id, prop, sizeof(netmsg_chat)+l+1,
714 k_nSteamNetworkingSend_Reliable );
715 }
716 else if( tmp->inetmsg_id == k_inetmsg_region )
717 {
718 netmsg_region *region = msg->m_pData,
719 *prop = alloca( sizeof(netmsg_region) + NETWORK_REGION_MAX );
720
721 prop->inetmsg_id = k_inetmsg_region;
722 prop->client = client_id;
723 prop->flags = region->flags;
724
725 u32 l = network_msgstring(
726 region->loc, msg->m_cbSize, sizeof(netmsg_region),
727 client->region, NETWORK_REGION_MAX );
728 client->region_flags = region->flags;
729
730 l = vg_strncpy( client->region, prop->loc, NETWORK_REGION_MAX,
731 k_strncpy_always_add_null );
732
733 gameserver_send_to_all( client_id, prop, sizeof(netmsg_region)+l+1,
734 k_nSteamNetworkingSend_Reliable );
735 vg_info( "client %d moved to region: %s\n", client_id, client->region );
736 }
737 else
738 {
739 vg_warn( "Unknown inetmsg_id recieved from client. (%u)\n",
740 tmp->inetmsg_id );
741 }
742 }
743
744 static void gameserver_request_respond( enum request_status status,
745 netmsg_request *res, vg_msg *body,
746 SteamNetworkingMessage_t *msg ){
747 int client_id = gameserver_conid( msg->m_conn );
748 u32 len = 0;
749 if( body ){
750 len = body->cur.co;
751 vg_low( "[%d#%d] Response: %d\n", client_id, (i32)res->id, status );
752 vg_msg_print( body, len );
753 }
754
755 res->status = status;
756
757 if( gameserver.loopback_test && !msg->m_conn )
758 {
759 release_message( msg );
760 return;
761 }
762
763 SteamAPI_ISteamNetworkingSockets_SendMessageToConnection(
764 hSteamNetworkingSockets, msg->m_conn,
765 res, sizeof(netmsg_request) + len,
766 k_nSteamNetworkingSend_Reliable, NULL );
767
768 release_message( msg );
769 }
770
771 struct user_request_thread_data {
772 SteamNetworkingMessage_t *msg;
773 };
774
775 static u32 gameserver_get_current_week(void){
776 return time(NULL) / (7*24*60*60);
777 }
778
779 static enum request_status gameserver_cat_table(
780 vg_msg *msg,
781 const char *mod, const char *route, u32 week, const char *alias )
782 {
783 char table_name[ DB_TABLE_UID_MAX ];
784 if( !db_get_highscore_table_name( mod, route, week, table_name ) )
785 return k_request_status_out_of_memory;
786
787 char buf[512];
788 vg_str q;
789 vg_strnull( &q, buf, 512 );
790 vg_strcat( &q, "SELECT * FROM \"" );
791 vg_strcat( &q, table_name );
792 vg_strcat( &q, "\" ORDER BY time ASC LIMIT 10;" );
793 if( !vg_strgood(&q) )
794 return k_request_status_out_of_memory;
795
796 sqlite3_stmt *stmt = db_stmt( q.buffer );
797 if( !stmt )
798 return k_request_status_database_error;
799
800 vg_msg_frame( msg, alias );
801 for( u32 i=0; i<10; i ++ ){
802 int fc = sqlite3_step( stmt );
803
804 if( fc == SQLITE_ROW ){
805 i32 time = sqlite3_column_int( stmt, 1 );
806 i64 steamid_i64 = sqlite3_column_int64( stmt, 0 );
807 u64 steamid = *((u64 *)&steamid_i64);
808
809 if( steamid == k_steamid_max )
810 continue;
811
812 vg_msg_frame( msg, "" );
813 vg_msg_wkvnum( msg, "time", k_vg_msg_u32, 1, &time );
814 vg_msg_wkvnum( msg, "steamid", k_vg_msg_u64, 1, &steamid );
815
816 char username[32];
817 if( db_getuserinfo( steamid, username, sizeof(username), NULL ) )
818 vg_msg_wkvstr( msg, "username", username );
819 vg_msg_end_frame( msg );
820 }
821 else if( fc == SQLITE_DONE ){
822 break;
823 }
824 else {
825 log_sqlite3( fc );
826 break;
827 }
828 }
829
830 sqlite3_finalize( stmt );
831 vg_msg_end_frame( msg );
832 return k_request_status_ok;
833 }
834
835 static void gameserver_process_user_request( db_request *db_req )
836 {
837 struct user_request_thread_data *inf = (void *)db_req->data;
838 SteamNetworkingMessage_t *msg = inf->msg;
839
840 int client_id = gameserver_conid( msg->m_conn );
841 if( client_id == -1 )
842 {
843 release_message( msg );
844 return;
845 }
846
847 struct gameserver_client *client = &gameserver.clients[ client_id ];
848
849 netmsg_request *req = (netmsg_request *)msg->m_pData;
850 vg_msg data;
851 vg_msg_init( &data, req->q, msg->m_cbSize - sizeof(netmsg_request) );
852
853 /* create response packet */
854 netmsg_request *res = alloca( sizeof(netmsg_request) + NETWORK_REQUEST_MAX );
855 res->inetmsg_id = k_inetmsg_response;
856 res->id = req->id;
857 vg_msg body;
858 vg_msg_init( &body, res->q, NETWORK_REQUEST_MAX );
859
860 const char *endpoint = vg_msg_getkvstr( &data, "endpoint" );
861
862 if( !endpoint ){
863 gameserver_request_respond( k_request_status_invalid_endpoint,
864 res, NULL, msg );
865 return;
866 }
867
868 if( !strcmp( endpoint, "scoreboard" ) ){
869 const char *mod = vg_msg_getkvstr( &data, "mod" );
870 const char *route = vg_msg_getkvstr( &data, "route" );
871 u32 week;
872 vg_msg_getkvintg( &data, "week", k_vg_msg_u32, &week, NULL );
873
874 if( week == NETWORK_LEADERBOARD_CURRENT_WEEK ){
875 gameserver_cat_table( &body, mod, route,
876 gameserver_get_current_week(), "rows_weekly" );
877 }
878 else if( week == NETWORK_LEADERBOARD_ALLTIME_AND_CURRENT_WEEK ){
879 gameserver_cat_table( &body, mod, route, 0, "rows" );
880 gameserver_cat_table( &body, mod, route,
881 gameserver_get_current_week(), "rows_weekly" );
882 }
883 else
884 gameserver_cat_table( &body, mod, route, week, "rows" );
885
886 if( body.error != k_vg_msg_error_OK ){
887 gameserver_request_respond( k_request_status_out_of_memory,
888 res, NULL, msg );
889 return;
890 }
891
892 gameserver_request_respond( k_request_status_ok, res, &body, msg );
893 }
894 else if( !strcmp( endpoint, "setlap" ) ){
895 if( client->steamid == k_steamid_max ){
896 gameserver_request_respond( k_request_status_unauthorized,
897 res, NULL, msg );
898 return;
899 }
900
901 const char *mod = vg_msg_getkvstr( &data, "mod" );
902 const char *route = vg_msg_getkvstr( &data, "route" );
903
904 char weekly_table[ DB_TABLE_UID_MAX ],
905 alltime_table[ DB_TABLE_UID_MAX ];
906
907 u32 week = gameserver_get_current_week();
908
909 if( !db_get_highscore_table_name( mod, route, 0, alltime_table ) ||
910 !db_get_highscore_table_name( mod, route, week, weekly_table ) ){
911 gameserver_request_respond( k_request_status_out_of_memory,
912 res, NULL, msg );
913 return;
914 }
915
916 i32 centiseconds;
917 vg_msg_getkvintg( &data, "time", k_vg_msg_i32, &centiseconds, NULL );
918 if( centiseconds < 5*100 ){
919 gameserver_request_respond( k_request_status_client_error,
920 res, NULL, msg );
921 return;
922 }
923
924 db_writeusertime( alltime_table, client->steamid, centiseconds, 1 );
925 db_writeusertime( weekly_table, client->steamid, centiseconds, 1 );
926 gameserver_request_respond( k_request_status_ok, res, NULL, msg );
927 }
928 else{
929 gameserver_request_respond( k_request_status_invalid_endpoint,
930 res, NULL, msg );
931 }
932 }
933
934 static void gameserver_rx_300_400( SteamNetworkingMessage_t *msg )
935 {
936 netmsg_blank *tmp = msg->m_pData;
937
938 int client_id = gameserver_conid( msg->m_conn );
939 if( client_id == -1 )
940 {
941 release_message( msg );
942 return;
943 }
944
945 if( tmp->inetmsg_id == k_inetmsg_request )
946 {
947 if( gameserver.loopback_test && (client_id == 1) )
948 {
949 release_message( msg );
950 return;
951 }
952
953 if( !packet_minsize( msg, sizeof(netmsg_request)+1 ))
954 {
955 release_message( msg );
956 return;
957 }
958
959 db_request *call = db_alloc_request(
960 sizeof(struct user_request_thread_data) );
961 struct user_request_thread_data *inf = (void *)call->data;
962 inf->msg = msg;
963 call->handler = gameserver_process_user_request;
964 db_send_request( call );
965 }
966 else
967 {
968 vg_warn( "Unknown inetmsg_id recieved from client. (%u)\n",
969 tmp->inetmsg_id );
970 release_message( msg );
971 }
972 }
973
974 static void process_network_message( SteamNetworkingMessage_t *msg )
975 {
976 if( msg->m_cbSize < sizeof(netmsg_blank) ){
977 vg_warn( "Discarding message (too small: %d)\n",
978 msg->m_cbSize );
979 return;
980 }
981
982 netmsg_blank *tmp = msg->m_pData;
983
984 if( (tmp->inetmsg_id >= 200) && (tmp->inetmsg_id < 300) )
985 {
986 gameserver_rx_200_300( msg );
987 release_message( msg );
988 }
989 else if( (tmp->inetmsg_id >= 300) && (tmp->inetmsg_id < 400) )
990 {
991 gameserver_rx_300_400( msg );
992 }
993 else{
994 if( tmp->inetmsg_id == k_inetmsg_auth )
995 gameserver_rx_auth( msg );
996 else if( tmp->inetmsg_id == k_inetmsg_version ){
997 gameserver_rx_version( msg );
998 }
999 else {
1000 vg_warn( "Unknown inetmsg_id recieved from client. (%u)\n",
1001 tmp->inetmsg_id );
1002 }
1003 release_message( msg );
1004 }
1005 }
1006
1007 static void poll_connections(void)
1008 {
1009 SteamNetworkingMessage_t *messages[32];
1010 int len;
1011
1012 while(1)
1013 {
1014 len = SteamAPI_ISteamNetworkingSockets_ReceiveMessagesOnPollGroup(
1015 hSteamNetworkingSockets,
1016 gameserver.client_group, messages, vg_list_size(messages) );
1017
1018 if( len <= 0 )
1019 return;
1020
1021 for( int i=0; i<len; i++ )
1022 {
1023 SteamNetworkingMessage_t *msg = messages[i];
1024 msg->m_nUserData = 1;
1025
1026 if( gameserver.loopback_test )
1027 {
1028 HSteamNetConnection conid = msg->m_conn;
1029 msg->m_conn = 0;
1030 msg->m_nUserData ++;
1031 process_network_message( msg );
1032 msg->m_conn = conid;
1033 }
1034
1035 process_network_message( msg );
1036 }
1037 }
1038 }
1039
1040 static u64 seconds_to_server_ticks( double s ){
1041 return s / 0.01;
1042 }
1043
1044 int main( int argc, char *argv[] ){
1045 signal( SIGINT, inthandler );
1046 signal( SIGQUIT, inthandler );
1047 signal( SIGPIPE, SIG_IGN );
1048
1049 char *arg;
1050 while( vg_argp( argc, argv ) )
1051 {
1052 if( vg_long_opt( "noauth" ) )
1053 gameserver.auth_mode = eServerModeNoAuthentication;
1054
1055 if( vg_long_opt( "loopback" ) )
1056 gameserver.loopback_test = 1;
1057 }
1058
1059 vg_set_mem_quota( 80*1024*1024 );
1060 vg_alloc_quota();
1061 db_init();
1062
1063 /* steamworks init
1064 * --------------------------------------------------------------- */
1065 steamworks_ensure_txt( "2103940" );
1066 if( gameserver.auth_mode == eServerModeAuthentication ){
1067 if( !vg_load_steam_symetric_key( "application_key",
1068 gameserver.app_symmetric_key )){
1069 return 0;
1070 }
1071 }
1072 else{
1073 vg_warn( "Running without user authentication.\n" );
1074 }
1075
1076 if( !SteamGameServer_Init( 0, NETWORK_PORT, NETWORK_PORT+1,
1077 gameserver.auth_mode, "1.0.0.0" ) ){
1078 vg_error( "SteamGameServer_Init failed\n" );
1079 return 0;
1080 }
1081
1082 void *hSteamGameServer = SteamAPI_SteamGameServer();
1083 SteamAPI_ISteamGameServer_LogOnAnonymous( hSteamGameServer );
1084
1085 SteamAPI_ManualDispatch_Init();
1086 HSteamPipe hsteampipe = SteamGameServer_GetHSteamPipe();
1087 hSteamNetworkingSockets =
1088 SteamAPI_SteamGameServerNetworkingSockets_SteamAPI();
1089
1090 steam_register_callback( k_iSteamNetAuthenticationStatus, on_auth_status );
1091 steam_register_callback( k_iSteamNetConnectionStatusChangedCallBack,
1092 on_connect_status );
1093
1094 vg_success( "Steamworks API running\n" );
1095 steamworks_event_loop( hsteampipe );
1096
1097 /*
1098 * Create a listener
1099 */
1100 HSteamListenSocket listener;
1101 SteamNetworkingIPAddr localAddr;
1102 SteamAPI_SteamNetworkingIPAddr_Clear( &localAddr );
1103 localAddr.m_port = NETWORK_PORT;
1104
1105 listener = SteamAPI_ISteamNetworkingSockets_CreateListenSocketIP(
1106 hSteamNetworkingSockets, &localAddr, 0, NULL );
1107 gameserver.client_group = SteamAPI_ISteamNetworkingSockets_CreatePollGroup(
1108 hSteamNetworkingSockets );
1109
1110 u64 server_ticks = 8000,
1111 last_record_save = 8000,
1112 last_scoreboard_gen = 0;
1113
1114 while( !sig_stop ){
1115 steamworks_event_loop( hsteampipe );
1116 poll_connections();
1117
1118 usleep(10000);
1119 server_ticks ++;
1120
1121 if( db_killed() )
1122 break;
1123 }
1124
1125 SteamAPI_ISteamNetworkingSockets_DestroyPollGroup( hSteamNetworkingSockets,
1126 gameserver.client_group );
1127 SteamAPI_ISteamNetworkingSockets_CloseListenSocket(
1128 hSteamNetworkingSockets, listener );
1129
1130 vg_info( "Shutting down\n..." );
1131 SteamGameServer_Shutdown();
1132 db_kill();
1133 db_free();
1134
1135 return 0;
1136 }