Notices & clean
[carveJwlIkooP6JGAAIwe30JlM.git] / server.c
1 /*
2 * Copyright (C) 2021-2022 Mt.ZERO Software, Harry Godden - All Rights Reserved
3 */
4
5 /*
6 * This server application requires steamclient.so to be present in the
7 * executable directory. This is not provided by vg system, it must be
8 * downloaded via steamcmd. It will likely be somewhere in ~/.steam/ ...
9 */
10
11 #define _DEFAULT_SOURCE
12 #include <unistd.h>
13 #include <signal.h>
14
15 volatile sig_atomic_t sig_stop;
16
17 void inthandler( int signum )
18 {
19 sig_stop = 1;
20 }
21
22 #define VG_SERVER
23 #include "vg/vg.h"
24 #include "vg/vg_steam.h"
25 #include "vg/vg_steam_networking.h"
26 #include "vg/vg_steam_http.h"
27 #include "vg/vg_steam_auth.h"
28 #include "network_msg.h"
29 #include "highscores.h"
30
31 static const u64 k_connection_unauthorized = 0xffffffffffffffff;
32
33 static void *hSteamHTTP,
34 *hSteamNetworkingSockets;
35
36 static u8 steam_symetric_key[ k_nSteamEncryptedAppTicketSymmetricKeyLen ];
37 static HSteamNetPollGroup client_pollgroup;
38
39 static void recieve_http( void *callresult, void *context )
40 {
41 HTTPRequestCompleted_t *result = callresult;
42
43 HTTPRequestHandle request = result->m_hRequest;
44 u32 size = 0;
45
46 SteamAPI_ISteamHTTP_GetHTTPResponseBodySize( hSteamHTTP, request, &size );
47
48 u8 *buffer = vg_alloc( size );
49 SteamAPI_ISteamHTTP_GetHTTPResponseBodyData(
50 hSteamHTTP, request, buffer, size );
51
52 buffer[size-1] = '\0';
53 vg_info( "%s\n", (char *)buffer );
54
55 vg_free( buffer );
56 SteamAPI_ISteamHTTP_ReleaseHTTPRequest( hSteamHTTP, result->m_hRequest );
57 }
58
59 static u64_steamid get_connection_authsteamid( SteamNetworkingMessage_t *msg )
60 {
61 i64 userdata = SteamAPI_ISteamNetworkingSockets_GetConnectionUserData(
62 hSteamNetworkingSockets, msg->m_conn );
63
64 return *((u64_steamid *)&userdata);
65 }
66
67 static void set_connection_authsteamid(HSteamNetConnection con, u64_steamid id)
68 {
69 i64 userdata = *((i64 *)&id);
70
71 SteamAPI_ISteamNetworkingSockets_SetConnectionUserData(
72 hSteamNetworkingSockets, con, userdata );
73 }
74
75 static void new_client_connecting( HSteamNetConnection client )
76 {
77 EResult accept_status = SteamAPI_ISteamNetworkingSockets_AcceptConnection(
78 hSteamNetworkingSockets, client );
79
80 if( accept_status == k_EResultOK )
81 {
82 vg_success( "Accepted client (id: %u)\n", client );
83 SteamAPI_ISteamNetworkingSockets_SetConnectionPollGroup(
84 hSteamNetworkingSockets,
85 client, client_pollgroup );
86
87 /* Just to be sure */
88 set_connection_authsteamid( client, -1 );
89 }
90 else
91 {
92 vg_warn( "Error accepting client (id: %u)\n", client );
93 }
94 }
95
96 static void on_auth_status( CallbackMsg_t *msg )
97 {
98 SteamNetAuthenticationStatus_t *info = (void *)msg->m_pubParam;
99 vg_info( " Authentication availibility: %s\n",
100 string_ESteamNetworkingAvailability(info->m_eAvail) );
101 vg_info( " %s\n", info->m_debugMsg );
102 }
103
104 static void on_connect_status( CallbackMsg_t *msg )
105 {
106 SteamNetConnectionStatusChangedCallback_t *info = (void *)msg->m_pubParam;
107 vg_info( " Connection status changed for %lu\n", info->m_hConn );
108
109 vg_info( " %s -> %s\n",
110 string_ESteamNetworkingConnectionState(info->m_eOldState),
111 string_ESteamNetworkingConnectionState(info->m_info.m_eState) );
112
113 if( info->m_info.m_eState==k_ESteamNetworkingConnectionState_Connecting )
114 {
115 new_client_connecting( info->m_hConn );
116 }
117 }
118
119 static void on_inet_auth( SteamNetworkingMessage_t *msg )
120 {
121 if( get_connection_authsteamid( msg ) != k_connection_unauthorized )
122 {
123 vg_warn( "Already authorized this user but app ticket was sent"
124 " again (%u)\n", msg->m_conn );
125 return;
126 }
127
128 vg_low( "Attempting to verify user\n" );
129
130 if( msg->m_cbSize < sizeof(netmsg_auth) )
131 {
132 vg_error( "Malformed auth ticket, too small (%u)\n", msg->m_conn );
133 return;
134 }
135
136 netmsg_auth *auth = msg->m_pData;
137
138 if( msg->m_cbSize < sizeof(netmsg_auth)+auth->ticket_length ||
139 auth->ticket_length > 1024 )
140 {
141 vg_error( "Malformed auth ticket, ticket_length incorrect (%u)\n",
142 auth->ticket_length );
143 return;
144 }
145
146 u8 decrypted[1024];
147 u32 ticket_len = 1024;
148
149 int success = SteamEncryptedAppTicket_BDecryptTicket(
150 auth->ticket, auth->ticket_length, decrypted,
151 &ticket_len, steam_symetric_key,
152 k_nSteamEncryptedAppTicketSymmetricKeyLen );
153
154 if( !success )
155 {
156 vg_error( "Failed to decrypt users ticket (client %u)\n", msg->m_conn );
157 vg_error( " ticket length: %u\n", auth->ticket_length );
158
159 SteamAPI_ISteamNetworkingSockets_CloseConnection(
160 hSteamNetworkingSockets,
161 msg->m_conn, 0, NULL, 1 );
162 return;
163 }
164
165 if( SteamEncryptedAppTicket_GetTicketIssueTime( decrypted, ticket_len ))
166 {
167 RTime32 ctime = time(NULL),
168 tickettime = SteamEncryptedAppTicket_GetTicketIssueTime(
169 decrypted, ticket_len ),
170 expiretime = tickettime + 24*3*60*60;
171
172 if( ctime > expiretime )
173 {
174 vg_error( "Ticket expired (client %u)\n", msg->m_conn );
175
176 /* TODO: Send expired information */
177 SteamAPI_ISteamNetworkingSockets_CloseConnection(
178 hSteamNetworkingSockets,
179 msg->m_conn, 0, NULL, 1 );
180 return;
181 }
182 }
183
184 CSteamID steamid;
185 SteamEncryptedAppTicket_GetTicketSteamID( decrypted, ticket_len, &steamid );
186 vg_success( "User is authenticated! steamid %lu (%u)\n",
187 steamid.m_unAll64Bits, msg->m_conn );
188
189 set_connection_authsteamid( msg->m_conn, steamid.m_unAll64Bits );
190 }
191
192 static int inet_require_auth( SteamNetworkingMessage_t *msg )
193 {
194 if( get_connection_authsteamid( msg ) == k_connection_unauthorized )
195 {
196 vg_warn( "Unauthorized request! Disconnecting client: %u\n",
197 msg->m_conn );
198
199 SteamAPI_ISteamNetworkingSockets_CloseConnection(
200 hSteamNetworkingSockets,
201 msg->m_conn, 0, NULL, 1 );
202
203 return 0;
204 }
205 else return 1;
206 }
207
208 static void on_inet_score_request( SteamNetworkingMessage_t *msg )
209 {
210 if( !inet_require_auth(msg) ) return;
211
212 SteamAPI_ISteamNetworkingSockets_SendMessageToConnection(
213 hSteamNetworkingSockets, msg->m_conn,
214 &scoreboard_client_data, sizeof(netmsg_scoreboard),
215 k_nSteamNetworkingSend_Reliable, NULL );
216 }
217
218 static void on_inet_set_nickname( SteamNetworkingMessage_t *msg )
219 {
220 if(!inet_require_auth(msg)) return;
221
222 u64_steamid steamid = get_connection_authsteamid(msg);
223 netmsg_set_nickname *setnick = msg->m_pData;
224 if( msg->m_cbSize < sizeof(netmsg_set_nickname) )
225 {
226 vg_warn( "Invalid nickname request from client: %u, steamid: %lu\n",
227 msg->m_conn, steamid );
228 return;
229 }
230
231 highscore_set_user_nickname( steamid, setnick->nickname );
232 }
233
234 static void on_inet_set_score( SteamNetworkingMessage_t *msg )
235 {
236 if(!inet_require_auth(msg)) return;
237
238 u64_steamid steamid = get_connection_authsteamid(msg);
239
240 if( msg->m_cbSize < sizeof(netmsg_set_score) )
241 {
242 vg_warn( "Invalid set score post from client: %u, steamid: %lu\n",
243 msg->m_conn, steamid );
244 return;
245 }
246
247 netmsg_set_score *info = msg->m_pData;
248
249 if( msg->m_cbSize < sizeof(netmsg_set_score) +
250 sizeof(struct netmsg_score_record)*info->record_count )
251 {
252 vg_warn( "Malformed set score post from client: %u, steamid: %lu\n",
253 msg->m_conn, steamid );
254 return;
255 }
256
257 for( int i=0; i<info->record_count; i++ )
258 {
259 highscore_record temp;
260 temp.trackid = info->records[i].trackid;
261 temp.datetime = time(NULL);
262 temp.playerid = steamid;
263 temp.points = info->records[i].points;
264 temp.time = info->records[i].time;
265
266 highscores_push_record( &temp );
267 }
268 }
269
270 static void poll_connections(void)
271 {
272 SteamNetworkingMessage_t *messages[32];
273 int len;
274
275 while(1)
276 {
277 len = SteamAPI_ISteamNetworkingSockets_ReceiveMessagesOnPollGroup(
278 hSteamNetworkingSockets,
279 client_pollgroup, messages, vg_list_size(messages) );
280
281 if( len <= 0 )
282 return;
283
284 for( int i=0; i<len; i++ )
285 {
286 SteamNetworkingMessage_t *msg = messages[i];
287
288 if( msg->m_cbSize < sizeof(netmsg_blank) )
289 {
290 vg_warn( "Discarding message (too small: %d)\n",
291 msg->m_cbSize );
292 continue;
293 }
294
295 netmsg_blank *tmp = msg->m_pData;
296
297 if( tmp->inetmsg_id == k_inetmsg_auth )
298 on_inet_auth( msg );
299 else if( tmp->inetmsg_id == k_inetmsg_scores_request )
300 on_inet_score_request( msg );
301 else if( tmp->inetmsg_id == k_inetmsg_set_nickname )
302 on_inet_set_nickname( msg );
303 else if( tmp->inetmsg_id == k_inetmsg_set_score )
304 on_inet_set_score( msg );
305
306 SteamAPI_SteamNetworkingMessage_t_Release( msg );
307 }
308 }
309 }
310
311 static u64 seconds_to_server_ticks( double s )
312 {
313 return s / 0.1;
314 }
315
316 static void generate_boards(void)
317 {
318 FILE *fp = fopen( "www/html/srhighscores.txt", "w" );
319
320 for( int i=0; i<vg_list_size(track_infos); i++ )
321 {
322 struct netmsg_board *board = &scoreboard_client_data.boards[i];
323
324 highscores_board_generate( board->data, i, 10 );
325 highscores_board_printf( fp, board->data, 10 );
326 }
327
328 fclose( fp );
329 }
330
331 int main( int argc, char *argv[] )
332 {
333 signal( SIGINT, inthandler );
334 signal( SIGQUIT, inthandler );
335
336 highscores_init( 250000, 10000 );
337
338 steamworks_ensure_txt( "2103940" );
339 if( !vg_load_steam_symetric_key( "application_key", steam_symetric_key ) )
340 return 0;
341
342 if( !SteamGameServer_Init( 0, 27400, 27401, eServerModeAuthentication,
343 "1.0.0.0" ) )
344 {
345 vg_error( "SteamGameServer_Init failed\n" );
346 return 0;
347 }
348
349 void *hSteamGameServer = SteamAPI_SteamGameServer();
350 SteamAPI_ISteamGameServer_LogOnAnonymous( hSteamGameServer );
351
352 SteamAPI_ManualDispatch_Init();
353 HSteamPipe hsteampipe = SteamGameServer_GetHSteamPipe();
354
355 //hSteamHTTP = SteamAPI_SteamGameServerHTTP();
356 hSteamNetworkingSockets =
357 SteamAPI_SteamGameServerNetworkingSockets_SteamAPI();
358
359 /*
360 * Server code
361 */
362
363 steam_register_callback( k_iSteamNetAuthenticationStatus, on_auth_status );
364 steam_register_callback( k_iSteamNetConnectionStatusChangedCallBack,
365 on_connect_status );
366
367 vg_success( "Steamworks API running\n" );
368 steamworks_event_loop( hsteampipe );
369
370 /*
371 * Create a listener
372 */
373
374 HSteamListenSocket listener;
375 SteamNetworkingIPAddr localAddr;
376 SteamAPI_SteamNetworkingIPAddr_Clear( &localAddr );
377 localAddr.m_port = 27402;
378
379 listener = SteamAPI_ISteamNetworkingSockets_CreateListenSocketIP(
380 hSteamNetworkingSockets, &localAddr, 0, NULL );
381 client_pollgroup = SteamAPI_ISteamNetworkingSockets_CreatePollGroup(
382 hSteamNetworkingSockets );
383
384 #if 0
385 HTTPRequestHandle test_req = SteamAPI_ISteamHTTP_CreateHTTPRequest(
386 hSteamHTTP, k_EHTTPMethodGET,
387 "https://www.harrygodden.com/hello.txt" );
388
389 steam_async *call1 = steam_new_async();
390 call1->data = NULL;
391 call1->p_handler = recieve_http;
392 SteamAPI_ISteamHTTP_SendHTTPRequest( hSteamHTTP, test_req, &call1->id );
393 #endif
394
395 u64 server_ticks = 8000,
396 last_record_save = 8000,
397 last_scoreboard_gen = 0;
398
399 generate_boards();
400
401 while( !sig_stop )
402 {
403 steamworks_event_loop( hsteampipe );
404 poll_connections();
405
406 usleep(100000);
407 server_ticks ++;
408
409 if(server_ticks > last_scoreboard_gen + seconds_to_server_ticks(1.0*60.0))
410 {
411 last_scoreboard_gen = server_ticks;
412 generate_boards();
413 }
414
415 if(server_ticks > last_record_save + seconds_to_server_ticks( 10.0*60.0 ))
416 {
417 last_record_save = server_ticks;
418 highscores_serialize_all();
419 }
420 }
421
422 highscores_serialize_all();
423 highscores_free();
424
425 SteamAPI_ISteamNetworkingSockets_DestroyPollGroup( hSteamNetworkingSockets,
426 client_pollgroup );
427 SteamAPI_ISteamNetworkingSockets_CloseListenSocket(
428 hSteamNetworkingSockets, listener );
429
430 vg_info( "Shutting down\n..." );
431 SteamGameServer_Shutdown();
432
433 return 0;
434 }