typedef struct highscore_record highscore_record;
typedef struct highscore_track_table highscore_track_table;
typedef struct highscore_database highscore_database;
+typedef struct highscore_playerinfo highscore_playerinfo;
#pragma pack(push,1)
+
+struct highscore_playerinfo
+{
+ char nickname[16];
+ u64 playerid;
+
+ union
+ {
+ aatree_pool_node aapn;
+ aatree_node aa_playerid;
+ };
+};
+
struct highscore_record
{
u16 trackid, points, time, reserved0;
u64 playerid;
u32 datetime;
-
- u32 reserved[7];
+ u32 reserved1;
union
{
}
aa;
- struct
- {
- /* TODO pool allocator */
- u32 next, prev;
- }
- pool;
+ aatree_pool_node pool;
};
};
{
highscore_track_table tracks[ 128 ];
- aatree_ptr pool_head;
- u32 reserved[63];
+ aatree_ptr pool_head, playerinfo_head;
+ u32 entry_capacity,
+ playerinfo_capacity, playerinfo_root;
+
+ u32 reserved[59];
};
#pragma pack(pop)
aainfo_points,
aainfo_time,
aainfo_playerid,
- aainfo_datetime;
+ aainfo_datetime,
+ aainfo_playerinfo_playerid,
+ aainfo_playerinfo;
- void *data;
+ void *data, *playerinfo_data;
}
highscore_system;
return pa->playerid < pb->playerid? -1: 1;
}
-static int highscores_init( u32 pool_size )
+static int highscore_cmp_playerinfo_playerid( void *a, void *b )
{
- struct highscore_system *sys = &highscore_system;
+ highscore_playerinfo *pa = a, *pb = b;
+ if( pa->playerid == pb->playerid ) return 0;
+ return pa->playerid < pb->playerid? -1: 1;
+}
- size_t requested_mem = pool_size * sizeof(highscore_record);
- sys->data = malloc( requested_mem );
+static void *highscore_malloc( u32 count, u32 size )
+{
+ size_t requested_mem = size * count;
+ void *data = malloc( requested_mem );
requested_mem /= 1024;
requested_mem /= 1024;
- if( !highscore_system.data )
+ if( !data )
{
- vg_error( "Could not allocated %dmb of memory for database\n",
- requested_mem );
- return 0;
+ vg_error( "Could not allocated %dmb of memory\n", requested_mem );
+ return NULL;
}
else
- vg_success( "Allocated %dmb for database\n", requested_mem );
+ vg_success( "Allocated %dmb for %u records\n", requested_mem, count );
+
+ return data;
+}
+
+static void highscores_free(void)
+{
+ free( highscore_system.data );
+ free( highscore_system.playerinfo_data );
+}
+
+static int highscores_init( u32 pool_size, u32 playerinfo_pool_size )
+{
+ struct highscore_system *sys = &highscore_system;
+
+ sys->data = highscore_malloc( pool_size, sizeof(highscore_record) );
+ if( !sys->data ) return 0;
+
+ sys->playerinfo_data =
+ highscore_malloc( playerinfo_pool_size, sizeof(highscore_playerinfo));
+ if( !sys->playerinfo_data )
+ {
+ free( sys->data );
+ return 0;
+ }
/* This is ugly.. too bad! */
sys->aainfo.base = highscore_system.data;
sys->aainfo_playerid.offset = offsetof(highscore_record,aa.playerid);
sys->aainfo_playerid.p_cmp = highscore_cmp_playerid;
+ sys->aainfo_playerinfo_playerid.base = highscore_system.playerinfo_data;
+ sys->aainfo_playerinfo_playerid.stride = sizeof(highscore_playerinfo);
+ sys->aainfo_playerinfo_playerid.offset =
+ offsetof(highscore_playerinfo,aa_playerid);
+ sys->aainfo_playerinfo_playerid.p_cmp = highscore_cmp_playerinfo_playerid;
+
+ sys->aainfo_playerinfo.base = highscore_system.playerinfo_data;
+ sys->aainfo_playerinfo.stride = sizeof(highscore_playerinfo);
+ sys->aainfo_playerinfo.offset = offsetof(highscore_playerinfo,aapn);
+ sys->aainfo_playerinfo.p_cmp = NULL;
- /* TODO: Load from disk if avalible */
- if( 0 )
+ FILE *fp = fopen( ".aadb", "rb" );
+ if( fp )
{
+ vg_info( "Loading existing database\n" );
+
+ u64 count = fread( &sys->dbheader, sizeof(highscore_database), 1, fp );
+
+ if( count != 1 )
+ {
+ vg_error( "Unexpected EOF reading database header\n" );
+
+ highscores_free();
+ return 0;
+ }
+
+ count = fread( sys->data, sizeof(highscore_record), pool_size, fp );
+ if( count != pool_size )
+ {
+ vg_error( "Unexpected EOF reading database contents;"
+ " %lu records of %u were read\n", count, pool_size );
+
+ highscores_free();
+ return 0;
+ }
+ count = fread( sys->playerinfo_data, sizeof(highscore_playerinfo),
+ playerinfo_pool_size, fp );
+ if( count != playerinfo_pool_size )
+ {
+ vg_error( "Unexpected EOF reading playerinfo contents;"
+ " %lu records of %u were read\n", count,
+ playerinfo_pool_size );
+
+ highscores_free();
+ return 0;
+ }
+
+ fclose( fp );
}
else
{
+ vg_log( "No existing database found (.aadb)\n" );
vg_info( "Initializing database nodes\n" );
+ memset( &sys->dbheader, 0, sizeof(highscore_database) );
+
sys->dbheader.pool_head = aatree_init_pool( &sys->aainfo, pool_size );
+ sys->dbheader.entry_capacity = pool_size;
for( int i=0; i<vg_list_size(sys->dbheader.tracks); i++ )
{
table->root_time = AATREE_PTR_NIL;
table->root_datetime = AATREE_PTR_NIL;
}
+
+ /* Initialize secondary db */
+ sys->dbheader.playerinfo_head = aatree_init_pool(
+ &sys->aainfo_playerinfo,
+ playerinfo_pool_size );
+ sys->dbheader.playerinfo_capacity = playerinfo_pool_size;
+ sys->dbheader.playerinfo_root = AATREE_PTR_NIL;
}
return 1;
}
-static void highscores_free(void)
+static int highscores_serialize_all(void)
{
- free( highscore_system.data );
+ struct highscore_system *sys = &highscore_system;
+ vg_info( "Serializing database\n" );
+
+ FILE *fp = fopen( ".aadb", "wb" );
+
+ if( !fp )
+ {
+ vg_error( "Could not open .aadb\n" );
+ return 0;
+ }
+
+ fwrite( &sys->dbheader, sizeof(highscore_database), 1, fp );
+ fwrite( sys->data, sizeof(highscore_record),
+ sys->dbheader.entry_capacity, fp );
+ fwrite( sys->playerinfo_data, sizeof(highscore_playerinfo),
+ sys->dbheader.playerinfo_capacity, fp );
+
+ fclose( fp );
+ return 1;
}
static aatree_ptr highscores_push_record( highscore_record *record )
if( existing != AATREE_PTR_NIL )
{
+ highscore_record *crecord = aatree_get_data( &sys->aainfo_playerid,
+ existing );
+
+ if( crecord->time < record->time ||
+ (crecord->time == record->time && crecord->points > record->points))
+ {
+ vg_log( "Not overwriting better score\n" );
+ return existing;
+ }
+
vg_log( "Freeing existing record for player %lu\n", record->playerid );
table->root_playerid = aatree_del( &sys->aainfo_playerid, existing );
table->root_datetime = aatree_del( &sys->aainfo_datetime, existing );
aatree_pool_alloc( &sys->aainfo, &sys->dbheader.pool_head );
if( index == AATREE_PTR_NIL )
+ {
+ vg_error( "Database records are over capacity!\n" );
return index;
+ }
highscore_record *dst = aatree_get_data( &sys->aainfo, index );
memset( dst, 0, sizeof(highscore_record) );
return index;
}
+static aatree_ptr highscore_set_user_nickname( u64 steamid, char nick[16] )
+{
+ vg_log( "Updating %lu's nickname\n", steamid );
+
+ struct highscore_system *sys = &highscore_system;
+
+ highscore_playerinfo temp;
+ temp.playerid = steamid;
+
+ aatree_ptr record = aatree_find( &sys->aainfo_playerinfo_playerid,
+ sys->dbheader.playerinfo_root,
+ &temp );
+ highscore_playerinfo *info;
+
+ if( record != AATREE_PTR_NIL )
+ {
+ info = aatree_get_data( &sys->aainfo_playerinfo, record );
+ }
+ else
+ {
+ record = aatree_pool_alloc( &sys->aainfo_playerinfo,
+ &sys->dbheader.playerinfo_head );
+
+ if( record == AATREE_PTR_NIL )
+ {
+ vg_error( "Player info database is over capacity!\n" );
+ return AATREE_PTR_NIL;
+ }
+
+ info = aatree_get_data( &sys->aainfo_playerinfo, record );
+ memset( info, 0, sizeof(highscore_playerinfo) );
+
+ info->playerid = steamid;
+ sys->dbheader.playerinfo_root = aatree_insert(
+ &sys->aainfo_playerinfo_playerid,
+ sys->dbheader.playerinfo_root,
+ record );
+ }
+
+ for( int i=0; i<16; i++ )
+ info->nickname[i] = nick[i];
+
+ return AATREE_PTR_NIL;
+}
+
static void _highscore_showtime( void *data )
{
highscore_record *record = data;
printf( "%hu", record->time );
}
+static void _highscore_showname( void *data )
+{
+ char namebuf[17];
+ namebuf[16] = '\0';
+
+ highscore_playerinfo *info = data;
+ for( int i=0; i<16; i++ )
+ namebuf[i] = info->nickname[i];
+
+ printf( " %lu %s", info->playerid, namebuf );
+}
+
static void highscores_print_track( u32 trackid, u32 count )
{
struct highscore_system *sys = &highscore_system;
highscore_track_table *table = &sys->dbheader.tracks[ trackid ];
aatree_ptr it = aatree_kth( &sys->aainfo_time, table->root_time, 0 );
- vg_info( "Highscores, top %u records for track %u\n", count, trackid );
- vg_info( "==============================================\n" );
-
+ vg_info( "Highscores: top %u fastest records for track %u\n", count, trackid );
+ vg_info( "================================================\n" );
+ vg_info( "%3s| %16s | %5s | %5s | %s\n", "#", "Player", "Time", "Score",
+ "TrackID" );
+ vg_info( "================================================\n" );
int i=0;
while( it != AATREE_PTR_NIL && i < 10 )
{
highscore_record *record = aatree_get_data( &sys->aainfo_time, it );
- vg_info( " [%d]: player(%lu), time: %hu, score: %hu, track:%hu\n",
- i+1, record->playerid, record->time, record->points,
+
+ highscore_playerinfo temp;
+ temp.playerid = record->playerid;
+
+ aatree_ptr info_ptr = aatree_find( &sys->aainfo_playerinfo_playerid,
+ sys->dbheader.playerinfo_root,
+ &temp );
+
+ char namebuf[17];
+ if( info_ptr == AATREE_PTR_NIL )
+ snprintf( namebuf, 16, "[%lu]", record->playerid );
+ else
+ {
+ highscore_playerinfo *inf = aatree_get_data(
+ &sys->aainfo_playerinfo_playerid, info_ptr );
+
+ for( int i=0; i<16; i++ )
+ namebuf[i] = inf->nickname[i];
+ namebuf[16] = '\0';
+ }
+
+ vg_info( "%3d| %16s %5hu %5hu %3hu\n",
+ i+1, namebuf, record->time, record->points,
record->trackid );
i++;
it = aatree_next( &sys->aainfo_time, it );
}
- vg_info( "==============================================\n" );
+ vg_info( "================================================\n" );
}
#endif /* HIGHSCORES_H */
};
enum{ k_inetmsg_blank = 0 };
+typedef struct netmsg_auth netmsg_auth;
+struct netmsg_auth
+{
+ u32 inetmsg_id;
+
+ u32 ticket_length;
+ u8 ticket[];
+};
+enum{ k_inetmsg_auth = 1 };
+
typedef struct netmsg_scores_request netmsg_scores_request;
struct netmsg_scores_request
{
u32 inetmsg_id;
};
-enum{ k_inetmsg_scores_request = 1 };
+enum{ k_inetmsg_scores_request = 2 };
-typedef struct netmsg_scores_info netmsg_scores_info;
-struct netmsg_scores_info
+typedef struct netmsg_set_score netmsg_set_score;
+struct netmsg_set_score
{
u32 inetmsg_id;
-
+
u32 record_count;
struct netmsg_score_record
{
u32 trackid;
-
- struct netmsg_score_entry
- {
- u64 steamid64;
- u16 points, time;
- }
- top10[10];
+ u64 playerid;
+ u16 points, time;
}
- scores[];
+ records[];
};
-enum{ k_inetmsg_scores_info = 2 };
+enum{ k_inetmsg_set_score = 3 };
-typedef struct netmsg_auth netmsg_auth;
-struct netmsg_auth
+typedef struct netmsg_set_nickname netmsg_set_nickname;
+struct netmsg_set_nickname
{
u32 inetmsg_id;
-
- u32 ticket_length;
- u8 ticket[];
+ char nickname[16];
};
-enum{ k_inetmsg_auth = 3 };
+enum{ k_inetmsg_set_nickname = 4 };
#pragma pack(pop)
#endif /* NETWORK_MSG_H */
#include "vg/vg_steam_http.h"
#include "vg/vg_steam_auth.h"
#include "network_msg.h"
+#include "highscores.h"
void *hSteamHTTP,
*hSteamNetworkingSockets;
SteamAPI_ISteamHTTP_ReleaseHTTPRequest( hSteamHTTP, result->m_hRequest );
}
+static u64_steamid get_connection_authsteamid( SteamNetworkingMessage_t *msg )
+{
+ i64 userdata = SteamAPI_ISteamNetworkingSockets_GetConnectionUserData(
+ hSteamNetworkingSockets, msg->m_conn );
+
+ return *((u64_steamid *)&userdata);
+}
+
+static void set_connection_authsteamid(HSteamNetConnection con, u64_steamid id)
+{
+ i64 userdata = *((i64 *)&id);
+
+ SteamAPI_ISteamNetworkingSockets_SetConnectionUserData(
+ hSteamNetworkingSockets, con, userdata );
+}
+
static void new_client_connecting( HSteamNetConnection client )
{
EResult accept_status = SteamAPI_ISteamNetworkingSockets_AcceptConnection(
SteamAPI_ISteamNetworkingSockets_SetConnectionPollGroup(
hSteamNetworkingSockets,
client, client_pollgroup );
+
+ set_connection_authsteamid( client, 0 );
}
else
{
}
}
+static void on_inet_auth( SteamNetworkingMessage_t *msg )
+{
+ if( get_connection_authsteamid( msg ) )
+ {
+ vg_warn( "Already authorized this user but app ticket was sent"
+ " again (%u)\n", msg->m_conn );
+ return;
+ }
+
+ vg_log( "Attempting to verify user\n" );
+
+ if( msg->m_cbSize < sizeof(netmsg_auth) )
+ {
+ vg_error( "Malformed auth ticket, too small (%u)\n", msg->m_conn );
+ return;
+ }
+
+ 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 );
+ return;
+ }
+
+ u8 decrypted[1024];
+ u32 ticket_len;
+
+ int success = SteamEncryptedAppTicket_BDecryptTicket(
+ auth->ticket, auth->ticket_length, decrypted,
+ &ticket_len, steam_symetric_key,
+ k_nSteamEncryptedAppTicketSymmetricKeyLen );
+
+ if( !success )
+ {
+ vg_error( "Failed to decrypt users ticket (client %u)\n", msg->m_conn );
+
+ SteamAPI_ISteamNetworkingSockets_CloseConnection(
+ hSteamNetworkingSockets,
+ msg->m_conn, 0, NULL, 0 );
+ return;
+ }
+
+ if( SteamEncryptedAppTicket_GetTicketIssueTime( decrypted, ticket_len ))
+ {
+ RTime32 ctime = time(NULL),
+ tickettime = SteamEncryptedAppTicket_GetTicketIssueTime(
+ decrypted, ticket_len ),
+ expiretime = tickettime + 24*3*60*60;
+
+ if( ctime > expiretime )
+ {
+ vg_error( "Ticket expired (client %u)\n", msg->m_conn );
+
+ /* TODO: Send expired information */
+ SteamAPI_ISteamNetworkingSockets_CloseConnection(
+ hSteamNetworkingSockets,
+ msg->m_conn, 0, NULL, 0 );
+ return;
+ }
+ }
+
+ CSteamID steamid;
+ SteamEncryptedAppTicket_GetTicketSteamID( decrypted, ticket_len, &steamid );
+ vg_success( "User is authenticated! steamid %lu (%u)\n",
+ steamid.m_unAll64Bits, msg->m_conn );
+
+ set_connection_authsteamid( msg->m_conn, steamid.m_unAll64Bits );
+}
+
+static int inet_require_auth( SteamNetworkingMessage_t *msg )
+{
+ if( !get_connection_authsteamid( msg ) )
+ {
+ vg_warn( "Unauthorized request! Disconnecting client: %u\n",
+ msg->m_conn );
+
+ SteamAPI_ISteamNetworkingSockets_CloseConnection(
+ hSteamNetworkingSockets,
+ msg->m_conn, 0, NULL, 0 );
+
+ return 0;
+ }
+ else return 1;
+}
+
+static void on_inet_score_request( SteamNetworkingMessage_t *msg )
+{
+#if 0
+ if( get_connection_authsteamid( msg ) )
+ {
+ vg_log( "Recieved score request, sending records. (id: %u)\n",
+ msg->m_conn );
+
+ /* Send back current scores */
+ u32 data_size = sizeof(netmsg_scores_info) +
+ 0*sizeof(struct netmsg_score_record);
+ netmsg_scores_info *return_info = malloc( data_size );
+
+ return_info->inetmsg_id = k_inetmsg_scores_info;
+ return_info->record_count = 0;
+
+ SteamAPI_ISteamNetworkingSockets_SendMessageToConnection(
+ hSteamNetworkingSockets, msg->m_conn,
+ return_info, data_size,
+ k_nSteamNetworkingSend_Reliable, NULL );
+ }
+ else
+ {
+ vg_warn( "Unauthorized request! Disconnecting client: %u\n",
+ msg->m_conn );
+
+ SteamAPI_ISteamNetworkingSockets_CloseConnection(
+ hSteamNetworkingSockets,
+ msg->m_conn, 0, NULL, 0 );
+ }
+#endif
+}
+
+static void on_inet_set_nickname( SteamNetworkingMessage_t *msg )
+{
+ if(!inet_require_auth(msg)) return;
+
+ u64_steamid steamid = get_connection_authsteamid(msg);
+ netmsg_set_nickname *setnick = msg->m_pData;
+ if( msg->m_cbSize < sizeof(netmsg_set_nickname) )
+ {
+ vg_warn( "Invalid nickname request from client: %u, steamid: %lu\n",
+ msg->m_conn, steamid );
+ return;
+ }
+
+ highscore_set_user_nickname( steamid, setnick->nickname );
+}
+
+static void on_inet_set_score( SteamNetworkingMessage_t *msg )
+{
+ if(!inet_require_auth(msg)) return;
+
+ u64_steamid steamid = get_connection_authsteamid(msg);
+
+ if( msg->m_cbSize < sizeof(netmsg_set_score) )
+ {
+ vg_warn( "Invalid set score post from client: %u, steamid: %lu\n",
+ msg->m_conn, steamid );
+ return;
+ }
+
+ netmsg_set_score *info = msg->m_pData;
+
+ if( msg->m_cbSize < sizeof(netmsg_set_score) +
+ sizeof(struct netmsg_score_record)*info->record_count )
+ {
+ vg_warn( "Malformed set score post from client: %u, steamid: %lu\n",
+ msg->m_conn, steamid );
+ return;
+ }
+
+ for( int i=0; i<info->record_count; i++ )
+ {
+ highscore_record temp;
+ temp.trackid = info->records[i].trackid;
+ temp.datetime = time(NULL);
+ temp.playerid = steamid;
+ temp.points = info->records[i].points;
+ temp.time = info->records[i].time;
+
+ highscores_push_record( &temp );
+ }
+}
+
static void poll_connections(void)
{
SteamNetworkingMessage_t *messages[32];
}
netmsg_blank *tmp = msg->m_pData;
- if( tmp->inetmsg_id == k_inetmsg_scores_request )
- {
- vg_log( "Recieved score request, sending records. (id: %u)\n",
- msg->m_conn );
-
- /* Send back current scores */
- u32 data_size = sizeof(netmsg_scores_info) +
- 0*sizeof(struct netmsg_score_record);
- netmsg_scores_info *return_info = malloc( data_size );
- return_info->inetmsg_id = k_inetmsg_scores_info;
- return_info->record_count = 0;
-
- SteamAPI_ISteamNetworkingSockets_SendMessageToConnection(
- hSteamNetworkingSockets, msg->m_conn,
- return_info, data_size,
- k_nSteamNetworkingSend_Reliable, NULL );
- }
+ if( tmp->inetmsg_id == k_inetmsg_auth )
+ on_inet_auth( msg );
+ else if( tmp->inetmsg_id == k_inetmsg_scores_request )
+ on_inet_score_request( msg );
+ else if( tmp->inetmsg_id == k_inetmsg_set_nickname )
+ on_inet_set_nickname( msg );
SteamAPI_SteamNetworkingMessage_t_Release( msg );
}
}
}
+u64 seconds_to_server_ticks( double s )
+{
+ return s / 0.1;
+}
+
int main( int argc, char *argv[] )
{
- steamworks_ensure_txt( "2103940" );
signal( SIGINT, inthandler );
+ signal( SIGQUIT, inthandler );
+ highscores_init( 250000, 10000 );
+
+ steamworks_ensure_txt( "2103940" );
if( !vg_load_steam_symetric_key( "application_key", steam_symetric_key ) )
return 0;
SteamAPI_ISteamHTTP_SendHTTPRequest( hSteamHTTP, test_req, &call1->id );
#endif
- u64 server_ticks = 8000;
+ u64 server_ticks = 8000,
+ last_record_save = 8000;
while( !sig_stop )
{
usleep(100000);
server_ticks ++;
+
+ if(last_record_save+server_ticks > seconds_to_server_ticks( 10.0*60.0 ))
+ {
+ last_record_save = server_ticks;
+ highscores_serialize_all();
+ }
}
+
+ highscores_serialize_all();
+ highscores_free();
SteamAPI_ISteamNetworkingSockets_DestroyPollGroup( hSteamNetworkingSockets,
client_pollgroup );
{
vg_info( "Database test\n" );
- if( !highscores_init( 200000 ) )
+ if( !highscores_init( 200000, 100000 ) )
return 0;
-
+
+ srand(time(0));
vg_log( "Inserting test records...\n" );
- for( int i=0; i<200000; i++ )
+ for( int i=0; i<5000; i++ )
{
highscore_record entry;
- entry.trackid = vg_randf() * 129.0f;
+ entry.trackid = vg_randf() * 138.0f;
entry.points = vg_randf() * 10000.0f;
entry.time = vg_randf() * 20000.0f;
entry.playerid = rand() % 800;
highscores_push_record( &entry );
}
+
+ for( int i=0; i<80; i++ )
+ {
+ char rando[16];
+
+ int l=2+rand()%8;
+ for( int i=0; i<l; i++ )
+ rando[i] = 'a' + rand() % 10;
+ rando[l] = '\0';
+
+ highscore_set_user_nickname( rand() % 800, rando );
+ }
+
vg_log( "Done.\n" );
- highscores_print_track( 2, 10 );
+#if 0
+ int ln =0, err=0;
+ aatree_show_counts( &highscore_system.aainfo_playerinfo_playerid,
+ highscore_system.dbheader.playerinfo_root, 0,
+ &ln, &err, _highscore_showname, 1 );
+#endif
+
+ highscores_print_track( 12, 10 );
+ highscores_serialize_all();
highscores_free();
return 0;
}