mirror of
https://github.com/DrBeef/ioq3quest.git
synced 2024-11-27 06:13:01 +00:00
1537 lines
35 KiB
C
1537 lines
35 KiB
C
/*
|
|
===========================================================================
|
|
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 Foobar; 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?
|
|
}
|
|
|