diff options
Diffstat (limited to 'src/server/sv_rankings.c')
-rw-r--r-- | src/server/sv_rankings.c | 1537 |
1 files changed, 1537 insertions, 0 deletions
diff --git a/src/server/sv_rankings.c b/src/server/sv_rankings.c new file mode 100644 index 00000000..844447be --- /dev/null +++ b/src/server/sv_rankings.c @@ -0,0 +1,1537 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code 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. + +Quake III Arena source code 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 Quake III Arena source code; 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 || Cvar_VariableValue( "g_gametype" ) == GT_SINGLE_PLAYER ) + { + 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;i<sv_maxclients->value;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? +} + |