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