📄 sv_client.c
字号:
Com_Printf( "clientDownload: %d : begining \"%s\"\n", cl - svs.clients, cl->downloadName );
missionPack = FS_idPak(cl->downloadName, "missionpack");
idPack = missionPack || FS_idPak(cl->downloadName, "baseq3");
if ( !sv_allowDownload->integer || idPack ||
( cl->downloadSize = FS_SV_FOpenFileRead( cl->downloadName, &cl->download ) ) <= 0 ) {
// cannot auto-download file
if (idPack) {
Com_Printf("clientDownload: %d : \"%s\" cannot download id pk3 files\n", cl - svs.clients, cl->downloadName);
if (missionPack) {
Com_sprintf(errorMessage, sizeof(errorMessage), "Cannot autodownload Team Arena file \"%s\"\n"
"The Team Arena mission pack can be found in your local game store.", cl->downloadName);
}
else {
Com_sprintf(errorMessage, sizeof(errorMessage), "Cannot autodownload id pk3 file \"%s\"", cl->downloadName);
}
} else if ( !sv_allowDownload->integer ) {
Com_Printf("clientDownload: %d : \"%s\" download disabled", cl - svs.clients, cl->downloadName);
if (sv_pure->integer) {
Com_sprintf(errorMessage, sizeof(errorMessage), "Could not download \"%s\" because autodownloading is disabled on the server.\n\n"
"You will need to get this file elsewhere before you "
"can connect to this pure server.\n", cl->downloadName);
} else {
Com_sprintf(errorMessage, sizeof(errorMessage), "Could not download \"%s\" because autodownloading is disabled on the server.\n\n"
"The server you are connecting to is not a pure server, "
"set autodownload to No in your settings and you might be "
"able to join the game anyway.\n", cl->downloadName);
}
} else {
// NOTE TTimo this is NOT supposed to happen unless bug in our filesystem scheme?
// if the pk3 is referenced, it must have been found somewhere in the filesystem
Com_Printf("clientDownload: %d : \"%s\" file not found on server\n", cl - svs.clients, cl->downloadName);
Com_sprintf(errorMessage, sizeof(errorMessage), "File \"%s\" not found on server for autodownloading.\n", cl->downloadName);
}
MSG_WriteByte( msg, svc_download );
MSG_WriteShort( msg, 0 ); // client is expecting block zero
MSG_WriteLong( msg, -1 ); // illegal file size
MSG_WriteString( msg, errorMessage );
*cl->downloadName = 0;
return;
}
// Init
cl->downloadCurrentBlock = cl->downloadClientBlock = cl->downloadXmitBlock = 0;
cl->downloadCount = 0;
cl->downloadEOF = qfalse;
}
// Perform any reads that we need to
while (cl->downloadCurrentBlock - cl->downloadClientBlock < MAX_DOWNLOAD_WINDOW &&
cl->downloadSize != cl->downloadCount) {
curindex = (cl->downloadCurrentBlock % MAX_DOWNLOAD_WINDOW);
if (!cl->downloadBlocks[curindex])
cl->downloadBlocks[curindex] = Z_Malloc( MAX_DOWNLOAD_BLKSIZE );
cl->downloadBlockSize[curindex] = FS_Read( cl->downloadBlocks[curindex], MAX_DOWNLOAD_BLKSIZE, cl->download );
if (cl->downloadBlockSize[curindex] < 0) {
// EOF right now
cl->downloadCount = cl->downloadSize;
break;
}
cl->downloadCount += cl->downloadBlockSize[curindex];
// Load in next block
cl->downloadCurrentBlock++;
}
// Check to see if we have eof condition and add the EOF block
if (cl->downloadCount == cl->downloadSize &&
!cl->downloadEOF &&
cl->downloadCurrentBlock - cl->downloadClientBlock < MAX_DOWNLOAD_WINDOW) {
cl->downloadBlockSize[cl->downloadCurrentBlock % MAX_DOWNLOAD_WINDOW] = 0;
cl->downloadCurrentBlock++;
cl->downloadEOF = qtrue; // We have added the EOF block
}
// Loop up to window size times based on how many blocks we can fit in the
// client snapMsec and rate
// based on the rate, how many bytes can we fit in the snapMsec time of the client
// normal rate / snapshotMsec calculation
rate = cl->rate;
if ( sv_maxRate->integer ) {
if ( sv_maxRate->integer < 1000 ) {
Cvar_Set( "sv_MaxRate", "1000" );
}
if ( sv_maxRate->integer < rate ) {
rate = sv_maxRate->integer;
}
}
if (!rate) {
blockspersnap = 1;
} else {
blockspersnap = ( (rate * cl->snapshotMsec) / 1000 + MAX_DOWNLOAD_BLKSIZE ) /
MAX_DOWNLOAD_BLKSIZE;
}
if (blockspersnap < 0)
blockspersnap = 1;
while (blockspersnap--) {
// Write out the next section of the file, if we have already reached our window,
// automatically start retransmitting
if (cl->downloadClientBlock == cl->downloadCurrentBlock)
return; // Nothing to transmit
if (cl->downloadXmitBlock == cl->downloadCurrentBlock) {
// We have transmitted the complete window, should we start resending?
//FIXME: This uses a hardcoded one second timeout for lost blocks
//the timeout should be based on client rate somehow
if (svs.time - cl->downloadSendTime > 1000)
cl->downloadXmitBlock = cl->downloadClientBlock;
else
return;
}
// Send current block
curindex = (cl->downloadXmitBlock % MAX_DOWNLOAD_WINDOW);
MSG_WriteByte( msg, svc_download );
MSG_WriteShort( msg, cl->downloadXmitBlock );
// block zero is special, contains file size
if ( cl->downloadXmitBlock == 0 )
MSG_WriteLong( msg, cl->downloadSize );
MSG_WriteShort( msg, cl->downloadBlockSize[curindex] );
// Write the block
if ( cl->downloadBlockSize[curindex] ) {
MSG_WriteData( msg, cl->downloadBlocks[curindex], cl->downloadBlockSize[curindex] );
}
Com_DPrintf( "clientDownload: %d : writing block %d\n", cl - svs.clients, cl->downloadXmitBlock );
// Move on to the next block
// It will get sent with next snap shot. The rate will keep us in line.
cl->downloadXmitBlock++;
cl->downloadSendTime = svs.time;
}
}
/*
=================
SV_Disconnect_f
The client is going to disconnect, so remove the connection immediately FIXME: move to game?
=================
*/
static void SV_Disconnect_f( client_t *cl ) {
SV_DropClient( cl, "disconnected" );
}
/*
=================
SV_VerifyPaks_f
If we are pure, disconnect the client if they do no meet the following conditions:
1. the first two checksums match our view of cgame and ui
2. there are no any additional checksums that we do not have
This routine would be a bit simpler with a goto but i abstained
=================
*/
static void SV_VerifyPaks_f( client_t *cl ) {
int nChkSum1, nChkSum2, nClientPaks, nServerPaks, i, j, nCurArg;
int nClientChkSum[1024];
int nServerChkSum[1024];
const char *pPaks, *pArg;
qboolean bGood = qtrue;
// if we are pure, we "expect" the client to load certain things from
// certain pk3 files, namely we want the client to have loaded the
// ui and cgame that we think should be loaded based on the pure setting
//
if ( sv_pure->integer != 0 ) {
bGood = qtrue;
nChkSum1 = nChkSum2 = 0;
// we run the game, so determine which cgame and ui the client "should" be running
bGood = (FS_FileIsInPAK("vm/cgame.qvm", &nChkSum1) == 1);
if (bGood)
bGood = (FS_FileIsInPAK("vm/ui.qvm", &nChkSum2) == 1);
nClientPaks = Cmd_Argc();
// start at arg 2 ( skip serverId cl_paks )
nCurArg = 1;
pArg = Cmd_Argv(nCurArg++);
if(!pArg) {
bGood = qfalse;
}
else
{
// https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=475
// we may get incoming cp sequences from a previous checksumFeed, which we need to ignore
// since serverId is a frame count, it always goes up
if (atoi(pArg) < sv.checksumFeedServerId)
{
Com_DPrintf("ignoring outdated cp command from client %s\n", cl->name);
return;
}
}
// we basically use this while loop to avoid using 'goto' :)
while (bGood) {
// must be at least 6: "cl_paks cgame ui @ firstref ... numChecksums"
// numChecksums is encoded
if (nClientPaks < 6) {
bGood = qfalse;
break;
}
// verify first to be the cgame checksum
pArg = Cmd_Argv(nCurArg++);
if (!pArg || *pArg == '@' || atoi(pArg) != nChkSum1 ) {
bGood = qfalse;
break;
}
// verify the second to be the ui checksum
pArg = Cmd_Argv(nCurArg++);
if (!pArg || *pArg == '@' || atoi(pArg) != nChkSum2 ) {
bGood = qfalse;
break;
}
// should be sitting at the delimeter now
pArg = Cmd_Argv(nCurArg++);
if (*pArg != '@') {
bGood = qfalse;
break;
}
// store checksums since tokenization is not re-entrant
for (i = 0; nCurArg < nClientPaks; i++) {
nClientChkSum[i] = atoi(Cmd_Argv(nCurArg++));
}
// store number to compare against (minus one cause the last is the number of checksums)
nClientPaks = i - 1;
// make sure none of the client check sums are the same
// so the client can't send 5 the same checksums
for (i = 0; i < nClientPaks; i++) {
for (j = 0; j < nClientPaks; j++) {
if (i == j)
continue;
if (nClientChkSum[i] == nClientChkSum[j]) {
bGood = qfalse;
break;
}
}
if (bGood == qfalse)
break;
}
if (bGood == qfalse)
break;
// get the pure checksums of the pk3 files loaded by the server
pPaks = FS_LoadedPakPureChecksums();
Cmd_TokenizeString( pPaks );
nServerPaks = Cmd_Argc();
if (nServerPaks > 1024)
nServerPaks = 1024;
for (i = 0; i < nServerPaks; i++) {
nServerChkSum[i] = atoi(Cmd_Argv(i));
}
// check if the client has provided any pure checksums of pk3 files not loaded by the server
for (i = 0; i < nClientPaks; i++) {
for (j = 0; j < nServerPaks; j++) {
if (nClientChkSum[i] == nServerChkSum[j]) {
break;
}
}
if (j >= nServerPaks) {
bGood = qfalse;
break;
}
}
if ( bGood == qfalse ) {
break;
}
// check if the number of checksums was correct
nChkSum1 = sv.checksumFeed;
for (i = 0; i < nClientPaks; i++) {
nChkSum1 ^= nClientChkSum[i];
}
nChkSum1 ^= nClientPaks;
if (nChkSum1 != nClientChkSum[nClientPaks]) {
bGood = qfalse;
break;
}
// break out
break;
}
cl->gotCP = qtrue;
if (bGood) {
cl->pureAuthentic = 1;
}
else {
cl->pureAuthentic = 0;
cl->nextSnapshotTime = -1;
cl->state = CS_ACTIVE;
SV_SendClientSnapshot( cl );
SV_DropClient( cl, "Unpure client detected. Invalid .PK3 files referenced!" );
}
}
}
/*
=================
SV_ResetPureClient_f
=================
*/
static void SV_ResetPureClient_f( client_t *cl ) {
cl->pureAuthentic = 0;
cl->gotCP = qfalse;
}
/*
=================
SV_UserinfoChanged
Pull specific info from a newly changed userinfo string
into a more C friendly form.
=================
*/
void SV_UserinfoChanged( client_t *cl ) {
char *val;
int i;
// name for C code
Q_strncpyz( cl->name, Info_ValueForKey (cl->userinfo, "name"), sizeof(cl->name) );
// rate command
// if the client is on the same subnet as the server and we aren't running an
// internet public server, assume they don't need a rate choke
if ( Sys_IsLANAddress( cl->netchan.remoteAddress ) && com_dedicated->integer != 2 && sv_lanForceRate->integer == 1) {
cl->rate = 99999; // lans should not rate limit
} else {
val = Info_ValueForKey (cl->userinfo, "rate");
if (strlen(val)) {
i = atoi(val);
cl->rate = i;
if (cl->rate < 1000) {
cl->rate = 1000;
} else if (cl->rate > 90000) {
cl->rate = 90000;
}
} else {
cl->rate = 3000;
}
}
val = Info_ValueForKey (cl->userinfo, "handicap");
if (strlen(val)) {
i = atoi(val);
if (i<=0 || i>100 || strlen(val) > 4) {
Info_SetValueForKey( cl->userinfo, "handicap", "100" );
}
}
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -