move database requests to seperate thread
[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
20 static void inthandler( int signum ) {
21 sig_stop = 1;
22 }
23
24 static const u64 k_connection_unauthorized = 0xffffffffffffffff;
25
26 static u64_steamid get_connection_authsteamid( SteamNetworkingMessage_t *msg ){
27 i64 userdata = SteamAPI_ISteamNetworkingSockets_GetConnectionUserData(
28 hSteamNetworkingSockets, msg->m_conn );
29
30 return *((u64_steamid *)&userdata);
31 }
32
33 static void set_connection_authsteamid(HSteamNetConnection con, u64_steamid id){
34 i64 userdata = *((i64 *)&id);
35
36 SteamAPI_ISteamNetworkingSockets_SetConnectionUserData(
37 hSteamNetworkingSockets, con, userdata );
38 }
39
40 static void gameserver_send_to_all( int ignore,
41 const void *pData, u32 cbData,
42 int nSendFlags ){
43 for( int i=0; i<vg_list_size(gameserver.clients); i++ ){
44 struct gameserver_client *client = &gameserver.clients[i];
45
46 if( (i==ignore) || !client->active )
47 continue;
48
49 SteamAPI_ISteamNetworkingSockets_SendMessageToConnection(
50 hSteamNetworkingSockets, client->connection,
51 pData, cbData, nSendFlags, NULL );
52 }
53 }
54
55 static void gameserver_player_join( int index ){
56 struct gameserver_client *joiner = &gameserver.clients[index];
57
58 netmsg_playerjoin join = { .inetmsg_id = k_inetmsg_playerjoin,
59 .index = index };
60 gameserver_send_to_all( index, &join, sizeof(join),
61 k_nSteamNetworkingSend_Reliable );
62
63 /* update the joining user about current connections */
64
65 netmsg_playerusername *username =
66 alloca( sizeof(netmsg_playerusername) + NETWORK_USERNAME_MAX );
67 username->inetmsg_id = k_inetmsg_playerusername;
68
69 netmsg_playeritem *item =
70 alloca( sizeof(netmsg_playeritem) + ADDON_UID_MAX );
71 item->inetmsg_id = k_inetmsg_playeritem;
72
73 for( int i=0; i<vg_list_size(gameserver.clients); i++ ){
74 struct gameserver_client *client = &gameserver.clients[i];
75
76 if( (i==index) || !client->active )
77 continue;
78
79 /* join */
80 netmsg_playerjoin init = { .inetmsg_id = k_inetmsg_playerjoin,
81 .index = i };
82 SteamAPI_ISteamNetworkingSockets_SendMessageToConnection(
83 hSteamNetworkingSockets, joiner->connection,
84 &init, sizeof(init), k_nSteamNetworkingSend_Reliable, NULL );
85
86 /* username */
87 username->index = i;
88 u32 chs = vg_strncpy( client->username, username->name,
89 NETWORK_USERNAME_MAX,
90 k_strncpy_always_add_null );
91 u32 size = sizeof(netmsg_playerusername) + chs + 1;
92 SteamAPI_ISteamNetworkingSockets_SendMessageToConnection(
93 hSteamNetworkingSockets, joiner->connection,
94 username, size, k_nSteamNetworkingSend_Reliable, NULL );
95
96 /* items */
97 for( int j=0; j<k_netmsg_playeritem_max; j++ ){
98 chs = vg_strncpy( client->items[j], item->uid, ADDON_UID_MAX,
99 k_strncpy_always_add_null );
100 item->type_index = j;
101 item->client = i;
102 size = sizeof(netmsg_playeritem) + chs + 1;
103 SteamAPI_ISteamNetworkingSockets_SendMessageToConnection(
104 hSteamNetworkingSockets, joiner->connection,
105 item, size, k_nSteamNetworkingSend_Reliable, NULL );
106 }
107 }
108 }
109
110 static void gameserver_player_leave( int index ){
111 netmsg_playerjoin leave;
112 leave.inetmsg_id = k_inetmsg_playerleave;
113 leave.index = index;
114
115 vg_info( "Player leave (%d)\n", index );
116 gameserver_send_to_all( index, &leave, sizeof(leave),
117 k_nSteamNetworkingSend_Reliable );
118 }
119
120 static void new_client_connecting( HSteamNetConnection client ){
121 int index = -1;
122
123 /* TODO: LRU */
124 for( int i=0; i<vg_list_size(gameserver.clients); i++ ){
125 if( !gameserver.clients[i].active ){
126 index = i;
127 break;
128 }
129 }
130
131 if( index == -1 ){
132 vg_error( "Server full\n" );
133 SteamAPI_ISteamNetworkingSockets_CloseConnection(
134 hSteamNetworkingSockets, client,
135 4500,
136 NULL, 1 );
137 return;
138 }
139
140 EResult accept_status = SteamAPI_ISteamNetworkingSockets_AcceptConnection(
141 hSteamNetworkingSockets, client );
142 if( accept_status == k_EResultOK ){
143 vg_success( "Accepted client (id: %u, index: %d)\n", client, index );
144 memset( &gameserver.clients[index], 0, sizeof(struct gameserver_client) );
145
146 gameserver.clients[index].active = 1;
147 gameserver.clients[index].connection = client;
148
149 SteamAPI_ISteamNetworkingSockets_SetConnectionPollGroup(
150 hSteamNetworkingSockets,
151 client, gameserver.client_group );
152
153 /* Just to be sure */
154 set_connection_authsteamid( client, -1 );
155 gameserver_player_join( index );
156 }
157 else{
158 vg_warn( "Error accepting client (id: %u)\n", client );
159 SteamAPI_ISteamNetworkingSockets_CloseConnection(
160 hSteamNetworkingSockets, client,
161 k_ESteamNetConnectionEnd_Misc_InternalError,
162 NULL, 1 );
163 gameserver.clients[index].active = 0;
164 gameserver.clients[index].connection = 0;
165 }
166 }
167
168 static void on_auth_status( CallbackMsg_t *msg ){
169 SteamNetAuthenticationStatus_t *info = (void *)msg->m_pubParam;
170 vg_info( " Authentication availibility: %s\n",
171 string_ESteamNetworkingAvailability(info->m_eAvail) );
172 vg_info( " %s\n", info->m_debugMsg );
173 }
174
175 static int gameserver_client_index( HSteamNetConnection hconn ){
176 for( int i=0; i<vg_list_size(gameserver.clients); i++ ){
177 struct gameserver_client *client = &gameserver.clients[i];
178
179 if( client->active ){
180 if( client->connection == hconn ){
181 return i;
182 }
183 }
184 }
185 return -1;
186 }
187
188 static void on_connect_status( CallbackMsg_t *msg ){
189 SteamNetConnectionStatusChangedCallback_t *info = (void *)msg->m_pubParam;
190 vg_info( " Connection status changed for %lu\n", info->m_hConn );
191
192 vg_info( " %s -> %s\n",
193 string_ESteamNetworkingConnectionState(info->m_eOldState),
194 string_ESteamNetworkingConnectionState(info->m_info.m_eState) );
195
196 if( info->m_info.m_eState==k_ESteamNetworkingConnectionState_Connecting ){
197 new_client_connecting( info->m_hConn );
198 }
199
200 if( (info->m_info.m_eState ==
201 k_ESteamNetworkingConnectionState_ClosedByPeer ) ||
202 (info->m_info.m_eState ==
203 k_ESteamNetworkingConnectionState_ProblemDetectedLocally ) ){
204
205 int client_id = gameserver_client_index( info->m_hConn );
206 if( client_id != -1 ){
207 struct gameserver_client *client = &gameserver.clients[client_id];
208 client->connection = 0;
209 client->active = 0;
210 gameserver_player_leave(client_id);
211 }
212
213 vg_info( "End reason: %d\n", info->m_info.m_eEndReason );
214 SteamAPI_ISteamNetworkingSockets_CloseConnection(
215 hSteamNetworkingSockets, info->m_hConn, 0, NULL, 0 );
216 }
217 }
218
219 static void gameserver_rx_auth( SteamNetworkingMessage_t *msg ){
220 if( gameserver.auth_mode != eServerModeAuthentication ){
221 vg_error( "Running server without authentication. "
222 "Connection %u tried to authenticate.\n", msg->m_conn );
223 return;
224 }
225
226 if( get_connection_authsteamid( msg ) != k_connection_unauthorized ){
227 vg_warn( "Already authorized this user but app ticket was sent"
228 " again (%u)\n", msg->m_conn );
229 return;
230 }
231
232 vg_low( "Attempting to verify user\n" );
233
234 if( msg->m_cbSize < sizeof(netmsg_auth) ){
235 vg_error( "Malformed auth ticket, too small (%u)\n", msg->m_conn );
236 return;
237 }
238
239 netmsg_auth *auth = msg->m_pData;
240
241 if( msg->m_cbSize < sizeof(netmsg_auth)+auth->ticket_length ||
242 auth->ticket_length > 1024 ){
243 vg_error( "Malformed auth ticket, ticket_length incorrect (%u)\n",
244 auth->ticket_length );
245 return;
246 }
247
248 u8 decrypted[1024];
249 u32 ticket_len = 1024;
250
251 int success = SteamEncryptedAppTicket_BDecryptTicket(
252 auth->ticket, auth->ticket_length, decrypted,
253 &ticket_len, gameserver.app_symmetric_key,
254 k_nSteamEncryptedAppTicketSymmetricKeyLen );
255
256 if( !success ){
257 vg_error( "Failed to decrypt users ticket (client %u)\n", msg->m_conn );
258 vg_error( " ticket length: %u\n", auth->ticket_length );
259
260 SteamAPI_ISteamNetworkingSockets_CloseConnection(
261 hSteamNetworkingSockets,
262 msg->m_conn, 0, NULL, 1 );
263 return;
264 }
265
266 if( SteamEncryptedAppTicket_GetTicketIssueTime( decrypted, ticket_len )){
267 RTime32 ctime = time(NULL),
268 tickettime = SteamEncryptedAppTicket_GetTicketIssueTime(
269 decrypted, ticket_len ),
270 expiretime = tickettime + 24*3*60*60;
271
272 if( ctime > expiretime ){
273 vg_error( "Ticket expired (client %u)\n", msg->m_conn );
274
275 /* TODO: Send expired information */
276 SteamAPI_ISteamNetworkingSockets_CloseConnection(
277 hSteamNetworkingSockets,
278 msg->m_conn, 0, NULL, 1 );
279 return;
280 }
281 }
282
283 CSteamID steamid;
284 SteamEncryptedAppTicket_GetTicketSteamID( decrypted, ticket_len, &steamid );
285 vg_success( "User is authenticated! steamid %lu (%u)\n",
286 steamid.m_unAll64Bits, msg->m_conn );
287
288 set_connection_authsteamid( msg->m_conn, steamid.m_unAll64Bits );
289 }
290
291 static int inet_require_auth( SteamNetworkingMessage_t *msg ){
292 if( gameserver.auth_mode == eServerModeNoAuthentication )
293 return 1;
294
295 if( get_connection_authsteamid( msg ) == k_connection_unauthorized ){
296 vg_warn( "Unauthorized request! Disconnecting client: %u\n",
297 msg->m_conn );
298
299 SteamAPI_ISteamNetworkingSockets_CloseConnection(
300 hSteamNetworkingSockets,
301 msg->m_conn, 0, NULL, 1 );
302
303 return 0;
304 }
305 else return 1;
306 }
307
308 /*
309 * Player updates sent to us
310 * -----------------------------------------------------------------------------
311 */
312
313 static int packet_minsize( SteamNetworkingMessage_t *msg, u32 size ){
314 if( msg->m_cbSize < size ) {
315 vg_error( "Invalid packet size (must be at least %u)\n", size );
316 return 0;
317 }
318 else{
319 return 1;
320 }
321 }
322
323 static void gameserver_rx_200_300( SteamNetworkingMessage_t *msg ){
324 netmsg_blank *tmp = msg->m_pData;
325
326 int client_id = gameserver_client_index( msg->m_conn );
327 if( client_id == -1 ) return;
328
329 if( tmp->inetmsg_id == k_inetmsg_playerusername ){
330 if( !packet_minsize( msg, sizeof(netmsg_playerusername)+1 ))
331 return;
332
333 struct gameserver_client *client = &gameserver.clients[ client_id ];
334 netmsg_playerusername *src = msg->m_pData;
335
336 u32 name_len = network_msgstring( src->name, msg->m_cbSize,
337 sizeof(netmsg_playerusername),
338 client->username,
339 NETWORK_USERNAME_MAX );
340
341 /* update other users about this change */
342 netmsg_playerusername *prop = alloca(sizeof(netmsg_playerusername)+
343 NETWORK_USERNAME_MAX );
344
345 prop->inetmsg_id = k_inetmsg_playerusername;
346 prop->index = client_id;
347 u32 chs = vg_strncpy( client->username, prop->name, NETWORK_USERNAME_MAX,
348 k_strncpy_always_add_null );
349
350 vg_info( "client #%d changed name to: %s\n", client_id, prop->name );
351
352 u32 propsize = sizeof(netmsg_playerusername) + chs + 1;
353 gameserver_send_to_all( client_id, prop, propsize,
354 k_nSteamNetworkingSend_Reliable );
355 }
356 else if( tmp->inetmsg_id == k_inetmsg_playerframe ){
357 /* propogate */
358 netmsg_playerframe *frame = alloca(msg->m_cbSize);
359 memcpy( frame, msg->m_pData, msg->m_cbSize );
360 frame->client = client_id;
361 gameserver_send_to_all( client_id, frame, msg->m_cbSize,
362 k_nSteamNetworkingSend_Unreliable );
363 }
364 else if( tmp->inetmsg_id == k_inetmsg_playeritem ){
365 netmsg_playeritem *item = msg->m_pData;
366
367 /* record */
368 struct gameserver_client *client = &gameserver.clients[ client_id ];
369
370 if( item->type_index >= k_netmsg_playeritem_max ){
371 vg_warn( "Client #%d invalid equip type %u\n",
372 client_id, (u32)item->type_index );
373 return;
374 }
375
376 char *dest = client->items[ item->type_index ];
377
378 network_msgstring( item->uid, msg->m_cbSize, sizeof(netmsg_playeritem),
379 dest, ADDON_UID_MAX );
380
381 vg_info( "Client #%d equiped: [%s] %s\n",
382 client_id,
383 (const char *[]){[k_netmsg_playeritem_board]="board",
384 [k_netmsg_playeritem_player]="player",
385 [k_netmsg_playeritem_world0]="world0",
386 [k_netmsg_playeritem_world1]="world1"
387 }[item->type_index], item->uid );
388
389 /* propogate */
390 netmsg_playeritem *prop = alloca(msg->m_cbSize);
391 memcpy( prop, msg->m_pData, msg->m_cbSize );
392 prop->client = client_id;
393 gameserver_send_to_all( client_id, prop, msg->m_cbSize,
394 k_nSteamNetworkingSend_Reliable );
395 }
396 }
397
398 static void poll_connections(void){
399 SteamNetworkingMessage_t *messages[32];
400 int len;
401
402 while(1){
403 len = SteamAPI_ISteamNetworkingSockets_ReceiveMessagesOnPollGroup(
404 hSteamNetworkingSockets,
405 gameserver.client_group, messages, vg_list_size(messages) );
406
407 if( len <= 0 )
408 return;
409
410 for( int i=0; i<len; i++ ){
411 SteamNetworkingMessage_t *msg = messages[i];
412
413 if( msg->m_cbSize < sizeof(netmsg_blank) ){
414 vg_warn( "Discarding message (too small: %d)\n",
415 msg->m_cbSize );
416 continue;
417 }
418
419 netmsg_blank *tmp = msg->m_pData;
420
421 if( (tmp->inetmsg_id >= 200) && (tmp->inetmsg_id < 300) ){
422 gameserver_rx_200_300( msg );
423 }
424 else{
425 if( tmp->inetmsg_id == k_inetmsg_auth )
426 gameserver_rx_auth( msg );
427 else {
428 vg_warn( "Unknown inetmsg_id recieved from client. (%u)\n",
429 tmp->inetmsg_id );
430 }
431 }
432
433 SteamAPI_SteamNetworkingMessage_t_Release( msg );
434 }
435 }
436 }
437
438 static u64 seconds_to_server_ticks( double s ){
439 return s / 0.01;
440 }
441
442 static void test_runner( db_request *req ){
443 vg_warn( "RUNNER\n" );
444 char table[DB_TABLE_UID_MAX];
445 if( db_get_highscore_table_name( "sr003-local-mp_mtzero",
446 "megapark-yellow", 302, table ) ){
447 if( db_writeusertime( table, 76561198072130043, 232, 1 ) ){
448 vg_success( "Written time\n" );
449 i32 v = db_readusertime( table, 76561198072130043 );
450 vg_success( "Returned time: %u\n", v );
451 }
452 }
453 }
454
455 int main( int argc, char *argv[] ){
456 signal( SIGINT, inthandler );
457 signal( SIGQUIT, inthandler );
458 signal( SIGPIPE, SIG_IGN );
459
460 char *arg;
461 while( vg_argp( argc, argv ) ){
462 if( vg_long_opt( "noauth" ) )
463 gameserver.auth_mode = eServerModeNoAuthentication;
464
465 /* TODO: Options to override, ammend, remove etc */
466 }
467
468 vg_set_mem_quota( 80*1024*1024 );
469 vg_alloc_quota();
470
471 db_init();
472 db_request *req = db_alloc_request(0);
473 if( req ){
474 req->handler = test_runner;
475 db_send_request(req);
476 }
477
478 monitor_start_server(); /* UNIX socket monitor */
479
480 /* steamworks init
481 * --------------------------------------------------------------- */
482 steamworks_ensure_txt( "2103940" );
483 if( gameserver.auth_mode == eServerModeAuthentication ){
484 if( !vg_load_steam_symetric_key( "application_key",
485 gameserver.app_symmetric_key )){
486 return 0;
487 }
488 }
489 else{
490 vg_warn( "Running without user authentication.\n" );
491 }
492
493 if( !SteamGameServer_Init( 0, 27400, 27401,
494 gameserver.auth_mode, "1.0.0.0" ) ){
495 vg_error( "SteamGameServer_Init failed\n" );
496 return 0;
497 }
498
499 void *hSteamGameServer = SteamAPI_SteamGameServer();
500 SteamAPI_ISteamGameServer_LogOnAnonymous( hSteamGameServer );
501
502 SteamAPI_ManualDispatch_Init();
503 HSteamPipe hsteampipe = SteamGameServer_GetHSteamPipe();
504 hSteamNetworkingSockets =
505 SteamAPI_SteamGameServerNetworkingSockets_SteamAPI();
506
507 steam_register_callback( k_iSteamNetAuthenticationStatus, on_auth_status );
508 steam_register_callback( k_iSteamNetConnectionStatusChangedCallBack,
509 on_connect_status );
510
511 vg_success( "Steamworks API running\n" );
512 steamworks_event_loop( hsteampipe );
513
514 /*
515 * Create a listener
516 */
517 HSteamListenSocket listener;
518 SteamNetworkingIPAddr localAddr;
519 SteamAPI_SteamNetworkingIPAddr_Clear( &localAddr );
520 localAddr.m_port = 27402;
521
522 listener = SteamAPI_ISteamNetworkingSockets_CreateListenSocketIP(
523 hSteamNetworkingSockets, &localAddr, 0, NULL );
524 gameserver.client_group = SteamAPI_ISteamNetworkingSockets_CreatePollGroup(
525 hSteamNetworkingSockets );
526
527 u64 server_ticks = 8000,
528 last_record_save = 8000,
529 last_scoreboard_gen = 0,
530 last_monitor_heartbeat = 0;
531
532 while( !sig_stop ){
533 monitor_event_loop();
534 steamworks_event_loop( hsteampipe );
535 poll_connections();
536
537 usleep(10000);
538 server_ticks ++;
539
540 if( server_ticks >
541 (last_monitor_heartbeat + seconds_to_server_ticks(10.0))){
542 last_monitor_heartbeat = server_ticks;
543 monitor_heartbeat();
544 }
545
546 if( db_killed() )
547 break;
548 }
549
550 SteamAPI_ISteamNetworkingSockets_DestroyPollGroup( hSteamNetworkingSockets,
551 gameserver.client_group );
552 SteamAPI_ISteamNetworkingSockets_CloseListenSocket(
553 hSteamNetworkingSockets, listener );
554
555 vg_info( "Shutting down\n..." );
556 SteamGameServer_Shutdown();
557 db_free();
558
559 return 0;
560 }