//////////////////////////////////////////////////////////////////////////
//
//this file is part of eMule
//Copyright (C)2002-2006 WiZaRd ( thewizardofdos@gmail.com )
//
//This program is free software; you can redistribute it and/or
//modify it under the terms of the GNU General Public License
//as published by the Free Software Foundation; either
//version 2 of the License, or (at your option) any later version.
//
//This program is distributed in the hope that it will be useful,
//but WITHOUT ANY WARRANTY; without even the implied warranty of
//MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//GNU General Public License for more details.
//
//You should have received a copy of the GNU General Public License
//along with this program; if not, write to the Free Software
//Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.


//////////////////////////////////////////////////////////////////////////
//About ClientAnalyzer:
//---------------------
//The purpose of this class is to provide an interface that is controlling all
//kinds of clients actions in eMule and to provide a score determined by the actions of each client
//That way, Modders won't need to update an "antileech" database file regularly with
//strings/opcodes or other things that are loose guesses about a client but just implement
//that class so leechers will be detected by their ACTIONS
//
//
//A few examples:
//---------------
//
//A releaser uses a mod with kick/ban/PS on partfiles and so on - would/should you ban him?
//Yes you WOULD with default methods but no, you SHOULD not ban that client!
//He is RELEASING, he is supporting the network and increasing its health...
//But as you can't destinguish between "good" clients using that mod (aka releases)
//and "bad" clients using the same mod (aka leechers) you would ban him/punish him on sight
//(though the latter should be forbidden iMHO - still waiting for a statement by the devs)
//
//A client uses a leecher mod by a modder with a brain of > half the size of a walnut,
//which means he won't send a string/opcode/something to identify himself - what to do?
//Default methods fail miserably in such cases... and the default CS is too exploitable
//
//A client uses a leecher mod which spams the network - what to do?
//Using default methods you are required to release a new filterfile over and over again...
//The class will use the user editable blocklist and remember good and bad messages automatically
//
//
//So, a final conclusion:
//-----------------------
//The class will NOT (NEVER!) need any updates in its final state!
//The class will AUTOMATICALLY collect and use data from each client!
//The class will REMEMBER that data and store it in a antileech.met file!
//The class will work ONLY by analyzing the BEHAVIOUR of a client to us!
//That means it is ABSOLUTELY following the rules and maybe even worth to be used officially!
//
//////////////////////////////////////////////////////////////////////////

//////////////////////////////////////////////////////////////////////////
// CHANGELOG
//////////////////////////////////////////////////////////////////////////
//
//v1.0:		first version as discussed on emfuture and other boards
//
//v1.1:		2 changes:
//-			UL/DL can only lower the score to 1/2 of the base score [SEPPL]
//-			No boost for higher reask times [CICCIOBASTARDO]
//
//v1.2:		2 BuGFiXes:
//-			ReaskTime was not evaluated and assigned correctly
//-			ReaskTime punisher was not calculated by MINUTES but by SECONDS!
//
//v1.3:		8 changes / 2 BuGFiXes:
//-			XS evaluation now uses the official system though slightly changed
//-			XS asks will only be counted for not too rare files
//-			We will also answer to XS exploiters to prevent entering a deadlock situation
//			when both clients will think the other one is an exploiter and noone will send anymore
//-			EVERY bad action now reduces the score - this will punish VERY bad clients harder
//-			Slow XS asks won't stack up credits [CICCIOBASTARDO]
//-			BuGFiX: fix in default initialization
//-			BuGFiX: the first connect/reask was not counted (reaskcounter = 0) but avgtime was set
//-			Added a "bad action counter" - for Devs and interested... not evaluated, yet
//-			Major change: rare file UL/DL is counted separately and weighted differently
//-			Major change: the "first contact" is saved and a client gets a bonus for every week
//						  he keeps his hash to encourage users to keep it and to punish hashchangers
//v1.4:		16 changes / 1 BuGFiX
//-			Credits are merged if no data is present
//-			Force one score calculation on startup
//-			New function "IsBadGuy()" - can be used to draw an icon or something...
//-			Made some functions non-static using the parent pointer
//-			Only use the UL/DL treshold as a punisher for clients that have something for us
//-         Fast Reask treshold will only be used after collecting 3 reasks
//-         XS spam/exploit check changed due to buggy implementation (***/WiZ)
//-			XS exploiters will only be checked after 10 reasks
//-			Punishment for Nick- and/or ModThieves reduced to 3*AT_MIDPUNISH
//-			Combined punishment for UDP-FNF and/or FileFakers
//-			Changed logging to an uniform standard
//-			Added missing initialisation for the badaction counter
//-			tweaked some functions to not call ReCheckScore() if it wasn't needed
//-			replaced the memset command by md4clr for the hashkey
//-			Major change: met header changed because 64 bit size data is now written to a single tag
//-			changed some types to more appropriate ones
//-			BuGFiX: fixed the incorrect badaction counter (crash on exit)
//
//////////////////////////////////////////////////////////////////////////

#include "stdafx.h"
#include "emule.h"
#include "ClientAnalyzer.h"
#include "Preferences.h"
#include "SafeFile.h"
#include "Opcodes.h"
#include "Packets.h"
#include "emuledlg.h"
#include "Log.h"
#include "Modname.h"
#include "updownclient.h"
#include "Version.h"
#include "ClientCredits.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[]=__FILE__;
#endif

// By Taz - ClientAnalyzer
#ifdef CLIENTANALYZER

