Team Fortress 2 Source Code as on 22/4/2020
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.

606 lines
18 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. #include "stdafx.h"
  3. #include <stdio.h>
  4. #include <process.h>
  5. #include <string.h>
  6. #include <windows.h>
  7. #include <sys/stat.h>
  8. #include <time.h>
  9. #include <string>
  10. #include "interface.h"
  11. #include "imysqlwrapper.h"
  12. #include "tier1/utlvector.h"
  13. #include "tier1/utlbuffer.h"
  14. #include "tier1/utlsymbol.h"
  15. #include "tier1/utlstring.h"
  16. #include "tier1/utldict.h"
  17. #include "tier2/tier2.h"
  18. #include "filesystem.h"
  19. #include "cbase.h"
  20. #include "gamestats.h"
  21. #include "episodic/ep2_gamestats.h"
  22. #include "base_gamestats_parse.h"
  23. void v_escape_string( std::string& s );
  24. // EP2 custom data blob parsing stuff
  25. static Ep2LevelStats_t *g_pCurrentMap;
  26. static CUtlDict<Ep2LevelStats_t, unsigned short> g_dictMapStats;
  27. extern CUtlDict< int, unsigned short > g_mapOrder;
  28. void DescribeData( BasicGameStats_t &stats, const char *szStatsFileUserID, int iStatsFileVersion );
  29. void InsertData( CUtlDict< int, unsigned short >& mapOrder, IMySQL *sql, BasicGameStats_t &gs, const char *szStatsFileUserID, int iStatsFileVersion, const char *gamename, char const *tag = NULL );
  30. struct CounterInfo_t
  31. {
  32. int type;
  33. char const *counterName;
  34. };
  35. static CounterInfo_t g_IntCounterNames[ ] =
  36. {
  37. { Ep2LevelStats_t::COUNTER_CRATESSMASHED, "Crates Smashed" },
  38. { Ep2LevelStats_t::COUNTER_OBJECTSPUNTED, "Objects Punted" },
  39. { Ep2LevelStats_t::COUNTER_VEHICULARHOMICIDES, "Vehicular Homicides" },
  40. { Ep2LevelStats_t::COUNTER_DISTANCE_INVEHICLE, "Distance in Vehicles (inches)" },
  41. { Ep2LevelStats_t::COUNTER_DISTANCE_ONFOOT, "Distance on Foot (inches)" },
  42. { Ep2LevelStats_t::COUNTER_DISTANCE_ONFOOTSPRINTING, "Distance on Foot Sprint (inches)" },
  43. { Ep2LevelStats_t::COUNTER_FALLINGDEATHS, "Falling Death" },
  44. { Ep2LevelStats_t::COUNTER_VEHICLE_OVERTURNED, "Flipped Vehicles" },
  45. { Ep2LevelStats_t::COUNTER_LOADGAME_STILLALIVE, "Loadgame while player alive" },
  46. { Ep2LevelStats_t::COUNTER_LOADS, "Number of saves (w/autosaves)" },
  47. { Ep2LevelStats_t::COUNTER_SAVES, "Number of game loads" },
  48. { Ep2LevelStats_t::COUNTER_GODMODES, "Times entered God mode" },
  49. { Ep2LevelStats_t::COUNTER_NOCLIPS, "Times entered NoClip mode" },
  50. };
  51. static CounterInfo_t g_FloatCounterNames[ ] =
  52. {
  53. { Ep2LevelStats_t::COUNTER_DAMAGETAKEN, "Damage Taken" },
  54. };
  55. char const *SaveType( int type )
  56. {
  57. switch ( type )
  58. {
  59. default:
  60. case Ep2LevelStats_t::SaveGameInfoRecord2_t::TYPE_UNKNOWN:
  61. break;
  62. case Ep2LevelStats_t::SaveGameInfoRecord2_t::TYPE_AUTOSAVE:
  63. return "Auto Save";
  64. case Ep2LevelStats_t::SaveGameInfoRecord2_t::TYPE_USERSAVE:
  65. return "User Save";
  66. }
  67. return "Unknown";
  68. }
  69. #define INCHES_TO_MILES ( 1.0 / ( 5280.0 * 12.0 ) )
  70. #define INCHES_TO_FEET ( 1.0 / 12.0 )
  71. void DescribeEp2Stats()
  72. {
  73. int i;
  74. int iMap;
  75. for ( iMap = g_dictMapStats.First(); iMap != g_dictMapStats.InvalidIndex(); iMap = g_dictMapStats.Next( iMap ) )
  76. {
  77. // Get the current map.
  78. Ep2LevelStats_t *pCurrentMap = &g_dictMapStats[iMap];
  79. Msg( "map version[ %d ], user tag[ %s ]\n", pCurrentMap->m_Tag.m_nMapVersion, pCurrentMap->m_Tag.m_szTagText );
  80. Msg( " --- %s ------\n %d deaths\n", pCurrentMap->m_Header.m_szMapName, pCurrentMap->m_aPlayerDeaths.Count() );
  81. for ( i = 0; i < pCurrentMap->m_aPlayerDeaths.Count(); ++i )
  82. {
  83. Msg( " @ ( %d, %d, %d )\n",
  84. pCurrentMap->m_aPlayerDeaths[ i ].nPosition[ 0 ],
  85. pCurrentMap->m_aPlayerDeaths[ i ].nPosition[ 1 ],
  86. pCurrentMap->m_aPlayerDeaths[ i ].nPosition[ 2 ] );
  87. }
  88. Msg( " -- Weapon Stats --\n" );
  89. CUtlDict< Ep2LevelStats_t::WeaponLump_t, int > &wdict = pCurrentMap->m_dictWeapons;
  90. for ( i = wdict.First(); i != wdict.InvalidIndex(); i = wdict.Next( i ) )
  91. {
  92. const Ep2LevelStats_t::WeaponLump_t &lump = wdict[ i ];
  93. double acc = 0.0f;
  94. if ( lump.m_nShots > 0 )
  95. {
  96. acc = 100.0f * (double)lump.m_nHits / (double)lump.m_nShots;
  97. }
  98. 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 );
  99. }
  100. Msg( " -- NPC Stats -- \n" );
  101. CUtlDict< Ep2LevelStats_t::EntityDeathsLump_t, int > &dict = pCurrentMap->m_dictEntityDeaths;
  102. for ( i = dict.First(); i != dict.InvalidIndex(); i = dict.Next( i ) )
  103. {
  104. const Ep2LevelStats_t::EntityDeathsLump_t &lump = dict[ i ];
  105. Msg( " %32.32s: bodycount %5d killedplayer %3d\n", dict.GetElementName( i ), lump.m_nBodyCount, lump.m_nKilledPlayer );
  106. }
  107. for ( i = 0; i < Ep2LevelStats_t::NUM_INTCOUNTER_TYPES; ++i )
  108. {
  109. if ( i == Ep2LevelStats_t::COUNTER_DISTANCE_INVEHICLE ||
  110. i == Ep2LevelStats_t::COUNTER_DISTANCE_ONFOOT ||
  111. i == Ep2LevelStats_t::COUNTER_DISTANCE_ONFOOTSPRINTING )
  112. {
  113. 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 ] );
  114. }
  115. else
  116. {
  117. Msg( " %32.32s: %8I64u\n", g_IntCounterNames[ i ].counterName, pCurrentMap->m_IntCounters[ i ] );
  118. }
  119. }
  120. for ( i = 0; i < Ep2LevelStats_t::NUM_FLOATCOUNTER_TYPES; ++i )
  121. {
  122. Msg( " %32.32s: %8.2f\n", g_FloatCounterNames[ i ].counterName, pCurrentMap->m_FloatCounters[ i ] );
  123. }
  124. Ep2LevelStats_t::SaveGameInfo_t *sg = &pCurrentMap->m_SaveGameInfo;
  125. Msg( " -- Save Game --\n" );
  126. time_t t = (time_t)sg->m_nCurrentSaveFileTime;
  127. struct tm *timestamp;
  128. timestamp = localtime( &t );
  129. if ( t == (time_t)0 )
  130. {
  131. Msg( " No save file\n" );
  132. }
  133. else
  134. {
  135. Msg( " Current save %s file time %s\n", sg->m_sCurrentSaveFile.String(), asctime( timestamp ) );
  136. }
  137. for ( i = 0; i < sg->m_Records.Count(); ++i )
  138. {
  139. const Ep2LevelStats_t::SaveGameInfoRecord2_t &rec = sg->m_Records[ i ];
  140. Msg( " %3d deaths, saved at (%5d %5d %5d) with health %3d %s\n",
  141. rec.m_nNumDeaths,
  142. (int)rec.m_nSavePos[ 0 ],
  143. (int)rec.m_nSavePos[ 1 ],
  144. (int)rec.m_nSavePos[ 2 ],
  145. (int)rec.m_nSaveHealth,
  146. SaveType( (int)rec.m_SaveType ) );
  147. }
  148. Msg( " -- Generic Stats --\n" );
  149. CUtlDict< Ep2LevelStats_t::GenericStatsLump_t, int > &gdict = pCurrentMap->m_dictGeneric;
  150. for ( i = gdict.First(); i != gdict.InvalidIndex(); i = gdict.Next( i ) )
  151. {
  152. const Ep2LevelStats_t::GenericStatsLump_t &lump = gdict[ i ];
  153. 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 ] );
  154. }
  155. Msg( "\n" );
  156. }
  157. }
  158. void InsertCustomData( CUtlDict< int, unsigned short >& mapOrder, IMySQL *sql, const char *szStatsFileUserID, int iStatsFileVersion, const char *gamename )
  159. {
  160. if ( !sql )
  161. return;
  162. char q[ 1024 ];
  163. char counternames[ 512 ];
  164. std::string userid;
  165. userid = szStatsFileUserID;
  166. v_escape_string( userid );
  167. // Deal with the maps
  168. int i;
  169. int iMap;
  170. for ( iMap = g_dictMapStats.First(); iMap != g_dictMapStats.InvalidIndex(); iMap = g_dictMapStats.Next( iMap ) )
  171. {
  172. // Get the current map.
  173. Ep2LevelStats_t *pCurrentMap = &g_dictMapStats[iMap];
  174. char const *pszMapName = g_dictMapStats.GetElementName( iMap );
  175. std::string mapname;
  176. mapname = pszMapName;
  177. v_escape_string( mapname );
  178. std::string tag;
  179. tag = pCurrentMap->m_Tag.m_szTagText;
  180. v_escape_string( tag );
  181. int mapversion = pCurrentMap->m_Tag.m_nMapVersion;
  182. #if 1
  183. int slot = mapOrder.Find( pszMapName );
  184. if ( slot == mapOrder.InvalidIndex() )
  185. {
  186. if ( Q_stricmp( pszMapName, "devtest" ) )
  187. continue;
  188. }
  189. #endif
  190. // Deal with deaths
  191. for ( i = 0; i < pCurrentMap->m_aPlayerDeaths.Count(); ++i )
  192. {
  193. 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);",
  194. gamename,
  195. userid.c_str(),
  196. tag.c_str(),
  197. mapname.c_str(),
  198. mapversion,
  199. i,
  200. pCurrentMap->m_aPlayerDeaths[ i ].nPosition[ 0 ],
  201. pCurrentMap->m_aPlayerDeaths[ i ].nPosition[ 1 ],
  202. pCurrentMap->m_aPlayerDeaths[ i ].nPosition[ 2 ] );
  203. int retcode = sql->Execute( q );
  204. if ( retcode != 0 )
  205. {
  206. printf( "Query %s failed\n", q );
  207. return;
  208. }
  209. }
  210. // Deal with weapon data
  211. CUtlDict< Ep2LevelStats_t::WeaponLump_t, int > &wdict = pCurrentMap->m_dictWeapons;
  212. for ( i = wdict.First(); i != wdict.InvalidIndex(); i = wdict.Next( i ) )
  213. {
  214. const Ep2LevelStats_t::WeaponLump_t &lump = wdict[ i ];
  215. std::string wname = wdict.GetElementName( i );
  216. v_escape_string( wname );
  217. 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);",
  218. gamename,
  219. userid.c_str(),
  220. tag.c_str(),
  221. mapname.c_str(),
  222. mapversion,
  223. wname.c_str(),
  224. lump.m_nShots,
  225. lump.m_nHits,
  226. lump.m_flDamageInflicted
  227. );
  228. int retcode = sql->Execute( q );
  229. if ( retcode != 0 )
  230. {
  231. printf( "Query %s failed\n", q );
  232. return;
  233. }
  234. }
  235. CUtlDict< Ep2LevelStats_t::EntityDeathsLump_t, int > &dict = pCurrentMap->m_dictEntityDeaths;
  236. for ( i = dict.First(); i != dict.InvalidIndex(); i = dict.Next( i ) )
  237. {
  238. const Ep2LevelStats_t::EntityDeathsLump_t &lump = dict[ i ];
  239. std::string ename = dict.GetElementName( i );
  240. v_escape_string( ename );
  241. 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);",
  242. gamename,
  243. userid.c_str(),
  244. tag.c_str(),
  245. mapname.c_str(),
  246. mapversion,
  247. ename.c_str(),
  248. lump.m_nBodyCount,
  249. lump.m_nKilledPlayer
  250. );
  251. int retcode = sql->Execute( q );
  252. if ( retcode != 0 )
  253. {
  254. printf( "Query %s failed\n", q );
  255. return;
  256. }
  257. }
  258. // Counters
  259. Q_snprintf( counternames, sizeof( counternames ),
  260. "CRATESSMASHED,"\
  261. "OBJECTSPUNTED,"\
  262. "VEHICULARHOMICIDES,"\
  263. "DISTANCE_INVEHICLE,"\
  264. "DISTANCE_ONFOOT,"\
  265. "DISTANCE_ONFOOTSPRINTING,"\
  266. "FALLINGDEATHS,"\
  267. "VEHICLE_OVERTURNED,"\
  268. "LOADGAME_STILLALIVE,"\
  269. "LOADS,"\
  270. "SAVES,"\
  271. "GODMODES,"\
  272. "NOCLIPS,"\
  273. "DAMAGETAKEN" );
  274. Q_snprintf( q, sizeof( q ), "REPLACE into %s_counters (UserID,Tag,LastUpdate,MapName,MapVersion,%s) values (\"%s\",\"%s\",Now(),\"%-.20s\",%d,"\
  275. "%u,"\
  276. "%u,"\
  277. "%u,"\
  278. "%I64u,"\
  279. "%I64u,"\
  280. "%I64u,"\
  281. "%u,"\
  282. "%u,"\
  283. "%u,"\
  284. "%u,"\
  285. "%u,"\
  286. "%u,"\
  287. "%u,"\
  288. "%f);",
  289. gamename,
  290. counternames,
  291. userid.c_str(),
  292. tag.c_str(),
  293. mapname.c_str(),
  294. mapversion,
  295. (uint32)pCurrentMap->m_IntCounters[ Ep2LevelStats_t::COUNTER_CRATESSMASHED ],
  296. (uint32)pCurrentMap->m_IntCounters[ Ep2LevelStats_t::COUNTER_OBJECTSPUNTED ],
  297. (uint32)pCurrentMap->m_IntCounters[ Ep2LevelStats_t::COUNTER_VEHICULARHOMICIDES ],
  298. pCurrentMap->m_IntCounters[ Ep2LevelStats_t::COUNTER_DISTANCE_INVEHICLE ],
  299. pCurrentMap->m_IntCounters[ Ep2LevelStats_t::COUNTER_DISTANCE_ONFOOT ],
  300. pCurrentMap->m_IntCounters[ Ep2LevelStats_t::COUNTER_DISTANCE_ONFOOTSPRINTING ],
  301. (uint32)pCurrentMap->m_IntCounters[ Ep2LevelStats_t::COUNTER_FALLINGDEATHS ],
  302. (uint32)pCurrentMap->m_IntCounters[ Ep2LevelStats_t::COUNTER_VEHICLE_OVERTURNED ],
  303. (uint32)pCurrentMap->m_IntCounters[ Ep2LevelStats_t::COUNTER_LOADGAME_STILLALIVE ],
  304. (uint32)pCurrentMap->m_IntCounters[ Ep2LevelStats_t::COUNTER_LOADS ],
  305. (uint32)pCurrentMap->m_IntCounters[ Ep2LevelStats_t::COUNTER_SAVES ],
  306. (uint32)pCurrentMap->m_IntCounters[ Ep2LevelStats_t::COUNTER_GODMODES ],
  307. (uint32)pCurrentMap->m_IntCounters[ Ep2LevelStats_t::COUNTER_NOCLIPS ],
  308. (double)pCurrentMap->m_FloatCounters[ Ep2LevelStats_t::COUNTER_DAMAGETAKEN ]
  309. );
  310. int retcode = sql->Execute( q );
  311. if ( retcode != 0 )
  312. {
  313. printf( "Query %s failed\n", q );
  314. return;
  315. }
  316. Ep2LevelStats_t::SaveGameInfo_t *sg = &pCurrentMap->m_SaveGameInfo;
  317. /*
  318. Msg( " -- Save Game --\n" );
  319. time_t t = (time_t)sg->m_nCurrentSaveFileTime;
  320. struct tm *timestamp;
  321. timestamp = localtime( &t );
  322. if ( t == (time_t)0 )
  323. {
  324. Msg( " No save file\n" );
  325. }
  326. else
  327. {
  328. Msg( " Current save %s file time %s\n", sg->m_sCurrentSaveFile.String(), asctime( timestamp ) );
  329. }
  330. */
  331. for ( i = 0; i < sg->m_Records.Count(); ++i )
  332. {
  333. const Ep2LevelStats_t::SaveGameInfoRecord2_t &rec = sg->m_Records[ i ];
  334. 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);",
  335. gamename,
  336. userid.c_str(),
  337. tag.c_str(),
  338. mapname.c_str(),
  339. mapversion,
  340. rec.m_nFirstDeathIndex,
  341. rec.m_nNumDeaths,
  342. (int)rec.m_nSavePos[ 0 ],
  343. (int)rec.m_nSavePos[ 1 ],
  344. (int)rec.m_nSavePos[ 2 ],
  345. (int)rec.m_nSaveHealth,
  346. (int)rec.m_SaveType
  347. );
  348. int retcode = sql->Execute( q );
  349. if ( retcode != 0 )
  350. {
  351. printf( "Query %s failed\n", q );
  352. return;
  353. }
  354. }
  355. // Deal with generic stats data
  356. CUtlDict< Ep2LevelStats_t::GenericStatsLump_t, int > &gdict = pCurrentMap->m_dictGeneric;
  357. for ( i = gdict.First(); i != gdict.InvalidIndex(); i = gdict.Next( i ) )
  358. {
  359. const Ep2LevelStats_t::GenericStatsLump_t &lump = gdict[ i ];
  360. std::string statname = gdict.GetElementName( i );
  361. v_escape_string( statname );
  362. 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);",
  363. gamename,
  364. userid.c_str(),
  365. tag.c_str(),
  366. mapname.c_str(),
  367. mapversion,
  368. statname.c_str(),
  369. lump.m_unCount,
  370. lump.m_flCurrentValue,
  371. lump.m_Pos[ 0 ],
  372. lump.m_Pos[ 1 ],
  373. lump.m_Pos[ 2 ]
  374. );
  375. int retcode = sql->Execute( q );
  376. if ( retcode != 0 )
  377. {
  378. printf( "Query %s failed\n", q );
  379. return;
  380. }
  381. }
  382. }
  383. }
  384. //-----------------------------------------------------------------------------
  385. // Purpose:
  386. // Input : *szMapName -
  387. // Output : Ep2LevelStats_t
  388. //-----------------------------------------------------------------------------
  389. Ep2LevelStats_t *FindOrAddMapStats( const char *szMapName )
  390. {
  391. int iMap = g_dictMapStats.Find( szMapName );
  392. if( iMap == g_dictMapStats.InvalidIndex() )
  393. {
  394. iMap = g_dictMapStats.Insert( szMapName );
  395. }
  396. return &g_dictMapStats[iMap];
  397. }
  398. int GetMaxLumpCount()
  399. {
  400. return EP2_MAX_LUMP_COUNT;
  401. }
  402. void LoadCustomDataFromBuffer( CUtlBuffer &LoadBuffer, char *tag, size_t tagsize )
  403. {
  404. tag[ 0 ] = 0;
  405. Ep2LevelStats_t::LoadData( g_dictMapStats, LoadBuffer );
  406. if ( g_dictMapStats.Count() > 0 )
  407. {
  408. Q_strncpy( tag, g_dictMapStats[ g_dictMapStats.First() ].m_Tag.m_szTagText, tagsize );
  409. }
  410. int i;
  411. int iMap;
  412. for ( iMap = g_dictMapStats.First(); iMap != g_dictMapStats.InvalidIndex(); iMap = g_dictMapStats.Next( iMap ) )
  413. {
  414. // Get the current map.
  415. Ep2LevelStats_t *pCurrentMap = &g_dictMapStats[iMap];
  416. CUtlDict< Ep2LevelStats_t::WeaponLump_t, int > &wdict = pCurrentMap->m_dictWeapons;
  417. for ( i = wdict.First(); i != wdict.InvalidIndex(); i = wdict.Next( i ) )
  418. {
  419. Ep2LevelStats_t::WeaponLump_t &lump = wdict[ i ];
  420. // Bogus #, some kind of damage scaling?
  421. if ( lump.m_flDamageInflicted > 50000 )
  422. {
  423. lump.m_flDamageInflicted = 0.0f;
  424. lump.m_nHits = 0;
  425. lump.m_nShots = 0;
  426. }
  427. }
  428. if ( (int64)pCurrentMap->m_IntCounters[ Ep2LevelStats_t::COUNTER_NOCLIPS ] < 0 )
  429. {
  430. pCurrentMap->m_IntCounters[ Ep2LevelStats_t::COUNTER_NOCLIPS ] = 0;
  431. }
  432. }
  433. }
  434. bool Ep2_ParseCurrentUserID( char const *pchDataFile, char *pchUserID, size_t bufsize, time_t &modifiedtime )
  435. {
  436. FILE *fp = fopen( pchDataFile, "rb" );
  437. if ( fp )
  438. {
  439. CUtlBuffer statsBuffer;
  440. struct _stat sb;
  441. _stat( pchDataFile, &sb );
  442. // Msg( "Processing %s\n", ctx->file );
  443. int nBytesToRead = min( sb.st_size, sizeof( short ) + 16 );
  444. statsBuffer.Clear();
  445. statsBuffer.EnsureCapacity( nBytesToRead );
  446. fread( statsBuffer.Base(), nBytesToRead, 1, fp );
  447. statsBuffer.SeekPut( CUtlBuffer::SEEK_HEAD, nBytesToRead );
  448. fclose( fp );
  449. // Skip the version
  450. statsBuffer.GetShort();
  451. statsBuffer.Get( pchUserID, 16 );
  452. pchUserID[ bufsize - 1 ] = 0;
  453. modifiedtime = sb.st_mtime;
  454. return statsBuffer.TellPut() == statsBuffer.TellGet();
  455. }
  456. return false;
  457. }
  458. int Ep2_ParseCustomGameStatsData( ParseContext_t *ctx )
  459. {
  460. g_dictMapStats.Purge();
  461. FILE *fp = fopen( ctx->file, "rb" );
  462. if ( fp )
  463. {
  464. CUtlBuffer statsBuffer;
  465. struct _stat sb;
  466. _stat( ctx->file, &sb );
  467. // Msg( "Processing %s\n", ctx->file );
  468. statsBuffer.Clear();
  469. statsBuffer.EnsureCapacity( sb.st_size );
  470. fread( statsBuffer.Base(), sb.st_size, 1, fp );
  471. statsBuffer.SeekPut( CUtlBuffer::SEEK_HEAD, sb.st_size );
  472. fclose( fp );
  473. char shortname[ 128 ];
  474. Q_FileBase( ctx->file, shortname, sizeof( shortname ) );
  475. char szCurrentStatsFileUserID[17];
  476. int iCurrentStatsFileVersion;
  477. iCurrentStatsFileVersion = statsBuffer.GetShort();
  478. statsBuffer.Get( szCurrentStatsFileUserID, 16 );
  479. szCurrentStatsFileUserID[ sizeof( szCurrentStatsFileUserID ) - 1 ] = 0;
  480. bool valid = true;
  481. BasicGameStats_t stats;
  482. char tag[ 32 ] = { 0 };
  483. unsigned int iCheckIfStandardDataSaved = statsBuffer.GetUnsignedInt();
  484. if( iCheckIfStandardDataSaved != GAMESTATS_STANDARD_NOT_SAVED )
  485. {
  486. //standard data was saved, rewind so the stats can read in time to completion
  487. statsBuffer.SeekGet( CUtlBuffer::SEEK_CURRENT, -((int)sizeof( unsigned int )) );
  488. valid = stats.ParseFromBuffer( statsBuffer, iCurrentStatsFileVersion );
  489. if ( ctx->describeonly )
  490. {
  491. DescribeData( stats, szCurrentStatsFileUserID, iCurrentStatsFileVersion );
  492. }
  493. }
  494. //check for custom data
  495. bool bHasCustomData = (valid && (statsBuffer.TellPut() != statsBuffer.TellGet()));
  496. if( bHasCustomData )
  497. {
  498. LoadCustomDataFromBuffer( statsBuffer, tag, sizeof( tag ) );
  499. if( ctx->describeonly )
  500. {
  501. DescribeEp2Stats();
  502. }
  503. else
  504. {
  505. InsertCustomData( g_mapOrder, ctx->mysql, szCurrentStatsFileUserID, iCurrentStatsFileVersion, ctx->gamename );
  506. }
  507. }
  508. // Do base data after custom since we need the custom to parse out the "tag" used for this user
  509. if ( valid )
  510. {
  511. if ( !ctx->describeonly )
  512. {
  513. InsertData( g_mapOrder, ctx->mysql, stats, szCurrentStatsFileUserID, iCurrentStatsFileVersion, ctx->gamename, tag );
  514. }
  515. }
  516. else
  517. {
  518. ++ctx->skipcount;
  519. }
  520. }
  521. return CUSTOMDATA_SUCCESS;
  522. }