Well I coded reading and writing to csstats.dat successfully in Pawn but all changes made by my code are lost at map-change\restart since the csstats.dat file is open and being manipulated by csx module while the server is running. Someone will need to code this into the dll.
Here it is if anyone needs it for anything.
PHP Code:
#include <amxmodx>
#include <amxmisc>
#include <csx>
/* -- csstats.dat file structure --
* int16 - RANK_VERSION
* uint16 - Bytes in first string (if 0, no entries)
o sz - Name (read N bytes, where N is previous uint16)
o uint16 - Bytes of next string
o sz - Steam ID (read N bytes, where N is previous uint16)
o uint32 - Team kills
o uint32 - Damage
o uint32 - Deaths
o int32 - Kills
o uint32 - Shots
o uint32 - Hits
o uint32 - Headshots
o uint32 - Defusions
o uint32 - Defusal Attempts
o uint32 - Plants
o uint32 - Explosions
o uint32[9] - Body Hits (array of ints, one for each body hit area)
o uint16 - If zero, end of file. Otherwise, loop back up and read the next name for this many bytes.
*/
const RANK_VERSION = 11;
//Array sizes for storing data in csstats.dat
const STATS_SIZE = 11;
const BODYHITS_SIZE = 9;
//Array sizes CSX functions use for reading data with natives
const CSX_STATS_SIZE = 8;
const CSX_BODYHITS_SIZE = 8;
enum _:Stats
{
TeamKills, //3
Damage, //6
Deaths, //1
Kills, //0
Shots, //4
Hits, //5
Headshots, //2
Defusions, //7
DefusalAttempts,
Plants,
Explosions
}
enum _:BodyHits
{
Generic,
Head,
Chest,
Stomach,
LeftArm,
RightArm,
LeftLeg,
RightLeg,
Empty
}
new StatsTable[] =
{
Kills,
Deaths,
Headshots,
TeamKills,
Shots,
Hits,
Damage,
Defusions
};
public plugin_init()
{
register_clcmd( "say hi" , "Test" );
}
public Test( id )
{
new iStats[ 8 ] , iBHits[ 8 ];
//Get position in stats and stats data from csstats.dat
new iRankPos = get_user_stats( id , iStats , iBHits );
server_print( "STATS BEFORE WRITE" );
//Read existing stats
ReadStats();
//Modify values to check if they change
iStats[ 0 ] = 1101;
iStats[ 1 ] = 1102;
iStats[ 2 ] = 1103;
iStats[ 3 ] = 1104;
iStats[ 4 ] = 1105;
iStats[ 5 ] = 1106;
iStats[ 6 ] = 1107;
iStats[ 7 ] = 1108;
//Write new values
WriteStats( iRankPos , iStats , iBHits );
server_print( "STATS AFTER WRITE" );
//Read to see if changed after writing
ReadStats();
}
public ReadStats()
{
new szFile[ 64 ] , iFile;
new iData[ 10 ];
new szString[ 35 ] , iStringLen;
new iStats[ Stats ] , iBHits[ BodyHits ];
//Format the file name
copy( szFile[ get_datadir( szFile , charsmax( szFile ) ) ] , charsmax( szFile ) , "/csstats.dat" );
//Open file for binary read
iFile = fopen( szFile , "rb" );
//Read rank version
fread_raw( iFile , iData , 1 , BLOCK_SHORT );
if ( iData[ 0 ] != RANK_VERSION )
set_fail_state( "Rank version does not match" );
//Read first name len (if 0, there are no player in stats file)
fread_raw( iFile , iData , 1 , BLOCK_SHORT );
iStringLen = iData[ 0 ] & 0xFFFF;
while ( iStringLen )
{
//Read name
fread_raw( iFile , iData , iStringLen , BLOCK_CHAR );
ReadString( szString , iStringLen , charsmax( szString ) , iData );
server_print( "Name: %s" , szString );
//Read steamid len
fread_raw( iFile , iData , 1 , BLOCK_SHORT );
iStringLen = iData[ 0 ] & 0xFFFF;
//Read steamid
fread_raw( iFile , iData , iStringLen , BLOCK_CHAR );
ReadString( szString , iStringLen , charsmax( szString ) , iData );
server_print( "SteamID: %s" , szString );
//Read stats data
fread_raw( iFile , iStats , STATS_SIZE , BLOCK_INT );
server_print( "%d %d %d %d %d %d %d %d %d %d %d" , iStats[ Damage ] ,
iStats[ Kills ] ,
iStats[ Deaths ] ,
iStats[ TeamKills ] ,
iStats[ Shots ] ,
iStats[ Hits ] ,
iStats[ Headshots ] ,
iStats[ Plants ] ,
iStats[ Explosions ] ,
iStats[ Defusions ] ,
iStats[ DefusalAttempts ] );
//Read body-hit data
fread_raw( iFile , iBHits , BODYHITS_SIZE , BLOCK_INT );
server_print( "%d %d %d %d %d %d %d %d %d" , iBHits[ Generic ] ,
iBHits[ Head ] ,
iBHits[ Chest ] ,
iBHits[ Stomach ] ,
iBHits[ LeftArm ] ,
iBHits[ RightArm ] ,
iBHits[ LeftLeg ] ,
iBHits[ RightLeg ] ,
iBHits[ 8 ] );
//Read next players name len (if 0, end of stats)
fread_raw( iFile , iData , 1 , BLOCK_SHORT );
iStringLen = iData[ 0 ] & 0xFFFF;
}
fclose( iFile );
}
public WriteStats( iEntry , iStats[ CSX_STATS_SIZE ] , iBHits[ CSX_BODYHITS_SIZE ] )
{
new szFile[ 64 ] , iFile;
new iData[ 10 ];
new iStringLen;
new iCurrentEntry;
copy( szFile[ get_datadir( szFile , charsmax( szFile ) ) ] , charsmax( szFile ) , "/csstats.dat" );
if ( !( iFile = fopen( szFile , "rb+" ) ) )
set_fail_state( "Error at file open" );
fread_raw( iFile , iData , 1 , BLOCK_SHORT );
if ( iData[ 0 ] != RANK_VERSION )
set_fail_state( "Rank version does not match" );
fread_raw( iFile , iData , 1 , BLOCK_SHORT );
iStringLen = iData[ 0 ] & 0xFFFF;
while ( iStringLen && ( ++iCurrentEntry <= iEntry ) )
{
//Seek past the name data
fseek( iFile , iStringLen , SEEK_CUR );
//Read steam-id len
fread_raw( iFile , iData , 1 , BLOCK_SHORT );
iStringLen = iData[ 0 ] & 0xFFFF;
//Seek past steam-id data
fseek( iFile , iStringLen , SEEK_CUR );
//If current entry is the one we want to modify
if ( iCurrentEntry == iEntry )
{
//The order of stats saved to csstats.dat and the order used
//by csx are different, this puts them in correct order.
FixStatsOrder( iStats );
//Write stats array
fwrite_raw( iFile , iStats , CSX_STATS_SIZE , BLOCK_INT )
//Only 8 of 11 ints are written so we seek past 3
fseek( iFile , ( STATS_SIZE - CSX_STATS_SIZE ) * BLOCK_INT , SEEK_CUR );
//Write body-hit data
fwrite_raw( iFile , iBHits , CSX_BODYHITS_SIZE , BLOCK_INT );
//Only 8 of 9 ints are written so we seek past 1
fseek( iFile , ( BODYHITS_SIZE - CSX_BODYHITS_SIZE ) * BLOCK_INT , SEEK_CUR );
}
else
{
//This is not the entry that we want to modify so we skip past
//the stats and bodyhit data.
fseek( iFile , ( ( STATS_SIZE + BODYHITS_SIZE ) * BLOCK_INT ) , SEEK_CUR );
}
fread_raw( iFile , iData , 1 , BLOCK_SHORT );
iStringLen = iData[ 0 ] & 0xFFFF;
}
fclose( iFile );
}
FixStatsOrder( iStats[ 8 ] )
{
new iTmpStats[ 8 ];
iTmpStats = iStats;
for ( new i = 0 ; i < 8 ; i++ )
iStats[ i ] = iTmpStats[ StatsTable[ i ] ];
}
ReadString( szDestString[] , iLen , iMaxLen , SourceData[] )
{
new iStrPos = -1;
new iRawPos = 0;
while ( ( ++iStrPos < iLen ) && ( iStrPos < iMaxLen ) && ( iRawPos < 10 ) )
{
szDestString[ iStrPos ] = ( SourceData[ iRawPos ] >> ( ( iStrPos % 4 ) * 8 ) ) & 0xFF;
if ( iStrPos && ( ( iStrPos % 4 ) == 3 ) )
iRawPos++
}
szDestString[ iStrPos ] = EOS;
}