CAntiLeechData::CAntiLeechData(CFileDataIO* file)
{
	Init();

	file->ReadHash16(m_pData->abyKey);
	UINT tagcount = file->ReadUInt32();
	for (UINT j = 0; j < tagcount; ++j)
	{
		CTag* newtag = new CTag(file, false);
		switch(newtag->GetNameID())
		{
//>>> RARE files
			case AT_UPLOAD_RARE:
			//case AT_UPLOAD_RARE_LO:
			{
				if (newtag->IsInt64(false))
				{
					m_pData->uSentBytesRare = newtag->GetInt64();
				}
				else if (newtag->IsInt())
					((UINT*)&m_pData->uSentBytesRare)[0] = newtag->GetInt();
				delete newtag;
				break;
			}
			case AT_UPLOAD_RARE_HI:
			{
				if (newtag->IsInt())
					((UINT*)&m_pData->uSentBytesRare)[1] = newtag->GetInt();
				delete newtag;
				break;
			}
			case AT_DOWNLOAD_RARE:
			//case AT_DOWNLOAD_RARE_LO:
			{
				if (newtag->IsInt64(false))
				{
					m_pData->uGotBytesRare = newtag->GetInt64();
				}
				else if (newtag->IsInt())
					((UINT*)&m_pData->uGotBytesRare)[0] = newtag->GetInt();
				delete newtag;
				break;
			}
			case AT_DOWNLOAD_RARE_HI:
			{
				if (newtag->IsInt())
					((UINT*)&m_pData->uGotBytesRare)[1] = newtag->GetInt();
				delete newtag;
				break;
			}
//<<< RARE files
			case AT_UPLOAD_PART:
			//case AT_UPLOAD_PART_LO:
			{
				if (newtag->IsInt64(false))
				{
					m_pData->uSentBytesPartial = newtag->GetInt64();
				}
				else if (newtag->IsInt())
					((UINT*)&m_pData->uSentBytesPartial)[0] = newtag->GetInt();
				delete newtag;
				break;
			}
			case AT_UPLOAD_PART_HI:
			{
				if (newtag->IsInt())
					((UINT*)&m_pData->uSentBytesPartial)[1] = newtag->GetInt();
				delete newtag;
				break;
			}
			case AT_UPLOAD:
			//case AT_UPLOAD_LO:
			{
				if (newtag->IsInt64(false))
				{
					m_pData->uSentBytes = newtag->GetInt64();
				}
				else if (newtag->IsInt())
					((UINT*)&m_pData->uSentBytes)[0] = newtag->GetInt();
				delete newtag;
				break;
			}
			case AT_UPLOAD_HI:
			{
				if (newtag->IsInt())
					((UINT*)&m_pData->uSentBytes)[1] = newtag->GetInt();
				delete newtag;
				break;
			}


			case AT_DOWNLOAD_PART:
			//case AT_DOWNLOAD_PART_LO:
			{
				if (newtag->IsInt64(false))
				{
					m_pData->uGotBytesPartial = newtag->GetInt64();
				}
				else if (newtag->IsInt())
					((UINT*)&m_pData->uGotBytesPartial)[0] = newtag->GetInt();
				delete newtag;
				break;
			}
			case AT_DOWNLOAD_PART_HI:
			{
				if (newtag->IsInt())
					((UINT*)&m_pData->uGotBytesPartial)[1] = newtag->GetInt();
				delete newtag;
				break;
			}
			case AT_DOWNLOAD:
			//case AT_DOWNLOAD_LO:
			{
				if (newtag->IsInt64(false))
				{
					m_pData->uGotBytes = newtag->GetInt64();
				}
				else if (newtag->IsInt())
					((UINT*)&m_pData->uGotBytes)[0] = newtag->GetInt();
				delete newtag;
				break;
			}
			case AT_DOWNLOAD_HI:
			{
				if (newtag->IsInt())
					((UINT*)&m_pData->uGotBytes)[1] = newtag->GetInt();
				delete newtag;
				break;
			}


			case AT_LASTSEEN:
			{
				if (newtag->IsInt())
					m_pData->dwLastSeen = newtag->GetInt();
				delete newtag;
				break;
			}
			case AT_FIRSTMET:
			{
				if (newtag->IsInt())
					m_pData->dwFirstMet = newtag->GetInt();
				delete newtag;
				break;
			}

			case AT_REASKS:
			{
				if(newtag->IsInt())
					m_pData->uReasks = newtag->GetInt();
				delete newtag;
				break;
			}
			case AT_AVGREASK:
			{
				if(newtag->IsInt())
					m_pData->uAvgReaskTime = newtag->GetInt();
				delete newtag;
				break;
			}
			case AT_SPAMS:
			{
				if(newtag->IsInt())
					m_pData->iBlockedMessages = newtag->GetInt();
				delete newtag;
				break;
			}
			case AT_XS_ASKS:
			{
				if(newtag->IsInt())
					m_pData->uXSAsks = newtag->GetInt();
				delete newtag;
				break;
			}
			case AT_XS_ANSW:
			{
				if(newtag->IsInt())
					m_pData->uXSAnsw = newtag->GetInt();
				delete newtag;
				break;
			}
			case AT_FASTXS_ASKS:
			{
				if(newtag->IsInt())
					m_pData->iFastXSAsks = newtag->GetInt();
				delete newtag;
				break;
			}

			//hmmm - strange tags?
			default:
			{
				taglist.Add(newtag);
				break;
			}
		}
	}

	//Failsafe:
	if(m_pData->uAvgReaskTime == 0)
		m_pData->uReasks = 0;

	ReCheckScore(); //perform the first check
}

CAntiLeechData::CAntiLeechData(const uchar* key)
{
	Init();
	md4cpy(m_pData->abyKey, key);
}

CAntiLeechData::~CAntiLeechData()
{
	while(!taglist.IsEmpty())
	{
		delete taglist[0];
		taglist.RemoveAt(0);
	}

	delete m_pData;
}

void CAntiLeechData::Init()
{
	//create a new datastruct and be sure to reset all values
	m_pData = new CAntiLeechStruct;
	memset(m_pData, 0, sizeof(CAntiLeechStruct));
	//memset(m_pData->abyKey, 0, sizeof(m_pData->abyKey));
	md4clr(m_pData->abyKey);
	bBadForThisSession = NULL;
	m_fLastScore = 1.0f;
	SetLastSeen();
	SetFirstMet();
	ReCheckScore();
	SetParent(NULL);
}

bool CAntiLeechData::IsEmpty() const
{
	//even if we have no data, we save up to 150 days!
	const DWORD dwExpired = time(NULL) - 12960000; // today - 150 day
	return(GetFirstMet() < dwExpired
		&& m_pData->uReasks == 0
		&& m_pData->iBlockedMessages < 0
		&& m_pData->uXSAsks == 0
		&& m_pData->uXSAnsw == 0
		&& m_pData->iFastXSAsks < 0
		&& m_pData->uSentBytes == 0
		&& m_pData->uGotBytes == 0);
}

