xash3d-fwgs/engine/server/sv_custom.c

579 lines
13 KiB
C

/*
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, "%s", 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 ), DEFAULT_SOUNDPATH "%s", 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_WriteStringf( msg, "upload \"!MD5%s\"\n", MD5_Print( p.rgucMD5_hash ));
return false;
}
void SV_MoveToOnHandList( sv_client_t *cl, resource_t *pResource )
{
if( !pResource )
{
Con_Reportf( "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 )
{
Con_Reportf( S_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
{
Con_Reportf( S_ERROR "Non customization in upload queue!\n" );
SV_MoveToOnHandList( cl, p );
}
}
}
}
void SV_SendResource( resource_t *pResource, sizebuf_t *msg )
{
static byte nullrguc[sizeof( pResource->rguc_reserved )];
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 );
}