mirror of
https://port.numenaute.org/aleajactaest/khanat-opennel-code.git
synced 2024-12-27 03:10:54 +00:00
546 lines
15 KiB
C++
546 lines
15 KiB
C++
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
#include "nel/misc/file.h"
|
||
|
#include "nel/misc/path.h"
|
||
|
#include "nel/misc/vector.h"
|
||
|
#include "nel/misc/algo.h"
|
||
|
|
||
|
|
||
|
using namespace std;
|
||
|
using namespace NLMISC;
|
||
|
|
||
|
|
||
|
#define myinfo NLMISC::createDebug (), NLMISC::InfoLog->setPosition( __LINE__, __FILE__ ), NLMISC::InfoLog->displayRawNL
|
||
|
|
||
|
|
||
|
// ***************************************************************************
|
||
|
void filterRyzomBug(const char *dirSrc, const char *dirDst, uint patchVersionWanted, const string specialFilter)
|
||
|
{
|
||
|
if(!CFile::isDirectory(dirDst))
|
||
|
{
|
||
|
if(!CFile::createDirectory(dirDst))
|
||
|
{
|
||
|
myinfo("%s is not a directory and cannot create it", dirDst);
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
vector<string> fileList;
|
||
|
CPath::getPathContent(dirSrc, false, false, true, fileList, NULL, true);
|
||
|
|
||
|
for(uint i=0;i<fileList.size();i++)
|
||
|
{
|
||
|
const string &fileFullPath= fileList[i];
|
||
|
CIFile f;
|
||
|
if(f.open(fileFullPath, true))
|
||
|
{
|
||
|
// Parse all "UserId: ", this get the number of crash in this file
|
||
|
const string userIdTok= "UserId: ";
|
||
|
const string patchVersionTok= "PatchVersion: ";
|
||
|
uint numUserId= 0;
|
||
|
uint numPatchVersion= 0;
|
||
|
bool precUserId= false;
|
||
|
bool ok= true;
|
||
|
bool filterFound= false;
|
||
|
while(!f.eof())
|
||
|
{
|
||
|
char tmp[1000];
|
||
|
f.getline(tmp, 1000);
|
||
|
string str= tmp;
|
||
|
if(str.compare(0, userIdTok.size(), userIdTok)==0)
|
||
|
{
|
||
|
numUserId++;
|
||
|
if(precUserId)
|
||
|
{
|
||
|
nlwarning("Don't find a PatchVersion for all UserId in %s", fileFullPath.c_str());
|
||
|
ok= false;
|
||
|
break;
|
||
|
}
|
||
|
else
|
||
|
precUserId= true;
|
||
|
}
|
||
|
if(str.compare(0, patchVersionTok.size(), patchVersionTok)==0)
|
||
|
{
|
||
|
numPatchVersion++;
|
||
|
if(!precUserId)
|
||
|
{
|
||
|
nlwarning("Don't find a PatchVersion for all UserId in %s", fileFullPath.c_str());
|
||
|
ok= false;
|
||
|
break;
|
||
|
}
|
||
|
else
|
||
|
precUserId= false;
|
||
|
|
||
|
// parse the version number
|
||
|
sint version;
|
||
|
sscanf(tmp, "PatchVersion: %d", &version);
|
||
|
if(version!=(sint)patchVersionWanted)
|
||
|
{
|
||
|
nlwarning("The Log %s contains a PatchVersion different: %d", fileFullPath.c_str(), version);
|
||
|
ok= false;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
if(!specialFilter.empty() && str.compare(0, specialFilter.size(), specialFilter)==0)
|
||
|
filterFound= true;
|
||
|
}
|
||
|
f.close();
|
||
|
if(ok && numUserId!=numPatchVersion)
|
||
|
{
|
||
|
nlwarning("Don't find a PatchVersion for all UserId in %s", fileFullPath.c_str());
|
||
|
ok= false;
|
||
|
}
|
||
|
// if specialFitler defined, but not found in the file, abort
|
||
|
if(ok && !specialFilter.empty() && !filterFound)
|
||
|
ok =false;
|
||
|
|
||
|
// if ok, copy the file and the associated .dmp dir
|
||
|
if(ok)
|
||
|
{
|
||
|
//myinfo("Copy %s", fileFullPath.c_str());
|
||
|
|
||
|
// get the log size
|
||
|
uint size= CFile::getFileSize(fileFullPath);
|
||
|
|
||
|
// copy the log
|
||
|
string fileNoDir= CFile::getFilename(fileFullPath);
|
||
|
string fileNoExt= CFile::getFilenameWithoutExtension(fileFullPath);
|
||
|
string dirDest= dirDst;
|
||
|
dirDest+= "/" + toString("%05d_", size/1024) + fileNoExt;
|
||
|
string dmpDirSrc= string(dirSrc) + "/" + fileNoExt;
|
||
|
|
||
|
CFile::createDirectory(dirDest);
|
||
|
|
||
|
// copy near the dmp
|
||
|
CFile::copyFile((dirDest + "/" + fileNoDir).c_str(), fileFullPath.c_str());
|
||
|
|
||
|
|
||
|
// copy all the .dmp in a new dir
|
||
|
static vector<string> dmpList;
|
||
|
dmpList.clear();
|
||
|
CPath::getPathContent(dmpDirSrc, false, false, true, dmpList, NULL);
|
||
|
for(uint j=0;j<dmpList.size();j++)
|
||
|
{
|
||
|
string dmpNoDir= CFile::getFilename(dmpList[j]);
|
||
|
CFile::copyFile((dirDest+ "/" + dmpNoDir).c_str(), dmpList[j].c_str());
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
nlwarning("cannot open %s", fileFullPath.c_str());
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
// ***************************************************************************
|
||
|
struct CStatVal
|
||
|
{
|
||
|
uint Val;
|
||
|
|
||
|
CStatVal()
|
||
|
{
|
||
|
Val = 0;
|
||
|
}
|
||
|
};
|
||
|
|
||
|
typedef map<sint, CStatVal> TStatMap;
|
||
|
typedef map<string, CStatVal> TStatStrMap;
|
||
|
typedef map<string, TStatStrMap> TStatStrStrMap;
|
||
|
|
||
|
class CCrashCont
|
||
|
{
|
||
|
public:
|
||
|
string Name;
|
||
|
sint X0,Y0,X1,Y1;
|
||
|
uint NumCrash;
|
||
|
CCrashCont(const std::string &name, sint x0, sint y0, sint x1, sint y1)
|
||
|
{
|
||
|
Name= name;
|
||
|
X0= min(x0,x1);
|
||
|
Y0= min(y0,y1);
|
||
|
X1= max(x0,x1);
|
||
|
Y1= max(y0,y1);
|
||
|
NumCrash= 0;
|
||
|
}
|
||
|
|
||
|
bool testPos(const CVector &pos)
|
||
|
{
|
||
|
if(pos.x>=X0 && pos.x<=X1 && pos.y>=Y0 && pos.y<=Y1)
|
||
|
{
|
||
|
NumCrash++;
|
||
|
return true;
|
||
|
}
|
||
|
return false;
|
||
|
}
|
||
|
};
|
||
|
|
||
|
void statRyzomBug(const char *dirSrc)
|
||
|
{
|
||
|
vector<string> fileList;
|
||
|
CPath::getPathContent(dirSrc, false, false, true, fileList, NULL, true);
|
||
|
|
||
|
// delete the log.log
|
||
|
CFile::deleteFile("log.log");
|
||
|
|
||
|
TStatStrMap senderMap;
|
||
|
TStatMap shardMap;
|
||
|
TStatMap timeInGameMap;
|
||
|
TStatMap patchVersionMap;
|
||
|
TStatMap info3dMap;
|
||
|
std::vector<CVector> userPosArray;
|
||
|
TStatStrStrMap senderToCrashFileMap;
|
||
|
TStatStrStrMap crashFileToSenderMap;
|
||
|
uint totalCrash= 0;
|
||
|
uint totalCrashDuplicate= 0;
|
||
|
|
||
|
|
||
|
// **** parse all files
|
||
|
for(uint i=0;i<fileList.size();i++)
|
||
|
{
|
||
|
const string &fileFullPath= fileList[i];
|
||
|
string fileNoDir= CFile::getFilename(fileFullPath);
|
||
|
// skip not .log files
|
||
|
if(!testWildCard(fileFullPath, "*.log"))
|
||
|
continue;
|
||
|
// parse
|
||
|
CIFile f;
|
||
|
if(f.open(fileFullPath, true))
|
||
|
{
|
||
|
const string senderIdTok= "Sender: ";
|
||
|
const string shardIdTok= "ShardId: ";
|
||
|
const string userPosTok= "UserPosition: ";
|
||
|
const string timeInGameIdTok= "Time in game: ";
|
||
|
const string patchVersionIdTok= "PatchVersion: ";
|
||
|
const string localTimeIdTok= "LocalTime: ";
|
||
|
const string nel3dIdTok= "NeL3D: ";
|
||
|
const string card3dIdTok= "3DCard: ";
|
||
|
|
||
|
string precSenderId;
|
||
|
string precSenderId2;
|
||
|
sint precShardId= -1;
|
||
|
sint precTimeInGame= -1; // 0 means "never in game", 1 means < 10 min, 2 means more
|
||
|
sint precNel3DMode= -1; // 0 OpenGL, 1 D3D, 2 ????
|
||
|
sint precCard3D= -1; // 0 NVidia, 1 ATI, 2 ????
|
||
|
sint precPatchVersion= -1;
|
||
|
sint64 precLocalTime= -1; // local time in second
|
||
|
sint64 precLocalTime2= -1; // local time in second
|
||
|
CVector precUserPos;
|
||
|
while(!f.eof())
|
||
|
{
|
||
|
char tmp[1000];
|
||
|
f.getline(tmp, 1000);
|
||
|
string str= tmp;
|
||
|
if(str.compare(0, senderIdTok.size(), senderIdTok)==0)
|
||
|
{
|
||
|
precSenderId2= precSenderId;
|
||
|
precSenderId= str.c_str()+senderIdTok.size();
|
||
|
}
|
||
|
else if(str.compare(0, shardIdTok.size(), shardIdTok)==0)
|
||
|
{
|
||
|
precShardId= atoi(str.c_str()+shardIdTok.size());
|
||
|
}
|
||
|
else if(str.compare(0, userPosTok.size(), userPosTok)==0)
|
||
|
{
|
||
|
string posStr= str.substr(userPosTok.size());
|
||
|
sscanf(posStr.c_str(), "%f%f%f", &precUserPos.x, &precUserPos.y, &precUserPos.z);
|
||
|
}
|
||
|
else if(str.compare(0, timeInGameIdTok.size(), timeInGameIdTok)==0)
|
||
|
{
|
||
|
string timeStr= str.substr(timeInGameIdTok.size(), string::npos);
|
||
|
if(timeStr=="0h 0min 0sec" || timeStr.size()<12)
|
||
|
precTimeInGame= 0;
|
||
|
else
|
||
|
{
|
||
|
if(timeStr[1]=='h' && timeStr[4]=='m' && timeStr[3]<='5')
|
||
|
precTimeInGame= 1;
|
||
|
else
|
||
|
precTimeInGame= 2;
|
||
|
}
|
||
|
}
|
||
|
else if(str.compare(0, patchVersionIdTok.size(), patchVersionIdTok)==0)
|
||
|
{
|
||
|
precPatchVersion= atoi(str.c_str()+patchVersionIdTok.size());
|
||
|
}
|
||
|
else if(str.compare(0, localTimeIdTok.size(), localTimeIdTok)==0)
|
||
|
{
|
||
|
precLocalTime2= precLocalTime;
|
||
|
// 2004/09/17 04:21:16
|
||
|
string timeStr= str.substr(localTimeIdTok.size(), string::npos);
|
||
|
if(timeStr.size()<19)
|
||
|
precLocalTime= 0;
|
||
|
else
|
||
|
{
|
||
|
sint64 year= atoi(timeStr.substr(0,4).c_str());
|
||
|
sint64 month= atoi(timeStr.substr(5,2).c_str());
|
||
|
sint64 day= atoi(timeStr.substr(8,2).c_str());
|
||
|
sint64 hour= atoi(timeStr.substr(11,2).c_str());
|
||
|
sint64 minute= atoi(timeStr.substr(14,2).c_str());
|
||
|
sint64 sec= atoi(timeStr.substr(17,2).c_str());
|
||
|
year= max(year, (sint64)2004);
|
||
|
year-=2004;
|
||
|
precLocalTime= ((year*366+month)*12)+day;
|
||
|
precLocalTime= (((precLocalTime*24)+hour)*60+minute)*60+sec;
|
||
|
}
|
||
|
}
|
||
|
else if(str.compare(0, nel3dIdTok.size(), nel3dIdTok)==0)
|
||
|
{
|
||
|
string tmp= str;
|
||
|
strlwr(tmp);
|
||
|
if(tmp.find("opengl")!=string::npos)
|
||
|
precNel3DMode= 0;
|
||
|
else if(tmp.find("direct3d")!=string::npos)
|
||
|
precNel3DMode= 1;
|
||
|
else
|
||
|
precNel3DMode= 2;
|
||
|
}
|
||
|
else if(str.compare(0, card3dIdTok.size(), card3dIdTok)==0)
|
||
|
{
|
||
|
string tmp= str;
|
||
|
if(tmp.find("NVIDIA")!=string::npos)
|
||
|
precCard3D= 0;
|
||
|
else if(tmp.find("RADEON")!=string::npos)
|
||
|
precCard3D= 1;
|
||
|
else
|
||
|
precCard3D= 2;
|
||
|
|
||
|
// END a block, add info in map (only if not repetition)
|
||
|
sint64 absTime= precLocalTime-precLocalTime2;
|
||
|
if(absTime<0) absTime= -absTime;
|
||
|
if( precSenderId!=precSenderId2 ||
|
||
|
absTime>sint64(60) )
|
||
|
{
|
||
|
senderMap[precSenderId].Val++;
|
||
|
shardMap[precShardId].Val++;
|
||
|
timeInGameMap[precTimeInGame].Val++;
|
||
|
patchVersionMap[precPatchVersion].Val++;
|
||
|
if(precNel3DMode!=0 && precNel3DMode!=1)
|
||
|
precNel3DMode= 2;
|
||
|
if(precCard3D!=0 && precCard3D!=1)
|
||
|
precCard3D= 2;
|
||
|
info3dMap[precNel3DMode*256+precCard3D].Val++;
|
||
|
userPosArray.push_back(precUserPos);
|
||
|
crashFileToSenderMap[fileNoDir][precSenderId].Val++;
|
||
|
senderToCrashFileMap[precSenderId][fileNoDir].Val++;
|
||
|
totalCrash++;
|
||
|
}
|
||
|
totalCrashDuplicate++;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// **** display Stats
|
||
|
// general stats
|
||
|
myinfo("**** Total: %d Crashs (%d with duplicates)", totalCrash, totalCrashDuplicate);
|
||
|
myinfo("NB: 'duplicates' means: crashs that are removed because suppose the player click 'ignore' (same sender/same Localtime, within about 1 min)");
|
||
|
|
||
|
// senderId
|
||
|
TStatMap::iterator it;
|
||
|
TStatStrMap::iterator itStr;
|
||
|
myinfo("");
|
||
|
myinfo("**** Stat Per Sender:");
|
||
|
multimap<uint, string> resortSender;
|
||
|
for(itStr=senderMap.begin();itStr!=senderMap.end();itStr++)
|
||
|
{
|
||
|
resortSender.insert(make_pair(itStr->second.Val, itStr->first));
|
||
|
}
|
||
|
multimap<uint, string>::iterator it2;
|
||
|
for(it2=resortSender.begin();it2!=resortSender.end();it2++)
|
||
|
{
|
||
|
myinfo("**** %d Crashs for UserId %s", it2->first, it2->second.c_str());
|
||
|
}
|
||
|
// shardId
|
||
|
myinfo("");
|
||
|
myinfo("**** Stat Per ShardId:");
|
||
|
for(it=shardMap.begin();it!=shardMap.end();it++)
|
||
|
{
|
||
|
myinfo("**** %d Crashs for ShardId %d", it->second.Val, it->first);
|
||
|
}
|
||
|
// timeInGame
|
||
|
myinfo("");
|
||
|
myinfo("**** Stat Per TimeInGame:");
|
||
|
for(it=timeInGameMap.begin();it!=timeInGameMap.end();it++)
|
||
|
{
|
||
|
myinfo("**** %d Crashs for TimeInGame %s", it->second.Val, it->first==0?"0h 0min 0sec":
|
||
|
(it->first==1?"<=5 min":
|
||
|
it->first==2?"> 5 min":"??? Bad parse ???"));
|
||
|
}
|
||
|
// infoPatch
|
||
|
myinfo("");
|
||
|
myinfo("**** Stat Per PatchVersion:");
|
||
|
for(it=patchVersionMap.begin();it!=patchVersionMap.end();it++)
|
||
|
{
|
||
|
myinfo("**** %d Crashs for PatchVersion %d", it->second.Val, it->first);
|
||
|
}
|
||
|
// info3d
|
||
|
myinfo("");
|
||
|
myinfo("**** Stat Per 3d Mode:");
|
||
|
for(it=info3dMap.begin();it!=info3dMap.end();it++)
|
||
|
{
|
||
|
uint card3d= it->first&255;
|
||
|
uint mode3d= it->first>>8;
|
||
|
|
||
|
myinfo("**** %d Crashs for %s / Card %s", it->second.Val,
|
||
|
mode3d==0?"OpenGL":(mode3d==1?"Direct3D":"??? No Driver ???"),
|
||
|
card3d==0?"NVIDIA":(card3d==1?"RADEON":"Misc"));
|
||
|
}
|
||
|
// crash by continent
|
||
|
{
|
||
|
// init cont info
|
||
|
CCrashCont crashCont[]=
|
||
|
{
|
||
|
// New First, because bbox may be included in Main bbox
|
||
|
CCrashCont("Matis Newb", 0,-5000,2800,-8500),
|
||
|
CCrashCont("Zorai Newb", 6500,-4000,9300,-6000),
|
||
|
CCrashCont("Trykr Newb", 20000,-32000,24000,-36000),
|
||
|
CCrashCont("Fyros Newb", 20500,-24500,24000,-27500),
|
||
|
CCrashCont("Matis Main", 0,0,6500,-8500),
|
||
|
CCrashCont("Zorai Main", 6500,0,13000,-6000),
|
||
|
CCrashCont("Trykr Main ", 13000,-29000,20000,-36000),
|
||
|
CCrashCont("Fyros Main ", 15000,-23000,20500,-27500)
|
||
|
};
|
||
|
uint numCont= sizeof(crashCont)/sizeof(crashCont[0]);
|
||
|
uint numNotFound= 0;
|
||
|
// count stats
|
||
|
uint i;
|
||
|
for(i=0;i<userPosArray.size();i++)
|
||
|
{
|
||
|
bool ok= false;
|
||
|
for(uint j=0;j<numCont;j++)
|
||
|
{
|
||
|
if(crashCont[j].testPos(userPosArray[i]))
|
||
|
{
|
||
|
ok= true;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
if(!ok)
|
||
|
numNotFound++;
|
||
|
}
|
||
|
myinfo("");
|
||
|
myinfo("**** Stat Per continent:");
|
||
|
// display stats
|
||
|
for(i=0;i<numCont;i++)
|
||
|
{
|
||
|
myinfo(" %s: %d", crashCont[i].Name.c_str(), crashCont[i].NumCrash);
|
||
|
}
|
||
|
myinfo(" NotFound: %d", numNotFound);
|
||
|
}
|
||
|
|
||
|
|
||
|
// **** display detailed Stats
|
||
|
myinfo("");
|
||
|
myinfo("");
|
||
|
myinfo("**************************");
|
||
|
myinfo("**************************");
|
||
|
myinfo("********* DETAIL *********");
|
||
|
myinfo("**************************");
|
||
|
myinfo("**************************");
|
||
|
myinfo("");
|
||
|
|
||
|
// Stats per User
|
||
|
myinfo("");
|
||
|
myinfo("**** Detailed Crashs per user:");
|
||
|
for(it2=resortSender.begin();it2!=resortSender.end();it2++)
|
||
|
{
|
||
|
string userId= it2->second;
|
||
|
TStatStrMap &crashFileMap= senderToCrashFileMap[userId];
|
||
|
if(crashFileMap.empty())
|
||
|
{
|
||
|
myinfo(" Error parsing Crashs for UserId %s (?????)", userId.c_str());
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
myinfo(" %d Crashs for %s:", it2->first, userId.c_str());
|
||
|
for(TStatStrMap::iterator it=crashFileMap.begin();it!=crashFileMap.end();it++)
|
||
|
{
|
||
|
myinfo(" %d in %s", it->second.Val, it->first.c_str());
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Stats per Crash File
|
||
|
myinfo("");
|
||
|
myinfo("**** Detailed Crashs per crash Log:");
|
||
|
multimap<uint, string> resortCrashLog;
|
||
|
for(TStatStrStrMap::iterator itStrStr=crashFileToSenderMap.begin();itStrStr!=crashFileToSenderMap.end();itStrStr++)
|
||
|
{
|
||
|
// count total crash instance
|
||
|
uint numCrash= 0;
|
||
|
TStatStrMap &userIdMap= itStrStr->second;
|
||
|
for(TStatStrMap::iterator it=userIdMap.begin();it!=userIdMap.end();it++)
|
||
|
numCrash+= it->second.Val;
|
||
|
// insert for resort by this number
|
||
|
resortCrashLog.insert(make_pair(numCrash, itStrStr->first));
|
||
|
}
|
||
|
for(it2=resortCrashLog.begin();it2!=resortCrashLog.end();it2++)
|
||
|
{
|
||
|
string crashLog= it2->second;
|
||
|
TStatStrMap &userIdMap= crashFileToSenderMap[crashLog];
|
||
|
if(userIdMap.empty())
|
||
|
{
|
||
|
myinfo(" Error parsing Crashs for CrashFile %s (?????)", crashLog.c_str());
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
myinfo(" %d Crashs in %s:", it2->first, crashLog.c_str());
|
||
|
for(TStatStrMap::iterator it=userIdMap.begin();it!=userIdMap.end();it++)
|
||
|
{
|
||
|
myinfo(" %d for %s", it->second.Val, it->first.c_str());
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// RAW userPos
|
||
|
myinfo("");
|
||
|
myinfo("**** RAW Crashs Pos (copy in excel, and use insert/chart/(X/Y)Scatter):");
|
||
|
for(uint i=0;i<userPosArray.size();i++)
|
||
|
{
|
||
|
myinfo("%.2f\t%.2f\t%.2f", userPosArray[i].x, userPosArray[i].y, userPosArray[i].z);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// ***************************************************************************
|
||
|
int main(int argc, char *argv[])
|
||
|
{
|
||
|
bool ok= false;
|
||
|
bool statMode= false;
|
||
|
bool filterMode= false;
|
||
|
if(argc == 3 && argv[2]==string("-s"))
|
||
|
ok= true,statMode= true;
|
||
|
if(argc >= 5 && argv[2]==string("-p"))
|
||
|
ok= true,filterMode= true;
|
||
|
|
||
|
if(!ok)
|
||
|
{
|
||
|
myinfo("Usage1 (stats):\n\t%s src_dir -s\n", CFile::getFilename(argv[0]).c_str());
|
||
|
myinfo("Usage2 (patch filter):\n\t%s src_dir -p patch_version dst_dir [specialFilter]\n", CFile::getFilename(argv[0]).c_str());
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
if(!CFile::isDirectory(argv[1]))
|
||
|
{
|
||
|
myinfo("%s is not a directory", argv[1]);
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
if(filterMode)
|
||
|
{
|
||
|
string specialFilter;
|
||
|
if(argc>=6)
|
||
|
specialFilter= argv[5];
|
||
|
filterRyzomBug(argv[1], argv[4], atoi(argv[3]), specialFilter);
|
||
|
}
|
||
|
else if(statMode)
|
||
|
{
|
||
|
statRyzomBug(argv[1]);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|