void CAntiLeechData::WriteToFile(CFileDataIO* file)
{
	file->WriteHash16(m_pData->abyKey);

	UINT uTagCount = 0;
	ULONG uTagCountFilePos = (ULONG)file->GetPosition();
	file->WriteUInt32(uTagCount);

	// standard tags
	if (m_pData->uSentBytesRare)
	{
		CTag(AT_UPLOAD_RARE, m_pData->uSentBytesRare, true).WriteTagToFile(file);
		++uTagCount;
	}
	if (m_pData->uSentBytesPartial)
	{
		CTag(AT_UPLOAD_PART, m_pData->uSentBytesPartial, true).WriteTagToFile(file);
		++uTagCount;
	}
	if(m_pData->uSentBytes)
	{
		CTag(AT_UPLOAD, m_pData->uSentBytes, true).WriteTagToFile(file);
		++uTagCount;
	}

	if (m_pData->uGotBytesRare)
	{
		CTag(AT_DOWNLOAD_RARE, m_pData->uGotBytesRare, true).WriteTagToFile(file);
		++uTagCount;
	}
	if (m_pData->uGotBytesPartial)
	{
		CTag(AT_DOWNLOAD_PART, m_pData->uGotBytesPartial, true).WriteTagToFile(file);
		++uTagCount;
	}
	if (m_pData->uGotBytes)
	{
		CTag(AT_DOWNLOAD, m_pData->uGotBytes, true).WriteTagToFile(file);
		++uTagCount;
	}


	CTag(AT_LASTSEEN, m_pData->dwLastSeen).WriteTagToFile(file);
	++uTagCount;
	CTag(AT_FIRSTMET, m_pData->dwFirstMet).WriteTagToFile(file);
	++uTagCount;


	if(m_pData->uReasks) //don't save "unneeded" tags
	{
		CTag(AT_REASKS, m_pData->uReasks).WriteTagToFile(file);
		++uTagCount;

		CTag(AT_AVGREASK, m_pData->uAvgReaskTime).WriteTagToFile(file);
		++uTagCount;
	}
	if(m_pData->iBlockedMessages > 0) //don't save "unneeded" tags
	{
		CTag(AT_SPAMS, m_pData->iBlockedMessages).WriteTagToFile(file);
		++uTagCount;
	}
	if(m_pData->uXSAsks > m_pData->uXSAnsw) //don't save "unneeded" tags
	{
		CTag(AT_XS_ASKS, m_pData->uXSAsks).WriteTagToFile(file);
		++uTagCount;

		CTag(AT_XS_ANSW, m_pData->uXSAnsw).WriteTagToFile(file);
		++uTagCount;
	}
	if(m_pData->iFastXSAsks > 0) //don't save "unneeded" tags
	{
		CTag(AT_FASTXS_ASKS, m_pData->iFastXSAsks).WriteTagToFile(file);
		++uTagCount;
	}

	// unidentified tags (for future versions)
	for (int j = 0; j < taglist.GetCount(); ++j)
	{
		taglist[j]->WriteTagToFile(file);
		++uTagCount;
	}

	file->Seek(uTagCountFilePos, CFile::begin);
	file->WriteUInt32(uTagCount);
	file->Seek(0, CFile::end);
}


//////////////////////////////////////////////////////////////////////////
// TRANSFER STATS - UPLOAD
void CAntiLeechData::AddUploaded(const uint64& bytes, const bool bPartial, const UINT srccount)
{
	if(bPartial)
		m_pData->uSentBytesPartial += bytes;
	if(srccount < AT_RAREFILE)
		m_pData->uSentBytesRare += bytes;
	m_pData->uSentBytes += bytes;
	ReCheckScore();
}

uint64  CAntiLeechData::GetUploadedRare() const
{
	return m_pData->uSentBytesRare;
}

uint64	CAntiLeechData::GetUploadedPartial() const
{
	return m_pData->uSentBytesPartial;
}

uint64	CAntiLeechData::GetUploadedFull() const
{
	return m_pData->uSentBytes - m_pData->uSentBytesPartial;
}

//For Xman and other who can not read properly: THIS is the data WE SENT!
//Rare data is counted TWICE as is partial data as we can expect something for that in return!
uint64	CAntiLeechData::GetUploadedTotal() const
{
	return GetUploadedFull()
		+ uint64(AT_PARTFILEWEIGHT * m_pData->uSentBytesPartial)
		+ uint64(AT_RARESHAREWEIGHT * m_pData->uSentBytesRare);
}


//////////////////////////////////////////////////////////////////////////
// TRANSFER STATS - DOWNLOAD
void CAntiLeechData::AddDownloaded(const uint64& bytes, const bool bPartial, const UINT srccount)
{
	if(bPartial)
		m_pData->uGotBytesPartial += bytes;
	if(srccount < AT_RAREFILE)
		m_pData->uGotBytesRare += bytes;
	m_pData->uGotBytes += bytes;
	ReCheckScore();
}

uint64	CAntiLeechData::GetDownloadedRare() const
{
	return m_pData->uGotBytesRare;
}

uint64	CAntiLeechData::GetDownloadedPartial() const
{
	return m_pData->uGotBytesPartial;
}

uint64	CAntiLeechData::GetDownloadedFull() const
{
	return m_pData->uGotBytes - m_pData->uGotBytesPartial;
}

//For Xman and other who can not read properly: THIS is the data WE GOT!
//Rare data is counted TWICE as is FULL data as we should payback such nice behaviour!
uint64	CAntiLeechData::GetDownloadedTotal() const
{
	//TODO: rare stuff!
	return uint64(AT_FULLFILEWEIGHT * GetDownloadedFull())
		+ m_pData->uGotBytesPartial
		+ uint64(AT_RARESHAREWEIGHT * m_pData->uGotBytesRare);
}

//////////////////////////////////////////////////////////////////////////
// ANTI SPAM
void	CAntiLeechData::AddSpam()
{
	++m_pData->iBlockedMessages;
	if(parent && thePrefs.GetLogAnalyzerEvents())
		DebugLog(LOG_CA | LOG_WARNING, L"Analyzer: Spamcounter for (%s) increased: %i", parent->DbgGetClientInfo(), m_pData->iBlockedMessages);
	theApp.antileechlist->IncBadActionCounter(BAD_ACTION_SPAMS);
	ReCheckScore();
}

void	CAntiLeechData::DecSpam()
{
	//we do not have to recheck the score if this guys isn't punished, yet
	//counter-1 = 0 -> no spam :)
	//counter-1 > 0 -> spammer, but reduced - check needed
	const bool bCheckNeeded = m_pData->iBlockedMessages > 0;
	--m_pData->iBlockedMessages;
	//if(parent && thePrefs.GetLogAnalyzerEvents())
	//	AddDebugLogLine(false, L"Analyzer: Spamcounter for (%s) decreased: %i", parent->DbgGetClientInfo(), m_pData->iBlockedMessages);
	if(bCheckNeeded)
		ReCheckScore();
}

