You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
578 lines
13 KiB
578 lines
13 KiB
7 years ago
|
/*
|
||
|
sv_custom.c - downloading custom resources
|
||
|
Copyright (C) 2010 Uncle Mike
|
||
|
|
||
|
This program 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 3 of the License, or
|
||
|
(at your option) any later version.
|
||
|
|
||
|
This program 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.
|
||
|
*/
|
||
|
|
||
|
#include "common.h"
|
||
|
#include "server.h"
|
||
|
|
||
|
void SV_CreateCustomizationList( sv_client_t *cl )
|
||
|
{
|
||
|
resource_t *pResource;
|
||
|
customization_t *pList, *pCust;
|
||
|
qboolean bFound;
|
||
|
int nLumps;
|
||
|
|
||
|
cl->customdata.pNext = NULL;
|
||
|
|
||
|
for( pResource = cl->resourcesonhand.pNext; pResource != &cl->resourcesonhand; pResource = pResource->pNext )
|
||
|
{
|
||
|
bFound = false;
|
||
|
|
||
|
for( pList = cl->customdata.pNext; pList != NULL; pList = pList->pNext )
|
||
|
{
|
||
|
if( !memcmp( pList->resource.rgucMD5_hash, pResource->rgucMD5_hash, 16 ))
|
||
|
{
|
||
|
bFound = true;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if( !bFound )
|
||
|
{
|
||
|
nLumps = 0;
|
||
|
|
||
|
if( COM_CreateCustomization( &cl->customdata, pResource, -1, FCUST_FROMHPAK|FCUST_WIPEDATA, &pCust, &nLumps ))
|
||
|
{
|
||
|
pCust->nUserData2 = nLumps;
|
||
|
svgame.dllFuncs.pfnPlayerCustomization( cl->edict, pCust );
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
if( sv_allow_upload.value )
|
||
|
Con_Printf( "Ignoring invalid custom decal from %s\n", cl->name );
|
||
|
else Con_Printf( "Ignoring custom decal from %s\n", cl->name );
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
Con_Printf( S_WARN "SV_CreateCustomization list, ignoring dup. resource for player %s\n", cl->name );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
qboolean SV_FileInConsistencyList( const char *filename, consistency_t **ppout )
|
||
|
{
|
||
|
int i;
|
||
|
|
||
|
if( ppout != NULL )
|
||
|
*ppout = NULL;
|
||
|
|
||
|
for( i = 0; i < MAX_MODELS; i++ )
|
||
|
{
|
||
|
consistency_t *pc = &sv.consistency_list[i];
|
||
|
|
||
|
if( !pc->filename )
|
||
|
break;
|
||
|
|
||
|
if( !Q_stricmp( pc->filename, filename ))
|
||
|
{
|
||
|
if( ppout != NULL )
|
||
|
*ppout = pc;
|
||
|
return true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
void SV_ParseConsistencyResponse( sv_client_t *cl, sizebuf_t *msg )
|
||
|
{
|
||
|
int i, c, idx, value;
|
||
|
byte readbuffer[32];
|
||
|
byte nullbuffer[32];
|
||
|
byte resbuffer[32];
|
||
|
qboolean invalid_type;
|
||
|
vec3_t cmins, cmaxs;
|
||
|
int badresindex;
|
||
|
vec3_t mins, maxs;
|
||
|
FORCE_TYPE ft;
|
||
|
resource_t *r;
|
||
|
|
||
|
memset( nullbuffer, 0, sizeof( nullbuffer ));
|
||
|
invalid_type = false;
|
||
|
badresindex = 0;
|
||
|
c = 0;
|
||
|
|
||
|
while( MSG_ReadOneBit( msg ))
|
||
|
{
|
||
|
idx = MSG_ReadUBitLong( msg, MAX_MODEL_BITS );
|
||
|
if( idx < 0 || idx >= sv.num_resources )
|
||
|
break;
|
||
|
|
||
|
r = &sv.resources[idx];
|
||
|
|
||
|
if( !FBitSet( r->ucFlags, RES_CHECKFILE ))
|
||
|
break;
|
||
|
|
||
|
memcpy( readbuffer, r->rguc_reserved, 32 );
|
||
|
|
||
|
if( !memcmp( readbuffer, nullbuffer, 32 ))
|
||
|
{
|
||
|
value = MSG_ReadUBitLong( msg, 32 );
|
||
|
|
||
|
// will be compare only first 4 bytes
|
||
|
if( value != *(int *)r->rgucMD5_hash )
|
||
|
badresindex = idx + 1;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
MSG_ReadBytes( msg, cmins, sizeof( cmins ));
|
||
|
MSG_ReadBytes( msg, cmaxs, sizeof( cmaxs ));
|
||
|
|
||
|
memcpy( resbuffer, r->rguc_reserved, 32 );
|
||
|
ft = resbuffer[0];
|
||
|
|
||
|
switch( ft )
|
||
|
{
|
||
|
case force_model_samebounds:
|
||
|
memcpy( mins, &resbuffer[0x01], sizeof( mins ));
|
||
|
memcpy( maxs, &resbuffer[0x0D], sizeof( maxs ));
|
||
|
|
||
|
if( !VectorCompare( cmins, mins ) || !VectorCompare( cmaxs, maxs ))
|
||
|
badresindex = idx + 1;
|
||
|
break;
|
||
|
case force_model_specifybounds:
|
||
|
memcpy( mins, &resbuffer[0x01], sizeof( mins ));
|
||
|
memcpy( maxs, &resbuffer[0x0D], sizeof( maxs ));
|
||
|
|
||
|
for( i = 0; i < 3; i++ )
|
||
|
{
|
||
|
if( cmins[i] < mins[i] || cmaxs[i] > maxs[i] )
|
||
|
{
|
||
|
badresindex = idx + 1;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
break;
|
||
|
default:
|
||
|
invalid_type = true;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if( invalid_type )
|
||
|
break;
|
||
|
c++;
|
||
|
}
|
||
|
|
||
|
if( sv.num_consistency != c )
|
||
|
{
|
||
|
Con_Printf( S_WARN "%s:%s sent bad file data\n", cl->name, NET_AdrToString( cl->netchan.remote_address ));
|
||
|
SV_DropClient( cl, false );
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if( badresindex != 0 )
|
||
|
{
|
||
|
char dropmessage[256];
|
||
|
|
||
|
dropmessage[0] = 0;
|
||
|
if( svgame.dllFuncs.pfnInconsistentFile( cl->edict, sv.resources[badresindex - 1].szFileName, dropmessage ))
|
||
|
{
|
||
|
if( COM_CheckString( dropmessage ))
|
||
|
SV_ClientPrintf( cl, dropmessage );
|
||
|
SV_DropClient( cl, false );
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
ClearBits( cl->flags, FCL_FORCE_UNMODIFIED );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void SV_TransferConsistencyInfo( void )
|
||
|
{
|
||
|
vec3_t mins, maxs;
|
||
|
int i, total = 0;
|
||
|
resource_t *pResource;
|
||
|
string filepath;
|
||
|
consistency_t *pc;
|
||
|
|
||
|
for( i = 0; i < sv.num_resources; i++ )
|
||
|
{
|
||
|
pResource = &sv.resources[i];
|
||
|
|
||
|
if( FBitSet( pResource->ucFlags, RES_CHECKFILE ))
|
||
|
continue; // already checked?
|
||
|
|
||
|
if( !SV_FileInConsistencyList( pResource->szFileName, &pc ))
|
||
|
continue;
|
||
|
|
||
|
SetBits( pResource->ucFlags, RES_CHECKFILE );
|
||
|
|
||
|
if( pResource->type == t_sound )
|
||
|
Q_snprintf( filepath, sizeof( filepath ), "%s%s", DEFAULT_SOUNDPATH, pResource->szFileName );
|
||
|
else Q_strncpy( filepath, pResource->szFileName, sizeof( filepath ));
|
||
|
|
||
|
MD5_HashFile( pResource->rgucMD5_hash, filepath, NULL );
|
||
|
|
||
|
if( pResource->type == t_model )
|
||
|
{
|
||
|
switch( pc->check_type )
|
||
|
{
|
||
|
case force_exactfile:
|
||
|
// only MD5 hash compare
|
||
|
break;
|
||
|
case force_model_samebounds:
|
||
|
if( !Mod_GetStudioBounds( filepath, mins, maxs ))
|
||
|
Host_Error( "Mod_GetStudioBounds: couldn't get bounds for %s\n", filepath );
|
||
|
memcpy( &pResource->rguc_reserved[0x01], mins, sizeof( mins ));
|
||
|
memcpy( &pResource->rguc_reserved[0x0D], maxs, sizeof( maxs ));
|
||
|
pResource->rguc_reserved[0] = pc->check_type;
|
||
|
break;
|
||
|
case force_model_specifybounds:
|
||
|
memcpy( &pResource->rguc_reserved[0x01], pc->mins, sizeof( pc->mins ));
|
||
|
memcpy( &pResource->rguc_reserved[0x0D], pc->maxs, sizeof( pc->maxs ));
|
||
|
pResource->rguc_reserved[0] = pc->check_type;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
total++;
|
||
|
}
|
||
|
|
||
|
sv.num_consistency = total;
|
||
|
}
|
||
|
|
||
|
void SV_SendConsistencyList( sv_client_t *cl, sizebuf_t *msg )
|
||
|
{
|
||
|
int i, lastcheck;
|
||
|
int delta;
|
||
|
|
||
|
if( svs.maxclients == 1 || !sv_consistency.value || !sv.num_consistency || FBitSet( cl->flags, FCL_HLTV_PROXY ))
|
||
|
{
|
||
|
ClearBits( cl->flags, FCL_FORCE_UNMODIFIED );
|
||
|
MSG_WriteOneBit( msg, 0 );
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
SetBits( cl->flags, FCL_FORCE_UNMODIFIED );
|
||
|
MSG_WriteOneBit( msg, 1 );
|
||
|
lastcheck = 0;
|
||
|
|
||
|
for( i = 0; i < sv.num_resources; i++ )
|
||
|
{
|
||
|
if( !FBitSet( sv.resources[i].ucFlags, RES_CHECKFILE ))
|
||
|
continue;
|
||
|
|
||
|
delta = i - lastcheck;
|
||
|
MSG_WriteOneBit( msg, 1 );
|
||
|
|
||
|
if( delta > 31 )
|
||
|
{
|
||
|
MSG_WriteOneBit( msg, 0 );
|
||
|
MSG_WriteUBitLong( msg, i, MAX_MODEL_BITS );
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
MSG_WriteOneBit( msg, 1 );
|
||
|
MSG_WriteUBitLong( msg, delta, 5 );
|
||
|
}
|
||
|
|
||
|
lastcheck = i;
|
||
|
}
|
||
|
|
||
|
// write end of the list
|
||
|
MSG_WriteOneBit( msg, 0 );
|
||
|
}
|
||
|
|
||
|
qboolean SV_CheckFile( sizebuf_t *msg, const char *filename )
|
||
|
{
|
||
|
resource_t p;
|
||
|
|
||
|
memset( &p, 0, sizeof( resource_t ));
|
||
|
|
||
|
if( Q_strlen( filename ) == 36 && !Q_strnicmp( filename, "!MD5", 4 ))
|
||
|
{
|
||
|
COM_HexConvert( filename + 4, 32, p.rgucMD5_hash );
|
||
|
|
||
|
if( HPAK_GetDataPointer( CUSTOM_RES_PATH, &p, NULL, NULL ))
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
if( !sv_allow_upload.value )
|
||
|
return true;
|
||
|
|
||
|
MSG_BeginServerCmd( msg, svc_stufftext );
|
||
|
MSG_WriteString( msg, va( "upload \"!MD5%s\"\n", MD5_Print( p.rgucMD5_hash )));
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
void SV_MoveToOnHandList( sv_client_t *cl, resource_t *pResource )
|
||
|
{
|
||
|
if( !pResource )
|
||
|
{
|
||
|
MsgDev( D_REPORT, "Null resource passed to SV_MoveToOnHandList\n" );
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
SV_RemoveFromResourceList( pResource );
|
||
|
SV_AddToResourceList( pResource, &cl->resourcesonhand );
|
||
|
}
|
||
|
|
||
|
void SV_AddToResourceList( resource_t *pResource, resource_t *pList )
|
||
|
{
|
||
|
if( pResource->pPrev != NULL || pResource->pNext != NULL )
|
||
|
{
|
||
|
MsgDev( D_ERROR, "Resource already linked\n" );
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
pResource->pPrev = pList->pPrev;
|
||
|
pResource->pNext = pList;
|
||
|
pList->pPrev->pNext = pResource;
|
||
|
pList->pPrev = pResource;
|
||
|
}
|
||
|
|
||
|
void SV_SendCustomization( sv_client_t *cl, int playernum, resource_t *pResource )
|
||
|
{
|
||
|
MSG_BeginServerCmd( &cl->netchan.message, svc_customization );
|
||
|
MSG_WriteByte( &cl->netchan.message, playernum ); // playernum
|
||
|
MSG_WriteByte( &cl->netchan.message, pResource->type );
|
||
|
MSG_WriteString( &cl->netchan.message, pResource->szFileName );
|
||
|
MSG_WriteShort( &cl->netchan.message, pResource->nIndex );
|
||
|
MSG_WriteLong( &cl->netchan.message, pResource->nDownloadSize );
|
||
|
MSG_WriteByte( &cl->netchan.message, pResource->ucFlags );
|
||
|
|
||
|
if( FBitSet( pResource->ucFlags, RES_CUSTOM ))
|
||
|
MSG_WriteBytes( &cl->netchan.message, pResource->rgucMD5_hash, 16 );
|
||
|
}
|
||
|
|
||
|
void SV_RemoveFromResourceList( resource_t *pResource )
|
||
|
{
|
||
|
pResource->pPrev->pNext = pResource->pNext;
|
||
|
pResource->pNext->pPrev = pResource->pPrev;
|
||
|
pResource->pPrev = NULL;
|
||
|
pResource->pNext = NULL;
|
||
|
}
|
||
|
|
||
|
void SV_ClearResourceList( resource_t *pList )
|
||
|
{
|
||
|
resource_t *p;
|
||
|
resource_t *n;
|
||
|
|
||
|
for( p = pList->pNext; pList != p && p; p = n )
|
||
|
{
|
||
|
n = p->pNext;
|
||
|
|
||
|
SV_RemoveFromResourceList( p );
|
||
|
Mem_Free( p );
|
||
|
}
|
||
|
|
||
|
pList->pPrev = pList;
|
||
|
pList->pNext = pList;
|
||
|
}
|
||
|
|
||
|
void SV_ClearResourceLists( sv_client_t *cl )
|
||
|
{
|
||
|
SV_ClearResourceList( &cl->resourcesneeded );
|
||
|
SV_ClearResourceList( &cl->resourcesonhand );
|
||
|
}
|
||
|
|
||
|
int SV_EstimateNeededResources( sv_client_t *cl )
|
||
|
{
|
||
|
int missing = 0;
|
||
|
int size = 0;
|
||
|
resource_t *p;
|
||
|
|
||
|
for( p = cl->resourcesneeded.pNext; p != &cl->resourcesneeded; p = p->pNext )
|
||
|
{
|
||
|
if( p->type != t_decal )
|
||
|
continue;
|
||
|
|
||
|
if( !HPAK_ResourceForHash( CUSTOM_RES_PATH, p->rgucMD5_hash, NULL ))
|
||
|
{
|
||
|
if( p->nDownloadSize != 0 )
|
||
|
{
|
||
|
SetBits( p->ucFlags, RES_WASMISSING );
|
||
|
size += p->nDownloadSize;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
missing++;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return size;
|
||
|
}
|
||
|
|
||
|
void SV_Customization( sv_client_t *pClient, resource_t *pResource, qboolean bSkipPlayer )
|
||
|
{
|
||
|
int i, nPlayerNumber = -1;
|
||
|
sv_client_t *cl;
|
||
|
|
||
|
i = pClient - svs.clients;
|
||
|
if( i >= 0 && i < svs.maxclients )
|
||
|
nPlayerNumber = i;
|
||
|
else Host_Error( "Couldn't find player index for customization.\n" );
|
||
|
|
||
|
for( i = 0, cl = svs.clients; i < svs.maxclients; i++, cl++ )
|
||
|
{
|
||
|
if( cl->state != cs_spawned )
|
||
|
continue;
|
||
|
|
||
|
if( FBitSet( cl->flags, FCL_FAKECLIENT ))
|
||
|
continue;
|
||
|
|
||
|
if( cl == pClient && bSkipPlayer )
|
||
|
continue;
|
||
|
|
||
|
SV_SendCustomization( cl, nPlayerNumber, pResource );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void SV_PropagateCustomizations( sv_client_t *pHost )
|
||
|
{
|
||
|
customization_t *pCust;
|
||
|
resource_t *pResource;
|
||
|
sv_client_t *cl;
|
||
|
int i;
|
||
|
|
||
|
for( i = 0, cl = svs.clients; i < svs.maxclients; i++, cl++ )
|
||
|
{
|
||
|
if( cl->state != cs_spawned )
|
||
|
continue;
|
||
|
|
||
|
if( FBitSet( cl->flags, FCL_FAKECLIENT ))
|
||
|
continue;
|
||
|
|
||
|
for( pCust = cl->customdata.pNext; pCust != NULL; pCust = pCust->pNext )
|
||
|
{
|
||
|
if( !pCust->bInUse ) continue;
|
||
|
pResource = &pCust->resource;
|
||
|
SV_SendCustomization( pHost, i, pResource );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void SV_RegisterResources( sv_client_t *pHost )
|
||
|
{
|
||
|
resource_t *pResource;
|
||
|
|
||
|
for( pResource = pHost->resourcesonhand.pNext; pResource != &pHost->resourcesonhand; pResource = pResource->pNext )
|
||
|
{
|
||
|
SV_CreateCustomizationList( pHost );
|
||
|
SV_Customization( pHost, pResource, true );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
qboolean SV_UploadComplete( sv_client_t *cl )
|
||
|
{
|
||
|
if( &cl->resourcesneeded != cl->resourcesneeded.pNext )
|
||
|
return false;
|
||
|
|
||
|
SV_RegisterResources( cl );
|
||
|
SV_PropagateCustomizations( cl );
|
||
|
|
||
|
if( sv_allow_upload.value )
|
||
|
Con_Printf( "Custom resource propagation complete.\n" );
|
||
|
cl->upstate = us_complete;
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
void SV_RequestMissingResources( void )
|
||
|
{
|
||
|
sv_client_t *cl;
|
||
|
int i;
|
||
|
|
||
|
for( i = 0, cl = svs.clients; i < svs.maxclients; i++, cl++ )
|
||
|
{
|
||
|
if( cl->state != cs_spawned )
|
||
|
continue;
|
||
|
|
||
|
if( cl->upstate == us_processing )
|
||
|
SV_UploadComplete( cl );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void SV_BatchUploadRequest( sv_client_t *cl )
|
||
|
{
|
||
|
string filename;
|
||
|
resource_t *p, *n;
|
||
|
|
||
|
for( p = cl->resourcesneeded.pNext; p != &cl->resourcesneeded; p = n )
|
||
|
{
|
||
|
n = p->pNext;
|
||
|
|
||
|
if( !FBitSet( p->ucFlags, RES_WASMISSING ))
|
||
|
{
|
||
|
SV_MoveToOnHandList( cl, p );
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
if( p->type == t_decal )
|
||
|
{
|
||
|
if( FBitSet( p->ucFlags, RES_CUSTOM ))
|
||
|
{
|
||
|
Q_snprintf( filename, sizeof( filename ), "!MD5%s", MD5_Print( p->rgucMD5_hash ));
|
||
|
|
||
|
if( SV_CheckFile( &cl->netchan.message, filename ))
|
||
|
SV_MoveToOnHandList( cl, p );
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
MsgDev( D_ERROR, "Non customization in upload queue!\n" );
|
||
|
SV_MoveToOnHandList( cl, p );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void SV_SendResource( resource_t *pResource, sizebuf_t *msg )
|
||
|
{
|
||
|
static byte nullrguc[36];
|
||
|
|
||
|
MSG_WriteUBitLong( msg, pResource->type, 4 );
|
||
|
MSG_WriteString( msg, pResource->szFileName );
|
||
|
MSG_WriteUBitLong( msg, pResource->nIndex, MAX_MODEL_BITS );
|
||
|
MSG_WriteSBitLong( msg, pResource->nDownloadSize, 24 ); // prevent to download a very big files?
|
||
|
MSG_WriteUBitLong( msg, pResource->ucFlags & ( RES_FATALIFMISSING|RES_WASMISSING ), 3 );
|
||
|
|
||
|
if( FBitSet( pResource->ucFlags, RES_CUSTOM ))
|
||
|
MSG_WriteBytes( msg, pResource->rgucMD5_hash, sizeof( pResource->rgucMD5_hash ));
|
||
|
|
||
|
if( memcmp( nullrguc, pResource->rguc_reserved, sizeof( nullrguc )))
|
||
|
{
|
||
|
MSG_WriteOneBit( msg, 1 );
|
||
|
MSG_WriteBytes( msg, pResource->rguc_reserved, sizeof( pResource->rguc_reserved ));
|
||
|
}
|
||
|
else MSG_WriteOneBit( msg, 0 );
|
||
|
}
|
||
|
|
||
|
void SV_SendResources( sv_client_t *cl, sizebuf_t *msg )
|
||
|
{
|
||
|
int i;
|
||
|
|
||
|
MSG_BeginServerCmd( msg, svc_resourcerequest );
|
||
|
MSG_WriteLong( msg, svs.spawncount );
|
||
|
MSG_WriteLong( msg, 0 );
|
||
|
|
||
|
if( COM_CheckString( sv_downloadurl.string ) && Q_strlen( sv_downloadurl.string ) < 256 )
|
||
|
{
|
||
|
MSG_BeginServerCmd( msg, svc_resourcelocation );
|
||
|
MSG_WriteString( msg, sv_downloadurl.string );
|
||
|
}
|
||
|
|
||
|
MSG_BeginServerCmd( msg, svc_resourcelist );
|
||
|
MSG_WriteUBitLong( msg, sv.num_resources, MAX_RESOURCE_BITS );
|
||
|
|
||
|
for( i = 0; i < sv.num_resources; i++ )
|
||
|
{
|
||
|
SV_SendResource( &sv.resources[i], msg );
|
||
|
}
|
||
|
|
||
|
SV_SendConsistencyList( cl, msg );
|
||
|
}
|