summaryrefslogtreecommitdiff
path: root/devtools/processgamestats
diff options
context:
space:
mode:
Diffstat (limited to 'devtools/processgamestats')
-rw-r--r--devtools/processgamestats/StdAfx.cpp15
-rw-r--r--devtools/processgamestats/StdAfx.h26
-rw-r--r--devtools/processgamestats/base_gamestats_parse.h34
-rw-r--r--devtools/processgamestats/cs_gamestats.h45
-rw-r--r--devtools/processgamestats/cstrike_gamestats.db191
-rw-r--r--devtools/processgamestats/cstrike_gamestats_parse.cpp235
-rw-r--r--devtools/processgamestats/ep1_gamestats.db47
-rw-r--r--devtools/processgamestats/ep2_gamestats.db168
-rw-r--r--devtools/processgamestats/ep2_gamestats_parse.cpp606
-rw-r--r--devtools/processgamestats/portal_gamestats.db47
-rw-r--r--devtools/processgamestats/processgamestats.cpp982
-rw-r--r--devtools/processgamestats/processgamestats.vpc78
-rw-r--r--devtools/processgamestats/tf_gamestats.db227
-rw-r--r--devtools/processgamestats/tf_gamestats_parse.cpp480
14 files changed, 3181 insertions, 0 deletions
diff --git a/devtools/processgamestats/StdAfx.cpp b/devtools/processgamestats/StdAfx.cpp
new file mode 100644
index 0000000..d9360a8
--- /dev/null
+++ b/devtools/processgamestats/StdAfx.cpp
@@ -0,0 +1,15 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//
+//=============================================================================//
+// stdafx.cpp : source file that includes just the standard includes
+// tagbuild.pch will be the pre-compiled header
+// stdafx.obj will contain the pre-compiled type information
+
+#include "stdafx.h"
+
+// TODO: reference any additional headers you need in STDAFX.H
+// and not in this file
diff --git a/devtools/processgamestats/StdAfx.h b/devtools/processgamestats/StdAfx.h
new file mode 100644
index 0000000..8b6ae51
--- /dev/null
+++ b/devtools/processgamestats/StdAfx.h
@@ -0,0 +1,26 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//
+//=============================================================================//
+// stdafx.h : include file for standard system include files,
+// or project specific include files that are used frequently, but
+// are changed infrequently
+//
+
+#if !defined(AFX_STDAFX_H__BC019BF6_D3B1_4EA1_AA0E_044EEC919C6D__INCLUDED_)
+#define AFX_STDAFX_H__BC019BF6_D3B1_4EA1_AA0E_044EEC919C6D__INCLUDED_
+
+#if _MSC_VER > 1000
+#pragma once
+#endif // _MSC_VER > 1000
+
+
+// TODO: reference additional headers your program requires here
+
+//{{AFX_INSERT_LOCATION}}
+// Microsoft Visual C++ will insert additional declarations immediately before the previous line.
+
+#endif // !defined(AFX_STDAFX_H__BC019BF6_D3B1_4EA1_AA0E_044EEC919C6D__INCLUDED_)
diff --git a/devtools/processgamestats/base_gamestats_parse.h b/devtools/processgamestats/base_gamestats_parse.h
new file mode 100644
index 0000000..6da452a
--- /dev/null
+++ b/devtools/processgamestats/base_gamestats_parse.h
@@ -0,0 +1,34 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================
+
+#ifndef BASE_GAMESTATS_PARSE_H
+#define BASE_GAMESTATS_PARSE_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+enum CustomDataReturnCode
+{
+ CUSTOMDATA_NONE = 0,
+ CUSTOMDATA_FAILED,
+ CUSTOMDATA_SUCCESS
+};
+
+class IMySQL;
+
+struct ParseContext_t
+{
+ char const *file;
+ char const *gamename;
+ bool describeonly;
+ IMySQL *mysql;
+ int skipcount;
+ bool bCustomDirectoryNotMade;
+};
+
+void ProcessPerfData( IMySQL *pSQL, time_t fileTime, char const *pGameName, char const *pPerfString );
+
+#endif // BASE_GAMESTATS_PARSE_H
diff --git a/devtools/processgamestats/cs_gamestats.h b/devtools/processgamestats/cs_gamestats.h
new file mode 100644
index 0000000..25fd1ef
--- /dev/null
+++ b/devtools/processgamestats/cs_gamestats.h
@@ -0,0 +1,45 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: The cs game stats header
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#define CS_STATS_BLOB_VERSION 3
+
+extern const char *pValidStatLevels[];
+
+#define CS_NUM_LEVELS 18
+#define WEAPON_MAX 34
+
+int GetCSLevelIndex( const char *pLevelName );
+
+typedef struct
+{
+ char szGameName[8];
+ byte iVersion;
+ char szMapName[32];
+ char ipAddr[4];
+ short port;
+ int serverid;
+} gamestats_header_t;
+
+typedef struct
+{
+ gamestats_header_t header;
+ short iMinutesPlayed;
+
+ short iTerroristVictories[CS_NUM_LEVELS];
+ short iCounterTVictories[CS_NUM_LEVELS];
+ short iBlackMarketPurchases[WEAPON_MAX];
+
+ short iAutoBuyPurchases;
+ short iReBuyPurchases;
+ short iAutoBuyM4A1Purchases;
+ short iAutoBuyAK47Purchases;
+ short iAutoBuyFamasPurchases;
+ short iAutoBuyGalilPurchases;
+ short iAutoBuyVestHelmPurchases;
+ short iAutoBuyVestPurchases;
+
+} cs_gamestats_t;
diff --git a/devtools/processgamestats/cstrike_gamestats.db b/devtools/processgamestats/cstrike_gamestats.db
new file mode 100644
index 0000000..30210d3
--- /dev/null
+++ b/devtools/processgamestats/cstrike_gamestats.db
@@ -0,0 +1,191 @@
+create table weapons
+(
+ WeaponID INT,
+ PRIMARY KEY( WeaponID ),
+ Count INT,
+) TYPE=MyISAM;
+
+create table maps
+(
+ MapName CHAR(16),
+ PRIMARY KEY( MapName ),
+ TerroristWins INT,
+ CTWins INT,
+) TYPE=MyISAM;
+
+create table autobuy
+(
+ Purchase CHAR(16),
+ PRIMARY KEY( Purchase ),
+ Count INT,
+) TYPE=MyISAM;
+
+create table purchases_pricing
+(
+ snapshot CHAR(16),
+ PRIMARY KEY( snapshot ),
+ dt2 DATETIME,
+ weapon_id INT,
+ purchases INT,
+ current_price INT,
+ projected_price INT,
+
+) TYPE=MyISAM;
+
+create table snapshot_counter
+(
+ counter INT,
+ PRIMARY KEY( counter ),
+
+) TYPE=MyISAM;
+
+create table weapon_info
+(
+ WeaponID INT,
+ PRIMARY KEY( WeaponID ),
+ side ENUM('Terrorists', 'Counter-Terrorists', 'Both'),
+ fullname CHAR( 32 ),
+ default_price INT,
+ current_price INT,
+) TYPE=MyISAM;
+
+
+
+Insert into weapon_info ( WeaponID, side, fullname, default_price, current_price ) values ( 1, 3, "228 COMPACT", 600, 600 );
+Insert into weapon_info ( WeaponID, side, fullname, default_price, current_price ) values ( 2, 3, "9X19MM SIDEARM", 400, 400 );
+Insert into weapon_info ( WeaponID, side, fullname, default_price, current_price ) values ( 16, 3, "K&M .45 TACTICAL", 500, 500 );
+Insert into weapon_info ( WeaponID, side, fullname, default_price, current_price ) values ( 25, 3, "NIGHT HAWK .50C", 650, 650 );
+Insert into weapon_info ( WeaponID, side, fullname, default_price, current_price ) values ( 11, 2, "ES FIVE-SEVEN", 750, 750 );
+Insert into weapon_info ( WeaponID, side, fullname, default_price, current_price ) values ( 10, 1, ".40 DUAL ELITES", 800, 800 );
+
+Insert into weapon_info ( WeaponID, side, fullname, default_price, current_price ) values ( 20, 3, "LEONE 12 GAUGE SUPER", 1700, 1700 );
+Insert into weapon_info ( WeaponID, side, fullname, default_price, current_price ) values ( 5, 3, "LEONE YG1265 AUTO SHOTGUN", 3000, 3000 );
+
+Insert into weapon_info ( WeaponID, side, fullname, default_price, current_price ) values ( 22, 2, "SCHMIDT MACHINE PISTOL", 1250, 1250 );
+Insert into weapon_info ( WeaponID, side, fullname, default_price, current_price ) values ( 18, 3, "K&M SUB-MACHINE GUN", 1500, 1500 );
+Insert into weapon_info ( WeaponID, side, fullname, default_price, current_price ) values ( 12, 3, "K&M UMP45", 1700, 1700 );
+Insert into weapon_info ( WeaponID, side, fullname, default_price, current_price ) values ( 29, 3, "ES C90", 2350, 2350 );
+Insert into weapon_info ( WeaponID, side, fullname, default_price, current_price ) values ( 7, 1, "INGRAM MAC-10", 1400, 1400 );
+
+Insert into weapon_info ( WeaponID, side, fullname, default_price, current_price ) values ( 15, 2, "CLARION 5.56", 2250, 2250 );
+Insert into weapon_info ( WeaponID, side, fullname, default_price, current_price ) values ( 3, 3, "SCHMIDT SCOUT", 2750, 2750 );
+Insert into weapon_info ( WeaponID, side, fullname, default_price, current_price ) values ( 21, 2, "MAVERICK M4A1 CARBINE", 3100, 3100 );
+Insert into weapon_info ( WeaponID, side, fullname, default_price, current_price ) values ( 8, 2, "BULLPUP", 3500, 3500 );
+Insert into weapon_info ( WeaponID, side, fullname, default_price, current_price ) values ( 13, 1, "KRIEG 550 COMMANDO", 4200, 4200 );
+Insert into weapon_info ( WeaponID, side, fullname, default_price, current_price ) values ( 26, 1, "KRIEG 552", 3500, 3500 );
+Insert into weapon_info ( WeaponID, side, fullname, default_price, current_price ) values ( 17, 3, "MAGNUM SNIPER RIFLE", 4750, 4750 );
+Insert into weapon_info ( WeaponID, side, fullname, default_price, current_price ) values ( 14, 1, "IDF DEFENDER", 2000, 2000 );
+Insert into weapon_info ( WeaponID, side, fullname, default_price, current_price ) values ( 27, 1, "CV-47", 2500, 2500 );
+Insert into weapon_info ( WeaponID, side, fullname, default_price, current_price ) values ( 23, 2, "D3/AU-1", 5000, 5000 );
+Insert into weapon_info ( WeaponID, side, fullname, default_price, current_price ) values ( 19, 3, "M249", 5750, 5750 );
+
+Insert into weapon_info ( WeaponID, side, fullname, default_price, current_price ) values ( 31, 3, "KEVLAR", 650, 650 );
+Insert into weapon_info ( WeaponID, side, fullname, default_price, current_price ) values ( 32, 3, "KEVLAR+HELMET", 1000, 1000 );
+Insert into weapon_info ( WeaponID, side, fullname, default_price, current_price ) values ( 24, 3, "FLASHBANG", 200, 200 );
+Insert into weapon_info ( WeaponID, side, fullname, default_price, current_price ) values ( 4, 3, "HE GRENADE", 300, 300 );
+Insert into weapon_info ( WeaponID, side, fullname, default_price, current_price ) values ( 9, 3, "SMOKE GRENADE", 300, 300 );
+Insert into weapon_info ( WeaponID, side, fullname, default_price, current_price ) values ( 33, 3, "NIGHTVISION", 1250, 1250 );
+
+
+
+# update weapons set Count=Count+450 where WeaponID = 0;
+# Insert into weapons (WeaponID,Count) values ( 1, 0 );
+
+# update maps set TerroristWins=0, CTWins=0 where MapName = "cs_assault";
+
+# Insert into maps (MapName,TerroristWins, CTWins) values ( "cs_assault", 0, 0 );
+# Insert into maps (MapName,TerroristWins, CTWins) values ( "cs_compound", 0, 0 );
+# Insert into maps (MapName,TerroristWins, CTWins) values ( "cs_havana", 0, 0 );
+# Insert into maps (MapName,TerroristWins, CTWins) values ( "cs_italy", 0, 0 );
+# Insert into maps (MapName,TerroristWins, CTWins) values ( "cs_militia", 0, 0 );
+# Insert into maps (MapName,TerroristWins, CTWins) values ( "cs_office", 0, 0 );
+# Insert into maps (MapName,TerroristWins, CTWins) values ( "de_aztec", 0, 0 );
+# Insert into maps (MapName,TerroristWins, CTWins) values ( "de_cbble", 0, 0 );
+# Insert into maps (MapName,TerroristWins, CTWins) values ( "de_chateau", 0, 0 );
+# Insert into maps (MapName,TerroristWins, CTWins) values ( "de_dust2", 0, 0 );
+# Insert into maps (MapName,TerroristWins, CTWins) values ( "de_dust", 0, 0 );
+# Insert into maps (MapName,TerroristWins, CTWins) values ( "de_inferno", 0, 0 );
+# Insert into maps (MapName,TerroristWins, CTWins) values ( "de_nuke", 0, 0 );
+# Insert into maps (MapName,TerroristWins, CTWins) values ( "de_piranesi", 0, 0 );
+# Insert into maps (MapName,TerroristWins, CTWins) values ( "de_port", 0, 0 );
+# Insert into maps (MapName,TerroristWins, CTWins) values ( "de_prodigy", 0, 0 );
+# Insert into maps (MapName,TerroristWins, CTWins) values ( "de_tides", 0, 0 );
+# Insert into maps (MapName,TerroristWins, CTWins) values ( "de_train", 0, 0 );
+
+
+//Autobuy
+Insert into autobuy (Purchase, Count) values ( "Auto-Buy", 0 );
+Insert into autobuy (Purchase, Count) values ( "Re-Buy", 0 );
+Insert into autobuy (Purchase, Count) values ( "Auto-Buy: M4A1", 0 );
+Insert into autobuy (Purchase, Count) values ( "Auto-Buy: AK47", 0 );
+Insert into autobuy (Purchase, Count) values ( "Auto-Buy: Famas", 0 );
+Insert into autobuy (Purchase, Count) values ( "Auto-Buy: Galil", 0 );
+Insert into autobuy (Purchase, Count) values ( "Auto-Buy: Suit", 0 );
+Insert into autobuy (Purchase, Count) values ( "Auto-Buy: Kev", 0 );
+
+update autobuy set Count=0 where Purchase = "Auto-Buy";
+update autobuy set Count=0 where Purchase = "Re-Buy";
+update autobuy set Count=0 where Purchase = "Auto-Buy: M4A1";
+update autobuy set Count=0 where Purchase = "Auto-Buy: AK47";
+update autobuy set Count=0 where Purchase = "Auto-Buy: Famas";
+update autobuy set Count=0 where Purchase = "Auto-Buy: Galil";
+update autobuy set Count=0 where Purchase = "Auto-Buy: Suit";
+update autobuy set Count=0 where Purchase = "Auto-Buy: Kev";
+
+
+
+//Copy and Paste into the SQL terminal to reset everything
+
+update weapons set Count=0 where WeaponID = 1;
+update weapons set Count=0 where WeaponID = 2;
+update weapons set Count=0 where WeaponID = 3;
+update weapons set Count=0 where WeaponID = 4;
+update weapons set Count=0 where WeaponID = 5;
+update weapons set Count=0 where WeaponID = 6;
+update weapons set Count=0 where WeaponID = 7;
+update weapons set Count=0 where WeaponID = 8;
+update weapons set Count=0 where WeaponID = 9;
+update weapons set Count=0 where WeaponID = 10;
+update weapons set Count=0 where WeaponID = 11;
+update weapons set Count=0 where WeaponID = 12;
+update weapons set Count=0 where WeaponID = 13;
+update weapons set Count=0 where WeaponID = 14;
+update weapons set Count=0 where WeaponID = 15;
+update weapons set Count=0 where WeaponID = 16;
+update weapons set Count=0 where WeaponID = 17;
+update weapons set Count=0 where WeaponID = 18;
+update weapons set Count=0 where WeaponID = 19;
+update weapons set Count=0 where WeaponID = 20;
+update weapons set Count=0 where WeaponID = 21;
+update weapons set Count=0 where WeaponID = 22;
+update weapons set Count=0 where WeaponID = 23;
+update weapons set Count=0 where WeaponID = 24;
+update weapons set Count=0 where WeaponID = 25;
+update weapons set Count=0 where WeaponID = 26;
+update weapons set Count=0 where WeaponID = 27;
+update weapons set Count=0 where WeaponID = 28;
+update weapons set Count=0 where WeaponID = 29;
+update weapons set Count=0 where WeaponID = 30;
+update weapons set Count=0 where WeaponID = 31;
+update weapons set Count=0 where WeaponID = 32;
+update weapons set Count=0 where WeaponID = 33;
+
+update maps set TerroristWins=0, CTWins=0 where MapName = "cs_assault";
+update maps set TerroristWins=0, CTWins=0 where MapName = "cs_compound";
+update maps set TerroristWins=0, CTWins=0 where MapName = "cs_havana";
+update maps set TerroristWins=0, CTWins=0 where MapName = "cs_italy";
+update maps set TerroristWins=0, CTWins=0 where MapName = "cs_militia";
+update maps set TerroristWins=0, CTWins=0 where MapName = "cs_office";
+update maps set TerroristWins=0, CTWins=0 where MapName = "de_aztec";
+update maps set TerroristWins=0, CTWins=0 where MapName = "de_cbble";
+update maps set TerroristWins=0, CTWins=0 where MapName = "de_chateau";
+update maps set TerroristWins=0, CTWins=0 where MapName = "de_dust2";
+update maps set TerroristWins=0, CTWins=0 where MapName = "de_dust";
+update maps set TerroristWins=0, CTWins=0 where MapName = "de_inferno";
+update maps set TerroristWins=0, CTWins=0 where MapName = "de_nuke";
+update maps set TerroristWins=0, CTWins=0 where MapName = "de_piranesi";
+update maps set TerroristWins=0, CTWins=0 where MapName = "de_port";
+update maps set TerroristWins=0, CTWins=0 where MapName = "de_prodigy";
+update maps set TerroristWins=0, CTWins=0 where MapName = "de_tides";
+update maps set TerroristWins=0, CTWins=0 where MapName = "de_train";
+
diff --git a/devtools/processgamestats/cstrike_gamestats_parse.cpp b/devtools/processgamestats/cstrike_gamestats_parse.cpp
new file mode 100644
index 0000000..6180279
--- /dev/null
+++ b/devtools/processgamestats/cstrike_gamestats_parse.cpp
@@ -0,0 +1,235 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//
+//=============================================================================//
+//
+//
+
+#include "stdafx.h"
+#include <stdio.h>
+#include <process.h>
+#include <string.h>
+#include <windows.h>
+#include <sys/stat.h>
+
+#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 "cs_gamestats.h"
+#include "base_gamestats_parse.h"
+
+extern CUtlDict< int, unsigned short > g_mapOrder;
+
+const char *pValidStatLevels[] =
+{
+ "cs_assault",
+ "cs_compound",
+ "cs_havana",
+ "cs_italy",
+ "cs_militia",
+ "cs_office",
+ "de_aztec",
+ "de_cbble",
+ "de_chateau",
+ "de_dust2",
+ "de_dust",
+ "de_inferno",
+ "de_nuke",
+ "de_piranesi",
+ "de_port",
+ "de_prodigy",
+ "de_tides",
+ "de_train",
+};
+
+static const char * s_WeaponAliasInfo[] =
+{
+ "none", // WEAPON_NONE
+ "p228", // WEAPON_P228
+ "glock", // WEAPON_GLOCK // old glock
+ "scout", // WEAPON_SCOUT
+ "hegren", // WEAPON_HEGRENADE
+ "xm1014", // WEAPON_XM1014 // auto shotgun
+ "c4", // WEAPON_C4
+ "mac10", // WEAPON_MAC10 // T only
+ "aug", // WEAPON_AUG
+ "sgren", // WEAPON_SMOKEGRENADE
+ "elite", // WEAPON_ELITE
+ "fiveseven",// WEAPON_FIVESEVEN
+ "ump45", // WEAPON_UMP45
+ "sg550", // WEAPON_SG550 // auto-sniper
+ "galil", // WEAPON_GALIL
+ "famas", // WEAPON_FAMAS // CT cheap m4a1
+ "usp", // WEAPON_USP
+ "awp", // WEAPON_AWP
+ "mp5navy", // WEAPON_MP5N
+ "m249", // WEAPON_M249 // big machinegun
+ "m3", // WEAPON_M3 // cheap shotgun
+ "m4a1", // WEAPON_M4A1
+ "tmp", // WEAPON_TMP
+ "g3sg1", // WEAPON_G3SG1 // T auto-sniper
+ "flash", // WEAPON_FLASHBANG
+ "deagle", // WEAPON_DEAGLE
+ "sg552", // WEAPON_SG552 // T aug equivalent
+ "ak47", // WEAPON_AK47
+ "knife", // WEAPON_KNIFE
+ "p90", // WEAPON_P90
+ "shield", // WEAPON_SHIELDGUN
+ "kevlar",
+ "assaultsuit",
+ "nightvision",
+ NULL, // WEAPON_NONE
+};
+
+void DescribeData( cs_gamestats_t &stats )
+{
+ Msg( " Blob version: %d\n", stats.header.iVersion );
+ Msg( " Server Uptime: %d\n", stats.iMinutesPlayed );
+
+ for ( int i = 0; i < CS_NUM_LEVELS; i++ )
+ {
+ Msg( "%s - Terrorists Wins: %d | Counter-Terrorists Wins: %d\n", pValidStatLevels[i], stats.iTerroristVictories[i], stats.iCounterTVictories[i] );
+ }
+
+ for ( int i = 0; i < WEAPON_MAX; i++ )
+ {
+ Msg( "%s was purchased %d time(s)\n", s_WeaponAliasInfo[i], stats.iBlackMarketPurchases[i] );
+ }
+
+
+ char q[ 512 ];
+
+ Q_snprintf( q, sizeof( q ), "Auto-Buy = %d\n", stats.iAutoBuyPurchases );
+ Msg( q );
+
+ Q_snprintf( q, sizeof( q ), "Re-Buy = %d\n", stats.iReBuyPurchases );
+ Msg( q );
+
+ Q_snprintf( q, sizeof( q ), "Auto-Buy: M4A1 = %d\n", stats.iAutoBuyM4A1Purchases );
+ Msg( q );
+
+ Q_snprintf( q, sizeof( q ), "Auto-Buy: AK47 = %d\n", stats.iAutoBuyAK47Purchases );
+ Msg( q );
+
+ Q_snprintf( q, sizeof( q ), "Auto-Buy: Famas = %d\n", stats.iAutoBuyFamasPurchases );
+ Msg( q );
+
+ Q_snprintf( q, sizeof( q ), "Auto-Buy: Galil = %d\n", stats.iAutoBuyGalilPurchases );
+ Msg( q );
+
+ Q_snprintf( q, sizeof( q ), "Auto-Buy: Suit = %d\n", stats.iAutoBuyVestHelmPurchases );
+ Msg( q );
+
+ Q_snprintf( q, sizeof( q ), "Auto-Buy: Kev = %d\n", stats.iAutoBuyVestPurchases );
+ Msg( q );
+}
+
+int CS_ParseCustomGameStatsData( ParseContext_t *ctx )
+{
+ if ( g_pFullFileSystem == NULL )
+ return CUSTOMDATA_FAILED;
+
+ FileHandle_t FileHandle = g_pFullFileSystem->Open( ctx->file, "rb" );
+
+ if ( !FileHandle )
+ {
+ return CUSTOMDATA_FAILED;
+ }
+
+ if ( ctx->mysql == NULL && ctx->describeonly == false )
+ return CUSTOMDATA_FAILED;
+
+ char q[ 512 ];
+ cs_gamestats_t stats;
+ g_pFullFileSystem->Read( &stats, sizeof( cs_gamestats_t ), FileHandle );
+
+ if ( Q_stricmp( stats.header.szGameName, "cstrike" ) )
+ return CUSTOMDATA_FAILED;
+
+ if ( stats.header.iVersion != CS_STATS_BLOB_VERSION )
+ {
+ Msg( "Error: Incorrect Blob Version! Got: %d - Expected: %d\n", stats.header.iVersion, CS_STATS_BLOB_VERSION );
+ return CUSTOMDATA_FAILED;
+ }
+
+ if ( ctx->describeonly == true )
+ {
+ DescribeData( stats );
+ return CUSTOMDATA_SUCCESS;
+ }
+
+ //Do maps first
+ for ( int i = 0; i < CS_NUM_LEVELS; i++ )
+ {
+ Q_snprintf( q, sizeof( q ), "update maps set TerroristWins=TerroristWins+%d, CTWins=CTWins+%d where MapName = \"%s\";", stats.iTerroristVictories[i], stats.iCounterTVictories[i], pValidStatLevels[i] );
+
+ int retcode = ctx->mysql->Execute( q );
+ if ( retcode != 0 )
+ {
+ printf( "Query:\n %s\n failed\n", q );
+ return CUSTOMDATA_FAILED;
+ }
+ }
+
+ //Now do all weapons
+ for ( int i = 0; i < WEAPON_MAX; i++ )
+ {
+ int iWeaponID = i;
+
+ //HACKHACK: Fix up incorrect data for the smoke grenades.
+ if ( i == 0 && stats.iBlackMarketPurchases[i] != 0 )
+ {
+ iWeaponID = 9;
+ }
+
+ Q_snprintf( q, sizeof( q ), "update weapons set Count=Count+%d where WeaponID = %d;", stats.iBlackMarketPurchases[i], iWeaponID );
+
+ int retcode = ctx->mysql->Execute( q );
+ if ( retcode != 0 )
+ {
+ printf( "Query:\n %s\n failed\n", q );
+ return CUSTOMDATA_FAILED;
+ }
+ }
+
+
+ Q_snprintf( q, sizeof( q ), "update autobuy set Count=Count+%d where Purchase = \"Auto-Buy\";", stats.iAutoBuyPurchases );
+ ctx->mysql->Execute( q );
+
+ Q_snprintf( q, sizeof( q ), "update autobuy set Count=Count+%d where Purchase = \"Re-Buy\";", stats.iReBuyPurchases );
+ ctx->mysql->Execute( q );
+
+ Q_snprintf( q, sizeof( q ), "update autobuy set Count=Count+%d where Purchase = \"Auto-Buy: M4A1\";", stats.iAutoBuyM4A1Purchases );
+ ctx->mysql->Execute( q );
+
+ Q_snprintf( q, sizeof( q ), "update autobuy set Count=Count+%d where Purchase = \"Auto-Buy: AK47\";", stats.iAutoBuyAK47Purchases );
+ ctx->mysql->Execute( q );
+
+ Q_snprintf( q, sizeof( q ), "update autobuy set Count=Count+%d where Purchase = \"Auto-Buy: Famas\";", stats.iAutoBuyFamasPurchases );
+ ctx->mysql->Execute( q );
+
+ Q_snprintf( q, sizeof( q ), "update autobuy set Count=Count+%d where Purchase = \"Auto-Buy: Galil\";", stats.iAutoBuyGalilPurchases );
+ ctx->mysql->Execute( q );
+
+ Q_snprintf( q, sizeof( q ), "update autobuy set Count=Count+%d where Purchase = \"Auto-Buy: Suit\";", stats.iAutoBuyVestHelmPurchases );
+ ctx->mysql->Execute( q );
+
+ Q_snprintf( q, sizeof( q ), "update autobuy set Count=Count+%d where Purchase = \"Auto-Buy: Kev\";", stats.iAutoBuyVestPurchases );
+ ctx->mysql->Execute( q );
+
+
+
+ return CUSTOMDATA_SUCCESS;
+}
diff --git a/devtools/processgamestats/ep1_gamestats.db b/devtools/processgamestats/ep1_gamestats.db
new file mode 100644
index 0000000..a8b05dd
--- /dev/null
+++ b/devtools/processgamestats/ep1_gamestats.db
@@ -0,0 +1,47 @@
+# create database gamestats;
+# use gamestats;
+
+create table ep1
+(
+ UserID CHAR(16),
+ PRIMARY KEY( UserID ),
+ LastUpdate DATETIME,
+ KEY( LastUpdate ),
+ Version TINYINT,
+ Count INT,
+ Seconds INT,
+ HDR INT,
+ Captions INT,
+ Commentary INT,
+ Easy INT,
+ Medium INT,
+ Hard INT,
+ nonsteam TINYINT,
+ cybercafe TINYINT,
+ hl2_chapter TINYINT,
+ SecondsToCompleteGame INT, # Non-zero if user has completed game
+ HighestMap CHAR(16),
+ DXLevel INT,
+ Deaths INT,
+) TYPE=MyISAM;
+
+create table ep1_maps
+(
+ UserID CHAR(16),
+ MapName CHAR(16),
+ PRIMARY KEY( UserID,MapName ),
+ LastUpdate DATETIME,
+ KEY( LastUpdate ),
+ Version TINYINT,
+ Count INT,
+ Seconds INT,
+ HDR INT,
+ Captions INT,
+ Commentary INT,
+ Easy INT,
+ Medium INT,
+ Hard INT,
+ nonsteam TINYINT,
+ cybercafe TINYINT,
+ Deaths INT,
+) TYPE=MyISAM; \ No newline at end of file
diff --git a/devtools/processgamestats/ep2_gamestats.db b/devtools/processgamestats/ep2_gamestats.db
new file mode 100644
index 0000000..9002bf3
--- /dev/null
+++ b/devtools/processgamestats/ep2_gamestats.db
@@ -0,0 +1,168 @@
+create database gamestats_ep2;
+use gamestats_ep2;
+
+create table ep2
+(
+ UserID CHAR(16),
+ PRIMARY KEY( UserID ),
+ LastUpdate DATETIME,
+ KEY( LastUpdate ),
+ Version TINYINT,
+ Tag CHAR(8),
+ KEY( Tag ),
+ Count INT,
+ Seconds INT,
+ HDR INT,
+ Captions INT,
+ Commentary INT,
+ Easy INT,
+ Medium INT,
+ Hard INT,
+ nonsteam TINYINT,
+ cybercafe TINYINT,
+ hl2_chapter TINYINT,
+ SecondsToCompleteGame INT, # Non-zero if user has completed game
+ HighestMap CHAR(20),
+ DXLevel INT,
+ Deaths INT
+) TYPE=MyISAM;
+
+create table ep2_maps
+(
+ UserID CHAR(16),
+ MapName CHAR(20),
+ PRIMARY KEY( UserID,MapName ),
+ LastUpdate DATETIME,
+ KEY( LastUpdate ),
+ Version TINYINT,
+ Tag CHAR(8),
+ KEY( Tag ),
+ Count INT,
+ Seconds INT,
+ HDR INT,
+ Captions INT,
+ Commentary INT,
+ Easy INT,
+ Medium INT,
+ Hard INT,
+ nonsteam TINYINT,
+ cybercafe TINYINT,
+ Deaths INT
+) TYPE=MyISAM;
+
+create table ep2_entities
+(
+ UserID CHAR(16),
+ Tag CHAR(8),
+ KEY( Tag ),
+ MapName CHAR(20),
+ MapVersion INT,
+ KEY( MapVersion ),
+ Entity CHAR(32),
+ PRIMARY KEY( UserID,Tag,MapName,Entity ),
+ LastUpdate DATETIME,
+ KEY( LastUpdate ),
+ BodyCount INT,
+ KilledPlayer INT
+) TYPE=MyISAM;
+
+create table ep2_deaths
+(
+ UserID CHAR(16),
+ Tag CHAR(8),
+ KEY( Tag ),
+ MapName CHAR(20),
+ MapVersion INT,
+ KEY( MapVersion ),
+ LastUpdate DATETIME,
+ KEY( LastUpdate ),
+ DeathIndex INT,
+ X SMALLINT,
+ Y SMALLINT,
+ Z SMALLINT,
+ PRIMARY KEY ( UserID, Tag, MapName, X, Y, Z )
+) TYPE=MyISAM;
+
+create table ep2_weapons
+(
+ UserID CHAR(16),
+ Tag CHAR(8),
+ KEY( Tag ),
+ MapName CHAR(20),
+ MapVersion INT,
+ KEY( MapVersion ),
+ Weapon CHAR(32),
+ PRIMARY KEY( UserID,Tag, MapName,Weapon ),
+ LastUpdate DATETIME,
+ KEY( LastUpdate ),
+ Shots INT,
+ Hits INT,
+ Damage DOUBLE
+) TYPE=MyISAM;
+
+create table ep2_saves
+(
+ UserID CHAR(16),
+ Tag CHAR(8),
+ KEY( Tag ),
+ MapName CHAR(20),
+ MapVersion INT,
+ KEY( MapVersion ),
+ LastUpdate DATETIME,
+ KEY( LastUpdate ),
+ FIRSTDEATH INT, # index into ep2_deaths
+ NUMDEATHS INT,
+ X SMALLINT,
+ Y SMALLINT,
+ Z SMALLINT,
+ HEALTH SMALLINT,
+ SAVETYPE TINYINT, # 0 unknown, 1 autosave, 2 user save (quick or other)
+ PRIMARY KEY( UseriD, Tag, MapName, FirstDeath, NumDeaths )
+) TYPE=MyISAM;
+
+create table ep2_counters
+(
+ UserID CHAR(16),
+ Tag CHAR(8),
+ KEY( Tag ),
+ MapName CHAR(20),
+ MapVersion INT,
+ KEY( MapVersion ),
+ PRIMARY KEY( UserID,Tag,MapName ),
+ LastUpdate DATETIME,
+ KEY( LastUpdate ),
+ CRATESSMASHED INT,
+ OBJECTSPUNTED INT,
+ VEHICULARHOMICIDES INT,
+ DISTANCE_INVEHICLE BIGINT,
+ DISTANCE_ONFOOT BIGINT,
+ DISTANCE_ONFOOTSPRINTING BIGINT,
+ FALLINGDEATHS INT,
+ VEHICLE_OVERTURNED INT,
+ LOADGAME_STILLALIVE INT,
+ LOADS INT,
+ SAVES INT,
+ GODMODES INT,
+ NOCLIPS INT,
+
+ DAMAGETAKEN DOUBLE
+) TYPE=MyISAM;
+
+create table ep2_generic
+(
+ UserID CHAR(16),
+ Tag CHAR(8),
+ KEY( Tag ),
+ MapName CHAR(20),
+ MapVersion INT,
+ KEY( MapVersion ),
+ StatName CHAR(16),
+ PRIMARY KEY( UserID,Tag, MapName,StatName ),
+ LastUpdate DATETIME,
+ KEY( LastUpdate ),
+ Count INT,
+ Value DOUBLE,
+ X SMALLINT,
+ Y SMALLINT,
+ Z SMALLINT
+) TYPE=MyISAM; \ No newline at end of file
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;
+}
+
diff --git a/devtools/processgamestats/portal_gamestats.db b/devtools/processgamestats/portal_gamestats.db
new file mode 100644
index 0000000..65acddf
--- /dev/null
+++ b/devtools/processgamestats/portal_gamestats.db
@@ -0,0 +1,47 @@
+# create database gamestats;
+# use gamestats;
+
+create table portal
+(
+ UserID CHAR(16),
+ PRIMARY KEY( UserID ),
+ LastUpdate DATETIME,
+ KEY( LastUpdate ),
+ Version TINYINT,
+ Count INT,
+ Seconds INT,
+ HDR INT,
+ Captions INT,
+ Commentary INT,
+ Easy INT,
+ Medium INT,
+ Hard INT,
+ nonsteam TINYINT,
+ cybercafe TINYINT,
+ hl2_chapter TINYINT,
+ SecondsToCompleteGame INT, # Non-zero if user has completed game
+ HighestMap CHAR(16),
+ DXLevel INT,
+ Deaths INT,
+) TYPE=MyISAM;
+
+create table portal_maps
+(
+ UserID CHAR(16),
+ MapName CHAR(16),
+ PRIMARY KEY( UserID,MapName ),
+ LastUpdate DATETIME,
+ KEY( LastUpdate ),
+ Version TINYINT,
+ Count INT,
+ Seconds INT,
+ HDR INT,
+ Captions INT,
+ Commentary INT,
+ Easy INT,
+ Medium INT,
+ Hard INT,
+ nonsteam TINYINT,
+ cybercafe TINYINT,
+ Deaths INT,
+) TYPE=MyISAM; \ No newline at end of file
diff --git a/devtools/processgamestats/processgamestats.cpp b/devtools/processgamestats/processgamestats.cpp
new file mode 100644
index 0000000..05e643f
--- /dev/null
+++ b/devtools/processgamestats/processgamestats.cpp
@@ -0,0 +1,982 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//
+//=============================================================================//
+// tagbuild.cpp : Defines the entry point for the console application.
+//
+
+#include "stdafx.h"
+#include <stdio.h>
+#include <process.h>
+#include <string.h>
+#include <windows.h>
+#include <sys/stat.h>
+#include <time.h>
+
+#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 "KeyValues.h"
+#include "filesystem_helpers.h"
+#include "tier2/tier2.h"
+#include "filesystem.h"
+#include "base_gamestats_parse.h"
+#include "cbase.h"
+#include "gamestats.h"
+#include "tier0/icommandline.h"
+
+// roll-our-own symbol table class. Note we don't use CUtlSymbolTable because that and related classes have short int deeply baked in as index type, so can
+// only hold 64K entries. We sometimes need to process more than 64K files at a time.
+struct AnalysisData
+{
+ AnalysisData()
+ {
+ symbols.SetLessFunc( CaselessStringLessThanIgnoreSlashes );
+ }
+
+ ~AnalysisData()
+ {
+ int i = symbols.FirstInorder();
+ while ( i != symbols.InvalidIndex() )
+ {
+ const char *symbol = symbols[i];
+ if ( symbol )
+ {
+ delete symbol;
+ }
+ i = symbols.NextInorder( i );
+ }
+ }
+
+ CUtlRBTree<const char*,int> symbols;
+};
+
+static AnalysisData g_Analysis;
+
+static bool describeonly = false;
+
+typedef int (*DataParseFunc)( ParseContext_t * );
+typedef void (*PostImportFunc) ( IMySQL *sql );
+typedef bool (*ParseCurrentUserIDFunc)( char const *pchDataFile, char *pchUserID, size_t bufsize, time_t &modifiedtime );
+
+extern int CS_ParseCustomGameStatsData( ParseContext_t *ctx );
+extern int Ep2_ParseCustomGameStatsData( ParseContext_t *ctx );
+extern int TF_ParseCustomGameStatsData( ParseContext_t *ctx );
+extern void TF_PostImport( IMySQL *sql );
+
+int Default_ParseCustomGameStatsData( ParseContext_t *ctx );
+
+extern bool Ep2_ParseCurrentUserID( char const *pchDataFile, char *pchUserID, size_t bufsize, time_t &modifiedtime );
+
+struct DataParser_t
+{
+ char const *pchGameName;
+ DataParseFunc pfnParseFunc;
+ PostImportFunc pfnPostImport;
+ ParseCurrentUserIDFunc pfnParseUserID;
+};
+
+static DataParser_t g_ParseFuncs[] =
+{
+ { "cstrike", CS_ParseCustomGameStatsData, NULL },
+ { "tf", TF_ParseCustomGameStatsData, TF_PostImport },
+// { "dods", Default_ParseCustomGameStatsData, NULL },
+// { "portal", Default_ParseCustomGameStatsData, NULL },
+ { "ep1", Default_ParseCustomGameStatsData, NULL }, // Just a STUB
+ { "ep2", Ep2_ParseCustomGameStatsData, NULL, Ep2_ParseCurrentUserID }
+};
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void printusage( void )
+{
+ printf( "processgamestats:\n" );
+ printf( "processgamestats game dbhost user password dbname rootdir\n" );
+ printf( "processgamestats game datafile [describe only]\n\n" );
+ printf( "valid gamenames:\n" );
+
+ for ( int i = 0 ; i < ARRAYSIZE( g_ParseFuncs ); ++i )
+ {
+ printf( " %s\n", g_ParseFuncs[ i ].pchGameName );
+ }
+
+ // Exit app
+ exit( 1 );
+}
+
+void BuildFileList_R( CUtlVector< int >& files, char const *dir, char const *extension )
+{
+ WIN32_FIND_DATA wfd;
+
+ char directory[ 256 ];
+ char filename[ 256 ];
+ HANDLE ff;
+
+ sprintf( directory, "%s\\*.*", dir );
+
+ if ( ( ff = FindFirstFile( directory, &wfd ) ) == INVALID_HANDLE_VALUE )
+ return;
+
+ int extlen = strlen( extension );
+
+ do
+ {
+ if ( wfd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY )
+ {
+
+ if ( wfd.cFileName[ 0 ] == '.' )
+ continue;
+
+ // Recurse down directory
+ sprintf( filename, "%s\\%s", dir, wfd.cFileName );
+ BuildFileList_R( files, filename, extension );
+ }
+ else
+ {
+ int len = strlen( wfd.cFileName );
+ if ( len > extlen )
+ {
+ if ( !stricmp( &wfd.cFileName[ len - extlen ], extension ) )
+ {
+ char filename[ MAX_PATH ];
+ Q_snprintf( filename, sizeof( filename ), "%s\\%s", dir, wfd.cFileName );
+ _strlwr( filename );
+
+ Q_FixSlashes( filename );
+
+ char *symbol = strdup( filename );
+ int sym = g_Analysis.symbols.Insert( symbol );
+
+ files.AddToTail( sym );
+ }
+ }
+ }
+ } while ( FindNextFile( ff, &wfd ) );
+}
+
+void BuildFileList( CUtlVector< int >& files, char const *rootdir, char const *extension )
+{
+ files.RemoveAll();
+ BuildFileList_R( files, rootdir, extension );
+}
+
+void DescribeData( BasicGameStats_t &stats, const char *szStatsFileUserID, int iStatsFileVersion )
+{
+ double averageSession = 0.0f;
+ if ( stats.m_Summary.m_nCount > 0 )
+ {
+ averageSession = (double)stats.m_Summary.m_nSeconds / (double)stats.m_Summary.m_nCount;
+ }
+
+ Msg( "---------------------------------------------------------------------------\n" );
+ Msg( "%16.16s : %s\n", "User", szStatsFileUserID );
+ Msg( " %16.16s: %8d\n", "Blob version", iStatsFileVersion );
+ Msg( " %16.16s: %8d sessions\n", "Played", stats.m_Summary.m_nCount );
+ Msg( " %16.16s: %8d seconds\n", "Total Time", stats.m_Summary.m_nSeconds );
+ Msg( " %16.16s: %8.2f seconds\n", "Avg Session", averageSession );
+
+ Msg( " %16.16s: %8d\n", "Commentary", stats.m_Summary.m_nCommentary );
+ Msg( " %16.16s: %8d\n", "HDR", stats.m_Summary.m_nHDR );
+ Msg( " %16.16s: %8d\n", "Captions", stats.m_Summary.m_nCaptions );
+ Msg( " %16.16s: %8d\n", "Easy", stats.m_Summary.m_nSkill[ 0 ] );
+ Msg( " %16.16s: %8d\n", "Medium", stats.m_Summary.m_nSkill[ 1 ] );
+ Msg( " %16.16s: %8d\n", "Hard", stats.m_Summary.m_nSkill[ 2 ] );
+ Msg( " %16.16s: %8d seconds\n", "Completion time ", stats.m_nSecondsToCompleteGame );
+ Msg( " %16.16s: %8d\n", "Number of deaths", stats.m_Summary.m_nDeaths );
+
+ Msg( " -- Maps played --\n" );
+
+ for ( int i = stats.m_MapTotals.First(); i != stats.m_MapTotals.InvalidIndex(); i = stats.m_MapTotals.Next( i ) )
+ {
+ char const *mapname = stats.m_MapTotals.GetElementName( i );
+ BasicGameStatsRecord_t &rec = stats.m_MapTotals[ i ];
+
+ Msg( " %16.16s: %5d seconds in %3d sessions (%4d deaths)\n", mapname, rec.m_nSeconds, rec.m_nCount, rec.m_nDeaths );
+ }
+}
+
+#include <string>
+//-------------------------------------------------
+void v_escape_string (std::string& s)
+{
+ if ( !s.size() )
+ return;
+ for ( unsigned int i = 0;i<s.size();i++ )
+ {
+ switch (s[i])
+ {
+ case '\0': /* Must be escaped for "mysql" */
+ s[i] = '\\';
+ s.insert(i+1,"0",1); i++;//lint !e534
+ break;
+ case '\n': /* Must be escaped for logs */
+ s[i] = '\\';
+ s.insert(i+1,"n",1); i++;//lint !e534
+ break;
+ case '\r':
+ s[i] = '\\';
+ s.insert(i+1,"r",1); i++;//lint !e534
+ break;
+ case '\\':
+ s[i] = '\\';
+ s.insert(i+1,"\\",1); i++;//lint !e534
+ break;
+ case '\"':
+ s[i] = '\\';
+ s.insert(i+1,"\"",1); i++;//lint !e534
+ break;
+ case '\'': /* Better safe than sorry */
+ s[i] = '\\';
+ s.insert(i+1,"\'",1); i++;//lint !e534
+ break;
+ case '\032': /* This gives problems on Win32 */
+ s[i] = '\\';
+ s.insert(i+1,"Z",1); i++;//lint !e534
+ break;
+ default:
+ break;
+ }
+ }
+}
+
+void InsertData( CUtlDict< int, unsigned short >& mapOrder, IMySQL *sql, BasicGameStats_t &gs, const char *szStatsFileUserID, int iStatsFileVersion, const char *gamename, char const *tag = NULL )
+{
+ if ( !sql )
+ return;
+
+ char q[ 512 ];
+
+ std::string userid;
+ userid = szStatsFileUserID;
+ v_escape_string( userid );
+
+ int farthestPlayed = -1;
+ std::string highestmap;
+
+ int namelen = 20;
+ if ( !Q_stricmp( gamename, "ep1" ) )
+ {
+ namelen = 16;
+ }
+
+ char finalname[ 64 ];
+
+ std::string finaltag;
+ finaltag = tag ? tag : "";
+ v_escape_string( finaltag );
+
+ // Deal with the maps
+ for ( int i = gs.m_MapTotals.First(); i != gs.m_MapTotals.InvalidIndex(); i = gs.m_MapTotals.Next( i ) )
+ {
+ char const *pszMapName = gs.m_MapTotals.GetElementName( i );
+ std::string mapname;
+ mapname = pszMapName;
+ v_escape_string( mapname );
+
+ Q_strncpy( finalname, mapname.c_str(), namelen );
+
+ int slot = mapOrder.Find( pszMapName );
+ if ( slot != mapOrder.InvalidIndex() )
+ {
+ int order = mapOrder[ slot ];
+ if ( order > farthestPlayed )
+ {
+ farthestPlayed = order;
+ }
+ }
+ else
+ {
+ if ( Q_stricmp( pszMapName, "devtest" ) )
+ continue;
+ }
+
+
+
+ BasicGameStatsRecord_t& rec = gs.m_MapTotals[ i ];
+
+ if ( tag )
+ {
+ Q_snprintf( q, sizeof( q ), "REPLACE into %s_maps (UserID,LastUpdate,Version,MapName,Tag,Count,Seconds,HDR,Captions,Commentary,Easy,Medium,Hard,nonsteam,cybercafe,Deaths) values (\"%s\",Now(),%d,\"%s\",\"%s\",%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d);",
+ gamename,
+ userid.c_str(),
+ iStatsFileVersion,
+ finalname,
+ finaltag.c_str(),
+ rec.m_nCount,
+ rec.m_nSeconds,
+ rec.m_nHDR,
+ rec.m_nCaptions,
+ rec.m_nCommentary,
+ rec.m_nSkill[ 0 ],
+ rec.m_nSkill[ 1 ],
+ rec.m_nSkill[ 2 ],
+ rec.m_bSteam ? 0 : 1,
+ rec.m_bCyberCafe ? 1 : 0,
+ rec.m_nDeaths );
+ }
+ else
+ {
+ Q_snprintf( q, sizeof( q ), "REPLACE into %s_maps (UserID,LastUpdate,Version,MapName,Count,Seconds,HDR,Captions,Commentary,Easy,Medium,Hard,nonsteam,cybercafe,Deaths) values (\"%s\",Now(),%d,\"%s\",%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d);",
+ gamename,
+ userid.c_str(),
+ iStatsFileVersion,
+ finalname,
+ rec.m_nCount,
+ rec.m_nSeconds,
+ rec.m_nHDR,
+ rec.m_nCaptions,
+ rec.m_nCommentary,
+ rec.m_nSkill[ 0 ],
+ rec.m_nSkill[ 1 ],
+ rec.m_nSkill[ 2 ],
+ rec.m_bSteam ? 0 : 1,
+ rec.m_bCyberCafe ? 1 : 0,
+ rec.m_nDeaths );
+ }
+
+ int retcode = sql->Execute( q );
+ if ( retcode != 0 )
+ {
+ printf( "Query %s failed\n", q );
+ return;
+ }
+ }
+
+ if ( farthestPlayed != -1 )
+ {
+ highestmap = mapOrder.GetElementName( farthestPlayed );
+ }
+ v_escape_string( highestmap );
+ Q_strncpy( finalname, highestmap.c_str(), namelen );
+
+ if ( tag )
+ {
+ Q_snprintf( q, sizeof( q ), "REPLACE into %s (UserID,LastUpdate,Version,Tag,Count,Seconds,HDR,Captions,Commentary,Easy,Medium,Hard,SecondsToCompleteGame,HighestMap,nonsteam,cybercafe,hl2_chapter,dxlevel,Deaths) values (\"%s\",Now(),%d,\"%s\",%d,%d,%d,%d,%d,%d,%d,%d,%d,\"%s\",%d,%d,%d,%d,%d);",
+
+ gamename,
+ userid.c_str(),
+ iStatsFileVersion,
+ finaltag.c_str(),
+ gs.m_Summary.m_nCount,
+ gs.m_Summary.m_nSeconds,
+ gs.m_Summary.m_nHDR,
+ gs.m_Summary.m_nCaptions,
+ gs.m_Summary.m_nCommentary,
+ gs.m_Summary.m_nSkill[ 0 ],
+ gs.m_Summary.m_nSkill[ 1 ],
+ gs.m_Summary.m_nSkill[ 2 ],
+ gs.m_nSecondsToCompleteGame,
+ finalname,
+ gs.m_bSteam ? 0 : 1,
+ gs.m_bCyberCafe ? 1 : 0,
+ gs.m_nHL2ChaptureUnlocked,
+ gs.m_nDXLevel,
+ gs.m_Summary.m_nDeaths );
+ }
+ else
+ {
+ Q_snprintf( q, sizeof( q ), "REPLACE into %s (UserID,LastUpdate,Version,Count,Seconds,HDR,Captions,Commentary,Easy,Medium,Hard,SecondsToCompleteGame,HighestMap,nonsteam,cybercafe,hl2_chapter,dxlevel,Deaths) values (\"%s\",Now(),%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,\"%s\",%d,%d,%d,%d,%d);",
+
+ gamename,
+ userid.c_str(),
+ iStatsFileVersion,
+ gs.m_Summary.m_nCount,
+ gs.m_Summary.m_nSeconds,
+ gs.m_Summary.m_nHDR,
+ gs.m_Summary.m_nCaptions,
+ gs.m_Summary.m_nCommentary,
+ gs.m_Summary.m_nSkill[ 0 ],
+ gs.m_Summary.m_nSkill[ 1 ],
+ gs.m_Summary.m_nSkill[ 2 ],
+ gs.m_nSecondsToCompleteGame,
+ finalname,
+ gs.m_bSteam ? 0 : 1,
+ gs.m_bCyberCafe ? 1 : 0,
+ gs.m_nHL2ChaptureUnlocked,
+ gs.m_nDXLevel,
+ gs.m_Summary.m_nDeaths );
+ }
+
+ int retcode = sql->Execute( q );
+ if ( retcode != 0 )
+ {
+ printf( "Query %s failed\n", q );
+ return;
+ }
+}
+CUtlDict< int, unsigned short > g_mapOrder;
+
+void BuildMapList( void )
+{
+ void *buffer = NULL;
+ char *pFileList;
+ FILE * pFile;
+ pFile = fopen ("maplist.txt", "r");
+ int i = 0;
+
+ if ( pFile )
+ {
+ long lSize;
+ // obtain file size.
+ fseek (pFile , 0 , SEEK_END);
+ lSize = ftell (pFile);
+ rewind (pFile);
+
+ // allocate memory to contain the whole file.
+ buffer = (char*) malloc (lSize);
+ if ( buffer != NULL )
+ {
+ // copy the file into the buffer.
+ fread (buffer,1,lSize,pFile);
+ pFileList = (char*)buffer;
+ char szToken[1024];
+
+ while ( 1 )
+ {
+ pFileList = ParseFile( pFileList, szToken, false );
+
+ if ( pFileList == NULL )
+ break;
+
+ g_mapOrder.Insert( szToken, i );
+ i++;
+ }
+ }
+
+ fclose( pFile );
+ free( buffer );
+ }
+ else
+ {
+ Msg( "Couldn't load maplist.txt for mod!!!\n" );
+ }
+}
+
+int Default_ParseCustomGameStatsData( ParseContext_t *ctx )
+{
+ FILE *fp = fopen( ctx->file, "rb" );
+ if ( fp )
+ {
+ CUtlBuffer statsBuffer;
+
+ struct _stat sb;
+ _stat( ctx->file, &sb );
+
+ 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;
+
+ 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 )) );
+
+ BasicGameStats_t stats;
+ valid = stats.ParseFromBuffer( statsBuffer, iCurrentStatsFileVersion );
+
+ if ( describeonly )
+ {
+ DescribeData( stats, szCurrentStatsFileUserID, iCurrentStatsFileVersion );
+ }
+ else
+ {
+ if ( valid )
+ {
+ InsertData( g_mapOrder, ctx->mysql, stats, szCurrentStatsFileUserID, iCurrentStatsFileVersion, ctx->gamename );
+ }
+ else
+ {
+ ++ctx->skipcount;
+ }
+ }
+ }
+
+ //check for custom data
+ bool bHasCustomData = (valid && (statsBuffer.TellPut() != statsBuffer.TellGet()));
+
+ if( bHasCustomData )
+ {
+ if( describeonly )
+ {
+ //separate out the custom data and store it off for processing by other applications,
+ //since they only wanted to 'describe' the data, just use a local temp and overwrite it each time
+
+ const char *szCustomDataOutputFileName = "customdata_temp.dat";
+
+ Msg( "\n\nFound custom data, dumping to %s\n", szCustomDataOutputFileName );
+
+ FILE *pCustomDataOutput = fopen( szCustomDataOutputFileName, "wb+" );
+ if( pCustomDataOutput )
+ {
+ int iGetPosition = statsBuffer.TellGet();
+ fwrite( (((unsigned char *)statsBuffer.Base()) + iGetPosition), statsBuffer.TellPut() - iGetPosition, 1, pCustomDataOutput );
+ fclose( pCustomDataOutput );
+ }
+ }
+ else
+ {
+ //separate out the custom data and store it off for processing by other applications,
+ //assume we will have multiple input stats files from the same user, so store custom data under their userid name and overwrite old data to avoid bloat
+ if( ctx->bCustomDirectoryNotMade )
+ {
+ CreateDirectory( "customdatadumps", NULL );
+ ctx->bCustomDirectoryNotMade = false;
+ }
+
+ char szCustomDataOutputFileName[256];
+ Q_snprintf( szCustomDataOutputFileName, sizeof( szCustomDataOutputFileName ), "customdatadumps/%s.dat", szCurrentStatsFileUserID );
+
+ FILE *pCustomDataOutput = fopen( szCustomDataOutputFileName, "wb+" );
+ if( pCustomDataOutput )
+ {
+ int iGetPosition = statsBuffer.TellGet();
+ fwrite( (((unsigned char *)statsBuffer.Base()) + iGetPosition), statsBuffer.TellPut() - iGetPosition, 1, pCustomDataOutput );
+ fclose( pCustomDataOutput );
+ }
+ }
+ }
+ }
+ return CUSTOMDATA_SUCCESS;
+}
+
+int main(int argc, char* argv[])
+{
+ CommandLine()->CreateCmdLine( argc, argv );
+
+ ParseContext_t ctx;
+
+ if ( argc < 7 && argc != 3 )
+ {
+ printusage();
+ }
+
+ describeonly = argc == 3;
+
+ int gameArg = 1;
+ int hostArg = 2;
+ int usernameArg = 3;
+ int pwArg = 4;
+ int dbArg = 5;
+ int dirArg = 6;
+ if ( describeonly )
+ {
+ dirArg = 2;
+ }
+
+ InitDefaultFileSystem();
+
+ BuildMapList();
+ const char *gamename = argv[ gameArg ];
+ DataParseFunc parseFunc = NULL;
+ PostImportFunc postImportFunc = NULL;
+ ParseCurrentUserIDFunc parseUserIDFunc = NULL;
+ for ( int i = 0 ; i < ARRAYSIZE( g_ParseFuncs ); ++i )
+ {
+ if ( !Q_stricmp( g_ParseFuncs[ i ].pchGameName, gamename ) )
+ {
+ parseFunc = g_ParseFuncs[ i ].pfnParseFunc;
+ postImportFunc = g_ParseFuncs[ i ].pfnPostImport;
+ parseUserIDFunc = g_ParseFuncs[ i ].pfnParseUserID;
+ break;
+ }
+ }
+
+ if ( !parseFunc )
+ {
+ printf( "Invalid game name '%s'\n", gamename );
+ printusage();
+ }
+
+ bool batchMode = true;
+
+ CUtlVector< int > files;
+ if ( describeonly || Q_stristr( argv[ dirArg ], ".dat" ) )
+ {
+ char filename[ MAX_PATH ];
+ Q_snprintf( filename, sizeof( filename ), "%s", argv[ dirArg ] );
+ _strlwr( filename );
+ Q_FixSlashes( filename );
+ char *symbol = strdup( filename );
+ int sym = g_Analysis.symbols.Insert( symbol );
+ files.AddToTail( sym );
+
+ batchMode = false;
+ }
+ else
+ {
+ Msg( "Building file list\n" );
+ BuildFileList( files, argv[ dirArg ], "dat" );
+ }
+
+ if ( !files.Count() )
+ {
+ printf( "No files to operate upon\n" );
+ exit( -1 );
+ }
+
+ int c = files.Count();
+
+ // Cull list of files by looking for most recent version of user's stats and only keeping around those files
+ if ( parseUserIDFunc )
+ {
+ struct CUserIDFileMapping
+ {
+ CUserIDFileMapping() :
+ filename( UTL_INVAL_SYMBOL ), filemodifiedtime( 0 ), modcount( 1 )
+ {
+ userid[ 0 ] = 0;
+ }
+
+ char userid[ 17 ];
+ CUtlSymbol filename;
+ time_t filemodifiedtime;
+ int modcount;
+
+ static bool Less( const CUserIDFileMapping &lhs, const CUserIDFileMapping &rhs )
+ {
+ return Q_stricmp( lhs.userid, rhs.userid ) < 0;
+ }
+ };
+
+
+ CUtlRBTree< CUserIDFileMapping, int > userIDToFileMap( 0, 0, CUserIDFileMapping::Less );
+
+ int nDiscards = 0;
+ int nSkips =0;
+ int nMaxMod = 1;
+ for ( int i = 0; i < c; ++i )
+ {
+ char const *fn = g_Analysis.symbols.Element( files[ i ] );
+
+ CUserIDFileMapping search;
+ search.filename = files[ i ];
+ if ( (*parseUserIDFunc)( fn, search.userid, sizeof( search.userid ), search.filemodifiedtime ) )
+ {
+ // Find map index
+ int idx = userIDToFileMap.Find( search );
+ if ( idx == userIDToFileMap.InvalidIndex() )
+ {
+ userIDToFileMap.Insert( search );
+ }
+ else
+ {
+ CUserIDFileMapping &update = userIDToFileMap[ idx ];
+ if ( search.filemodifiedtime > update.filemodifiedtime )
+ {
+ update.filename = files[ i ];
+ update.filemodifiedtime = search.filemodifiedtime;
+ update.modcount++;
+ if ( update.modcount > nMaxMod )
+ {
+ nMaxMod = update.modcount;
+ }
+ }
+ ++nDiscards;
+ }
+ }
+ else
+ {
+ ++nSkips;
+ }
+
+ if ( i > 0 && !( i % 100 ) )
+ {
+ printf( "Parsing user ID's: [%-6.6d/%-6.6d] %.2f %% complete\n", i, c, 100.0f * (float)i/(float)c );
+ }
+ }
+
+ Msg( "discarded %d of %d, remainder %d [%d skipped] max mod %d\n", nDiscards, c, userIDToFileMap.Count(), nSkips, nMaxMod );
+
+ // Now re-write files and count with pared down listing
+ files.Purge();
+ for( int i = userIDToFileMap.FirstInorder(); i != userIDToFileMap.InvalidIndex(); i = userIDToFileMap.NextInorder( i ) )
+ {
+ files.AddToTail( userIDToFileMap[ i ].filename );
+ }
+ c = files.Count();
+ }
+
+ bool bTrySql = !describeonly;
+
+ bool bSqlOkay = false;
+
+ CSysModule *sql = NULL;
+ CreateInterfaceFn factory = NULL;
+ IMySQL *mysql = NULL;
+
+ if ( bTrySql )
+ {
+ // Now connect to steamweb and update the engineaccess table
+ sql = Sys_LoadModule( "mysql_wrapper" );
+ if ( sql )
+ {
+ factory = Sys_GetFactory( sql );
+ if ( factory )
+ {
+ mysql = ( IMySQL * )factory( MYSQL_WRAPPER_VERSION_NAME, NULL );
+ if ( mysql )
+ {
+ if ( mysql->InitMySQL( argv[ dbArg ], argv[ hostArg ], argv[ usernameArg ], argv[ pwArg ] ) )
+ {
+ bSqlOkay = true;
+ if ( batchMode )
+ {
+ Msg( "Successfully connected to database %s on host %s, user %s\n",
+ argv[ dbArg ], argv[ hostArg ], argv[ usernameArg ] );
+ }
+ }
+ else
+ {
+ Msg( "mysql->InitMySQL( %s, %s, %s, [password]) failed\n",
+ argv[ dbArg ], argv[ hostArg ], argv[ usernameArg ] );
+ }
+ }
+ else
+ {
+ Msg( "Unable to get MYSQL_WRAPPER_VERSION_NAME(%s) from mysql_wrapper\n", MYSQL_WRAPPER_VERSION_NAME );
+ }
+ }
+ else
+ {
+ Msg( "Sys_GetFactory on mysql_wrapper failed\n" );
+ }
+ }
+ else
+ {
+ Msg( "Sys_LoadModule( mysql_wrapper ) failed\n" );
+ }
+ }
+
+ ctx.gamename = gamename;
+ ctx.describeonly = describeonly;
+ ctx.mysql = mysql;
+ ctx.skipcount = 0;
+ ctx.bCustomDirectoryNotMade = true;
+
+ if ( bSqlOkay || describeonly )
+ {
+
+ for ( int i = 0; i < c; ++i )
+ {
+ char const *fn = g_Analysis.symbols.Element( files[ i ] );
+
+ ctx.file = fn;
+
+ int iCustomData = (*parseFunc)( &ctx );
+ if ( iCustomData == CUSTOMDATA_SUCCESS )
+ {
+ if ( i > 0 && !( i % 100 ) )
+ {
+ printf( "Processing: [%-6.6d/%-6.6d] %.2f %% complete\n", i, c, 100.0f * (float)i/(float)c );
+ }
+ }
+ }
+
+ if ( ctx.skipcount > 0 )
+ {
+ printf( "Skipped %d samples which appear to be malformed or contain bogus data\n", ctx.skipcount );
+ }
+
+ // if this game has a post-import function to call after all the files have been imported, call it now
+ if ( bSqlOkay && postImportFunc )
+ {
+ postImportFunc( mysql );
+ }
+ }
+
+ if ( bSqlOkay )
+ {
+ if ( mysql )
+ {
+ mysql->Release();
+ mysql = NULL;
+ }
+
+ if ( sql )
+ {
+ Sys_UnloadModule( sql );
+ sql = NULL;
+ }
+ }
+
+ return 0;
+}
+
+
+static void OverWriteCharsWeHate( char *pStr )
+{
+ while( *pStr )
+ {
+ switch( *pStr )
+ {
+ case '\n':
+ case '\r':
+ case '\\':
+ case '\"':
+ case '\'':
+ case '\032':
+ case ';':
+ *pStr = ' ';
+ }
+ pStr++;
+ }
+}
+
+void InsertKeyDataIntoTable( IMySQL *pSQL, time_t fileTime, char const *pTableName, char const *pPerfData, char const *pKeyWhiteList[], int nNumFields )
+{
+ char szDate[128]="Now()";
+ if ( fileTime > 0 )
+ {
+ tm * pTm = localtime( &fileTime );
+ Q_snprintf( szDate, ARRAYSIZE( szDate ), "'%04d-%02d-%02d %02d:%02d:%02d'",
+ pTm->tm_year + 1900, pTm->tm_mon + 1, pTm->tm_mday, pTm->tm_hour, pTm->tm_min, pTm->tm_sec );
+ }
+
+ // we don't need to worry about semicolons embedded in string fields because we supressed them
+ // on the client. if some malicious person inserts them, the mandled field names will fail the
+ // whitelist check, causing the record to be ignored.
+ CUtlVector<char *> tokens;
+ // split into tokens at non-quoated spaces or ;'s
+ for(;;)
+ {
+ char const *pStr = pPerfData;
+ if ( pStr[0] == 0 )
+ break;
+ while( pStr[0] && ( pStr[0] != ' ' ) && ( pStr[0] != ';' ) )
+ {
+ if ( pStr[0]=='"')
+ {
+ // skip to end quote
+ char const *pEq = strchr( pStr + 1, '\"' );
+ if ( ! pEq )
+ {
+ printf(" close quote with no open quote\n" );
+ return;
+ }
+ pStr = pEq;
+ }
+ pStr++;
+ }
+ // got a field
+ int nlen = pStr - pPerfData;
+ if ( nlen > 2 )
+ {
+ char *pToken = new char[ nlen + 1 ];
+ memcpy( pToken, pPerfData, nlen );
+ pToken[nlen] = 0;
+ tokens.AddToTail( pToken );
+ }
+ if ( pStr[0] )
+ pStr++;
+ pPerfData = pStr;
+ }
+
+ bool bBadData = false;
+ char fieldNameBuffer[1024];
+ char fieldValueBuffer[2048];
+ strcpy( fieldNameBuffer, "(CreationTimeStamp, " );
+ Q_snprintf( fieldValueBuffer, ARRAYSIZE( fieldValueBuffer), "( %s,", szDate );
+ for( int i = 0; i < tokens.Count(); i++ )
+ {
+ char *pKVData = tokens[i];
+ char *pEqualsSign = strchr( pKVData, '=' );
+ if (! pEqualsSign )
+ {
+ bBadData = true;
+ break;
+ }
+ *pEqualsSign = 0; // *semicolon->null
+ // check that the field is in the white list
+ bool bFoundIt = false;
+ for( int nCheck = 0; nCheck < nNumFields; nCheck++ )
+ if ( strcmp( pKVData, pKeyWhiteList[nCheck] ) == 0 )
+ {
+ bFoundIt = true;
+ break;
+ }
+ V_strncat( fieldNameBuffer, pKVData, sizeof( fieldNameBuffer ) );
+ if ( i != tokens.Count() -1 )
+ V_strncat( fieldNameBuffer, ",", sizeof( fieldNameBuffer ) );
+ else
+ V_strncat( fieldNameBuffer, ")", sizeof( fieldNameBuffer ) );
+ char *pValue = pEqualsSign + 1;
+ OverWriteCharsWeHate( pValue );
+ if ( ( strlen( pValue ) < 1 ) || (! bFoundIt ) )
+ {
+ bBadData = true;
+ break;
+ }
+ // kill lead + trail space
+ if ( pValue[0] == ' ' )
+ pValue++;
+ if ( pValue[strlen(pValue) - 1 ] == ' ' )
+ pValue[strlen( pValue ) - 1 ] =0;
+ V_strncat( fieldValueBuffer, "'", sizeof( fieldValueBuffer ) );
+ V_strncat( fieldValueBuffer, pValue, sizeof( fieldValueBuffer ) );
+ if ( i != tokens.Count() -1 )
+ V_strncat( fieldValueBuffer, "',", sizeof( fieldValueBuffer ) );
+ else
+ V_strncat( fieldValueBuffer, "')", sizeof( fieldValueBuffer ) );
+ }
+ if (! bBadData )
+ {
+ char sqlCommandBuffer[1024 + sizeof( fieldNameBuffer ) + sizeof( fieldValueBuffer ) ];
+ sprintf( sqlCommandBuffer, "insert into %s %s values %s;", pTableName, fieldNameBuffer, fieldValueBuffer );
+// printf("cmd %s\n", sqlCommandBuffer);
+ int retcode = pSQL->Execute( sqlCommandBuffer );
+ if ( retcode != 0 )
+ {
+ printf( "command %s failed\n", sqlCommandBuffer );
+ }
+ }
+
+ tokens.PurgeAndDeleteElements();
+}
+
+char const *s_PerfKeyList[] = {
+ "AvgFps",
+ "MinFps",
+ "MaxFps",
+ "CPUID",
+ "CPUGhz",
+ "NumCores",
+ "GPUDrv",
+ "GPUVendor",
+ "GPUDeviceID",
+ "GPUDriverVersion",
+ "DxLvl",
+ "Width",
+ "Height",
+ "MapName",
+ "TotalLevelTime",
+ "NumLevels"
+};
+
+void ProcessPerfData( IMySQL *pSQL, time_t fileTime, char const *pTableName, char const *pPerfData )
+{
+ InsertKeyDataIntoTable( pSQL, fileTime, pTableName, pPerfData, s_PerfKeyList, ARRAYSIZE( s_PerfKeyList) );
+}
+
diff --git a/devtools/processgamestats/processgamestats.vpc b/devtools/processgamestats/processgamestats.vpc
new file mode 100644
index 0000000..8b8d184
--- /dev/null
+++ b/devtools/processgamestats/processgamestats.vpc
@@ -0,0 +1,78 @@
+//-----------------------------------------------------------------------------
+// PROCESSGAMESTATS.VPC
+//
+// Project Script
+//-----------------------------------------------------------------------------
+
+$Macro SRCDIR "..\.."
+$Macro OUTBINDIR "$SRCDIR\..\game\bin"
+
+$Include "$SRCDIR\vpc_scripts\source_exe_con_base.vpc"
+
+$Configuration
+{
+ $Compiler
+ {
+ $AdditionalIncludeDirectories "$BASE;$SRCDIR\utils\vmpi;$SRCDIR\game\shared;$SRCDIR\game\server;$SRCDIR\game\shared\tf;$SRCDIR\game\shared\econ;$SRCDIR\gcsdk\steamextra"
+ $EnableC++Exceptions "Yes (/EHsc)"
+ $PreprocessorDefinitions "$BASE;GAME_DLL;TF_DLL;NO_STEAM"
+ }
+}
+
+$Project "Processgamestats"
+{
+ $Folder "Source Files"
+ {
+ $File "$SRCDIR\public\filesystem_helpers.cpp"
+ $File "$SRCDIR\public\filesystem_helpers.h"
+ $File "$SRCDIR\game\server\GameStats_BasicStatsFunctions.cpp"
+ $File "processgamestats.cpp"
+
+ $Folder "Custom Stats"
+ {
+ $File "base_gamestats_parse.h"
+ $File "cs_gamestats.h"
+ $File "cstrike_gamestats_parse.cpp"
+ $File "$SRCDIR\game\server\episodic\ep2_gamestats.h"
+ $File "ep2_gamestats_parse.cpp"
+ $File "$SRCDIR\game\server\tf\tf_gamestats.h"
+ $File "tf_gamestats_parse.cpp"
+ $File "$SRCDIR\game\shared\tf\tf_gamestats_shared.cpp"
+ $File "$SRCDIR\game\shared\tf\tf_gamestats_shared.h"
+ }
+ }
+
+ $Folder "Header Files"
+ {
+ $File "$SRCDIR\public\tier0\basetypes.h"
+ $File "$SRCDIR\public\tier0\commonmacros.h"
+ $File "$SRCDIR\public\tier0\dbg.h"
+ $File "$SRCDIR\public\tier0\fasttimer.h"
+ $File "$SRCDIR\game\shared\gamestats.h"
+ $File "$SRCDIR\public\tier0\icommandline.h"
+ $File "$SRCDIR\utils\vmpi\imysqlwrapper.h"
+ $File "$SRCDIR\public\tier0\memdbgoff.h"
+ $File "$SRCDIR\public\tier0\memdbgon.h"
+ $File "$SRCDIR\public\tier0\platform.h"
+ $File "$SRCDIR\public\tier0\protected_things.h"
+ $File "$SRCDIR\public\string_t.h"
+ $File "$SRCDIR\public\tier1\strtools.h"
+ $File "$SRCDIR\public\tier1\utlmemory.h"
+ $File "$SRCDIR\public\tier1\utlvector.h"
+ $File "$SRCDIR\public\vstdlib\vstdlib.h"
+ }
+
+ $Folder "Database Setup"
+ {
+ $File "cstrike_gamestats.db"
+ $File "portal_gamestats.db"
+ $File "ep1_gamestats.db"
+ $File "ep2_gamestats.db"
+ $File "tf_gamestats.db"
+ }
+
+ $Folder "Link Libraries"
+ {
+ $Lib tier2
+ }
+}
diff --git a/devtools/processgamestats/tf_gamestats.db b/devtools/processgamestats/tf_gamestats.db
new file mode 100644
index 0000000..7f62b7c
--- /dev/null
+++ b/devtools/processgamestats/tf_gamestats.db
@@ -0,0 +1,227 @@
+create database if not exists gamestats_tf;
+use gamestats_tf;
+
+create table if not exists tf_mapdata
+(
+ MapName VARCHAR(64)
+);
+alter table tf_mapdata add INDEX(MapName);
+alter table tf_mapdata add column ServerID VARCHAR(16);
+alter table tf_mapdata add column TimeSubmitted DATETIME;
+alter table tf_mapdata add INDEX(TimeSubmitted);
+alter table tf_mapdata add column RoundsPlayed BIGINT;
+alter table tf_mapdata add column TotalTime BIGINT;
+alter table tf_mapdata add column BlueWins BIGINT;
+alter table tf_mapdata add column RedWins BIGINT;
+alter table tf_mapdata add column Stalemates BIGINT;
+
+
+create table if not exists tf_mapdata_rollup
+(
+ MapName VARCHAR(64)
+);
+alter table tf_mapdata_rollup add INDEX(MapName);
+alter table tf_mapdata_rollup add column TimeSubmitted DATETIME;
+alter table tf_mapdata_rollup add INDEX(TimeSubmitted);
+alter table tf_mapdata_rollup add column RoundsPlayed BIGINT;
+alter table tf_mapdata_rollup add column TotalTime BIGINT;
+alter table tf_mapdata_rollup add column BlueWins BIGINT;
+alter table tf_mapdata_rollup add column RedWins BIGINT;
+alter table tf_mapdata_rollup add column Stalemates BIGINT;
+
+
+create table if not exists tf_classdata
+(
+ MapName VARCHAR(64)
+);
+alter table tf_classdata add INDEX(MapName);
+alter table tf_classdata add column ServerID VARCHAR(16);
+alter table tf_classdata add column TimeSubmitted DATETIME;
+alter table tf_classdata add INDEX(TimeSubmitted);
+alter table tf_classdata add column Class TINYINT;
+alter table tf_classdata add column Spawns BIGINT;
+alter table tf_classdata add column TotalTime BIGINT;
+alter table tf_classdata add column Score BIGINT;
+alter table tf_classdata add column Kills BIGINT;
+alter table tf_classdata add column Deaths BIGINT;
+alter table tf_classdata add column Assists BIGINT;
+alter table tf_classdata add column Captures BIGINT;
+
+create table if not exists tf_classdata_rollup
+(
+ MapName VARCHAR(64)
+);
+alter table tf_classdata_rollup add INDEX(MapName);
+alter table tf_classdata_rollup add column TimeSubmitted DATETIME;
+alter table tf_classdata_rollup add INDEX(TimeSubmitted);
+alter table tf_classdata_rollup add column Class TINYINT;
+alter table tf_classdata_rollup add column Spawns BIGINT;
+alter table tf_classdata_rollup add column TotalTime BIGINT;
+alter table tf_classdata_rollup add column Score BIGINT;
+alter table tf_classdata_rollup add column Kills BIGINT;
+alter table tf_classdata_rollup add column Deaths BIGINT;
+alter table tf_classdata_rollup add column Assists BIGINT;
+alter table tf_classdata_rollup add column Captures BIGINT;
+
+create table if not exists tf_weapondata
+(
+ WeaponID TINYINT
+);
+alter table tf_weapondata add INDEX(WeaponID);
+alter table tf_weapondata add column ServerID VARCHAR(16);
+alter table tf_weapondata add column MapName VARCHAR(64);
+alter table tf_weapondata add INDEX(MapName);
+alter table tf_weapondata add column TimeSubmitted DATETIME;
+alter table tf_weapondata add INDEX(TimeSubmitted);
+alter table tf_weapondata add column ShotsFired BIGINT;
+alter table tf_weapondata add column ShotsFiredCrit BIGINT;
+alter table tf_weapondata add column ShotsHit BIGINT;
+alter table tf_weapondata add column DamageTotal BIGINT;
+alter table tf_weapondata add column HitsWithKnownDistance BIGINT;
+alter table tf_weapondata add column DistanceTotal BIGINT;
+
+create table if not exists tf_weapondata_rollup
+(
+ WeaponID TINYINT
+);
+alter table tf_weapondata_rollup add INDEX(WeaponID);
+alter table tf_weapondata_rollup add column MapName VARCHAR(64);
+alter table tf_weapondata_rollup add INDEX(MapName);
+alter table tf_weapondata_rollup add column TimeSubmitted DATETIME;
+alter table tf_weapondata_rollup add INDEX(TimeSubmitted);
+alter table tf_weapondata_rollup add column ShotsFired BIGINT;
+alter table tf_weapondata_rollup add column ShotsFiredCrit BIGINT;
+alter table tf_weapondata_rollup add column ShotsHit BIGINT;
+alter table tf_weapondata_rollup add column DamageTotal BIGINT;
+alter table tf_weapondata_rollup add column HitsWithKnownDistance BIGINT;
+alter table tf_weapondata_rollup add column DistanceTotal BIGINT;
+
+create table if not exists tf_deaths
+(
+ UserID CHAR(16),
+ Tag CHAR(8),
+ KEY( Tag ),
+ MapName CHAR(20),
+ MapVersion INT,
+ KEY( MapVersion ),
+ LastUpdate DATETIME,
+ KEY( LastUpdate ),
+ DeathIndex INT,
+ X SMALLINT,
+ Y SMALLINT,
+ Z SMALLINT,
+ PRIMARY KEY ( UserID, Tag, MapName, X, Y, Z )
+) TYPE=MyISAM;
+
+
+create table if not exists tf_perfdata
+(
+ CreationTimeStamp DATETIME,
+ AvgFps FLOAT,
+ MinFps FLOAT,
+ MaxFps FLOAT,
+ CPUID VARCHAR(64),
+ CPUGhz FLOAT,
+ NumCores INT,
+ GPUDrv VARCHAR(64),
+ GPUVendor INT,
+ GPUDeviceID INT,
+ GPUDriverVersion VARCHAR(25),
+ DxLvl INT,
+ Width INT,
+ Height INT,
+ MapName VARCHAR(64),
+ TotalLevelTime INT,
+ NumLevels SMALLINT,
+ INDEX(CreationTimeStamp),
+ INDEX(AvgFps),
+ INDEX(GPUDrv),
+ INDEX(Width),
+ INDEX(MapName)
+);
+
+create table if not exists tf_perfdata_rollup_fps
+(
+ CreationTimeStamp DATETIME,
+ NumEntries BIGINT,
+ AvgFps FLOAT,
+ INDEX(CreationTimeStamp),
+);
+
+create table if not exists tf_perfdata_rollup_fpsdist
+(
+ CreationTimeStamp DATETIME,
+ NumEntries BIGINT,
+ BaseFps FLOAT,
+ GPUDrv VARCHAR(64),
+ INDEX(CreationTimeStamp),
+ INDEX(GPUDrv)
+);
+
+create table if not exists tf_perfdata_rollup_fpsmap
+(
+ CreationTimeStamp DATETIME,
+ NumEntries BIGINT,
+ AvgFps FLOAT,
+ MapName VARCHAR(64),
+ INDEX(CreationTimeStamp),
+ INDEX(MapName),
+);
+
+create table if not exists tf_perfdata_rollup_fpscard
+(
+ CreationTimeStamp DATETIME,
+ NumEntries BIGINT,
+ AvgFps FLOAT,
+ GPUDrv VARCHAR(64),
+ INDEX(CreationTimeStamp),
+ INDEX(GPUDrv),
+);
+
+create table if not exists tf_perfdata_rollup_fpswidth
+(
+ CreationTimeStamp DATETIME,
+ NumEntries BIGINT,
+ AvgFps FLOAT,
+ Width INT,
+ INDEX(CreationTimeStamp),
+);
+
+create table if not exists tf_perfdata_rollup_fpscardwidth
+(
+ CreationTimeStamp DATETIME,
+ NumEntries BIGINT,
+ AvgFps FLOAT,
+ Width INT,
+ GPUDrv VARCHAR(64),
+ INDEX(CreationTimeStamp),
+ INDEX(GPUDrv),
+);
+
+create table if not exists tf_perfdata_rollup_cpudist
+(
+ CreationTimeStamp DATETIME,
+ NumEntries BIGINT,
+ AvgFps FLOAT,
+ CPUID VARCHAR(64),
+ NumCores INT,
+ BaseCPUGhz FLOAT,
+ GPUVendor INT,
+ INDEX(CreationTimeStamp),
+);
+
+create table if not exists tf_perfdata_rollup_playtime
+(
+ CreationTimeStamp DATETIME,
+ AvgSession FLOAT,
+ AvgLevels FLOAT,
+ INDEX(CreationTimeStamp),
+);
+
+create table if not exists tf_perfdata_rollup_playtimedist
+(
+ CreationTimeStamp DATETIME,
+ BaseSession FLOAT,
+ NumEntries INT,
+ INDEX(CreationTimeStamp),
+);
diff --git a/devtools/processgamestats/tf_gamestats_parse.cpp b/devtools/processgamestats/tf_gamestats_parse.cpp
new file mode 100644
index 0000000..b8dd242
--- /dev/null
+++ b/devtools/processgamestats/tf_gamestats_parse.cpp
@@ -0,0 +1,480 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//
+//=============================================================================//
+//
+//
+
+#include "stdafx.h"
+#include <stdio.h>
+#include <process.h>
+#include <string.h>
+#include <windows.h>
+#include <sys/stat.h>
+#include <time.h>
+#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"
+class CBaseObject;
+#include "tf/tf_gamestats.h"
+#include "base_gamestats_parse.h"
+#include "tier0/icommandline.h"
+
+#include <string>
+
+static TFReportedStats_t g_reportedStats;
+
+extern void v_escape_string (std::string& s);
+
+//-----------------------------------------------------------------------------
+// Weapons.
+//-----------------------------------------------------------------------------
+static const char *s_aWeaponNames[] =
+{
+ "NONE",
+ "BAT",
+ "BOTTLE",
+ "FIREAXE",
+ "CLUB",
+ "CROWBAR",
+ "KNIFE",
+ "MEDIKIT",
+ "PIPE",
+ "SHOVEL",
+ "WRENCH",
+ "BONESAW",
+ "SHOTGUN_PRIMARY",
+ "SHOTGUN_SECONDARY",
+ "SNIPERRIFLE",
+ "MINIGUN",
+ "SMG",
+ "SYRINGEGUN_MEDIC",
+ "TRANQ",
+ "ROCKETLAUNCHER",
+ "GRENADELAUNCHER",
+ "PIPEBOMBLAUNCHER",
+ "FLAMETHROWER",
+ "GRENADE_NORMAL",
+ "GRENADE_CONCUSSION",
+ "GRENADE_NAIL",
+ "GRENADE_MIRV",
+ "GRENADE_MIRV_DEMOMAN",
+ "GRENADE_NAPALM",
+ "GRENADE_GAS",
+ "GRENADE_EMP",
+ "GRENADE_CALTROP",
+ "GRENADE_PIPEBOMB",
+ "GRENADE_SMOKE_BOMB",
+ "GRENADE_HEAL",
+ "PISTOL",
+ "REVOLVER",
+ "NAILGUN",
+ "PDA",
+ "PDA_DEMOMAN",
+ "PDA_ENGINEER",
+ "PDA_SPY",
+ "BUILDER",
+ "MEDIGUN",
+ "FLAG",
+ "GRENADE_MIRVBOMB",
+ "FLAMETHROWER_ROCKET",
+ "GRENADE_DEMOMAN",
+ "SENTRY_BULLET",
+ "SENTRY_ROCKET",
+ "DISPENSER",
+ "INVIS",
+};
+
+//-----------------------------------------------------------------------------
+// Classes
+//-----------------------------------------------------------------------------
+static const char *s_aClassNames[] =
+{
+ "UNDEFINED",
+ "SCOUT",
+ "SNIPER",
+ "SOLDIER",
+ "DEMOMAN",
+ "MEDIC",
+ "HEAVYWEAPONS",
+ "PYRO",
+ "SPY",
+ "ENGINEER",
+ "CIVILIAN",
+};
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+static bool LoadCustomDataFromBuffer( CUtlBuffer &LoadBuffer )
+{
+ g_reportedStats.Clear();
+ return g_reportedStats.LoadCustomDataFromBuffer( LoadBuffer );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+static const char *ClassIdToAlias( int iClass )
+{
+ if ( ( iClass >= ARRAYSIZE( s_aClassNames ) ) || ( iClass < 0 ) )
+ return NULL;
+
+ return s_aClassNames[iClass];
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+const char *WeaponIdToAlias( int iWeapon )
+{
+ if ( ( iWeapon >= ARRAYSIZE( s_aWeaponNames ) ) || ( iWeapon < 0 ) )
+ return NULL;
+
+ return s_aWeaponNames[iWeapon];
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void DescribeTF2Stats()
+{
+#if 0 // not up to date w/latest stats code.
+ int iMap;
+ for ( iMap = g_dictMapStats.First(); iMap != g_dictMapStats.InvalidIndex(); iMap = g_dictMapStats.Next( iMap ) )
+ {
+ // Get the current map.
+ TF_Gamestats_LevelStats_t *pCurrentMap = &g_dictMapStats[iMap];
+
+ Msg( " --- %s ------\n %d deaths\n %.2f seconds total playtime\n", pCurrentMap->m_Header.m_szMapName,
+ pCurrentMap->m_aPlayerDeaths.Count(), pCurrentMap->m_Header.m_flTime );
+ for( int i = 0; i < pCurrentMap->m_aPlayerDeaths.Count(); i++ )
+ {
+ Msg( " %s killed %s with %s at (%d,%d,%d), distance %d\n",
+ ClassIdToAlias( pCurrentMap->m_aPlayerDeaths[ i ].iAttackClass ),
+ ClassIdToAlias( pCurrentMap->m_aPlayerDeaths[ i ].iTargetClass ),
+ WeaponIdToAlias( pCurrentMap->m_aPlayerDeaths[ i ].iWeapon ),
+ pCurrentMap->m_aPlayerDeaths[ i ].nPosition[ 0 ],
+ pCurrentMap->m_aPlayerDeaths[ i ].nPosition[ 1 ],
+ pCurrentMap->m_aPlayerDeaths[ i ].nPosition[ 2 ],
+ pCurrentMap->m_aPlayerDeaths[ i ].iDistance );
+ }
+ Msg( "\n---------------------------------\n\n %d damage records\n", pCurrentMap->m_aPlayerDamage.Count() );
+
+ for( int i = 0; i < pCurrentMap->m_aPlayerDamage.Count(); i++ )
+ {
+ Msg( " %.2f : %s at (%d,%d,%d) caused %d damage to %s with %s at (%d,%d,%d)%s%s\n",
+ pCurrentMap->m_aPlayerDamage[ i ].fTime,
+ ClassIdToAlias( pCurrentMap->m_aPlayerDamage[ i ].iAttackClass ),
+ pCurrentMap->m_aPlayerDamage[ i ].nAttackerPosition[ 0 ],
+ pCurrentMap->m_aPlayerDamage[ i ].nAttackerPosition[ 1 ],
+ pCurrentMap->m_aPlayerDamage[ i ].nAttackerPosition[ 2 ],
+ pCurrentMap->m_aPlayerDamage[ i ].iDamage,
+ ClassIdToAlias( pCurrentMap->m_aPlayerDamage[ i ].iTargetClass ),
+ WeaponIdToAlias( pCurrentMap->m_aPlayerDamage[ i ].iWeapon ),
+ pCurrentMap->m_aPlayerDamage[ i ].nTargetPosition[ 0 ],
+ pCurrentMap->m_aPlayerDamage[ i ].nTargetPosition[ 1 ],
+ pCurrentMap->m_aPlayerDamage[ i ].nTargetPosition[ 2 ],
+ pCurrentMap->m_aPlayerDamage[ i ].iCrit ? ", CRIT!" : "",
+ pCurrentMap->m_aPlayerDamage[ i ].iKill ? ", KILL" : "" );
+ }
+
+ Msg( "\n" );
+ }
+#endif
+}
+extern CUtlDict< int, unsigned short > g_mapOrder;
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void InsertTF2Data( bool bDeathOnly, char const *szStatsFileUserID, time_t fileTime, IMySQL *sql, int iStatsFileVersion )
+{
+ if ( !sql )
+ return;
+
+ std::string userid;
+ userid = szStatsFileUserID;
+ v_escape_string( userid );
+
+
+ char szDate[128]="Now()";
+ if ( fileTime > 0 )
+ {
+ tm * pTm = localtime( &fileTime );
+ Q_snprintf( szDate, ARRAYSIZE( szDate ), "'%04d-%02d-%02d %02d:%02d:%02d'",
+ pTm->tm_year + 1900, pTm->tm_mon + 1, pTm->tm_mday, pTm->tm_hour, pTm->tm_min, pTm->tm_sec );
+ }
+
+ char q[4096];
+
+
+ {
+ int iMap;
+ for ( iMap = g_reportedStats.m_dictMapStats.First(); iMap != g_reportedStats.m_dictMapStats.InvalidIndex(); iMap = g_reportedStats.m_dictMapStats.Next( iMap ) )
+ {
+ // Get the current map.
+ TF_Gamestats_LevelStats_t *pCurrentMap = &g_reportedStats.m_dictMapStats[iMap];
+
+#if 1
+ int slot = g_mapOrder.Find( pCurrentMap->m_Header.m_szMapName );
+ if ( slot == g_mapOrder.InvalidIndex() )
+ {
+ if ( Q_stricmp( pCurrentMap->m_Header.m_szMapName, "devtest" ) )
+ continue;
+ }
+#endif
+
+ std::string mapname;
+ mapname = pCurrentMap->m_Header.m_szMapName;
+ v_escape_string( mapname );
+
+ std::string tag;
+ tag = ""; // pCurrentMap->m_Tag.m_szTagText;
+ v_escape_string( tag );
+
+ int mapversion = 0;
+
+
+
+ /*
+ for( int i = 0; i < pCurrentMap->m_aPlayerDeaths.Count(); i++ )
+ {
+ Msg( " %s killed %s with %s at (%d,%d,%d), distance %d\n",
+ ClassIdToAlias( pCurrentMap->m_aPlayerDeaths[ i ].iAttackClass ),
+ ClassIdToAlias( pCurrentMap->m_aPlayerDeaths[ i ].iTargetClass ),
+ WeaponIdToAlias( pCurrentMap->m_aPlayerDeaths[ i ].iWeapon ),
+ pCurrentMap->m_aPlayerDeaths[ i ].nPosition[ 0 ],
+ pCurrentMap->m_aPlayerDeaths[ i ].nPosition[ 1 ],
+ pCurrentMap->m_aPlayerDeaths[ i ].nPosition[ 2 ],
+ pCurrentMap->m_aPlayerDeaths[ i ].iDistance );
+ }
+ */
+ // Deal with deaths
+ for ( int 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);",
+ "tf",
+ 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;
+ }
+ }
+ }
+ if ( bDeathOnly )
+ return;
+ }
+
+ int iMap;
+ for ( iMap = g_reportedStats.m_dictMapStats.First(); iMap != g_reportedStats.m_dictMapStats.InvalidIndex();
+ iMap = g_reportedStats.m_dictMapStats.Next( iMap ) )
+ {
+ // Get the current map.
+ TF_Gamestats_LevelStats_t *pCurrentMap = &g_reportedStats.m_dictMapStats[iMap];
+ std::string mapname = pCurrentMap->m_Header.m_szMapName;
+ v_escape_string( mapname );
+
+ // insert map data into database
+ Q_snprintf( q, sizeof( q ), "INSERT into tf_mapdata (ServerID,TimeSubmitted,MapName,RoundsPlayed,TotalTime,BlueWins,RedWins,Stalemates,BlueSuddenDeathWins,RedSuddenDeathWins) "
+ "values (\"%s\",%s,\"%s\",%d,%d,%d,%d,%d,%d,%d);",
+ szStatsFileUserID,szDate, mapname.c_str(), pCurrentMap->m_Header.m_iRoundsPlayed, pCurrentMap->m_Header.m_iTotalTime, pCurrentMap->m_Header.m_iBlueWins,
+ pCurrentMap->m_Header.m_iRedWins, pCurrentMap->m_Header.m_iStalemates, pCurrentMap->m_Header.m_iBlueSuddenDeathWins, pCurrentMap->m_Header.m_iRedSuddenDeathWins );
+
+// Msg( "%s\n", q );
+
+ int retcode = sql->Execute( q );
+ if ( retcode != 0 )
+ {
+ Msg( "Query %s failed\n", q );
+ return;
+ }
+
+ // insert the class data
+ for ( int i = TF_CLASS_UNDEFINED + 1; i < TF_CLASS_CIVILIAN; i++ )
+ {
+ TF_Gamestats_ClassStats_t &classStats = pCurrentMap->m_aClassStats[i];
+ if ( 0 == classStats.iSpawns ) // skip any classes that have had no spawns
+ continue;
+
+ Q_snprintf( q, sizeof( q ), "INSERT into tf_classdata (ServerID,MapName,TimeSubmitted,Class,Spawns,TotalTime,Score,Kills,Deaths,Assists,Captures) "
+ "values (\"%s\",\"%s\",%s,%d,%d,%d,%d,%d,%d,%d,%d);",
+ szStatsFileUserID, mapname.c_str(), szDate, i, classStats.iSpawns, classStats.iTotalTime, classStats.iScore, classStats.iKills, classStats.iDeaths,
+ classStats.iAssists, classStats.iCaptures );
+
+// Msg( "%s\n", q );
+ int retcode = sql->Execute( q );
+ if ( retcode != 0 )
+ {
+ Msg( "Query %s failed\n", q );
+ return;
+ }
+ }
+
+ // insert the weapon data
+ for ( int i = TF_WEAPON_NONE+1; i < TF_WEAPON_COUNT; i++ )
+ {
+ TF_Gamestats_WeaponStats_t &weaponStats = pCurrentMap->m_aWeaponStats[i];
+
+ // skip any weapons that have no shots fired
+ if ( 0 == weaponStats.iShotsFired )
+ continue;
+
+ Q_snprintf( q, sizeof( q ), "INSERT into tf_weapondata (ServerID, MapName, TimeSubmitted,WeaponID,ShotsFired,ShotsFiredCrit,ShotsHit,DamageTotal,"
+ "HitsWithKnownDistance,DistanceTotal) "
+ "values (\"%s\",\"%s\",%s,%d,%d,%d,%d,%d,%d,%llu)",
+ szStatsFileUserID, mapname.c_str(), szDate, i, weaponStats.iShotsFired, weaponStats.iCritShotsFired, weaponStats.iHits, weaponStats.iTotalDamage,
+ weaponStats.iHitsWithKnownDistance, weaponStats.iTotalDistance );
+
+// Msg( "%s\n", q );
+ int retcode = sql->Execute( q );
+ if ( retcode != 0 )
+ {
+ Msg( "Query %s failed\n", q );
+ return;
+ }
+ }
+ }
+}
+
+bool g_bDeathOnly = false;
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+int TF_ParseCustomGameStatsData( ParseContext_t *ctx )
+{
+ FILE *fp = fopen( ctx->file, "rb" );
+ if ( !fp )
+ return CUSTOMDATA_FAILED;
+
+ CUtlBuffer statsBuffer;
+
+ struct _stat sb;
+ _stat( ctx->file, &sb );
+ 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 );
+
+ if ( memcmp( statsBuffer.Base(), "\"gamestats\"", 11 ) == 0 )
+ {
+ Msg( "Got new-style file format that we don't support, skipping\n" );
+ return CUSTOMDATA_SUCCESS;
+ }
+
+ if ( memcmp( statsBuffer.Base(), "PERFDATA:", 9 ) == 0 )
+ {
+ ProcessPerfData( ctx->mysql, sb.st_mtime, "tf_perfdata", ( ( char * ) statsBuffer.Base() ) + 9 );
+ return CUSTOMDATA_SUCCESS;
+ }
+ 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;
+
+ unsigned int iCheckIfStandardDataSaved = statsBuffer.GetUnsignedInt();
+ if( iCheckIfStandardDataSaved != GAMESTATS_STANDARD_NOT_SAVED )
+ {
+ // we don't care about the standard data, so why is it here?
+ return CUSTOMDATA_FAILED;
+ }
+
+ // check for custom data
+ bool bHasCustomData = (statsBuffer.TellPut() != statsBuffer.TellGet());
+ if( !bHasCustomData )
+ {
+ // where's our data?
+ return CUSTOMDATA_FAILED;
+ }
+
+ if ( !LoadCustomDataFromBuffer( statsBuffer ) )
+ return CUSTOMDATA_FAILED;
+
+ if( ctx->describeonly )
+ {
+ DescribeTF2Stats();
+ }
+ else
+ {
+ if ( CommandLine()->CheckParm( "-deathsonly" ) )
+ {
+ g_bDeathOnly = true;
+ }
+
+ InsertTF2Data( g_bDeathOnly, szCurrentStatsFileUserID, sb.st_mtime, ctx->mysql, iCurrentStatsFileVersion );
+ }
+
+ return CUSTOMDATA_SUCCESS;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Called after all new files have been parsed & imported
+//-----------------------------------------------------------------------------
+void TF_PostImport( IMySQL *sql )
+{
+#if 0 // now handled by PHP script
+ if ( g_bDeathOnly )
+ return;
+ // All the new data files have been imported to SQL. Now, do a rollup of the raw data into the rollup tables.
+
+ // delete existing rollup for class data
+ int retcode = sql->Execute( "delete from tf_classdata_rollup" );
+ if ( 0 != retcode )
+ {
+ Msg( "Failed to delete from tf_classdata_rollup\n" );
+ return;
+ }
+ // create new rollup for class data
+ retcode = sql->Execute( "insert into tf_classdata_rollup (class,spawns,totaltime,score,kills,deaths,assists,captures) "
+ "select class,sum(spawns),sum(totaltime),sum(score),sum(kills),sum(deaths),sum(assists),sum(captures) from tf_classdata group by class;" );
+ if ( 0 != retcode )
+ {
+ Msg( "Failed to create class data rollup\n" );
+ return;
+ }
+
+ // delete existing rollup for map data
+ retcode = sql->Execute( "delete from tf_mapdata_rollup" );
+ if ( 0 != retcode )
+ {
+ Msg( "Failed to delete from tf_mapdata_rollup\n" );
+ return;
+ }
+ // create new rollup for map data
+ retcode = sql->Execute( "insert into tf_mapdata_rollup (mapname,roundsplayed,totaltime,bluewins,redwins,stalemates) "
+ "select mapname,sum(roundsplayed),sum(totaltime),sum(bluewins),sum(redwins),sum(stalemates) from tf_mapdata group by mapname;" );
+ if ( 0 != retcode )
+ {
+ Msg( "Failed to create map data rollup\n" );
+ return;
+ }
+#endif
+}