/* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. Copyright (C) 2000-2006 Tim Angus This file is part of Tremulous. Tremulous is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Tremulous is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Tremulous; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ // sv_rankings.c -- global rankings interface #include "server.h" #include "..\rankings\1.0\gr\grapi.h" #include "..\rankings\1.0\gr\grlog.h" typedef struct { GR_CONTEXT context; uint64_t game_id; uint64_t match; uint64_t player_id; GR_PLAYER_TOKEN token; grank_status_t grank_status; grank_status_t final_status; // status to set after cleanup uint32_t grank; // global rank char name[32]; } ranked_player_t; static int s_rankings_contexts = 0; static qboolean s_rankings_active = qfalse; static GR_CONTEXT s_server_context = 0; static uint64_t s_server_match = 0; static char* s_rankings_game_key = NULL; static uint64_t s_rankings_game_id = 0; static ranked_player_t* s_ranked_players = NULL; static qboolean s_server_quitting = qfalse; static const char s_ascii_encoding[] = "0123456789abcdef" "ghijklmnopqrstuv" "wxyzABCDEFGHIJKL" "MNOPQRSTUVWXYZ[]"; // private functions static void SV_RankNewGameCBF( GR_NEWGAME* gr_newgame, void* cbf_arg ); static void SV_RankUserCBF( GR_LOGIN* gr_login, void* cbf_arg ); static void SV_RankJoinGameCBF( GR_JOINGAME* gr_joingame, void* cbf_arg ); static void SV_RankSendReportsCBF( GR_STATUS* gr_status, void* cbf_arg ); static void SV_RankCleanupCBF( GR_STATUS* gr_status, void* cbf_arg ); static void SV_RankCloseContext( ranked_player_t* ranked_player ); static int SV_RankAsciiEncode( char* dest, const unsigned char* src, int src_len ); static int SV_RankAsciiDecode( unsigned char* dest, const char* src, int src_len ); static void SV_RankEncodeGameID( uint64_t game_id, char* result, int len ); static uint64_t SV_RankDecodePlayerID( const char* string ); static void SV_RankDecodePlayerKey( const char* string, GR_PLAYER_TOKEN key ); static char* SV_RankStatusString( GR_STATUS status ); static void SV_RankError( const char* fmt, ... ); static char SV_RankGameKey[64]; /* ================ SV_RankBegin ================ */ void SV_RankBegin( char *gamekey ) { GR_INIT init; GR_STATUS status; assert( s_rankings_contexts == 0 ); assert( !s_rankings_active ); assert( s_ranked_players == NULL ); if( sv_enableRankings->integer == 0 ) { s_rankings_active = qfalse; if( sv_rankingsActive->integer == 1 ) { Cvar_Set( "sv_rankingsActive", "0" ); } return; } // only allow official game key on pure servers if( strcmp(gamekey, GR_GAMEKEY) == 0 ) { /* if( Cvar_VariableValue("sv_pure") != 1 ) { Cvar_Set( "sv_enableRankings", "0" ); return; } */ // substitute game-specific game key switch( (int)Cvar_VariableValue("g_gametype") ) { case GT_FFA: gamekey = "Q3 Free For All"; break; case GT_TOURNAMENT: gamekey = "Q3 Tournament"; break; case GT_TEAM: gamekey = "Q3 Team Deathmatch"; break; case GT_CTF: gamekey = "Q3 Capture the Flag"; break; case GT_1FCTF: gamekey = "Q3 One Flag CTF"; break; case GT_OBELISK: gamekey = "Q3 Overload"; break; case GT_HARVESTER: gamekey = "Q3 Harvester"; break; default: break; } } s_rankings_game_key = gamekey; // initialize rankings GRankLogLevel( GRLOG_OFF ); memset(SV_RankGameKey,0,sizeof(SV_RankGameKey)); strncpy(SV_RankGameKey,gamekey,sizeof(SV_RankGameKey)-1); init = GRankInit( 1, SV_RankGameKey, GR_OPT_POLL, GR_OPT_END ); s_server_context = init.context; s_rankings_contexts++; Com_DPrintf( "SV_RankBegin(); GR_GAMEKEY is %s\n", gamekey ); Com_DPrintf( "SV_RankBegin(); s_rankings_contexts=%d\n",s_rankings_contexts ); Com_DPrintf( "SV_RankBegin(); s_server_context=%d\n",init.context ); // new game if(!strlen(Cvar_VariableString( "sv_leagueName" ))) { status = GRankNewGameAsync ( s_server_context, SV_RankNewGameCBF, NULL, GR_OPT_LEAGUENAME, (void*)(Cvar_VariableString( "sv_leagueName" )), GR_OPT_END ); } else { status = GRankNewGameAsync ( s_server_context, SV_RankNewGameCBF, NULL, GR_OPT_END ); } if( status != GR_STATUS_PENDING ) { SV_RankError( "SV_RankBegin: Expected GR_STATUS_PENDING, got %s", SV_RankStatusString( status ) ); return; } // logging if( com_developer->value ) { GRankLogLevel( GRLOG_TRACE ); } // allocate rankings info for each player s_ranked_players = Z_Malloc( sv_maxclients->value * sizeof(ranked_player_t) ); memset( (void*)s_ranked_players, 0 ,sv_maxclients->value * sizeof(ranked_player_t)); } /* ================ SV_RankEnd ================ */ void SV_RankEnd( void ) { GR_STATUS status; int i; Com_DPrintf( "SV_RankEnd();\n" ); if( !s_rankings_active ) { // cleanup after error during game if( s_ranked_players != NULL ) { for( i = 0; i < sv_maxclients->value; i++ ) { if( s_ranked_players[i].context != 0 ) { SV_RankCloseContext( &(s_ranked_players[i]) ); } } } if( s_server_context != 0 ) { SV_RankCloseContext( NULL ); } return; } for( i = 0; i < sv_maxclients->value; i++ ) { if( s_ranked_players[i].grank_status == QGR_STATUS_ACTIVE ) { SV_RankUserLogout( i ); Com_DPrintf( "SV_RankEnd: SV_RankUserLogout %d\n",i ); } } assert( s_server_context != 0 ); // send match reports, proceed to SV_RankSendReportsCBF status = GRankSendReportsAsync ( s_server_context, 0, SV_RankSendReportsCBF, NULL, GR_OPT_END ); if( status != GR_STATUS_PENDING ) { SV_RankError( "SV_RankEnd: Expected GR_STATUS_PENDING, got %s", SV_RankStatusString( status ) ); } s_rankings_active = qfalse; Cvar_Set( "sv_rankingsActive", "0" ); } /* ================ SV_RankPoll ================ */ void SV_RankPoll( void ) { GRankPoll(); } /* ================ SV_RankCheckInit ================ */ qboolean SV_RankCheckInit( void ) { return (s_rankings_contexts > 0); } /* ================ SV_RankActive ================ */ qboolean SV_RankActive( void ) { return s_rankings_active; } /* ================= SV_RankUserStatus ================= */ grank_status_t SV_RankUserStatus( int index ) { if( !s_rankings_active ) { return GR_STATUS_ERROR; } assert( s_ranked_players != NULL ); assert( index >= 0 ); assert( index < sv_maxclients->value ); return s_ranked_players[index].grank_status; } /* ================ SV_RankUserGRank ================ */ int SV_RankUserGrank( int index ) { if( !s_rankings_active ) { return 0; } assert( s_ranked_players != NULL ); assert( index >= 0 ); assert( index < sv_maxclients->value ); return s_ranked_players[index].grank; } /* ================ SV_RankUserReset ================ */ void SV_RankUserReset( int index ) { if( !s_rankings_active ) { return; } assert( s_ranked_players != NULL ); assert( index >= 0 ); assert( index < sv_maxclients->value ); switch( s_ranked_players[index].grank_status ) { case QGR_STATUS_SPECTATOR: case QGR_STATUS_NO_USER: case QGR_STATUS_BAD_PASSWORD: case QGR_STATUS_USER_EXISTS: case QGR_STATUS_NO_MEMBERSHIP: case QGR_STATUS_TIMEOUT: case QGR_STATUS_ERROR: s_ranked_players[index].grank_status = QGR_STATUS_NEW; break; default: break; } } /* ================ SV_RankUserSpectate ================ */ void SV_RankUserSpectate( int index ) { if( !s_rankings_active ) { return; } assert( s_ranked_players != NULL ); assert( index >= 0 ); assert( index < sv_maxclients->value ); // GRANK_FIXME - check current status? s_ranked_players[index].grank_status = QGR_STATUS_SPECTATOR; } /* ================ SV_RankUserCreate ================ */ void SV_RankUserCreate( int index, char* username, char* password, char* email ) { GR_INIT init; GR_STATUS status; assert( index >= 0 ); assert( index < sv_maxclients->value ); assert( username != NULL ); assert( password != NULL ); assert( email != NULL ); assert( s_ranked_players ); assert( s_ranked_players[index].grank_status != QGR_STATUS_ACTIVE ); Com_DPrintf( "SV_RankUserCreate( %d, %s, \"****\", %s );\n", index, username, email ); if( !s_rankings_active ) { Com_DPrintf( "SV_RankUserCreate: Not ready to create\n" ); s_ranked_players[index].grank_status = QGR_STATUS_ERROR; return; } if( s_ranked_players[index].grank_status == QGR_STATUS_ACTIVE ) { Com_DPrintf( "SV_RankUserCreate: Got Create from active player\n" ); return; } // get a separate context for the new user init = GRankInit( 0, SV_RankGameKey, GR_OPT_POLL, GR_OPT_END ); s_ranked_players[index].context = init.context; s_rankings_contexts++; Com_DPrintf( "SV_RankUserCreate(); s_rankings_contexts=%d\n",s_rankings_contexts ); Com_DPrintf( "SV_RankUserCreate(); s_ranked_players[%d].context=%d\n",index,init.context ); // attempt to create a new account, proceed to SV_RankUserCBF status = GRankUserCreateAsync ( s_ranked_players[index].context, username, password, email, SV_RankUserCBF, (void*)&s_ranked_players[index], GR_OPT_END ); if( status == GR_STATUS_PENDING ) { s_ranked_players[index].grank_status = QGR_STATUS_PENDING; s_ranked_players[index].final_status = QGR_STATUS_NEW; } else { SV_RankError( "SV_RankUserCreate: Expected GR_STATUS_PENDING, got %s", SV_RankStatusString( status ) ); } } /* ================ SV_RankUserLogin ================ */ void SV_RankUserLogin( int index, char* username, char* password ) { GR_INIT init; GR_STATUS status; assert( index >= 0 ); assert( index < sv_maxclients->value ); assert( username != NULL ); assert( password != NULL ); assert( s_ranked_players ); assert( s_ranked_players[index].grank_status != QGR_STATUS_ACTIVE ); Com_DPrintf( "SV_RankUserLogin( %d, %s, \"****\" );\n", index, username ); if( !s_rankings_active ) { Com_DPrintf( "SV_RankUserLogin: Not ready for login\n" ); s_ranked_players[index].grank_status = QGR_STATUS_ERROR; return; } if( s_ranked_players[index].grank_status == QGR_STATUS_ACTIVE ) { Com_DPrintf( "SV_RankUserLogin: Got Login from active player\n" ); return; } // get a separate context for the new user init = GRankInit( 0, SV_RankGameKey, GR_OPT_POLL, GR_OPT_END ); s_ranked_players[index].context = init.context; s_rankings_contexts++; Com_DPrintf( "SV_RankUserLogin(); s_rankings_contexts=%d\n",s_rankings_contexts ); Com_DPrintf( "SV_RankUserLogin(); s_ranked_players[%d].context=%d\n",index,init.context ); // login user, proceed to SV_RankUserCBF status = GRankUserLoginAsync ( s_ranked_players[index].context, username, password, SV_RankUserCBF, (void*)&s_ranked_players[index], GR_OPT_END ); if( status == GR_STATUS_PENDING ) { s_ranked_players[index].grank_status = QGR_STATUS_PENDING; s_ranked_players[index].final_status = QGR_STATUS_NEW; } else { SV_RankError( "SV_RankUserLogin: Expected GR_STATUS_PENDING, got %s", SV_RankStatusString( status ) ); } } /* =================== SV_RankUserValidate =================== */ qboolean SV_RankUserValidate( int index, const char* player_id, const char* key, int token_len, int rank, char* name ) { GR_INIT init; GR_STATUS status; qboolean rVal; ranked_player_t* ranked_player; int i; assert( s_ranked_players ); assert( s_ranked_players[index].grank_status != QGR_STATUS_ACTIVE ); rVal = qfalse; if( !s_rankings_active ) { Com_DPrintf( "SV_RankUserValidate: Not ready to validate\n" ); s_ranked_players[index].grank_status = QGR_STATUS_ERROR; return rVal; } ranked_player = &(s_ranked_players[index]); if ( (player_id != NULL) && (key != NULL)) { // the real player_id and key is set when SV_RankJoinGameCBF // is called we do this so that SV_RankUserValidate // can be shared by both server side login and client side login // for client side logined in players // server is creating GR_OPT_PLAYERCONTEXT init = GRankInit( 0, SV_RankGameKey, GR_OPT_POLL, GR_OPT_END ); ranked_player->context = init.context; s_rankings_contexts++; Com_DPrintf( "SV_RankUserValidate(); s_rankings_contexts=%d\n",s_rankings_contexts ); Com_DPrintf( "SV_RankUserValidate(); s_ranked_players[%d].context=%d\n",index,init.context ); // uudecode player id and player token ranked_player->player_id = SV_RankDecodePlayerID(player_id); Com_DPrintf( "SV_RankUserValidate(); ranked_player->player_id =%u\n", (uint32_t)ranked_player->player_id ); SV_RankDecodePlayerKey(key, ranked_player->token); // save name and check for duplicates Q_strncpyz( ranked_player->name, name, sizeof(ranked_player->name) ); for( i = 0; i < sv_maxclients->value; i++ ) { if( (i != index) && (s_ranked_players[i].grank_status == QGR_STATUS_ACTIVE) && (strcmp( s_ranked_players[i].name, name ) == 0) ) { Com_DPrintf( "SV_RankUserValidate: Duplicate login\n" ); ranked_player->grank_status = QGR_STATUS_NO_USER; ranked_player->final_status = QGR_STATUS_NEW; ranked_player->grank = 0; return qfalse; } } // then validate status = GRankPlayerValidate( s_server_context, ranked_player->player_id, ranked_player->token, token_len, GR_OPT_PLAYERCONTEXT, ranked_player->context, GR_OPT_END); } else { // make server side login (bots) happy status = GR_STATUS_OK; } if (status == GR_STATUS_OK) { ranked_player->grank_status = QGR_STATUS_ACTIVE; ranked_player->final_status = QGR_STATUS_NEW; ranked_player->grank = rank; rVal = qtrue; } else if (status == GR_STATUS_INVALIDUSER) { ranked_player->grank_status = QGR_STATUS_INVALIDUSER; ranked_player->final_status = QGR_STATUS_NEW; ranked_player->grank = 0; rVal = qfalse; } else { SV_RankError( "SV_RankUserValidate: Unexpected status %s", SV_RankStatusString( status ) ); s_ranked_players[index].grank_status = QGR_STATUS_ERROR; ranked_player->grank = 0; } return rVal; } /* ================ SV_RankUserLogout ================ */ void SV_RankUserLogout( int index ) { GR_STATUS status; GR_STATUS cleanup_status; if( !s_rankings_active ) { return; } assert( index >= 0 ); assert( index < sv_maxclients->value ); assert( s_ranked_players ); if( s_ranked_players[index].context == 0 ) { return; } Com_DPrintf( "SV_RankUserLogout( %d );\n", index ); // masqueraded player may not be active yet, if they fail validation, // but still they have a context needs to be cleaned // what matters is the s_ranked_players[index].context // send reports, proceed to SV_RankSendReportsCBF status = GRankSendReportsAsync ( s_ranked_players[index].context, 0, SV_RankSendReportsCBF, (void*)&s_ranked_players[index], GR_OPT_END ); if( status == GR_STATUS_PENDING ) { s_ranked_players[index].grank_status = QGR_STATUS_PENDING; s_ranked_players[index].final_status = QGR_STATUS_NEW; } else { SV_RankError( "SV_RankUserLogout: Expected GR_STATUS_PENDING, got %s", SV_RankStatusString( status ) ); cleanup_status = GRankCleanupAsync ( s_ranked_players[index].context, 0, SV_RankCleanupCBF, (void*)&s_ranked_players[index], GR_OPT_END ); if( cleanup_status != GR_STATUS_PENDING ) { SV_RankError( "SV_RankUserLogout: Expected " "GR_STATUS_PENDING from GRankCleanupAsync, got %s", SV_RankStatusString( cleanup_status ) ); SV_RankCloseContext( &(s_ranked_players[index]) ); } } } /* ================ SV_RankReportInt ================ */ void SV_RankReportInt( int index1, int index2, int key, int value, qboolean accum ) { GR_STATUS status; GR_CONTEXT context; uint64_t match; uint64_t user1; uint64_t user2; int opt_accum; if( !s_rankings_active ) { return; } assert( index1 >= -1 ); assert( index1 < sv_maxclients->value ); assert( index2 >= -1 ); assert( index2 < sv_maxclients->value ); assert( s_ranked_players ); // Com_DPrintf( "SV_RankReportInt( %d, %d, %d, %d, %d );\n", index1, index2, // key, value, accum ); // get context, match, and player_id for player index1 if( index1 == -1 ) { context = s_server_context; match = s_server_match; user1 = 0; } else { if( s_ranked_players[index1].grank_status != QGR_STATUS_ACTIVE ) { Com_DPrintf( "SV_RankReportInt: Expecting QGR_STATUS_ACTIVE" " Got Unexpected status %d for player %d\n", s_ranked_players[index1].grank_status, index1 ); return; } context = s_ranked_players[index1].context; match = s_ranked_players[index1].match; user1 = s_ranked_players[index1].player_id; } // get player_id for player index2 if( index2 == -1 ) { user2 = 0; } else { if( s_ranked_players[index2].grank_status != QGR_STATUS_ACTIVE ) { Com_DPrintf( "SV_RankReportInt: Expecting QGR_STATUS_ACTIVE" " Got Unexpected status %d for player %d\n", s_ranked_players[index2].grank_status, index2 ); return; } user2 = s_ranked_players[index2].player_id; } opt_accum = accum ? GR_OPT_ACCUM : GR_OPT_END; status = GRankReportInt ( context, match, user1, user2, key, value, opt_accum, GR_OPT_END ); if( status != GR_STATUS_OK ) { SV_RankError( "SV_RankReportInt: Unexpected status %s", SV_RankStatusString( status ) ); } if( user2 != 0 ) { context = s_ranked_players[index2].context; match = s_ranked_players[index2].match; status = GRankReportInt ( context, match, user1, user2, key, value, opt_accum, GR_OPT_END ); if( status != GR_STATUS_OK ) { SV_RankError( "SV_RankReportInt: Unexpected status %s", SV_RankStatusString( status ) ); } } } /* ================ SV_RankReportStr ================ */ void SV_RankReportStr( int index1, int index2, int key, char* value ) { GR_STATUS status; GR_CONTEXT context; uint64_t match; uint64_t user1; uint64_t user2; if( !s_rankings_active ) { return; } assert( index1 >= -1 ); assert( index1 < sv_maxclients->value ); assert( index2 >= -1 ); assert( index2 < sv_maxclients->value ); assert( s_ranked_players ); // Com_DPrintf( "SV_RankReportStr( %d, %d, %d, \"%s\" );\n", index1, index2, // key, value ); // get context, match, and player_id for player index1 if( index1 == -1 ) { context = s_server_context; match = s_server_match; user1 = 0; } else { if( s_ranked_players[index1].grank_status != QGR_STATUS_ACTIVE ) { Com_DPrintf( "SV_RankReportStr: Unexpected status %d\n", s_ranked_players[index1].grank_status ); return; } context = s_ranked_players[index1].context; match = s_ranked_players[index1].match; user1 = s_ranked_players[index1].player_id; } // get player_id for player index2 if( index2 == -1 ) { user2 = 0; } else { if( s_ranked_players[index2].grank_status != QGR_STATUS_ACTIVE ) { Com_DPrintf( "SV_RankReportStr: Unexpected status %d\n", s_ranked_players[index2].grank_status ); return; } user2 = s_ranked_players[index2].player_id; } status = GRankReportStr ( context, match, user1, user2, key, value, GR_OPT_END ); if( status != GR_STATUS_OK ) { SV_RankError( "SV_RankReportStr: Unexpected status %s", SV_RankStatusString( status ) ); } if( user2 != 0 ) { context = s_ranked_players[index2].context; match = s_ranked_players[index2].match; status = GRankReportStr ( context, match, user1, user2, key, value, GR_OPT_END ); if( status != GR_STATUS_OK ) { SV_RankError( "SV_RankReportInt: Unexpected status %s", SV_RankStatusString( status ) ); } } } /* ================ SV_RankQuit ================ */ void SV_RankQuit( void ) { int i; int j = 0; // yuck while( s_rankings_contexts > 1 ) { assert(s_ranked_players); if( s_ranked_players != NULL ) { for( i = 0; i < sv_maxclients->value; i++ ) { // check for players that weren't yet active in SV_RankEnd if( s_ranked_players[i].grank_status == QGR_STATUS_ACTIVE ) { SV_RankUserLogout( i ); Com_DPrintf( "SV_RankQuit: SV_RankUserLogout %d\n",i ); } else { if( s_ranked_players[i].context ) { GR_STATUS cleanup_status; cleanup_status = GRankCleanupAsync ( s_ranked_players[i].context, 0, SV_RankCleanupCBF, (void*)&(s_ranked_players[i]), GR_OPT_END ); if( cleanup_status != GR_STATUS_PENDING ) { SV_RankError( "SV_RankQuit: Expected " "GR_STATUS_PENDING from GRankCleanupAsync, got %s", SV_RankStatusString( cleanup_status ) ); } } } } } SV_RankPoll(); // should've finished by now assert( (j++) < 68 ); } } /* ============================================================================== Private Functions ============================================================================== */ /* ================= SV_RankNewGameCBF ================= */ static void SV_RankNewGameCBF( GR_NEWGAME* gr_newgame, void* cbf_arg ) { GR_MATCH match; int i; assert( gr_newgame != NULL ); assert( cbf_arg == NULL ); Com_DPrintf( "SV_RankNewGameCBF( %08X, %08X );\n", gr_newgame, cbf_arg ); if( gr_newgame->status == GR_STATUS_OK ) { char info[MAX_INFO_STRING]; char gameid[sizeof(s_ranked_players[i].game_id) * 4 / 3 + 2]; // save game id s_rankings_game_id = gr_newgame->game_id; // encode gameid memset(gameid,0,sizeof(gameid)); SV_RankEncodeGameID(s_rankings_game_id,gameid,sizeof(gameid)); // set CS_GRANK rankingsGameID to pass to client memset(info,0,sizeof(info)); Info_SetValueForKey( info, "rankingsGameKey", s_rankings_game_key ); Info_SetValueForKey( info, "rankingsGameID", gameid ); SV_SetConfigstring( CS_GRANK, info ); // initialize client status for( i = 0; i < sv_maxclients->value; i++ ) s_ranked_players[i].grank_status = QGR_STATUS_NEW; // start new match match = GRankStartMatch( s_server_context ); s_server_match = match.match; // ready to go s_rankings_active = qtrue; Cvar_Set( "sv_rankingsActive", "1" ); } else if( gr_newgame->status == GR_STATUS_BADLEAGUE ) { SV_RankError( "SV_RankNewGameCBF: Invalid League name\n" ); } else { //GRank handle new game failure // force SV_RankEnd() to run //SV_RankEnd(); SV_RankError( "SV_RankNewGameCBF: Unexpected status %s", SV_RankStatusString( gr_newgame->status ) ); } } /* ================ SV_RankUserCBF ================ */ static void SV_RankUserCBF( GR_LOGIN* gr_login, void* cbf_arg ) { ranked_player_t* ranked_player; GR_STATUS join_status; GR_STATUS cleanup_status; assert( gr_login != NULL ); assert( cbf_arg != NULL ); Com_DPrintf( "SV_RankUserCBF( %08X, %08X );\n", gr_login, cbf_arg ); ranked_player = (ranked_player_t*)cbf_arg; assert(ranked_player); assert( ranked_player->context ); switch( gr_login->status ) { case GR_STATUS_OK: // attempt to join the game, proceed to SV_RankJoinGameCBF join_status = GRankJoinGameAsync ( ranked_player->context, s_rankings_game_id, SV_RankJoinGameCBF, cbf_arg, GR_OPT_END ); if( join_status != GR_STATUS_PENDING ) { SV_RankError( "SV_RankUserCBF: Expected GR_STATUS_PENDING " "from GRankJoinGameAsync, got %s", SV_RankStatusString( join_status ) ); } break; case GR_STATUS_NOUSER: Com_DPrintf( "SV_RankUserCBF: Got status %s\n", SV_RankStatusString( gr_login->status ) ); ranked_player->final_status = QGR_STATUS_NO_USER; break; case GR_STATUS_BADPASSWORD: Com_DPrintf( "SV_RankUserCBF: Got status %s\n", SV_RankStatusString( gr_login->status ) ); ranked_player->final_status = QGR_STATUS_BAD_PASSWORD; break; case GR_STATUS_TIMEOUT: Com_DPrintf( "SV_RankUserCBF: Got status %s\n", SV_RankStatusString( gr_login->status ) ); ranked_player->final_status = QGR_STATUS_TIMEOUT; break; default: Com_DPrintf( "SV_RankUserCBF: Unexpected status %s\n", SV_RankStatusString( gr_login->status ) ); ranked_player->final_status = QGR_STATUS_ERROR; break; } if( ranked_player->final_status != QGR_STATUS_NEW ) { // login or create failed, so clean up before the next attempt cleanup_status = GRankCleanupAsync ( ranked_player->context, 0, SV_RankCleanupCBF, (void*)ranked_player, GR_OPT_END ); if( cleanup_status != GR_STATUS_PENDING ) { SV_RankError( "SV_RankUserCBF: Expected GR_STATUS_PENDING " "from GRankCleanupAsync, got %s", SV_RankStatusString( cleanup_status ) ); SV_RankCloseContext( ranked_player ); } } } /* ================ SV_RankJoinGameCBF ================ */ static void SV_RankJoinGameCBF( GR_JOINGAME* gr_joingame, void* cbf_arg ) { ranked_player_t* ranked_player; GR_MATCH match; GR_STATUS cleanup_status; assert( gr_joingame != NULL ); assert( cbf_arg != NULL ); Com_DPrintf( "SV_RankJoinGameCBF( %08X, %08X );\n", gr_joingame, cbf_arg ); ranked_player = (ranked_player_t*)cbf_arg; assert( ranked_player ); assert( ranked_player->context != 0 ); if( gr_joingame->status == GR_STATUS_OK ) { int i; // save user id ranked_player->player_id = gr_joingame->player_id; memcpy(ranked_player->token,gr_joingame->token, sizeof(GR_PLAYER_TOKEN)) ; match = GRankStartMatch( ranked_player->context ); ranked_player->match = match.match; ranked_player->grank = gr_joingame->rank; // find the index and call SV_RankUserValidate for (i=0;ivalue;i++) if ( ranked_player == &s_ranked_players[i] ) SV_RankUserValidate(i,NULL,NULL,0, gr_joingame->rank,ranked_player->name); } else { //GRand handle join game failure SV_RankError( "SV_RankJoinGameCBF: Unexpected status %s", SV_RankStatusString( gr_joingame->status ) ); cleanup_status = GRankCleanupAsync ( ranked_player->context, 0, SV_RankCleanupCBF, cbf_arg, GR_OPT_END ); if( cleanup_status != GR_STATUS_PENDING ) { SV_RankError( "SV_RankJoinGameCBF: Expected " "GR_STATUS_PENDING from GRankCleanupAsync, got %s", SV_RankStatusString( cleanup_status ) ); SV_RankCloseContext( ranked_player ); } } } /* ================ SV_RankSendReportsCBF ================ */ static void SV_RankSendReportsCBF( GR_STATUS* status, void* cbf_arg ) { ranked_player_t* ranked_player; GR_CONTEXT context; GR_STATUS cleanup_status; assert( status != NULL ); // NULL cbf_arg means server is sending match reports Com_DPrintf( "SV_RankSendReportsCBF( %08X, %08X );\n", status, cbf_arg ); ranked_player = (ranked_player_t*)cbf_arg; if( ranked_player == NULL ) { Com_DPrintf( "SV_RankSendReportsCBF: server\n" ); context = s_server_context; } else { Com_DPrintf( "SV_RankSendReportsCBF: player\n" ); context = ranked_player->context; } //assert( context != 0 ); if( *status != GR_STATUS_OK ) { SV_RankError( "SV_RankSendReportsCBF: Unexpected status %s", SV_RankStatusString( *status ) ); } if( context == 0 ) { Com_DPrintf( "SV_RankSendReportsCBF: WARNING: context == 0" ); SV_RankCloseContext( ranked_player ); } else { cleanup_status = GRankCleanupAsync ( context, 0, SV_RankCleanupCBF, cbf_arg, GR_OPT_END ); if( cleanup_status != GR_STATUS_PENDING ) { SV_RankError( "SV_RankSendReportsCBF: Expected " "GR_STATUS_PENDING from GRankCleanupAsync, got %s", SV_RankStatusString( cleanup_status ) ); SV_RankCloseContext( ranked_player ); } } } /* ================ SV_RankCleanupCBF ================ */ static void SV_RankCleanupCBF( GR_STATUS* status, void* cbf_arg ) { ranked_player_t* ranked_player; ranked_player = (ranked_player_t*)cbf_arg; assert( status != NULL ); // NULL cbf_arg means server is cleaning up Com_DPrintf( "SV_RankCleanupCBF( %08X, %08X );\n", status, cbf_arg ); if( *status != GR_STATUS_OK ) { SV_RankError( "SV_RankCleanupCBF: Unexpected status %s", SV_RankStatusString( *status ) ); } SV_RankCloseContext( ranked_player ); } /* ================ SV_RankCloseContext ================ */ static void SV_RankCloseContext( ranked_player_t* ranked_player ) { if( ranked_player == NULL ) { // server cleanup if( s_server_context == 0 ) { return; } s_server_context = 0; s_server_match = 0; } else { // player cleanup if( s_ranked_players == NULL ) { return; } if( ranked_player->context == 0 ) { return; } ranked_player->context = 0; ranked_player->match = 0; ranked_player->player_id = 0; memset( ranked_player->token, 0, sizeof(GR_PLAYER_TOKEN) ); ranked_player->grank_status = ranked_player->final_status; ranked_player->final_status = QGR_STATUS_NEW; ranked_player->name[0] = '\0'; } assert( s_rankings_contexts > 0 ); s_rankings_contexts--; Com_DPrintf( "SV_RankCloseContext: s_rankings_contexts = %d\n", s_rankings_contexts ); if( s_rankings_contexts == 0 ) { GRankLogLevel( GRLOG_OFF ); if( s_ranked_players != NULL ) { Z_Free( s_ranked_players ); s_ranked_players = NULL; } s_rankings_active = qfalse; Cvar_Set( "sv_rankingsActive", "0" ); } } /* ================ SV_RankAsciiEncode Encodes src_len bytes of binary data from the src buffer as ASCII text, using 6 bits per character. The result string is null-terminated and stored in the dest buffer. The dest buffer must be at least (src_len * 4) / 3 + 2 bytes in length. Returns the length of the result string, not including the null. ================ */ static int SV_RankAsciiEncode( char* dest, const unsigned char* src, int src_len ) { unsigned char bin[3]; unsigned char txt[4]; int dest_len = 0; int i; int j; int num_chars; assert( dest != NULL ); assert( src != NULL ); for( i = 0; i < src_len; i += 3 ) { // read three bytes of input for( j = 0; j < 3; j++ ) { bin[j] = (i + j < src_len) ? src[i + j] : 0; } // get four 6-bit values from three bytes txt[0] = bin[0] >> 2; txt[1] = ((bin[0] << 4) | (bin[1] >> 4)) & 63; txt[2] = ((bin[1] << 2) | (bin[2] >> 6)) & 63; txt[3] = bin[2] & 63; // store ASCII encoding of 6-bit values num_chars = (i + 2 < src_len) ? 4 : ((src_len - i) * 4) / 3 + 1; for( j = 0; j < num_chars; j++ ) { dest[dest_len++] = s_ascii_encoding[txt[j]]; } } dest[dest_len] = '\0'; return dest_len; } /* ================ SV_RankAsciiDecode Decodes src_len characters of ASCII text from the src buffer, stores the binary result in the dest buffer. The dest buffer must be at least (src_len * 3) / 4 bytes in length. Returns the length of the binary result, or zero for invalid input. ================ */ static int SV_RankAsciiDecode( unsigned char* dest, const char* src, int src_len ) { static unsigned char s_inverse_encoding[256]; static char s_init = 0; unsigned char bin[3]; unsigned char txt[4]; int dest_len = 0; int i; int j; int num_bytes; assert( dest != NULL ); assert( src != NULL ); if( !s_init ) { // initialize lookup table for decoding memset( s_inverse_encoding, 255, sizeof(s_inverse_encoding) ); for( i = 0; i < 64; i++ ) { s_inverse_encoding[s_ascii_encoding[i]] = i; } s_init = 1; } for( i = 0; i < src_len; i += 4 ) { // read four characters of input, decode them to 6-bit values for( j = 0; j < 4; j++ ) { txt[j] = (i + j < src_len) ? s_inverse_encoding[src[i + j]] : 0; if (txt[j] == 255) { return 0; // invalid input character } } // get three bytes from four 6-bit values bin[0] = (txt[0] << 2) | (txt[1] >> 4); bin[1] = (txt[1] << 4) | (txt[2] >> 2); bin[2] = (txt[2] << 6) | txt[3]; // store binary data num_bytes = (i + 3 < src_len) ? 3 : ((src_len - i) * 3) / 4; for( j = 0; j < num_bytes; j++ ) { dest[dest_len++] = bin[j]; } } return dest_len; } /* ================ SV_RankEncodeGameID ================ */ static void SV_RankEncodeGameID( uint64_t game_id, char* result, int len ) { assert( result != NULL ); if( len < ( ( sizeof(game_id) * 4) / 3 + 2) ) { Com_DPrintf( "SV_RankEncodeGameID: result buffer too small\n" ); result[0] = '\0'; } else { qint64 gameid = LittleLong64(*(qint64*)&game_id); SV_RankAsciiEncode( result, (unsigned char*)&gameid, sizeof(qint64) ); } } /* ================ SV_RankDecodePlayerID ================ */ static uint64_t SV_RankDecodePlayerID( const char* string ) { unsigned char buffer[9]; int len; qint64 player_id; assert( string != NULL ); len = strlen (string) ; Com_DPrintf( "SV_RankDecodePlayerID: string length %d\n",len ); SV_RankAsciiDecode( buffer, string, len ); player_id = LittleLong64(*(qint64*)buffer); return *(uint64_t*)&player_id; } /* ================ SV_RankDecodePlayerKey ================ */ static void SV_RankDecodePlayerKey( const char* string, GR_PLAYER_TOKEN key ) { unsigned char buffer[1400]; int len; assert( string != NULL ); len = strlen (string) ; Com_DPrintf( "SV_RankDecodePlayerKey: string length %d\n",len ); memset(key,0,sizeof(GR_PLAYER_TOKEN)); memset(buffer,0,sizeof(buffer)); memcpy( key, buffer, SV_RankAsciiDecode( buffer, string, len ) ); } /* ================ SV_RankStatusString ================ */ static char* SV_RankStatusString( GR_STATUS status ) { switch( status ) { case GR_STATUS_OK: return "GR_STATUS_OK"; case GR_STATUS_ERROR: return "GR_STATUS_ERROR"; case GR_STATUS_BADPARAMS: return "GR_STATUS_BADPARAMS"; case GR_STATUS_NETWORK: return "GR_STATUS_NETWORK"; case GR_STATUS_NOUSER: return "GR_STATUS_NOUSER"; case GR_STATUS_BADPASSWORD: return "GR_STATUS_BADPASSWORD"; case GR_STATUS_BADGAME: return "GR_STATUS_BADGAME"; case GR_STATUS_PENDING: return "GR_STATUS_PENDING"; case GR_STATUS_BADDOMAIN: return "GR_STATUS_BADDOMAIN"; case GR_STATUS_DOMAINLOCK: return "GR_STATUS_DOMAINLOCK"; case GR_STATUS_TIMEOUT: return "GR_STATUS_TIMEOUT"; case GR_STATUS_INVALIDUSER: return "GR_STATUS_INVALIDUSER"; case GR_STATUS_INVALIDCONTEXT: return "GR_STATUS_INVALIDCONTEXT"; default: return "(UNKNOWN)"; } } /* ================ SV_RankError ================ */ static void SV_RankError( const char* fmt, ... ) { va_list arg_ptr; char text[1024]; va_start( arg_ptr, fmt ); vsprintf( text, fmt, arg_ptr ); va_end( arg_ptr ); Com_DPrintf( "****************************************\n" ); Com_DPrintf( "SV_RankError: %s\n", text ); Com_DPrintf( "****************************************\n" ); s_rankings_active = qfalse; Cvar_Set( "sv_rankingsActive", "0" ); // FIXME - attempt clean shutdown? }