Exemple #1
0
void CmdLib_FPrintf( FileHandle_t hFile, const char *pFormat, ... )
{
	static CUtlVector<char> buf;
	if ( buf.Count() == 0 )
		buf.SetCount( 1024 );

	va_list marker;
	va_start( marker, pFormat );
	
	while ( 1 )
	{
		int ret = Q_vsnprintf( buf.Base(), buf.Count(), pFormat, marker );
		if ( ret >= 0 )
		{
			// Write the string.
			g_pFileSystem->Write( buf.Base(), ret, hFile );
			
			break;
		}
		else
		{
			// Make the buffer larger.
			int newSize = buf.Count() * 2;
			buf.SetCount( newSize );
			if ( buf.Count() != newSize )
			{
				Error( "CmdLib_FPrintf: can't allocate space for text." );
			}
		}
	}

	va_end( marker );
}
Exemple #2
0
//-----------------------------------------------------------------------------
// For every specular surface that wasn't referenced by some env_cubemap, call Cubemap_CreateTexInfo.
//-----------------------------------------------------------------------------
void Cubemap_AttachDefaultCubemapToSpecularSides( void )
{
	Cubemap_ResetCubemapSideData();
	Cubemap_InitCubemapSideData();

	CUtlVector<entitySideList_t> sideList;
	sideList.SetCount( num_entities );
	int i;
	for ( i = 0; i < num_entities; i++ )
	{
		sideList[i].firstBrushSide = 0;
		sideList[i].brushSideCount = 0;
	}

	for ( i = 0; i < nummapbrushes; i++ )
	{
		sideList[mapbrushes[i].entitynum].brushSideCount += mapbrushes[i].numsides;
	}
	int curSide = 0;
	for ( i = 0; i < num_entities; i++ )
	{
		sideList[i].firstBrushSide = curSide;
		curSide += sideList[i].brushSideCount;
	}

	int currentEntity = 0;
	for ( int iSide = 0; iSide < nummapbrushsides; ++iSide )
	{
		side_t *pSide = &brushsides[iSide];
		if ( !SideHasCubemapAndWasntManuallyReferenced( iSide ) )
			continue;

		while ( currentEntity < num_entities-1 && 
			iSide > sideList[currentEntity].firstBrushSide + sideList[currentEntity].brushSideCount )
		{
			currentEntity++;
		}

		int iCubemap = Cubemap_FindClosestCubemap( entities[currentEntity].origin, pSide );
		if ( iCubemap == -1 )
			continue;

#ifdef DEBUG
		if ( pSide->pMapDisp )
		{
			Assert( pSide->texinfo == pSide->pMapDisp->face.texinfo );
		}
#endif				
		pSide->texinfo = Cubemap_CreateTexInfo( pSide->texinfo, g_CubemapSamples[iCubemap].origin );
		if ( pSide->pMapDisp )
		{
			pSide->pMapDisp->face.texinfo = pSide->texinfo;
		}
	}
}
Exemple #3
0
//-----------------------------------------------------------------------------
// For every specular surface that wasn't referenced by some env_cubemap, call Cubemap_CreateTexInfo.
//-----------------------------------------------------------------------------
void Cubemap_AttachDefaultCubemapToSpecularSides( void )
{
	Cubemap_ResetCubemapSideData();
	Cubemap_InitCubemapSideData();

	// build a mapping from side to entity id so that we can get the entity origin
	CUtlVector<int> sideToEntityIndex;
	sideToEntityIndex.SetCount(g_MainMap->nummapbrushsides);
	int i;
	for ( i = 0; i < g_MainMap->nummapbrushsides; i++ )
	{
		sideToEntityIndex[i] = -1;
	}

	for ( i = 0; i < g_MainMap->nummapbrushes; i++ )
	{
		int entityIndex = g_MainMap->mapbrushes[i].entitynum;
		for ( int j = 0; j < g_MainMap->mapbrushes[i].numsides; j++ )
		{
			side_t *side = &g_MainMap->mapbrushes[i].original_sides[j];
			int sideIndex = side - g_MainMap->brushsides;
			sideToEntityIndex[sideIndex] = entityIndex;
		}
	}

	for ( int iSide = 0; iSide < g_MainMap->nummapbrushsides; ++iSide )
	{
		side_t *pSide = &g_MainMap->brushsides[iSide];
		if ( !SideHasCubemapAndWasntManuallyReferenced( iSide ) )
			continue;


		int currentEntity = sideToEntityIndex[iSide];

		int iCubemap = Cubemap_FindClosestCubemap( g_MainMap->entities[currentEntity].origin, pSide );
		if ( iCubemap == -1 )
			continue;

#ifdef DEBUG
		if ( pSide->pMapDisp )
		{
			Assert( pSide->texinfo == pSide->pMapDisp->face.texinfo );
		}
#endif				
		pSide->texinfo = Cubemap_CreateTexInfo( pSide->texinfo, g_CubemapSamples[iCubemap].origin );
		if ( pSide->pMapDisp )
		{
			pSide->pMapDisp->face.texinfo = pSide->texinfo;
		}
	}
}
void WorldVertexTransitionFixup( void )
{
	CUtlVector<entitySideList_t> sideList;
	sideList.SetCount( g_MainMap->num_entities );
	int i;
	for ( i = 0; i < g_MainMap->num_entities; i++ )
	{
		sideList[i].firstBrushSide = 0;
		sideList[i].brushSideCount = 0;
	}

	for ( i = 0; i < g_MainMap->nummapbrushes; i++ )
	{
		sideList[g_MainMap->mapbrushes[i].entitynum].brushSideCount += g_MainMap->mapbrushes[i].numsides;
	}
	int curSide = 0;
	for ( i = 0; i < g_MainMap->num_entities; i++ )
	{
		sideList[i].firstBrushSide = curSide;
		curSide += sideList[i].brushSideCount;
	}

	int currentEntity = 0;
	for ( int iSide = 0; iSide < g_MainMap->nummapbrushsides; ++iSide )
	{
		side_t *pSide = &g_MainMap->brushsides[iSide];

		// skip displacments
		if ( pSide->pMapDisp )
			continue;

		if( pSide->texinfo < 0 )
			continue;

		const char *pShaderName = GetShaderNameForTexInfo( pSide->texinfo );
		if ( !pShaderName || !Q_stristr( pShaderName, "worldvertextransition" ) )
		{
			continue;
		}

		while ( currentEntity < g_MainMap->num_entities-1 && 
			iSide > sideList[currentEntity].firstBrushSide + sideList[currentEntity].brushSideCount )
		{
			currentEntity++;
		}

		pSide->texinfo = CreateBrushVersionOfWorldVertexTransitionMaterial( pSide->texinfo );
	}
}
void RecvData( CUtlVector<unsigned char> &recvBuf )
{
#if defined( USE_MPI )
	MPI_Status stat;
	MPI_Probe(MPI_ANY_SOURCE, MPI_ANY_TAG, MPI_COMM_WORLD, &stat);
	
	recvBuf.SetCount( stat.count );
	MPI_Recv( recvBuf.Base(), stat.count, MPI_BYTE, MPI_ANY_SOURCE, MPI_ANY_TAG, MPI_COMM_WORLD, &stat);
#else
	if ( !g_pSocket->Recv( recvBuf, 50000 ) )
	{
		g_pSocket->Release();
		g_pSocket = NULL;
	}
#endif
}
Exemple #6
0
// need to do this so that if we are building HDR data, the LDR data is intact, and vice versa.s
void UnserializeDetailPropLighting( int lumpID, int lumpVersion, CUtlVector<DetailPropLightstylesLump_t> &lumpData )
{
	GameLumpHandle_t handle = g_GameLumps.GetGameLumpHandle( lumpID );

	if( handle == g_GameLumps.InvalidGameLump() )
	{
		return;
	}

	if (g_GameLumps.GetGameLumpVersion(handle) != lumpVersion)
		return;

	// Unserialize
	CUtlBuffer buf( g_GameLumps.GetGameLump(handle), g_GameLumps.GameLumpSize( handle ), CUtlBuffer::READ_ONLY );

	int count = buf.GetInt();
	if( !count )
	{
		return;
	}
	lumpData.SetCount( count );
	int lightsize = lumpData.Size() * sizeof(DetailPropLightstylesLump_t);
	buf.Get( lumpData.Base(), lightsize );
}
void ComputePerLeafAmbientLighting()
{
	// Figure out which lights should go in the per-leaf ambient cubes.
	int nInAmbientCube = 0;
	int nSurfaceLights = 0;
	for ( int i=0; i < *pNumworldlights; i++ )
	{
		dworldlight_t *wl = &dworldlights[i];
		
		if ( IsLeafAmbientSurfaceLight( wl ) )
			wl->flags |= DWL_FLAGS_INAMBIENTCUBE;
		else
			wl->flags &= ~DWL_FLAGS_INAMBIENTCUBE;
	
		if ( wl->type == emit_surface )
			++nSurfaceLights;

		if ( wl->flags & DWL_FLAGS_INAMBIENTCUBE )
			++nInAmbientCube;
	}

	Msg( "%d of %d (%d%% of) surface lights went in leaf ambient cubes.\n", nInAmbientCube, nSurfaceLights, nSurfaceLights ? ((nInAmbientCube*100) / nSurfaceLights) : 0 );

	g_LeafAmbientSamples.SetCount(numleafs);

	if ( g_bUseMPI )
	{
		// Distribute the work among the workers.
		VMPI_SetCurrentStage( "ComputeLeafAmbientLighting" );
		DistributeWork( numleafs, VMPI_DISTRIBUTEWORK_PACKETID, VMPI_ProcessLeafAmbient, VMPI_ReceiveLeafAmbientResults );
	}
	else
	{
		RunThreadsOn(numleafs, true, ThreadComputeLeafAmbient);
	}

	// now write out the data
	Msg("Writing leaf ambient...");
	g_pLeafAmbientIndex->RemoveAll();
	g_pLeafAmbientLighting->RemoveAll();
	g_pLeafAmbientIndex->SetCount( numleafs );
	g_pLeafAmbientLighting->EnsureCapacity( numleafs*4 );
	for ( int leafID = 0; leafID < numleafs; leafID++ )
	{
		const CUtlVector<ambientsample_t> &list = g_LeafAmbientSamples[leafID];
		g_pLeafAmbientIndex->Element(leafID).ambientSampleCount = list.Count();
		if ( !list.Count() )
		{
			g_pLeafAmbientIndex->Element(leafID).firstAmbientSample = 0;
		}
		else
		{
			g_pLeafAmbientIndex->Element(leafID).firstAmbientSample = g_pLeafAmbientLighting->Count();
			// compute the samples in disk format.  Encode the positions in 8-bits using leaf bounds fractions
			for ( int i = 0; i < list.Count(); i++ )
			{
				int outIndex = g_pLeafAmbientLighting->AddToTail();
				dleafambientlighting_t &light = g_pLeafAmbientLighting->Element(outIndex);

				light.x = Fixed8Fraction( list[i].pos.x, dleafs[leafID].mins[0], dleafs[leafID].maxs[0] );
				light.y = Fixed8Fraction( list[i].pos.y, dleafs[leafID].mins[1], dleafs[leafID].maxs[1] );
				light.z = Fixed8Fraction( list[i].pos.z, dleafs[leafID].mins[2], dleafs[leafID].maxs[2] );
				light.pad = 0;
				for ( int side = 0; side < 6; side++ )
				{
					VectorToColorRGBExp32( list[i].cube[side], light.cube.m_Color[side] );
				}
			}
		}
	}
	for ( int i = 0; i < numleafs; i++ )
	{
		// UNDONE: Do this dynamically in the engine instead.  This will allow us to sample across leaf
		// boundaries always which should improve the quality of lighting in general
		if ( g_pLeafAmbientIndex->Element(i).ambientSampleCount == 0 )
		{
			if ( !(dleafs[i].contents & CONTENTS_SOLID) )
			{
				Msg("Bad leaf ambient for leaf %d\n", i );
			}

			int refLeaf = NearestNeighborWithLight(i);
			g_pLeafAmbientIndex->Element(i).ambientSampleCount = 0;
			g_pLeafAmbientIndex->Element(i).firstAmbientSample = refLeaf;
		}
	}
	Msg("done\n");
}
// Returns time it took to finish the work.
double DistributeWork(
    uint64 nWorkUnits,				// how many work units to dole out
    char cPacketID,
    ProcessWorkUnitFn processFn,	// workers implement this to process a work unit and send results back
    ReceiveWorkUnitFn receiveFn		// the master implements this to receive a work unit
)
{
    ++g_iCurDSInfo;

    if ( g_iCurDSInfo == 0 )
    {
        // Register our disconnect handler so we can deal with it if clients bail out.
        if ( g_bMPIMaster )
        {
            VMPI_AddDisconnectHandler( VMPI_DistributeWork_DisconnectHandler );
        }
    }
    else if ( g_iCurDSInfo >= MAX_DW_CALLS )
    {
        Error( "DistributeWork: called more than %d times.\n", MAX_DW_CALLS );
    }

    CDSInfo *pInfo = &g_DSInfo;

    pInfo->m_cPacketID = cPacketID;
    pInfo->m_nWorkUnits = nWorkUnits;

    // Make all the workers wait until the master is ready.
    PreDistributeWorkSync( pInfo );

    g_nWUs = nWorkUnits;
    g_nCompletedWUs = 0ull;
    g_nDuplicatedWUs = 0ull;

    // Setup stats info.
    double flMPIStartTime = Plat_FloatTime();
    g_wuCountByProcess.SetCount( 512 );
    memset( g_wuCountByProcess.Base(), 0, sizeof( int ) * g_wuCountByProcess.Count() );

    unsigned long nBytesSentStart = g_nBytesSent;
    unsigned long nBytesReceivedStart = g_nBytesReceived;
    unsigned long nMessagesSentStart = g_nMessagesSent;
    unsigned long nMessagesReceivedStart = g_nMessagesReceived;

    EWorkUnitDistributor eWorkUnitDistributor = VMPI_GetActiveWorkUnitDistributor();
    if ( g_bMPIMaster )
    {
        Assert( !g_pCurDistributorMaster );
        g_pCurDistributorMaster = ( eWorkUnitDistributor == k_eWorkUnitDistributor_SDK ? CreateWUDistributor_SDKMaster() : CreateWUDistributor_DefaultMaster() );

        DistributeWork_Master( pInfo, processFn, receiveFn );

        g_pCurDistributorMaster->Release();
        g_pCurDistributorMaster = NULL;
    }
    else
    {
        Assert( !g_pCurDistributorWorker );
        g_pCurDistributorWorker = ( eWorkUnitDistributor == k_eWorkUnitDistributor_SDK ? CreateWUDistributor_SDKWorker() : CreateWUDistributor_DefaultWorker() );

        DistributeWork_Worker( pInfo, processFn );

        g_pCurDistributorWorker->Release();
        g_pCurDistributorWorker = NULL;
    }

    double flTimeSpent = Plat_FloatTime() - flMPIStartTime;
    ShowMPIStats(
        flTimeSpent,
        g_nBytesSent - nBytesSentStart,
        g_nBytesReceived - nBytesReceivedStart,
        g_nMessagesSent - nMessagesSentStart,
        g_nMessagesReceived - nMessagesReceivedStart
    );

    // Mark that the threads aren't working on anything at the moment.
    for ( int i=0; i < ARRAYSIZE( g_ThreadWUs ); i++ )
        g_ThreadWUs[i] = ~0ull;

    return flTimeSpent;
}
int main( int argc, char* argv[] )
{
	if ( argc < 2 )
	{
		return PrintUsage();
	}

	const char *pClientOrServer = argv[1];
	const char *pIP = NULL;

	bool bClient = false;
	if ( stricmp( pClientOrServer, "-client" ) == 0 )
	{
		if ( argc < 3 )
		{
			return PrintUsage();
		}

		bClient = true;
		pIP = argv[2];
	}

	CUtlVector<unsigned char> recvBuf;
	if ( bClient )
	{
		DoClientConnect( pIP );

		// Ok, now start blasting packets of different sizes and measure how long it takes to get an ack back.
		int nIterations = 30;
		
		for ( int size=350; size <= 350000; size += 512 )
		{
			CUtlVector<unsigned char> buf;
			buf.SetCount( size );

			double flTotalRoundTripTime = 0;

			CFastTimer throughputTimer;
			throughputTimer.Start();

			for ( int i=0; i < nIterations; i++ )
			{
				for ( int z=0; z < size; z++ )
					buf[z] = (char)rand();
				
				SendData( buf.Base(), buf.Count() );
				
				CFastTimer timer;
				timer.Start();
					RecvData( recvBuf );
				timer.End();

				
				// Make sure we got the same data back.
				assert( recvBuf.Count() == buf.Count() );
				for ( z=0; z < size; z++ )
				{
					assert( recvBuf[z] == buf[z] );
				}
					

				//if ( i % 100 == 0 )
				//	printf( "%05d\n", i );
printf( "%d\n", i );
				flTotalRoundTripTime += timer.GetDuration().GetMillisecondsF();
			}
			throughputTimer.End();
			double flTotalSeconds = throughputTimer.GetDuration().GetSeconds();

			double flAvgRoundTripTime = flTotalRoundTripTime / nIterations;
			printf( "%d: %.2f ms per roundtrip (%d bytes/sec) sec: %.2f megs: %.2f\n", 
				size, 
				flAvgRoundTripTime, 
				(int)((size*nIterations)/flTotalSeconds),
				flTotalSeconds,
				(double)(size*nIterations) / (1024*1024)  );
		}

		// Send an 'end' message to the server.
		int val = -1;
		SendData( &val, sizeof( val ) );
	}
	else
	{
		// Wait for a connection.
		DoServerConnect();

		// Wait for packets and ack them.
		while ( 1 )
		{
			RecvData( recvBuf );
			if ( !g_pSocket )
				break;

			if ( recvBuf.Count() < 4 )
			{
				assert( false );
			}

			SendData( recvBuf.Base(), recvBuf.Count() );
		}
	}

	return 0;
}
//-----------------------------------------------------------------------------
// Purpose: Loads data from buffer
//-----------------------------------------------------------------------------
bool TFReportedStats_t::LoadCustomDataFromBuffer( CUtlBuffer &LoadBuffer )
{
	// read the version lump of beginning of file and verify version
	bool bGotEndTag = false;
	unsigned short iLump = 0;
	unsigned short iLumpCount = 0;
	if ( !CBaseGameStats::GetLumpHeader( MAX_LUMP_COUNT, LoadBuffer, iLump, iLumpCount ) )
		return false;
	if ( iLump != TFSTATS_LUMP_VERSION )
	{
		Msg( "Didn't find version header.  Expected lump type TFSTATS_LUMP_VERSION, got lump type %d.  Skipping file.\n", iLump );
		return false;
	}
	TF_Gamestats_Version_t versionLump;
	CBaseGameStats::LoadLump( LoadBuffer, iLumpCount, sizeof( versionLump ), &versionLump );
	if ( versionLump.m_iMagic != TF_GAMESTATS_MAGIC )
	{
		Msg( "Incorrect magic # in version header.  Expected %x, got %x.  Skipping file.\n", TF_GAMESTATS_MAGIC, versionLump.m_iMagic );
		return false;
	}
	if ( versionLump.m_iVersion != TF_GAMESTATS_FILE_VERSION )
	{
		Msg( "Mismatched file version.  Expected file version %d, got %d. Skipping file.\n", TF_GAMESTATS_FILE_VERSION, versionLump.m_iVersion  );
		return false;
	}

	// read all the lumps in the file
	while( CBaseGameStats::GetLumpHeader( MAX_LUMP_COUNT, LoadBuffer, iLump, iLumpCount ) )
	{
		switch ( iLump )
		{
		case TFSTATS_LUMP_MAPHEADER: 
			{
				TF_Gamestats_LevelStats_t::LevelHeader_t header;
				CBaseGameStats::LoadLump( LoadBuffer, iLumpCount, sizeof( TF_Gamestats_LevelStats_t::LevelHeader_t ), &header );

				// quick sanity check on some data -- we get some stat files that start out OK but are corrupted later in the file
				if ( ( header.m_iRoundsPlayed < 0 ) || ( header.m_iTotalTime < 0 ) || ( header.m_iRoundsPlayed > 1000 ) )
					return false;

				// if there's no interesting data, skip this file.  (Need to have server not send it in this case.)
				if ( header.m_iTotalTime == 0 )
					return false;

				m_pCurrentGame = FindOrAddMapStats( header.m_szMapName );
				if ( m_pCurrentGame )
				{
					m_pCurrentGame->m_Header = header;
				}
				break; 
			}
		case TFSTATS_LUMP_MAPDEATH:
			{
				CUtlVector<TF_Gamestats_LevelStats_t::PlayerDeathsLump_t> playerDeaths;

				playerDeaths.SetCount( iLumpCount );
				CBaseGameStats::LoadLump( LoadBuffer, iLumpCount, sizeof( TF_Gamestats_LevelStats_t::PlayerDeathsLump_t ), static_cast<void*>( playerDeaths.Base() ) );
				if ( m_pCurrentGame )
				{
					m_pCurrentGame->m_aPlayerDeaths = playerDeaths;
				}
				break;
			}
		case TFSTATS_LUMP_MAPDAMAGE:
			{
				CUtlVector<TF_Gamestats_LevelStats_t::PlayerDamageLump_t> playerDamage;

				playerDamage.SetCount( iLumpCount );
				CBaseGameStats::LoadLump( LoadBuffer, iLumpCount, sizeof( TF_Gamestats_LevelStats_t::PlayerDamageLump_t ), static_cast<void*>( playerDamage.Base() ) );
				if ( m_pCurrentGame )
				{
					m_pCurrentGame->m_aPlayerDamage = playerDamage;
				}
				break;
			}		
		case TFSTATS_LUMP_CLASS:
			{
				Assert( m_pCurrentGame );
				Assert ( iLumpCount == ARRAYSIZE( m_pCurrentGame->m_aClassStats ) );
				if ( iLumpCount == ARRAYSIZE( m_pCurrentGame->m_aClassStats ) )
				{
					CBaseGameStats::LoadLump( LoadBuffer, ARRAYSIZE( m_pCurrentGame->m_aClassStats ), sizeof( m_pCurrentGame->m_aClassStats[0] ), 
						m_pCurrentGame->m_aClassStats );

					// quick sanity check on some data -- we get some stat files that start out OK but are corrupted later in the file
					for ( int i = 0; i < ARRAYSIZE( m_pCurrentGame->m_aClassStats ); i++ )
					{
						TF_Gamestats_ClassStats_t &classStats = m_pCurrentGame->m_aClassStats[i];
						if ( ( classStats.iSpawns < 0 ) || ( classStats.iSpawns > 10000 ) || ( classStats.iTotalTime < 0 ) || ( classStats.iTotalTime > 36000 * 20 ) ||
							( classStats.iKills < 0 ) || ( classStats.iKills > 10000 ) )
						{
							return false;
						}
					}
				}
				else
				{
					// mismatched lump size, possibly from different build, don't know how it interpret it, just skip over it
					return false;
				}				
				break;
			}
		case TFSTATS_LUMP_WEAPON:
			{
				Assert( m_pCurrentGame );
				Assert ( iLumpCount == ARRAYSIZE( m_pCurrentGame->m_aWeaponStats ) );
				if ( iLumpCount == ARRAYSIZE( m_pCurrentGame->m_aWeaponStats ) )
				{
					CBaseGameStats::LoadLump( LoadBuffer, ARRAYSIZE( m_pCurrentGame->m_aWeaponStats ), sizeof( m_pCurrentGame->m_aWeaponStats[0] ), 
						m_pCurrentGame->m_aWeaponStats );

					// quick sanity check on some data -- we get some stat files that start out OK but are corrupted later in the file
					if ( ( m_pCurrentGame->m_aWeaponStats[TF_WEAPON_MEDIGUN].iShotsFired < 0 ) || ( m_pCurrentGame->m_aWeaponStats[TF_WEAPON_MEDIGUN].iShotsFired > 100000 )
						|| ( m_pCurrentGame->m_aWeaponStats[TF_WEAPON_FLAMETHROWER_ROCKET].iShotsFired != 0 ) ) // check that unused weapon has 0 shots
					{
						return false;
					}
					
				}				
				else
				{
					// mismatched lump size, possibly from different build, don't know how it interpret it, just skip over it
					return false;				
				}				
				break;
			}
		case TFSTATS_LUMP_ENDTAG:
			{
				// check that end tag is valid -- should be version lump again
				TF_Gamestats_Version_t versionLump;
				CBaseGameStats::LoadLump( LoadBuffer, iLumpCount, sizeof( versionLump ), &versionLump );
				if ( versionLump.m_iMagic != TF_GAMESTATS_MAGIC )
				{
					Msg( "Incorrect magic # in version header.  Expected %x, got %x.  Skipping file.\n", TF_GAMESTATS_MAGIC, versionLump.m_iMagic );
					return false;
				}
				if ( versionLump.m_iVersion != TF_GAMESTATS_FILE_VERSION )
				{
					Msg( "Mismatched file version.  Expected file version %d, got %d. Skipping file.\n", TF_GAMESTATS_FILE_VERSION, versionLump.m_iVersion  );
					return false;
				}
				bGotEndTag = true;
				break;
			}

		}
	}

	return bGotEndTag;
}