From 0b75d51821259e7cd61f0ac999eea8379836dda7 Mon Sep 17 00:00:00 2001 From: hgn Date: Wed, 26 Mar 2025 04:03:10 +0000 Subject: [PATCH] server building; demo server replays... --- build.c | 3 +- content_skaterift/maps/mp_line1/main.mdl | Bin 5991144 -> 5986128 bytes src/gameserver.c | 367 +++++++++++------------ src/gameserver.h | 3 + src/gameserver_db.h | 58 ++-- src/gameserver_replay.c | 196 ++++++++++++ src/gameserver_replay.h | 39 +++ src/network.c | 7 +- src/network_common.h | 5 +- src/network_msg.h | 28 +- 10 files changed, 477 insertions(+), 229 deletions(-) create mode 100644 src/gameserver_replay.c create mode 100644 src/gameserver_replay.h diff --git a/build.c b/build.c index c35905b..95766b4 100644 --- a/build.c +++ b/build.c @@ -274,11 +274,12 @@ void compile_server( struct vg_project *proj, struct vg_compiler_env *env ) struct compile_result sqlite = build_sqlite_for_env( env ); vg_str sources = {0}; - vg_strcat( &sources, "gameserver.c vg/vg_tool.c vg/vg_steam.c \\\n " ); + vg_strcat( &sources, "src/gameserver.c vg/vg_tool.c vg/vg_steam.c \\\n " ); vg_strcat( &sources, sqlite.path.buffer ); struct vg_compiler_conf conf = {0}; vg_strcat( &conf.include, "-Isrc -I./dep " ); + vg_strcat( &conf.include, "-I. -I./vg -I./vg/dep " ); vg_strcat( &conf.library, "-L./vg/dep/steam " ); vg_strcat( &conf.link, "-ldl -lpthread -lm " "-lsdkencryptedappticket -lsteam_api " ); diff --git a/content_skaterift/maps/mp_line1/main.mdl b/content_skaterift/maps/mp_line1/main.mdl index e8d212ba3d739da9b42a1b4f89aef3fa650433db..019bc06db9abad6afe528cc542015158655edd90 100644 GIT binary patch delta 7779 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 ev$6lJoK1U{_7(5kyZYH2Z&pZ|9l- delta 12452 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 S86IDgwqWsrrilv|_xpd3h1ZP$ diff --git a/src/gameserver.c b/src/gameserver.c index e315fd0..9f4614f 100644 --- a/src/gameserver.c +++ b/src/gameserver.c @@ -16,6 +16,7 @@ volatile sig_atomic_t sig_stop; #include "gameserver_db.h" #include "vg/vg_m.h" #include "vg/vg_msg.h" +#include "gameserver_replay.h" static u64 const k_steamid_max = 0xffffffffffffffff; @@ -54,11 +55,9 @@ static void gameserver_send_to_client( i32 client_id, /* * Send message to all clients if they are authenticated */ -static void gameserver_send_to_all( int ignore, - const void *pData, u32 cbData, - int nSendFlags ) +static void gameserver_send_to_all( int ignore, const void *pData, u32 cbData, int nSendFlags ) { - for( int i=0; iinetmsg_id = k_inetmsg_region; - for( int i=0; iactive = 1; client->connection = conn; - SteamAPI_ISteamNetworkingSockets_SetConnectionPollGroup( - hSteamNetworkingSockets, conn, gameserver.client_group ); - - SteamAPI_ISteamNetworkingSockets_SetConnectionUserData( - hSteamNetworkingSockets, conn, index ); + SteamAPI_ISteamNetworkingSockets_SetConnectionPollGroup( hSteamNetworkingSockets, conn, gameserver.client_group ); + SteamAPI_ISteamNetworkingSockets_SetConnectionUserData( hSteamNetworkingSockets, conn, index ); if( gameserver.loopback_test ) { @@ -254,17 +248,15 @@ static void handle_new_connection( HSteamNetConnection conn ) else { vg_warn( "Error accepting connection (id: %u)\n", conn ); - SteamAPI_ISteamNetworkingSockets_CloseConnection( - hSteamNetworkingSockets, conn, - k_ESteamNetConnectionEnd_Misc_InternalError, - NULL, 1 ); + SteamAPI_ISteamNetworkingSockets_CloseConnection( hSteamNetworkingSockets, conn, + k_ESteamNetConnectionEnd_Misc_InternalError, NULL, 1 ); } } -static void on_auth_status( CallbackMsg_t *msg ){ +static void on_auth_status( CallbackMsg_t *msg ) +{ SteamNetAuthenticationStatus_t *info = (void *)msg->m_pubParam; - vg_info( " Authentication availibility: %s\n", - string_ESteamNetworkingAvailability(info->m_eAvail) ); + vg_info( " Authentication availibility: %s\n", string_ESteamNetworkingAvailability(info->m_eAvail) ); vg_info( " %s\n", info->m_debugMsg ); } @@ -284,8 +276,7 @@ static i32 gameserver_conid( HSteamNetConnection hconn ) return -1; } else - id = SteamAPI_ISteamNetworkingSockets_GetConnectionUserData( - hSteamNetworkingSockets, hconn ); + id = SteamAPI_ISteamNetworkingSockets_GetConnectionUserData( hSteamNetworkingSockets, hconn ); if( (id < 0) || (id >= NETWORK_MAX_PLAYERS) ) return -1; @@ -310,14 +301,10 @@ static void on_connect_status( CallbackMsg_t *msg ) handle_new_connection( info->m_hConn ); } - if( (info->m_info.m_eState == - k_ESteamNetworkingConnectionState_ClosedByPeer ) || - (info->m_info.m_eState == - k_ESteamNetworkingConnectionState_ProblemDetectedLocally ) || - (info->m_info.m_eState == - k_ESteamNetworkingConnectionState_Dead) || - (info->m_info.m_eState == - k_ESteamNetworkingConnectionState_None) ) + if( (info->m_info.m_eState == k_ESteamNetworkingConnectionState_ClosedByPeer ) || + (info->m_info.m_eState == k_ESteamNetworkingConnectionState_ProblemDetectedLocally ) || + (info->m_info.m_eState == k_ESteamNetworkingConnectionState_Dead) || + (info->m_info.m_eState == k_ESteamNetworkingConnectionState_None) ) { vg_info( "End reason: %d\n", info->m_info.m_eEndReason ); @@ -335,8 +322,7 @@ static void on_connect_status( CallbackMsg_t *msg ) } else { - SteamAPI_ISteamNetworkingSockets_CloseConnection( - hSteamNetworkingSockets, info->m_hConn, 0, NULL, 0 ); + SteamAPI_ISteamNetworkingSockets_CloseConnection( hSteamNetworkingSockets, info->m_hConn, 0, NULL, 0 ); } } } @@ -349,10 +335,8 @@ static void gameserver_rx_version( SteamNetworkingMessage_t *msg ) if( client_id == -1 ) { vg_warn( "Recieved version from unkown connection (%u)\n", msg->m_conn ); - SteamAPI_ISteamNetworkingSockets_CloseConnection( - hSteamNetworkingSockets, msg->m_conn, - k_ESteamNetConnectionEnd_Misc_InternalError, - NULL, 1 ); + SteamAPI_ISteamNetworkingSockets_CloseConnection( hSteamNetworkingSockets, msg->m_conn, + k_ESteamNetConnectionEnd_Misc_InternalError, NULL, 1 ); return; } @@ -360,8 +344,7 @@ static void gameserver_rx_version( SteamNetworkingMessage_t *msg ) if( client->version ) { - vg_warn( "Already have version for this client (%d conn: %u)", - client_id, msg->m_conn ); + vg_warn( "Already have version for this client (%d conn: %u)", client_id, msg->m_conn ); return; } @@ -403,23 +386,24 @@ static void gameserver_rx_auth( SteamNetworkingMessage_t *msg ){ } int client_id = gameserver_conid( msg->m_conn ); - if( client_id == -1 ) { - vg_warn( "Recieved auth ticket from unkown connection (%u)\n", - msg->m_conn ); - SteamAPI_ISteamNetworkingSockets_CloseConnection( - hSteamNetworkingSockets, msg->m_conn, - k_ESteamNetConnectionEnd_Misc_InternalError, NULL, 1 ); + if( client_id == -1 ) + { + vg_warn( "Recieved auth ticket from unkown connection (%u)\n", msg->m_conn ); + SteamAPI_ISteamNetworkingSockets_CloseConnection( hSteamNetworkingSockets, msg->m_conn, + k_ESteamNetConnectionEnd_Misc_InternalError, NULL, 1 ); return; } struct gameserver_client *client = &gameserver.clients[ client_id ]; - if( client->steamid ){ - vg_warn( "Already authorized this user but another app ticket was sent" - " again (%d conn: %u)\n", client_id, msg->m_conn ); + if( client->steamid ) + { + vg_warn( "Already authorized this user but another app ticket was sent again (%d conn: %u)\n", + client_id, msg->m_conn ); return; } - if( client->version == 0 ){ + if( client->version == 0 ) + { vg_error( "Client has not sent their version yet (%u)\n", msg->m_conn ); remove_client( client_id ); return; @@ -427,7 +411,8 @@ static void gameserver_rx_auth( SteamNetworkingMessage_t *msg ){ vg_low( "Attempting to verify user\n" ); - if( msg->m_cbSize < sizeof(netmsg_auth) ){ + if( msg->m_cbSize < sizeof(netmsg_auth) ) + { vg_error( "Malformed auth ticket, too small (%u)\n", msg->m_conn ); remove_client( client_id ); return; @@ -435,10 +420,9 @@ static void gameserver_rx_auth( SteamNetworkingMessage_t *msg ){ netmsg_auth *auth = msg->m_pData; - if( msg->m_cbSize < sizeof(netmsg_auth)+auth->ticket_length || - auth->ticket_length > 1024 ){ - vg_error( "Malformed auth ticket, ticket_length incorrect (%u)\n", - auth->ticket_length ); + if( msg->m_cbSize < sizeof(netmsg_auth)+auth->ticket_length || auth->ticket_length > 1024 ) + { + vg_error( "Malformed auth ticket, ticket_length incorrect (%u)\n", auth->ticket_length ); remove_client( client_id ); return; } @@ -446,25 +430,26 @@ static void gameserver_rx_auth( SteamNetworkingMessage_t *msg ){ u8 decrypted[1024]; u32 ticket_len = 1024; - int success = SteamEncryptedAppTicket_BDecryptTicket( - auth->ticket, auth->ticket_length, decrypted, - &ticket_len, gameserver.app_symmetric_key, - k_nSteamEncryptedAppTicketSymmetricKeyLen ); + int success = SteamEncryptedAppTicket_BDecryptTicket( auth->ticket, auth->ticket_length, decrypted, + &ticket_len, gameserver.app_symmetric_key, + k_nSteamEncryptedAppTicketSymmetricKeyLen ); - if( !success ){ + if( !success ) + { vg_error( "Failed to decrypt users ticket (client %u)\n", msg->m_conn ); vg_error( " ticket length: %u\n", auth->ticket_length ); remove_client( client_id ); return; } - if( SteamEncryptedAppTicket_GetTicketIssueTime( decrypted, ticket_len )){ + if( SteamEncryptedAppTicket_GetTicketIssueTime( decrypted, ticket_len )) + { RTime32 ctime = time(NULL), - tickettime = SteamEncryptedAppTicket_GetTicketIssueTime( - decrypted, ticket_len ), + tickettime = SteamEncryptedAppTicket_GetTicketIssueTime( decrypted, ticket_len ), expiretime = tickettime + 24*3*60*60; - if( ctime > expiretime ){ + if( ctime > expiretime ) + { vg_error( "Ticket expired (client %u)\n", msg->m_conn ); remove_client( client_id ); return; @@ -473,9 +458,7 @@ static void gameserver_rx_auth( SteamNetworkingMessage_t *msg ){ CSteamID steamid; SteamEncryptedAppTicket_GetTicketSteamID( decrypted, ticket_len, &steamid ); - vg_success( "User is authenticated! steamid %lu (%u)\n", - steamid.m_unAll64Bits, msg->m_conn ); - + vg_success( "User is authenticated! steamid %lu (%u)\n", steamid.m_unAll64Bits, msg->m_conn ); client->steamid = steamid.m_unAll64Bits; gameserver_player_join( client_id ); } @@ -485,22 +468,25 @@ static void gameserver_rx_auth( SteamNetworkingMessage_t *msg ){ * ----------------------------------------------------------------------------- */ -static int packet_minsize( SteamNetworkingMessage_t *msg, u32 size ){ - if( msg->m_cbSize < size ) { +static int packet_minsize( SteamNetworkingMessage_t *msg, u32 size ) +{ + if( msg->m_cbSize < size ) + { vg_error( "Invalid packet size (must be at least %u)\n", size ); return 0; } - else{ + else return 1; - } } -struct db_set_username_thread_data { +struct db_set_username_thread_data +{ u64 steamid; char username[ NETWORK_USERNAME_MAX ]; }; -static void gameserver_update_db_username( db_request *db_req ){ +static void gameserver_update_db_username( db_request *db_req ) +{ struct db_set_username_thread_data *inf = (void *)db_req->data; if( inf->steamid == k_steamid_max ) @@ -513,8 +499,8 @@ static void gameserver_update_db_username( db_request *db_req ){ db_updateuser( inf->steamid, inf->username, admin ); } -static int gameserver_item_eq( struct gameserver_item *ia, - struct gameserver_item *ib ){ +static int gameserver_item_eq( struct gameserver_item *ia, struct gameserver_item *ib ) +{ if( ia->hash == ib->hash ) if( !strcmp(ia->uid,ib->uid) ) return 1; @@ -526,8 +512,8 @@ static int gameserver_item_eq( struct gameserver_item *ia, * Match addons between two player IDs. if clear is set, then the flags between * those two IDs will all be set to 0. */ -static void gameserver_update_knowledge_table( int client0, int client1, - int clear ){ +static void gameserver_update_knowledge_table( int client0, int client1, int clear ) +{ u32 idx = network_pair_index( client0, client1 ); struct gameserver_client *c0 = &gameserver.clients[client0], @@ -553,8 +539,10 @@ static void gameserver_update_knowledge_table( int client0, int client1, * table of other players. if clear is set, all references to client will be set * to 0. */ -static void gameserver_update_all_knowledge( int client, int clear ){ - for( int i=0; iinstance = frame->flags & NETMSG_PLAYERFRAME_INSTANCE_ID; - for( int i=0; im_pData; - u32 name_len = network_msgstring( src->name, msg->m_cbSize, - sizeof(netmsg_playerusername), - client->username, + u32 name_len = network_msgstring( src->name, msg->m_cbSize, sizeof(netmsg_playerusername), client->username, NETWORK_USERNAME_MAX ); /* update other users about this change */ - netmsg_playerusername *prop = alloca(sizeof(netmsg_playerusername)+ - NETWORK_USERNAME_MAX ); + netmsg_playerusername *prop = alloca(sizeof(netmsg_playerusername)+NETWORK_USERNAME_MAX ); prop->inetmsg_id = k_inetmsg_playerusername; prop->index = client_id; - u32 chs = vg_strncpy( client->username, prop->name, NETWORK_USERNAME_MAX, - k_strncpy_always_add_null ); + u32 chs = vg_strncpy( client->username, prop->name, NETWORK_USERNAME_MAX, k_strncpy_always_add_null ); vg_info( "client #%d changed name to: %s\n", client_id, prop->name ); u32 propsize = sizeof(netmsg_playerusername) + chs + 1; - gameserver_send_to_all( client_id, prop, propsize, - k_nSteamNetworkingSend_Reliable ); + gameserver_send_to_all( client_id, prop, propsize, k_nSteamNetworkingSend_Reliable ); /* update database about this */ - db_request *call = db_alloc_request( - sizeof(struct db_set_username_thread_data) ); + db_request *call = db_alloc_request( sizeof(struct db_set_username_thread_data) ); + /* FIXME: Call can be NULL, crash possible. */ struct db_set_username_thread_data *inf = (void *)call->data; inf->steamid = client->steamid; - vg_strncpy( client->username, inf->username, - sizeof(inf->username), k_strncpy_always_add_null ); + vg_strncpy( client->username, inf->username, sizeof(inf->username), k_strncpy_always_add_null ); call->handler = gameserver_update_db_username; db_send_request( call ); } else if( tmp->inetmsg_id == k_inetmsg_playerframe ) { - gameserver_propogate_player_frame( client_id, - msg->m_pData, msg->m_cbSize ); + gameserver_propogate_player_frame( client_id, msg->m_pData, msg->m_cbSize ); + _gs_replay_save_frame( client_id, msg->m_pData, msg->m_cbSize ); } else if( tmp->inetmsg_id == k_inetmsg_playeritem ) { @@ -672,15 +647,13 @@ static void gameserver_rx_200_300( SteamNetworkingMessage_t *msg ) /* record */ if( item->type_index >= k_netmsg_playeritem_max ) { - vg_warn( "Client #%d invalid equip type %u\n", - client_id, (u32)item->type_index ); + vg_warn( "Client #%d invalid equip type %u\n", client_id, (u32)item->type_index ); return; } char *dest = client->items[ item->type_index ].uid; - network_msgstring( item->uid, msg->m_cbSize, sizeof(netmsg_playeritem), - dest, ADDON_UID_MAX ); + network_msgstring( item->uid, msg->m_cbSize, sizeof(netmsg_playeritem), dest, ADDON_UID_MAX ); vg_info( "Client #%d equiped: [%s] %s\n", client_id, @@ -696,8 +669,7 @@ static void gameserver_rx_200_300( SteamNetworkingMessage_t *msg ) netmsg_playeritem *prop = alloca(msg->m_cbSize); memcpy( prop, msg->m_pData, msg->m_cbSize ); prop->client = client_id; - gameserver_send_to_all( client_id, prop, msg->m_cbSize, - k_nSteamNetworkingSend_Reliable ); + gameserver_send_to_all( client_id, prop, msg->m_cbSize, k_nSteamNetworkingSend_Reliable ); } else if( tmp->inetmsg_id == k_inetmsg_chat ) { @@ -706,12 +678,20 @@ static void gameserver_rx_200_300( SteamNetworkingMessage_t *msg ) prop->inetmsg_id = k_inetmsg_chat; prop->client = client_id; - u32 l = network_msgstring( chat->msg, msg->m_cbSize, sizeof(netmsg_chat), - prop->msg, NETWORK_MAX_CHAT ); + u32 l = network_msgstring( chat->msg, msg->m_cbSize, sizeof(netmsg_chat), prop->msg, NETWORK_MAX_CHAT ); vg_info( "[%d]: %s\n", client_id, prop->msg ); - gameserver_send_to_all( client_id, prop, sizeof(netmsg_chat)+l+1, - k_nSteamNetworkingSend_Reliable ); + /* WARNING FIXME WARNING FIXME ------------------------------------------- */ + if( !strcmp( prop->msg, "save" ) ) + { + _gs_write_replay_to_disk( client_id, 1.0*60.0, "/tmp/server-replay.replay" ); + } + else if( !strcmp( prop->msg, "play" ) ) + { + _gs_test_replay( "/tmp/server-replay.replay" ); + } + + gameserver_send_to_all( client_id, prop, sizeof(netmsg_chat)+l+1, k_nSteamNetworkingSend_Reliable ); } else if( tmp->inetmsg_id == k_inetmsg_region ) { @@ -727,26 +707,25 @@ static void gameserver_rx_200_300( SteamNetworkingMessage_t *msg ) client->region, NETWORK_REGION_MAX ); client->region_flags = region->flags; - l = vg_strncpy( client->region, prop->loc, NETWORK_REGION_MAX, - k_strncpy_always_add_null ); + l = vg_strncpy( client->region, prop->loc, NETWORK_REGION_MAX, k_strncpy_always_add_null ); - gameserver_send_to_all( client_id, prop, sizeof(netmsg_region)+l+1, - k_nSteamNetworkingSend_Reliable ); + gameserver_send_to_all( client_id, prop, sizeof(netmsg_region)+l+1, k_nSteamNetworkingSend_Reliable ); vg_info( "client %d moved to region: %s\n", client_id, client->region ); } else { - vg_warn( "Unknown inetmsg_id recieved from client. (%u)\n", - tmp->inetmsg_id ); + vg_warn( "Unknown inetmsg_id recieved from client. (%u)\n", tmp->inetmsg_id ); } } static void gameserver_request_respond( enum request_status status, netmsg_request *res, vg_msg *body, - SteamNetworkingMessage_t *msg ){ + SteamNetworkingMessage_t *msg ) +{ int client_id = gameserver_conid( msg->m_conn ); u32 len = 0; - if( body ){ + if( body ) + { len = body->cur.co; vg_low( "[%d#%d] Response: %d\n", client_id, (i32)res->id, status ); vg_msg_print( body, len ); @@ -859,9 +838,9 @@ static void gameserver_process_user_request( db_request *db_req ) const char *endpoint = vg_msg_getkvstr( &data, "endpoint" ); - if( !endpoint ){ - gameserver_request_respond( k_request_status_invalid_endpoint, - res, NULL, msg ); + if( !endpoint ) + { + gameserver_request_respond( k_request_status_invalid_endpoint, res, NULL, msg ); return; } @@ -883,18 +862,19 @@ static void gameserver_process_user_request( db_request *db_req ) else gameserver_cat_table( &body, mod, route, week, "rows" ); - if( body.error != k_vg_msg_error_OK ){ - gameserver_request_respond( k_request_status_out_of_memory, - res, NULL, msg ); + if( body.error != k_vg_msg_error_OK ) + { + gameserver_request_respond( k_request_status_out_of_memory, res, NULL, msg ); return; } gameserver_request_respond( k_request_status_ok, res, &body, msg ); } - else if( !strcmp( endpoint, "setlap" ) ){ - if( client->steamid == k_steamid_max ){ - gameserver_request_respond( k_request_status_unauthorized, - res, NULL, msg ); + else if( !strcmp( endpoint, "setlap" ) ) + { + if( client->steamid == k_steamid_max ) + { + gameserver_request_respond( k_request_status_unauthorized, res, NULL, msg ); return; } @@ -907,17 +887,17 @@ static void gameserver_process_user_request( db_request *db_req ) u32 week = gameserver_get_current_week(); if( !db_get_highscore_table_name( mod, route, 0, alltime_table ) || - !db_get_highscore_table_name( mod, route, week, weekly_table ) ){ - gameserver_request_respond( k_request_status_out_of_memory, - res, NULL, msg ); + !db_get_highscore_table_name( mod, route, week, weekly_table ) ) + { + gameserver_request_respond( k_request_status_out_of_memory, res, NULL, msg ); return; } i32 centiseconds; vg_msg_getkvintg( &data, "time", k_vg_msg_i32, ¢iseconds, NULL ); - if( centiseconds < 5*100 ){ - gameserver_request_respond( k_request_status_client_error, - res, NULL, msg ); + if( centiseconds < 5*100 ) + { + gameserver_request_respond( k_request_status_client_error, res, NULL, msg ); return; } @@ -925,9 +905,9 @@ static void gameserver_process_user_request( db_request *db_req ) db_writeusertime( weekly_table, client->steamid, centiseconds, 1 ); gameserver_request_respond( k_request_status_ok, res, NULL, msg ); } - else{ - gameserver_request_respond( k_request_status_invalid_endpoint, - res, NULL, msg ); + else + { + gameserver_request_respond( k_request_status_invalid_endpoint, res, NULL, msg ); } } @@ -956,8 +936,8 @@ static void gameserver_rx_300_400( SteamNetworkingMessage_t *msg ) return; } - db_request *call = db_alloc_request( - sizeof(struct user_request_thread_data) ); + db_request *call = db_alloc_request( sizeof(struct user_request_thread_data) ); + /* FIXME: Call can be NULL, crash possible. */ struct user_request_thread_data *inf = (void *)call->data; inf->msg = msg; call->handler = gameserver_process_user_request; @@ -965,17 +945,16 @@ static void gameserver_rx_300_400( SteamNetworkingMessage_t *msg ) } else { - vg_warn( "Unknown inetmsg_id recieved from client. (%u)\n", - tmp->inetmsg_id ); + vg_warn( "Unknown inetmsg_id recieved from client. (%u)\n", tmp->inetmsg_id ); release_message( msg ); } } static void process_network_message( SteamNetworkingMessage_t *msg ) { - if( msg->m_cbSize < sizeof(netmsg_blank) ){ - vg_warn( "Discarding message (too small: %d)\n", - msg->m_cbSize ); + if( msg->m_cbSize < sizeof(netmsg_blank) ) + { + vg_warn( "Discarding message (too small: %d)\n", msg->m_cbSize ); return; } @@ -1013,7 +992,7 @@ static void poll_connections(void) { len = SteamAPI_ISteamNetworkingSockets_ReceiveMessagesOnPollGroup( hSteamNetworkingSockets, - gameserver.client_group, messages, vg_list_size(messages) ); + gameserver.client_group, messages, VG_ARRAY_LEN(messages) ); if( len <= 0 ) return; @@ -1025,11 +1004,15 @@ static void poll_connections(void) if( gameserver.loopback_test ) { - HSteamNetConnection conid = msg->m_conn; - msg->m_conn = 0; - msg->m_nUserData ++; - process_network_message( msg ); - msg->m_conn = conid; + netmsg_blank *tmp = msg->m_pData; + if( tmp->inetmsg_id != k_inetmsg_playerframe ) + { + HSteamNetConnection conid = msg->m_conn; + msg->m_conn = 0; + msg->m_nUserData ++; + process_network_message( msg ); + msg->m_conn = conid; + } } process_network_message( msg ); @@ -1037,11 +1020,15 @@ static void poll_connections(void) } } -static u64 seconds_to_server_ticks( double s ){ +u64 seconds_to_server_ticks( f64 s ) +{ return s / 0.01; } -int main( int argc, char *argv[] ){ +int main( int argc, char *argv[] ) +{ + vg_log_init(); + signal( SIGINT, inthandler ); signal( SIGQUIT, inthandler ); signal( SIGPIPE, SIG_IGN ); @@ -1049,11 +1036,14 @@ int main( int argc, char *argv[] ){ char *arg; while( vg_argp( argc, argv ) ) { - if( vg_long_opt( "noauth" ) ) + if( vg_long_opt( "noauth", "Disable server authentication" ) ) gameserver.auth_mode = eServerModeNoAuthentication; - if( vg_long_opt( "loopback" ) ) + if( vg_long_opt( "loopback", "Development flag" ) ) gameserver.loopback_test = 1; + + if( vg_long_opt( "replay-info", "Print replay info periodically" ) ) + _gs_replay.print_info = 1; } vg_set_mem_quota( 80*1024*1024 ); @@ -1063,18 +1053,16 @@ int main( int argc, char *argv[] ){ /* steamworks init * --------------------------------------------------------------- */ steamworks_ensure_txt( "2103940" ); - if( gameserver.auth_mode == eServerModeAuthentication ){ - if( !vg_load_steam_symetric_key( "application_key", - gameserver.app_symmetric_key )){ + if( gameserver.auth_mode == eServerModeAuthentication ) + { + if( !vg_load_steam_symetric_key( "application_key", gameserver.app_symmetric_key )) return 0; - } } - else{ + else vg_warn( "Running without user authentication.\n" ); - } - if( !SteamGameServer_Init( 0, NETWORK_PORT, NETWORK_PORT+1, - gameserver.auth_mode, "1.0.0.0" ) ){ + if( !SteamGameServer_Init( 0, NETWORK_PORT, NETWORK_PORT+1, gameserver.auth_mode, "1.0.0.0" ) ) + { vg_error( "SteamGameServer_Init failed\n" ); return 0; } @@ -1084,12 +1072,10 @@ int main( int argc, char *argv[] ){ SteamAPI_ManualDispatch_Init(); HSteamPipe hsteampipe = SteamGameServer_GetHSteamPipe(); - hSteamNetworkingSockets = - SteamAPI_SteamGameServerNetworkingSockets_SteamAPI(); + hSteamNetworkingSockets = SteamAPI_SteamGameServerNetworkingSockets_SteamAPI(); steam_register_callback( k_iSteamNetAuthenticationStatus, on_auth_status ); - steam_register_callback( k_iSteamNetConnectionStatusChangedCallBack, - on_connect_status ); + steam_register_callback( k_iSteamNetConnectionStatusChangedCallBack, on_connect_status ); vg_success( "Steamworks API running\n" ); steamworks_event_loop( hsteampipe ); @@ -1102,30 +1088,25 @@ int main( int argc, char *argv[] ){ SteamAPI_SteamNetworkingIPAddr_Clear( &localAddr ); localAddr.m_port = NETWORK_PORT; - listener = SteamAPI_ISteamNetworkingSockets_CreateListenSocketIP( - hSteamNetworkingSockets, &localAddr, 0, NULL ); - gameserver.client_group = SteamAPI_ISteamNetworkingSockets_CreatePollGroup( - hSteamNetworkingSockets ); - - u64 server_ticks = 8000, - last_record_save = 8000, - last_scoreboard_gen = 0; + listener = SteamAPI_ISteamNetworkingSockets_CreateListenSocketIP( hSteamNetworkingSockets, &localAddr, 0, NULL ); + gameserver.client_group = SteamAPI_ISteamNetworkingSockets_CreatePollGroup( hSteamNetworkingSockets ); + gameserver.ticks = seconds_to_server_ticks( 30.0 * 60.0 ); - while( !sig_stop ){ + while( !sig_stop ) + { steamworks_event_loop( hsteampipe ); poll_connections(); + _gs_replay_server_tick(); usleep(10000); - server_ticks ++; + gameserver.ticks ++; if( db_killed() ) break; } - SteamAPI_ISteamNetworkingSockets_DestroyPollGroup( hSteamNetworkingSockets, - gameserver.client_group ); - SteamAPI_ISteamNetworkingSockets_CloseListenSocket( - hSteamNetworkingSockets, listener ); + SteamAPI_ISteamNetworkingSockets_DestroyPollGroup( hSteamNetworkingSockets, gameserver.client_group ); + SteamAPI_ISteamNetworkingSockets_CloseListenSocket( hSteamNetworkingSockets, listener ); vg_info( "Shutting down\n..." ); SteamGameServer_Shutdown(); @@ -1134,3 +1115,5 @@ int main( int argc, char *argv[] ){ return 0; } + +#include "gameserver_replay.c" diff --git a/src/gameserver.h b/src/gameserver.h index a03b1a0..0b2620f 100644 --- a/src/gameserver.h +++ b/src/gameserver.h @@ -44,9 +44,12 @@ struct { u8 app_symmetric_key[ k_nSteamEncryptedAppTicketSymmetricKeyLen ]; bool loopback_test; + u64 ticks; } static gameserver = { .auth_mode = eServerModeAuthentication }; static ISteamNetworkingSockets *hSteamNetworkingSockets = NULL; + +u64 seconds_to_server_ticks( f64 s ); diff --git a/src/gameserver_db.h b/src/gameserver_db.h index fe39931..f6abfd3 100644 --- a/src/gameserver_db.h +++ b/src/gameserver_db.h @@ -34,9 +34,10 @@ static database; /* * Log the error code (or carry on if its OK). */ -static void log_sqlite3( int code ){ +static void log_sqlite3( int code ) +{ if( code == SQLITE_OK ) return; - vg_print_backtrace(); + //vg_print_backtrace(); vg_error( "sqlite3(%d): %s\n", code, sqlite3_errstr(code) ); #ifdef DB_CRASH_ON_SQLITE_ERROR @@ -300,10 +301,12 @@ static void *db_loop(void *_){ /* * Request processing loop */ - while(1){ + while(1) + { pthread_mutex_lock( &database.mux ); - if( database.kill ){ + if( database.kill ) + { pthread_mutex_unlock( &database.mux ); _db_thread_end(); break; @@ -311,16 +314,12 @@ static void *db_loop(void *_){ u32 processed = 0; - for( u32 i=0; i<16; i ++ ){ - db_request *req = NULL; - if( database.queue.tail ){ - req = (db_request *)database.queue.tail->data; - pthread_mutex_unlock( &database.mux ); - } - else{ - pthread_mutex_unlock( &database.mux ); + for( u32 i=0; i<16; i ++ ) + { + db_request *req = vg_queue_tail_data( &database.queue ); + if( !req ) break; - } + pthread_mutex_unlock( &database.mux ); req->handler( req ); processed ++; @@ -329,6 +328,8 @@ static void *db_loop(void *_){ vg_queue_pop( &database.queue ); } + pthread_mutex_unlock( &database.mux ); + if( processed ) vg_low( "Processed %u database requests.\n", processed ); @@ -342,9 +343,9 @@ static void *db_loop(void *_){ /* * Create database connection and users table */ -static int db_init(void){ - database.queue.buffer = - (u8 *)vg_linear_alloc( vg_mem.rtmemory, DB_REQUEST_BUFFER_SIZE ), +static int db_init(void) +{ + database.queue.buffer = (u8 *)vg_linear_alloc( vg_mem.rtmemory, DB_REQUEST_BUFFER_SIZE ), database.queue.size = DB_REQUEST_BUFFER_SIZE; if( pthread_mutex_init( &database.mux, NULL ) ) @@ -356,42 +357,49 @@ static int db_init(void){ return 1; } -static int db_killed(void){ +static int db_killed(void) +{ pthread_mutex_lock( &database.mux ); int result = database.kill; pthread_mutex_unlock( &database.mux ); return result; } -static void db_kill(void){ +static void db_kill(void) +{ pthread_mutex_lock( &database.mux ); database.kill = 1; pthread_mutex_unlock( &database.mux ); pthread_join( database.thread, NULL ); } -static void db_free(void){ +static void db_free(void) +{ pthread_mutex_destroy( &database.mux ); } -static db_request *db_alloc_request( u32 size ){ +static db_request *db_alloc_request( u32 size ) +{ u32 total = sizeof(db_request) + size; pthread_mutex_lock( &database.mux ); - vg_queue_frame *frame = vg_queue_alloc( &database.queue, total ); - if( frame ){ - db_request *req = (db_request *)frame->data; + u32 queue_offset; + db_request *req = vg_queue_alloc( &database.queue, total, NULL ); + if( req ) + { req->size = size; return req; } - else { + else + { pthread_mutex_unlock( &database.mux ); return NULL; } } -static void db_send_request( db_request *request ){ +static void db_send_request( db_request *request ) +{ pthread_mutex_unlock( &database.mux ); } diff --git a/src/gameserver_replay.c b/src/gameserver_replay.c new file mode 100644 index 0000000..e3eea44 --- /dev/null +++ b/src/gameserver_replay.c @@ -0,0 +1,196 @@ +#include "gameserver.h" +#include "gameserver_replay.h" + +struct _gs_replay _gs_replay; + +void _gs_replay_server_tick(void) +{ + _gs_replay.print_ticker ++; + if( _gs_replay.print_ticker > seconds_to_server_ticks(5) ) + { + _gs_replay.print_ticker = 0; + + vg_info( "------------ Replay info -----------\n" ); + + for( u32 i=0; iframe_count ) + vg_info( "Player %u: %u frames\n", i, replay->frame_count ); + } + } + + static u64 divider = 0; + divider ++; + + if( divider >= 10 ) + { + if( _gs_replay.playback_buffer ) + { + u16 frame_size = *(u16 *)(_gs_replay.playback_buffer + _gs_replay.playback_buffer_offset); + _gs_replay.playback_buffer_offset += 2; + + struct netmsg_playerframe *frame = _gs_replay.playback_buffer + _gs_replay.playback_buffer_offset; + frame->client = 1; + + gameserver_send_to_client( 0, frame, frame_size, k_nSteamNetworkingSend_Unreliable ); + _gs_replay.playback_buffer_offset += frame_size; + + if( _gs_replay.playback_buffer_offset >= _gs_replay.playback_buffer_len ) + { + vg_success( "End replay playback\n" ); + _gs_replay.playback_buffer = NULL; + _gs_replay.playback_buffer_len = 0; + _gs_replay.playback_buffer_offset = 0; + } + } + + divider = 0; + } +} + +void _gs_test_replay( const char *path ) +{ + free( _gs_replay.playback_buffer ); + _gs_replay.playback_buffer = vg_file_read( NULL, path, &_gs_replay.playback_buffer_len ); + _gs_replay.playback_buffer_offset = 0; + vg_info( "Test replay\n" ); +} + +void _gs_write_replay_to_disk( u8 client_id, f64 server_duration, const char *path ) +{ + u64 min_ticks = gameserver.ticks - seconds_to_server_ticks( server_duration ); + + /* FIXME--- this is to ignore the loopback */ + if( client_id == 1 ) + return; + + gs_replay *replay = &_gs_replay.replays[ client_id ]; + if( replay->frame_count == 0 ) + { + vg_error( "Replay frame count is 0, nothing to save..\n" ); + return; + } + + /* FIXME: Should we do this on another thread */ + FILE *fp = fopen( path, "w" ); + if( !fp ) + { + vg_error( "Failed to open '%s' for writing\n", fp ); + return; + } + + u32 frame_id = replay->head_item; + while(1) + { + gs_replay_frame *frame = vg_queue_data( &_gs_replay.ring_buffer, frame_id ); + if( frame->tick_recorded < min_ticks ) + break; + + if( frame->previous_queue_item == VG_MEM_QUEUE_INVALID ) + break; + + frame_id = frame->previous_queue_item; + } + + u32 frame_count = 0; + while(1) + { + gs_replay_frame *frame = vg_queue_data( &_gs_replay.ring_buffer, frame_id ); + fwrite( &frame->frame_size, sizeof(frame->frame_size), 1, fp ); + fwrite( &frame->network_frame, frame->frame_size, 1, fp ); + frame_count ++; + + frame_id = frame->next_queue_item; + if( frame_id == VG_MEM_QUEUE_INVALID ) + break; + } + + fclose( fp ); + vg_success( "Written %u frames to disk.\n", frame_count ); +} + +void _gs_replay_save_frame( u8 client_id, netmsg_playerframe *frame, u32 frame_size ) +{ + vg_queue *ring = &_gs_replay.ring_buffer; + gs_replay *dest_replay = &_gs_replay.replays[ client_id ]; + + u32 total_size = sizeof( gs_replay_frame ) + frame_size; + + u32 dest_item_id; + gs_replay_frame *dest_frame = vg_queue_alloc( ring, total_size, &dest_item_id ); + if( !dest_frame ) + { + /* policy 1: free if 4 minutes passed in server ticks + * 2: player left the server + */ + u64 min_ticks = gameserver.ticks - seconds_to_server_ticks( 4.0 * 60.0 ); + + for( u32 i=0; i<64; i ++ ) + { + gs_replay_frame *free_frame = vg_queue_tail_data( ring ); + if( !free_frame ) + break; + + /* blocked by valid frame */ + if( (free_frame->tick_recorded >= min_ticks) && (free_frame->network_frame.client!=255) ) + break; + + /* clean refs */ + if( free_frame->next_queue_item != VG_MEM_QUEUE_INVALID ) + { + gs_replay_frame *next_frame = vg_queue_data( ring, free_frame->next_queue_item ); + next_frame->previous_queue_item = VG_MEM_QUEUE_INVALID; + } + if( free_frame->network_frame.client != 255 ) + { + gs_replay *free_replay = &_gs_replay.replays[ free_frame->network_frame.client ]; + free_replay->frame_count --; + if( free_replay->frame_count == 0 ) + free_replay->head_item = VG_MEM_QUEUE_INVALID; + } + + vg_queue_pop( ring ); + } + + dest_frame = vg_queue_alloc( ring, total_size, &dest_item_id ); + if( !dest_frame ) + { + /* policy 3: grow (double size) */ + u32 cur_size = _gs_replay.ring_buffer.size; + if( cur_size < 1024 ) + cur_size = 1024; + u32 new_size = cur_size * 2; + vg_low( "growing replay buffer (%u bytes)\n", new_size ); + + vg_queue new_q = { .buffer = malloc( new_size ), .size = new_size }; + vg_queue_copy_upgrade( ring, &new_q ); + free( ring->buffer ); + *ring = new_q; + + dest_frame = vg_queue_alloc( ring, total_size, &dest_item_id ); + VG_ASSERT( dest_frame ); + } + } + + memcpy( &dest_frame->network_frame, frame, frame_size ); + dest_frame->network_frame.client = client_id; + dest_frame->tick_recorded = gameserver.ticks; + dest_frame->frame_size = frame_size; + dest_frame->_ = 0; + dest_frame->next_queue_item = VG_MEM_QUEUE_INVALID; + + if( dest_replay->frame_count ) + { + dest_frame->previous_queue_item = dest_replay->head_item; + gs_replay_frame *prev_frame = vg_queue_data( ring, dest_replay->head_item ); + prev_frame->next_queue_item = dest_item_id; + } + else + { + dest_frame->previous_queue_item = VG_MEM_QUEUE_INVALID; + } + + dest_replay->head_item = dest_item_id; + dest_replay->frame_count ++; +} diff --git a/src/gameserver_replay.h b/src/gameserver_replay.h new file mode 100644 index 0000000..e11059e --- /dev/null +++ b/src/gameserver_replay.h @@ -0,0 +1,39 @@ +#pragma once +#include "network_msg.h" + +typedef struct gs_replay_frame gs_replay_frame; +typedef struct gs_replay gs_replay; + +struct gs_replay_frame +{ + u64 tick_recorded; + u32 previous_queue_item; + u32 next_queue_item; + u16 frame_size; + u16 _; + struct netmsg_playerframe network_frame; +}; + +struct _gs_replay +{ + struct gs_replay + { + u32 frame_count, + head_item; + } + replays[ NETWORK_MAX_PLAYERS ]; + struct vg_queue ring_buffer; + + bool print_info; + u64 print_ticker; + + u32 playback_buffer_len, + playback_buffer_offset; + void *playback_buffer; +} +extern _gs_replay; + +void _gs_replay_server_tick(void); +void _gs_replay_save_frame( u8 client_id, netmsg_playerframe *frame, u32 frame_size ); +void _gs_write_replay_to_disk( u8 client_id, f64 server_duration, const char *path ); +void _gs_test_replay( const char *path ); diff --git a/src/network.c b/src/network.c index 06e4d57..cc71717 100644 --- a/src/network.c +++ b/src/network.c @@ -200,9 +200,10 @@ void network_request_scoreboard( const char *mod_uid, network_send_request( req, &data, network_scoreboard_callback, userdata ); } -static void network_publish_callback( netmsg_request *res, vg_msg *body, - u64 userdata ){ - if( res->status != k_request_status_ok ){ +static void network_publish_callback( netmsg_request *res, vg_msg *body, u64 userdata ) +{ + if( res->status != k_request_status_ok ) + { vg_error( "Publish laptime, server error #%d\n", (i32)res->status ); } } diff --git a/src/network_common.h b/src/network_common.h index ca46e47..b2ecd56 100644 --- a/src/network_common.h +++ b/src/network_common.h @@ -27,13 +27,12 @@ static u32 network_msgstring( const char *src, return vg_strncpy( src, buf, string_len, k_strncpy_always_add_null ); } -static u32 network_pair_index( u32 _a, u32 _b ){ +static u32 network_pair_index( u32 _a, u32 _b ) +{ const u32 N = NETWORK_MAX_PLAYERS; if( !((_a != _b) && (_a