int		CAntiLeechData::GetSpams() const
{
	return m_pData->iBlockedMessages;
}

//////////////////////////////////////////////////////////////////////////
// ANTI FAST REASK
bool	CAntiLeechData::IsFastAskClient() const
{
	return GetReaskCount() > 2
		&& GetAvgReaskTime()
		&& (GetAvgReaskTime() + AT_REASKBUFFER - FILEREASKTIME)/60000.0f < 0;
}

void	CAntiLeechData::AddReask(const UINT time)
{
	const bool bBadTimeDiff1 = ((GetAvgReaskTime() + AT_REASKBUFFER - FILEREASKTIME)/60000.0f)<0;
	m_pData->uAvgReaskTime = ((m_pData->uAvgReaskTime*m_pData->uReasks)+time)/(++m_pData->uReasks);
	const bool bBadTimeDiff2 = ((GetAvgReaskTime() + AT_REASKBUFFER - FILEREASKTIME)/60000.0f)<0;
	if(parent && thePrefs.GetLogAnalyzerEvents() && bBadTimeDiff1 != bBadTimeDiff2) //log only if needed
		DebugLog(LOG_CA | LOG_WARNING, L"Analyzer: Reask for (%s) updated: %i reasks @ %s avg", parent->DbgGetClientInfo(), m_pData->uReasks, CastSecondsToHM(m_pData->uAvgReaskTime/1000));
	if(time < FILEREASKTIME)
		theApp.antileechlist->IncBadActionCounter(BAD_ACTION_FASTREASK);
	ReCheckScore();
}

UINT	CAntiLeechData::GetAvgReaskTime() const
{
	return m_pData->uAvgReaskTime;
}

UINT	CAntiLeechData::GetReaskCount() const
{
	return m_pData->uReasks;
}

//////////////////////////////////////////////////////////////////////////
// ANTI XS-EXPLOIT/SPAM
bool	CAntiLeechData::IsXSSpammer() const
{
	return m_pData->iFastXSAsks > AT_XSSPAM_LEVEL;
}

void	CAntiLeechData::IncFastXSCounter()
{
	++m_pData->iFastXSAsks;
	if(parent && thePrefs.GetLogAnalyzerEvents())
		DebugLog(LOG_CA | LOG_WARNING, L"Analyzer: FastXS for (%s) increased: %i", parent->DbgGetClientInfo(), m_pData->iFastXSAsks);
	theApp.antileechlist->IncBadActionCounter(BAD_ACTION_FASTXS);
	ReCheckScore();
}

void	CAntiLeechData::DecFastXSCounter()
{
	//we do not have to recheck the score if this guys isn't punished, yet
	//counter-1 = 0 -> no fast XS anymore :)
	//counter-1 > 0 -> fast XS :( - check needed
	//[CICCIOBASTARDO] - Don't stack up credits for slow XS asks
	if(m_pData->iFastXSAsks > 0)
	{
		--m_pData->iFastXSAsks;
		//if(parent && thePrefs.GetLogAnalyzerEvents())
		//	AddDebugLogLine(false, L"Analyzer: FastXS for (%s) decreased: %i", parent->DbgGetClientInfo(), m_pData->iFastXSAsks);
		ReCheckScore();
	}
}

void	CAntiLeechData::IncXSAsks()
{
	bool b = IsXSExploiter();
	++m_pData->uXSAsks;
	//if(parent && thePrefs.GetLogAnalyzerEvents())
	//	AddDebugLogLine(false, L"XS Asks for (%s) increased: %i", parent->DbgGetClientInfo(), m_pData->uXSAsks);
	if(b != IsXSExploiter())
	{
		if(parent && thePrefs.GetLogAnalyzerEvents())
			DebugLog(LOG_CA | LOG_ERROR, L"Analyzer: (%s) reached XS exploiter level: %u : %u", parent->DbgGetClientInfo(), m_pData->uXSAsks, m_pData->uXSAnsw);
		ReCheckScore();
	}
}

void	CAntiLeechData::IncXSAnsw()
{
	bool b = IsXSExploiter();
	++m_pData->uXSAnsw;
	//if(parent && thePrefs.GetLogAnalyzerEvents())
	//	AddDebugLogLine(false, L"Analyzer: XS Answs for (%s) increased: %i", parent->DbgGetClientInfo(), m_pData->uXSAnsw);
	if(b != IsXSExploiter())
	{
		if(parent && thePrefs.GetLogAnalyzerEvents())
			DebugLog(LOG_CA | LOG_SUCCESS, L"Analyzer: (%s) left XS exploiter level: %u : %u", parent->DbgGetClientInfo(), m_pData->uXSAsks, m_pData->uXSAnsw);
		ReCheckScore();
	}
}

bool	CAntiLeechData::IsXSExploiter()	const
{
	return (m_pData->uXSAsks > AT_XSEXPLOIT_LEVEL && m_pData->uXSAsks > AT_XSEXPLOIT_PERCENT*m_pData->uXSAnsw);
}

