summaryrefslogtreecommitdiff
path: root/utils/gamestats_reader
diff options
context:
space:
mode:
Diffstat (limited to 'utils/gamestats_reader')
-rw-r--r--utils/gamestats_reader/dod_gamestats.h175
-rw-r--r--utils/gamestats_reader/gamestats_reader.cpp460
-rw-r--r--utils/gamestats_reader/gamestats_reader.vpc39
3 files changed, 674 insertions, 0 deletions
diff --git a/utils/gamestats_reader/dod_gamestats.h b/utils/gamestats_reader/dod_gamestats.h
new file mode 100644
index 0000000..a33abfc
--- /dev/null
+++ b/utils/gamestats_reader/dod_gamestats.h
@@ -0,0 +1,175 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: The dod game stats header
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#ifndef DOD_GAMESTATS_H
+#define DOD_GAMESTATS_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+// Redefine some things for the stat reader so it doesn't have to include weapon_dodbase.h
+#ifndef GAME_DLL
+
+ typedef enum
+ {
+ WEAPON_NONE = 0,
+
+ //Melee
+ WEAPON_AMERKNIFE,
+ WEAPON_SPADE,
+
+ //Pistols
+ WEAPON_COLT,
+ WEAPON_P38,
+ WEAPON_C96,
+
+ //Rifles
+ WEAPON_GARAND,
+ WEAPON_M1CARBINE,
+ WEAPON_K98,
+
+ //Sniper Rifles
+ WEAPON_SPRING,
+ WEAPON_K98_SCOPED,
+
+ //SMG
+ WEAPON_THOMPSON,
+ WEAPON_MP40,
+ WEAPON_MP44,
+ WEAPON_BAR,
+
+ //Machine guns
+ WEAPON_30CAL,
+ WEAPON_MG42,
+
+ //Rocket weapons
+ WEAPON_BAZOOKA,
+ WEAPON_PSCHRECK,
+
+ //Grenades
+ WEAPON_FRAG_US,
+ WEAPON_FRAG_GER,
+
+ WEAPON_FRAG_US_LIVE,
+ WEAPON_FRAG_GER_LIVE,
+
+ WEAPON_SMOKE_US,
+ WEAPON_SMOKE_GER,
+
+ WEAPON_RIFLEGREN_US,
+ WEAPON_RIFLEGREN_GER,
+
+ WEAPON_RIFLEGREN_US_LIVE,
+ WEAPON_RIFLEGREN_GER_LIVE,
+
+ // not actually separate weapons, but defines used in stats recording
+ // find a better way to do this without polluting the list of actual weapons.
+ WEAPON_THOMPSON_PUNCH,
+ WEAPON_MP40_PUNCH,
+
+ WEAPON_GARAND_ZOOMED,
+ WEAPON_K98_ZOOMED,
+ WEAPON_SPRING_ZOOMED,
+ WEAPON_K98_SCOPED_ZOOMED,
+
+ WEAPON_30CAL_UNDEPLOYED,
+ WEAPON_MG42_UNDEPLOYED,
+
+ WEAPON_BAR_SEMIAUTO,
+ WEAPON_MP44_SEMIAUTO,
+
+ WEAPON_MAX, // number of weapons weapon index
+
+ } DODWeaponID;
+
+#endif // ndef WEAPON_NONE
+
+#define DOD_STATS_BLOB_VERSION 1
+
+#define DOD_NUM_DISTANCE_STAT_WEAPONS 22
+#define DOD_NUM_NODIST_STAT_WEAPONS 14
+#define DOD_NUM_WEAPON_DISTANCE_BUCKETS 10
+
+extern int iDistanceStatWeapons[DOD_NUM_DISTANCE_STAT_WEAPONS];
+extern int iNoDistStatWeapons[DOD_NUM_NODIST_STAT_WEAPONS];
+extern int iWeaponBucketDistances[DOD_NUM_WEAPON_DISTANCE_BUCKETS-1];
+
+#ifndef GAME_DLL
+ extern const char * s_WeaponAliasInfo[];
+#endif
+
+typedef struct
+{
+ char szGameName[8];
+ byte iVersion;
+ char szMapName[32];
+ char ipAddr[4];
+ short port;
+ int serverid;
+} gamestats_header_t;
+
+// Stats for bullet weapons - includes distance of hits
+typedef struct
+{
+ short iNumAttacks; // times fired
+ short iNumHits; // times hit
+
+ // distance buckets - distances are defined per-weapon ( 0 is closest, buckets-1 farthest )
+ short iDistanceBuckets[DOD_NUM_WEAPON_DISTANCE_BUCKETS];
+
+} dod_gamestats_weapon_distance_t;
+
+// Stats for non-bullet weapons
+typedef struct
+{
+ short iNumAttacks; // times fired
+ short iNumHits; // times hit
+} dod_gamestats_weapon_nodist_t;
+
+typedef struct
+{
+ gamestats_header_t header;
+
+ // Team Scores
+ byte iNumAlliesWins;
+ byte iNumAxisWins;
+
+ short iAlliesTickPoints;
+ short iAxisTickPoints;
+
+ short iMinutesPlayed; // time spent on the map rotation
+
+ // Player Data
+ short iMinutesPlayedPerClass_Allies[7]; // includes random
+ short iMinutesPlayedPerClass_Axis[7]; // includes random
+
+ short iKillsPerClass_Allies[6];
+ short iKillsPerClass_Axis[6];
+
+ short iSpawnsPerClass_Allies[6];
+ short iSpawnsPerClass_Axis[6];
+
+ short iCapsPerClass_Allies[6];
+ short iCapsPerClass_Axis[6];
+
+ byte iDefensesPerClass_Allies[6];
+ byte iDefensesPerClass_Axis[6];
+
+ // Server Settings
+ // assume these class limits don't change through the course of the map
+ byte iClassLimits_Allies[6];
+ byte iClassLimits_Axis[6];
+
+ // Weapon Data
+ dod_gamestats_weapon_distance_t weaponStatsDistance[DOD_NUM_DISTANCE_STAT_WEAPONS]; // 14 * 22 = 308 bytes
+ dod_gamestats_weapon_nodist_t weaponStats[DOD_NUM_NODIST_STAT_WEAPONS]; // 4 * 14 = 56 bytes
+
+ // how many times a weapon was picked up ?
+
+} dod_gamestats_t;
+
+#endif // DOD_GAMESTATS_H \ No newline at end of file
diff --git a/utils/gamestats_reader/gamestats_reader.cpp b/utils/gamestats_reader/gamestats_reader.cpp
new file mode 100644
index 0000000..038ae58
--- /dev/null
+++ b/utils/gamestats_reader/gamestats_reader.cpp
@@ -0,0 +1,460 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//
+//===========================================================================//
+#include <stdio.h>
+#include <stdlib.h>
+#include <direct.h>
+#include "tier1/strtools.h"
+#include <conio.h>
+#include "tier1/utlbuffer.h"
+#include "tier2/tier2.h"
+#include "filesystem.h"
+#include "imysqlwrapper.h"
+#include "../../game/server/dod/dod_gamestats.h"
+#include <time.h>
+
+
+static void Pause( void )
+{
+ printf( "Hit a key to continue\n" );
+ getch();
+}
+
+static void Usage()
+{
+ fprintf( stderr, "Usage: gamestats_reader <hostname> <database> <username> <password> <table> <directory to parse> ( -verbose )\n" );
+ Pause();
+ exit( -1 );
+}
+
+#define SQL_CMD_BUFSIZE 16000
+
+char sqlCmd[SQL_CMD_BUFSIZE];
+bool g_bFirstCmd;
+bool g_bVerbose;
+
+IMySQL *mysql;
+
+char **g_argv;
+
+void StartMYSQLInsert( void )
+{
+ g_bFirstCmd = true;
+ sqlCmd[0] = '\0';
+
+ Q_snprintf( sqlCmd, SQL_CMD_BUFSIZE, "INSERT INTO %s SET ", g_argv[5] );
+}
+
+void AddField( const char *field, const char *value )
+{
+ char buf[128];
+
+ if ( !g_bFirstCmd )
+ {
+ Q_strncat( sqlCmd, ", ", SQL_CMD_BUFSIZE, COPY_ALL_CHARACTERS );
+ }
+
+ Q_snprintf( buf, sizeof(buf), "%s=\"%s\"", field, value );
+ Q_strncat( sqlCmd, buf, SQL_CMD_BUFSIZE, COPY_ALL_CHARACTERS );
+
+ g_bFirstCmd = false;
+}
+
+void AddField( const char *field, const int value )
+{
+ char buf[128];
+
+ if ( !g_bFirstCmd )
+ {
+ Q_strncat( sqlCmd, ", ", SQL_CMD_BUFSIZE, COPY_ALL_CHARACTERS );
+ }
+
+ Q_snprintf( buf, sizeof(buf), "%s=%d", field, value );
+
+#ifdef _DEBUG
+ if ( Q_strlen(buf) + Q_strlen(sqlCmd) > SQL_CMD_BUFSIZE )
+ {
+ Assert( !"increase buf size\n" );
+ }
+#endif
+
+ Q_strncat( sqlCmd, buf, SQL_CMD_BUFSIZE, COPY_ALL_CHARACTERS );
+
+ g_bFirstCmd = false;
+}
+
+int CompleteMYSQLInsert( void )
+{
+ // terminate command and execute it
+ Q_strncat( sqlCmd, "\n", SQL_CMD_BUFSIZE, COPY_ALL_CHARACTERS );
+
+ if ( g_bVerbose )
+ {
+ printf( "%s", sqlCmd );
+ }
+
+ int retcode = mysql->Execute( sqlCmd );
+
+ if ( retcode != 0 )
+ {
+ const char *asdf = mysql->GetLastError();
+ printf( "Error: %s\n", asdf );
+ }
+
+ return retcode;
+}
+
+static const char *pszTeamNames[] =
+{
+ "allies",
+ "axis"
+};
+
+static const char *pszClassNames[] =
+{
+ "rifleman",
+ "assault",
+ "support",
+ "sniper",
+ "mg",
+ "rocket"
+};
+
+int iDistanceStatWeapons[DOD_NUM_DISTANCE_STAT_WEAPONS] =
+{
+ WEAPON_COLT,
+ WEAPON_P38,
+ WEAPON_C96,
+ WEAPON_GARAND,
+ WEAPON_GARAND_ZOOMED,
+ WEAPON_M1CARBINE,
+ WEAPON_K98,
+ WEAPON_K98_ZOOMED,
+ WEAPON_SPRING,
+ WEAPON_SPRING_ZOOMED,
+ WEAPON_K98_SCOPED,
+ WEAPON_K98_SCOPED_ZOOMED,
+ WEAPON_THOMPSON,
+ WEAPON_MP40,
+ WEAPON_MP44,
+ WEAPON_MP44_SEMIAUTO,
+ WEAPON_BAR,
+ WEAPON_BAR_SEMIAUTO,
+ WEAPON_30CAL,
+ WEAPON_30CAL_UNDEPLOYED,
+ WEAPON_MG42,
+ WEAPON_MG42_UNDEPLOYED,
+};
+
+// Send hit/shots only for the following weapons
+int iNoDistStatWeapons[DOD_NUM_NODIST_STAT_WEAPONS] =
+{
+ WEAPON_AMERKNIFE,
+ WEAPON_SPADE,
+ WEAPON_BAZOOKA,
+ WEAPON_PSCHRECK,
+ WEAPON_FRAG_US,
+ WEAPON_FRAG_GER,
+ WEAPON_FRAG_US_LIVE,
+ WEAPON_FRAG_GER_LIVE,
+ WEAPON_RIFLEGREN_US,
+ WEAPON_RIFLEGREN_GER,
+ WEAPON_RIFLEGREN_US_LIVE,
+ WEAPON_RIFLEGREN_GER_LIVE,
+ WEAPON_THOMPSON_PUNCH,
+ WEAPON_MP40_PUNCH,
+};
+
+const char * s_WeaponAliasInfo[] =
+{
+ "none", // WEAPON_NONE = 0,
+
+ //Melee
+ "amerknife", //WEAPON_AMERKNIFE,
+ "spade", //WEAPON_SPADE,
+
+ //Pistols
+ "colt", //WEAPON_COLT,
+ "p38", //WEAPON_P38,
+ "c96", //WEAPON_C96
+
+ //Rifles
+ "garand", //WEAPON_GARAND,
+ "m1carbine", //WEAPON_M1CARBINE,
+ "k98", //WEAPON_K98,
+
+ //Sniper Rifles
+ "spring", //WEAPON_SPRING,
+ "k98_scoped", //WEAPON_K98_SCOPED,
+
+ //SMG
+ "thompson", //WEAPON_THOMPSON,
+ "mp40", //WEAPON_MP40,
+ "mp44", //WEAPON_MP44,
+ "bar", //WEAPON_BAR,
+
+ //Machine guns
+ "30cal", //WEAPON_30CAL,
+ "mg42", //WEAPON_MG42,
+
+ //Rocket weapons
+ "bazooka", //WEAPON_BAZOOKA,
+ "pschreck", //WEAPON_PSCHRECK,
+
+ //Grenades
+ "frag_us", //WEAPON_FRAG_US,
+ "frag_ger", //WEAPON_FRAG_GER,
+
+ "frag_us_live", //WEAPON_FRAG_US_LIVE
+ "frag_ger_live", //WEAPON_FRAG_GER_LIVE
+
+ "smoke_us", //WEAPON_SMOKE_US
+ "smoke_ger", //WEAPON_SMOKE_GER
+
+ "riflegren_us", //WEAPON_RIFLEGREN_US
+ "riflegren_ger", //WEAPON_RIFLEGREN_GER
+
+ "riflegren_us_live", //WEAPON_RIFLEGREN_US_LIVE
+ "riflegren_ger_live", //WEAPON_RIFLEGREN_GER_LIVE
+
+ // not actually separate weapons, but defines used in stats recording
+ "thompson_punch", //WEAPON_THOMPSON_PUNCH
+ "mp40_punch", //WEAPON_MP40_PUNCH
+ "garand_zoomed", //WEAPON_GARAND_ZOOMED,
+
+ "k98_zoomed", //WEAPON_K98_ZOOMED
+ "spring_zoomed", //WEAPON_SPRING_ZOOMED
+ "k98_scoped_zoomed", //WEAPON_K98_SCOPED_ZOOMED
+
+ "30cal_undeployed", //WEAPON_30CAL_UNDEPLOYED,
+ "mg42_undeployed", //WEAPON_MG42_UNDEPLOYED,
+
+ "bar_semiauto", //WEAPON_BAR_SEMIAUTO,
+ "mp44_semiauto", //WEAPON_MP44_SEMIAUTO,
+
+ 0, // end of list marker
+};
+
+void ParseFile( const char *fileName )
+{
+ FileHandle_t file = g_pFullFileSystem->Open( fileName, "rb" );
+
+ if ( !file )
+ {
+ return;
+ }
+
+ dod_gamestats_t stats;
+ g_pFullFileSystem->Read( &stats, sizeof( dod_gamestats_t ), file );
+
+ if ( stats.header.iVersion != DOD_STATS_BLOB_VERSION || Q_stricmp( stats.header.szGameName, "dod" ) )
+ {
+ printf( "Error parsing file, bad header info: %s\n", fileName );
+ return;
+ }
+
+ StartMYSQLInsert();
+
+ AddField( "map", stats.header.szMapName );
+
+ const time_t mapfiletime = g_pFullFileSystem->GetFileTime( fileName );
+
+ struct tm *t = localtime( &mapfiletime );
+
+ // YYYY-MM-DD HH:MM::SS
+
+ char filetimebuf[64];
+
+ Q_snprintf( filetimebuf, sizeof(filetimebuf), "%04d-%02d-%02d %02d:%02d:%02d",
+ t->tm_year + 1900,
+ t->tm_mon + 1,
+ t->tm_mday,
+ t->tm_hour,
+ t->tm_min,
+ t->tm_sec );
+
+ AddField( "time", filetimebuf );
+
+ AddField( "version", stats.header.iVersion );
+
+ AddField( "ipaddr_0", stats.header.ipAddr[0] );
+ AddField( "ipaddr_1", stats.header.ipAddr[1] );
+ AddField( "ipaddr_2", stats.header.ipAddr[2] );
+ AddField( "ipaddr_3", stats.header.ipAddr[3] );
+ AddField( "port", stats.header.port );
+
+ AddField( "minutes_map", stats.iMinutesPlayed );
+ AddField( "wins_allies", stats.iNumAlliesWins );
+ AddField( "wins_axis", stats.iNumAxisWins);
+ AddField( "tickpoints_allies", stats.iAlliesTickPoints );
+ AddField( "tickpoints_axis", stats.iAxisTickPoints );
+
+ char buf[128];
+
+ for ( int cls=0;cls<6;cls++ )
+ {
+ Q_snprintf( buf, sizeof(buf), "minutes_allies_%s", pszClassNames[cls] );
+ AddField( buf, stats.iMinutesPlayedPerClass_Allies[cls] );
+
+ Q_snprintf( buf, sizeof(buf), "kills_allies_%s", pszClassNames[cls] );
+ AddField( buf, stats.iKillsPerClass_Allies[cls] );
+
+ Q_snprintf( buf, sizeof(buf), "defenses_allies_%s", pszClassNames[cls] );
+ AddField( buf, stats.iDefensesPerClass_Allies[cls] );
+
+ Q_snprintf( buf, sizeof(buf), "caps_allies_%s", pszClassNames[cls] );
+ AddField( buf, stats.iCapsPerClass_Allies[cls] );
+
+ Q_snprintf( buf, sizeof(buf), "spawns_allies_%s", pszClassNames[cls] );
+ AddField( buf, stats.iSpawnsPerClass_Allies[cls] );
+
+ Q_snprintf( buf, sizeof(buf), "classlimit_allies_%s", pszClassNames[cls] );
+ AddField( buf, stats.iClassLimits_Allies[cls] );
+
+
+ Q_snprintf( buf, sizeof(buf), "minutes_axis_%s", pszClassNames[cls] );
+ AddField( buf, stats.iMinutesPlayedPerClass_Axis[cls] );
+
+ Q_snprintf( buf, sizeof(buf), "kills_axis_%s", pszClassNames[cls] );
+ AddField( buf, stats.iKillsPerClass_Axis[cls] );
+
+ Q_snprintf( buf, sizeof(buf), "defenses_axis_%s", pszClassNames[cls] );
+ AddField( buf, stats.iDefensesPerClass_Axis[cls] );
+
+ Q_snprintf( buf, sizeof(buf), "caps_axis_%s", pszClassNames[cls] );
+ AddField( buf, stats.iCapsPerClass_Axis[cls] );
+
+ Q_snprintf( buf, sizeof(buf), "spawns_axis_%s", pszClassNames[cls] );
+ AddField( buf, stats.iSpawnsPerClass_Axis[cls] );
+
+ Q_snprintf( buf, sizeof(buf), "classlimit_axis_%s", pszClassNames[cls] );
+ AddField( buf, stats.iClassLimits_Axis[cls] );
+ }
+
+ int i;
+
+ for ( i=0;i<DOD_NUM_DISTANCE_STAT_WEAPONS;i++ )
+ {
+ int iWeapon = iDistanceStatWeapons[i];
+ const char *pszWeapon = s_WeaponAliasInfo[iWeapon];
+
+ Q_snprintf( buf, sizeof(buf), "weapon_shots_%s", pszWeapon );
+ AddField( buf, stats.weaponStatsDistance[i].iNumAttacks );
+
+ Q_snprintf( buf, sizeof(buf), "weapon_hits_%s", pszWeapon );
+ AddField( buf, stats.weaponStatsDistance[i].iNumHits );
+
+ for ( int j=0;j<DOD_NUM_WEAPON_DISTANCE_BUCKETS;j++ )
+ {
+ Q_snprintf( buf, sizeof(buf), "weapon_distance_%s_%d", pszWeapon, j );
+ AddField( buf, stats.weaponStatsDistance[i].iDistanceBuckets[j] );
+ }
+ }
+
+ for ( i=0;i<DOD_NUM_NODIST_STAT_WEAPONS;i++ )
+ {
+ int iWeapon = iNoDistStatWeapons[i];
+ const char *pszWeapon = s_WeaponAliasInfo[iWeapon];
+
+ Q_snprintf( buf, sizeof(buf), "weapon_shots_%s", pszWeapon );
+ AddField( buf, stats.weaponStats[i].iNumAttacks );
+
+ Q_snprintf( buf, sizeof(buf), "weapon_hits_%s", pszWeapon );
+ AddField( buf, stats.weaponStats[i].iNumHits );
+ }
+
+ CompleteMYSQLInsert();
+
+ g_pFullFileSystem->Close( file );
+}
+
+int main( int argc, char **argv )
+{
+ g_argv = argv;
+
+ if( argc < 6 )
+ {
+ Usage();
+ }
+
+ if ( argc == 7 && !Q_stricmp( argv[6], "-verbose" ) )
+ {
+ g_bVerbose = true;
+ }
+
+ InitDefaultFileSystem();
+
+ // Init MYSQL connection
+ CSysModule *sql = Sys_LoadModule( "mysql_wrapper" );
+ if ( sql )
+ {
+ CreateInterfaceFn factory = Sys_GetFactory( sql );
+ if ( factory )
+ {
+ mysql = ( IMySQL * )factory( MYSQL_WRAPPER_VERSION_NAME, NULL );
+ if ( mysql )
+ {
+ if ( mysql->InitMySQL( argv[ 2 ], argv[ 1 ], argv[ 3 ], argv[ 4 ] ) )
+ {
+ // insert rows
+
+ const char *dir = argv[6];
+
+ char searchString[MAX_PATH*2];
+ Q_strncpy( searchString, dir, sizeof( searchString ) );
+ Q_AppendSlash( searchString, sizeof( searchString ) );
+ Q_strncat( searchString, "*.dat", sizeof( searchString ), COPY_ALL_CHARACTERS );
+
+ int iNumFiles = 0;
+ FileFindHandle_t findHandle = NULL;
+ const char *filename = g_pFullFileSystem->FindFirst( searchString, &findHandle );
+ while ( filename )
+ {
+ char fullFileName[MAX_PATH*2];
+
+ Q_strncpy( fullFileName, dir, sizeof( fullFileName ) );
+ Q_AppendSlash( fullFileName, sizeof( fullFileName ) );
+ Q_strncat( fullFileName, filename, sizeof( fullFileName ), COPY_ALL_CHARACTERS );
+
+ ParseFile( fullFileName );
+
+ printf( "processing file: %s\n", fullFileName );
+ iNumFiles++;
+
+ filename = g_pFullFileSystem->FindNext(findHandle);
+ }
+ g_pFullFileSystem->FindClose(findHandle);
+
+ printf( "Completed: %d files processed from directory \"%s\"\n", iNumFiles, dir );
+
+ }
+ else
+ {
+ printf( "InitMySQL failed ( %s )\n", mysql->GetLastError() );
+ }
+
+ mysql->Release();
+ }
+ else
+ {
+ printf( "Unable to connect via mysql_wrapper\n");
+ }
+ }
+ else
+ {
+ printf( "Unable to get factory from mysql_wrapper.dll, not updating access mysql table!!!" );
+ }
+
+ Sys_UnloadModule( sql );
+ }
+ else
+ {
+ printf( "Unable to load mysql_wrapper.dll, not updating access mysql table!!!" );
+ }
+
+ return 0;
+}
diff --git a/utils/gamestats_reader/gamestats_reader.vpc b/utils/gamestats_reader/gamestats_reader.vpc
new file mode 100644
index 0000000..a71157e
--- /dev/null
+++ b/utils/gamestats_reader/gamestats_reader.vpc
@@ -0,0 +1,39 @@
+//-----------------------------------------------------------------------------
+// GAMESTATS_READER.VPC
+//
+// Project Script
+//-----------------------------------------------------------------------------
+
+$Macro SRCDIR "..\.."
+$Macro OUTBINDIR "$SRCDIR\..\game\bin"
+
+$Include "$SRCDIR\vpc_scripts\source_exe_con_base.vpc"
+
+$Configuration
+{
+ $Compiler
+ {
+ $AdditionalIncludeDirectories "$BASE;..\common;..\vmpi"
+ }
+}
+
+$Project "Gamestats_reader"
+{
+ $Folder "Source Files"
+ {
+ $File "gamestats_reader.cpp"
+ }
+
+ $Folder "Header Files"
+ {
+ $File "$SRCDIR\public\tier1\interface.h"
+ $File "$SRCDIR\public\tier1\UtlBuffer.h"
+ }
+
+ $Folder "Link Libraries"
+ {
+ $DynamicFile "$SRCDIR\lib\public\bitmap.lib"
+ $DynamicFile "$SRCDIR\lib\public\mathlib.lib"
+ $DynamicFile "$SRCDIR\lib\public\tier2.lib"
+ }
+}