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