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