Quake-III-Arena

Quake III Arena GPL Source Release
Log | Files | Refs

sv_rankings.c (35537B)


      1 /*
      2 ===========================================================================
      3 Copyright (C) 1999-2005 Id Software, Inc.
      4 
      5 This file is part of Quake III Arena source code.
      6 
      7 Quake III Arena source code is free software; you can redistribute it
      8 and/or modify it under the terms of the GNU General Public License as
      9 published by the Free Software Foundation; either version 2 of the License,
     10 or (at your option) any later version.
     11 
     12 Quake III Arena source code is distributed in the hope that it will be
     13 useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
     14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     15 GNU General Public License for more details.
     16 
     17 You should have received a copy of the GNU General Public License
     18 along with Foobar; if not, write to the Free Software
     19 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
     20 ===========================================================================
     21 */
     22 // sv_rankings.c -- global rankings interface
     23 
     24 #include "server.h"
     25 #include "..\rankings\1.0\gr\grapi.h"
     26 #include "..\rankings\1.0\gr\grlog.h"
     27 
     28 typedef struct
     29 {
     30 	GR_CONTEXT		context;
     31 	uint64_t        game_id;
     32 	uint64_t		match;
     33 	uint64_t		player_id;
     34 	GR_PLAYER_TOKEN     token;
     35 	grank_status_t	grank_status;
     36 	grank_status_t	final_status;	// status to set after cleanup
     37 	uint32_t        grank;          // global rank
     38 	char			name[32];
     39 } ranked_player_t;
     40 
     41 static int				s_rankings_contexts = 0;
     42 static qboolean			s_rankings_active = qfalse;
     43 static GR_CONTEXT		s_server_context = 0;
     44 static uint64_t			s_server_match = 0;
     45 static char*			s_rankings_game_key = NULL;
     46 static uint64_t			s_rankings_game_id = 0;
     47 static ranked_player_t*	s_ranked_players = NULL;
     48 static qboolean			s_server_quitting = qfalse;
     49 static const char		s_ascii_encoding[] = 
     50 							"0123456789abcdef"
     51 							"ghijklmnopqrstuv"
     52 							"wxyzABCDEFGHIJKL"
     53 							"MNOPQRSTUVWXYZ[]";
     54 
     55 // private functions
     56 static void		SV_RankNewGameCBF( GR_NEWGAME* gr_newgame, void* cbf_arg );
     57 static void		SV_RankUserCBF( GR_LOGIN* gr_login, void* cbf_arg );
     58 static void		SV_RankJoinGameCBF( GR_JOINGAME* gr_joingame, void* cbf_arg );
     59 static void		SV_RankSendReportsCBF( GR_STATUS* gr_status, void* cbf_arg );
     60 static void		SV_RankCleanupCBF( GR_STATUS* gr_status, void* cbf_arg );
     61 static void		SV_RankCloseContext( ranked_player_t* ranked_player );
     62 static int		SV_RankAsciiEncode( char* dest, const unsigned char* src, 
     63 					int src_len );
     64 static int		SV_RankAsciiDecode( unsigned char* dest, const char* src, 
     65 					int src_len );
     66 static void		SV_RankEncodeGameID( uint64_t game_id, char* result, 
     67 					int len );
     68 static uint64_t	SV_RankDecodePlayerID( const char* string );
     69 static void		SV_RankDecodePlayerKey( const char* string, GR_PLAYER_TOKEN key );
     70 static char*	SV_RankStatusString( GR_STATUS status );
     71 static void		SV_RankError( const char* fmt, ... );
     72 static char     SV_RankGameKey[64];
     73 
     74 /*
     75 ================
     76 SV_RankBegin
     77 ================
     78 */
     79 void SV_RankBegin( char *gamekey )
     80 {
     81 	GR_INIT		init;
     82 	GR_STATUS	status;
     83 
     84 	assert( s_rankings_contexts == 0 );
     85 	assert( !s_rankings_active );
     86 	assert( s_ranked_players == NULL );
     87 
     88 	if( sv_enableRankings->integer == 0 || Cvar_VariableValue( "g_gametype" ) == GT_SINGLE_PLAYER )
     89 	{
     90 		s_rankings_active = qfalse;
     91 		if( sv_rankingsActive->integer == 1 )
     92 		{
     93 			Cvar_Set( "sv_rankingsActive", "0" );
     94 		}
     95 		return;
     96 	}
     97 
     98 	// only allow official game key on pure servers
     99 	if( strcmp(gamekey, GR_GAMEKEY) == 0 )
    100 	{
    101 /*
    102 		if( Cvar_VariableValue("sv_pure") != 1 )
    103 		{
    104 			Cvar_Set( "sv_enableRankings", "0" );
    105 			return;
    106 		}
    107 */
    108 
    109 		// substitute game-specific game key
    110 		switch( (int)Cvar_VariableValue("g_gametype") )
    111 		{
    112 		case GT_FFA:
    113 			gamekey = "Q3 Free For All";
    114 			break;
    115 		case GT_TOURNAMENT:
    116 			gamekey = "Q3 Tournament";
    117 			break;
    118 		case GT_TEAM:
    119 			gamekey = "Q3 Team Deathmatch";
    120 			break;
    121 		case GT_CTF:
    122 			gamekey = "Q3 Capture the Flag";
    123 			break;
    124 		case GT_1FCTF:
    125 			gamekey = "Q3 One Flag CTF";
    126 			break;
    127 		case GT_OBELISK:
    128 			gamekey = "Q3 Overload";
    129 			break;
    130 		case GT_HARVESTER:
    131 			gamekey = "Q3 Harvester";
    132 			break;
    133 		default:
    134 			break;
    135 		}
    136 	}
    137 	s_rankings_game_key = gamekey;
    138 
    139 	// initialize rankings
    140 	GRankLogLevel( GRLOG_OFF );
    141 	memset(SV_RankGameKey,0,sizeof(SV_RankGameKey));
    142 	strncpy(SV_RankGameKey,gamekey,sizeof(SV_RankGameKey)-1);
    143 	init = GRankInit( 1, SV_RankGameKey, GR_OPT_POLL, GR_OPT_END );
    144 	s_server_context = init.context;
    145 	s_rankings_contexts++;
    146 	Com_DPrintf( "SV_RankBegin(); GR_GAMEKEY is %s\n", gamekey );
    147 	Com_DPrintf( "SV_RankBegin(); s_rankings_contexts=%d\n",s_rankings_contexts );
    148 	Com_DPrintf( "SV_RankBegin(); s_server_context=%d\n",init.context );
    149 
    150 	// new game
    151 	if(!strlen(Cvar_VariableString( "sv_leagueName" )))
    152 	{
    153 		status = GRankNewGameAsync
    154 			( 			
    155 				s_server_context, 
    156 				SV_RankNewGameCBF, 
    157 				NULL, 
    158 				GR_OPT_LEAGUENAME,
    159 				(void*)(Cvar_VariableString( "sv_leagueName" )),
    160 				GR_OPT_END 
    161 			);
    162 	}
    163 	else
    164 	{
    165 		status = GRankNewGameAsync
    166 			( 			
    167 				s_server_context, 
    168 				SV_RankNewGameCBF, 
    169 				NULL, 
    170 				GR_OPT_END 
    171 			);
    172 	}
    173 		
    174 	if( status != GR_STATUS_PENDING )
    175 	{
    176 		SV_RankError( "SV_RankBegin: Expected GR_STATUS_PENDING, got %s", 
    177 			SV_RankStatusString( status ) );
    178 		return;
    179 	}
    180 
    181 	// logging
    182 	if( com_developer->value )
    183 	{
    184 		GRankLogLevel( GRLOG_TRACE );
    185 	}
    186 	
    187 	// allocate rankings info for each player
    188 	s_ranked_players = Z_Malloc( sv_maxclients->value * 
    189 		sizeof(ranked_player_t) );
    190 	memset( (void*)s_ranked_players, 0 ,sv_maxclients->value 
    191 		* sizeof(ranked_player_t));
    192 }
    193 
    194 /*
    195 ================
    196 SV_RankEnd
    197 ================
    198 */
    199 void SV_RankEnd( void )
    200 {
    201 	GR_STATUS	status;
    202 	int			i;
    203 	
    204 	Com_DPrintf( "SV_RankEnd();\n" );
    205 
    206 	if( !s_rankings_active )
    207 	{
    208 		// cleanup after error during game
    209 		if( s_ranked_players != NULL )
    210 		{
    211 			for( i = 0; i < sv_maxclients->value; i++ )
    212 			{
    213 				if( s_ranked_players[i].context != 0 )
    214 				{
    215 					SV_RankCloseContext( &(s_ranked_players[i]) );
    216 				}
    217 			}
    218 		}
    219 		if( s_server_context != 0 )
    220 		{
    221 			SV_RankCloseContext( NULL );
    222 		}
    223 
    224 		return;
    225 	}
    226 
    227 	for( i = 0; i < sv_maxclients->value; i++ )
    228 	{
    229 		if( s_ranked_players[i].grank_status == QGR_STATUS_ACTIVE )
    230 		{
    231 			SV_RankUserLogout( i );
    232 			Com_DPrintf( "SV_RankEnd: SV_RankUserLogout %d\n",i );
    233 		}
    234 	}
    235 
    236 	assert( s_server_context != 0 );
    237 	
    238 	// send match reports, proceed to SV_RankSendReportsCBF
    239 	status = GRankSendReportsAsync
    240 		( 
    241 			s_server_context,
    242 			0,
    243 			SV_RankSendReportsCBF,
    244 			NULL, 
    245 			GR_OPT_END
    246 		);
    247 			
    248 	if( status != GR_STATUS_PENDING )
    249 	{
    250 		SV_RankError( "SV_RankEnd: Expected GR_STATUS_PENDING, got %s", 
    251 			SV_RankStatusString( status ) );
    252 	}
    253 
    254 	s_rankings_active = qfalse;
    255 	Cvar_Set( "sv_rankingsActive", "0" );
    256 }
    257 
    258 /*
    259 ================
    260 SV_RankPoll
    261 ================
    262 */
    263 void SV_RankPoll( void )
    264 {
    265 	GRankPoll();
    266 }
    267 
    268 /*
    269 ================
    270 SV_RankCheckInit
    271 ================
    272 */
    273 qboolean SV_RankCheckInit( void )
    274 {
    275 	return (s_rankings_contexts > 0);
    276 }
    277 
    278 /*
    279 ================
    280 SV_RankActive
    281 ================
    282 */
    283 qboolean SV_RankActive( void )
    284 {
    285 	return s_rankings_active;
    286 }
    287 
    288 /*
    289 =================
    290 SV_RankUserStatus
    291 =================
    292 */
    293 grank_status_t SV_RankUserStatus( int index )
    294 {
    295 	if( !s_rankings_active )
    296 	{
    297 		return GR_STATUS_ERROR;
    298 	}
    299 
    300 	assert( s_ranked_players != NULL );
    301 	assert( index >= 0 );
    302 	assert( index < sv_maxclients->value );
    303 
    304 	return s_ranked_players[index].grank_status;
    305 }
    306 
    307 /*
    308 ================
    309 SV_RankUserGRank
    310 ================
    311 */
    312 int SV_RankUserGrank( int index )
    313 {
    314 	if( !s_rankings_active )
    315 	{
    316 		return 0;
    317 	}
    318 
    319 	assert( s_ranked_players != NULL );
    320 	assert( index >= 0 );
    321 	assert( index < sv_maxclients->value );
    322 
    323 	return s_ranked_players[index].grank;
    324 }
    325 
    326 /*
    327 ================
    328 SV_RankUserReset
    329 ================
    330 */
    331 void SV_RankUserReset( int index )
    332 {
    333 	if( !s_rankings_active )
    334 	{
    335 		return;
    336 	}
    337 
    338 	assert( s_ranked_players != NULL );
    339 	assert( index >= 0 );
    340 	assert( index < sv_maxclients->value );
    341 
    342 	switch( s_ranked_players[index].grank_status )
    343 	{
    344 	case QGR_STATUS_SPECTATOR:
    345 	case QGR_STATUS_NO_USER:
    346 	case QGR_STATUS_BAD_PASSWORD:
    347 	case QGR_STATUS_USER_EXISTS:
    348 	case QGR_STATUS_NO_MEMBERSHIP:
    349 	case QGR_STATUS_TIMEOUT:
    350 	case QGR_STATUS_ERROR:
    351 		s_ranked_players[index].grank_status = QGR_STATUS_NEW;
    352 		break;
    353 	default:
    354 		break;
    355 	}
    356 }
    357 
    358 /*
    359 ================
    360 SV_RankUserSpectate
    361 ================
    362 */
    363 void SV_RankUserSpectate( int index )
    364 {
    365 	if( !s_rankings_active )
    366 	{
    367 		return;
    368 	}
    369 
    370 	assert( s_ranked_players != NULL );
    371 	assert( index >= 0 );
    372 	assert( index < sv_maxclients->value );
    373 
    374 	// GRANK_FIXME - check current status?
    375 	s_ranked_players[index].grank_status = QGR_STATUS_SPECTATOR;
    376 }
    377 
    378 /*
    379 ================
    380 SV_RankUserCreate
    381 ================
    382 */
    383 void SV_RankUserCreate( int index, char* username, char* password, 
    384 	char* email )
    385 {
    386 	GR_INIT		init;
    387 	GR_STATUS	status;
    388 
    389 	assert( index >= 0 );
    390 	assert( index < sv_maxclients->value );
    391 	assert( username != NULL );
    392 	assert( password != NULL );
    393 	assert( email != NULL );
    394 	assert( s_ranked_players );
    395 	assert( s_ranked_players[index].grank_status != QGR_STATUS_ACTIVE );
    396 	
    397 	Com_DPrintf( "SV_RankUserCreate( %d, %s, \"****\", %s );\n", index, 
    398 		username, email );
    399 
    400 	if( !s_rankings_active )
    401 	{
    402 		Com_DPrintf( "SV_RankUserCreate: Not ready to create\n" );
    403 		s_ranked_players[index].grank_status = QGR_STATUS_ERROR;
    404 		return;
    405 	}
    406 	
    407 	if( s_ranked_players[index].grank_status == QGR_STATUS_ACTIVE )
    408 	{
    409 		Com_DPrintf( "SV_RankUserCreate: Got Create from active player\n" );
    410 		return;
    411 	}
    412 	
    413 	// get a separate context for the new user
    414 	init = GRankInit( 0, SV_RankGameKey, GR_OPT_POLL, GR_OPT_END );
    415 	s_ranked_players[index].context = init.context;
    416 	s_rankings_contexts++;
    417 	Com_DPrintf( "SV_RankUserCreate(); s_rankings_contexts=%d\n",s_rankings_contexts );
    418 	Com_DPrintf( "SV_RankUserCreate(); s_ranked_players[%d].context=%d\n",index,init.context );
    419 	
    420 	// attempt to create a new account, proceed to SV_RankUserCBF
    421 	status = GRankUserCreateAsync
    422 		( 
    423 			s_ranked_players[index].context, 
    424 			username, 
    425 			password, 
    426 			email, 
    427 			SV_RankUserCBF, 
    428 			(void*)&s_ranked_players[index], 
    429 			GR_OPT_END
    430 		);
    431 
    432 	if( status == GR_STATUS_PENDING )
    433 	{
    434 		s_ranked_players[index].grank_status = QGR_STATUS_PENDING;
    435 		s_ranked_players[index].final_status = QGR_STATUS_NEW;
    436 	}
    437 	else
    438 	{
    439 		SV_RankError( "SV_RankUserCreate: Expected GR_STATUS_PENDING, got %s", 
    440 			SV_RankStatusString( status ) );
    441 	}
    442 }
    443 
    444 /*
    445 ================
    446 SV_RankUserLogin
    447 ================
    448 */
    449 void SV_RankUserLogin( int index, char* username, char* password )
    450 {
    451 	GR_INIT		init;
    452 	GR_STATUS	status;
    453 
    454 	assert( index >= 0 );
    455 	assert( index < sv_maxclients->value );
    456 	assert( username != NULL );
    457 	assert( password != NULL );
    458 	assert( s_ranked_players );
    459 	assert( s_ranked_players[index].grank_status != QGR_STATUS_ACTIVE );
    460 
    461 	Com_DPrintf( "SV_RankUserLogin( %d, %s, \"****\" );\n", index, username );
    462 
    463 	if( !s_rankings_active )
    464 	{
    465 		Com_DPrintf( "SV_RankUserLogin: Not ready for login\n" );
    466 		s_ranked_players[index].grank_status = QGR_STATUS_ERROR;
    467 		return;
    468 	}
    469 	
    470 	if( s_ranked_players[index].grank_status == QGR_STATUS_ACTIVE )
    471 	{
    472 		Com_DPrintf( "SV_RankUserLogin: Got Login from active player\n" );
    473 		return;
    474 	}
    475 	
    476 	// get a separate context for the new user
    477 	init = GRankInit( 0, SV_RankGameKey, GR_OPT_POLL, GR_OPT_END );
    478 	s_ranked_players[index].context = init.context;
    479 	s_rankings_contexts++;
    480 	Com_DPrintf( "SV_RankUserLogin(); s_rankings_contexts=%d\n",s_rankings_contexts );
    481 	Com_DPrintf( "SV_RankUserLogin(); s_ranked_players[%d].context=%d\n",index,init.context );
    482 	
    483 	// login user, proceed to SV_RankUserCBF
    484 	status = GRankUserLoginAsync
    485 		(
    486 			s_ranked_players[index].context, 
    487 			username, 
    488 			password, 
    489 			SV_RankUserCBF, 
    490 			(void*)&s_ranked_players[index], 
    491 			GR_OPT_END 
    492 		);
    493 
    494 	if( status == GR_STATUS_PENDING )
    495 	{
    496 		s_ranked_players[index].grank_status = QGR_STATUS_PENDING;
    497 		s_ranked_players[index].final_status = QGR_STATUS_NEW;
    498 	}
    499 	else
    500 	{
    501 		SV_RankError( "SV_RankUserLogin: Expected GR_STATUS_PENDING, got %s", 
    502 			SV_RankStatusString( status )  );
    503 	}
    504 }
    505 
    506 /*
    507 ===================
    508 SV_RankUserValidate
    509 ===================
    510 */
    511 qboolean SV_RankUserValidate( int index, const char* player_id, const char* key, int token_len, int rank, char* name )
    512 {
    513 	GR_INIT		init;
    514 	GR_STATUS status;
    515 	qboolean rVal;
    516 	ranked_player_t* ranked_player;
    517 	int i;
    518 
    519 	assert( s_ranked_players );
    520 	assert( s_ranked_players[index].grank_status != QGR_STATUS_ACTIVE );
    521 
    522 	rVal = qfalse;
    523 	
    524 	if( !s_rankings_active )
    525 	{
    526 		Com_DPrintf( "SV_RankUserValidate: Not ready to validate\n" );
    527 		s_ranked_players[index].grank_status = QGR_STATUS_ERROR;
    528 		return rVal;
    529 	}
    530 	
    531 	ranked_player = &(s_ranked_players[index]);
    532 	
    533 	if ( (player_id != NULL) && (key != NULL))
    534 	{
    535 		// the real player_id and key is set when SV_RankJoinGameCBF
    536 		// is called we do this so that SV_RankUserValidate
    537 		// can be shared by both server side login and client side login
    538 		
    539 		// for client side logined in players
    540 		// server is creating GR_OPT_PLAYERCONTEXT
    541 		init = GRankInit( 0, SV_RankGameKey, GR_OPT_POLL, GR_OPT_END );
    542 		ranked_player->context   = init.context;
    543 		s_rankings_contexts++;
    544 		Com_DPrintf( "SV_RankUserValidate(); s_rankings_contexts=%d\n",s_rankings_contexts );
    545 		Com_DPrintf( "SV_RankUserValidate(); s_ranked_players[%d].context=%d\n",index,init.context );
    546 		
    547 		// uudecode player id and player token
    548 		ranked_player->player_id = SV_RankDecodePlayerID(player_id);
    549 		Com_DPrintf( "SV_RankUserValidate(); ranked_player->player_id =%u\n", (uint32_t)ranked_player->player_id );
    550 		SV_RankDecodePlayerKey(key, ranked_player->token);
    551 		
    552 		// save name and check for duplicates
    553 		Q_strncpyz( ranked_player->name, name, sizeof(ranked_player->name) );
    554 		for( i = 0; i < sv_maxclients->value; i++ )
    555 		{
    556 			if( (i != index) && (s_ranked_players[i].grank_status == QGR_STATUS_ACTIVE) && 
    557 				(strcmp( s_ranked_players[i].name, name ) == 0) )
    558 			{
    559 				Com_DPrintf( "SV_RankUserValidate: Duplicate login\n" );
    560 				ranked_player->grank_status = QGR_STATUS_NO_USER;
    561 				ranked_player->final_status = QGR_STATUS_NEW;
    562 				ranked_player->grank = 0;
    563 				return qfalse;
    564 			}
    565 		}
    566 
    567 		// then validate
    568 		status  = GRankPlayerValidate(
    569 							s_server_context,
    570 							ranked_player->player_id, 
    571 							ranked_player->token,
    572 							token_len,
    573 							GR_OPT_PLAYERCONTEXT,
    574 							ranked_player->context,
    575 							GR_OPT_END);
    576 	}
    577 	else
    578 	{
    579 		// make server side login (bots) happy
    580 		status = GR_STATUS_OK;
    581 	}
    582 
    583 	if (status == GR_STATUS_OK)
    584 	{
    585  		ranked_player->grank_status = QGR_STATUS_ACTIVE;
    586 		ranked_player->final_status = QGR_STATUS_NEW;
    587 		ranked_player->grank = rank;
    588 		rVal = qtrue;
    589 	}
    590 	else if (status == GR_STATUS_INVALIDUSER)
    591 	{
    592 		ranked_player->grank_status = QGR_STATUS_INVALIDUSER;
    593 		ranked_player->final_status = QGR_STATUS_NEW;
    594 		ranked_player->grank = 0;
    595 		rVal = qfalse;
    596 	}
    597 	else
    598 	{
    599 		SV_RankError( "SV_RankUserValidate: Unexpected status %s",
    600 			SV_RankStatusString( status ) );
    601 		s_ranked_players[index].grank_status = QGR_STATUS_ERROR;
    602 		ranked_player->grank = 0;
    603 	}
    604 	
    605 	return rVal;
    606 }
    607 
    608 /*
    609 ================
    610 SV_RankUserLogout
    611 ================
    612 */
    613 void SV_RankUserLogout( int index )
    614 {
    615 	GR_STATUS	status;
    616 	GR_STATUS	cleanup_status;
    617 
    618 	if( !s_rankings_active )
    619 	{
    620 		return;
    621 	}
    622 
    623 	assert( index >= 0 );
    624 	assert( index < sv_maxclients->value );
    625 	assert( s_ranked_players );
    626 
    627 	if( s_ranked_players[index].context == 0 ) {
    628 		return;
    629 	}
    630 
    631 	Com_DPrintf( "SV_RankUserLogout( %d );\n", index );
    632 
    633 	// masqueraded player may not be active yet, if they fail validation, 
    634 	// but still they have a context needs to be cleaned 
    635 	// what matters is the s_ranked_players[index].context
    636 	
    637 	// send reports, proceed to SV_RankSendReportsCBF
    638 	status = GRankSendReportsAsync
    639 		( 
    640 			s_ranked_players[index].context,
    641 			0,
    642 			SV_RankSendReportsCBF,
    643 			(void*)&s_ranked_players[index], 
    644 			GR_OPT_END
    645 		);
    646 		
    647 	if( status == GR_STATUS_PENDING )
    648 	{
    649 		s_ranked_players[index].grank_status = QGR_STATUS_PENDING;
    650 		s_ranked_players[index].final_status = QGR_STATUS_NEW;
    651 	}
    652 	else
    653 	{
    654 		SV_RankError( "SV_RankUserLogout: Expected GR_STATUS_PENDING, got %s", 
    655 			SV_RankStatusString( status ) );
    656 
    657 		cleanup_status = GRankCleanupAsync
    658 			(
    659 				s_ranked_players[index].context,
    660 				0,
    661 				SV_RankCleanupCBF,
    662 				(void*)&s_ranked_players[index],
    663 				GR_OPT_END
    664 			);
    665 		
    666 		if( cleanup_status != GR_STATUS_PENDING )
    667 		{
    668 			SV_RankError( "SV_RankUserLogout: Expected "
    669 				"GR_STATUS_PENDING from GRankCleanupAsync, got %s", 
    670 				SV_RankStatusString( cleanup_status ) );
    671 			SV_RankCloseContext( &(s_ranked_players[index]) );
    672 		}
    673 	}
    674 }
    675 
    676 /*
    677 ================
    678 SV_RankReportInt
    679 ================
    680 */
    681 void SV_RankReportInt( int index1, int index2, int key, int value, 
    682 	qboolean accum )
    683 {
    684 	GR_STATUS	status;
    685 	GR_CONTEXT	context;
    686 	uint64_t	match;
    687 	uint64_t	user1;
    688 	uint64_t	user2;
    689 	int			opt_accum;
    690 
    691 	if( !s_rankings_active )
    692 	{
    693 		return;
    694 	}
    695 
    696 	assert( index1 >= -1 );
    697 	assert( index1 < sv_maxclients->value );
    698 	assert( index2 >= -1 );
    699 	assert( index2 < sv_maxclients->value );
    700 	assert( s_ranked_players );
    701 
    702 //	Com_DPrintf( "SV_RankReportInt( %d, %d, %d, %d, %d );\n", index1, index2, 
    703 //		key, value, accum );
    704 
    705 	// get context, match, and player_id for player index1
    706 	if( index1 == -1 )
    707 	{
    708 		context = s_server_context;
    709 		match = s_server_match;
    710 		user1 = 0;
    711 	}
    712 	else
    713 	{
    714 		if( s_ranked_players[index1].grank_status != QGR_STATUS_ACTIVE )
    715 		{
    716 			Com_DPrintf( "SV_RankReportInt: Expecting QGR_STATUS_ACTIVE"
    717 				" Got Unexpected status %d for player %d\n", 
    718 				s_ranked_players[index1].grank_status, index1 );
    719 			return;
    720 		}
    721 	
    722 		context = s_ranked_players[index1].context;
    723 		match = s_ranked_players[index1].match;
    724 		user1 = s_ranked_players[index1].player_id;
    725 	}
    726 
    727 	// get player_id for player index2
    728 	if( index2 == -1 )
    729 	{
    730 		user2 = 0;
    731 	}
    732 	else
    733 	{
    734 		if( s_ranked_players[index2].grank_status != QGR_STATUS_ACTIVE )
    735 		{
    736 			Com_DPrintf( "SV_RankReportInt: Expecting QGR_STATUS_ACTIVE"
    737 				" Got Unexpected status %d for player %d\n", 
    738 				s_ranked_players[index2].grank_status, index2 );
    739 			return;
    740 		}
    741 
    742 		user2 = s_ranked_players[index2].player_id;
    743 	}
    744 
    745 	opt_accum = accum ? GR_OPT_ACCUM : GR_OPT_END;
    746 	
    747 	status = GRankReportInt
    748 		(
    749 			context,
    750 			match,
    751 			user1, 
    752 			user2,
    753 			key,
    754 			value,
    755 			opt_accum,
    756 			GR_OPT_END
    757 		);
    758 		
    759 	if( status != GR_STATUS_OK )
    760 	{
    761 		SV_RankError( "SV_RankReportInt: Unexpected status %s",
    762 			SV_RankStatusString( status ) );
    763 	}
    764 
    765 	if( user2 != 0 )
    766 	{
    767 		context = s_ranked_players[index2].context;
    768 		match   = s_ranked_players[index2].match;
    769 		
    770 		status = GRankReportInt
    771 			(
    772 				context,
    773 				match,
    774 				user1, 
    775 				user2,
    776 				key,
    777 				value,
    778 				opt_accum,
    779 				GR_OPT_END
    780 			);
    781 			
    782 		if( status != GR_STATUS_OK )
    783 		{
    784 			SV_RankError( "SV_RankReportInt: Unexpected status %s",
    785 				SV_RankStatusString( status ) );
    786 		}
    787 	}
    788 }
    789 
    790 /*
    791 ================
    792 SV_RankReportStr
    793 ================
    794 */
    795 void SV_RankReportStr( int index1, int index2, int key, char* value )
    796 {
    797 	GR_STATUS	status;
    798 	GR_CONTEXT	context;
    799 	uint64_t	match;
    800 	uint64_t	user1;
    801 	uint64_t	user2;
    802 
    803 	if( !s_rankings_active )
    804 	{
    805 		return;
    806 	}
    807 
    808 	assert( index1 >= -1 );
    809 	assert( index1 < sv_maxclients->value );
    810 	assert( index2 >= -1 );
    811 	assert( index2 < sv_maxclients->value );
    812 	assert( s_ranked_players );
    813 
    814 //	Com_DPrintf( "SV_RankReportStr( %d, %d, %d, \"%s\" );\n", index1, index2, 
    815 //		key, value );
    816 	
    817 	// get context, match, and player_id for player index1
    818 	if( index1 == -1 )
    819 	{
    820 		context = s_server_context;
    821 		match = s_server_match;
    822 		user1 = 0;
    823 	}
    824 	else
    825 	{
    826 		if( s_ranked_players[index1].grank_status != QGR_STATUS_ACTIVE )
    827 		{
    828 			Com_DPrintf( "SV_RankReportStr: Unexpected status %d\n", 
    829 				s_ranked_players[index1].grank_status );
    830 			return;
    831 		}
    832 	
    833 		context = s_ranked_players[index1].context;
    834 		match = s_ranked_players[index1].match;
    835 		user1 = s_ranked_players[index1].player_id;
    836 	}
    837 
    838 	// get player_id for player index2
    839 	if( index2 == -1 )
    840 	{
    841 		user2 = 0;
    842 	}
    843 	else
    844 	{
    845 		if( s_ranked_players[index2].grank_status != QGR_STATUS_ACTIVE )
    846 		{
    847 			Com_DPrintf( "SV_RankReportStr: Unexpected status %d\n", 
    848 				s_ranked_players[index2].grank_status );
    849 			return;
    850 		}
    851 
    852 		user2 = s_ranked_players[index2].player_id;
    853 	}
    854 
    855 	status = GRankReportStr
    856 		(
    857 			context,
    858 			match,
    859 			user1,
    860 			user2,
    861 			key,
    862 			value,
    863 			GR_OPT_END
    864 		);
    865 		
    866 	if( status != GR_STATUS_OK )
    867 	{
    868 		SV_RankError( "SV_RankReportStr: Unexpected status %s",
    869 			SV_RankStatusString( status ) );
    870 	}
    871 	
    872 	if( user2 != 0 )
    873 	{
    874 		context = s_ranked_players[index2].context;
    875 		match = s_ranked_players[index2].match;
    876 		
    877 		status = GRankReportStr
    878 			(
    879 				context,
    880 				match,
    881 				user1, 
    882 				user2,
    883 				key,
    884 				value,
    885 				GR_OPT_END
    886 			);
    887 			
    888 		if( status != GR_STATUS_OK )
    889 		{
    890 			SV_RankError( "SV_RankReportInt: Unexpected status %s",
    891 				SV_RankStatusString( status ) );
    892 		}
    893 	}
    894 }
    895 
    896 /*
    897 ================
    898 SV_RankQuit
    899 ================
    900 */
    901 void SV_RankQuit( void )
    902 {
    903 	int	i;
    904 	int j = 0;	
    905 	// yuck
    906 	
    907 	while( s_rankings_contexts > 1 )
    908 	{
    909 		assert(s_ranked_players);
    910 		if( s_ranked_players != NULL )
    911 		{
    912 			for( i = 0; i < sv_maxclients->value; i++ )
    913 			{
    914 				// check for players that weren't yet active in SV_RankEnd
    915 				if( s_ranked_players[i].grank_status == QGR_STATUS_ACTIVE )
    916 				{
    917 					SV_RankUserLogout( i );
    918 					Com_DPrintf( "SV_RankQuit: SV_RankUserLogout %d\n",i );
    919 				}
    920 				else
    921 				{
    922 					if( s_ranked_players[i].context )
    923 					{
    924 						GR_STATUS cleanup_status;
    925 						cleanup_status = GRankCleanupAsync
    926 							(
    927 								s_ranked_players[i].context,
    928 								0,
    929 								SV_RankCleanupCBF,
    930 								(void*)&(s_ranked_players[i]),
    931 								GR_OPT_END
    932 							);
    933 						
    934 						if( cleanup_status != GR_STATUS_PENDING )
    935 						{
    936 							SV_RankError( "SV_RankQuit: Expected "
    937 								"GR_STATUS_PENDING from GRankCleanupAsync, got %s", 
    938 								SV_RankStatusString( cleanup_status ) );
    939 						}
    940 					}
    941 				}
    942 			}
    943 		}
    944 		SV_RankPoll();
    945 		
    946 		// should've finished by now
    947 		assert( (j++) < 68 );
    948 	}
    949 }
    950 
    951 /*
    952 ==============================================================================
    953 
    954 Private Functions
    955 
    956 ==============================================================================
    957 */
    958 
    959 /*
    960 =================
    961 SV_RankNewGameCBF
    962 =================
    963 */
    964 static void SV_RankNewGameCBF( GR_NEWGAME* gr_newgame, void* cbf_arg )
    965 {
    966 	GR_MATCH	match;
    967 	int			i;
    968 	
    969 	assert( gr_newgame != NULL );
    970 	assert( cbf_arg == NULL );
    971 
    972 	Com_DPrintf( "SV_RankNewGameCBF( %08X, %08X );\n", gr_newgame, cbf_arg );
    973 	
    974 	if( gr_newgame->status == GR_STATUS_OK )
    975 	{
    976 		char info[MAX_INFO_STRING];
    977 		char gameid[sizeof(s_ranked_players[i].game_id) * 4 / 3 + 2];
    978 		
    979 		// save game id
    980 		s_rankings_game_id = gr_newgame->game_id;
    981 		
    982 		// encode gameid 
    983 		memset(gameid,0,sizeof(gameid));
    984 		SV_RankEncodeGameID(s_rankings_game_id,gameid,sizeof(gameid));
    985 		
    986 		// set CS_GRANK rankingsGameID to pass to client
    987 		memset(info,0,sizeof(info));
    988 		Info_SetValueForKey( info, "rankingsGameKey", s_rankings_game_key );
    989 		Info_SetValueForKey( info, "rankingsGameID", gameid );
    990 		SV_SetConfigstring( CS_GRANK, info );
    991 
    992 		// initialize client status
    993 		for( i = 0; i < sv_maxclients->value; i++ )
    994 			s_ranked_players[i].grank_status = QGR_STATUS_NEW;
    995 
    996 		// start new match
    997 		match = GRankStartMatch( s_server_context );
    998 		s_server_match = match.match;
    999 
   1000 		// ready to go
   1001 		s_rankings_active = qtrue;
   1002 		Cvar_Set( "sv_rankingsActive", "1" );
   1003 
   1004 	}
   1005 	else if( gr_newgame->status == GR_STATUS_BADLEAGUE )
   1006 	{
   1007 		SV_RankError( "SV_RankNewGameCBF: Invalid League name\n" );
   1008 	}
   1009 	else
   1010 	{
   1011 		//GRank handle new game failure
   1012 		// force  SV_RankEnd() to run
   1013 		//SV_RankEnd();
   1014 		SV_RankError( "SV_RankNewGameCBF: Unexpected status %s", 
   1015 			SV_RankStatusString( gr_newgame->status ) );
   1016 	}
   1017 }
   1018 
   1019 /*
   1020 ================
   1021 SV_RankUserCBF
   1022 ================
   1023 */
   1024 static void SV_RankUserCBF( GR_LOGIN* gr_login, void* cbf_arg )
   1025 {
   1026 	ranked_player_t*	ranked_player;
   1027 	GR_STATUS			join_status;
   1028 	GR_STATUS			cleanup_status;
   1029 	
   1030 	assert( gr_login != NULL );
   1031 	assert( cbf_arg != NULL );
   1032 
   1033 	Com_DPrintf( "SV_RankUserCBF( %08X, %08X );\n", gr_login, cbf_arg );
   1034 	
   1035 	ranked_player = (ranked_player_t*)cbf_arg;
   1036 	assert(ranked_player);
   1037 	assert( ranked_player->context );
   1038 	
   1039 	switch( gr_login->status )
   1040 	{
   1041 		case GR_STATUS_OK:
   1042 			// attempt to join the game, proceed to SV_RankJoinGameCBF
   1043 			join_status = GRankJoinGameAsync
   1044 				( 
   1045 					ranked_player->context,
   1046 					s_rankings_game_id,
   1047 					SV_RankJoinGameCBF,
   1048 					cbf_arg,
   1049 					GR_OPT_END
   1050 				);
   1051 
   1052 			if( join_status != GR_STATUS_PENDING )
   1053 			{
   1054 				SV_RankError( "SV_RankUserCBF: Expected GR_STATUS_PENDING "
   1055 					"from GRankJoinGameAsync, got %s", 
   1056 					SV_RankStatusString( join_status ) );
   1057 			}
   1058 			break;
   1059 		case GR_STATUS_NOUSER:
   1060 			Com_DPrintf( "SV_RankUserCBF: Got status %s\n",
   1061 				SV_RankStatusString( gr_login->status ) );
   1062 			ranked_player->final_status = QGR_STATUS_NO_USER;
   1063 			break;
   1064 		case GR_STATUS_BADPASSWORD:
   1065 			Com_DPrintf( "SV_RankUserCBF: Got status %s\n",
   1066 				SV_RankStatusString( gr_login->status ) );
   1067 			ranked_player->final_status = QGR_STATUS_BAD_PASSWORD;
   1068 			break;
   1069 		case GR_STATUS_TIMEOUT:
   1070 			Com_DPrintf( "SV_RankUserCBF: Got status %s\n",
   1071 				SV_RankStatusString( gr_login->status ) );
   1072 			ranked_player->final_status = QGR_STATUS_TIMEOUT;
   1073 			break;
   1074 		default:
   1075 			Com_DPrintf( "SV_RankUserCBF: Unexpected status %s\n",
   1076 				SV_RankStatusString( gr_login->status ) );
   1077 			ranked_player->final_status = QGR_STATUS_ERROR;
   1078 			break;
   1079 	}
   1080 
   1081 	if( ranked_player->final_status != QGR_STATUS_NEW )
   1082 	{
   1083 		// login or create failed, so clean up before the next attempt
   1084 		cleanup_status = GRankCleanupAsync
   1085 			(
   1086 				ranked_player->context,
   1087 				0,
   1088 				SV_RankCleanupCBF,
   1089 				(void*)ranked_player,
   1090 				GR_OPT_END
   1091 			);
   1092 			
   1093 		if( cleanup_status != GR_STATUS_PENDING )
   1094 		{
   1095 			SV_RankError( "SV_RankUserCBF: Expected GR_STATUS_PENDING "
   1096 				"from GRankCleanupAsync, got %s", 
   1097 				SV_RankStatusString( cleanup_status ) );
   1098 			SV_RankCloseContext( ranked_player );
   1099 		}
   1100 	}
   1101 }
   1102 
   1103 /*
   1104 ================
   1105 SV_RankJoinGameCBF
   1106 ================
   1107 */
   1108 static void SV_RankJoinGameCBF( GR_JOINGAME* gr_joingame, void* cbf_arg )
   1109 {
   1110 	ranked_player_t*	ranked_player;
   1111 	GR_MATCH			match;
   1112 	GR_STATUS           cleanup_status;
   1113 
   1114 	assert( gr_joingame != NULL );
   1115 	assert( cbf_arg != NULL );
   1116 	
   1117 	Com_DPrintf( "SV_RankJoinGameCBF( %08X, %08X );\n", gr_joingame, cbf_arg );
   1118 	
   1119 	ranked_player = (ranked_player_t*)cbf_arg;
   1120 
   1121 	assert( ranked_player );
   1122 	assert( ranked_player->context != 0 );
   1123 	
   1124 	if( gr_joingame->status == GR_STATUS_OK )
   1125 	{
   1126 		int i;
   1127 		// save user id
   1128 		ranked_player->player_id = gr_joingame->player_id;
   1129 		memcpy(ranked_player->token,gr_joingame->token,
   1130 			sizeof(GR_PLAYER_TOKEN)) ;
   1131 		match = GRankStartMatch( ranked_player->context );
   1132 		ranked_player->match = match.match;
   1133 		ranked_player->grank = gr_joingame->rank;
   1134 
   1135 		// find the index and call SV_RankUserValidate
   1136 		for (i=0;i<sv_maxclients->value;i++)
   1137 			if ( ranked_player == &s_ranked_players[i] )
   1138 				SV_RankUserValidate(i,NULL,NULL,0, gr_joingame->rank,ranked_player->name);
   1139 	}
   1140 	else
   1141 	{
   1142 		//GRand handle join game failure
   1143 		SV_RankError( "SV_RankJoinGameCBF: Unexpected status %s",
   1144 			SV_RankStatusString( gr_joingame->status ) );
   1145 		
   1146 		cleanup_status = GRankCleanupAsync
   1147 			(
   1148 				ranked_player->context,
   1149 				0,
   1150 				SV_RankCleanupCBF,
   1151 				cbf_arg,
   1152 				GR_OPT_END
   1153 			);
   1154 		
   1155 		if( cleanup_status != GR_STATUS_PENDING )
   1156 		{
   1157 			SV_RankError( "SV_RankJoinGameCBF: Expected "
   1158 				"GR_STATUS_PENDING from GRankCleanupAsync, got %s", 
   1159 				SV_RankStatusString( cleanup_status ) );
   1160 			SV_RankCloseContext( ranked_player );
   1161 		}
   1162 	}		
   1163 }
   1164 
   1165 /*
   1166 ================
   1167 SV_RankSendReportsCBF
   1168 ================
   1169 */
   1170 static void SV_RankSendReportsCBF( GR_STATUS* status, void* cbf_arg )
   1171 {
   1172 	ranked_player_t*	ranked_player;
   1173 	GR_CONTEXT			context;
   1174 	GR_STATUS			cleanup_status;
   1175 
   1176 	assert( status != NULL );
   1177 	// NULL cbf_arg means server is sending match reports
   1178 	
   1179 	Com_DPrintf( "SV_RankSendReportsCBF( %08X, %08X );\n", status, cbf_arg );
   1180 	
   1181 	ranked_player = (ranked_player_t*)cbf_arg;
   1182 	if( ranked_player == NULL )
   1183 	{
   1184 		Com_DPrintf( "SV_RankSendReportsCBF: server\n" );
   1185 		context = s_server_context;
   1186 	}
   1187 	else
   1188 	{
   1189 		Com_DPrintf( "SV_RankSendReportsCBF: player\n" );
   1190 		context = ranked_player->context;
   1191 	}
   1192 
   1193 	//assert( context != 0 );
   1194 	if( *status != GR_STATUS_OK )
   1195 	{
   1196 		SV_RankError( "SV_RankSendReportsCBF: Unexpected status %s",
   1197 			SV_RankStatusString( *status ) );
   1198 	}
   1199 	
   1200 	if( context == 0 )
   1201 	{
   1202 		Com_DPrintf( "SV_RankSendReportsCBF: WARNING: context == 0" );
   1203 		SV_RankCloseContext( ranked_player );
   1204 	}
   1205 	else
   1206 	{
   1207 		cleanup_status = GRankCleanupAsync
   1208 			(
   1209 				context,
   1210 				0,
   1211 				SV_RankCleanupCBF,
   1212 				cbf_arg,
   1213 				GR_OPT_END
   1214 			);
   1215 		
   1216 		if( cleanup_status != GR_STATUS_PENDING )
   1217 		{
   1218 			SV_RankError( "SV_RankSendReportsCBF: Expected "
   1219 				"GR_STATUS_PENDING from GRankCleanupAsync, got %s", 
   1220 				SV_RankStatusString( cleanup_status ) );
   1221 			SV_RankCloseContext( ranked_player );
   1222 		}
   1223 	}
   1224 }
   1225 
   1226 /*
   1227 ================
   1228 SV_RankCleanupCBF
   1229 ================
   1230 */
   1231 static void SV_RankCleanupCBF( GR_STATUS* status, void* cbf_arg )
   1232 {
   1233 	ranked_player_t*	ranked_player;
   1234 	ranked_player = (ranked_player_t*)cbf_arg;
   1235 
   1236 	assert( status != NULL );
   1237 	// NULL cbf_arg means server is cleaning up
   1238 
   1239 	Com_DPrintf( "SV_RankCleanupCBF( %08X, %08X );\n", status, cbf_arg );
   1240 	
   1241 	if( *status != GR_STATUS_OK )
   1242 	{
   1243 		SV_RankError( "SV_RankCleanupCBF: Unexpected status %s",
   1244 			SV_RankStatusString( *status ) );
   1245 	}
   1246 
   1247 	SV_RankCloseContext( ranked_player );
   1248 }
   1249 
   1250 /*
   1251 ================
   1252 SV_RankCloseContext
   1253 ================
   1254 */
   1255 static void SV_RankCloseContext( ranked_player_t* ranked_player )
   1256 {
   1257 	if( ranked_player == NULL )
   1258 	{
   1259 		// server cleanup
   1260 		if( s_server_context == 0 )
   1261 		{
   1262 			return;
   1263 		}
   1264 		s_server_context = 0;
   1265 		s_server_match = 0;
   1266 	}
   1267 	else
   1268 	{
   1269 		// player cleanup
   1270 		if( s_ranked_players == NULL )
   1271 		{
   1272 			return;
   1273 		}
   1274 		if( ranked_player->context == 0 )
   1275 		{
   1276 			return;
   1277 		}
   1278 		ranked_player->context = 0;
   1279 		ranked_player->match = 0;
   1280 		ranked_player->player_id = 0;
   1281 		memset( ranked_player->token, 0, sizeof(GR_PLAYER_TOKEN) );
   1282 		ranked_player->grank_status = ranked_player->final_status;
   1283 		ranked_player->final_status = QGR_STATUS_NEW;
   1284 		ranked_player->name[0] = '\0';
   1285 	}
   1286 
   1287 	assert( s_rankings_contexts > 0 );
   1288 	s_rankings_contexts--;
   1289 	Com_DPrintf( "SV_RankCloseContext: s_rankings_contexts = %d\n", 
   1290 		s_rankings_contexts );
   1291 
   1292 	if( s_rankings_contexts == 0 )
   1293 	{
   1294 		GRankLogLevel( GRLOG_OFF );
   1295 		
   1296 		if( s_ranked_players != NULL )
   1297 		{
   1298 			Z_Free( s_ranked_players );
   1299 			s_ranked_players = NULL;
   1300 		}
   1301 
   1302 		s_rankings_active = qfalse;
   1303 		Cvar_Set( "sv_rankingsActive", "0" );
   1304 	}
   1305 }
   1306 
   1307 /*
   1308 ================
   1309 SV_RankAsciiEncode
   1310 
   1311 Encodes src_len bytes of binary data from the src buffer as ASCII text, 
   1312 using 6 bits per character. The result string is null-terminated and 
   1313 stored in the dest buffer.
   1314 
   1315 The dest buffer must be at least (src_len * 4) / 3 + 2 bytes in length.
   1316 
   1317 Returns the length of the result string, not including the null.
   1318 ================
   1319 */
   1320 static int SV_RankAsciiEncode( char* dest, const unsigned char* src, 
   1321 	int src_len )
   1322 {
   1323 	unsigned char	bin[3];
   1324 	unsigned char	txt[4];
   1325 	int				dest_len = 0;
   1326 	int				i;
   1327 	int				j;
   1328 	int				num_chars;
   1329 
   1330 	assert( dest != NULL );
   1331 	assert( src != NULL );
   1332 	
   1333 	for( i = 0; i < src_len; i += 3 )
   1334 	{
   1335 		// read three bytes of input
   1336 		for( j = 0; j < 3; j++ )
   1337 		{
   1338 			bin[j] = (i + j < src_len) ? src[i + j] : 0;
   1339 		}
   1340 
   1341 		// get four 6-bit values from three bytes
   1342 		txt[0] = bin[0] >> 2;
   1343 		txt[1] = ((bin[0] << 4) | (bin[1] >> 4)) & 63;
   1344 		txt[2] = ((bin[1] << 2) | (bin[2] >> 6)) & 63;
   1345 		txt[3] = bin[2] & 63;
   1346 
   1347 		// store ASCII encoding of 6-bit values
   1348 		num_chars = (i + 2 < src_len) ? 4 : ((src_len - i) * 4) / 3 + 1;
   1349 		for( j = 0; j < num_chars; j++ )
   1350 		{
   1351 			dest[dest_len++] = s_ascii_encoding[txt[j]];
   1352 		}
   1353 	}
   1354 	
   1355 	dest[dest_len] = '\0';
   1356 
   1357 	return dest_len;
   1358 }
   1359 
   1360 /*
   1361 ================
   1362 SV_RankAsciiDecode
   1363 
   1364 Decodes src_len characters of ASCII text from the src buffer, stores 
   1365 the binary result in the dest buffer.
   1366 
   1367 The dest buffer must be at least (src_len * 3) / 4 bytes in length.
   1368 
   1369 Returns the length of the binary result, or zero for invalid input.
   1370 ================
   1371 */
   1372 static int SV_RankAsciiDecode( unsigned char* dest, const char* src, 
   1373 	int src_len )
   1374 {
   1375 	static unsigned char	s_inverse_encoding[256];
   1376 	static char				s_init = 0;
   1377 	
   1378 	unsigned char	bin[3];
   1379 	unsigned char	txt[4];
   1380 	int				dest_len = 0;
   1381 	int				i;
   1382 	int				j;
   1383 	int				num_bytes;
   1384 	
   1385 	assert( dest != NULL );
   1386 	assert( src != NULL );
   1387 
   1388 	if( !s_init )
   1389 	{
   1390 		// initialize lookup table for decoding
   1391 		memset( s_inverse_encoding, 255, sizeof(s_inverse_encoding) );
   1392 		for( i = 0; i < 64; i++ )
   1393 		{
   1394 			s_inverse_encoding[s_ascii_encoding[i]] = i;
   1395 		}
   1396 		s_init = 1;
   1397 	}
   1398 	
   1399 	for( i = 0; i < src_len; i += 4 )
   1400 	{
   1401 		// read four characters of input, decode them to 6-bit values
   1402 		for( j = 0; j < 4; j++ )
   1403 		{
   1404 			txt[j] = (i + j < src_len) ? s_inverse_encoding[src[i + j]] : 0;
   1405 			if (txt[j] == 255)
   1406 			{
   1407 				return 0; // invalid input character
   1408 			}
   1409 		}
   1410 		
   1411 		// get three bytes from four 6-bit values
   1412 		bin[0] = (txt[0] << 2) | (txt[1] >> 4);
   1413 		bin[1] = (txt[1] << 4) | (txt[2] >> 2);
   1414 		bin[2] = (txt[2] << 6) | txt[3];
   1415 
   1416 		// store binary data
   1417 		num_bytes = (i + 3 < src_len) ? 3 : ((src_len - i) * 3) / 4;
   1418 		for( j = 0; j < num_bytes; j++ )
   1419 		{
   1420 			dest[dest_len++] = bin[j];
   1421 		}
   1422 	}
   1423 
   1424 	return dest_len;
   1425 }
   1426 
   1427 /*
   1428 ================
   1429 SV_RankEncodeGameID
   1430 ================
   1431 */
   1432 static void SV_RankEncodeGameID( uint64_t game_id, char* result, 
   1433 	int len )
   1434 {
   1435 	assert( result != NULL );
   1436 
   1437 	if( len < ( ( sizeof(game_id) * 4) / 3 + 2) )
   1438 	{
   1439 		Com_DPrintf( "SV_RankEncodeGameID: result buffer too small\n" );
   1440 		result[0] = '\0';
   1441 	}
   1442 	else
   1443 	{
   1444 		qint64 gameid = LittleLong64(*(qint64*)&game_id);
   1445 		SV_RankAsciiEncode( result, (unsigned char*)&gameid, 
   1446 			sizeof(qint64) );
   1447 	}
   1448 }
   1449 
   1450 /*
   1451 ================
   1452 SV_RankDecodePlayerID
   1453 ================
   1454 */
   1455 static uint64_t SV_RankDecodePlayerID( const char* string )
   1456 {
   1457 	unsigned char	buffer[9];
   1458 	int len;
   1459 	qint64	player_id;
   1460 
   1461 	assert( string != NULL );
   1462 	
   1463 	len = strlen (string) ;
   1464 	Com_DPrintf( "SV_RankDecodePlayerID: string length %d\n",len );
   1465 	SV_RankAsciiDecode( buffer, string, len );
   1466 	player_id = LittleLong64(*(qint64*)buffer);
   1467 	return *(uint64_t*)&player_id;
   1468 }
   1469 
   1470 /*
   1471 ================
   1472 SV_RankDecodePlayerKey
   1473 ================
   1474 */
   1475 static void SV_RankDecodePlayerKey( const char* string, GR_PLAYER_TOKEN key )
   1476 {
   1477 	unsigned char	buffer[1400];
   1478 	int len;
   1479 	assert( string != NULL );
   1480 
   1481 	len = strlen (string) ;
   1482 	Com_DPrintf( "SV_RankDecodePlayerKey: string length %d\n",len );
   1483 	
   1484 	memset(key,0,sizeof(GR_PLAYER_TOKEN));
   1485 	memset(buffer,0,sizeof(buffer));
   1486 	memcpy( key, buffer, SV_RankAsciiDecode( buffer, string, len ) );
   1487 }
   1488 
   1489 /*
   1490 ================
   1491 SV_RankStatusString
   1492 ================
   1493 */
   1494 static char* SV_RankStatusString( GR_STATUS status )
   1495 {
   1496 	switch( status )
   1497 	{
   1498 		case GR_STATUS_OK:				return "GR_STATUS_OK";
   1499 		case GR_STATUS_ERROR:			return "GR_STATUS_ERROR";
   1500 		case GR_STATUS_BADPARAMS:		return "GR_STATUS_BADPARAMS";
   1501 		case GR_STATUS_NETWORK:			return "GR_STATUS_NETWORK";
   1502 		case GR_STATUS_NOUSER:			return "GR_STATUS_NOUSER";
   1503 		case GR_STATUS_BADPASSWORD:		return "GR_STATUS_BADPASSWORD";
   1504 		case GR_STATUS_BADGAME:			return "GR_STATUS_BADGAME";
   1505 		case GR_STATUS_PENDING:			return "GR_STATUS_PENDING";
   1506 		case GR_STATUS_BADDOMAIN:		return "GR_STATUS_BADDOMAIN";
   1507 		case GR_STATUS_DOMAINLOCK:		return "GR_STATUS_DOMAINLOCK";
   1508 		case GR_STATUS_TIMEOUT:			return "GR_STATUS_TIMEOUT";
   1509 		case GR_STATUS_INVALIDUSER:	    return "GR_STATUS_INVALIDUSER";
   1510 		case GR_STATUS_INVALIDCONTEXT:	return "GR_STATUS_INVALIDCONTEXT";
   1511 		default:						return "(UNKNOWN)";
   1512 	}
   1513 }
   1514 
   1515 /*
   1516 ================
   1517 SV_RankError
   1518 ================
   1519 */
   1520 static void SV_RankError( const char* fmt, ... )
   1521 {
   1522 	va_list	arg_ptr;
   1523 	char	text[1024];
   1524 
   1525 	va_start( arg_ptr, fmt );
   1526 	vsprintf( text, fmt, arg_ptr );
   1527 	va_end( arg_ptr );
   1528 
   1529 	Com_DPrintf( "****************************************\n" );
   1530 	Com_DPrintf( "SV_RankError: %s\n", text );
   1531 	Com_DPrintf( "****************************************\n" );
   1532 
   1533 	s_rankings_active = qfalse;
   1534 	Cvar_Set( "sv_rankingsActive", "0" );
   1535 	// FIXME - attempt clean shutdown?
   1536 }
   1537