//////////////////////////////////////////////////////////////////////////
// SCORE CALCULATION
float CAntiLeechData::GetScore()
{
	if(!m_bCheckScore)
		return m_fLastScore;
	m_bCheckScore = false;

	float cur_score = AT_BASESCORE;

	//VOODOO 2 DO :)
	//number of chunks we downloaded
	const uint64 chunksDL = uint64(double(GetDownloadedTotal()/PARTSIZE) + AT_ROUNDOFFSET);
	cur_score += chunksDL * AT_MIDPUNISH;
	//number of chunks we upped
	const uint64 chunksUL = uint64(double(GetUploadedTotal()/PARTSIZE) + AT_ROUNDOFFSET);
	cur_score -= chunksUL * AT_MIDPUNISH;

	//number of weeks we know this client
	const DWORD dwExpired = (time(NULL) - GetFirstMet()) / 604800;
	cur_score += dwExpired * AT_LOWPUNISH;

	//[SEPPL] - Don't let the score fall too low because of UL/DL
	if(parent
		&& (!parent->GetRequestFile() || parent->GetDownloadState() > DS_ONQUEUE))
	{
		//Keep an even score with clients who do not have something for us
		if(cur_score < AT_BASESCORE)
			cur_score = AT_BASESCORE;
	}
	//[WiZaRd] - but only for clients who can't "pay back"
	/*else
	{
		if(cur_score < AT_BASESCORE / 2)
		cur_score = AT_BASESCORE / 2;
	}*/

	if(GetSpams() > 0)
		cur_score -= GetSpams() * AT_LOWPUNISH;
	if(IsXSSpammer())
		cur_score -= m_pData->iFastXSAsks * AT_LOWPUNISH;
	cur_score -= IsXSExploiter() ? AT_HIPUNISH : 0.0f;

	if(GetReaskCount() > 2 && GetAvgReaskTime())
	{
		//BuGFiX: e.g. 22min avg. + 5 = 27 - 29 = -2 --> -2*2.5f punishment
		const float fTimeDiff = (GetAvgReaskTime() + AT_REASKBUFFER - FILEREASKTIME)/60000.0f; //BuGFiX: how could I miss that!? :confused:
		//[CICCIOBASTARDO] - Don't reward a higher reask
		if(fTimeDiff < 0)
		//idea, just written down... punish clients who asked in such short times OFTEN harder than others!
		//	cur_score += fTimeDiff * AT_LOWPUNISH;
		//ok let's try that idea :)
			cur_score += GetReaskCount() * AT_LOWPUNISH;
	}

	//really bad!
	if(bBadForThisSession != NULL)
	{
		//NickThieves and ModThieves are commonly used...
		//and pretty often used both in one mod, so don't punish them too hard
		if(bBadForThisSession & AT_NICKTHIEF || bBadForThisSession & AT_MODTHIEF)
			cur_score -= 3*AT_MIDPUNISH; //= 3 chunks needed to balance
		//this is also a pretty common combination... but it is much worse so punish them harder
		if(bBadForThisSession & AT_FILEFAKER || bBadForThisSession & AT_UDPFNFFAKER)
			cur_score -= AT_HIPUNISH; //= 5 chunks needed to balance
	}

	//Cap the score
	if(cur_score < AT_MINSCORE)
		cur_score = AT_MINSCORE;
	else if(cur_score > AT_MAXSCORE)
		cur_score = AT_MAXSCORE;

	m_fLastScore = cur_score / AT_BASESCORE;
	return m_fLastScore;
}

void CAntiLeechData::SetBadForThisSession(const UINT i, const CString& strDetail)
{
	if(parent && (bBadForThisSession&i)==NULL)
	{
		CString reason = L"none";
		if(i == AT_NICKTHIEF)
		{
			theApp.antileechlist->IncBadActionCounter(BAD_ACTION_NICKTHIEF);
			reason = L"NickThief";
		}
		else if(i == AT_MODTHIEF)
		{
			theApp.antileechlist->IncBadActionCounter(BAD_ACTION_MODTHIEF);
			reason = L"ModThief";
		}
		else if(i == AT_FILEFAKER)
		{
			theApp.antileechlist->IncBadActionCounter(BAD_ACTION_FILEFAKE);
			reason = L"FileFaker";
		}
		else if(i == AT_UDPFNFFAKER)
		{
			theApp.antileechlist->IncBadActionCounter(BAD_ACTION_UDPFNF);
			reason = L"UDPFNFFaker";
		}
		else
			ASSERT(0); //this should not happen...
		if(parent && thePrefs.GetLogAnalyzerEvents())
		{
			if(!strDetail.IsEmpty())
				reason.AppendFormat(L" - %s", strDetail);
			DebugLog(LOG_CA | LOG_ERROR, L"Analyzer: Marked as %s: (%s)", reason, parent->DbgGetClientInfo());
		}
	}
	bBadForThisSession |= i;
	ReCheckScore();
}

void CAntiLeechData::ClearBadForThisSession(const UINT i)
{
	if(parent && (bBadForThisSession&i)!=NULL)
	{
		CString reason = L"none";
		if(i == AT_NICKTHIEF)
			reason = L"NickThief";
		else if(i == AT_MODTHIEF)
			reason = L"ModThief";
		else if(i == AT_FILEFAKER)
			reason = L"FileFaker";
		else if(i == AT_UDPFNFFAKER)
			reason = L"UDPFNFFaker";
		if(parent && thePrefs.GetLogAnalyzerEvents())
			DebugLog(LOG_CA | LOG_SUCCESS, L"Analyzer: Cleared from %s state: (%s)", reason, parent->DbgGetClientInfo());
	}
	bBadForThisSession &= ~i;
	ReCheckScore();
}

bool CAntiLeechData::GetBadForThisSession(const UINT i) const
{
	return (bBadForThisSession & i) != NULL;
}

//////////////////////////////////////////////////////////////////////////
// ANTINICKTHIEF
//
//Remark!
//Other than other modders (nonleecher of course) believe it is NOT recommended
//to create a tag containing other chars than letters only... just look up a copy of
//the nickthief code to see why...

#define MIN_LENGTH	4						//min chars to use
#define MAX_LENGTH	8						//max chars to use
#if (MIN_LENGTH > MAX_LENGTH)
#error MAX_LENGTH MUST BE GREATER THAN OR EQUAL TO MIN_LENGTH
#endif
#define MAX_ADD		(MAX_LENGTH-MIN_LENGTH) //max chars to add
CString	CAntiLeechData::m_sAntiNickThiefTag;
CString	CAntiLeechData::m_sAntiNickThiefTag2;
void CAntiLeechData::CreateAntiNickThiefTag()
{
	CString nick = L"";
	uint8 maxchar = uint8(MIN_LENGTH+rand()%MAX_ADD);
	_TINT ch;
	for(uint8 i = 0; i < maxchar; ++i)
	{
		ch = (rand()%2 ? L'A' : L'a') + (_TINT)rand()%25;
		nick.AppendChar(ch);
	}
	m_sAntiNickThiefTag = nick;

	nick = L"";
	maxchar = uint8(MIN_LENGTH+rand()%MAX_ADD);
	for(uint8 i = 0; i < maxchar; ++i)
	{
		ch = (rand()%2 ? L'A' : L'a') + (_TINT)rand()%25;
		nick.AppendChar(ch);
	}
	m_sAntiNickThiefTag2 = nick;
}

