summaryrefslogtreecommitdiff
path: root/devtools/processgamestats/ep2_gamestats_parse.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'devtools/processgamestats/ep2_gamestats_parse.cpp')
-rw-r--r--devtools/processgamestats/ep2_gamestats_parse.cpp606
1 files changed, 606 insertions, 0 deletions
diff --git a/devtools/processgamestats/ep2_gamestats_parse.cpp b/devtools/processgamestats/ep2_gamestats_parse.cpp
new file mode 100644
index 0000000..0e1b67b
--- /dev/null
+++ b/devtools/processgamestats/ep2_gamestats_parse.cpp
@@ -0,0 +1,606 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+#include "stdafx.h"
+#include <stdio.h>
+#include <process.h>
+#include <string.h>
+#include <windows.h>
+#include <sys/stat.h>
+#include <time.h>
+#include <string>
+
+
+#include "interface.h"
+#include "imysqlwrapper.h"
+#include "tier1/utlvector.h"
+#include "tier1/utlbuffer.h"
+#include "tier1/utlsymbol.h"
+#include "tier1/utlstring.h"
+#include "tier1/utldict.h"
+#include "tier2/tier2.h"
+#include "filesystem.h"
+
+#include "cbase.h"
+#include "gamestats.h"
+#include "episodic/ep2_gamestats.h"
+#include "base_gamestats_parse.h"
+
+void v_escape_string( std::string& s );
+
+// EP2 custom data blob parsing stuff
+
+static Ep2LevelStats_t *g_pCurrentMap;
+static CUtlDict<Ep2LevelStats_t, unsigned short> g_dictMapStats;
+
+extern CUtlDict< int, unsigned short > g_mapOrder;
+
+void DescribeData( BasicGameStats_t &stats, const char *szStatsFileUserID, int iStatsFileVersion );
+void InsertData( CUtlDict< int, unsigned short >& mapOrder, IMySQL *sql, BasicGameStats_t &gs, const char *szStatsFileUserID, int iStatsFileVersion, const char *gamename, char const *tag = NULL );
+
+struct CounterInfo_t
+{
+ int type;
+ char const *counterName;
+};
+
+static CounterInfo_t g_IntCounterNames[ ] =
+{
+ { Ep2LevelStats_t::COUNTER_CRATESSMASHED, "Crates Smashed" },
+ { Ep2LevelStats_t::COUNTER_OBJECTSPUNTED, "Objects Punted" },
+ { Ep2LevelStats_t::COUNTER_VEHICULARHOMICIDES, "Vehicular Homicides" },
+ { Ep2LevelStats_t::COUNTER_DISTANCE_INVEHICLE, "Distance in Vehicles (inches)" },
+ { Ep2LevelStats_t::COUNTER_DISTANCE_ONFOOT, "Distance on Foot (inches)" },
+ { Ep2LevelStats_t::COUNTER_DISTANCE_ONFOOTSPRINTING, "Distance on Foot Sprint (inches)" },
+ { Ep2LevelStats_t::COUNTER_FALLINGDEATHS, "Falling Death" },
+ { Ep2LevelStats_t::COUNTER_VEHICLE_OVERTURNED, "Flipped Vehicles" },
+ { Ep2LevelStats_t::COUNTER_LOADGAME_STILLALIVE, "Loadgame while player alive" },
+ { Ep2LevelStats_t::COUNTER_LOADS, "Number of saves (w/autosaves)" },
+ { Ep2LevelStats_t::COUNTER_SAVES, "Number of game loads" },
+ { Ep2LevelStats_t::COUNTER_GODMODES, "Times entered God mode" },
+ { Ep2LevelStats_t::COUNTER_NOCLIPS, "Times entered NoClip mode" },
+};
+
+static CounterInfo_t g_FloatCounterNames[ ] =
+{
+ { Ep2LevelStats_t::COUNTER_DAMAGETAKEN, "Damage Taken" },
+};
+
+char const *SaveType( int type )
+{
+ switch ( type )
+ {
+ default:
+ case Ep2LevelStats_t::SaveGameInfoRecord2_t::TYPE_UNKNOWN:
+ break;
+ case Ep2LevelStats_t::SaveGameInfoRecord2_t::TYPE_AUTOSAVE:
+ return "Auto Save";
+ case Ep2LevelStats_t::SaveGameInfoRecord2_t::TYPE_USERSAVE:
+ return "User Save";
+ }
+ return "Unknown";
+}
+
+#define INCHES_TO_MILES ( 1.0 / ( 5280.0 * 12.0 ) )
+#define INCHES_TO_FEET ( 1.0 / 12.0 )
+
+void DescribeEp2Stats()
+{
+ int i;
+ int iMap;
+ for ( iMap = g_dictMapStats.First(); iMap != g_dictMapStats.InvalidIndex(); iMap = g_dictMapStats.Next( iMap ) )
+ {
+ // Get the current map.
+ Ep2LevelStats_t *pCurrentMap = &g_dictMapStats[iMap];
+
+ Msg( "map version[ %d ], user tag[ %s ]\n", pCurrentMap->m_Tag.m_nMapVersion, pCurrentMap->m_Tag.m_szTagText );
+
+ Msg( " --- %s ------\n %d deaths\n", pCurrentMap->m_Header.m_szMapName, pCurrentMap->m_aPlayerDeaths.Count() );
+ for ( i = 0; i < pCurrentMap->m_aPlayerDeaths.Count(); ++i )
+ {
+ Msg( " @ ( %d, %d, %d )\n",
+ pCurrentMap->m_aPlayerDeaths[ i ].nPosition[ 0 ],
+ pCurrentMap->m_aPlayerDeaths[ i ].nPosition[ 1 ],
+ pCurrentMap->m_aPlayerDeaths[ i ].nPosition[ 2 ] );
+ }
+
+ Msg( " -- Weapon Stats --\n" );
+
+ CUtlDict< Ep2LevelStats_t::WeaponLump_t, int > &wdict = pCurrentMap->m_dictWeapons;
+ for ( i = wdict.First(); i != wdict.InvalidIndex(); i = wdict.Next( i ) )
+ {
+ const Ep2LevelStats_t::WeaponLump_t &lump = wdict[ i ];
+ double acc = 0.0f;
+ if ( lump.m_nShots > 0 )
+ {
+ acc = 100.0f * (double)lump.m_nHits / (double)lump.m_nShots;
+ }
+ Msg( " %32.32s: shots %5d hits %5d damage %8.2f [accuracy %8.2f %%]\n", wdict.GetElementName( i ), lump.m_nShots, lump.m_nHits, lump.m_flDamageInflicted, acc );
+ }
+ Msg( " -- NPC Stats -- \n" );
+
+ CUtlDict< Ep2LevelStats_t::EntityDeathsLump_t, int > &dict = pCurrentMap->m_dictEntityDeaths;
+ for ( i = dict.First(); i != dict.InvalidIndex(); i = dict.Next( i ) )
+ {
+ const Ep2LevelStats_t::EntityDeathsLump_t &lump = dict[ i ];
+ Msg( " %32.32s: bodycount %5d killedplayer %3d\n", dict.GetElementName( i ), lump.m_nBodyCount, lump.m_nKilledPlayer );
+ }
+
+ for ( i = 0; i < Ep2LevelStats_t::NUM_INTCOUNTER_TYPES; ++i )
+ {
+ if ( i == Ep2LevelStats_t::COUNTER_DISTANCE_INVEHICLE ||
+ i == Ep2LevelStats_t::COUNTER_DISTANCE_ONFOOT ||
+ i == Ep2LevelStats_t::COUNTER_DISTANCE_ONFOOTSPRINTING )
+ {
+ Msg( " %32.32s: %8I64u [%8.3f feet] [%8.3f miles]\n", g_IntCounterNames[ i ].counterName, pCurrentMap->m_IntCounters[ i ], INCHES_TO_FEET * (double)pCurrentMap->m_IntCounters[ i ], INCHES_TO_MILES * (double)pCurrentMap->m_IntCounters[ i ] );
+ }
+ else
+ {
+ Msg( " %32.32s: %8I64u\n", g_IntCounterNames[ i ].counterName, pCurrentMap->m_IntCounters[ i ] );
+ }
+ }
+ for ( i = 0; i < Ep2LevelStats_t::NUM_FLOATCOUNTER_TYPES; ++i )
+ {
+ Msg( " %32.32s: %8.2f\n", g_FloatCounterNames[ i ].counterName, pCurrentMap->m_FloatCounters[ i ] );
+ }
+
+ Ep2LevelStats_t::SaveGameInfo_t *sg = &pCurrentMap->m_SaveGameInfo;
+ Msg( " -- Save Game --\n" );
+ time_t t = (time_t)sg->m_nCurrentSaveFileTime;
+ struct tm *timestamp;
+ timestamp = localtime( &t );
+ if ( t == (time_t)0 )
+ {
+ Msg( " No save file\n" );
+ }
+ else
+ {
+ Msg( " Current save %s file time %s\n", sg->m_sCurrentSaveFile.String(), asctime( timestamp ) );
+ }
+ for ( i = 0; i < sg->m_Records.Count(); ++i )
+ {
+ const Ep2LevelStats_t::SaveGameInfoRecord2_t &rec = sg->m_Records[ i ];
+ Msg( " %3d deaths, saved at (%5d %5d %5d) with health %3d %s\n",
+ rec.m_nNumDeaths,
+ (int)rec.m_nSavePos[ 0 ],
+ (int)rec.m_nSavePos[ 1 ],
+ (int)rec.m_nSavePos[ 2 ],
+ (int)rec.m_nSaveHealth,
+ SaveType( (int)rec.m_SaveType ) );
+ }
+
+ Msg( " -- Generic Stats --\n" );
+
+ CUtlDict< Ep2LevelStats_t::GenericStatsLump_t, int > &gdict = pCurrentMap->m_dictGeneric;
+ for ( i = gdict.First(); i != gdict.InvalidIndex(); i = gdict.Next( i ) )
+ {
+ const Ep2LevelStats_t::GenericStatsLump_t &lump = gdict[ i ];
+ Msg( " %32.32s: count %u total %f @[%d, %d, %d]\n", gdict.GetElementName( i ), lump.m_unCount, lump.m_flCurrentValue, lump.m_Pos[ 0 ], lump.m_Pos[ 1 ], lump.m_Pos[ 2 ] );
+ }
+
+ Msg( "\n" );
+ }
+}
+
+void InsertCustomData( CUtlDict< int, unsigned short >& mapOrder, IMySQL *sql, const char *szStatsFileUserID, int iStatsFileVersion, const char *gamename )
+{
+ if ( !sql )
+ return;
+
+ char q[ 1024 ];
+ char counternames[ 512 ];
+
+ std::string userid;
+ userid = szStatsFileUserID;
+ v_escape_string( userid );
+
+ // Deal with the maps
+ int i;
+ int iMap;
+ for ( iMap = g_dictMapStats.First(); iMap != g_dictMapStats.InvalidIndex(); iMap = g_dictMapStats.Next( iMap ) )
+ {
+ // Get the current map.
+ Ep2LevelStats_t *pCurrentMap = &g_dictMapStats[iMap];
+
+ char const *pszMapName = g_dictMapStats.GetElementName( iMap );
+ std::string mapname;
+ mapname = pszMapName;
+ v_escape_string( mapname );
+
+ std::string tag;
+ tag = pCurrentMap->m_Tag.m_szTagText;
+ v_escape_string( tag );
+
+ int mapversion = pCurrentMap->m_Tag.m_nMapVersion;
+
+#if 1
+ int slot = mapOrder.Find( pszMapName );
+ if ( slot == mapOrder.InvalidIndex() )
+ {
+ if ( Q_stricmp( pszMapName, "devtest" ) )
+ continue;
+ }
+#endif
+
+ // Deal with deaths
+ for ( i = 0; i < pCurrentMap->m_aPlayerDeaths.Count(); ++i )
+ {
+ Q_snprintf( q, sizeof( q ), "REPLACE into %s_deaths (UserID,Tag,LastUpdate,MapName,MapVersion,DeathIndex,X,Y,Z) values (\"%s\",\"%s\",Now(),\"%-.20s\",%d,%d,%d,%d,%d);",
+ gamename,
+ userid.c_str(),
+ tag.c_str(),
+ mapname.c_str(),
+ mapversion,
+ i,
+ pCurrentMap->m_aPlayerDeaths[ i ].nPosition[ 0 ],
+ pCurrentMap->m_aPlayerDeaths[ i ].nPosition[ 1 ],
+ pCurrentMap->m_aPlayerDeaths[ i ].nPosition[ 2 ] );
+
+ int retcode = sql->Execute( q );
+ if ( retcode != 0 )
+ {
+ printf( "Query %s failed\n", q );
+ return;
+ }
+ }
+
+ // Deal with weapon data
+ CUtlDict< Ep2LevelStats_t::WeaponLump_t, int > &wdict = pCurrentMap->m_dictWeapons;
+ for ( i = wdict.First(); i != wdict.InvalidIndex(); i = wdict.Next( i ) )
+ {
+ const Ep2LevelStats_t::WeaponLump_t &lump = wdict[ i ];
+
+ std::string wname = wdict.GetElementName( i );
+ v_escape_string( wname );
+
+ Q_snprintf( q, sizeof( q ), "REPLACE into %s_weapons (UserID,Tag,LastUpdate,MapName,MapVersion,Weapon,Shots,Hits,Damage) values (\"%s\",\"%s\",Now(),\"%-.20s\",%d,\"%-.32s\",%d,%d,%g);",
+ gamename,
+ userid.c_str(),
+ tag.c_str(),
+ mapname.c_str(),
+ mapversion,
+ wname.c_str(),
+ lump.m_nShots,
+ lump.m_nHits,
+ lump.m_flDamageInflicted
+ );
+
+ int retcode = sql->Execute( q );
+ if ( retcode != 0 )
+ {
+ printf( "Query %s failed\n", q );
+ return;
+ }
+ }
+
+ CUtlDict< Ep2LevelStats_t::EntityDeathsLump_t, int > &dict = pCurrentMap->m_dictEntityDeaths;
+ for ( i = dict.First(); i != dict.InvalidIndex(); i = dict.Next( i ) )
+ {
+ const Ep2LevelStats_t::EntityDeathsLump_t &lump = dict[ i ];
+
+ std::string ename = dict.GetElementName( i );
+ v_escape_string( ename );
+
+ Q_snprintf( q, sizeof( q ), "REPLACE into %s_entities (UserID,Tag,LastUpdate,MapName,MapVersion,Entity,BodyCount,KilledPlayer) values (\"%s\",\"%s\",Now(),\"%-.20s\",%d,\"%-.32s\",%d,%d);",
+ gamename,
+ userid.c_str(),
+ tag.c_str(),
+ mapname.c_str(),
+ mapversion,
+ ename.c_str(),
+ lump.m_nBodyCount,
+ lump.m_nKilledPlayer
+ );
+
+ int retcode = sql->Execute( q );
+ if ( retcode != 0 )
+ {
+ printf( "Query %s failed\n", q );
+ return;
+ }
+ }
+
+ // Counters
+ Q_snprintf( counternames, sizeof( counternames ),
+ "CRATESSMASHED,"\
+ "OBJECTSPUNTED,"\
+ "VEHICULARHOMICIDES,"\
+ "DISTANCE_INVEHICLE,"\
+ "DISTANCE_ONFOOT,"\
+ "DISTANCE_ONFOOTSPRINTING,"\
+ "FALLINGDEATHS,"\
+ "VEHICLE_OVERTURNED,"\
+ "LOADGAME_STILLALIVE,"\
+ "LOADS,"\
+ "SAVES,"\
+ "GODMODES,"\
+ "NOCLIPS,"\
+ "DAMAGETAKEN" );
+ Q_snprintf( q, sizeof( q ), "REPLACE into %s_counters (UserID,Tag,LastUpdate,MapName,MapVersion,%s) values (\"%s\",\"%s\",Now(),\"%-.20s\",%d,"\
+ "%u,"\
+ "%u,"\
+ "%u,"\
+ "%I64u,"\
+ "%I64u,"\
+ "%I64u,"\
+ "%u,"\
+ "%u,"\
+ "%u,"\
+ "%u,"\
+ "%u,"\
+ "%u,"\
+ "%u,"\
+ "%f);",
+ gamename,
+ counternames,
+ userid.c_str(),
+ tag.c_str(),
+ mapname.c_str(),
+ mapversion,
+ (uint32)pCurrentMap->m_IntCounters[ Ep2LevelStats_t::COUNTER_CRATESSMASHED ],
+ (uint32)pCurrentMap->m_IntCounters[ Ep2LevelStats_t::COUNTER_OBJECTSPUNTED ],
+ (uint32)pCurrentMap->m_IntCounters[ Ep2LevelStats_t::COUNTER_VEHICULARHOMICIDES ],
+ pCurrentMap->m_IntCounters[ Ep2LevelStats_t::COUNTER_DISTANCE_INVEHICLE ],
+ pCurrentMap->m_IntCounters[ Ep2LevelStats_t::COUNTER_DISTANCE_ONFOOT ],
+ pCurrentMap->m_IntCounters[ Ep2LevelStats_t::COUNTER_DISTANCE_ONFOOTSPRINTING ],
+ (uint32)pCurrentMap->m_IntCounters[ Ep2LevelStats_t::COUNTER_FALLINGDEATHS ],
+ (uint32)pCurrentMap->m_IntCounters[ Ep2LevelStats_t::COUNTER_VEHICLE_OVERTURNED ],
+ (uint32)pCurrentMap->m_IntCounters[ Ep2LevelStats_t::COUNTER_LOADGAME_STILLALIVE ],
+ (uint32)pCurrentMap->m_IntCounters[ Ep2LevelStats_t::COUNTER_LOADS ],
+ (uint32)pCurrentMap->m_IntCounters[ Ep2LevelStats_t::COUNTER_SAVES ],
+ (uint32)pCurrentMap->m_IntCounters[ Ep2LevelStats_t::COUNTER_GODMODES ],
+ (uint32)pCurrentMap->m_IntCounters[ Ep2LevelStats_t::COUNTER_NOCLIPS ],
+ (double)pCurrentMap->m_FloatCounters[ Ep2LevelStats_t::COUNTER_DAMAGETAKEN ]
+ );
+
+ int retcode = sql->Execute( q );
+ if ( retcode != 0 )
+ {
+ printf( "Query %s failed\n", q );
+ return;
+ }
+
+ Ep2LevelStats_t::SaveGameInfo_t *sg = &pCurrentMap->m_SaveGameInfo;
+ /*
+ Msg( " -- Save Game --\n" );
+ time_t t = (time_t)sg->m_nCurrentSaveFileTime;
+ struct tm *timestamp;
+ timestamp = localtime( &t );
+ if ( t == (time_t)0 )
+ {
+ Msg( " No save file\n" );
+ }
+ else
+ {
+ Msg( " Current save %s file time %s\n", sg->m_sCurrentSaveFile.String(), asctime( timestamp ) );
+ }
+ */
+ for ( i = 0; i < sg->m_Records.Count(); ++i )
+ {
+ const Ep2LevelStats_t::SaveGameInfoRecord2_t &rec = sg->m_Records[ i ];
+
+ Q_snprintf( q, sizeof( q ), "REPLACE into %s_saves (UserID,Tag,LastUpdate,MapName,MapVersion,FirstDeath,NumDeaths,X,Y,Z,Health,SaveType) values (\"%s\",\"%s\",Now(),\"%-.20s\",%d,%d,%d,%d,%d,%d,%d,%d);",
+ gamename,
+ userid.c_str(),
+ tag.c_str(),
+ mapname.c_str(),
+ mapversion,
+ rec.m_nFirstDeathIndex,
+ rec.m_nNumDeaths,
+ (int)rec.m_nSavePos[ 0 ],
+ (int)rec.m_nSavePos[ 1 ],
+ (int)rec.m_nSavePos[ 2 ],
+ (int)rec.m_nSaveHealth,
+ (int)rec.m_SaveType
+ );
+
+ int retcode = sql->Execute( q );
+ if ( retcode != 0 )
+ {
+ printf( "Query %s failed\n", q );
+ return;
+ }
+ }
+
+
+ // Deal with generic stats data
+ CUtlDict< Ep2LevelStats_t::GenericStatsLump_t, int > &gdict = pCurrentMap->m_dictGeneric;
+ for ( i = gdict.First(); i != gdict.InvalidIndex(); i = gdict.Next( i ) )
+ {
+ const Ep2LevelStats_t::GenericStatsLump_t &lump = gdict[ i ];
+
+ std::string statname = gdict.GetElementName( i );
+ v_escape_string( statname );
+
+ Q_snprintf( q, sizeof( q ), "REPLACE into %s_generic (UserID,Tag,LastUpdate,MapName,MapVersion,StatName,Count,Value,X,Y,Z) values (\"%s\",\"%s\",Now(),\"%-.20s\",%d,\"%-.16s\",%d,%f,%d,%d,%d);",
+ gamename,
+ userid.c_str(),
+ tag.c_str(),
+ mapname.c_str(),
+ mapversion,
+ statname.c_str(),
+ lump.m_unCount,
+ lump.m_flCurrentValue,
+ lump.m_Pos[ 0 ],
+ lump.m_Pos[ 1 ],
+ lump.m_Pos[ 2 ]
+ );
+
+ int retcode = sql->Execute( q );
+ if ( retcode != 0 )
+ {
+ printf( "Query %s failed\n", q );
+ return;
+ }
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : *szMapName -
+// Output : Ep2LevelStats_t
+//-----------------------------------------------------------------------------
+Ep2LevelStats_t *FindOrAddMapStats( const char *szMapName )
+{
+ int iMap = g_dictMapStats.Find( szMapName );
+ if( iMap == g_dictMapStats.InvalidIndex() )
+ {
+ iMap = g_dictMapStats.Insert( szMapName );
+ }
+
+ return &g_dictMapStats[iMap];
+}
+
+int GetMaxLumpCount()
+{
+ return EP2_MAX_LUMP_COUNT;
+}
+
+void LoadCustomDataFromBuffer( CUtlBuffer &LoadBuffer, char *tag, size_t tagsize )
+{
+ tag[ 0 ] = 0;
+ Ep2LevelStats_t::LoadData( g_dictMapStats, LoadBuffer );
+ if ( g_dictMapStats.Count() > 0 )
+ {
+ Q_strncpy( tag, g_dictMapStats[ g_dictMapStats.First() ].m_Tag.m_szTagText, tagsize );
+ }
+
+ int i;
+ int iMap;
+ for ( iMap = g_dictMapStats.First(); iMap != g_dictMapStats.InvalidIndex(); iMap = g_dictMapStats.Next( iMap ) )
+ {
+ // Get the current map.
+ Ep2LevelStats_t *pCurrentMap = &g_dictMapStats[iMap];
+ CUtlDict< Ep2LevelStats_t::WeaponLump_t, int > &wdict = pCurrentMap->m_dictWeapons;
+ for ( i = wdict.First(); i != wdict.InvalidIndex(); i = wdict.Next( i ) )
+ {
+ Ep2LevelStats_t::WeaponLump_t &lump = wdict[ i ];
+ // Bogus #, some kind of damage scaling?
+ if ( lump.m_flDamageInflicted > 50000 )
+ {
+ lump.m_flDamageInflicted = 0.0f;
+ lump.m_nHits = 0;
+ lump.m_nShots = 0;
+ }
+ }
+
+ if ( (int64)pCurrentMap->m_IntCounters[ Ep2LevelStats_t::COUNTER_NOCLIPS ] < 0 )
+ {
+ pCurrentMap->m_IntCounters[ Ep2LevelStats_t::COUNTER_NOCLIPS ] = 0;
+ }
+ }
+}
+
+bool Ep2_ParseCurrentUserID( char const *pchDataFile, char *pchUserID, size_t bufsize, time_t &modifiedtime )
+{
+ FILE *fp = fopen( pchDataFile, "rb" );
+ if ( fp )
+ {
+ CUtlBuffer statsBuffer;
+
+ struct _stat sb;
+ _stat( pchDataFile, &sb );
+
+ // Msg( "Processing %s\n", ctx->file );
+ int nBytesToRead = min( sb.st_size, sizeof( short ) + 16 );
+
+ statsBuffer.Clear();
+ statsBuffer.EnsureCapacity( nBytesToRead );
+ fread( statsBuffer.Base(), nBytesToRead, 1, fp );
+ statsBuffer.SeekPut( CUtlBuffer::SEEK_HEAD, nBytesToRead );
+ fclose( fp );
+
+ // Skip the version
+ statsBuffer.GetShort();
+ statsBuffer.Get( pchUserID, 16 );
+ pchUserID[ bufsize - 1 ] = 0;
+
+ modifiedtime = sb.st_mtime;
+
+ return statsBuffer.TellPut() == statsBuffer.TellGet();
+ }
+
+ return false;
+}
+
+int Ep2_ParseCustomGameStatsData( ParseContext_t *ctx )
+{
+ g_dictMapStats.Purge();
+
+ FILE *fp = fopen( ctx->file, "rb" );
+ if ( fp )
+ {
+ CUtlBuffer statsBuffer;
+
+ struct _stat sb;
+ _stat( ctx->file, &sb );
+
+ // Msg( "Processing %s\n", ctx->file );
+
+ statsBuffer.Clear();
+ statsBuffer.EnsureCapacity( sb.st_size );
+ fread( statsBuffer.Base(), sb.st_size, 1, fp );
+ statsBuffer.SeekPut( CUtlBuffer::SEEK_HEAD, sb.st_size );
+ fclose( fp );
+
+ char shortname[ 128 ];
+ Q_FileBase( ctx->file, shortname, sizeof( shortname ) );
+
+ char szCurrentStatsFileUserID[17];
+ int iCurrentStatsFileVersion;
+
+ iCurrentStatsFileVersion = statsBuffer.GetShort();
+ statsBuffer.Get( szCurrentStatsFileUserID, 16 );
+ szCurrentStatsFileUserID[ sizeof( szCurrentStatsFileUserID ) - 1 ] = 0;
+
+ bool valid = true;
+ BasicGameStats_t stats;
+
+ char tag[ 32 ] = { 0 };
+
+ unsigned int iCheckIfStandardDataSaved = statsBuffer.GetUnsignedInt();
+ if( iCheckIfStandardDataSaved != GAMESTATS_STANDARD_NOT_SAVED )
+ {
+ //standard data was saved, rewind so the stats can read in time to completion
+ statsBuffer.SeekGet( CUtlBuffer::SEEK_CURRENT, -((int)sizeof( unsigned int )) );
+
+ valid = stats.ParseFromBuffer( statsBuffer, iCurrentStatsFileVersion );
+
+ if ( ctx->describeonly )
+ {
+ DescribeData( stats, szCurrentStatsFileUserID, iCurrentStatsFileVersion );
+ }
+ }
+
+ //check for custom data
+ bool bHasCustomData = (valid && (statsBuffer.TellPut() != statsBuffer.TellGet()));
+
+ if( bHasCustomData )
+ {
+ LoadCustomDataFromBuffer( statsBuffer, tag, sizeof( tag ) );
+
+ if( ctx->describeonly )
+ {
+ DescribeEp2Stats();
+ }
+ else
+ {
+ InsertCustomData( g_mapOrder, ctx->mysql, szCurrentStatsFileUserID, iCurrentStatsFileVersion, ctx->gamename );
+ }
+ }
+
+ // Do base data after custom since we need the custom to parse out the "tag" used for this user
+ if ( valid )
+ {
+ if ( !ctx->describeonly )
+ {
+ InsertData( g_mapOrder, ctx->mysql, stats, szCurrentStatsFileUserID, iCurrentStatsFileVersion, ctx->gamename, tag );
+ }
+ }
+ else
+ {
+ ++ctx->skipcount;
+ }
+ }
+ return CUSTOMDATA_SUCCESS;
+}
+