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