CString	CAntiLeechData::GetAntiNickThiefNick()
{
	CString ret;

	CString tag1;
	CString tag2;
	if(rand()%2 == 0)
	{
		switch(rand()%2)
		{
			case 0:
				tag1.Format(L"(%s)", m_sAntiNickThiefTag);
				break;
			default:
			case 1:
				tag1.Format(L"{%s}", m_sAntiNickThiefTag);
				break;
		}
		tag2.Format(L"[%s]", m_sAntiNickThiefTag2);
	}
	else
	{
		switch(rand()%2)
		{
			case 0:
				tag1.Format(L"(%s)", m_sAntiNickThiefTag2);
				break;
			default:
			case 1:
				tag1.Format(L"{%s}", m_sAntiNickThiefTag2);
				break;
		}
		tag2.Format(L"[%s]", m_sAntiNickThiefTag);
	}
	ret.Format(L"%s %s %s", tag1, thePrefs.GetUserNick(), tag2);
	return ret;
}

void CAntiLeechData::Check4NickThief()
{
	if(parent == NULL)
	{
		ASSERT(0);
		return;
	}

	//you can add a switch here...
	CString nick = L"";
	if(!parent->GetUserName()
		|| (nick = parent->GetUserName()).IsEmpty())
	{
		ClearBadForThisSession(AT_NICKTHIEF);
		return; //just a sanity check
	}

	const bool bHit1 = nick.Find(m_sAntiNickThiefTag) != -1;
	const bool bHit2 = nick.Find(m_sAntiNickThiefTag2) != -1;
	//only changed the logs for now... we *could* give it different weight, though...
	if(bHit1 && bHit2)
	{
		SetBadForThisSession(AT_NICKTHIEF, L"double tag");
	}
	else if(bHit1 || bHit2)
	{
		SetBadForThisSession(AT_NICKTHIEF, L"simple tag");
	}
	else
	{
		//there are some wise guys out there I think...
		//(Torni, is that you? *lol*)
		nick.MakeLower();
		CString tofind = m_sAntiNickThiefTag;
		CString tofind2 = m_sAntiNickThiefTag2;
		tofind.MakeLower();
		tofind2.MakeLower();
		//just add a logline for now...
		//no punish them - but only if both cases are true (very unlikely...)
		const bool bHit1 = nick.Find(tofind) != -1;
		const bool bHit2 = nick.Find(tofind2) != -1;
		if(bHit1 && bHit2)
		{
			SetBadForThisSession(AT_NICKTHIEF, L"wrong case double tag");
			//if(parent && thePrefs.GetLogAnalyzerEvents())
			//	DebugLogWarning(L"Analyzer: there seems to be a wise guy... (%s)"), parent->DbgGetClientInfo());
		}
		else if(bHit1 || bHit2)
		{
			SetBadForThisSession(AT_NICKTHIEF, L"wrong case single tag");
		}
		else
			ClearBadForThisSession(AT_NICKTHIEF);
	}
}

//////////////////////////////////////////////////////////////////////////
// ANTIMODTHIEF
CString	CAntiLeechData::m_sMyVersion;
void CAntiLeechData::Check4ModThief()
{
	if(parent == NULL)
	{
		ASSERT(0);
		return;
	}

	//you can add a switch here...
	if(parent->GetClientModVer().IsEmpty())
	{
		ClearBadForThisSession(AT_MODTHIEF);
		return;
	}

	const CString OurMod = MOD_VERSION; //cache it
	if(StrStrI(parent->GetClientModVer(), OurMod)) //uses our string
	{
		CString strDetail = L"";
		//but not the correct length
		if(parent->GetClientModVer().GetLength() != OurMod.GetLength())
			strDetail.Format(L"invalid length (%i != %i)", parent->GetClientModVer().GetLength(), OurMod.GetLength());
		//but uses a wrong eMule version
		else if(parent->GetClientSoftVer() != m_sMyVersion)
			strDetail.Format(L"invalid version (%s != %s)", parent->GetClientSoftVer(), m_sMyVersion);
		//but not in correct case
		else if(_tcsncmp(parent->GetClientModVer(), OurMod, OurMod.GetLength()) != 0)
			strDetail.Format(L"invalid case (%s != %s)", parent->GetClientModVer(), OurMod);
		//but uses a wrong eMule version (detailed)
		else if(parent->GetRealVersion() != ((CemuleApp::m_nVersionMjr<<17)|(CemuleApp::m_nVersionMin<<10)|(CemuleApp::m_nVersionUpd<<7)|(UINT(DBG_VERSION_BUILD))) )
		{
			const UINT Maj = (parent->GetRealVersion() >> 17) & 0x7F;
			const UINT Min = (parent->GetRealVersion() >> 10) & 0x7F;
			const UINT Up  = (parent->GetRealVersion() >>  7) & 0x07;
			const UINT Dbg = (parent->GetRealVersion()) & 0x7F;
			strDetail.Format(L"invalid version+ (%u.%u.%u.%u != %u.%u.%u.%u)", Maj, Min, Up, Dbg, VERSION_MJR, VERSION_MIN, VERSION_UPDATE, DBG_VERSION_BUILD);
		}

		if(!strDetail.IsEmpty())
		{
			SetBadForThisSession(AT_MODTHIEF, strDetail);
			return;
		}
	}
	ClearBadForThisSession(AT_MODTHIEF);
}

