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