CString	CAntiLeechData::GetAntiLeechDataString(const bool bBasics) const
{
	CString reason = L"";
	if(bBadForThisSession != 0)
	{
		reason = L"Very bad: ";
		if(bBadForThisSession & AT_NICKTHIEF)
			reason.Append(L"NickThief ");
		if(bBadForThisSession & AT_MODTHIEF)
			reason.Append(L"ModThief ");
		if(bBadForThisSession & AT_FILEFAKER)
			reason.Append(L"FileFaker ");
		if(bBadForThisSession & AT_UDPFNFFAKER)
			reason.Append(L"UDPFNFFaker ");
		if(!bBasics)
			reason.Append(L"|| "); //just a separator
	}

	if(!bBasics)
	{
		reason.Append(L"\nAnalyzer:\n");
		reason.AppendFormat(L"UL: Full: %s | Part: %s | Rare: %s\n", CastItoXBytes(GetDownloadedFull(), false, false), CastItoXBytes(GetDownloadedPartial(), false, false), CastItoXBytes(GetDownloadedRare(), false, false));
		reason.AppendFormat(L"DL: Full: %s | Part: %s | Rare: %s\n", CastItoXBytes(GetUploadedFull(), false, false), CastItoXBytes(GetUploadedPartial(), false, false), CastItoXBytes(GetUploadedRare(), false, false));
	}
	else
	{
		//number of chunks we downloaded
		const uint64 chunksDL = uint64(double(GetDownloadedTotal()/PARTSIZE) + AT_ROUNDOFFSET);
		//number of chunks we upped
		const uint64 chunksUL = uint64(double(GetUploadedTotal()/PARTSIZE) + AT_ROUNDOFFSET);

		//[SEPPL] - Don't let the score fall too low because of UL/DL
		if(parent
			&& (!parent->GetRequestFile() || parent->GetDownloadState() > DS_ONQUEUE))
			; //idle :P
		else if(chunksDL < chunksUL)
			reason.AppendFormat(L"Bad UL/DL ratio (%I64u/%I64u)", chunksDL, chunksUL);
	}

	if(!bBasics || IsFastAskClient())
		reason.AppendFormat(L"Reask: %u @ %s avg\n", GetReaskCount(), CastSecondsToHM(GetAvgReaskTime()/1000));
	if(!bBasics || GetSpams() > 0)
		reason.AppendFormat(L"Spams: %i\n", GetSpams());
	if(!bBasics || IsXSExploiter())
		reason.AppendFormat(L"XS: %u | %u\n", GetDataStruct()->uXSAsks, GetDataStruct()->uXSAnsw);
	if(!bBasics || IsXSSpammer())
		reason.AppendFormat(L"FastXS: %i\n", GetDataStruct()->iFastXSAsks);

	if(bBasics)
		reason.Replace(L"\n", L" "); //try to get a single line with spacing

	return reason;
}

//////////////////////////////////////////////////////////////////////////
// LIST

CAntiLeechDataList::CAntiLeechDataList()
{
	memset(m_uiBadActions, 0, sizeof(m_uiBadActions));
	m_dwLastSaved = ::GetTickCount();
	m_mapClients.InitHashTable(7411);
	CAntiLeechData::CreateAntiNickThiefTag();
	CAntiLeechData::m_sMyVersion.Format(L"eMule v%u.%u%c", VERSION_MJR, VERSION_MIN, _T('a') + VERSION_UPDATE);
	LoadList();
}

CAntiLeechDataList::~CAntiLeechDataList()
{
	SaveList();
	CAntiLeechData* cur_credit;
	CCKey tmpkey(0);
	POSITION pos = m_mapClients.GetStartPosition();
	while (pos)
	{
		m_mapClients.GetNextAssoc(pos, tmpkey, cur_credit);
		delete cur_credit;
	}
	m_mapClients.RemoveAll(); //needed? doesn't hurt either...
}

bool CAntiLeechDataList::LoadList(const CString& path)
{
	bool bTriedWithBackup = false;
	CString strFileName = thePrefs.GetMuleDirectory(EMULE_CONFIGDIR) + ANTILEECH_MET_FILENAME;
	const bool bCustomLoad = !path.IsEmpty();
	if(bCustomLoad)
		strFileName = path;
	const UINT iOpenFlags = CFile::modeRead|CFile::osSequentialScan|CFile::typeBinary|CFile::shareDenyWrite;
	CSafeBufferedFile file;
	CFileException fexp;

loadstart:
	if (!file.Open(strFileName, iOpenFlags, &fexp))
	{
		if (fexp.m_cause != CFileException::fileNotFound)
		{
			if(bTriedWithBackup)
			{
				CString strError(L"Failed to load AntiLeechFile, a new file will be created");
				TCHAR szError[MAX_CFEXP_ERRORMSG];
				if (fexp.GetErrorMessage(szError, ARRSIZE(szError)))
					strError.AppendFormat(L" - %s", szError);
				LogError(LOG_STATUSBAR, L"%s", strError);
				return false;
			}
			else
			{
				bTriedWithBackup = true;
				strFileName.Format(L"%s%s.bak", thePrefs.GetMuleDirectory(EMULE_CONFIGDIR), ANTILEECH_MET_FILENAME);
				goto loadstart; //I hate using gotos but it works...
			}
		}
		return false;
	}
	setvbuf(file.m_pStream, NULL, _IOFBF, 16384);

	try
	{
		uint8 version = file.ReadUInt8();
		if (version != AT_MET_HEADER
			&& version != AT_MET_HEADER_ACCEPTED)
		{
			if(bTriedWithBackup)
			{
				LogWarning(GetResString(IDS_ERR_CREDITFILEOLD));
				file.Close();
				return false;
			}
			else
			{
				bTriedWithBackup = true;
				strFileName.Format(L"%s%s.bak", thePrefs.GetMuleDirectory(EMULE_CONFIGDIR), ANTILEECH_MET_FILENAME);
				goto loadstart; //I hate using gotos but it works...
			}
		}

		// everything is ok, lets see if the backup exists...
		CString strBakFileName;
		strBakFileName.Format(L"%s%s.bak", thePrefs.GetMuleDirectory(EMULE_CONFIGDIR), ANTILEECH_MET_FILENAME);

		DWORD dwBakFileSize = 0;
		BOOL bCreateBackup = TRUE;

		HANDLE hBakFile = ::CreateFile(strBakFileName, GENERIC_READ, FILE_SHARE_READ, NULL,
			OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
		if (hBakFile != INVALID_HANDLE_VALUE)
		{
			// OK, the backup exists, get the size
			dwBakFileSize = ::GetFileSize(hBakFile, NULL); //debug
			if (dwBakFileSize > (DWORD)file.GetLength())
			{
				// the size of the backup was larger then the original file, something is wrong here, don't overwrite old backup..
				bCreateBackup = FALSE;
			}
			//else: backup is smaller or the same size as original file, proceed with copying of file
			::CloseHandle(hBakFile);
		}
		//else: the backup doesn't exist, create it

		if (bCreateBackup)
		{
			file.Close(); // close the file before copying

			if (!::CopyFile(strFileName, strBakFileName, FALSE))
				LogError(L"Failed to make backup of AntiLeechFile");
			else
				bTriedWithBackup = true; //reset - no use to try loading the backup now - no going back :)

			// reopen file
			CFileException fexp;
			if (!file.Open(strFileName, iOpenFlags, &fexp))
			{
				CString strError(L"Failed to load AntiLeechFile, a new file will be created");
				TCHAR szError[MAX_CFEXP_ERRORMSG];
				if (fexp.GetErrorMessage(szError, ARRSIZE(szError)))
					strError.AppendFormat(L" - %s", szError);
				LogError(LOG_STATUSBAR, L"%s", strError);
				return false;
			}
			setvbuf(file.m_pStream, NULL, _IOFBF, 16384);
			file.Seek(1, CFile::begin); //set filepointer behind file version byte
		}

		UINT count = file.ReadUInt32();

		const DWORD dwExpired = time(NULL) - 12960000; // today - 150 day
		UINT cDeleted = 0;
		for (UINT i = 0; i < count; ++i)
		{
			CAntiLeechData* newcredits = new CAntiLeechData(&file);

			//Too old? Delete 'em!
			if (newcredits->GetLastSeen() < dwExpired || newcredits->IsEmpty())
			{
				++cDeleted;
				delete newcredits;
				continue;
			}

//>>> .met-Merger
			if(bCustomLoad)
			{
				CAntiLeechData* tmpcredits = NULL;
				if(m_mapClients.Lookup(CCKey(newcredits->GetKey()), tmpcredits))
				{
					DebugLogWarning(L"DBG: WARNING - Found duplicate credits for %s", md4str(newcredits->GetKey()));
					//choose the "better one" - we default to the older one :)
					if(newcredits->GetLastSeen() > tmpcredits->GetLastSeen())
					{
						++cDeleted;
						delete newcredits;
						continue;
					}
				}
			}
//<<< .met-Merger

			m_mapClients.SetAt(CCKey(newcredits->GetKey()), newcredits);
		}

		if (cDeleted>0)
			AddLogLine(false, L"AntiLeechFile loaded, %u clients are known, %i clients deleted (not seen within 150 days or empty)", count-cDeleted,cDeleted);
		else
			AddLogLine(false, L"AntiLeechFile loaded, %u clients are known", count);
		file.Close();
		return true;
	}
	catch(CFileException* error)
	{
		if(bTriedWithBackup)
		{
			if (error->m_cause == CFileException::endOfFile)
				LogError(LOG_STATUSBAR, L"Error: AntiLeechFile is corrupted and will be replaced!");
			else
			{
				TCHAR buffer[MAX_CFEXP_ERRORMSG];
				error->GetErrorMessage(buffer, ARRSIZE(buffer));
				LogError(LOG_STATUSBAR, L"Unexpected file error while reading AntiLeechFile: %s", buffer);
			}
			error->Delete();
			file.Close();
			return false;
		}
		else
		{
			error->Delete();
			file.Close();
			bTriedWithBackup = true;
			strFileName.Format(L"%s%s.bak", thePrefs.GetMuleDirectory(EMULE_CONFIGDIR), ANTILEECH_MET_FILENAME);
			goto loadstart; //I hate using gotos but it works...
		}
	}
	//return false; //should never be reached
}

void CAntiLeechDataList::SaveList()
{
	if (thePrefs.GetLogFileSaving())
		AddDebugLogLine(false, L"Saving AntiLeechFile \"%s\"", ANTILEECH_MET_FILENAME);
	m_dwLastSaved = ::GetTickCount();

	const CString name = thePrefs.GetMuleDirectory(EMULE_CONFIGDIR) + ANTILEECH_MET_FILENAME;
	CSafeBufferedFile file;
	CFileException fexp;
	if (!file.Open(name, CFile::modeWrite|CFile::modeCreate|CFile::typeBinary|CFile::shareDenyWrite, &fexp))
	{
		CString strError(L"Failed to save AntiLeechFile");
		TCHAR szError[MAX_CFEXP_ERRORMSG];
		if (fexp.GetErrorMessage(szError, ARRSIZE(szError)))
			strError.AppendFormat(L" - %s", szError);
		LogError(LOG_STATUSBAR, L"%s", strError);
		return;
	}

	setvbuf(file.m_pStream, NULL, _IOFBF, 16384);

	UINT count = 0;
	CAntiLeechData* cur_credit;
	CCKey tempkey(0);
	POSITION pos = m_mapClients.GetStartPosition();
	while (pos)
	{
		m_mapClients.GetNextAssoc(pos, tempkey, cur_credit);
		if (!cur_credit->IsEmpty())
			++count;
	}

	try
	{
		file.WriteUInt8(AT_MET_HEADER);
		file.WriteUInt32(count);
		pos = m_mapClients.GetStartPosition();
		while (pos)
		{
			m_mapClients.GetNextAssoc(pos, tempkey, cur_credit);
			if (!cur_credit->IsEmpty())
				cur_credit->WriteToFile(&file);
		}

		if (thePrefs.GetCommitFiles() >= 2
			|| (thePrefs.GetCommitFiles() >= 1 && !theApp.emuledlg->IsRunning()))
			file.Flush();
	}
	catch(CFileException* error)
	{
		CString strError(L"Failed to save AntiLeechFile");
		TCHAR szError[MAX_CFEXP_ERRORMSG];
		if (error->GetErrorMessage(szError, ARRSIZE(szError)))
			strError.AppendFormat(L" - %s", szError);
		LogError(LOG_STATUSBAR, L"%s", strError);
		error->Delete();
	}
	file.Close();
}

CAntiLeechData* CAntiLeechDataList::GetData(const uchar* key)
{
	CAntiLeechData* result;
	CCKey tkey(key);
	if (!m_mapClients.Lookup(tkey, result))
	{
		result = new CAntiLeechData(key);
		m_mapClients.SetAt(CCKey(result->GetKey()), result);
	}
	result->SetLastSeen();
	return result;
}

void CAntiLeechDataList::Process()
{
	if (::GetTickCount() - m_dwLastSaved > MIN2MS(18))
		SaveList();
}
void CAntiLeechData::SetParent(CUpDownClient* client)
{
	parent = client;
	//if the client has credits...
	if(!parent || parent->Credits())
		return;

	//... but we have no stats about him...
	if(GetUploadedTotal() == 0
		&& GetDownloadedTotal() == 0)
	{
		//... then we add the credits' values to the antileechdata
		//take it as "normal" data, not partial, not rare...
		AddUploaded(parent->Credits()->GetUploadedTotal(), false, AT_RAREFILE+1);
		AddDownloaded(parent->Credits()->GetDownloadedTotal(), false, AT_RAREFILE+1);
	}
}

#endif
// <------- ClientAnalyzer
